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):
|
||||
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):
|
||||
if alpha < 0:
|
||||
alpha = 0.0
|
||||
|
|
|
@ -4,6 +4,7 @@ import itertools as it
|
|||
from helpers import *
|
||||
from animation import Animation
|
||||
from transform import Transform
|
||||
from mobject import Mobject
|
||||
|
||||
|
||||
class DelayByOrder(Animation):
|
||||
|
@ -43,6 +44,7 @@ class TransformAnimations(Transform):
|
|||
"alpha_func" : squish_alpha_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:
|
||||
|
|
|
@ -11,14 +11,26 @@ from transform import Transform
|
|||
class Rotating(Animation):
|
||||
DEFAULT_CONFIG = {
|
||||
"axes" : [RIGHT, UP],
|
||||
"axis" : None,
|
||||
"radians" : 2*np.pi,
|
||||
"run_time" : 20.0,
|
||||
"alpha_func" : None,
|
||||
"in_place" : True,
|
||||
}
|
||||
def update_mobject(self, alpha):
|
||||
self.mobject.points = self.starting_mobject.points
|
||||
for axis in self.axes:
|
||||
self.mobject.rotate(self.radians * alpha, axis)
|
||||
axes = [self.axis] if self.axis is not None else self.axes
|
||||
families = [
|
||||
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):
|
||||
def update_mobject(self, alpha):
|
||||
|
|
|
@ -103,9 +103,17 @@ class ApplyMethod(Transform):
|
|||
)
|
||||
|
||||
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)
|
||||
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):
|
||||
|
|
|
@ -120,12 +120,17 @@ class Mobject(object):
|
|||
mob.points *= scale_factor
|
||||
return self
|
||||
|
||||
def rotate(self, angle, axis = OUT):
|
||||
t_rotation_matrix = np.transpose(rotation_matrix(angle, axis))
|
||||
def rotate(self, angle, axis = OUT, axes = []):
|
||||
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():
|
||||
mob.points = np.dot(mob.points, t_rotation_matrix)
|
||||
mob.points = np.dot(mob.points, t_rot_matrix)
|
||||
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
|
||||
|
||||
def stretch(self, factor, dim):
|
||||
|
@ -165,6 +170,8 @@ class Mobject(object):
|
|||
|
||||
def filter_out(self, condition):
|
||||
for mob in self.get_full_submobject_family():
|
||||
if len(mob.points) == 0:
|
||||
continue
|
||||
to_eliminate = ~np.apply_along_axis(condition, 1, mob.points)
|
||||
mob.points = mob.points[to_eliminate]
|
||||
mob.rgbs = mob.rgbs[to_eliminate]
|
||||
|
@ -205,8 +212,8 @@ class Mobject(object):
|
|||
self.shift(center)
|
||||
return self
|
||||
|
||||
def rotate_in_place(self, angle, axis = OUT):
|
||||
self.do_in_place(self.rotate, angle, axis)
|
||||
def rotate_in_place(self, angle, axis = OUT, axes = []):
|
||||
self.do_in_place(self.rotate, angle, axis, axes)
|
||||
return self
|
||||
|
||||
def scale_in_place(self, scale_factor):
|
||||
|
@ -314,13 +321,10 @@ class Mobject(object):
|
|||
return 0
|
||||
|
||||
def get_merged_array(self, array_attr):
|
||||
return reduce(
|
||||
lambda a1, a2 : np.append(a1, a2, axis = 0),
|
||||
[getattr(self, array_attr)] + [
|
||||
mob.get_merged_array(array_attr)
|
||||
for mob in self.sub_mobjects
|
||||
]
|
||||
)
|
||||
result = np.zeros((0, self.DIM))
|
||||
for mob in self.get_full_submobject_family():
|
||||
result = np.append(result, getattr(mob, array_attr), 0)
|
||||
return result
|
||||
|
||||
def get_all_points(self):
|
||||
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.digest_mobject_attrs()
|
||||
self.pose_at_angle()
|
||||
|
||||
|
||||
class SpaceGrid(Mobject1D):
|
||||
|
@ -246,6 +247,7 @@ class SpaceGrid(Mobject1D):
|
|||
start = np.array([a, b, -self.radius])[perm]
|
||||
end = np.array([a, b, self.radius])[perm]
|
||||
self.add_line(start, end)
|
||||
self.pose_at_angle()
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@ def half_plane():
|
|||
x_radius = SPACE_WIDTH/2,
|
||||
x_unit_to_spatial_width = 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(
|
||||
x_vals = range(-6, 7, 2),
|
||||
|
@ -71,12 +73,12 @@ class SingleVariableFunction(Scene):
|
|||
|
||||
class LineToPlaneFunction(Scene):
|
||||
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", {
|
||||
"0" : 0,
|
||||
"\\frac{\\pi}{2}" : np.pi/2,
|
||||
"\\pi" : np.pi
|
||||
})
|
||||
(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", "(1, 0)", 0),
|
||||
("\\frac{\\pi}{2}", "(0, \\pi / 4)", np.pi/2),
|
||||
("\\pi", "(-1, 0)", np.pi),
|
||||
])
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
|
@ -97,6 +99,10 @@ class LineToPlaneFunction(Scene):
|
|||
line.add_numbers(*range(0, 14, 2))
|
||||
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
|
||||
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)
|
||||
self.add(line, divider, plane)
|
||||
|
||||
|
@ -110,7 +116,7 @@ class LineToPlaneFunction(Scene):
|
|||
anims = [Transform(line_copy, target, **anim_config)]
|
||||
|
||||
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)
|
||||
dot = Dot(center, color = colors.next())
|
||||
anims.append(ApplyMethod(
|
||||
|
@ -118,7 +124,7 @@ class LineToPlaneFunction(Scene):
|
|||
point_function(center) - center,
|
||||
**anim_config
|
||||
))
|
||||
label = TexMobject(tex)
|
||||
label = TexMobject(input_tex)
|
||||
label.shift(center + 2*UP)
|
||||
arrow = Arrow(label, dot)
|
||||
self.add(label)
|
||||
|
@ -131,6 +137,17 @@ class LineToPlaneFunction(Scene):
|
|||
self.play(*anims)
|
||||
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):
|
||||
args_list = [
|
||||
(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.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