diff --git a/camera.py b/camera.py index dca4a501..8737a54b 100644 --- a/camera.py +++ b/camera.py @@ -84,15 +84,38 @@ class Camera(object): def reset(self): self.set_pixel_array(np.array(self.background)) + #### + + def extract_mobject_family_members(self, mobjects, only_those_with_points = False): + if only_those_with_points: + method = Mobject.family_members_with_points + else: + method = Mobject.submobject_family + return remove_list_redundancies(list( + it.chain(*[ + method(m) + for m in mobjects + if not (isinstance(m, VMobject) and m.is_subpath) + ]) + )) + def capture_mobject(self, mobject): return self.capture_mobjects([mobject]) - def capture_mobjects(self, mobjects, include_submobjects = True): + def capture_mobjects( + self, mobjects, + include_submobjects = True, + excluded_mobjects = None, + ): if include_submobjects: - mobjects = it.chain(*[ - mob.family_members_with_points() - for mob in mobjects - ]) + mobjects = self.extract_mobject_family_members( + mobjects, only_those_with_points = True + ) + if excluded_mobjects: + all_excluded = self.extract_mobject_family_members( + excluded_mobjects + ) + mobjects = list_difference_update(mobjects, all_excluded) vmobjects = [] for mobject in mobjects: if isinstance(mobject, VMobject): diff --git a/constants.py b/constants.py index 78a2bad9..6efe1a1d 100644 --- a/constants.py +++ b/constants.py @@ -43,7 +43,7 @@ DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = MED_SMALL_BUFF #All in seconds DEFAULT_ANIMATION_RUN_TIME = 1.0 DEFAULT_POINTWISE_FUNCTION_RUN_TIME = 3.0 -DEFAULT_wait_TIME = 1.0 +DEFAULT_WAIT_TIME = 1.0 ORIGIN = np.array(( 0., 0., 0.)) diff --git a/mobject/mobject.py b/mobject/mobject.py index 797efbda..8f7e0ae1 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -454,7 +454,6 @@ class Mobject(object): return self def submobject_radial_gradient_highlight(self, center = None, radius = 1, inner_color = WHITE, outer_color = BLACK): - mobs = self.family_members_with_points() if center == None: center = self.get_center() @@ -462,14 +461,11 @@ class Mobject(object): for mob in self.family_members_with_points(): t = np.linalg.norm(mob.get_center() - center)/radius t = min(t,1) - print t mob_color = interpolate_color(inner_color, outer_color, t) - print mob_color mob.highlight(mob_color, family = False) return self - def set_color(self, color): self.highlight(color) self.color = Color(color) diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index d6d53441..deed8d98 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -14,8 +14,9 @@ class VMobject(Mobject): "is_subpath" : False, "close_new_points" : False, "mark_paths_closed" : False, - "considered_smooth" : True, "propagate_style_to_family" : False, + "pre_function_handle_to_anchor_scale_factor" : 0.01, + "make_smooth_after_applying_functions" : False, } ## Colors @@ -191,7 +192,6 @@ class VMobject(Mobject): return self def make_smooth(self): - self.considered_smooth = True return self.change_anchor_mode("smooth") def make_jagged(self): @@ -232,12 +232,35 @@ class VMobject(Mobject): self.submobjects ) - def apply_function(self, function, maintain_smoothness = False): + def apply_function(self, function): + factor = self.pre_function_handle_to_anchor_scale_factor + self.scale_handle_to_anchor_distances(factor) Mobject.apply_function(self, function) - if maintain_smoothness and self.considered_smooth: + self.scale_handle_to_anchor_distances(1./factor) + if self.make_smooth_after_applying_functions: self.make_smooth() return self + def scale_handle_to_anchor_distances(self, factor): + """ + If the distance between a given handle point H and its associated + anchor point A is d, then it changes H to be a distances factor*d + away from A, but so that the line from A to H doesn't change. + + This is mostly useful in the context of applying a (differentiable) + function, to preserve tangency properties. One would pull all the + handles closer to their anchors, apply the function then push them out + again. + """ + if self.get_num_points() == 0: + return + anchors, handles1, handles2 = self.get_anchors_and_handles() + # print len(anchors), len(handles1), len(handles2) + a_to_h1 = handles1 - anchors[:-1] + a_to_h2 = handles2 - anchors[1:] + handles1 = anchors[:-1] + factor*a_to_h1 + handles2 = anchors[1:] + factor*a_to_h2 + self.set_anchors_and_handles(anchors, handles1, handles2) ## Information about line @@ -271,8 +294,7 @@ class VMobject(Mobject): return self.get_anchors() - ## Alignment - + ## Alignment def align_points(self, mobject): Mobject.align_points(self, mobject) is_subpath = self.is_subpath or mobject.is_subpath @@ -280,7 +302,7 @@ class VMobject(Mobject): mark_closed = self.mark_paths_closed and mobject.mark_paths_closed self.mark_paths_closed = mobject.mark_paths_closed = mark_closed return self - + def align_points_with_larger(self, larger_mobject): assert(isinstance(larger_mobject, VMobject)) self.insert_n_anchor_points( @@ -288,7 +310,7 @@ class VMobject(Mobject): self.get_num_anchor_points() ) return self - + def insert_n_anchor_points(self, n): curr = self.get_num_anchor_points() if curr == 0: @@ -318,17 +340,17 @@ class VMobject(Mobject): ) self.set_points(points) return self - + def get_point_mobject(self, center = None): if center is None: center = self.get_center() return VectorizedPoint(center) - + def repeat_submobject(self, submobject): if submobject.is_subpath: return VectorizedPoint(submobject.points[0]) return submobject.copy() - + def interpolate_color(self, mobject1, mobject2, alpha): attrs = [ "stroke_rgb", @@ -345,7 +367,7 @@ class VMobject(Mobject): if alpha == 1.0: # print getattr(mobject2, attr) setattr(self, attr, getattr(mobject2, attr)) - + def pointwise_become_partial(self, mobject, a, b): assert(isinstance(mobject, VMobject)) #Partial curve includes three portions: diff --git a/scene/scene.py b/scene/scene.py index 823b642d..ccbf9df6 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -123,19 +123,23 @@ class Scene(object): def capture_mobjects_in_camera(self, mobjects, **kwargs): self.camera.capture_mobjects(mobjects, **kwargs) - def update_frame(self, mobjects = None, background = None, **kwargs): - if "include_submobjects" not in kwargs: - kwargs["include_submobjects"] = False + def update_frame( + self, + mobjects = None, + background = None, + include_submobjects = True, + **kwargs): if mobjects is None: mobjects = list_update( - self.foreground_mobjects, self.mobjects, + self.foreground_mobjects, ) if background is not None: self.set_camera_pixel_array(background) else: self.reset_camera() + kwargs["include_submobjects"] = include_submobjects self.capture_mobjects_in_camera(mobjects, **kwargs) def freeze_background(self): @@ -167,15 +171,6 @@ class Scene(object): ### - def extract_mobject_family_members(self, *mobjects): - return remove_list_redundancies(list( - it.chain(*[ - m.submobject_family() - for m in mobjects - if not (isinstance(m, VMobject) and m.is_subpath) - ]) - )) - def get_top_level_mobjects(self): # Return only those which are not in the family # of another mobject from the scene @@ -209,14 +204,10 @@ class Scene(object): """ Mobjects will be displayed, from background to foreground, in the order with which they are entered. - - Scene class keeps track not just of the mobject directly added, - but also of every family member therein. """ mobjects, continual_animations = self.separate_mobjects_and_continual_animations( mobjects_or_continual_animations ) - mobjects = self.extract_mobject_family_members(*mobjects) self.mobjects = list_update(self.mobjects, mobjects) self.continual_animations = list_update( self.continual_animations, continual_animations @@ -236,31 +227,38 @@ class Scene(object): mobjects, continual_animations = self.separate_mobjects_and_continual_animations( mobjects_or_continual_animations ) - mobjects = self.extract_mobject_family_members(*mobjects) - self.mobjects = filter( - lambda m : m not in mobjects, - self.mobjects - ) - self.remove_mobjects_not_completely_on_screen() - self.remove_foreground_mobjects(*mobjects) + to_remove = self.camera.extract_mobject_family_members(mobjects) + self.mobjects = self.get_restructured_mobject_list(self.mobjects, to_remove) + self.foreground_mobjects = self.get_restructured_mobject_list( + self.foreground_mobjects, to_remove + ) self.continual_animations = filter( lambda ca : ca not in continual_animations and \ - ca.mobject not in mobjects, + ca.mobject not in to_remove, self.continual_animations ) - return self - def remove_mobjects_not_completely_on_screen(self): - def should_keep(mobject): - return all([ - submob in self.mobjects - for submob in mobject.family_members_with_points() - ]) - - self.mobjects = filter(should_keep, self.mobjects) - return self + def get_restructured_mobject_list(self, mobjects, to_remove): + """ + In cases where the scene contains a group, e.g. Group(m1, m2, m3), but one + of its submobjects is removed, e.g. scene.remove(m1), the list of mobjects + will be editing to contain other submobjects, but not m1, e.g. it will now + insert m2 and m3 to where the group once was. + """ + new_mobjects = [] + def add_safe_mobjects_from_list(list_to_examine, set_to_remove): + for mob in list_to_examine: + if mob in set_to_remove: + continue + intersect = set_to_remove.intersection(mob.submobject_family()) + if intersect: + add_safe_mobjects_from_list(mob.submobjects, intersect) + else: + new_mobjects.append(mob) + add_safe_mobjects_from_list(mobjects, set(to_remove)) + return new_mobjects def add_foreground_mobjects(self, *mobjects): self.foreground_mobjects = list_update( @@ -274,9 +272,9 @@ class Scene(object): return self.add_foreground_mobjects(mobject) def remove_foreground_mobjects(self, *mobjects): - self.foreground_mobjects = filter( - lambda m : m not in mobjects, - self.foreground_mobjects + self.foreground_mobjects = get_restructured_mobject_list( + self.foreground_mobjects, + self.camera.extract_mobject_family_members(mobjects) ) return self @@ -289,7 +287,7 @@ class Scene(object): def bring_to_back(self, *mobjects): self.remove(*mobjects) - self.mobjects = mobjects + self.mobjects + self.mobjects = list(mobjects) + self.mobjects return self def clear(self): @@ -304,17 +302,13 @@ class Scene(object): def get_mobject_copies(self): return [m.copy() for m in self.mobjects] - def separate_moving_and_static_mobjects(self, *animations): - moving_mobjects = self.extract_mobject_family_members(*it.chain( + def get_moving_mobjects(self, *animations): + moving_mobjects = list(it.chain( [anim.mobject for anim in animations], [ca.mobject for ca in self.continual_animations], self.foreground_mobjects, )) - static_mobjects = filter( - lambda m : m not in moving_mobjects, - self.mobjects - ) - return moving_mobjects, static_mobjects + return moving_mobjects def get_time_progression(self, run_time): times = np.arange(0, run_time, self.frame_duration) @@ -395,9 +389,8 @@ class Scene(object): self.num_plays += 1 sync_animation_run_times_and_rate_funcs(*animations, **kwargs) - moving_mobjects, static_mobjects = \ - self.separate_moving_and_static_mobjects(*animations) - self.update_frame(static_mobjects) + moving_mobjects = self.get_moving_mobjects(*animations) + self.update_frame(excluded_mobjects = moving_mobjects) static_image = self.get_frame() for t in self.get_animation_time_progression(animations): for animation in animations: @@ -422,7 +415,7 @@ class Scene(object): return self.mobjects_from_last_animation return [] - def wait(self, duration = DEFAULT_wait_TIME): + def wait(self, duration = DEFAULT_WAIT_TIME): if self.skip_animations: return self @@ -527,3 +520,7 @@ class Scene(object): shutil.move(*self.args_to_rename_file) else: os.rename(*self.args_to_rename_file) + + + + diff --git a/scene/zoomed_scene.py b/scene/zoomed_scene.py index 7b197667..52a7403d 100644 --- a/scene/zoomed_scene.py +++ b/scene/zoomed_scene.py @@ -122,15 +122,13 @@ class ZoomedScene(Scene): self.zoomed_camera.capture_mobjects( mobjects, **kwargs ) - def separate_moving_and_static_mobjects(self, *animations): - moving_mobjects, static_mobjects = Scene.separate_moving_and_static_mobjects( - self, *animations - ) + def get_moving_mobjects(self, *animations): + moving_mobjects = Scene.get_moving_mobjects(self, *animations) if self.zoom_activated and self.little_rectangle in moving_mobjects: # When the camera is moving, so is everything, - return self.get_mobjects(), [] + return self.mobjects else: - return moving_mobjects, static_mobjects + return moving_mobjects diff --git a/topics/geometry.py b/topics/geometry.py index 1883ad03..eadb6889 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -114,7 +114,6 @@ class Sector(VMobject): class Line(VMobject): CONFIG = { "buff" : 0, - "considered_smooth" : False, "path_arc" : None, "n_arc_anchors" : 10, #Only used if path_arc is not None } @@ -132,7 +131,6 @@ class Line(VMobject): path_func(self.start, self.end, alpha) for alpha in np.linspace(0, 1, self.n_arc_anchors) ]) - self.considered_smooth = True self.account_for_buff() def account_for_buff(self): @@ -449,7 +447,6 @@ class Polygon(VMobject): "color" : GREEN_D, "mark_paths_closed" : True, "close_new_points" : True, - "considered_smooth" : False, } def __init__(self, *vertices, **kwargs): assert len(vertices) > 1 @@ -479,7 +476,6 @@ class Rectangle(VMobject): "width" : 4.0, "mark_paths_closed" : True, "close_new_points" : True, - "considered_smooth" : False, } def generate_points(self): y, x = self.height/2., self.width/2. @@ -586,7 +582,6 @@ class Grid(VMobject): CONFIG = { "height" : 6.0, "width" : 6.0, - "considered_smooth" : False, } def __init__(self, rows, columns, **kwargs): digest_config(self, kwargs, locals()) diff --git a/topics/number_line.py b/topics/number_line.py index 7fa6c40f..3b631b67 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -223,6 +223,7 @@ class NumberPlane(VMobject): "secondary_line_ratio" : 1, "written_coordinate_height" : 0.2, "propagate_style_to_family" : False, + "make_smooth_after_applying_functions" : True, } def generate_points(self): if self.x_radius is None: @@ -360,9 +361,6 @@ class NumberPlane(VMobject): mob.make_smooth() return self - def apply_function(self, function, maintain_smoothness = True): - VMobject.apply_function(self, function, maintain_smoothness = maintain_smoothness) - diff --git a/topics/numerals.py b/topics/numerals.py index a7714ba8..a4437df4 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -67,8 +67,9 @@ class ChangingDecimal(Animation): for x in range(self.spare_parts)] ) if self.tracked_mobject: - self.diff_from_tracked_mobject = \ - decimal_number_mobject.get_center() - self.tracked_mobject.get_center() + dmc = decimal_number_mobject.get_center() + tmc = self.tracked_mobject.get_center() + self.diff_from_tracked_mobject = dmc - tmc Animation.__init__(self, decimal_number_mobject, **kwargs) def update_mobject(self, alpha): @@ -85,14 +86,9 @@ class ChangingDecimal(Animation): ) new_decimal.replace(decimal, dim_to_match = 1) new_decimal.highlight(decimal.get_color()) - decimal.align_data(new_decimal) - families = [ - mob.family_members_with_points() - for mob in decimal, new_decimal - ] - for sm1, sm2 in zip(*families): - sm1.interpolate(sm1, sm2, 1) - self.mobject.number = new_number + + decimal.submobjects = new_decimal.submobjects + decimal.number = new_number def update_position(self): if self.position_update_func is not None: diff --git a/topics/objects.py b/topics/objects.py index bedc7b25..66434722 100644 --- a/topics/objects.py +++ b/topics/objects.py @@ -267,7 +267,6 @@ class VideoIcon(SVGMobject): CONFIG = { "file_name" : "video_icon", "width" : 2*SPACE_WIDTH/12., - "considered_smooth" : False, } def __init__(self, **kwargs): SVGMobject.__init__(self, **kwargs) @@ -275,8 +274,6 @@ class VideoIcon(SVGMobject): self.scale_to_fit_width(self.width) self.set_stroke(color = WHITE, width = 0) self.set_fill(color = WHITE, opacity = 1) - for mob in self: - mob.considered_smooth = False class VideoSeries(VGroup): CONFIG = { diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index 5e1cc47c..9a5faed1 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -175,11 +175,10 @@ class ThreeDScene(Scene): if is_camera_rotating: self.add(self.ambient_camera_rotation) - def separate_moving_and_static_mobjects(self, *animations): - moving, static = Scene.separate_moving_and_static_mobjects(self, *animations) + def get_moving_mobjects(self, *animations): if self.camera.rotation_mobject in moving: - return moving + static, [] - return moving, static + return self.mobjects + return Scene.get_moving_mobjects(self, *animations) ##############