mirror of
https://github.com/3b1b/manim.git
synced 2025-09-01 00:48:45 +00:00
More fractal additions
This commit is contained in:
parent
0835d52bb4
commit
b0e53b8f2a
3 changed files with 296 additions and 15 deletions
|
@ -25,6 +25,21 @@ from mobject.svg_mobject import *
|
||||||
from mobject.tex_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):
|
class KochTest(Scene):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
koch = KochCurve(order = 5, stroke_width = 2)
|
koch = KochCurve(order = 5, stroke_width = 2)
|
||||||
|
@ -51,6 +66,27 @@ class SierpinskiTest(Scene):
|
||||||
# self.play(sierp.scale, 2, sierp.get_top())
|
# self.play(sierp.scale, 2, sierp.get_top())
|
||||||
# self.dither(3)
|
# 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):
|
class WhatAreFractals(TeacherStudentsScene):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
self.student_says(
|
self.student_says(
|
||||||
"But what \\emph{is} a fractal",
|
"But what \\emph{is} a fractal?",
|
||||||
student_index = 2,
|
student_index = 2,
|
||||||
width = 6
|
width = 6
|
||||||
)
|
)
|
||||||
|
@ -150,13 +186,186 @@ class WhatAreFractals(TeacherStudentsScene):
|
||||||
self.play(self.get_teacher().change_mode, "happy")
|
self.play(self.get_teacher().change_mode, "happy")
|
||||||
self.dither(2)
|
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 =
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,8 @@ class SVGMobject(VMobject):
|
||||||
result.append(self.rect_to_mobject(element))
|
result.append(self.rect_to_mobject(element))
|
||||||
elif element.tagName == 'circle':
|
elif element.tagName == 'circle':
|
||||||
result.append(self.circle_to_mobject(element))
|
result.append(self.circle_to_mobject(element))
|
||||||
|
elif element.tagName == 'polygon':
|
||||||
|
result.append(self.polygon_to_mobject(element))
|
||||||
else:
|
else:
|
||||||
warnings.warn("Unknown element type: " + element.tagName)
|
warnings.warn("Unknown element type: " + element.tagName)
|
||||||
result = filter(lambda m : m is not None, result)
|
result = filter(lambda m : m is not None, result)
|
||||||
|
@ -86,6 +88,14 @@ class SVGMobject(VMobject):
|
||||||
self.ref_to_element[ref]
|
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"/>
|
# <circle class="st1" cx="143.8" cy="268" r="22.6"/>
|
||||||
|
|
||||||
def circle_to_mobject(self, circle_element):
|
def circle_to_mobject(self, circle_element):
|
||||||
|
|
|
@ -14,6 +14,50 @@ def rotate(points, angle = np.pi, axis = OUT):
|
||||||
points = np.dot(points, np.transpose(matrix))
|
points = np.dot(points, np.transpose(matrix))
|
||||||
return points
|
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):
|
class SelfSimilarFractal(VMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
|
@ -87,15 +131,31 @@ class DiamondFractal(SelfSimilarFractal):
|
||||||
VGroup(*subparts).rotate(np.pi/4)
|
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 ############
|
######## Space filling curves ############
|
||||||
|
|
||||||
class SpaceFillingCurve(VMobject):
|
class FractalCurve(VMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"radius" : 3,
|
"radius" : 3,
|
||||||
"order" : 5,
|
"order" : 5,
|
||||||
"colors" : [RED, GREEN],
|
"colors" : [RED, GREEN],
|
||||||
"monochromatic" : False,
|
"monochromatic" : False,
|
||||||
"stroke_width" : 2,
|
"stroke_width" : 3,
|
||||||
|
"propogate_style_to_family" : True,
|
||||||
}
|
}
|
||||||
|
|
||||||
def generate_points(self):
|
def generate_points(self):
|
||||||
|
@ -107,13 +167,15 @@ class SpaceFillingCurve(VMobject):
|
||||||
corner = VMobject()
|
corner = VMobject()
|
||||||
corner.set_points_as_corners(triplet)
|
corner.set_points_as_corners(triplet)
|
||||||
self.add(corner)
|
self.add(corner)
|
||||||
|
|
||||||
|
def init_colors(self):
|
||||||
self.gradient_highlight(*self.colors)
|
self.gradient_highlight(*self.colors)
|
||||||
|
|
||||||
def get_anchor_points(self):
|
def get_anchor_points(self):
|
||||||
raise Exception("Not implemented")
|
raise Exception("Not implemented")
|
||||||
|
|
||||||
|
|
||||||
class LindenmayerCurve(SpaceFillingCurve):
|
class LindenmayerCurve(FractalCurve):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"axiom" : "A",
|
"axiom" : "A",
|
||||||
"rule" : {},
|
"rule" : {},
|
||||||
|
@ -154,7 +216,7 @@ class LindenmayerCurve(SpaceFillingCurve):
|
||||||
return np.array(result) - center_of_mass(result)
|
return np.array(result) - center_of_mass(result)
|
||||||
|
|
||||||
|
|
||||||
class SelfSimilarSpaceFillingCurve(SpaceFillingCurve):
|
class SelfSimilarSpaceFillingCurve(FractalCurve):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"offsets" : [],
|
"offsets" : [],
|
||||||
#keys must awkwardly be in string form...
|
#keys must awkwardly be in string form...
|
||||||
|
@ -339,7 +401,7 @@ class SierpinskiCurve(LindenmayerCurve):
|
||||||
|
|
||||||
class KochSnowFlake(LindenmayerCurve):
|
class KochSnowFlake(LindenmayerCurve):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"colors" : [BLUE_D, WHITE],
|
"colors" : [BLUE_D, WHITE, BLUE_D],
|
||||||
"axiom" : "A--A--A--",
|
"axiom" : "A--A--A--",
|
||||||
"rule" : {
|
"rule" : {
|
||||||
"A" : "A+A--A+A"
|
"A" : "A+A--A+A"
|
||||||
|
@ -374,7 +436,7 @@ class StellarCurve(LindenmayerCurve):
|
||||||
"angle" : 2*np.pi/5,
|
"angle" : 2*np.pi/5,
|
||||||
}
|
}
|
||||||
|
|
||||||
class SnakeCurve(SpaceFillingCurve):
|
class SnakeCurve(FractalCurve):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"start_color" : BLUE,
|
"start_color" : BLUE,
|
||||||
"end_color" : YELLOW,
|
"end_color" : YELLOW,
|
||||||
|
@ -409,7 +471,7 @@ class SpaceFillingCurveScene(Scene):
|
||||||
curve_class_name, order_str = arg_str.split()
|
curve_class_name, order_str = arg_str.split()
|
||||||
space_filling_curves = dict([
|
space_filling_curves = dict([
|
||||||
(Class.__name__, Class)
|
(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:
|
if curve_class_name not in space_filling_curves:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
|
|
Loading…
Add table
Reference in a new issue