mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Transformation article
This commit is contained in:
parent
ac930952f1
commit
199c600e36
7 changed files with 189 additions and 60 deletions
|
@ -30,32 +30,6 @@ class Animation(object):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_points_and_rgbs(self):
|
|
||||||
"""
|
|
||||||
It is the responsibility of this class to only emit points within
|
|
||||||
the space. Returns np array of points and corresponding np array
|
|
||||||
of rgbs
|
|
||||||
"""
|
|
||||||
#TODO, I don't think this should be necessary. This should happen
|
|
||||||
#under the individual mobjects.
|
|
||||||
points = self.mobject.points
|
|
||||||
rgbs = self.mobject.rgbs
|
|
||||||
#Filters out what is out of bounds.
|
|
||||||
admissibles = (abs(points[:,0]) < self.restricted_width) * \
|
|
||||||
(abs(points[:,1]) < self.restricted_height)
|
|
||||||
for filter_function in self.filter_functions:
|
|
||||||
admissibles *= ~filter_function(points)
|
|
||||||
if any(self.spatial_center):
|
|
||||||
points += self.spatial_center
|
|
||||||
#Filter out points pushed off the edge
|
|
||||||
admissibles *= (abs(points[:,0]) < SPACE_WIDTH) * \
|
|
||||||
(abs(points[:,1]) < SPACE_HEIGHT)
|
|
||||||
if rgbs.shape[0] < points.shape[0]:
|
|
||||||
#TODO, this shouldn't be necessary, find what's happening.
|
|
||||||
points = points[:rgbs.shape[0], :]
|
|
||||||
admissibles = admissibles[:rgbs.shape[0]]
|
|
||||||
return points[admissibles, :], rgbs[admissibles, :]
|
|
||||||
|
|
||||||
def update(self, alpha):
|
def update(self, alpha):
|
||||||
if alpha < 0:
|
if alpha < 0:
|
||||||
alpha = 0.0
|
alpha = 0.0
|
||||||
|
|
|
@ -4,6 +4,7 @@ import itertools as it
|
||||||
from helpers import *
|
from helpers import *
|
||||||
from animation import Animation
|
from animation import Animation
|
||||||
from transform import Transform
|
from transform import Transform
|
||||||
|
from mobject import Mobject
|
||||||
|
|
||||||
|
|
||||||
class DelayByOrder(Animation):
|
class DelayByOrder(Animation):
|
||||||
|
@ -43,6 +44,7 @@ class TransformAnimations(Transform):
|
||||||
"alpha_func" : squish_alpha_func(smooth)
|
"alpha_func" : squish_alpha_func(smooth)
|
||||||
}
|
}
|
||||||
def __init__(self, start_anim, end_anim, **kwargs):
|
def __init__(self, start_anim, end_anim, **kwargs):
|
||||||
|
digest_config(self, kwargs, locals())
|
||||||
if "run_time" in kwargs:
|
if "run_time" in kwargs:
|
||||||
self.run_time = kwargs.pop("run_time")
|
self.run_time = kwargs.pop("run_time")
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -10,15 +10,27 @@ from transform import Transform
|
||||||
|
|
||||||
class Rotating(Animation):
|
class Rotating(Animation):
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
"axes" : [RIGHT, UP],
|
"axes" : [RIGHT, UP],
|
||||||
"radians" : 2*np.pi,
|
"axis" : None,
|
||||||
"run_time" : 20.0,
|
"radians" : 2*np.pi,
|
||||||
|
"run_time" : 20.0,
|
||||||
"alpha_func" : None,
|
"alpha_func" : None,
|
||||||
|
"in_place" : True,
|
||||||
}
|
}
|
||||||
def update_mobject(self, alpha):
|
def update_mobject(self, alpha):
|
||||||
self.mobject.points = self.starting_mobject.points
|
axes = [self.axis] if self.axis is not None else self.axes
|
||||||
for axis in self.axes:
|
families = [
|
||||||
self.mobject.rotate(self.radians * alpha, axis)
|
self.mobject.get_full_submobject_family(),
|
||||||
|
self.starting_mobject.get_full_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:
|
||||||
|
method = self.mobject.rotate
|
||||||
|
method(alpha*self.radians, axes = axes)
|
||||||
|
|
||||||
|
|
||||||
class FadeOut(Animation):
|
class FadeOut(Animation):
|
||||||
def update_mobject(self, alpha):
|
def update_mobject(self, alpha):
|
||||||
|
|
|
@ -103,9 +103,17 @@ class ApplyMethod(Transform):
|
||||||
)
|
)
|
||||||
|
|
||||||
class Rotate(ApplyMethod):
|
class Rotate(ApplyMethod):
|
||||||
def __init__(self, mobject, angle = np.pi, **kwargs):
|
DEFAULT_CONFIG = {
|
||||||
|
"in_place" : False,
|
||||||
|
}
|
||||||
|
def __init__(self, mobject, angle = np.pi, axis = OUT, **kwargs):
|
||||||
kwargs["interpolation_function"] = path_along_arc(angle)
|
kwargs["interpolation_function"] = path_along_arc(angle)
|
||||||
ApplyMethod.__init__(self, mobject.rotate, angle, **kwargs)
|
digest_config(self, kwargs, locals())
|
||||||
|
if self.in_place:
|
||||||
|
method = mobject.rotate_in_place
|
||||||
|
else:
|
||||||
|
method = mobject.rotate
|
||||||
|
ApplyMethod.__init__(self, method, angle, axis, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ApplyPointwiseFunction(ApplyMethod):
|
class ApplyPointwiseFunction(ApplyMethod):
|
||||||
|
|
|
@ -120,12 +120,17 @@ class Mobject(object):
|
||||||
mob.points *= scale_factor
|
mob.points *= scale_factor
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def rotate(self, angle, axis = OUT):
|
def rotate(self, angle, axis = OUT, axes = []):
|
||||||
t_rotation_matrix = np.transpose(rotation_matrix(angle, axis))
|
if len(axes) == 0:
|
||||||
|
axes = [axis]
|
||||||
|
rot_matrix = np.identity(self.DIM)
|
||||||
|
for axis in axes:
|
||||||
|
rot_matrix = np.dot(rot_matrix, rotation_matrix(angle, axis))
|
||||||
|
t_rot_matrix = np.transpose(rot_matrix)
|
||||||
for mob in self.get_full_submobject_family():
|
for mob in self.get_full_submobject_family():
|
||||||
mob.points = np.dot(mob.points, t_rotation_matrix)
|
mob.points = np.dot(mob.points, t_rot_matrix)
|
||||||
if mob.has_normals:
|
if mob.has_normals:
|
||||||
mob.unit_normals = np.dot(mob.unit_normals, t_rotation_matrix)
|
mob.unit_normals = np.dot(mob.unit_normals, t_rot_matrix)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def stretch(self, factor, dim):
|
def stretch(self, factor, dim):
|
||||||
|
@ -165,6 +170,8 @@ class Mobject(object):
|
||||||
|
|
||||||
def filter_out(self, condition):
|
def filter_out(self, condition):
|
||||||
for mob in self.get_full_submobject_family():
|
for mob in self.get_full_submobject_family():
|
||||||
|
if len(mob.points) == 0:
|
||||||
|
continue
|
||||||
to_eliminate = ~np.apply_along_axis(condition, 1, mob.points)
|
to_eliminate = ~np.apply_along_axis(condition, 1, mob.points)
|
||||||
mob.points = mob.points[to_eliminate]
|
mob.points = mob.points[to_eliminate]
|
||||||
mob.rgbs = mob.rgbs[to_eliminate]
|
mob.rgbs = mob.rgbs[to_eliminate]
|
||||||
|
@ -205,8 +212,8 @@ class Mobject(object):
|
||||||
self.shift(center)
|
self.shift(center)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def rotate_in_place(self, angle, axis = OUT):
|
def rotate_in_place(self, angle, axis = OUT, axes = []):
|
||||||
self.do_in_place(self.rotate, angle, axis)
|
self.do_in_place(self.rotate, angle, axis, axes)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def scale_in_place(self, scale_factor):
|
def scale_in_place(self, scale_factor):
|
||||||
|
@ -314,13 +321,10 @@ class Mobject(object):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def get_merged_array(self, array_attr):
|
def get_merged_array(self, array_attr):
|
||||||
return reduce(
|
result = np.zeros((0, self.DIM))
|
||||||
lambda a1, a2 : np.append(a1, a2, axis = 0),
|
for mob in self.get_full_submobject_family():
|
||||||
[getattr(self, array_attr)] + [
|
result = np.append(result, getattr(mob, array_attr), 0)
|
||||||
mob.get_merged_array(array_attr)
|
return result
|
||||||
for mob in self.sub_mobjects
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_all_points(self):
|
def get_all_points(self):
|
||||||
return self.get_merged_array("points")
|
return self.get_merged_array("points")
|
||||||
|
|
|
@ -229,6 +229,7 @@ class XYZAxes(Mobject1D):
|
||||||
self.z_axis = self.x_axis.copy().rotate(np.pi/2, DOWN)
|
self.z_axis = self.x_axis.copy().rotate(np.pi/2, DOWN)
|
||||||
|
|
||||||
self.digest_mobject_attrs()
|
self.digest_mobject_attrs()
|
||||||
|
self.pose_at_angle()
|
||||||
|
|
||||||
|
|
||||||
class SpaceGrid(Mobject1D):
|
class SpaceGrid(Mobject1D):
|
||||||
|
@ -246,6 +247,7 @@ class SpaceGrid(Mobject1D):
|
||||||
start = np.array([a, b, -self.radius])[perm]
|
start = np.array([a, b, -self.radius])[perm]
|
||||||
end = np.array([a, b, self.radius])[perm]
|
end = np.array([a, b, self.radius])[perm]
|
||||||
self.add_line(start, end)
|
self.add_line(start, end)
|
||||||
|
self.pose_at_angle()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,9 @@ def half_plane():
|
||||||
x_radius = SPACE_WIDTH/2,
|
x_radius = SPACE_WIDTH/2,
|
||||||
x_unit_to_spatial_width = 0.5,
|
x_unit_to_spatial_width = 0.5,
|
||||||
y_unit_to_spatial_height = 0.5,
|
y_unit_to_spatial_height = 0.5,
|
||||||
density = 2*DEFAULT_POINT_DENSITY_1D,
|
x_faded_line_frequency = 0,
|
||||||
|
y_faded_line_frequency = 0,
|
||||||
|
density = 4*DEFAULT_POINT_DENSITY_1D,
|
||||||
)
|
)
|
||||||
plane.add_coordinates(
|
plane.add_coordinates(
|
||||||
x_vals = range(-6, 7, 2),
|
x_vals = range(-6, 7, 2),
|
||||||
|
@ -71,12 +73,12 @@ class SingleVariableFunction(Scene):
|
||||||
|
|
||||||
class LineToPlaneFunction(Scene):
|
class LineToPlaneFunction(Scene):
|
||||||
args_list = [
|
args_list = [
|
||||||
(lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", {}),
|
(lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", []),
|
||||||
(lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", {
|
(lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", [
|
||||||
"0" : 0,
|
("0", "(1, 0)", 0),
|
||||||
"\\frac{\\pi}{2}" : np.pi/2,
|
("\\frac{\\pi}{2}", "(0, \\pi / 4)", np.pi/2),
|
||||||
"\\pi" : np.pi
|
("\\pi", "(-1, 0)", np.pi),
|
||||||
})
|
])
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -97,6 +99,10 @@ class LineToPlaneFunction(Scene):
|
||||||
line.add_numbers(*range(0, 14, 2))
|
line.add_numbers(*range(0, 14, 2))
|
||||||
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
|
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
|
||||||
plane = half_plane()
|
plane = half_plane()
|
||||||
|
plane.sub_mobjects = []
|
||||||
|
plane.filter_out(
|
||||||
|
lambda (x, y, z) : abs(x) > 0.1 and abs(y) > 0.1
|
||||||
|
)
|
||||||
plane.shift(0.5*SPACE_WIDTH*RIGHT)
|
plane.shift(0.5*SPACE_WIDTH*RIGHT)
|
||||||
self.add(line, divider, plane)
|
self.add(line, divider, plane)
|
||||||
|
|
||||||
|
@ -110,7 +116,7 @@ class LineToPlaneFunction(Scene):
|
||||||
anims = [Transform(line_copy, target, **anim_config)]
|
anims = [Transform(line_copy, target, **anim_config)]
|
||||||
|
|
||||||
colors = iter([BLUE_B, GREEN_D, RED_D])
|
colors = iter([BLUE_B, GREEN_D, RED_D])
|
||||||
for tex, number in numbers_to_follow.items():
|
for input_tex, output_tex, number in numbers_to_follow:
|
||||||
center = line.number_to_point(number)
|
center = line.number_to_point(number)
|
||||||
dot = Dot(center, color = colors.next())
|
dot = Dot(center, color = colors.next())
|
||||||
anims.append(ApplyMethod(
|
anims.append(ApplyMethod(
|
||||||
|
@ -118,7 +124,7 @@ class LineToPlaneFunction(Scene):
|
||||||
point_function(center) - center,
|
point_function(center) - center,
|
||||||
**anim_config
|
**anim_config
|
||||||
))
|
))
|
||||||
label = TexMobject(tex)
|
label = TexMobject(input_tex)
|
||||||
label.shift(center + 2*UP)
|
label.shift(center + 2*UP)
|
||||||
arrow = Arrow(label, dot)
|
arrow = Arrow(label, dot)
|
||||||
self.add(label)
|
self.add(label)
|
||||||
|
@ -131,6 +137,17 @@ class LineToPlaneFunction(Scene):
|
||||||
self.play(*anims)
|
self.play(*anims)
|
||||||
self.dither()
|
self.dither()
|
||||||
|
|
||||||
|
for input_tex, output_tex, number in numbers_to_follow:
|
||||||
|
point = plane.num_pair_to_point(func(number))
|
||||||
|
label = TexMobject(output_tex)
|
||||||
|
side_shift = LEFT if number == np.pi else RIGHT
|
||||||
|
label.shift(point, 2*UP, side_shift)
|
||||||
|
arrow = Arrow(label, point)
|
||||||
|
self.add(label)
|
||||||
|
self.play(ShowCreation(arrow))
|
||||||
|
self.dither(2)
|
||||||
|
self.remove(arrow, label)
|
||||||
|
|
||||||
class PlaneToPlaneFunctionSeparatePlanes(Scene):
|
class PlaneToPlaneFunctionSeparatePlanes(Scene):
|
||||||
args_list = [
|
args_list = [
|
||||||
(lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic")
|
(lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic")
|
||||||
|
@ -185,9 +202,119 @@ class PlaneToPlaneFunction(Scene):
|
||||||
self.play(ApplyPointwiseFunction(point_function, plane, **anim_config))
|
self.play(ApplyPointwiseFunction(point_function, plane, **anim_config))
|
||||||
self.dither(3)
|
self.dither(3)
|
||||||
|
|
||||||
|
class PlaneToLineFunction(Scene):
|
||||||
|
args_list = [
|
||||||
|
(lambda (x, y) : x**2 + y**2, "Bowl"),
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def args_to_string(func, name):
|
||||||
|
return name
|
||||||
|
|
||||||
|
def construct(self, func, name):
|
||||||
|
line = NumberLine(
|
||||||
|
color = GREEN,
|
||||||
|
unit_length_to_spatial_width = 0.5,
|
||||||
|
tick_frequency = 1,
|
||||||
|
number_at_center = 6,
|
||||||
|
numerical_radius = 6,
|
||||||
|
numbers_with_elongated_ticks = [0, 12],
|
||||||
|
).to_edge(RIGHT)
|
||||||
|
line.add_numbers()
|
||||||
|
plane = half_plane().to_edge(LEFT, buff = 0)
|
||||||
|
|
||||||
|
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
|
||||||
|
line_left = line.number_to_point(0)
|
||||||
|
def point_function(point):
|
||||||
|
shifter = 0.5*SPACE_WIDTH*RIGHT
|
||||||
|
return func((point+shifter)[:2])*RIGHT + line_left
|
||||||
|
|
||||||
|
self.add(line, plane, divider)
|
||||||
|
self.dither()
|
||||||
|
plane.sub_mobjects = []
|
||||||
|
self.play(ApplyPointwiseFunction(point_function, plane))
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PlaneToSpaceFunction(Scene):
|
||||||
|
args_list = [
|
||||||
|
(lambda (x, y) : (x*x, x*y, y*y), "Quadratic"),
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def args_to_string(func, name):
|
||||||
|
return name
|
||||||
|
|
||||||
|
def construct(self, func, name):
|
||||||
|
plane = half_plane().shift(0.5*SPACE_WIDTH*LEFT)
|
||||||
|
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
|
||||||
|
axes = XYZAxes()
|
||||||
|
axes.filter_out(lambda p : np.linalg.norm(p) > 3)
|
||||||
|
rot_kwargs = {
|
||||||
|
"run_time" : 3,
|
||||||
|
"radians" : 0.3*np.pi,
|
||||||
|
"axis" : [0.1, 1, 0.1],
|
||||||
|
}
|
||||||
|
axes.to_edge(RIGHT).shift(DOWN)
|
||||||
|
dampening_factor = 0.1
|
||||||
|
def point_function((x, y, z)):
|
||||||
|
return dampening_factor*np.array(func((x, y)))
|
||||||
|
target = NumberPlane().apply_function(point_function)
|
||||||
|
target.highlight("yellow")
|
||||||
|
target.shift(axes.get_center())
|
||||||
|
|
||||||
|
self.add(plane, divider, axes)
|
||||||
|
self.play(Rotating(axes, **rot_kwargs))
|
||||||
|
|
||||||
|
target.rotate_in_place(rot_kwargs["radians"])
|
||||||
|
self.play(
|
||||||
|
TransformAnimations(
|
||||||
|
Animation(plane.copy()),
|
||||||
|
Rotating(target, **rot_kwargs),
|
||||||
|
alpha_func = smooth
|
||||||
|
),
|
||||||
|
Rotating(axes, **rot_kwargs)
|
||||||
|
)
|
||||||
|
axes.add(target)
|
||||||
|
self.clear()
|
||||||
|
self.add(plane, divider, axes)
|
||||||
|
self.play(Rotating(axes, **rot_kwargs))
|
||||||
|
self.clear()
|
||||||
|
for i in range(5):
|
||||||
|
self.play(Rotating(axes, **rot_kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
class SpaceToSpaceFunction(Scene):
|
||||||
|
args_list = [
|
||||||
|
(lambda (x, y, z) : (y*z, x*z, x*y), "Quadratic"),
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def args_to_string(func, name):
|
||||||
|
return name
|
||||||
|
|
||||||
|
def construct(self, func, name):
|
||||||
|
space = SpaceGrid()
|
||||||
|
rot_kwargs = {
|
||||||
|
"run_time" : 10,
|
||||||
|
"radians" : 2*np.pi/5,
|
||||||
|
"axis" : [0.1, 1, 0.1],
|
||||||
|
"in_place" : False,
|
||||||
|
}
|
||||||
|
axes = XYZAxes()
|
||||||
|
target = space.copy().apply_function(func)
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
TransformAnimations(
|
||||||
|
Rotating(space, **rot_kwargs),
|
||||||
|
Rotating(target, **rot_kwargs),
|
||||||
|
alpha_func = squish_alpha_func(smooth, 0.3, 0.7)
|
||||||
|
),
|
||||||
|
Rotating(axes, **rot_kwargs)
|
||||||
|
)
|
||||||
|
axes.add(space)
|
||||||
|
self.play(Rotating(axes, **rot_kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue