3b1b-manim/animation/transform.py

271 lines
8.1 KiB
Python
Raw Normal View History

2018-08-09 17:56:05 -07:00
2015-06-10 22:00:35 -07:00
import inspect
import numpy as np
2015-06-10 22:00:35 -07:00
from constants import *
2015-06-10 22:00:35 -07:00
from animation.animation import Animation
from mobject.mobject import Group
from mobject.mobject import Mobject
from utils.config_ops import digest_config
from utils.iterables import adjacent_pairs
from utils.paths import path_along_arc
from utils.paths import straight_path
from utils.config_ops import instantiate
from utils.rate_functions import smooth
from utils.rate_functions import squish_rate_func
from utils.space_ops import complex_to_R3
2015-10-12 19:39:46 -07:00
2015-06-10 22:00:35 -07:00
class Transform(Animation):
2016-02-27 16:32:53 -08:00
CONFIG = {
"path_arc": 0,
"path_arc_axis": OUT,
"path_func": None,
"submobject_mode": "all_at_once",
"replace_mobject_with_target_in_scene": False,
}
def __init__(self, mobject, target_mobject, **kwargs):
# Copy target_mobject so as to not mess with caller
self.original_target_mobject = target_mobject
target_mobject = target_mobject.copy()
mobject.align_data(target_mobject)
2018-02-07 16:20:16 -08:00
self.target_mobject = target_mobject
digest_config(self, kwargs)
2016-04-17 19:29:27 -07:00
self.init_path_func()
Animation.__init__(self, mobject, **kwargs)
self.name += "To" + str(target_mobject)
2016-07-22 11:22:31 -07:00
def update_config(self, **kwargs):
Animation.update_config(self, **kwargs)
if "path_arc" in kwargs:
2017-02-13 18:43:55 -08:00
self.path_func = path_along_arc(
kwargs["path_arc"],
2017-03-03 17:33:06 -08:00
kwargs.get("path_arc_axis", OUT)
2017-02-13 18:43:55 -08:00
)
2016-07-22 11:22:31 -07:00
2016-04-17 19:29:27 -07:00
def init_path_func(self):
if self.path_func is not None:
return
2018-02-07 16:20:16 -08:00
elif self.path_arc == 0:
2016-04-17 19:29:27 -07:00
self.path_func = straight_path
else:
2017-02-13 18:43:55 -08:00
self.path_func = path_along_arc(
self.path_arc,
self.path_arc_axis,
)
2018-02-10 22:19:00 -08:00
2017-02-13 18:43:55 -08:00
def get_all_mobjects(self):
return self.mobject, self.starting_mobject, self.target_mobject
def update_submobject(self, submob, start, end, alpha):
submob.interpolate(start, end, alpha, self.path_func)
return self
def clean_up(self, surrounding_scene=None):
Animation.clean_up(self, surrounding_scene)
if self.replace_mobject_with_target_in_scene and surrounding_scene is not None:
surrounding_scene.remove(self.mobject)
2017-10-10 13:48:13 -07:00
if not self.remover:
surrounding_scene.add(self.original_target_mobject)
2017-01-28 15:49:22 -08:00
class ReplacementTransform(Transform):
CONFIG = {
"replace_mobject_with_target_in_scene": True,
2017-01-28 15:49:22 -08:00
}
2018-08-15 21:54:43 -07:00
class TransformFromCopy(ReplacementTransform):
def __init__(self, mobject, target_mobject, **kwargs):
ReplacementTransform.__init__(
self, mobject.deepcopy(), target_mobject, **kwargs
)
class ClockwiseTransform(Transform):
2016-02-27 16:32:53 -08:00
CONFIG = {
"path_arc": -np.pi
}
2015-06-13 19:00:23 -07:00
class CounterclockwiseTransform(Transform):
2016-02-27 16:32:53 -08:00
CONFIG = {
"path_arc": np.pi
}
2015-06-10 22:00:35 -07:00
2016-08-30 22:17:07 -07:00
class MoveToTarget(Transform):
def __init__(self, mobject, **kwargs):
if not hasattr(mobject, "target"):
raise Exception(
"MoveToTarget called on mobject without attribute 'target' ")
2016-08-30 22:17:07 -07:00
Transform.__init__(self, mobject, mobject.target, **kwargs)
2015-06-10 22:00:35 -07:00
class ApplyMethod(Transform):
CONFIG = {
"submobject_mode": "all_at_once"
}
2015-06-10 22:00:35 -07:00
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):
raise Exception(
"Whoops, looks like you accidentally invoked " +
"the method you want to animate"
)
assert(isinstance(method.__self__, Mobject))
args = list(args) # So that args.pop() works
if "method_kwargs" in kwargs:
method_kwargs = kwargs["method_kwargs"]
elif len(args) > 0 and isinstance(args[-1], dict):
method_kwargs = args.pop()
else:
method_kwargs = {}
target = method.__self__.copy()
method.__func__(target, *args, **method_kwargs)
Transform.__init__(self, method.__self__, target, **kwargs)
2015-06-10 22:00:35 -07:00
2015-10-20 21:55:46 -07:00
class ApplyPointwiseFunction(ApplyMethod):
2016-02-27 16:32:53 -08:00
CONFIG = {
"run_time": DEFAULT_POINTWISE_FUNCTION_RUN_TIME
2015-10-20 21:55:46 -07:00
}
2015-10-20 21:55:46 -07:00
def __init__(self, function, mobject, **kwargs):
ApplyMethod.__init__(
self, mobject.apply_function, function, **kwargs
)
class ApplyPointwiseFunctionToCenter(ApplyPointwiseFunction):
def __init__(self, function, mobject, **kwargs):
ApplyMethod.__init__(
self, mobject.move_to, function(mobject.get_center()), **kwargs
)
class FadeToColor(ApplyMethod):
def __init__(self, mobject, color, **kwargs):
2018-03-30 11:51:31 -07:00
ApplyMethod.__init__(self, mobject.set_color, color, **kwargs)
class ScaleInPlace(ApplyMethod):
def __init__(self, mobject, scale_factor, **kwargs):
ApplyMethod.__init__(self, mobject.scale_in_place,
scale_factor, **kwargs)
2018-07-14 10:31:29 -07:00
class Restore(ApplyMethod):
def __init__(self, mobject, **kwargs):
ApplyMethod.__init__(self, mobject.restore, **kwargs)
2015-06-10 22:00:35 -07:00
class ApplyFunction(Transform):
2016-07-25 16:04:54 -07:00
CONFIG = {
"submobject_mode": "all_at_once",
2016-07-25 16:04:54 -07:00
}
def __init__(self, function, mobject, **kwargs):
Transform.__init__(
self,
mobject,
function(mobject.copy()),
**kwargs
)
self.name = "ApplyFunctionTo" + str(mobject)
2016-07-12 10:34:35 -07:00
class ApplyMatrix(ApplyPointwiseFunction):
# Truth be told, I'm not sure if this is useful.
def __init__(self, matrix, mobject, **kwargs):
matrix = np.array(matrix)
if matrix.shape == (2, 2):
2016-07-12 10:34:35 -07:00
new_matrix = np.identity(3)
new_matrix[:2, :2] = matrix
matrix = new_matrix
elif matrix.shape != (3, 3):
raise Exception("Matrix has bad dimensions")
2016-07-12 10:34:35 -07:00
transpose = np.transpose(matrix)
2016-07-12 10:34:35 -07:00
def func(p):
return np.dot(p, transpose)
ApplyPointwiseFunction.__init__(self, func, mobject, **kwargs)
2015-06-10 22:00:35 -07:00
class ComplexFunction(ApplyPointwiseFunction):
def __init__(self, function, mobject, **kwargs):
if "path_func" not in kwargs:
self.path_func = path_along_arc(
np.log(function(complex(1))).imag
)
ApplyPointwiseFunction.__init__(
self,
lambda x_y_z: complex_to_R3(function(complex(x_y_z[0], x_y_z[1]))),
instantiate(mobject),
**kwargs
)
###
class CyclicReplace(Transform):
CONFIG = {
"path_arc": np.pi / 2
}
def __init__(self, *mobjects, **kwargs):
start = Group(*mobjects)
target = Group(*[
m1.copy().move_to(m2)
for m1, m2 in adjacent_pairs(start)
])
Transform.__init__(self, start, target, **kwargs)
2015-06-10 22:00:35 -07:00
class Swap(CyclicReplace):
pass # Renaming, more understandable for two entries
# TODO: Um...does this work
class TransformAnimations(Transform):
2016-02-27 16:32:53 -08:00
CONFIG = {
"rate_func": squish_rate_func(smooth)
}
def __init__(self, start_anim, end_anim, **kwargs):
digest_config(self, kwargs, locals())
if "run_time" in kwargs:
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():
start_anim.starting_mobject.align_data(end_anim.starting_mobject)
for anim in start_anim, end_anim:
if hasattr(anim, "target_mobject"):
anim.starting_mobject.align_data(anim.target_mobject)
Transform.__init__(self, start_anim.mobject,
end_anim.mobject, **kwargs)
# Rewire starting and ending mobjects
start_anim.mobject = self.starting_mobject
end_anim.mobject = self.target_mobject
def update(self, alpha):
self.start_anim.update(alpha)
self.end_anim.update(alpha)
Transform.update(self, alpha)