2015-06-10 22:00:35 -07:00
|
|
|
import numpy as np
|
|
|
|
import itertools as it
|
|
|
|
import inspect
|
|
|
|
import copy
|
|
|
|
import warnings
|
|
|
|
|
|
|
|
from animation import Animation
|
2015-10-06 15:27:12 -07:00
|
|
|
from mobject import Mobject, Point
|
2015-06-10 22:00:35 -07:00
|
|
|
from constants import *
|
|
|
|
from helpers import *
|
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
def straight_path(start_points, end_points, alpha):
|
|
|
|
return (1-alpha)*start_points + alpha*end_points
|
|
|
|
|
|
|
|
def semi_circular_path(start_points, end_points, alpha, axis):
|
|
|
|
midpoints = (start_points + end_points) / 2
|
|
|
|
angle = alpha * np.pi
|
|
|
|
rot_matrix = rotation_matrix(angle, axis)[:2, :2]
|
|
|
|
result = np.zeros(start_points.shape)
|
|
|
|
result[:,:2] = np.dot(
|
|
|
|
(start_points - midpoints)[:,:2],
|
|
|
|
np.transpose(rot_matrix)
|
|
|
|
) + midpoints[:,:2]
|
|
|
|
result[:,2] = (1-alpha)*start_points[:,2] + alpha*end_points[:,2]
|
|
|
|
return result
|
|
|
|
|
|
|
|
def clockwise_path(start_points, end_points, alpha):
|
|
|
|
return semi_circular_path(start_points, end_points, alpha, IN)
|
|
|
|
|
|
|
|
def counterclockwise_path(start_points, end_points, alpha):
|
|
|
|
return semi_circular_path(start_points, end_points, alpha, OUT)
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
class Transform(Animation):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"run_time" : DEFAULT_TRANSFORM_RUN_TIME,
|
|
|
|
"interpolation_function" : straight_path,
|
|
|
|
"should_black_out_extra_points" : False
|
|
|
|
}
|
|
|
|
def __init__(self, mobject, ending_mobject, **kwargs):
|
|
|
|
digest_config(self, Transform, kwargs, locals())
|
|
|
|
count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points()
|
2015-06-10 22:00:35 -07:00
|
|
|
if count2 == 0:
|
2015-09-28 16:25:18 -07:00
|
|
|
ending_mobject.add_points([SPACE_WIDTH*RIGHT+SPACE_HEIGHT*UP])
|
|
|
|
count2 = ending_mobject.get_num_points()
|
|
|
|
Mobject.align_data(mobject, ending_mobject)
|
|
|
|
if self.should_black_out_extra_points and count2 < count1:
|
2015-09-25 19:43:53 -07:00
|
|
|
self.black_out_extra_points(count1, count2)
|
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
Animation.__init__(self, mobject, **kwargs)
|
|
|
|
self.name += "To" + str(ending_mobject)
|
|
|
|
self.mobject.should_buffer_points = \
|
|
|
|
mobject.should_buffer_points and ending_mobject.should_buffer_points
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2015-09-25 19:43:53 -07:00
|
|
|
def black_out_extra_points(self, count1, count2):
|
|
|
|
#Ensure redundant pixels fade to black
|
|
|
|
indices = np.arange(
|
|
|
|
0, count1-1, float(count1) / count2
|
|
|
|
).astype('int')
|
|
|
|
temp = np.zeros(self.ending_mobject.points.shape)
|
|
|
|
temp[indices] = self.ending_mobject.rgbs[indices]
|
|
|
|
self.ending_mobject.rgbs = temp
|
|
|
|
self.non_redundant_m2_indices = indices
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
def update_mobject(self, alpha):
|
2015-08-07 18:10:00 -07:00
|
|
|
self.mobject.points = self.interpolation_function(
|
|
|
|
self.starting_mobject.points,
|
|
|
|
self.ending_mobject.points,
|
|
|
|
alpha
|
|
|
|
)
|
|
|
|
self.mobject.rgbs = straight_path(
|
|
|
|
self.starting_mobject.rgbs,
|
|
|
|
self.ending_mobject.rgbs,
|
2015-06-10 22:00:35 -07:00
|
|
|
alpha
|
|
|
|
)
|
|
|
|
|
|
|
|
def clean_up(self):
|
2015-06-19 08:31:02 -07:00
|
|
|
Animation.clean_up(self)
|
2015-06-10 22:00:35 -07:00
|
|
|
if hasattr(self, "non_redundant_m2_indices"):
|
|
|
|
#Reduce mobject (which has become identical to mobject2), as
|
|
|
|
#well as mobject2 itself
|
|
|
|
for mobject in [self.mobject, self.ending_mobject]:
|
|
|
|
for attr in ['points', 'rgbs']:
|
|
|
|
setattr(
|
|
|
|
mobject, attr,
|
|
|
|
getattr(
|
|
|
|
self.ending_mobject,
|
|
|
|
attr
|
|
|
|
)[self.non_redundant_m2_indices]
|
|
|
|
)
|
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
class ClockwiseTransform(Transform):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"interpolation_function" : clockwise_path
|
|
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
digest_config(self, ClockwiseTransform, kwargs)
|
|
|
|
Transform.__init__(self, *args, **kwargs)
|
2015-06-13 19:00:23 -07:00
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
class CounterclockwiseTransform(Transform):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"interpolation_function" : counterclockwise_path
|
|
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
digest_config(self, ClockwiseTransform, kwargs)
|
|
|
|
Transform.__init__(self, *args, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2015-09-18 14:22:59 -07:00
|
|
|
class SpinInFromNothing(Transform):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"interpolation_function" : counterclockwise_path
|
|
|
|
}
|
2015-09-18 14:22:59 -07:00
|
|
|
def __init__(self, mob, **kwargs):
|
2015-09-28 16:25:18 -07:00
|
|
|
digest_config(self, SpinInFromNothing, kwargs)
|
|
|
|
Transform.__init__(self, Point(mob.get_center()), mob, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
class ApplyMethod(Transform):
|
|
|
|
def __init__(self, method, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Method is a method of Mobject. *args is for the method,
|
|
|
|
**kwargs is for the transform itself.
|
|
|
|
|
|
|
|
Relies on the fact that mobject methods return the mobject
|
|
|
|
"""
|
|
|
|
if not inspect.ismethod(method) or \
|
|
|
|
not isinstance(method.im_self, Mobject):
|
|
|
|
raise "Not a valid Mobject method"
|
|
|
|
Transform.__init__(
|
|
|
|
self,
|
|
|
|
method.im_self,
|
|
|
|
copy.deepcopy(method)(*args),
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
class FadeToColor(ApplyMethod):
|
|
|
|
def __init__(self, mobject, color, **kwargs):
|
|
|
|
ApplyMethod.__init__(self, mobject.highlight, color, **kwargs)
|
|
|
|
|
|
|
|
class ScaleInPlace(ApplyMethod):
|
|
|
|
def __init__(self, mobject, scale_factor, **kwargs):
|
|
|
|
ApplyMethod.__init__(self, mobject.scale_in_place, scale_factor, **kwargs)
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
class ApplyFunction(Transform):
|
2015-10-05 17:45:13 -07:00
|
|
|
def __init__(self, mobject, function, **kwargs):
|
2015-08-07 18:10:00 -07:00
|
|
|
Transform.__init__(
|
|
|
|
self,
|
|
|
|
mobject,
|
|
|
|
function(copy.deepcopy(mobject)),
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
self.name = "ApplyFunctionTo"+str(mobject)
|
|
|
|
|
|
|
|
class ApplyPointwiseFunction(Transform):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"run_time" : DEFAULT_ANIMATION_RUN_TIME
|
|
|
|
}
|
2015-10-05 17:45:13 -07:00
|
|
|
def __init__(self, mobject, function, **kwargs):
|
2015-09-28 16:25:18 -07:00
|
|
|
digest_config(self, ApplyPointwiseFunction, kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
map_image = copy.deepcopy(mobject)
|
|
|
|
map_image.points = np.array(map(function, map_image.points))
|
2015-09-28 16:25:18 -07:00
|
|
|
Transform.__init__(self, mobject, map_image, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
self.name = "".join([
|
|
|
|
"Apply",
|
|
|
|
"".join([s.capitalize() for s in function.__name__.split("_")]),
|
|
|
|
"To" + str(mobject)
|
|
|
|
])
|
|
|
|
|
2015-08-12 14:24:36 -07:00
|
|
|
class ComplexFunction(ApplyPointwiseFunction):
|
2015-10-05 17:45:13 -07:00
|
|
|
def __init__(self, mobject, function, **kwargs):
|
2015-06-10 22:00:35 -07:00
|
|
|
def point_map(point):
|
|
|
|
x, y, z = point
|
|
|
|
c = np.complex(x, y)
|
|
|
|
c = function(c)
|
|
|
|
return c.real, c.imag, z
|
2015-10-05 17:45:13 -07:00
|
|
|
ApplyPointwiseFunction.__init__(self, mobject, point_map, *args, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
self.name = "ComplexFunction" + to_cammel_case(function.__name__)
|
|
|
|
#Todo, abstract away function naming'
|
|
|
|
|
|
|
|
|
2015-06-19 08:31:02 -07:00
|
|
|
class TransformAnimations(Transform):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"alpha_func" : squish_alpha_func(smooth)
|
|
|
|
}
|
|
|
|
def __init__(self, start_anim, end_anim, **kwargs):
|
|
|
|
digest_config(self, TransformAnimations, kwargs, locals())
|
2015-06-27 04:49:10 -07:00
|
|
|
if "run_time" in kwargs:
|
2015-09-28 16:25:18 -07:00
|
|
|
self.run_time = kwargs.pop("run_time")
|
|
|
|
else:
|
|
|
|
self.run_time = max(start_anim.run_time, end_anim.run_time)
|
|
|
|
for anim in start_anim, end_anim:
|
|
|
|
anim.set_run_time(self.run_time)
|
|
|
|
|
|
|
|
if start_anim.starting_mobject.get_num_points() != end_anim.starting_mobject.get_num_points():
|
|
|
|
Mobject.align_data(start_anim.starting_mobject, end_anim.starting_mobject)
|
2015-06-27 04:49:10 -07:00
|
|
|
for anim in start_anim, end_anim:
|
2015-09-28 16:25:18 -07:00
|
|
|
if hasattr(anim, "ending_mobject"):
|
|
|
|
Mobject.align_data(anim.starting_mobject, anim.ending_mobject)
|
|
|
|
|
|
|
|
Transform.__init__(self, start_anim.mobject, end_anim.mobject, **kwargs)
|
2015-06-19 08:31:02 -07:00
|
|
|
#Rewire starting and ending mobjects
|
|
|
|
start_anim.mobject = self.starting_mobject
|
|
|
|
end_anim.mobject = self.ending_mobject
|
|
|
|
|
|
|
|
def update(self, alpha):
|
|
|
|
self.start_anim.update(alpha)
|
|
|
|
self.end_anim.update(alpha)
|
|
|
|
Transform.update(self, alpha)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|