From 40d0fcc974ba0bae912a51d15b815eed3c91489a Mon Sep 17 00:00:00 2001 From: frozar Date: Fri, 2 Feb 2018 10:34:08 +0100 Subject: [PATCH 01/14] [3D CAMERA] New feature: give the possibility to change the center of rotation. --- topics/three_dimensions.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index b0c18645..809979f5 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -36,8 +36,11 @@ class ThreeDCamera(CameraWithPerspective): def __init__(self, *args, **kwargs): Camera.__init__(self, *args, **kwargs) self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect) - ## Lives in the phi-theta-distance space + ## rotation_mobject lives in the phi-theta-distance space self.rotation_mobject = VectorizedPoint() + ## moving_center lives in the x-y-z space + ## It representes the center of rotation + self.moving_center = VectorizedPoint(self.space_center) self.set_position(self.phi, self.theta, self.distance) def get_color(self, method): @@ -126,6 +129,16 @@ class ThreeDCamera(CameraWithPerspective): np.cos(phi) ]) + def get_center_of_rotation(self, x = None, y = None, z = None): + curr_x, curr_y, curr_z = self.moving_center.points[0] + if x is None: + x = curr_x + if y is None: + y = curr_y + if z is None: + z = curr_z + return np.array([x, y, z]) + def set_position(self, phi = None, theta = None, distance = None): point = self.get_spherical_coords(phi, theta, distance) self.rotation_mobject.move_to(point) @@ -140,6 +153,8 @@ class ThreeDCamera(CameraWithPerspective): def points_to_pixel_coords(self, points): matrix = self.get_view_transformation_matrix() new_points = np.dot(points, matrix.T) + self.space_center = self.moving_center.points[0] + return Camera.points_to_pixel_coords(self, new_points) class ThreeDScene(Scene): @@ -167,6 +182,7 @@ class ThreeDScene(Scene): def move_camera( self, phi = None, theta = None, distance = None, + center_x = None, center_y = None, center_z = None, added_anims = [], **kwargs ): @@ -176,10 +192,17 @@ class ThreeDScene(Scene): target_point, **kwargs ) + target_center = self.camera.get_center_of_rotation(center_x, center_y, center_z) + movement_center = ApplyMethod( + self.camera.moving_center.move_to, + target_center, + **kwargs + ) is_camera_rotating = self.ambient_camera_rotation in self.continual_animations if is_camera_rotating: self.remove(self.ambient_camera_rotation) - self.play(movement, *added_anims) + self.play(movement, movement_center, *added_anims) + target_point = self.camera.get_spherical_coords(phi, theta, distance) if is_camera_rotating: self.add(self.ambient_camera_rotation) From fb458de37d4531a6998af8d03134ba558768af49 Mon Sep 17 00:00:00 2001 From: frozar Date: Fri, 2 Feb 2018 11:10:17 +0100 Subject: [PATCH 02/14] [3D CAMERA] Add an example 'Rotation3d' to see the feature. --- example_scenes.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/example_scenes.py b/example_scenes.py index 6855b3cd..4561c1fc 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -23,6 +23,8 @@ from mobject.tex_mobject import * from mobject.vectorized_mobject import * +from topics.three_dimensions import * + # To watch one of these scenes, run the following: # python extract_scene.py file_name -p # @@ -58,6 +60,67 @@ class WriteStuff(Scene): self.play(Write(TextMobject("Stuff").scale(3))) +class Rotation3d(ThreeDScene): + def construct(self): + # STEP 1 + # Build two cube in the 3D scene, one for around the origin, + # the other shifted along the vector RIGHT + UP + OUT + cube_origin = Cube(fill_opacity = 0.8, stroke_width = 1., + side_length = 1., fill_color = WHITE) + + # RIGHT side: Red + # UP side: Green + # OUT side: Blue + orientations = [IN, OUT, LEFT, RIGHT, UP, DOWN] + for face, orient in zip(cube_origin.family_members_with_points(), orientations): + if np.array_equal(orient, RIGHT): + face.set_style_data(fill_color = RED) + elif np.array_equal(orient, UP): + face.set_style_data(fill_color = GREEN) + elif np.array_equal(orient, OUT): + face.set_style_data(fill_color = BLUE) + + cube_shifted = Cube(fill_opacity = 0.8, stroke_width = 1., + side_length = 1., fill_color = BLUE) + shift_vec = 2*(RIGHT + UP + OUT) + cube_shifted.shift(shift_vec) + + # STEP 2 + # Add the cubes in the 3D scene + self.add(cube_origin) + self.add(cube_shifted) + + # STEP 3 + # Setup the camera position + phi, theta, distance = ThreeDCamera().get_spherical_coords() + angle_factor = 0.9 + phi += 2*np.pi/4*angle_factor + theta += 3*2*np.pi/8 + self.set_camera_position(phi, theta, distance) + self.wait() + + # STEP 4 + # Animation + # Animation 1: rotation around the Z-axis with the ORIGIN of the space + # as center of rotation + theta += 2*np.pi + self.move_camera(phi, theta, distance, + run_time = 5) + + # Animation 2: shift the space in order of to get the center of the shifted cube + # as the next center of rotation + cube_center = cube_shifted.get_center() + self.move_camera(center_x = cube_center[0], + center_y = cube_center[1], + center_z = cube_center[2], + run_time = 2) + + # Animation 3: rotation around the Z-axis with the center of the shifted cube + # as center of rotation + theta += 2*np.pi + self.move_camera(phi, theta, distance, + run_time = 5) + From 2c85f66dddd470656d773c9c45800e10296b2b48 Mon Sep 17 00:00:00 2001 From: frozar Date: Fri, 2 Feb 2018 15:12:07 +0100 Subject: [PATCH 03/14] [3D CAMERA] Add parameter to set the camera position. --- topics/three_dimensions.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index 809979f5..ba9611cb 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -139,10 +139,14 @@ class ThreeDCamera(CameraWithPerspective): z = curr_z return np.array([x, y, z]) - def set_position(self, phi = None, theta = None, distance = None): + def set_position(self, phi = None, theta = None, distance = None, + center_x = None, center_y = None, center_z = None): point = self.get_spherical_coords(phi, theta, distance) self.rotation_mobject.move_to(point) self.phi, self.theta, self.distance = point + center_of_rotation = self.get_center_of_rotation(center_x, center_y, center_z) + self.moving_center.move_to(center_of_rotation) + self.space_center = self.moving_center.points[0] def get_view_transformation_matrix(self): return (self.default_distance / self.get_distance()) * np.dot( @@ -163,8 +167,9 @@ class ThreeDScene(Scene): "ambient_camera_rotation" : None, } - def set_camera_position(self, phi = None, theta = None, distance = None): - self.camera.set_position(phi, theta, distance) + def set_camera_position(self, phi = None, theta = None, distance = None, + center_x = None, center_y = None, center_z = None): + self.camera.set_position(phi, theta, distance, center_x, center_y, center_z) def begin_ambient_camera_rotation(self, rate = 0.01): self.ambient_camera_rotation = AmbientMovement( @@ -180,9 +185,9 @@ class ThreeDScene(Scene): self.ambient_camera_rotation = None def move_camera( - self, + self, phi = None, theta = None, distance = None, - center_x = None, center_y = None, center_z = None, + center_x = None, center_y = None, center_z = None, added_anims = [], **kwargs ): From 886af7cbebc898f846f4bc31d7bce71533cc47e6 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Tue, 6 Feb 2018 17:59:39 -0800 Subject: [PATCH 04/14] Incremental --- active_projects/WindingNumber.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index b55c368b..f38aea34 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -575,7 +575,7 @@ class EquationSolver2d(ColorMappedByFuncScene): colored_line = BackgroundColoredVMobject(thick_line, background_image_file = None) colored_line.set_background_array(background) else: - colored_line = thick_line.set_stroke_with(4) + colored_line = thick_line.set_stroke(width = 4) walker_anim = LinearWalker( start_coords = start, @@ -622,11 +622,11 @@ class EquationSolver2d(ColorMappedByFuncScene): rect.get_bottom_right(), rect.get_bottom_left() ] - points = np.array([num_plane.coords_to_point(x, y) for (x, y) in coords]) + IN + points = np.array([num_plane.coords_to_point(x, y) for (x, y) in coords]) + 2 * IN # TODO: Maybe use diagonal lines or something to fill in rectangles indicating # their "Nothing here" status? # Or draw a large X or something - fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = GREY) + fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = DARK_BROWN) return Succession(anim, FadeIn(fill_rect)) else: (sub_rect1, sub_rect2) = rect.splits_on_dim(dim_to_split) @@ -644,7 +644,7 @@ class EquationSolver2d(ColorMappedByFuncScene): for (sub_rect, side_to_draw) in sub_rect_and_sides ] mid_line_coords = rect.split_line_on_dim(dim_to_split) - mid_line_points = [num_plane.coords_to_point(x, y) for (x, y) in mid_line_coords] + mid_line_points = [num_plane.coords_to_point(x, y) + IN for (x, y) in mid_line_coords] # TODO: Have this match rectangle line style, apart from dashes and thin-ness? # Though there is also informational value in seeing the dashed line separately from rectangle lines mid_line = DashedLine(*mid_line_points) @@ -1129,7 +1129,8 @@ class FundThmAlg(EquationSolver2d): CONFIG = { "func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5)), "num_iterations" : 5, - "display_in_parallel" : True + "display_in_parallel" : True, + "use_fancy_lines" : False } # TODO: Borsuk-Ulam visuals From e38ac243821d08aa03942edae4eca41d7ce71d8f Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Wed, 7 Feb 2018 17:24:31 -0800 Subject: [PATCH 05/14] Unified display location (but not yet the code behind) labels in NumberPlane and ComplexPlane --- topics/complex_numbers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/topics/complex_numbers.py b/topics/complex_numbers.py index c411cd20..27b39f4f 100644 --- a/topics/complex_numbers.py +++ b/topics/complex_numbers.py @@ -193,6 +193,8 @@ class ComplexPlane(NumberPlane): return complex(x, y) def get_coordinate_labels(self, *numbers): + # TODO: Should merge this with the code from NumberPlane.get_coordinate_labels + result = VGroup() nudge = 0.1*(DOWN+RIGHT) if len(numbers) == 0: @@ -211,10 +213,7 @@ class ComplexPlane(NumberPlane): num_mob = TexMobject(num_str) num_mob.add_background_rectangle() num_mob.scale(self.number_scale_factor) - if complex(number).imag != 0: - vect = DOWN+RIGHT - else: - vect = DOWN+RIGHT + vect = DOWN + LEFT num_mob.next_to(point, vect, SMALL_BUFF) result.add(num_mob) return result @@ -224,6 +223,8 @@ class ComplexPlane(NumberPlane): return self def add_spider_web(self, circle_freq = 1, angle_freq = np.pi/6): + # This code no longer works because it has this reference to self.fade_factor + # which is never initialized. Shall we delete this little-used function entirely? self.fade(self.fade_factor) config = { "color" : self.color, From ebc53e3948927708aa9432cd9997d0f0b5908fbd Mon Sep 17 00:00:00 2001 From: mirefek Date: Mon, 12 Feb 2018 22:33:41 +0100 Subject: [PATCH 06/14] add requirements for BraceLabel --- topics/objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topics/objects.py b/topics/objects.py index a91c2b33..2ef461fe 100644 --- a/topics/objects.py +++ b/topics/objects.py @@ -3,10 +3,10 @@ from helpers import * from mobject import Mobject from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint from mobject.svg_mobject import SVGMobject -from mobject.tex_mobject import TextMobject, TexMobject +from mobject.tex_mobject import TextMobject, TexMobject, Brace from animation import Animation -from animation.simple_animations import Rotating, LaggedStart +from animation.simple_animations import Rotating, LaggedStart, AnimationGroup from animation.transform import ApplyMethod, FadeIn, GrowFromCenter from topics.geometry import Circle, Line, Rectangle, Square, \ From bc83c8a4f33f0afc5cf6ca77946f05a0772e0670 Mon Sep 17 00:00:00 2001 From: mirefek Date: Mon, 12 Feb 2018 22:35:51 +0100 Subject: [PATCH 07/14] SVG transforms hopefully finally handled properly --- mobject/svg_mobject.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/mobject/svg_mobject.py b/mobject/svg_mobject.py index 0b3b0b6c..8d0fbbc8 100644 --- a/mobject/svg_mobject.py +++ b/mobject/svg_mobject.py @@ -159,31 +159,52 @@ class SVGMobject(VMobject): x = float(element.getAttribute('x')) #Flip y y = -float(element.getAttribute('y')) + mobject.shift(x*RIGHT+y*UP) except: pass - try: - transform = element.getAttribute('transform') + transform = element.getAttribute('transform') + + try: # transform matrix prefix = "matrix(" suffix = ")" if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception() transform = transform[len(prefix):-len(suffix)] transform = string_to_numbers(transform) transform = np.array(transform).reshape([3,2]) - x += transform[2][0] - y -= transform[2][1] + x = transform[2][0] + y = -transform[2][1] matrix = np.identity(self.dim) matrix[:2,:2] = transform[:2,:] - t_matrix = np.transpose(matrix) + matrix[1] *= -1 + matrix[:,1] *= -1 for mob in mobject.family_members_with_points(): - mob.points = np.dot(mob.points, t_matrix) - + mob.points = np.dot(mob.points, matrix) + mobject.shift(x*RIGHT+y*UP) except: pass - mobject.shift(x*RIGHT+y*UP) - #TODO, transforms + try: # transform scale + prefix = "scale(" + suffix = ")" + if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception() + transform = transform[len(prefix):-len(suffix)] + scale_x, scale_y = string_to_numbers(transform) + mobject.scale(np.array([scale_x, scale_y, 1])) + except: + pass + + try: # transform translate + prefix = "translate(" + suffix = ")" + if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception() + transform = transform[len(prefix):-len(suffix)] + x, y = string_to_numbers(transform) + mobject.shift(x*RIGHT + y*DOWN) + except: + pass + #TODO, ... def update_ref_to_element(self, defs): new_refs = dict([ From 6b39ba0502786d63d13671325a78351f89dfd3be Mon Sep 17 00:00:00 2001 From: mirefek Date: Mon, 12 Feb 2018 22:41:34 +0100 Subject: [PATCH 08/14] Feature: argument coor_mask for move_to / next_to: setting coor_mask = X_AXIS or Y_AXIS makes the alignment in just one coordinate --- constants.py | 3 +++ mobject/mobject.py | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/constants.py b/constants.py index 2efb773f..cac15811 100644 --- a/constants.py +++ b/constants.py @@ -53,6 +53,9 @@ RIGHT = np.array(( 1., 0., 0.)) LEFT = np.array((-1., 0., 0.)) IN = np.array(( 0., 0.,-1.)) OUT = np.array(( 0., 0., 1.)) +X_AXIS = np.array(( 1., 0., 0.)) +Y_AXIS = np.array(( 0., 1., 0.)) +Z_AXIS = np.array(( 0., 0., 1.)) TOP = SPACE_HEIGHT*UP BOTTOM = SPACE_HEIGHT*DOWN diff --git a/mobject/mobject.py b/mobject/mobject.py index cf01b713..6618d1aa 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -296,6 +296,7 @@ class Mobject(Container): aligned_edge = ORIGIN, submobject_to_align = None, index_of_submobject_to_align = None, + coor_mask = np.array([1,1,1]), ): if isinstance(mobject_or_point, Mobject): mob = mobject_or_point @@ -315,7 +316,7 @@ class Mobject(Container): else: aligner = self point_to_align = aligner.get_critical_point(aligned_edge - direction) - self.shift(target_point - point_to_align + buff*direction) + self.shift((target_point - point_to_align + buff*direction)*coor_mask) return self def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP): @@ -403,13 +404,14 @@ class Mobject(Container): submob.scale(1./factor) return self - def move_to(self, point_or_mobject, aligned_edge = ORIGIN): + def move_to(self, point_or_mobject, aligned_edge = ORIGIN, + coor_mask = np.array([1,1,1])): if isinstance(point_or_mobject, Mobject): target = point_or_mobject.get_critical_point(aligned_edge) else: target = point_or_mobject point_to_align = self.get_critical_point(aligned_edge) - self.shift(target - point_to_align) + self.shift((target - point_to_align)*coor_mask) return self def replace(self, mobject, dim_to_match = 0, stretch = False): From f84eeda5465be557f3f4ac73ac61cc85e36774df Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Mon, 12 Feb 2018 13:53:33 -0800 Subject: [PATCH 09/14] Incremental --- active_projects/WindingNumber.py | 182 ++++++++++++++++++++++++------- helpers.py | 4 + 2 files changed, 148 insertions(+), 38 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index f38aea34..844b1fd2 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -52,8 +52,10 @@ class EquationSolver1d(GraphScene, ZoomedScene): self.add(self.graph) if self.graph_label != None: - self.add(self.get_graph_label(self.graph, self.graph_label, - x_val = 4, direction = RIGHT)) + curve_label = self.get_graph_label(self.graph, self.graph_label, + x_val = 4, direction = LEFT) + curve_label.shift(LEFT) + self.add(curve_label) if self.show_target_line: target_line_object = DashedLine( @@ -66,14 +68,20 @@ class EquationSolver1d(GraphScene, ZoomedScene): target_line_label.next_to(target_line_object.get_left(), UP + RIGHT) self.add(target_line_label) + self.wait() # Give us time to appreciate the graph + self.play(FadeOut(target_line_label)) # Reduce clutter + + print "For reference, graphOrigin: ", self.coords_to_point(0, 0) + print "targetYPoint: ", self.coords_to_point(0, self.targetY) + def solveEquation(self): - leftBrace = TexMobject("[") - rightBrace = TexMobject("]") + leftBrace = TexMobject("|") # Not using [ and ] because they end up crossing over + rightBrace = TexMobject("|") xBraces = Group(leftBrace, rightBrace) xBraces.stretch(2, 0) - downBrace = TexMobject("[") - upBrace = TexMobject("]") + downBrace = TexMobject("|") + upBrace = TexMobject("|") yBraces = Group(downBrace, upBrace) yBraces.stretch(2, 0) yBraces.rotate(TAU/4) @@ -83,7 +91,7 @@ class EquationSolver1d(GraphScene, ZoomedScene): upperX = self.initial_upper_x upperY = self.func(upperX) - leftBrace.move_to(self.coords_to_point(lowerX, 0), aligned_edge = LEFT) + leftBrace.move_to(self.coords_to_point(lowerX, 0)) #, aligned_edge = RIGHT) leftBraceLabel = DecimalNumber(lowerX) leftBraceLabel.next_to(leftBrace, DOWN + LEFT, buff = SMALL_BUFF) leftBraceLabelAnimation = ContinualChangingDecimal(leftBraceLabel, @@ -91,7 +99,7 @@ class EquationSolver1d(GraphScene, ZoomedScene): tracked_mobject = leftBrace) self.add(leftBraceLabelAnimation) - rightBrace.move_to(self.coords_to_point(upperX, 0), aligned_edge = RIGHT) + rightBrace.move_to(self.coords_to_point(upperX, 0)) #, aligned_edge = LEFT) rightBraceLabel = DecimalNumber(upperX) rightBraceLabel.next_to(rightBrace, DOWN + RIGHT, buff = SMALL_BUFF) rightBraceLabelAnimation = ContinualChangingDecimal(rightBraceLabel, @@ -99,7 +107,7 @@ class EquationSolver1d(GraphScene, ZoomedScene): tracked_mobject = rightBrace) self.add(rightBraceLabelAnimation) - downBrace.move_to(self.coords_to_point(0, lowerY), aligned_edge = DOWN) + downBrace.move_to(self.coords_to_point(0, lowerY)) #, aligned_edge = UP) downBraceLabel = DecimalNumber(lowerY) downBraceLabel.next_to(downBrace, LEFT + DOWN, buff = SMALL_BUFF) downBraceLabelAnimation = ContinualChangingDecimal(downBraceLabel, @@ -107,7 +115,7 @@ class EquationSolver1d(GraphScene, ZoomedScene): tracked_mobject = downBrace) self.add(downBraceLabelAnimation) - upBrace.move_to(self.coords_to_point(0, upperY), aligned_edge = UP) + upBrace.move_to(self.coords_to_point(0, upperY)) #, aligned_edge = DOWN) upBraceLabel = DecimalNumber(upperY) upBraceLabel.next_to(upBrace, LEFT + UP, buff = SMALL_BUFF) upBraceLabelAnimation = ContinualChangingDecimal(upBraceLabel, @@ -166,9 +174,24 @@ class EquationSolver1d(GraphScene, ZoomedScene): midCoords = self.coords_to_point(midX, midY) midColor = RED midXPoint = Dot(self.coords_to_point(midX, 0), color = midColor) + + x_guess_label_caption = TextMobject("New guess: x = ", fill_color = midColor) + x_guess_label_num = DecimalNumber(midX, fill_color = midColor) + x_guess_label_num.move_to(0.9 * SPACE_HEIGHT * DOWN) + x_guess_label_caption.next_to(x_guess_label_num, LEFT) + x_guess_label = Group(x_guess_label_caption, x_guess_label_num) + y_guess_label_caption = TextMobject(", y = ", fill_color = midColor) + y_guess_label_num = DecimalNumber(midY, fill_color = midColor) + y_guess_label_caption.next_to(x_guess_label_num, RIGHT) + y_guess_label_num.next_to(y_guess_label_caption, RIGHT) + y_guess_label = Group(y_guess_label_caption, y_guess_label_num) + guess_labels = Group(x_guess_label, y_guess_label) + self.play( ReplacementTransform(leftBrace.copy(), midXPoint), - ReplacementTransform(rightBrace.copy(), midXPoint)) + ReplacementTransform(rightBrace.copy(), midXPoint), + FadeIn(x_guess_label)) + midXLine = Line(self.coords_to_point(midX, 0), midCoords, color = midColor) self.play(ShowCreation(midXLine)) midDot = Dot(midCoords, color = midColor) @@ -177,14 +200,15 @@ class EquationSolver1d(GraphScene, ZoomedScene): midDot.scale_in_place(inverseZoomFactor) self.add(midDot) midYLine = Line(midCoords, self.coords_to_point(0, midY), color = midColor) - self.play(ShowCreation(midYLine)) + self.play(ShowCreation(midYLine), FadeIn(y_guess_label)) if midY < self.targetY: movingGroup = Group(lowerDot, leftBrace, downBrace, lowerXLine, lowerYLine) self.play( - UpdateFromAlphaFunc(movingGroup, makeUpdater(lowerX))) + UpdateFromAlphaFunc(movingGroup, makeUpdater(lowerX)), + FadeOut(guess_labels)) lowerX = midX lowerY = midY @@ -193,7 +217,8 @@ class EquationSolver1d(GraphScene, ZoomedScene): rightBrace, upBrace, upperXLine, upperYLine) self.play( - UpdateFromAlphaFunc(movingGroup, makeUpdater(upperX))) + UpdateFromAlphaFunc(movingGroup, makeUpdater(upperX)), + FadeOut(guess_labels)) upperX = midX upperY = midY self.remove(midXLine, midDot, midYLine) @@ -319,7 +344,7 @@ class RectangleData(): return tuple([mid(x, y) for (x, y) in sides]) def complex_to_pair(c): - return (c.real, c.imag) + return np.array((c.real, c.imag)) def plane_poly_with_roots(*points): def f((x, y)): @@ -723,7 +748,9 @@ class ArrowCircleTest(Scene): return x num_arrows = 8 * 3 - arrows = [rev_rotate(base_arrow.copy(), (fdiv(i, num_arrows))) for i in range(num_arrows)] + + # 0.5 - fdiv below so as to get a clockwise rotation from left + arrows = [rev_rotate(base_arrow.copy(), 0.5 - (fdiv(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) @@ -803,7 +830,7 @@ class FirstSqrtScene(EquationSolver1d): "x_max" : 2.5, "y_min" : 0, "y_max" : 2.5**2, - "graph_origin" : 2*DOWN + 5 * LEFT, + "graph_origin" : 2.5*DOWN + 5.5*LEFT, "x_axis_width" : 12, "zoom_factor" : 3, "zoomed_canvas_center" : 2.25 * UP + 1.75 * LEFT, @@ -818,31 +845,110 @@ class FirstSqrtScene(EquationSolver1d): "show_target_line" : True, } -class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene): -# TODO: Don't bother with ReconfigurableScene; just use new config from start -# (But can also use this as written, and just cut into middle in Premiere) +FirstSqrtSceneConfig = FirstSqrtScene.CONFIG +shiftVal = FirstSqrtSceneConfig["targetY"] - def setup(self): - FirstSqrtScene.setup(self) - ReconfigurableScene.setup(self) - - def construct(self): - shiftVal = self.targetY - - self.drawGraph() - newOrigin = self.coords_to_point(0, shiftVal) - self.transition_to_alt_config( - func = lambda x : x**2 - shiftVal, - targetY = 0, - graph_label = "y = x^2 - " + str(shiftVal), - y_min = self.y_min - shiftVal, - y_max = self.y_max - shiftVal, - show_target_line = False, - graph_origin = newOrigin) - self.solveEquation() +class SecondSqrtScene(FirstSqrtScene): + CONFIG = { + "func" : lambda x : FirstSqrtSceneConfig["func"](x) - shiftVal, + "targetY" : 0, + "graph_label" : FirstSqrtSceneConfig["graph_label"] + " - " + str(shiftVal), + "y_min" : FirstSqrtSceneConfig["y_min"] - shiftVal, + "y_max" : FirstSqrtSceneConfig["y_max"] - shiftVal, + "show_target_line" : False, + # 0.96 hacked in by checking calculations above + "graph_origin" : 0.96 * shiftVal * UP + FirstSqrtSceneConfig["graph_origin"], + } # TODO: Pi creatures intrigued +class SignsExplanation(Scene): + def construct(self): + num_line = NumberLine(stroke_width = 1) + largest_num = 10 + num_line.add_numbers(*range(-largest_num, largest_num + 1)) + self.add(num_line) + self.wait() + + pos_num = 3 + neg_num = -pos_num + + pos_arrow = Arrow( + num_line.number_to_point(0), + num_line.number_to_point(pos_num), + buff = 0, + color = YELLOW) + neg_arrow = Arrow( + num_line.number_to_point(0), + num_line.number_to_point(neg_num), + buff = 0, + color = RED) + + #num_line.add_numbers(pos_num) + self.play(ShowCreation(pos_arrow)) + + #num_line.add_numbers(neg_num) + self.play(ShowCreation(neg_arrow)) + +class VectorField(Scene): + CONFIG = { + "func" : plane_func_from_complex_func(lambda p : p**2 + 2), + "granularity" : 10, + "arrow_scale_factor" : 0.1, + "normalized_arrow_scale_factor" : 5 + } + + def construct(self): + num_plane = NumberPlane() + self.add(num_plane) + + x_min, y_min = num_plane.point_to_coords(SPACE_WIDTH * LEFT + SPACE_HEIGHT * UP) + x_max, y_max = num_plane.point_to_coords(SPACE_WIDTH * RIGHT + SPACE_HEIGHT * DOWN) + + x_points = range_via_num_steps(x_min, x_max, self.granularity) + y_points = range_via_num_steps(y_min, y_max, self.granularity) + points = it.product(x_points, y_points) + + sized_arrows = Group() + unsized_arrows = Group() + for (x, y) in points: + output = self.func((x, y)) + output_size = np.sqrt(sum(output**2)) + normalized_output = output * fdiv(self.normalized_arrow_scale_factor, output_size) # Assume output has nonzero size here + arrow = Vector(output * self.arrow_scale_factor) + normalized_arrow = Vector(normalized_output * self.arrow_scale_factor) + arrow.move_to(num_plane.coords_to_point(x, y)) + normalized_arrow.move_to(arrow) + sized_arrows.add(arrow) + unsized_arrows.add(normalized_arrow) + + self.add(sized_arrows) + self.wait() + + self.play(ReplacementTransform(sized_arrows, unsized_arrows)) + self.wait() + +class HasItsLimitations(Scene): + def construct(self): + num_line = NumberLine() + num_line.add_numbers() + self.add(num_line) + + self.wait() + + num_plane = NumberPlane() + num_plane.add_coordinates() + + self.play(FadeOut(num_line), FadeIn(num_plane)) + + self.wait() + + complex_plane = ComplexPlane() + complex_plane.add_coordinates() + + self.play(FadeOut(num_plane), FadeIn(complex_plane)) + + class ComplexPlaneIs2d(Scene): def construct(self): com_plane = ComplexPlane() diff --git a/helpers.py b/helpers.py index 8e1af238..fdb2f463 100644 --- a/helpers.py +++ b/helpers.py @@ -671,6 +671,10 @@ class DictAsObject(object): def fdiv(a, b): return np.true_divide(a,b) +def range_via_num_steps(start, end, num_steps, include_end = True): + num_points = num_steps + (1 if include_end else 0) + return [interpolate(start, end, fdiv(i, num_steps)) for i in range(num_points)] + # For debugging purposes def print_mobject_family(mob, n_tabs = 0): From ced8275f6e9e7b43a6b0882cda59ca61123d9aa9 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Tue, 13 Feb 2018 16:47:29 -0800 Subject: [PATCH 10/14] Incremental --- active_projects/WindingNumber.py | 74 +++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 844b1fd2..aebfd650 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -32,9 +32,17 @@ from topics.graph_scene import * # 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 + +positive_color = GREEN +negative_color = RED +neutral_color = YELLOW class EquationSolver1d(GraphScene, ZoomedScene): CONFIG = { + "camera_config" : + { + #"use_z_coordinate_for_display_order": True, + }, "func" : lambda x : x, "targetX" : 0, "targetY" : 0, @@ -74,14 +82,20 @@ class EquationSolver1d(GraphScene, ZoomedScene): print "For reference, graphOrigin: ", self.coords_to_point(0, 0) print "targetYPoint: ", self.coords_to_point(0, self.targetY) + # This is a mess right now (first major animation coded), + # but it works; can be refactored later or never def solveEquation(self): leftBrace = TexMobject("|") # Not using [ and ] because they end up crossing over + leftBrace.set_color(negative_color) rightBrace = TexMobject("|") + rightBrace.set_color(positive_color) xBraces = Group(leftBrace, rightBrace) xBraces.stretch(2, 0) downBrace = TexMobject("|") + downBrace.set_color(negative_color) upBrace = TexMobject("|") + upBrace.set_color(positive_color) yBraces = Group(downBrace, upBrace) yBraces.stretch(2, 0) yBraces.rotate(TAU/4) @@ -126,20 +140,36 @@ class EquationSolver1d(GraphScene, ZoomedScene): lowerDotPoint = self.input_to_graph_point(lowerX, self.graph) lowerDotXPoint = self.coords_to_point(lowerX, 0) lowerDotYPoint = self.coords_to_point(0, self.func(lowerX)) - lowerDot = Dot(lowerDotPoint) + lowerDot = Dot(lowerDotPoint, color = negative_color) upperDotPoint = self.input_to_graph_point(upperX, self.graph) - upperDot = Dot(upperDotPoint) + upperDot = Dot(upperDotPoint, color = positive_color) upperDotXPoint = self.coords_to_point(upperX, 0) upperDotYPoint = self.coords_to_point(0, self.func(upperX)) - lowerXLine = Line(lowerDotXPoint, lowerDotPoint, stroke_width = 1, color = YELLOW) - upperXLine = Line(upperDotXPoint, upperDotPoint, stroke_width = 1, color = YELLOW) - lowerYLine = Line(lowerDotYPoint, lowerDotPoint, stroke_width = 1, color = YELLOW) - upperYLine = Line(upperDotYPoint, upperDotPoint, stroke_width = 1, color = YELLOW) + lowerXLine = Line(lowerDotXPoint, lowerDotPoint, color = negative_color) + upperXLine = Line(upperDotXPoint, upperDotPoint, color = positive_color) + lowerYLine = Line(lowerDotYPoint, lowerDotPoint, color = negative_color) + upperYLine = Line(upperDotYPoint, upperDotPoint, color = positive_color) self.add(lowerXLine, upperXLine, lowerYLine, upperYLine) self.add(xBraces, yBraces, lowerDot, upperDot) + lowerGroup = Group( + lowerDot, + leftBrace, downBrace, + lowerXLine, lowerYLine) + + upperGroup = Group( + upperDot, + rightBrace, upBrace, + upperXLine, upperYLine) + + initialLowerXDot = Dot(lowerDotXPoint, color = negative_color) + initialUpperXDot = Dot(upperDotXPoint, color = positive_color) + initialLowerYDot = Dot(lowerDotYPoint, color = negative_color) + initialUpperYDot = Dot(upperDotYPoint, color = positive_color) + self.add(initialLowerXDot, initialUpperXDot, initialLowerYDot, initialUpperYDot) + for i in range(self.num_iterations): if i == self.iteration_at_which_to_start_zoom: self.activate_zooming() @@ -172,7 +202,7 @@ class EquationSolver1d(GraphScene, ZoomedScene): midY = self.func(midX) midCoords = self.coords_to_point(midX, midY) - midColor = RED + midColor = neutral_color midXPoint = Dot(self.coords_to_point(midX, 0), color = midColor) x_guess_label_caption = TextMobject("New guess: x = ", fill_color = midColor) @@ -200,27 +230,30 @@ class EquationSolver1d(GraphScene, ZoomedScene): midDot.scale_in_place(inverseZoomFactor) self.add(midDot) midYLine = Line(midCoords, self.coords_to_point(0, midY), color = midColor) - self.play(ShowCreation(midYLine), FadeIn(y_guess_label)) + self.play( + ShowCreation(midYLine), + FadeIn(y_guess_label)) + midYPoint = Dot(self.coords_to_point(0, midY), color = midColor) + self.add(midYPoint) if midY < self.targetY: - movingGroup = Group(lowerDot, - leftBrace, downBrace, - lowerXLine, lowerYLine) self.play( - UpdateFromAlphaFunc(movingGroup, makeUpdater(lowerX)), - FadeOut(guess_labels)) + UpdateFromAlphaFunc(lowerGroup, makeUpdater(lowerX)), + FadeOut(guess_labels), + ApplyMethod(midXPoint.set_color, negative_color), + ApplyMethod(midYPoint.set_color, negative_color)) lowerX = midX lowerY = midY else: - movingGroup = Group(upperDot, - rightBrace, upBrace, - upperXLine, upperYLine) self.play( - UpdateFromAlphaFunc(movingGroup, makeUpdater(upperX)), - FadeOut(guess_labels)) + UpdateFromAlphaFunc(upperGroup, makeUpdater(upperX)), + FadeOut(guess_labels), + ApplyMethod(midXPoint.set_color, positive_color), + ApplyMethod(midYPoint.set_color, positive_color)) upperX = midX upperY = midY + #mid_group = Group(midXLine, midDot, midYLine) Removing groups doesn't flatten as expected? self.remove(midXLine, midDot, midYLine) self.wait() @@ -560,6 +593,7 @@ class PiWalkerCircle(PiWalker): # TODO: Give drawn lines a bit of buffer, so that the rectangle's corners are filled in class EquationSolver2d(ColorMappedByFuncScene): CONFIG = { + "camera_config" : {"use_z_coordinate_for_display_order": True}, "initial_lower_x" : -5.1, "initial_upper_x" : 5.1, "initial_lower_y" : -3.1, @@ -597,8 +631,8 @@ class EquationSolver2d(ColorMappedByFuncScene): stroke_width = 10, color = RED) if self.use_fancy_lines: - colored_line = BackgroundColoredVMobject(thick_line, background_image_file = None) - colored_line.set_background_array(background) + colored_line = thick_line.color_using_background_image("color_background") + # colored_line.set_background_array(background) else: colored_line = thick_line.set_stroke(width = 4) From e4ba832aacfbd33689971980fb847d6f6f3aa8ef Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Thu, 15 Feb 2018 11:57:45 -0800 Subject: [PATCH 11/14] Incremental --- active_projects/WindingNumber.py | 239 +++++++++++++++++++++++-------- animation/transform.py | 13 +- 2 files changed, 194 insertions(+), 58 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index aebfd650..bd9bb1e2 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -33,15 +33,60 @@ from topics.graph_scene import * # (and it will be done, but first I'll figure out what I'm doing with all this...) # -SR -positive_color = GREEN -negative_color = RED -neutral_color = YELLOW +def rev_to_rgba(alpha): + alpha = (0.5 - alpha) % 1 # For convenience, to go CW from red on left instead of CCW from right + # 0 is red, 1/6 is yellow, 1/3 is green, 2/3 is blue + hue_list = [0, 0.5/6.0, 1/6.0, 1.1/6.0, 2/6.0, 3/6.0, 4/6.0, 5/6.0] + num_hues = len(hue_list) + start_index = int(np.floor(num_hues * alpha)) % num_hues + end_index = (start_index + 1) % num_hues + beta = (alpha % (1.0/num_hues)) * num_hues + + start_hue = hue_list[start_index] + end_hue = hue_list[end_index] + if end_hue < start_hue: + end_hue = end_hue + 1 + hue = interpolate(start_hue, end_hue, beta) + + return color_to_rgba(Color(hue = hue, saturation = 1, luminance = 0.5)) + + # alpha = alpha % 1 + # colors = colorslist + # 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(colors[start_index], colors[end_index], beta) + +def rev_to_color(alpha): + return rgba_to_color(rev_to_rgba(alpha)) + +def point_to_rev((x, y), allow_origin = False): + # 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 + # illegitimate, and all winding number calculations must be set up to avoid this + if not(allow_origin) and (x, y) == (0, 0): + print "Error! Angle of (0, 0) computed!" + return + return fdiv(np.arctan2(y, x), TAU) + +def point_to_rgba(point): + rev = point_to_rev(point, allow_origin = True) + rgba = rev_to_rgba(rev) + base_size = np.sqrt(point[0]**2 + point[1]**2) + rescaled_size = np.sqrt(base_size/(base_size + 1)) + return rgba * rescaled_size + +positive_color = rev_to_color(0) +negative_color = rev_to_color(0.5) +neutral_color = rev_to_color(0.25) class EquationSolver1d(GraphScene, ZoomedScene): CONFIG = { "camera_config" : { - #"use_z_coordinate_for_display_order": True, + "use_z_coordinate_for_display_order": True, }, "func" : lambda x : x, "targetX" : 0, @@ -140,9 +185,9 @@ class EquationSolver1d(GraphScene, ZoomedScene): lowerDotPoint = self.input_to_graph_point(lowerX, self.graph) lowerDotXPoint = self.coords_to_point(lowerX, 0) lowerDotYPoint = self.coords_to_point(0, self.func(lowerX)) - lowerDot = Dot(lowerDotPoint, color = negative_color) + lowerDot = Dot(lowerDotPoint + OUT, color = negative_color) upperDotPoint = self.input_to_graph_point(upperX, self.graph) - upperDot = Dot(upperDotPoint, color = positive_color) + upperDot = Dot(upperDotPoint + OUT, color = positive_color) upperDotXPoint = self.coords_to_point(upperX, 0) upperDotYPoint = self.coords_to_point(0, self.func(upperX)) @@ -154,15 +199,22 @@ class EquationSolver1d(GraphScene, ZoomedScene): self.add(xBraces, yBraces, lowerDot, upperDot) + x_guess_line = Line(lowerDotXPoint, upperDotXPoint, color = neutral_color) + self.add(x_guess_line) + lowerGroup = Group( lowerDot, leftBrace, downBrace, - lowerXLine, lowerYLine) + lowerXLine, lowerYLine, + x_guess_line + ) upperGroup = Group( upperDot, rightBrace, upBrace, - upperXLine, upperYLine) + upperXLine, upperYLine, + x_guess_line + ) initialLowerXDot = Dot(lowerDotXPoint, color = negative_color) initialUpperXDot = Dot(upperDotXPoint, color = positive_color) @@ -181,9 +233,9 @@ class EquationSolver1d(GraphScene, ZoomedScene): upperDot.scale_in_place, inverseZoomFactor) - def makeUpdater(xAtStart): + def makeUpdater(xAtStart, fixed_guess_x): def updater(group, alpha): - dot, xBrace, yBrace, xLine, yLine = group + dot, xBrace, yBrace, xLine, yLine, guess_line = group newX = interpolate(xAtStart, midX, alpha) newY = self.func(newX) graphPoint = self.input_to_graph_point(newX, @@ -195,15 +247,20 @@ class EquationSolver1d(GraphScene, ZoomedScene): yBrace.move_to(yAxisPoint) xLine.put_start_and_end_on(xAxisPoint, graphPoint) yLine.put_start_and_end_on(yAxisPoint, graphPoint) + fixed_guess_point = self.coords_to_point(fixed_guess_x, 0) + guess_line.put_start_and_end_on(xAxisPoint, fixed_guess_point) return group return updater midX = (lowerX + upperX)/float(2) midY = self.func(midX) + in_negative_branch = midY < self.targetY + sign_color = negative_color if in_negative_branch else positive_color midCoords = self.coords_to_point(midX, midY) midColor = neutral_color - midXPoint = Dot(self.coords_to_point(midX, 0), color = midColor) + # Hm... even the z buffer isn't helping keep this above x_guess_line + midXPoint = Dot(self.coords_to_point(midX, 0) + OUT, color = midColor) x_guess_label_caption = TextMobject("New guess: x = ", fill_color = midColor) x_guess_label_num = DecimalNumber(midX, fill_color = midColor) @@ -211,7 +268,7 @@ class EquationSolver1d(GraphScene, ZoomedScene): x_guess_label_caption.next_to(x_guess_label_num, LEFT) x_guess_label = Group(x_guess_label_caption, x_guess_label_num) y_guess_label_caption = TextMobject(", y = ", fill_color = midColor) - y_guess_label_num = DecimalNumber(midY, fill_color = midColor) + y_guess_label_num = DecimalNumber(midY, fill_color = sign_color) y_guess_label_caption.next_to(x_guess_label_num, RIGHT) y_guess_label_num.next_to(y_guess_label_caption, RIGHT) y_guess_label = Group(y_guess_label_caption, y_guess_label_num) @@ -222,35 +279,43 @@ class EquationSolver1d(GraphScene, ZoomedScene): ReplacementTransform(rightBrace.copy(), midXPoint), FadeIn(x_guess_label)) - midXLine = Line(self.coords_to_point(midX, 0), midCoords, color = midColor) + midXLine = DashedLine(self.coords_to_point(midX, 0), midCoords, color = midColor) self.play(ShowCreation(midXLine)) - midDot = Dot(midCoords, color = midColor) + midDot = Dot(midCoords, color = sign_color) if(self.iteration_at_which_to_start_zoom != None and i >= self.iteration_at_which_to_start_zoom): midDot.scale_in_place(inverseZoomFactor) self.add(midDot) - midYLine = Line(midCoords, self.coords_to_point(0, midY), color = midColor) + midYLine = DashedLine(midCoords, self.coords_to_point(0, midY), color = sign_color) self.play( ShowCreation(midYLine), - FadeIn(y_guess_label)) - midYPoint = Dot(self.coords_to_point(0, midY), color = midColor) + FadeIn(y_guess_label), + ApplyMethod(midXPoint.set_color, sign_color), + ApplyMethod(midXLine.set_color, sign_color)) + midYPoint = Dot(self.coords_to_point(0, midY), color = sign_color) self.add(midYPoint) - if midY < self.targetY: + if in_negative_branch: self.play( - UpdateFromAlphaFunc(lowerGroup, makeUpdater(lowerX)), + UpdateFromAlphaFunc(lowerGroup, + makeUpdater(lowerX, + fixed_guess_x = upperX + ) + ), FadeOut(guess_labels), - ApplyMethod(midXPoint.set_color, negative_color), - ApplyMethod(midYPoint.set_color, negative_color)) + ) lowerX = midX lowerY = midY else: self.play( - UpdateFromAlphaFunc(upperGroup, makeUpdater(upperX)), + UpdateFromAlphaFunc(upperGroup, + makeUpdater(upperX, + fixed_guess_x = lowerX + ) + ), FadeOut(guess_labels), - ApplyMethod(midXPoint.set_color, positive_color), - ApplyMethod(midYPoint.set_color, positive_color)) + ) upperX = midX upperY = midY #mid_group = Group(midXLine, midDot, midYLine) Removing groups doesn't flatten as expected? @@ -262,37 +327,6 @@ class EquationSolver1d(GraphScene, ZoomedScene): self.drawGraph() self.solveEquation() -colorslist = map(color_to_rgba, ["#FF0000", "#FFFF00", "#00FF00", "#0000FF"]) - -def rev_to_rgba(alpha): - alpha = alpha % 1 - colors = colorslist - 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(colors[start_index], colors[end_index], beta) - -def rev_to_color(alpha): - return rgba_to_color(rev_to_rgba(alpha)) - -def point_to_rev((x, y), allow_origin = False): - # 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 - # illegitimate, and all winding number calculations must be set up to avoid this - if not(allow_origin) and (x, y) == (0, 0): - print "Error! Angle of (0, 0) computed!" - return - return fdiv(np.arctan2(y, x), TAU) - -def point_to_rgba(point): - rev = point_to_rev(point, allow_origin = True) - rgba = rev_to_rgba(rev) - base_size = np.sqrt(point[0]**2 + point[1]**2) - rescaled_size = np.sqrt(base_size/(base_size + 1)) - return rgba * rescaled_size - # Returns the value with the same fractional component as x, closest to m def resit_near(x, m): frac_diff = (x - m) % 1 @@ -520,6 +554,11 @@ class ColorMappedByFuncScene(Scene): ) ) +class PureColorMap(ColorMappedByFuncScene): + CONFIG = { + "show_num_plane" : False + } + class ColorMappedByFuncStill(ColorMappedByFuncScene): def construct(self): ColorMappedByFuncScene.construct(self) @@ -896,6 +935,37 @@ class SecondSqrtScene(FirstSqrtScene): # TODO: Pi creatures intrigued +class RewriteEquation(Scene): + def construct(self): + # Can maybe fitz around with smoothening the transform, so just = goes to - and new stuff + # is added at the right end, while things re-center + f_old = TexMobject("f(x)") + f_new = f_old.copy() + equals_old = TexMobject("=") + equals_old_2 = equals_old.copy() + equals_new = equals_old.copy() + g_old = TexMobject("g(x)") + g_new = g_old.copy() + minus_new = TexMobject("-") + zero_new = TexMobject("0") + f_old.next_to(equals_old, LEFT) + g_old.next_to(equals_old, RIGHT) + minus_new.next_to(g_new, LEFT) + f_new.next_to(minus_new, LEFT) + equals_new.next_to(g_new, RIGHT) + zero_new.next_to(equals_new, RIGHT) + + self.add(f_old, equals_old, equals_old_2, g_old) + self.wait() + self.play( + ReplacementTransform(f_old, f_new), + ReplacementTransform(equals_old, equals_new), + ReplacementTransform(g_old, g_new), + ReplacementTransform(equals_old_2, minus_new), + ShowCreation(zero_new), + ) + self.wait() + class SignsExplanation(Scene): def construct(self): num_line = NumberLine(stroke_width = 1) @@ -911,12 +981,12 @@ class SignsExplanation(Scene): num_line.number_to_point(0), num_line.number_to_point(pos_num), buff = 0, - color = YELLOW) + color = positive_color) neg_arrow = Arrow( num_line.number_to_point(0), num_line.number_to_point(neg_num), buff = 0, - color = RED) + color = negative_color) #num_line.add_numbers(pos_num) self.play(ShowCreation(pos_arrow)) @@ -1300,6 +1370,55 @@ class DiffOdometer(OdometerScene): "biased_display_start" : 0 } +class CombineInterval(Scene): + def construct(self): + plus_sign = TexMobject("+", fill_color = positive_color) + minus_sign = TexMobject("-", fill_color = negative_color) + + left_point = Dot(LEFT, color = positive_color) + right_point = Dot(RIGHT, color = negative_color) + line1 = Line(LEFT, RIGHT) + interval1 = Group(line1, left_point, right_point) + + plus_sign.next_to(left_point, UP) + minus_sign.next_to(right_point, UP) + + self.add(interval1, plus_sign, minus_sign) + self.wait() + + mid_point = Dot(ORIGIN, color = GREY) + + question_mark = TexMobject("?", fill_color = GREY) + plus_sign_copy = plus_sign.copy() + minus_sign_copy = minus_sign.copy() + new_signs = Group(question_mark, plus_sign_copy, minus_sign_copy) + for sign in new_signs: sign.next_to(mid_point, UP) + + self.play(ShowCreation(mid_point), ShowCreation(question_mark)) + self.wait() + + self.play( + ApplyMethod(mid_point.set_color, positive_color), + ReplacementTransform(question_mark, plus_sign_copy), + ) + self.play( + HighlightCircle(plus_sign_copy), + HighlightCircle(minus_sign), + ) + + self.wait() + + self.play( + ApplyMethod(mid_point.set_color, negative_color), + ReplacementTransform(plus_sign_copy, minus_sign_copy), + ) + self.play( + HighlightCircle(minus_sign_copy), + HighlightCircle(plus_sign), + ) + + self.wait() + # TODO: Brouwer's fixed point theorem visuals # class BFTScene(Scene): @@ -1351,4 +1470,10 @@ class ShowBack(PiWalkerRect): "func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5)) } +class Diagnostic(Scene): + def construct(self): + testList = map( (lambda n : (n, rev_to_rgba(n))), [0, 0.25, 0.5, 0.9]) + print "rev_to_rgbas", testList + self.wait() + # FIN \ No newline at end of file diff --git a/animation/transform.py b/animation/transform.py index 4ee132a5..ff13a388 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -8,7 +8,7 @@ from helpers import * from animation import Animation from mobject import Mobject, Point, VMobject, Group -from topics.geometry import Dot +from topics.geometry import Dot, Circle class Transform(Animation): CONFIG = { @@ -214,6 +214,17 @@ class Indicate(Transform): target.highlight(self.color) Transform.__init__(self, mobject, target, **kwargs) +class HighlightCircle(Indicate): + CONFIG = { + "rate_func" : squish_rate_func(there_and_back, 0, 0.8), + "remover" : True + } + def __init__(self, mobject, **kwargs): + digest_config(self, kwargs) + circle = Circle(color = self.color, **kwargs) + circle.surround(mobject) + Indicate.__init__(self, circle, **kwargs) + class Rotate(ApplyMethod): CONFIG = { "in_place" : False, From 551501b69e6e983e1a795efdfbfe2c72683bfa2c Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Thu, 15 Feb 2018 14:43:55 -0800 Subject: [PATCH 12/14] extract_scene now behaves more as expected when passed explicit output file names already containing an extension --- extract_scene.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/extract_scene.py b/extract_scene.py index 605b4513..82e7a250 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -70,6 +70,17 @@ def get_configuration(): parser.add_argument("-o", "--output_name") parser.add_argument("-n", "--start_at_animation_number") args = parser.parse_args() + if args.output_name != None: + output_name_root, output_name_ext = os.path.splitext(args.output_name) + expected_ext = '.png' if args.show_last_frame else '.mp4' + if not output_name_ext in ['', expected_ext] + print "WARNING: The output will be to (doubly-dotted) %s%s"%output_name_root%expected_ext + output_name = args.output_name + else: + # If anyone wants .mp4.mp4 and is surprised to only get .mp4, or such... Well, too bad. + output_name = output_name_root + else: + output_name = args.output_name except argparse.ArgumentError as err: print(str(err)) sys.exit(2) @@ -87,7 +98,7 @@ def get_configuration(): "quiet" : args.quiet or args.write_all, "ignore_waits" : args.preview, "write_all" : args.write_all, - "output_name" : args.output_name, + "output_name" : output_name, "start_at_animation_number" : args.start_at_animation_number, "end_at_animation_number" : None, } From 05e073325e78956c494f9b204d68cf3a0ee91c95 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Thu, 15 Feb 2018 14:44:23 -0800 Subject: [PATCH 13/14] Added CircleIndicate animation --- animation/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/animation/transform.py b/animation/transform.py index ff13a388..433650df 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -214,7 +214,7 @@ class Indicate(Transform): target.highlight(self.color) Transform.__init__(self, mobject, target, **kwargs) -class HighlightCircle(Indicate): +class CircleIndicate(Indicate): CONFIG = { "rate_func" : squish_rate_func(there_and_back, 0, 0.8), "remover" : True From bdc11cac510eb2efea832793e712b6819f8b8567 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Thu, 15 Feb 2018 14:44:32 -0800 Subject: [PATCH 14/14] Incremental --- active_projects/WindingNumber.py | 109 ++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 18 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index bd9bb1e2..b9b2faa4 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -533,26 +533,58 @@ class ColorMappedByFuncScene(Scene): def setup(self): if self.show_output: - self.pos_func = self.func + self.input_to_pos_func = self.func + self.pos_to_color_func = lambda p : p else: - self.pos_func = lambda p : p + self.input_to_pos_func = lambda p : p + self.pos_to_color_func = self.func + + # func_hash hashes the function at some random points + func_hash_points = [(-0.93, 1), (1, -2.7), (20, 4)] + to_hash = tuple((self.func(p)[0], self.func(p)[1]) for p in func_hash_points) + func_hash = hash(to_hash) + full_hash = hash((func_hash, self.camera.pixel_shape)) + self.background_image_file = "color_mapped_background_" + str(full_hash) + try: + file_path = get_full_raster_image_path(self.background_image_file) + # If we succeed in finding the file: + self.in_background_pass = False + except IOError: + file_path = os.path.join(RASTER_IMAGE_DIR, self.background_image_file + ".png") + self.in_background_pass = True + + print "Background file: " + file_path + if self.in_background_pass: + print "The background file does not exist yet; this will be the background creation pass" + print "If not already doing so, please re-run this render with the flags -s -n 0,1 -o \"%s\""%file_path + self.show_num_plane = False + else: + print "The background file already exists; this will be the video pass as usual" def construct(self): - display_func = self.func if not self.show_output else lambda p : p - if self.show_num_plane: self.num_plane.fade() self.add(self.num_plane) - self.camera.set_background_from_func( - lambda (x, y): point_to_rgba( - display_func( - # Should be self.num_plane.point_to_coords_cheap(np.array([x, y, 0])), - # but for cheapness, we'll go with just (x, y), having never altered - # any num_plane's from default settings so far - (x, y) + + if self.in_background_pass: + self.camera.set_background_from_func( + lambda (x, y): point_to_rgba( + self.pos_to_color_func( + # Should be self.num_plane.point_to_coords_cheap(np.array([x, y, 0])), + # but for cheapness, we'll go with just (x, y), having never altered + # any num_plane's from default settings so far + (x, y) + ) ) ) - ) + + # The one scene to be rendered by the desired -s -n 0, 1 invocation: + self.play(EmptyAnimation()) + self.wait() + + else: + self.camera.background_image = self.background_image_file + self.camera.init_background() class PureColorMap(ColorMappedByFuncScene): CONFIG = { @@ -670,7 +702,7 @@ class EquationSolver2d(ColorMappedByFuncScene): stroke_width = 10, color = RED) if self.use_fancy_lines: - colored_line = thick_line.color_using_background_image("color_background") + colored_line = thick_line.color_using_background_image(self.background_image_file) # colored_line.set_background_array(background) else: colored_line = thick_line.set_stroke(width = 4) @@ -1385,6 +1417,11 @@ class CombineInterval(Scene): self.add(interval1, plus_sign, minus_sign) self.wait() + self.play( + CircleIndicate(plus_sign), + CircleIndicate(minus_sign), + ) + self.wait() mid_point = Dot(ORIGIN, color = GREY) @@ -1394,7 +1431,7 @@ class CombineInterval(Scene): new_signs = Group(question_mark, plus_sign_copy, minus_sign_copy) for sign in new_signs: sign.next_to(mid_point, UP) - self.play(ShowCreation(mid_point), ShowCreation(question_mark)) + self.play(FadeIn(mid_point), FadeIn(question_mark)) self.wait() self.play( @@ -1402,8 +1439,8 @@ class CombineInterval(Scene): ReplacementTransform(question_mark, plus_sign_copy), ) self.play( - HighlightCircle(plus_sign_copy), - HighlightCircle(minus_sign), + CircleIndicate(plus_sign_copy), + CircleIndicate(minus_sign), ) self.wait() @@ -1413,12 +1450,48 @@ class CombineInterval(Scene): ReplacementTransform(plus_sign_copy, minus_sign_copy), ) self.play( - HighlightCircle(minus_sign_copy), - HighlightCircle(plus_sign), + CircleIndicate(minus_sign_copy), + CircleIndicate(plus_sign), ) self.wait() +class CombineInterval2(Scene): + def construct(self): + plus_sign = TexMobject("+", fill_color = positive_color) + + def make_interval(a, b): + line = Line(a, b) + start_dot = Dot(a, color = positive_color) + end_dot = Dot(b, color = positive_color) + start_sign = plus_sign.copy().next_to(start_dot, UP) + end_sign = plus_sign.copy().next_to(end_dot, UP) + return Group(start_sign, end_sign, line, start_dot, end_dot) + + def pair_indicate(a, b): + self.play( + CircleIndicate(a), + CircleIndicate(b) + ) + + left_interval = make_interval(2 * LEFT, LEFT) + right_interval = make_interval(RIGHT, 2 * RIGHT) + + self.play(FadeIn(left_interval), FadeIn(right_interval)) + + pair_indicate(left_interval[0], left_interval[1]) + + pair_indicate(right_interval[0], right_interval[1]) + + self.play( + ApplyMethod(left_interval.shift, RIGHT), + ApplyMethod(right_interval.shift, LEFT), + ) + + pair_indicate(left_interval[0], right_interval[1]) + + self.wait() + # TODO: Brouwer's fixed point theorem visuals # class BFTScene(Scene):