From b097620389e9268a507f5cb09cad9633136a1369 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 27 Jan 2017 13:23:17 -0800 Subject: [PATCH] Finished cube animation for eoc/chapter3 --- animation/simple_animations.py | 4 +- animation/transform.py | 20 +++-- eoc/chapter2.py | 2 +- eoc/chapter3.py | 149 +++++++++++++++++++++++++++------ scene/scene.py | 7 +- topics/characters.py | 2 +- topics/fractals.py | 5 ++ 7 files changed, 151 insertions(+), 38 deletions(-) diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 70b6c813..b8b0c009 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -321,8 +321,8 @@ class Succession(Animation): #require leveraging implementation details of #Animations, and knowing about the different #struction of Transform? - if hasattr(curr_anim, "ending_mobject"): - curr_anim.mobject.align_data(curr_anim.ending_mobject) + if hasattr(curr_anim, "target_mobject"): + curr_anim.mobject.align_data(curr_anim.target_mobject) curr_anim.starting_mobject = curr_anim.mobject.copy() curr_anim.update(scaled_alpha - index) self.last_index = index diff --git a/animation/transform.py b/animation/transform.py index 055a581e..9ea71d55 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -15,16 +15,18 @@ class Transform(Animation): "path_arc" : 0, "path_func" : None, "submobject_mode" : "all_at_once", + "replace_mobject_with_target_in_scene" : False, } - def __init__(self, mobject, ending_mobject, **kwargs): - #Copy ending_mobject so as to not mess with caller - ending_mobject = ending_mobject.copy() + def __init__(self, mobject, target_mobject, **kwargs): + #Copy target_mobject so as to not mess with caller + self.original_target_mobject = target_mobject + target_mobject = target_mobject.copy() digest_config(self, kwargs, locals()) - mobject.align_data(ending_mobject) + mobject.align_data(target_mobject) self.init_path_func() Animation.__init__(self, mobject, **kwargs) - self.name += "To" + str(ending_mobject) + self.name += "To" + str(target_mobject) def update_config(self, **kwargs): Animation.update_config(self, **kwargs) @@ -42,7 +44,7 @@ class Transform(Animation): def get_all_families_zipped(self): return zip(*map( Mobject.submobject_family, - [self.mobject, self.starting_mobject, self.ending_mobject] + [self.mobject, self.starting_mobject, self.target_mobject] )) def update_submobject(self, submob, start, end, alpha): @@ -235,13 +237,13 @@ class TransformAnimations(Transform): if start_anim.starting_mobject.get_num_points() != end_anim.starting_mobject.get_num_points(): start_anim.starting_mobject.align_data(end_anim.starting_mobject) for anim in start_anim, end_anim: - if hasattr(anim, "ending_mobject"): - anim.starting_mobject.align_data(anim.ending_mobject) + if hasattr(anim, "target_mobject"): + anim.starting_mobject.align_data(anim.target_mobject) Transform.__init__(self, start_anim.mobject, end_anim.mobject, **kwargs) #Rewire starting and ending mobjects start_anim.mobject = self.starting_mobject - end_anim.mobject = self.ending_mobject + end_anim.mobject = self.target_mobject def update(self, alpha): self.start_anim.update(alpha) diff --git a/eoc/chapter2.py b/eoc/chapter2.py index 839fdf4a..314a6a51 100644 --- a/eoc/chapter2.py +++ b/eoc/chapter2.py @@ -101,7 +101,7 @@ class Car(SVGMobject): class MoveCar(ApplyMethod): def __init__(self, car, target_point, **kwargs): ApplyMethod.__init__(self, car.move_to, target_point, **kwargs) - displacement = self.ending_mobject.get_right()-self.starting_mobject.get_right() + displacement = self.target_mobject.get_right()-self.starting_mobject.get_right() distance = np.linalg.norm(displacement) tire_radius = car.get_tires()[0].get_width()/2 self.total_tire_radians = -distance/tire_radius diff --git a/eoc/chapter3.py b/eoc/chapter3.py index 076a7a5a..052d2811 100644 --- a/eoc/chapter3.py +++ b/eoc/chapter3.py @@ -621,12 +621,21 @@ class NudgeSideLengthOfCube(Scene): "pose_angle" : np.pi/12, "pose_axis" : UP+RIGHT, "small_piece_scaling_factor" : 0.7, - "is_recursing" : False, + "allow_recursion" : True, } def construct(self): + self.states = dict() + if self.allow_recursion: + self.alt_scene = self.__class__( + skip_animations = True, + allow_recursion = False, + dx = self.alt_dx, + ) + self.add_title() self.introduce_cube() self.write_df_equation() + self.write_derivative() def add_title(self): title = TexMobject("f(x) = x^3") @@ -671,6 +680,8 @@ class NudgeSideLengthOfCube(Scene): self.add(dv_pieces) self.play(GrowFromCenter(dx_brace)) self.dither() + for piece in dv_pieces: + piece.on_cube_state = piece.copy() self.play(*[ ApplyMethod( piece.shift, @@ -683,6 +694,7 @@ class NudgeSideLengthOfCube(Scene): self.dither() self.cube = cube + self.dx_brace = dx_brace self.faces, self.bars, self.corner_cube = [ VGroup(*[ piece @@ -713,7 +725,7 @@ class NudgeSideLengthOfCube(Scene): df_equation.to_edge(UP) faces_brace = Brace(faces, DOWN) - faces_brace_text = faces_brace.get_text("$3x^2", "\\, dx$") + derivative = faces_brace.get_text("$3x^2", "\\, dx$") extras_brace = Brace(VGroup(bars, corner_cube), DOWN) ignore_text = extras_brace.get_text( "Multiple \\\\ of $dx^2$" @@ -725,19 +737,26 @@ class NudgeSideLengthOfCube(Scene): self.play(*map(Write, [df, equals])) self.grab_pieces(self.faces, faces) self.dither() - # self.shrink_dx() + self.shrink_dx("Faces are introduced") face = self.faces[0] face.save_state() self.play(face.shift, SPACE_WIDTH*RIGHT) - x_squared_dx.move_to(self.faces[0]) + x_squared_dx.next_to(face, LEFT) self.play(Write(x_squared_dx, run_time = 1)) - for submob in x_squared_dx: - self.play(submob.highlight, RED, rate_func = there_and_back) - self.play(submob.highlight, RED, rate_func = there_and_back) self.dither() + for submob, sides in zip(x_squared_dx, [face[0], VGroup(*face[1:])]): + self.play( + submob.highlight, RED, + sides.highlight, RED, + rate_func = there_and_back + ) + self.dither() self.play( face.restore, - Transform(x_squared_dx, faces_brace_text), + Transform( + x_squared_dx, derivative, + replace_mobject_with_target_in_scene = True + ), GrowFromCenter(faces_brace) ) self.dither() @@ -757,6 +776,79 @@ class NudgeSideLengthOfCube(Scene): ]) self.dither() + self.df_equation = df_equation + self.derivative = derivative + + def write_derivative(self): + df, equals, faces, plus1, bars, plus2, corner_cube = self.df_equation + df = df.copy() + equals = equals.copy() + df_equals = VGroup(df, equals) + + derivative = self.derivative.copy() + dx = derivative[1] + + extra_stuff = TexMobject("+(\\dots)", "dx^2") + dx_squared = extra_stuff[1] + + derivative.generate_target() + derivative.target.shift(2*DOWN) + extra_stuff.next_to(derivative.target) + self.play( + MoveToTarget(derivative), + df_equals.next_to, derivative.target[0], LEFT, + df_equals.shift, 0.07*DOWN + ) + self.play(Write(extra_stuff)) + self.dither() + + frac_line = TexMobject("-") + frac_line.replace(df) + extra_stuff.generate_target() + extra_stuff.target.next_to(derivative[0]) + frac_line2 = TexMobject("-") + frac_line2.stretch_to_fit_width( + extra_stuff.target[1].get_width() + ) + frac_line2.move_to(extra_stuff.target[1]) + extra_stuff.target[1].next_to(frac_line2, UP, buff = SMALL_BUFF) + dx_below_dx_squared = TexMobject("dx") + dx_below_dx_squared.next_to(frac_line2, DOWN, buff = SMALL_BUFF) + self.play( + FadeIn(frac_line), + FadeIn(frac_line2), + df.next_to, frac_line, UP, SMALL_BUFF, + dx.next_to, frac_line, DOWN, SMALL_BUFF, + MoveToTarget(extra_stuff), + Write(dx_below_dx_squared), + path_arc = -np.pi + ) + self.dither() + inner_dx = VGroup(*dx_squared[:-1]) + self.play( + FadeOut(frac_line2), + FadeOut(dx_below_dx_squared), + dx_squared[-1].highlight, BLACK, + inner_dx.next_to, extra_stuff[0], RIGHT, SMALL_BUFF + ) + self.dither() + self.shrink_dx("Derivative is written", restore = False) + self.play(*[ + ApplyMethod(mob.fade, 0.7) + for mob in extra_stuff, inner_dx + ]) + self.dither(2) + + anims = [] + for mob in list(self.faces)+list(self.bars)+list(self.corner_cube): + vect = mob.get_center()-self.cube.get_center() + anims += [ + mob.shift, -(1./3)*vect + ] + anims += self.dx_brace.shift, 0.7*DOWN + self.play(*anims) + self.dither() + def grab_pieces(self, start_pieces, end_pices, to_write = None): for piece in start_pieces: piece.generate_target() @@ -774,29 +866,38 @@ class NudgeSideLengthOfCube(Scene): *added_anims ) - def shrink_dx(self): - self.mobjects_at_start_of_shrink_dx = self.get_mobjects() - if self.is_recursing: + def shrink_dx(self, state_name, restore = True): + mobjects = self.get_mobjects() + mobjects_with_points = [ + m for m in mobjects + if m.get_num_points() > 0 + ] + #Alt_scene will reach this point, and save copy of self + #in states dict + self.states[state_name] = [ + mob.copy() for mob in mobjects_with_points + ] + if not self.allow_recursion: return - alt_scene = self.__class__( - dx = self.alt_dx, - skip_animations = True, - is_recursing = True - ) - for mob in self.get_mobjects(): - mob.save_state() + if restore: + movers = self.states[state_name] + for mob in movers: + mob.save_state() + self.remove(*mobjects) + else: + movers = mobjects_with_points self.play(*[ Transform(*pair) for pair in zip( - self.get_mobjects(), - alt_scene.mobjects_at_start_of_shrink_dx + movers, + self.alt_scene.states[state_name] ) ]) self.dither() - self.play(*[ - mob.restore - for mob in self.get_mobjects() - ]) + if restore: + self.play(*[m.restore for m in movers]) + self.remove(*movers) + self.mobjects = mobjects def get_cube(self): cube = self.get_prism(self.x, self.x, self.x) diff --git a/scene/scene.py b/scene/scene.py index 20158dcc..2ac2ff12 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -16,7 +16,7 @@ from camera import Camera from tk_scene import TkSceneRoot from mobject import Mobject, VMobject from animation import Animation -from animation.transform import MoveToTarget +from animation.transform import MoveToTarget, Transform class Scene(object): CONFIG = { @@ -241,6 +241,7 @@ class Scene(object): def play(self, *args, **kwargs): if len(args) == 0: warnings.warn("Called Scene.play with no animations") + return if self.skip_animations: kwargs["run_time"] = 0 @@ -267,6 +268,10 @@ class Scene(object): animation.clean_up() if animation.is_remover(): self.remove(animation.mobject) + if isinstance(animation, Transform) : + if animation.replace_mobject_with_target_in_scene: + self.remove(animation.mobject) + self.add(animation.original_target_mobject) return self def get_mobjects_from_last_animation(self): diff --git a/topics/characters.py b/topics/characters.py index d91660cf..1beed673 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -317,7 +317,7 @@ class PiCreatureScene(Scene): last_anim = animations[-1] if last_anim.mobject is self.pi_creature and isinstance(last_anim, Transform): if isinstance(animations[-1], Transform): - animations[-1].ending_mobject.look_at(point_of_interest) + animations[-1].target_mobject.look_at(point_of_interest) return animations new_anim = ApplyMethod(self.pi_creature.look_at, point_of_interest) return list(animations) + [new_anim] diff --git a/topics/fractals.py b/topics/fractals.py index 4eeac34f..6da22322 100644 --- a/topics/fractals.py +++ b/topics/fractals.py @@ -587,6 +587,11 @@ class QuadraticKoch(LindenmayerCurve): } +class QuadraticKochIsland(QuadraticKoch): + CONFIG = { + "axiom" : "A+A+A+A" + } + class StellarCurve(LindenmayerCurve): CONFIG = { "start_color" : RED,