Introduced update_submobject to Animation. I'd be surprised if this didn't break something.

This commit is contained in:
Grant Sanderson
2016-07-22 15:50:52 -07:00
parent 627f16b057
commit 46e5dbfa14
8 changed files with 232 additions and 133 deletions

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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()

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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: