3b1b-manim/manimlib/scene/vector_space_scene.py

513 lines
18 KiB
Python
Raw Normal View History

2016-07-12 10:36:04 -07:00
import numpy as np
from manimlib.animation.animation import Animation
from manimlib.animation.creation import ShowCreation
from manimlib.animation.creation import Write
from manimlib.animation.fading import FadeOut
from manimlib.animation.growing import GrowArrow
from manimlib.animation.transform import ApplyFunction
from manimlib.animation.transform import ApplyPointwiseFunction
from manimlib.animation.transform import Transform
from manimlib.constants import *
from manimlib.mobject.coordinate_systems import Axes
from manimlib.mobject.coordinate_systems import NumberPlane
from manimlib.mobject.geometry import Arrow
from manimlib.mobject.geometry import Dot
from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.geometry import Vector
from manimlib.mobject.matrix import Matrix
from manimlib.mobject.matrix import VECTOR_LABEL_SCALE_FACTOR
from manimlib.mobject.matrix import vector_coordinate_label
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.svg.tex_mobject import TexMobject
from manimlib.mobject.svg.tex_mobject import TextMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.scene.scene import Scene
from manimlib.utils.rate_functions import rush_from
from manimlib.utils.rate_functions import rush_into
from manimlib.utils.space_ops import angle_of_vector
from manimlib.utils.space_ops import get_norm
2016-07-12 10:36:04 -07:00
X_COLOR = GREEN_C
Y_COLOR = RED_C
2016-07-16 13:34:21 -07:00
Z_COLOR = BLUE_D
# TODO: Much of this scene type seems dependent on the coordinate system chosen.
# That is, being centered at the origin with grid units corresponding to the
# arbitrary space units. Change it!
#
# Also, methods I would have thought of as getters, like coords_to_vector, are
# actually doing a lot of animating.
2016-07-16 13:34:21 -07:00
class VectorScene(Scene):
2016-07-19 11:08:58 -07:00
CONFIG = {
"basis_vector_stroke_width": 6
2016-07-19 11:08:58 -07:00
}
def add_plane(self, animate=False, **kwargs):
2016-07-16 13:34:21 -07:00
plane = NumberPlane(**kwargs)
if animate:
2019-02-08 15:44:58 -08:00
self.play(ShowCreation(plane, lag_ratio=0.5))
2016-07-16 13:34:21 -07:00
self.add(plane)
return plane
def add_axes(self, animate=False, color=WHITE, **kwargs):
axes = Axes(color=color, tick_frequency=1)
2016-07-18 11:50:26 -07:00
if animate:
2019-02-08 15:44:58 -08:00
self.play(ShowCreation(axes))
2016-07-18 11:50:26 -07:00
self.add(axes)
return axes
def lock_in_faded_grid(self, dimness=0.7, axes_dimness=0.5):
plane = self.add_plane()
axes = plane.get_axes()
plane.fade(dimness)
2018-03-30 11:51:31 -07:00
axes.set_color(WHITE)
axes.fade(axes_dimness)
self.add(axes)
self.freeze_background()
def get_vector(self, numerical_vector, **kwargs):
return Arrow(
self.plane.coords_to_point(0, 0),
self.plane.coords_to_point(*numerical_vector[:2]),
buff=0,
**kwargs
)
def add_vector(self, vector, color=YELLOW, animate=True, **kwargs):
if not isinstance(vector, Arrow):
vector = Vector(vector, color=color, **kwargs)
2016-07-16 13:34:21 -07:00
if animate:
self.play(GrowArrow(vector))
self.add(vector)
return vector
2016-07-16 13:34:21 -07:00
2016-07-28 11:16:28 -07:00
def write_vector_coordinates(self, vector, **kwargs):
coords = vector_coordinate_label(vector, **kwargs)
self.play(Write(coords))
return coords
def get_basis_vectors(self, i_hat_color=X_COLOR, j_hat_color=Y_COLOR):
return VGroup(*[
2016-07-19 11:08:58 -07:00
Vector(
vect,
color=color,
stroke_width=self.basis_vector_stroke_width
2016-07-19 11:08:58 -07:00
)
for vect, color in [
([1, 0], i_hat_color),
([0, 1], j_hat_color)
2016-07-19 11:08:58 -07:00
]
])
2016-07-19 11:08:58 -07:00
def get_basis_vector_labels(self, **kwargs):
2016-07-19 11:08:58 -07:00
i_hat, j_hat = self.get_basis_vectors()
return VGroup(*[
self.get_vector_label(
vect, label, color=color,
label_scale_factor=1,
2016-07-19 11:08:58 -07:00
**kwargs
)
for vect, label, color in [
2016-07-19 11:08:58 -07:00
(i_hat, "\\hat{\\imath}", X_COLOR),
(j_hat, "\\hat{\\jmath}", Y_COLOR),
]
])
2016-07-18 11:50:26 -07:00
def get_vector_label(self, vector, label,
at_tip=False,
direction="left",
rotate=False,
color=None,
label_scale_factor=VECTOR_LABEL_SCALE_FACTOR):
2016-08-02 15:50:32 -07:00
if not isinstance(label, TexMobject):
if len(label) == 1:
label = "\\vec{\\textbf{%s}}" % label
2016-08-02 15:50:32 -07:00
label = TexMobject(label)
2016-08-17 16:14:15 -07:00
if color is None:
color = vector.get_color()
2018-03-30 11:51:31 -07:00
label.set_color(color)
2016-08-02 15:50:32 -07:00
label.scale(label_scale_factor)
label.add_background_rectangle()
if at_tip:
vect = vector.get_vector()
2018-08-15 17:30:24 -07:00
vect /= get_norm(vect)
label.next_to(vector.get_end(), vect, buff=SMALL_BUFF)
2016-07-25 16:04:54 -07:00
else:
angle = vector.get_angle()
if not rotate:
label.rotate(-angle, about_point=ORIGIN)
2019-04-30 21:52:34 -07:00
if direction == "left":
label.shift(-label.get_bottom() + 0.1 * UP)
else:
label.shift(-label.get_top() + 0.1 * DOWN)
label.rotate(angle, about_point=ORIGIN)
label.shift((vector.get_end() - vector.get_start()) / 2)
return label
def label_vector(self, vector, label, animate=True, **kwargs):
label = self.get_vector_label(vector, label, **kwargs)
2016-07-16 13:34:21 -07:00
if animate:
self.play(Write(label, run_time=1))
2016-07-16 13:34:21 -07:00
self.add(label)
return label
def position_x_coordinate(self, x_coord, x_line, vector):
x_coord.next_to(x_line, -np.sign(vector[1]) * UP)
2018-03-30 11:51:31 -07:00
x_coord.set_color(X_COLOR)
return x_coord
def position_y_coordinate(self, y_coord, y_line, vector):
y_coord.next_to(y_line, np.sign(vector[0]) * RIGHT)
2018-03-30 11:51:31 -07:00
y_coord.set_color(Y_COLOR)
return y_coord
def coords_to_vector(self, vector, coords_start=2 * RIGHT + 2 * UP, clean_up=True):
starting_mobjects = list(self.mobjects)
array = Matrix(vector)
array.shift(coords_start)
arrow = Vector(vector)
x_line = Line(ORIGIN, vector[0] * RIGHT)
y_line = Line(x_line.get_end(), arrow.get_end())
2018-03-30 11:51:31 -07:00
x_line.set_color(X_COLOR)
y_line.set_color(Y_COLOR)
x_coord, y_coord = array.get_mob_matrix().flatten()
self.play(Write(array, run_time=1))
2018-01-15 19:15:05 -08:00
self.wait()
self.play(ApplyFunction(
lambda x: self.position_x_coordinate(x, x_line, vector),
x_coord
))
self.play(ShowCreation(x_line))
self.play(
ApplyFunction(
lambda y: self.position_y_coordinate(y, y_line, vector),
y_coord
),
FadeOut(array.get_brackets())
)
2016-07-19 11:08:58 -07:00
y_coord, brackets = self.get_mobjects_from_last_animation()
self.play(ShowCreation(y_line))
2016-07-19 11:08:58 -07:00
self.play(ShowCreation(arrow))
2018-01-15 19:15:05 -08:00
self.wait()
if clean_up:
self.clear()
self.add(*starting_mobjects)
def vector_to_coords(self, vector, integer_labels=True, clean_up=True):
starting_mobjects = list(self.mobjects)
show_creation = False
if isinstance(vector, Arrow):
arrow = vector
vector = arrow.get_end()[:2]
else:
arrow = Vector(vector)
show_creation = True
array = vector_coordinate_label(arrow, integer_labels=integer_labels)
x_line = Line(ORIGIN, vector[0] * RIGHT)
y_line = Line(x_line.get_end(), arrow.get_end())
2018-03-30 11:51:31 -07:00
x_line.set_color(X_COLOR)
y_line.set_color(Y_COLOR)
x_coord, y_coord = array.get_mob_matrix().flatten()
x_coord_start = self.position_x_coordinate(
x_coord.copy(), x_line, vector
)
y_coord_start = self.position_y_coordinate(
y_coord.copy(), y_line, vector
)
brackets = array.get_brackets()
if show_creation:
2016-07-25 16:04:54 -07:00
self.play(ShowCreation(arrow))
self.play(
ShowCreation(x_line),
Write(x_coord_start),
run_time=1
)
self.play(
ShowCreation(y_line),
Write(y_coord_start),
run_time=1
)
2018-01-15 19:15:05 -08:00
self.wait()
self.play(
2019-02-08 15:49:06 -08:00
Transform(x_coord_start, x_coord, lag_ratio=0),
Transform(y_coord_start, y_coord, lag_ratio=0),
Write(brackets, run_time=1),
)
2018-01-15 19:15:05 -08:00
self.wait()
2016-07-19 11:08:58 -07:00
self.remove(x_coord_start, y_coord_start, brackets)
self.add(array)
if clean_up:
self.clear()
self.add(*starting_mobjects)
2016-07-18 11:50:26 -07:00
return array, x_line, y_line
def show_ghost_movement(self, vector):
if isinstance(vector, Arrow):
vector = vector.get_end() - vector.get_start()
elif len(vector) == 2:
vector = np.append(np.array(vector), 0.0)
x_max = int(FRAME_X_RADIUS + abs(vector[0]))
y_max = int(FRAME_Y_RADIUS + abs(vector[1]))
2016-07-18 11:50:26 -07:00
dots = VMobject(*[
Dot(x * RIGHT + y * UP)
2016-07-18 11:50:26 -07:00
for x in range(-x_max, x_max)
for y in range(-y_max, y_max)
])
dots.set_fill(BLACK, opacity=0)
dots_halfway = dots.copy().shift(vector / 2).set_fill(WHITE, 1)
2016-07-18 11:50:26 -07:00
dots_end = dots.copy().shift(vector)
self.play(Transform(
dots, dots_halfway, rate_func=rush_into
2016-07-18 11:50:26 -07:00
))
self.play(Transform(
dots, dots_end, rate_func=rush_from
2016-07-18 11:50:26 -07:00
))
self.remove(dots)
2016-07-22 11:22:31 -07:00
class LinearTransformationScene(VectorScene):
CONFIG = {
"include_background_plane": True,
"include_foreground_plane": True,
"foreground_plane_kwargs": {
"x_max": FRAME_WIDTH / 2,
"x_min": -FRAME_WIDTH / 2,
"y_max": FRAME_WIDTH / 2,
"y_min": -FRAME_WIDTH / 2,
"faded_line_ratio": 0
2016-07-22 11:22:31 -07:00
},
"background_plane_kwargs": {
"color": GREY,
"axis_config": {
"stroke_color": GREY_B,
},
"axis_config": {
"color": GREY,
},
"background_line_style": {
"stroke_color": GREY,
"stroke_width": 1,
},
2016-07-22 11:22:31 -07:00
},
"show_coordinates": False,
"show_basis_vectors": True,
"basis_vector_stroke_width": 6,
"i_hat_color": X_COLOR,
"j_hat_color": Y_COLOR,
"leave_ghost_vectors": False,
"t_matrix": [[3, 0], [1, 2]],
2016-07-22 11:22:31 -07:00
}
2016-07-22 11:22:31 -07:00
def setup(self):
# The has_already_setup attr is to not break all the old Scenes
2016-09-07 22:04:24 -07:00
if hasattr(self, "has_already_setup"):
2016-08-10 10:26:07 -07:00
return
2016-09-07 22:04:24 -07:00
self.has_already_setup = True
2016-07-22 11:22:31 -07:00
self.background_mobjects = []
self.foreground_mobjects = []
self.transformable_mobjects = []
self.moving_vectors = []
self.transformable_labels = []
2016-08-02 12:26:15 -07:00
self.moving_mobjects = []
2016-07-22 11:22:31 -07:00
2016-08-09 14:07:23 -07:00
self.t_matrix = np.array(self.t_matrix)
2016-07-22 11:22:31 -07:00
self.background_plane = NumberPlane(
**self.background_plane_kwargs
)
if self.show_coordinates:
self.background_plane.add_coordinates()
if self.include_background_plane:
2016-07-22 11:22:31 -07:00
self.add_background_mobject(self.background_plane)
if self.include_foreground_plane:
self.plane = NumberPlane(**self.foreground_plane_kwargs)
self.add_transformable_mobject(self.plane)
if self.show_basis_vectors:
self.basis_vectors = self.get_basis_vectors(
i_hat_color=self.i_hat_color,
j_hat_color=self.j_hat_color,
)
self.moving_vectors += list(self.basis_vectors)
self.i_hat, self.j_hat = self.basis_vectors
2018-04-26 17:09:32 -07:00
self.add(self.basis_vectors)
2016-07-22 11:22:31 -07:00
2016-07-25 16:04:54 -07:00
def add_special_mobjects(self, mob_list, *mobs_to_add):
for mobject in mobs_to_add:
if mobject not in mob_list:
mob_list.append(mobject)
2016-07-22 11:22:31 -07:00
self.add(mobject)
2016-07-25 16:04:54 -07:00
def add_background_mobject(self, *mobjects):
self.add_special_mobjects(self.background_mobjects, *mobjects)
# TODO, this conflicts with Scene.add_fore
2016-07-25 16:04:54 -07:00
def add_foreground_mobject(self, *mobjects):
self.add_special_mobjects(self.foreground_mobjects, *mobjects)
2018-04-02 17:49:04 -07:00
2016-07-22 11:22:31 -07:00
def add_transformable_mobject(self, *mobjects):
2016-07-25 16:04:54 -07:00
self.add_special_mobjects(self.transformable_mobjects, *mobjects)
2016-07-22 11:22:31 -07:00
def add_moving_mobject(self, mobject, target_mobject=None):
2016-08-02 12:26:15 -07:00
mobject.target = target_mobject
self.add_special_mobjects(self.moving_mobjects, mobject)
def get_unit_square(self, color=YELLOW, opacity=0.3, stroke_width=3):
square = self.square = Rectangle(
color=color,
width=self.plane.get_x_unit_size(),
height=self.plane.get_y_unit_size(),
stroke_color=color,
stroke_width=stroke_width,
fill_color=color,
fill_opacity=opacity
)
square.move_to(self.plane.coords_to_point(0, 0), DL)
return square
def add_unit_square(self, animate=False, **kwargs):
square = self.get_unit_square(**kwargs)
2016-08-02 12:26:15 -07:00
if animate:
self.play(
DrawBorderThenFill(square),
Animation(Group(*self.moving_vectors))
)
2016-08-02 12:26:15 -07:00
self.add_transformable_mobject(square)
self.bring_to_front(*self.moving_vectors)
2016-08-02 12:26:15 -07:00
self.square = square
return self
def add_vector(self, vector, color=YELLOW, **kwargs):
2016-07-22 11:22:31 -07:00
vector = VectorScene.add_vector(
self, vector, color=color, **kwargs
2016-07-22 11:22:31 -07:00
)
self.moving_vectors.append(vector)
return vector
2016-07-28 11:16:28 -07:00
def write_vector_coordinates(self, vector, **kwargs):
coords = VectorScene.write_vector_coordinates(self, vector, **kwargs)
self.add_foreground_mobject(coords)
return coords
def add_transformable_label(
self, vector, label,
transformation_name="L",
new_label=None,
**kwargs):
label_mob = self.label_vector(vector, label, **kwargs)
2016-07-25 16:04:54 -07:00
if new_label:
label_mob.target_text = new_label
else:
label_mob.target_text = "%s(%s)" % (
transformation_name,
label_mob.get_tex_string()
)
label_mob.vector = vector
label_mob.kwargs = kwargs
2016-07-27 13:10:45 -07:00
if "animate" in label_mob.kwargs:
label_mob.kwargs.pop("animate")
self.transformable_labels.append(label_mob)
return label_mob
def add_title(self, title, scale_factor=1.5, animate=False):
2016-07-28 11:16:28 -07:00
if not isinstance(title, Mobject):
2016-08-02 15:50:32 -07:00
title = TextMobject(title).scale(scale_factor)
2016-07-28 11:16:28 -07:00
title.to_edge(UP)
title.add_background_rectangle()
if animate:
self.play(Write(title))
self.add_foreground_mobject(title)
2016-08-02 12:26:15 -07:00
self.title = title
2016-07-28 11:16:28 -07:00
return self
def get_matrix_transformation(self, matrix):
return self.get_transposed_matrix_transformation(np.array(matrix).T)
def get_transposed_matrix_transformation(self, transposed_matrix):
2016-07-22 11:22:31 -07:00
transposed_matrix = np.array(transposed_matrix)
if transposed_matrix.shape == (2, 2):
new_matrix = np.identity(3)
new_matrix[:2, :2] = transposed_matrix
transposed_matrix = new_matrix
elif transposed_matrix.shape != (3, 3):
raise Exception("Matrix has bad dimensions")
2016-07-22 11:22:31 -07:00
return lambda point: np.dot(point, transposed_matrix)
def get_piece_movement(self, pieces):
start = VGroup(*pieces)
target = VGroup(*[mob.target for mob in pieces])
if self.leave_ghost_vectors:
self.add(start.copy().fade(0.7))
2019-02-08 15:49:06 -08:00
return Transform(start, target, lag_ratio=0)
2016-07-22 11:22:31 -07:00
2016-08-02 12:26:15 -07:00
def get_moving_mobject_movement(self, func):
for m in self.moving_mobjects:
if m.target is None:
m.target = m.copy()
target_point = func(m.get_center())
m.target.move_to(target_point)
return self.get_piece_movement(self.moving_mobjects)
2016-07-22 11:22:31 -07:00
def get_vector_movement(self, func):
for v in self.moving_vectors:
v.target = Vector(func(v.get_end()), color=v.get_color())
2018-08-15 17:30:24 -07:00
norm = get_norm(v.target.get_end())
if norm < 0.1:
v.target.get_tip().scale_in_place(norm)
return self.get_piece_movement(self.moving_vectors)
def get_transformable_label_movement(self):
for l in self.transformable_labels:
l.target = self.get_vector_label(
l.vector.target, l.target_text, **l.kwargs
)
return self.get_piece_movement(self.transformable_labels)
2016-07-22 11:22:31 -07:00
def apply_matrix(self, matrix, **kwargs):
self.apply_transposed_matrix(np.array(matrix).T, **kwargs)
def apply_inverse(self, matrix, **kwargs):
self.apply_matrix(np.linalg.inv(matrix), **kwargs)
2016-07-22 11:22:31 -07:00
def apply_transposed_matrix(self, transposed_matrix, **kwargs):
func = self.get_transposed_matrix_transformation(transposed_matrix)
2016-07-22 11:22:31 -07:00
if "path_arc" not in kwargs:
net_rotation = np.mean([
angle_of_vector(func(RIGHT)),
angle_of_vector(func(UP)) - np.pi / 2
2016-07-22 11:22:31 -07:00
])
kwargs["path_arc"] = net_rotation
self.apply_function(func, **kwargs)
2016-08-09 14:07:23 -07:00
def apply_inverse_transpose(self, t_matrix, **kwargs):
t_inv = np.linalg.inv(np.array(t_matrix).T).T
self.apply_transposed_matrix(t_inv, **kwargs)
2016-07-22 11:22:31 -07:00
def apply_nonlinear_transformation(self, function, **kwargs):
self.plane.prepare_for_nonlinear_transform()
2016-07-22 11:22:31 -07:00
self.apply_function(function, **kwargs)
def apply_function(self, function, added_anims=[], **kwargs):
2016-07-22 11:22:31 -07:00
if "run_time" not in kwargs:
kwargs["run_time"] = 3
2016-08-02 12:26:15 -07:00
anims = [
ApplyPointwiseFunction(function, t_mob)
for t_mob in self.transformable_mobjects
] + [
self.get_vector_movement(function),
self.get_transformable_label_movement(),
self.get_moving_mobject_movement(function),
] + [
Animation(f_mob)
for f_mob in self.foreground_mobjects
] + added_anims
self.play(*anims, **kwargs)