More fractal additions

This commit is contained in:
Grant Sanderson 2017-01-17 17:14:32 -08:00
parent 0835d52bb4
commit b0e53b8f2a
3 changed files with 296 additions and 15 deletions

View file

@ -25,6 +25,21 @@ from mobject.svg_mobject import *
from mobject.tex_mobject import *
class Britain(SVGMobject):
CONFIG = {
"file_name" : "Britain.svg",
"stroke_width" : 1,
"stroke_color" : WHITE,
"fill_opacity" : 0,
"height" : 5,
"mark_paths_closed" : True,
}
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.scale_to_fit_height(self.height)
self.center()
class KochTest(Scene):
def construct(self):
koch = KochCurve(order = 5, stroke_width = 2)
@ -51,6 +66,27 @@ class SierpinskiTest(Scene):
# self.play(sierp.scale, 2, sierp.get_top())
# self.dither(3)
class FractalCreation(Scene):
CONFIG = {
"fractal_class" : PentagonalFractal,
"max_order" : 6,
"path_arc" : np.pi/6,
"submobject_mode" : "lagged_start"
}
def construct(self):
fractal = self.fractal_class(order = 0)
self.play(FadeIn(fractal))
for order in range(1, self.max_order+1):
new_fractal = self.fractal_class(order = order)
self.play(Transform(
fractal, new_fractal,
path_arc = self.path_arc,
submobject_mode = self.submobject_mode,
run_time = 2
))
self.dither()
self.dither()
###################################
@ -122,7 +158,7 @@ class ZoomInOnFractal(PiCreatureScene):
class WhatAreFractals(TeacherStudentsScene):
def construct(self):
self.student_says(
"But what \\emph{is} a fractal",
"But what \\emph{is} a fractal?",
student_index = 2,
width = 6
)
@ -150,13 +186,186 @@ class WhatAreFractals(TeacherStudentsScene):
self.play(self.get_teacher().change_mode, "happy")
self.dither(2)
class IntroduceVonKochCurve(Scene):
CONFIG = {
"order" : 5,
"stroke_width" : 3,
}
def construct(self):
snowflake = self.get_snowflake()
name = TextMobject("Von Koch Snowflake")
name.to_edge(UP)
self.play(ShowCreation(snowflake, run_time = 3))
self.play(Write(name, run_time = 2))
curve = self.isolate_one_curve(snowflake)
self.dither()
self.zoom_in_on(curve)
def get_snowflake(self):
triangle = RegularPolygon(n = 3, start_angle = np.pi/2)
triangle.scale_to_fit_height(4)
curves = VGroup(*[
KochCurve(
order = self.order,
stroke_width = self.stroke_width
)
for x in range(3)
])
for index, curve in enumerate(curves):
width = curve.get_width()
curve.move_to(
(np.sqrt(3)/6)*width*UP, DOWN
)
curve.rotate(-index*2*np.pi/3)
curves.gradient_highlight(BLUE, WHITE, BLUE)
return curves
def isolate_one_curve(self, snowflake):
self.play(*[
ApplyMethod(curve.shift, curve.get_center()/2)
for curve in snowflake
])
self.dither()
self.play(
snowflake.scale, 2.1,
snowflake.next_to, UP, DOWN
)
self.remove(*snowflake[1:])
return snowflake[0]
def zoom_in_on(self, curve):
larger_curve = KochCurve(order = self.order+1)
larger_curve.replace(curve)
larger_curve.scale(3, about_point = curve.get_corner(DOWN+LEFT))
larger_curve.gradient_highlight(
curve[0].get_color(),
curve[-1].get_color(),
)
self.play(Transform(curve, larger_curve, run_time = 2))
n_parts = len(curve.split())
sub_portion = VGroup(*curve[:n_parts/4])
self.play(
sub_portion.highlight, YELLOW,
rate_func = there_and_back
)
self.dither()
class IntroduceSierpinskiTriangle(PiCreatureScene):
CONFIG = {
"order" : 7,
}
def construct(self):
sierp = Sierpinski(order = self.order)
sierp.save_state()
self.play(FadeIn(
sierp,
run_time = 2,
submobject_mode = "lagged_start"
))
self.dither()
self.play(
self.pi_creature.change_mode, "pondering",
*[
ApplyMethod(submob.shift, submob.get_center())
for submob in sierp
]
)
self.dither()
for submob in sierp:
self.play(sierp.shift, -submob.get_center())
self.dither()
self.play(sierp.restore)
self.change_mode("happy")
self.dither()
class SelfSimilarFractalsAsSubset(Scene):
CONFIG = {
"fractal_width" : 1.5
}
def construct(self):
self.add_self_similar_fractals()
self.add_general_fractals()
def add_self_similar_fractals(self):
fractals = VGroup(
DiamondFractal(order = 5),
KochSnowFlake(order = 3),
Sierpinski(order = 5),
)
for submob in fractals:
submob.scale_to_fit_width(self.fractal_width)
fractals.arrange_submobjects(RIGHT)
fractals[-1].next_to(VGroup(*fractals[:-1]), DOWN)
title = TextMobject("Self-similar fractals")
title.next_to(fractals, UP)
small_rect = Rectangle()
small_rect.replace(VGroup(fractals, title), stretch = True)
small_rect.scale_in_place(1.2)
self.small_rect = small_rect
group = VGroup(fractals, title, small_rect)
group.to_corner(UP+LEFT, buff = 2*MED_BUFF)
self.play(
Write(title),
ShowCreation(fractals),
run_time = 3
)
self.play(ShowCreation(small_rect))
self.dither()
def add_general_fractals(self):
big_rectangle = Rectangle(
width = 2*SPACE_WIDTH - 2*MED_BUFF,
height = 2*SPACE_HEIGHT - 2*MED_BUFF,
)
title = TextMobject("Fractals")
title.scale(1.5)
title.next_to(ORIGIN, RIGHT, buff = LARGE_BUFF)
title.to_edge(UP, buff = 2*MED_BUFF)
britain = Britain()
britain.next_to(self.small_rect, RIGHT)
britain.shift(2*DOWN)
randy = Randolph().flip().scale(1.4)
randy.next_to(britain, buff = SMALL_BUFF)
randy.generate_target()
randy.target.change_mode("pleading")
fractalify(randy.target, order = 2)
self.play(
ShowCreation(big_rectangle),
Write(title),
)
self.play(ShowCreation(britain), run_time = 5)
self.play(
britain.set_stroke, BLACK, 0,
britain.set_fill, BLUE, 1,
)
self.play(FadeIn(randy))
self.play(MoveToTarget(randy, run_time = 2))
self.dither(2)
class ConstrastSmoothAndFractal(Scene):
def construct(self):
v_line = Line(UP, DOWN).scale(SPACE_HEIGHT)
smooth = TextMobject("Smooth")
smooth.shift(SPACE_WIDTH*LEFT/2)
fractal = TextMobject("Fractal")
fractal.shift(SPACE_WIDTH*RIGHT/2)
VGroup(smooth, fractal).to_edge(UP)
self.add(v_line, smooth, fractal)
britain = Britain()
anchors = britain.get_anchors()
smooth_britain =

View file

@ -62,6 +62,8 @@ class SVGMobject(VMobject):
result.append(self.rect_to_mobject(element))
elif element.tagName == 'circle':
result.append(self.circle_to_mobject(element))
elif element.tagName == 'polygon':
result.append(self.polygon_to_mobject(element))
else:
warnings.warn("Unknown element type: " + element.tagName)
result = filter(lambda m : m is not None, result)
@ -86,6 +88,14 @@ class SVGMobject(VMobject):
self.ref_to_element[ref]
)
def polygon_to_mobject(self, polygon_element):
#TODO, This seems hacky...
path_string = polygon_element.getAttribute("points")
for digit in string.digits:
path_string = path_string.replace(" " + digit, " L" + digit)
path_string = "M" + path_string
return self.path_string_to_mobject(path_string)
# <circle class="st1" cx="143.8" cy="268" r="22.6"/>
def circle_to_mobject(self, circle_element):

View file

@ -14,6 +14,50 @@ def rotate(points, angle = np.pi, axis = OUT):
points = np.dot(points, np.transpose(matrix))
return points
def fractalify(vmobject, order = 3, *args, **kwargs):
for x in range(order):
fractalification_iteration(vmobject)
return vmobject
def fractalification_iteration(vmobject,
dimension = 1.05,
num_inserted_anchors_range = range(1, 4)
):
num_points = vmobject.get_num_points()
if num_points > 0:
# original_anchors = vmobject.get_anchors()
original_anchors = [
vmobject.point_from_proportion(x)
for x in np.linspace(0, 0.99, num_points)
]
new_anchors = []
for p1, p2, in zip(original_anchors, original_anchors[1:]):
num_inserts = random.choice(num_inserted_anchors_range)
inserted_points = [
interpolate(p1, p2, alpha)
for alpha in np.linspace(0, 1, num_inserts+2)[1:-1]
]
mass_scaling_factor = 1./(num_inserts+1)
length_scaling_factor = mass_scaling_factor**(1./dimension)
target_length = np.linalg.norm(p1-p2)*length_scaling_factor
curr_length = np.linalg.norm(p1-p2)*mass_scaling_factor
#offset^2 + curr_length^2 = target_length^2
offset_len = np.sqrt(target_length**2 - curr_length**2)
unit_vect = (p1-p2)/np.linalg.norm(p1-p2)
offset_unit_vect = rotate_vector(unit_vect, np.pi/2)
inserted_points = [
point + u*offset_len*offset_unit_vect
for u, point in zip(it.cycle([-1, 1]), inserted_points)
]
new_anchors += [p1] + inserted_points
new_anchors.append(original_anchors[-1])
vmobject.set_points_as_corners(new_anchors)
vmobject.submobjects = [
fractalification_iteration(submob, dimension, num_inserted_anchors_range)
for submob in vmobject.submobjects
]
return vmobject
class SelfSimilarFractal(VMobject):
CONFIG = {
@ -87,15 +131,31 @@ class DiamondFractal(SelfSimilarFractal):
VGroup(*subparts).rotate(np.pi/4)
class PentagonalFractal(SelfSimilarFractal):
CONFIG = {
"num_subparts" : 5,
"colors" : [MAROON_B, YELLOW, RED]
}
def get_seed_shape(self):
return RegularPolygon(n = 5, start_angle = np.pi/2)
def arrange_subparts(self, *subparts):
phi = (1 + np.sqrt(5))/2
for x, part in enumerate(subparts):
part.shift(0.95*part.get_height()*UP)
part.rotate(2*np.pi*x/5)
######## Space filling curves ############
class SpaceFillingCurve(VMobject):
class FractalCurve(VMobject):
CONFIG = {
"radius" : 3,
"order" : 5,
"colors" : [RED, GREEN],
"monochromatic" : False,
"stroke_width" : 2,
"stroke_width" : 3,
"propogate_style_to_family" : True,
}
def generate_points(self):
@ -107,13 +167,15 @@ class SpaceFillingCurve(VMobject):
corner = VMobject()
corner.set_points_as_corners(triplet)
self.add(corner)
def init_colors(self):
self.gradient_highlight(*self.colors)
def get_anchor_points(self):
raise Exception("Not implemented")
class LindenmayerCurve(SpaceFillingCurve):
class LindenmayerCurve(FractalCurve):
CONFIG = {
"axiom" : "A",
"rule" : {},
@ -154,7 +216,7 @@ class LindenmayerCurve(SpaceFillingCurve):
return np.array(result) - center_of_mass(result)
class SelfSimilarSpaceFillingCurve(SpaceFillingCurve):
class SelfSimilarSpaceFillingCurve(FractalCurve):
CONFIG = {
"offsets" : [],
#keys must awkwardly be in string form...
@ -339,7 +401,7 @@ class SierpinskiCurve(LindenmayerCurve):
class KochSnowFlake(LindenmayerCurve):
CONFIG = {
"colors" : [BLUE_D, WHITE],
"colors" : [BLUE_D, WHITE, BLUE_D],
"axiom" : "A--A--A--",
"rule" : {
"A" : "A+A--A+A"
@ -374,7 +436,7 @@ class StellarCurve(LindenmayerCurve):
"angle" : 2*np.pi/5,
}
class SnakeCurve(SpaceFillingCurve):
class SnakeCurve(FractalCurve):
CONFIG = {
"start_color" : BLUE,
"end_color" : YELLOW,
@ -409,7 +471,7 @@ class SpaceFillingCurveScene(Scene):
curve_class_name, order_str = arg_str.split()
space_filling_curves = dict([
(Class.__name__, Class)
for Class in get_all_descendent_classes(SpaceFillingCurve)
for Class in get_all_descendent_classes(FractalCurve)
])
if curve_class_name not in space_filling_curves:
raise Exception(