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 *
|
||||
|
||||
|
||||
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 =
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Reference in a new issue