From f0ed90a8845b4f55885029c15ea0d82523a220eb Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Mon, 29 Jan 2018 17:56:30 -0800 Subject: [PATCH 1/6] Minor improvements to some details of Successor implementation --- animation/simple_animations.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/animation/simple_animations.py b/animation/simple_animations.py index c151a326..94dad47d 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -431,20 +431,25 @@ class Succession(Animation): self.current_anim_index = 0 #TODO: What if self.num_anims == 0? self.mobject = Group() - self.jump_to_start_of_anim(0) + self.current_anim_index = 0 + self.current_alpha = 0 + Animation.__init__(self, self.mobject, run_time = run_time, **kwargs) + # Beware: This does NOT take care of updating the subanimation to 0 + # This was important to avoid a pernicious possibility in which subanimations were called + # with update(0) twice, which could in turn call a sub-Succession with update(0) four times, + # continuing exponentially def jump_to_start_of_anim(self, index): + if index != self.current_anim_index: + self.mobject.remove(*self.mobject.submobjects) # Should probably have a cleaner "remove_all" method... + self.mobject.add(self.animations[index].mobject) + for m in self.scene_mobjects_at_time[index].submobjects: + self.mobject.add(m) + self.current_anim_index = index self.current_alpha = self.critical_alphas[index] - self.mobject.remove(*self.mobject.submobjects) # Should probably have a cleaner "remove_all" method... - self.mobject.add(self.animations[index].mobject) - for m in self.scene_mobjects_at_time[index].submobjects: - self.mobject.add(m) - - self.animations[index].update(0) - def update_mobject(self, alpha): i = 0 while self.critical_alphas[i + 1] < alpha: From 426fb33b94c159e5ec76e1a0f28ae1ff2e0fce03 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Mon, 29 Jan 2018 18:12:51 -0800 Subject: [PATCH 2/6] Small improvements --- animation/simple_animations.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 94dad47d..268b7792 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -403,12 +403,17 @@ class Succession(Animation): for anim in animations: anim.update(0) + animations = filter (lambda x : x.run_time != 0, animations) + self.run_times = [anim.run_time for anim in animations] if "run_time" in kwargs: run_time = kwargs.pop("run_time") else: run_time = sum(self.run_times) - self.num_anims = len(animations) #TODO: If this is zero, some special handling below + self.num_anims = len(animations) + if self.num_anims == 0: + # TODO: Handle this; it should be easy enough, but requires some special cases below + print "Warning! Successions with zero animations are not currently handled!" self.animations = animations #Have to keep track of this run_time, because Scene.play #might very well mess with it. @@ -429,10 +434,8 @@ class Succession(Animation): self.current_alpha = 0 self.current_anim_index = 0 #TODO: What if self.num_anims == 0? - - self.mobject = Group() - self.current_anim_index = 0 - self.current_alpha = 0 + self.mobject = self.scene_mobjects_at_time[0] + self.mobject.add(self.animations[0].mobject) Animation.__init__(self, self.mobject, run_time = run_time, **kwargs) From 6b1fb89d16e60634b633523fab5b9f27ff251d98 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Mon, 29 Jan 2018 18:13:11 -0800 Subject: [PATCH 3/6] Incremental progress --- active_projects/WindingNumber.py | 81 +++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 190891c3..3fd9ac13 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -29,6 +29,8 @@ from mobject.svg_mobject import * from mobject.tex_mobject import * from topics.graph_scene import * +import sys + # TODO/WARNING: There's a lot of refactoring and cleanup to be done in this code, # (and it will be done, but first I'll figure out what I'm doing with all this...) # -SR @@ -306,7 +308,7 @@ class OdometerScene(Scene): dashed_line.rotate(-self.dashed_line_angle * TAU, about_point = ORIGIN) self.add(dashed_line) - num_display = DecimalNumber(0) + num_display = DecimalNumber(0, include_background_rectangle = True) num_display.move_to(2 * DOWN) display_val_bias = 0 @@ -428,7 +430,7 @@ def plane_func_from_complex_func(f): def point_func_from_complex_func(f): return lambda (x, y, z): complex_to_R3(f(complex(x, y))) -empty_animation = Animation(Mobject()) +empty_animation = Animation(Mobject(), run_time = 0) def EmptyAnimation(): return empty_animation @@ -440,13 +442,13 @@ class WalkerAnimation(Animation): "coords_to_point" : None } - def __init__(self, walk_func, rev_func, coords_to_point, **kwargs): + def __init__(self, walk_func, rev_func, coords_to_point, scale_factor, **kwargs): self.walk_func = walk_func self.rev_func = rev_func self.coords_to_point = coords_to_point self.compound_walker = VGroup() self.compound_walker.walker = PiCreature(color = RED) - self.compound_walker.walker.scale(0.35) + self.compound_walker.walker.scale(scale_factor) self.compound_walker.arrow = Arrow(ORIGIN, RIGHT) #, buff = 0) self.compound_walker.digest_mobject_attrs() Animation.__init__(self, self.compound_walker, **kwargs) @@ -458,7 +460,8 @@ class WalkerAnimation(Animation): def update_mobject(self, alpha): Animation.update_mobject(self, alpha) cur_x, cur_y = cur_coords = self.walk_func(alpha) - self.mobject.walker.move_to(self.coords_to_point(cur_x, cur_y)) + cur_point = self.coords_to_point(cur_x, cur_y) + self.mobject.walker.move_to(cur_point) rev = self.rev_func(cur_coords) self.mobject.walker.set_color(color_func(rev)) self.mobject.arrow.set_color(color_func(rev)) @@ -467,12 +470,50 @@ class WalkerAnimation(Animation): about_point = ORIGIN #self.mobject.arrow.get_start() ) -def LinearWalker(start_coords, end_coords, coords_to_point, rev_func, **kwargs): +def walker_animation_with_display( + walk_func, + rev_func, + coords_to_point, + number_update_func = None, + scale_factor = 0.35, + **kwargs + ): + + walker_anim = WalkerAnimation( + walk_func = walk_func, + rev_func = rev_func, + coords_to_point = coords_to_point, + scale_factor = scale_factor, + **kwargs) + walker = walker_anim.compound_walker.walker + + if number_update_func != None: + display = DecimalNumber(0, include_background_rectangle = True) + displaycement = scale_factor * DOWN # How about that pun, eh? + display.move_to(walker.get_center() + displaycement) + display_anim = ChangingDecimal(display, + number_update_func, + tracked_mobject = walker_anim.compound_walker.walker, + **kwargs) + anim_group = AnimationGroup(walker_anim, display_anim) + return anim_group + else: + return walker_anim + +def LinearWalker( + start_coords, + end_coords, + coords_to_point, + rev_func, + number_update_func = None, + **kwargs + ): walk_func = lambda alpha : interpolate(start_coords, end_coords, alpha) - return WalkerAnimation( + return walker_animation_with_display( walk_func = walk_func, coords_to_point = coords_to_point, rev_func = rev_func, + number_update_func = number_update_func, **kwargs) class PiWalker(Scene): @@ -566,6 +607,9 @@ class EquationSolver2d(Scene): rev_func = lambda p : point_to_rev(self.func(p)) def Animate2dSolver(cur_depth, rect, dim_to_split): + print "Solver at depth: " + str(cur_depth) + sys.stdout.flush() + if cur_depth >= self.num_iterations: return EmptyAnimation() @@ -573,21 +617,19 @@ class EquationSolver2d(Scene): alpha_winder = make_alpha_winder(rev_func, start, end, self.num_checkpoints) a0 = alpha_winder(0) rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind - flashing_line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end), - stroke_width = 5, + thin_line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end), + stroke_width = 2, color = RED) - thin_line = flashing_line.copy() - thin_line.set_stroke(width = 1) walker_anim = LinearWalker( start_coords = start, end_coords = end, coords_to_point = num_plane.coords_to_point, rev_func = rev_func, + number_update_func = rebased_winder, remover = True ) line_draw_anim = AnimationGroup( ShowCreation(thin_line), - #ShowPassingFlash(flashing_line), walker_anim, rate_func = None) anim = line_draw_anim @@ -636,6 +678,7 @@ class EquationSolver2d(Scene): return Succession(anim, ShowCreation(mid_line), # FadeOut(mid_line), # TODO: Can change timing so this fades out at just the time it would be overdrawn + # TODO: Investigate weirdness with changing z buffer order on mid_line vs. rectangle lines AnimationGroup(*sub_anims) ) @@ -649,12 +692,18 @@ class EquationSolver2d(Scene): rect = RectangleData(x_interval, y_interval) + print "Starting to compute anim" + sys.stdout.flush() + anim = Animate2dSolver( cur_depth = 0, rect = rect, dim_to_split = 0, ) + print "Done computing anim" + sys.stdout.flush() + self.play(anim) self.wait() @@ -979,7 +1028,7 @@ class LoopSplitSceneMapped(LoopSplitScene): class FundThmAlg(EquationSolver2d): CONFIG = { "func" : plane_poly_with_roots((1, 2), (-1, 2.5), (-1, 2.5)), - "num_iterations" : 1, + "num_iterations" : 2, } # TODO: Borsuk-Ulam visuals @@ -1026,7 +1075,9 @@ class DiffOdometer(OdometerScene): # Generalizing Pi walker stuff to make bullets on pulsing lines change colors dynamically according to # function traced out -# Debugging Pi walker stuff added to EquationSolver2d +# Ask about tracked mobject, which is probably very useful for our animations +# (let's add a ChangingDecimal outputting winding number calculations tracking Pi walkers, +# particularly in EquationSolver2d) # ---- @@ -1038,6 +1089,4 @@ class DiffOdometer(OdometerScene): # Domain coloring -# TODO: Ask about tracked mobject, which is probably very useful for our animations - # FIN From 7a71927c90ddac23ecd8b9a6ef83e2f61031a1d7 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Tue, 30 Jan 2018 13:55:59 -0800 Subject: [PATCH 4/6] Incremental progress --- active_projects/WindingNumber.py | 66 +++++++++++++++++++------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 3fd9ac13..ac0c309d 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -29,8 +29,6 @@ from mobject.svg_mobject import * from mobject.tex_mobject import * from topics.graph_scene import * -import sys - # TODO/WARNING: There's a lot of refactoring and cleanup to be done in this code, # (and it will be done, but first I'll figure out what I'm doing with all this...) # -SR @@ -203,18 +201,28 @@ class EquationSolver1d(GraphScene, ZoomedScene): self.solveEquation() +def color_func(alpha): + alpha = alpha % 1 + colors = ["#FF0000", ORANGE, YELLOW, "#00FF00", "#0000FF", "#FF00FF"] + num_colors = len(colors) + beta = (alpha % (1.0/num_colors)) * num_colors + start_index = int(np.floor(num_colors * alpha)) % num_colors + end_index = (start_index + 1) % num_colors + + return interpolate_color(colors[start_index], colors[end_index], beta) + # TODO: Perhaps have bullets (pulses) fade out and in at ends of line, instead of jarringly # popping out and in? # # TODO: Perhaps have bullets change color corresponding to a function of their coordinates? # This could involve some merging of functoinality with PiWalker class LinePulser(ContinualAnimation): - def __init__(self, line, bullet_template, num_bullets, pulse_time, color_func = None, **kwargs): + def __init__(self, line, bullet_template, num_bullets, pulse_time, output_func = None, **kwargs): self.line = line self.num_bullets = num_bullets self.pulse_time = pulse_time self.bullets = [bullet_template.copy() for i in range(num_bullets)] - self.color_func = color_func + self.output_func = output_func ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs) def update_mobject(self, dt): @@ -225,19 +233,11 @@ class LinePulser(ContinualAnimation): position = interpolate(start, end, np.true_divide((i + alpha),(self.num_bullets))) self.bullets[i].move_to(position) - if self.color_func: - self.bullets.set_color(self.color_func(position)) - - -def color_func(alpha): - alpha = alpha % 1 - colors = ["#FF0000", ORANGE, YELLOW, "#00FF00", "#0000FF", "#FF00FF"] - num_colors = len(colors) - beta = (alpha % (1.0/num_colors)) * num_colors - start_index = int(np.floor(num_colors * alpha)) % num_colors - end_index = (start_index + 1) % num_colors - - return interpolate_color(colors[start_index], colors[end_index], beta) + if self.output_func: + position_2d = (position[0], position[1]) + rev = point_to_rev(self.output_func(position_2d)) + color = color_func(rev) + self.bullets[i].set_color(color) class ArrowCircleTest(Scene): def construct(self): @@ -548,7 +548,7 @@ class PiWalker(Scene): ShowCreation(Line(start_point, end_point), rate_func = None), run_time = self.step_run_time) - # TODO: Allow smooth paths instead of brekaing them up into lines, and + # TODO: Allow smooth paths instead of breaking them up into lines, and # use point_from_proportion to get points along the way @@ -605,16 +605,16 @@ class EquationSolver2d(Scene): self.add(num_plane) rev_func = lambda p : point_to_rev(self.func(p)) + clockwise_rev_func = lambda p : -rev_func(p) def Animate2dSolver(cur_depth, rect, dim_to_split): print "Solver at depth: " + str(cur_depth) - sys.stdout.flush() if cur_depth >= self.num_iterations: return EmptyAnimation() def draw_line_return_wind(start, end, start_wind): - alpha_winder = make_alpha_winder(rev_func, start, end, self.num_checkpoints) + alpha_winder = make_alpha_winder(clockwise_rev_func, start, end, self.num_checkpoints) a0 = alpha_winder(0) rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind thin_line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end), @@ -693,7 +693,6 @@ class EquationSolver2d(Scene): rect = RectangleData(x_interval, y_interval) print "Starting to compute anim" - sys.stdout.flush() anim = Animate2dSolver( cur_depth = 0, @@ -702,7 +701,6 @@ class EquationSolver2d(Scene): ) print "Done computing anim" - sys.stdout.flush() self.play(anim) @@ -902,10 +900,25 @@ class Initial2dFuncSceneWithoutMorphing(Scene): # creature from previous scene, then place it as a simultaneous inset with Premiere) class LoopSplitScene(Scene): + CONFIG = { + "output_func" : plane_poly_with_roots((1, 1)) + } - def PulsedLine(self, start, end, bullet_template, num_bullets = 4, pulse_time = 1, **kwargs): + def PulsedLine(self, + start, end, + bullet_template, + num_bullets = 4, + pulse_time = 1, + color_func = None, + **kwargs): line = Line(start, end, **kwargs) - anim = LinePulser(line, bullet_template, num_bullets, pulse_time, **kwargs) + anim = LinePulser( + line = line, + bullet_template = bullet_template, + num_bullets = num_bullets, + pulse_time = pulse_time, + output_func = self.output_func, + **kwargs) return [VGroup(line, *anim.bullets), anim] def construct(self): @@ -1027,8 +1040,8 @@ class LoopSplitSceneMapped(LoopSplitScene): # to illustrate relation between degree and large-scale winding number class FundThmAlg(EquationSolver2d): CONFIG = { - "func" : plane_poly_with_roots((1, 2), (-1, 2.5), (-1, 2.5)), - "num_iterations" : 2, + "func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5)), + "num_iterations" : 10, } # TODO: Borsuk-Ulam visuals @@ -1059,6 +1072,7 @@ class DiffOdometer(OdometerScene): } # TODO: Brouwer's fixed point theorem visuals +# class BFTScene(Scene): # TODO: Pi creatures wide-eyed in amazement From c28b81a2307c032dd94f892cc86722766b355d8a Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Tue, 30 Jan 2018 18:06:05 -0800 Subject: [PATCH 5/6] Fixed bug in set_background_by_color_function --- camera/camera.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/camera/camera.py b/camera/camera.py index 87b1ee36..d97a4d7a 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -385,12 +385,12 @@ class Camera(object): def get_points_of_all_pixels(self): """ Returns an array a such that a[i, j] gives the spatial - coordsinates associated with the pixel self.pixel_array[i, j] + coordinates associated with the pixel self.pixel_array[i, j] """ shape = self.pixel_array.shape indices = np.indices(shape[:2], dtype = 'float64') all_point_coords = np.zeros((shape[0], shape[1], 3)) - for i, space_dim in enumerate([SPACE_WIDTH, SPACE_HEIGHT]): + for i, space_dim in enumerate([SPACE_HEIGHT, SPACE_WIDTH]): all_point_coords[:,:,i] = \ indices[i,:,:]*2*space_dim/shape[i] - space_dim return all_point_coords @@ -411,7 +411,7 @@ class Camera(object): lambda p : float_rgba_to_int_rgba(point_to_rgba_func(p)), 2, points_of_all_pixels )) - self.reset() + self.reset() # Perhaps this really belongs in set_background? class MovingCamera(Camera): From c1dd5f168922fb330fb695342ea4a37703df9119 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Tue, 30 Jan 2018 18:06:19 -0800 Subject: [PATCH 6/6] Incremental --- active_projects/WindingNumber.py | 243 +++++++++++++++---------------- 1 file changed, 119 insertions(+), 124 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index ac0c309d..2b346d5d 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -201,7 +201,7 @@ class EquationSolver1d(GraphScene, ZoomedScene): self.solveEquation() -def color_func(alpha): +def rev_to_color(alpha): alpha = alpha % 1 colors = ["#FF0000", ORANGE, YELLOW, "#00FF00", "#0000FF", "#FF00FF"] num_colors = len(colors) @@ -211,119 +211,6 @@ def color_func(alpha): return interpolate_color(colors[start_index], colors[end_index], beta) -# TODO: Perhaps have bullets (pulses) fade out and in at ends of line, instead of jarringly -# popping out and in? -# -# TODO: Perhaps have bullets change color corresponding to a function of their coordinates? -# This could involve some merging of functoinality with PiWalker -class LinePulser(ContinualAnimation): - def __init__(self, line, bullet_template, num_bullets, pulse_time, output_func = None, **kwargs): - self.line = line - self.num_bullets = num_bullets - self.pulse_time = pulse_time - self.bullets = [bullet_template.copy() for i in range(num_bullets)] - self.output_func = output_func - ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs) - - def update_mobject(self, dt): - alpha = self.external_time % self.pulse_time - start = self.line.get_start() - end = self.line.get_end() - for i in range(self.num_bullets): - position = interpolate(start, end, - np.true_divide((i + alpha),(self.num_bullets))) - self.bullets[i].move_to(position) - if self.output_func: - position_2d = (position[0], position[1]) - rev = point_to_rev(self.output_func(position_2d)) - color = color_func(rev) - self.bullets[i].set_color(color) - -class ArrowCircleTest(Scene): - def construct(self): - circle_radius = 3 - circle = Circle(radius = circle_radius, color = WHITE) - self.add(circle) - - base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT) - - def rev_rotate(x, revs): - x.rotate(revs * TAU, about_point = ORIGIN) - x.set_color(color_func(revs)) - return x - - num_arrows = 8 * 3 - arrows = [rev_rotate(base_arrow.copy(), (np.true_divide(i, num_arrows))) for i in range(num_arrows)] - arrows_vgroup = VGroup(*arrows) - - self.play(ShowCreation(arrows_vgroup), run_time = 2.5, rate_func = None) - - self.wait() - -class FuncRotater(Animation): - CONFIG = { - "rotate_func" : lambda x : x # Func from alpha to revolutions - } - - # Perhaps abstract this out into an "Animation updating from original object" class - def update_submobject(self, submobject, starting_submobject, alpha): - submobject.points = np.array(starting_submobject.points) - - def update_mobject(self, alpha): - Animation.update_mobject(self, alpha) - angle_revs = self.rotate_func(alpha) - # We do a clockwise rotation - self.mobject.rotate( - -angle_revs * TAU, - about_point = ORIGIN - ) - self.mobject.set_color(color_func(angle_revs)) - -class TestRotater(Scene): - def construct(self): - test_line = Line(ORIGIN, RIGHT) - self.play(FuncRotater( - test_line, - rotate_func = lambda x : x % 0.25, - run_time = 10)) - -# TODO: Be careful about clockwise vs. counterclockwise convention throughout! -# Make sure this is correct everywhere in resulting video. -class OdometerScene(Scene): - CONFIG = { - "rotate_func" : lambda x : np.sin(x * TAU), - "run_time" : 5, - "dashed_line_angle" : None, - "biased_display_start" : None - } - - def construct(self): - radius = 1.3 - circle = Circle(center = ORIGIN, radius = radius) - self.add(circle) - - if self.dashed_line_angle: - dashed_line = DashedLine(ORIGIN, radius * RIGHT) - # Clockwise rotation - dashed_line.rotate(-self.dashed_line_angle * TAU, about_point = ORIGIN) - self.add(dashed_line) - - num_display = DecimalNumber(0, include_background_rectangle = True) - num_display.move_to(2 * DOWN) - - display_val_bias = 0 - if self.biased_display_start != None: - display_val_bias = self.biased_display_start - self.rotate_func(0) - display_func = lambda alpha : self.rotate_func(alpha) + display_val_bias - - base_arrow = Arrow(ORIGIN, RIGHT, buff = 0) - - self.play( - FuncRotater(base_arrow, rotate_func = self.rotate_func), - ChangingDecimal(num_display, display_func), - run_time = self.run_time, - rate_func = None) - def point_to_rev((x, y)): # Warning: np.arctan2 would happily discontinuously returns the value 0 for (0, 0), due to # design choices in the underlying atan2 library call, but for our purposes, this is @@ -463,8 +350,8 @@ class WalkerAnimation(Animation): cur_point = self.coords_to_point(cur_x, cur_y) self.mobject.walker.move_to(cur_point) rev = self.rev_func(cur_coords) - self.mobject.walker.set_color(color_func(rev)) - self.mobject.arrow.set_color(color_func(rev)) + self.mobject.walker.set_color(rev_to_color(rev)) + self.mobject.arrow.set_color(rev_to_color(rev)) self.mobject.arrow.rotate( rev * TAU, about_point = ORIGIN #self.mobject.arrow.get_start() @@ -706,6 +593,119 @@ class EquationSolver2d(Scene): self.wait() +# TODO: Perhaps have bullets (pulses) fade out and in at ends of line, instead of jarringly +# popping out and in? +# +# TODO: Perhaps have bullets change color corresponding to a function of their coordinates? +# This could involve some merging of functoinality with PiWalker +class LinePulser(ContinualAnimation): + def __init__(self, line, bullet_template, num_bullets, pulse_time, output_func = None, **kwargs): + self.line = line + self.num_bullets = num_bullets + self.pulse_time = pulse_time + self.bullets = [bullet_template.copy() for i in range(num_bullets)] + self.output_func = output_func + ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs) + + def update_mobject(self, dt): + alpha = self.external_time % self.pulse_time + start = self.line.get_start() + end = self.line.get_end() + for i in range(self.num_bullets): + position = interpolate(start, end, + np.true_divide((i + alpha),(self.num_bullets))) + self.bullets[i].move_to(position) + if self.output_func: + position_2d = (position[0], position[1]) + rev = point_to_rev(self.output_func(position_2d)) + color = rev_to_color(rev) + self.bullets[i].set_color(color) + +class ArrowCircleTest(Scene): + def construct(self): + circle_radius = 3 + circle = Circle(radius = circle_radius, color = WHITE) + self.add(circle) + + base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT) + + def rev_rotate(x, revs): + x.rotate(revs * TAU, about_point = ORIGIN) + x.set_color(rev_to_color(revs)) + return x + + num_arrows = 8 * 3 + arrows = [rev_rotate(base_arrow.copy(), (np.true_divide(i, num_arrows))) for i in range(num_arrows)] + arrows_vgroup = VGroup(*arrows) + + self.play(ShowCreation(arrows_vgroup), run_time = 2.5, rate_func = None) + + self.wait() + +class FuncRotater(Animation): + CONFIG = { + "rotate_func" : lambda x : x # Func from alpha to revolutions + } + + # Perhaps abstract this out into an "Animation updating from original object" class + def update_submobject(self, submobject, starting_submobject, alpha): + submobject.points = np.array(starting_submobject.points) + + def update_mobject(self, alpha): + Animation.update_mobject(self, alpha) + angle_revs = self.rotate_func(alpha) + # We do a clockwise rotation + self.mobject.rotate( + -angle_revs * TAU, + about_point = ORIGIN + ) + self.mobject.set_color(rev_to_color(angle_revs)) + +class TestRotater(Scene): + def construct(self): + test_line = Line(ORIGIN, RIGHT) + self.play(FuncRotater( + test_line, + rotate_func = lambda x : x % 0.25, + run_time = 10)) + +# TODO: Be careful about clockwise vs. counterclockwise convention throughout! +# Make sure this is correct everywhere in resulting video. +class OdometerScene(Scene): + CONFIG = { + "rotate_func" : lambda x : np.sin(x * TAU), + "run_time" : 5, + "dashed_line_angle" : None, + "biased_display_start" : None + } + + def construct(self): + radius = 1.3 + circle = Circle(center = ORIGIN, radius = radius) + self.add(circle) + + if self.dashed_line_angle: + dashed_line = DashedLine(ORIGIN, radius * RIGHT) + # Clockwise rotation + dashed_line.rotate(-self.dashed_line_angle * TAU, about_point = ORIGIN) + self.add(dashed_line) + + num_display = DecimalNumber(0, include_background_rectangle = True) + num_display.move_to(2 * DOWN) + + display_val_bias = 0 + if self.biased_display_start != None: + display_val_bias = self.biased_display_start - self.rotate_func(0) + display_func = lambda alpha : self.rotate_func(alpha) + display_val_bias + + base_arrow = Arrow(ORIGIN, RIGHT, buff = 0) + + self.play( + FuncRotater(base_arrow, rotate_func = self.rotate_func), + ChangingDecimal(num_display, display_func), + run_time = self.run_time, + rate_func = None) + ############# # Above are mostly general tools; here, we list, in order, finished or near-finished scenes @@ -909,7 +909,6 @@ class LoopSplitScene(Scene): bullet_template, num_bullets = 4, pulse_time = 1, - color_func = None, **kwargs): line = Line(start, end, **kwargs) anim = LinePulser( @@ -1086,13 +1085,6 @@ class DiffOdometer(OdometerScene): # Writing new Pi walker scenes by parametrizing general template -# Generalizing Pi walker stuff to make bullets on pulsing lines change colors dynamically according to -# function traced out - -# Ask about tracked mobject, which is probably very useful for our animations -# (let's add a ChangingDecimal outputting winding number calculations tracking Pi walkers, -# particularly in EquationSolver2d) - # ---- # Pi creature emotion stuff @@ -1103,4 +1095,7 @@ class DiffOdometer(OdometerScene): # Domain coloring +# TODO: Add to camera an option for low-quality background than other rendering, helpful +# for previews + # FIN