diff --git a/animation/animation.py b/animation/animation.py index 14b7b567..dcd72f6d 100644 --- a/animation/animation.py +++ b/animation/animation.py @@ -18,6 +18,9 @@ class Animation(object): "name" : None, #Does this animation add or remove a mobject form the screen "remover" : False, + #Options are lagged_start, smoothed_lagged_start, + #one_at_a_time, all_at_once + "submobject_mode" : "lagged_start", } def __init__(self, mobject, **kwargs): mobject = instantiate(mobject) @@ -31,8 +34,6 @@ class Animation(object): self.update(0) def update_config(self, **kwargs): - if "path_arc" in kwargs: - kwargs["path_arc"] digest_config(self, kwargs) return self @@ -49,6 +50,40 @@ class Animation(object): alpha = 1.0 self.update_mobject(self.rate_func(alpha)) + def update_mobject(self, alpha): + families = self.get_all_families_zipped() + for i, mobs in enumerate(families): + sub_alpha = self.get_sub_alpha(alpha, i, len(families)) + self.update_submobject(*list(mobs) + [sub_alpha]) + return self + + def update_submobject(self, submobject, starting_sumobject, alpha): + #Typically ipmlemented by subclass + pass + + def get_all_families_zipped(self): + """ + Ordering must match the ording of arguments to update_submobject + """ + return zip( + self.mobject.submobject_family(), + self.starting_mobject.submobject_family() + ) + + def get_sub_alpha(self, alpha, index, num_submobjects): + if self.submobject_mode in ["lagged_start", "smoothed_lagged_start"]: + prop = float(index)/num_submobjects + if self.submobject_mode is "smoothed_lagged_start": + prop = smooth(prop) + return np.clip(2*alpha - prop, 0, 1) + elif self.submobject_mode == "one_at_a_time": + lower = float(index)/num_submobjects + upper = float(index+1)/num_submobjects + return np.clip((alpha-lower)/(upper-lower), 0, 1) + elif self.submobject_mode == "all_at_once": + return alpha + raise Exception("Invalid submobject mode") + def filter_out(self, *filter_functions): self.filter_functions += filter_functions return self @@ -71,11 +106,7 @@ class Animation(object): def set_name(self, name): self.name = name - return self - - def update_mobject(self, alpha): - #Typically ipmlemented by subclass - pass + return self def is_remover(self): return self.remover diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 16816b33..ebe54e8e 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -18,14 +18,12 @@ class Rotating(Animation): "rate_func" : None, "in_place" : True, } + def update_submobject(self, submobject, starting_mobject, alpha): + submobject.points = starting_submobject.points + def update_mobject(self, alpha): + Animation.update_mobject(self, alpha) axes = [self.axis] if self.axis is not None else self.axes - families = [ - self.mobject.submobject_family(), - self.starting_mobject.submobject_family() - ] - for mob, start in zip(*families): - mob.points = np.array(start.points) if self.in_place: method = self.mobject.rotate_in_place else: @@ -34,14 +32,9 @@ class Rotating(Animation): class ShowPartial(Animation): - CONFIG = { - "submobject_mode" : None - } - def update_mobject(self, alpha): - self.mobject.become_partial( - self.starting_mobject, - *self.get_bounds(alpha), - submobject_partial_creation_mode = self.submobject_mode + def update_submobject(self, submobject, starting_submobject, alpha): + submobject.pointwise_become_partial( + starting_submobject, *self.get_bounds(alpha) ) def get_bounds(self, alpha): @@ -83,32 +76,6 @@ class ShowPassingFlash(ShowPartial): return (lower, upper) -class Flash(Animation): - CONFIG = { - "color" : "white", - "slow_factor" : 0.01, - "run_time" : 0.1, - "rate_func" : None, - } - def __init__(self, mobject, **kwargs): - self.intermediate = Mobject(color = self.color) - self.intermediate.add_points([ - point + (x, y, 0) - for point in self.mobject.points - for x in [-1, 1] - for y in [-1, 1] - ]) - Animation.__init__(self, mobject, **kwargs) - - def update_mobject(self, alpha): - #Makes alpha go from 0 to slow_factor to 0 instead of 0 to 1 - alpha = self.slow_factor * (1.0 - 4 * (alpha - 0.5)**2) - self.mobject.interpolate( - self.starting_mobject, - self.intermediate, - alpha - ) - class MoveAlongPath(Animation): def __init__(self, mobject, vmobject, **kwargs): digest_config(self, kwargs, locals()) @@ -131,16 +98,12 @@ class Homotopy(Animation): digest_config(self, kwargs) Animation.__init__(self, mobject, **kwargs) + def update_submobject(self, submob, start, alpha): + submob.points = start.points + def update_mobject(self, alpha): - pairs = zip( - self.mobject.submobject_family(), - self.starting_mobject.submobject_family() - ) - for mob, start_mob in pairs: - mob.become_partial(start_mob, 0, 1) - self.mobject.apply_function( - self.function_at_time_t(alpha) - ) + Animation.update_mobject(self, alpha) + submob.apply_function(self.function_at_time_t(alpha)) class PhaseFlow(Animation): diff --git a/animation/transform.py b/animation/transform.py index 50f73486..1db70193 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -39,14 +39,15 @@ class Transform(Animation): else: self.path_func = path_along_arc(self.path_arc) - - def update_mobject(self, alpha): - families = map( + def get_all_families_zipped(self): + return zip(*map( Mobject.submobject_family, [self.mobject, self.starting_mobject, self.ending_mobject] - ) - for m, start, end in zip(*families): - m.interpolate(start, end, alpha, self.path_func) + )) + + def update_submobject(self, submob, start, end, alpha): + submob.interpolate(start, end, alpha, self.path_func) + return self class ClockwiseTransform(Transform): @@ -81,6 +82,9 @@ class ShrinkToCenter(Transform): ) class ApplyMethod(Transform): + CONFIG = { + "submobject_mode" : "all_at_once" + } def __init__(self, method, *args, **kwargs): """ Method is a method of Mobject. *args is for the method, @@ -88,7 +92,11 @@ class ApplyMethod(Transform): Relies on the fact that mobject methods return the mobject """ - assert(inspect.ismethod(method)) + if not inspect.ismethod(method): + raise Exception( + "Whoops, looks like you accidentally invoked " + \ + "the method you want to animate" + ) assert(isinstance(method.im_self, Mobject)) Transform.__init__( self, @@ -119,10 +127,7 @@ class FadeIn(Transform): if isinstance(mobject, VMobject): mobject.set_stroke(width = 0) Transform.__init__(self, mobject, target, **kwargs) - # self.mobject.rgbs = self.starting_mobject.rgbs * alpha - # if self.mobject.points.shape != self.starting_mobject.points.shape: - # self.mobject.points = self.starting_mobject.points - # #TODO, Why do you need to do this? Shouldn't points always align? + class ShimmerIn(DelayByOrder): def __init__(self, mobject, **kwargs): diff --git a/eola/chapter3.py b/eola/chapter3.py index 8998c76c..50b4702e 100644 --- a/eola/chapter3.py +++ b/eola/chapter3.py @@ -288,16 +288,144 @@ class RotateIHat(LinearTransformationScene): self.setup() i_hat, j_hat = self.get_basis_vectors() i_label, j_label = self.get_basis_vector_labels() - self.play(ShowCreation(i_hat)) + self.add_vector(i_hat) self.play(Write(i_label, run_time = 1)) self.dither() self.play(FadeOut(i_label)) self.apply_transposed_matrix([[0, 1], [-1, 0]]) + self.dither() self.play(Write(j_label, run_time = 1)) self.dither() +class TransformationsAreFunctions(Scene): + def construct(self): + title = TextMobject([ + """Linear transformations are a + special kind of""", + "function" + ]) + title_start, function = title.split() + function.highlight(YELLOW) + title.to_edge(UP) + + equation = TexMobject([ + "L", + "(", + "\\vec{\\textbf{v}}", + ") = ", + "\\vec{\\textbf{w}}", + ]) + L, lp, _input, equals, _output = equation.split() + L.highlight(YELLOW) + _input.highlight(MAROON_C) + _output.highlight(BLUE) + equation.scale(2) + equation.next_to(title, DOWN, buff = 1) + + starting_vector = TextMobject("Starting vector") + starting_vector.shift(DOWN+3*LEFT) + starting_vector.highlight(MAROON_C) + ending_vector = TextMobject("The vector where it lands") + ending_vector.shift(DOWN).to_edge(RIGHT) + ending_vector.highlight(BLUE) + + func_arrow = Arrow(function.get_bottom(), L.get_top(), color = YELLOW) + start_arrow = Arrow(starting_vector.get_top(), _input.get_bottom(), color = MAROON_C) + ending_arrow = Arrow(ending_vector, _output, color = BLUE) + + + self.add(title) + self.play( + Write(equation), + ShowCreation(func_arrow) + ) + for v, a in [(starting_vector, start_arrow), (ending_vector, ending_arrow)]: + self.play(Write(v), ShowCreation(a), run_time = 1) + self.dither() + + +class UsedToThinkinfOfFunctionsAsGraphs(VectorScene): + def construct(self): + self.show_graph() + self.show_inputs_and_output() + + def show_graph(self): + axes = self.add_axes() + graph = FunctionGraph(lambda x : x**2, x_min = -2, x_max = 2) + name = TexMobject("f(x) = x^2") + name.next_to(graph, RIGHT).to_edge(UP) + point = Dot(graph.point_from_proportion(0.8)) + point_label = TexMobject("(x, x^2)") + point_label.next_to(point, DOWN+RIGHT, buff = 0.1) + + self.play(ShowCreation(graph)) + self.play(Write(name, run_time = 1)) + self.play( + ShowCreation(point), + Write(point_label), + run_time = 1 + ) + self.dither() + + def collapse_func(p): + return np.dot(p, [RIGHT, RIGHT, OUT]) + (SPACE_HEIGHT+1)*DOWN + self.play( + ApplyPointwiseFunction( + collapse_func, axes, + submobject_mode = "all_at_once", + ), + ApplyPointwiseFunction(collapse_func, graph), + ApplyMethod(point.shift, 10*DOWN), + ApplyMethod(point_label.shift, 10*DOWN), + ApplyFunction(lambda m : m.center().to_edge(UP), name), + run_time = 1 + ) + self.clear() + self.add(name) + self.dither() + + def show_inputs_and_output(self): + numbers = range(-3, 4) + inputs = VMobject(*map(TexMobject, map(str, numbers))) + inputs.arrange_submobjects(DOWN, buff = 0.5, aligned_edge = RIGHT) + arrows = VMobject(*[ + Arrow(LEFT, RIGHT).next_to(mob) + for mob in inputs.split() + ]) + outputs = VMobject(*[ + TexMobject(str(num**2)).next_to(arrow) + for num, arrow in zip(numbers, arrows.split()) + ]) + everyone = VMobject(inputs, arrows, outputs) + everyone.center().to_edge(UP, buff = 1.5) + + self.play(Write(inputs, run_time = 1)) + self.dither() + self.play( + Transform(inputs.copy(), outputs), + ShowCreation(arrows) + ) + self.dither() + + + + + + + + + + + + + + + + + + diff --git a/eola/two_d_space.py b/eola/two_d_space.py index 792f57ca..f30eb2b4 100644 --- a/eola/two_d_space.py +++ b/eola/two_d_space.py @@ -46,11 +46,11 @@ class VectorScene(Scene): self.add(axes) self.freeze_background() - def add_vector(self, vector, animate = True, color = YELLOW): + def add_vector(self, vector, color = YELLOW, animate = True): if not isinstance(vector, Arrow): vector = Vector(vector, color = color) if animate: - self.play(ShowCreation(vector, submobject_mode = "one_at_a_time")) + self.play(ShowCreation(vector)) self.add(vector) return vector @@ -66,12 +66,11 @@ class VectorScene(Scene): ] ] - def get_basis_vector_labels(self, animate = False, **kwargs): + def get_basis_vector_labels(self, **kwargs): i_hat, j_hat = self.get_basis_vectors() return [ - self.label_vector( + self.get_vector_label( vect, label, color = color, - animate = animate, label_scale_val = 1, **kwargs ) @@ -81,11 +80,12 @@ class VectorScene(Scene): ] ] - def label_vector(self, vector, label, animate = True, - direction = "left", rotate = False, - color = WHITE, add_to_vector = False, - buff_factor = 2, - label_scale_val = VECTOR_LABEL_SCALE_VAL): + def get_vector_label(self, vector, label, + direction = "left", + rotate = False, + color = WHITE, + buff_factor = 2, + label_scale_val = VECTOR_LABEL_SCALE_VAL): if len(label) == 1: label = "\\vec{\\textbf{%s}}"%label label = TexMobject(label) @@ -103,9 +103,11 @@ class VectorScene(Scene): boundary_point = label.get_critical_point(boundary_dir) label.shift(buff_factor*boundary_point) label.shift(vector_vect/2.) + return label - if add_to_vector: - vector.add(label) + + def label_vector(self, vector, label, animate = True, **kwargs): + label = self.get_vector_label(vector, label, **kwargs) if animate: self.play(Write(label, run_time = 1)) self.add(label) @@ -268,8 +270,8 @@ class LinearTransformationScene(VectorScene): self.plane = NumberPlane(**self.foreground_plane_kwargs) self.add_transformable_mobject(self.plane) if self.show_basis_vectors: - self.add_vector((1, 0), self.i_hat_color) - self.add_vector((0, 1), self.j_hat_color) + self.add_vector((1, 0), self.i_hat_color, animate = False) + self.add_vector((0, 1), self.j_hat_color, animate = False) def add_background_mobject(self, *mobjects): for mobject in mobjects: @@ -283,9 +285,9 @@ class LinearTransformationScene(VectorScene): self.transformable_mobject.append(mobject) self.add(mobject) - def add_vector(self, vector, color = YELLOW, animate = False): + def add_vector(self, vector, color = YELLOW, **kwargs): vector = VectorScene.add_vector( - self, vector, color = color, animate = animate + self, vector, color = color, **kwargs ) self.moving_vectors.append(vector) return vector diff --git a/mobject/mobject.py b/mobject/mobject.py index b83a44fb..11fe2fe0 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -22,10 +22,6 @@ class Mobject(object): "name" : None, "dim" : 3, "target" : None, - #Options are lagged_start, smoothed_lagged_start, - #one_at_a_time, all_at_once - "submobject_partial_creation_mode" : "lagged_start", - #TODO, probably make this Animations's responsibility? } def __init__(self, *submobjects, **kwargs): digest_config(self, kwargs) @@ -548,7 +544,7 @@ class Mobject(object): def interpolate_color(self, mobject1, mobject2, alpha): raise Exception("Not implemented") - def become_partial(self, mobject, a, b, submobject_partial_creation_mode = None): + def become_partial(self, mobject, a, b): """ Set points in such a way as to become only part of mobject. @@ -557,29 +553,6 @@ class Mobject(object): """ #TODO, color? - spcm = submobject_partial_creation_mode or self.submobject_partial_creation_mode - pairs = zip( - self.family_members_with_points(), - mobject.family_members_with_points() - ) - for i, (self_sub, mob_sub) in enumerate(pairs): - if spcm in ["lagged_start", "smoothed_lagged_start"]: - prop = float(i)/len(pairs) - if spcm is "smoothed_lagged_start": - prop = smooth(prop) - sub_a = np.clip(2*a - prop, 0, 1) - sub_b = np.clip(2*b - prop, 0, 1) - elif spcm == "one_at_a_time": - lower = float(i)/len(pairs) - upper = float(i+1)/len(pairs) - sub_a = np.clip((a-lower)/(upper-lower), 0, 1) - sub_b = np.clip((b-lower)/(upper-lower), 0, 1) - elif spcm == "all_at_once": - sub_a, sub_b = a, b - else: - raise Exception("Invalid submobject partial creation mode") - self_sub.pointwise_become_partial(mob_sub, sub_a, sub_b) - return self def pointwise_become_partial(self, mobject, a, b): raise Exception("Not implemented") diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index afd57433..8a1dc9bb 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -41,24 +41,26 @@ class VMobject(Mobject): fill_color = None, fill_opacity = None, family = True): - if stroke_color is not None: - setattr(self, "stroke_rgb", color_to_rgb(stroke_color)) - if stroke_width is not None: - setattr(self, "stroke_width", stroke_width) - if fill_color is not None: - setattr(self, "fill_rgb", color_to_rgb(fill_color)) - if fill_opacity is not None: - setattr(self, "fill_opacity", fill_opacity) - if family: - kwargs = locals() - kwargs.pop("self") - for mob in self.submobjects: - mob.set_style_data(**kwargs) + mobs = self.submobject_family() if family else [self] + for mob in mobs: + if stroke_color is not None: + mob.stroke_rgb = color_to_rgb(stroke_color) + if stroke_width is not None: + mob.stroke_width = stroke_width + if fill_color is not None: + mob.fill_rgb = color_to_rgb(fill_color) + if fill_opacity is not None: + mob.fill_opacity = fill_opacity + probably_meant_to_change_opacity = reduce(op.and_, [ + fill_color is not None, + fill_opacity is None, + mob.fill_opacity == 0 + ]) + if probably_meant_to_change_opacity: + mob.fill_opacity = 1 return self def set_fill(self, color = None, opacity = None, family = True): - if self.fill_opacity == 0 and opacity is None: - opacity = 1 return self.set_style_data( fill_color = color, fill_opacity = opacity, @@ -73,11 +75,7 @@ class VMobject(Mobject): ) def highlight(self, color, family = True): - self.set_fill( - color = color, - opacity = self.get_fill_opacity(), - family = family - ) + self.set_fill(color = color, family = family) self.set_stroke(color = color, family = family) return self diff --git a/topics/geometry.py b/topics/geometry.py index 445127c3..01199da1 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -133,7 +133,6 @@ class Arrow(Line): "buff" : 0.3, "propogate_style_to_family" : False, "preserve_tip_size_when_scaling" : True, - "submobject_partial_creation_mode" : "one_at_a_time", } def __init__(self, *args, **kwargs): if len(args) == 1: