mirror of
https://github.com/3b1b/manim.git
synced 2025-07-30 13:34:19 +08:00
Material needed for matrix as transform article
This commit is contained in:
@ -57,7 +57,7 @@ class Rotating(Animation):
|
||||
|
||||
class RotationAsTransform(Rotating):
|
||||
DEFAULT_CONFIG = {
|
||||
"axes" : [IN],
|
||||
"axes" : [OUT],
|
||||
"radians" : np.pi / 2,
|
||||
"run_time" : DEFAULT_ANIMATION_RUN_TIME,
|
||||
"alpha_func" : smooth,
|
||||
|
@ -18,7 +18,7 @@ LOW_QUALITY_DISPLAY_CONFIG = {
|
||||
|
||||
|
||||
DEFAULT_POINT_DENSITY_2D = 25
|
||||
DEFAULT_POINT_DENSITY_1D = 150
|
||||
DEFAULT_POINT_DENSITY_1D = 200
|
||||
|
||||
#TODO, Make sure these are not needd
|
||||
DEFAULT_HEIGHT = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
|
||||
|
@ -65,13 +65,15 @@ class NumberLine(Mobject1D):
|
||||
"unit_length_to_spatial_width" : 1,
|
||||
"tick_size" : 0.1,
|
||||
"tick_frequency" : 0.5,
|
||||
"leftmost_tick" : -int(SPACE_WIDTH),
|
||||
"leftmost_tick" : None,
|
||||
"number_at_center" : 0,
|
||||
"numbers_with_elongated_ticks" : [0],
|
||||
"longer_tick_multiple" : 2,
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, NumberLine, kwargs)
|
||||
if self.leftmost_tick is None:
|
||||
self.leftmost_tick = -int(self.numerical_radius)
|
||||
self.left_num = self.number_at_center - self.numerical_radius
|
||||
self.right_num = self.number_at_center + self.numerical_radius
|
||||
Mobject1D.__init__(self, **kwargs)
|
||||
@ -128,20 +130,12 @@ class NumberLine(Mobject1D):
|
||||
return 4*DOWN*self.tick_size
|
||||
|
||||
def get_number_mobjects(self, *numbers):
|
||||
#TODO, handle decimals
|
||||
if len(numbers) == 0:
|
||||
numbers = self.default_numbers_to_display()
|
||||
log_spacing = int(np.log10(self.tick_frequency))
|
||||
if log_spacing < 0:
|
||||
num_decimal_places = 2-log_spacing
|
||||
else:
|
||||
num_decimal_places = 1+log_spacing
|
||||
result = []
|
||||
for number in numbers:
|
||||
if number < 0:
|
||||
num_string = str(number)[:num_decimal_places+1]
|
||||
else:
|
||||
num_string = str(number)[:num_decimal_places]
|
||||
mob = tex_mobject(num_string)
|
||||
mob = tex_mobject(str(int(number)))
|
||||
vert_scale = 2*self.tick_size/mob.get_height()
|
||||
hori_scale = self.tick_frequency*self.unit_length_to_spatial_width/mob.get_width()
|
||||
mob.scale(min(vert_scale, hori_scale))
|
||||
@ -152,10 +146,12 @@ class NumberLine(Mobject1D):
|
||||
|
||||
def add_numbers(self, *numbers):
|
||||
self.add(*self.get_number_mobjects(*numbers))
|
||||
return self
|
||||
|
||||
def remove_numbers(self):
|
||||
self.points = self.points[:self.number_of_points_without_numbers]
|
||||
self.rgbs = self.rgbs[:self.number_of_points_without_numbers]
|
||||
return self
|
||||
|
||||
class UnitInterval(NumberLine):
|
||||
DEFAULT_CONFIG = {
|
||||
|
@ -10,18 +10,16 @@ from animation import *
|
||||
from mobject import *
|
||||
from constants import *
|
||||
from region import *
|
||||
from scene import Scene, NumberLineScene
|
||||
from scene import Scene
|
||||
from script_wrapper import command_line_create_scene
|
||||
|
||||
|
||||
class SampleScene(NumberLineScene):
|
||||
class SampleScene(Scene):
|
||||
def construct(self):
|
||||
NumberLineScene.construct(self)
|
||||
arrow = Arrow(2*RIGHT+UP, 2*RIGHT)
|
||||
self.add(arrow)
|
||||
self.dither(2)
|
||||
self.zoom_in_on(2.4, zoom_factor = 10)
|
||||
self.dither(2)
|
||||
plane = NumberPlane(density = 400)
|
||||
arrow1 = Arrow(ORIGIN, UP, color = "green")
|
||||
arrow2 = Arrow(ORIGIN, LEFT, color = "Red")
|
||||
self.add(plane, arrow1, arrow2)
|
||||
|
||||
if __name__ == "__main__":
|
||||
command_line_create_scene()
|
@ -73,6 +73,20 @@ class NumberLineScene(Scene):
|
||||
self.add(self.number_line, *self.number_mobs)
|
||||
self.add(*additional_mobjects)
|
||||
|
||||
def show_multiplication(self, num, **kwargs):
|
||||
if "interpolation_function" not in kwargs:
|
||||
if num > 0:
|
||||
kwargs["interpolation_function"] = straight_path
|
||||
else:
|
||||
kwargs["interpolation_function"] = counterclockwise_path
|
||||
self.play(*[
|
||||
ApplyMethod(self.number_line.stretch, num, 0, **kwargs)
|
||||
]+[
|
||||
ApplyMethod(mob.shift, (num-1)*mob.get_center()[0]*RIGHT, **kwargs)
|
||||
for mob in self.number_mobs
|
||||
])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -43,6 +43,8 @@ class Scene(object):
|
||||
pass #To be implemented in subclasses
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, "name"):
|
||||
return self.name
|
||||
return self.__class__.__name__
|
||||
|
||||
def set_name(self, name):
|
||||
|
@ -38,6 +38,7 @@ class ShowMultiplication(NumberLineScene):
|
||||
(-3, False),
|
||||
(-3, True),
|
||||
(2, True),
|
||||
(6, True),
|
||||
]
|
||||
@staticmethod
|
||||
def args_to_string(num, show_original_line):
|
||||
@ -45,30 +46,17 @@ class ShowMultiplication(NumberLineScene):
|
||||
return str(num) + end_string
|
||||
|
||||
def construct(self, num, show_original_line):
|
||||
NumberLineScene.construct(self, density = abs(num)*DEFAULT_POINT_DENSITY_1D)
|
||||
config = {"density" : abs(num)*DEFAULT_POINT_DENSITY_1D}
|
||||
if abs(num) < 1:
|
||||
config["numerical_radius"] = SPACE_WIDTH/num
|
||||
|
||||
NumberLineScene.construct(self, **config)
|
||||
if show_original_line:
|
||||
self.copy_original_line()
|
||||
kwargs = {
|
||||
"run_time" : 2.0,
|
||||
"interpolation_function" : straight_path if num > 0 else counterclockwise_path
|
||||
}
|
||||
self.dither()
|
||||
new_number_line = deepcopy(self.number_line)
|
||||
new_number_line.stretch(num, 0)
|
||||
self.play(
|
||||
Transform(self.number_line, new_number_line, **kwargs),
|
||||
*[
|
||||
ApplyFunction(
|
||||
lambda m : m.do_in_place(m.stretch, 1.0/num, 0).stretch(num, 0),
|
||||
mobject,
|
||||
**kwargs
|
||||
)
|
||||
for mobject in self.number_mobs
|
||||
]
|
||||
)
|
||||
self.show_multiplication(num, run_time = 2.0)
|
||||
self.dither()
|
||||
|
||||
|
||||
def copy_original_line(self):
|
||||
copied_line = deepcopy(self.number_line)
|
||||
copied_num_mobs = deepcopy(self.number_mobs)
|
||||
@ -83,15 +71,76 @@ class ShowMultiplication(NumberLineScene):
|
||||
)
|
||||
self.dither()
|
||||
|
||||
class ExamplesOfOneDimensionalLinearTransforms(ShowMultiplication):
|
||||
args_list = []
|
||||
@staticmethod
|
||||
def args_to_string():
|
||||
return ""
|
||||
|
||||
def construct(self):
|
||||
for num in [2, 0.5, -3]:
|
||||
ShowMultiplication.construct(self, num, False)
|
||||
self.clear()
|
||||
|
||||
|
||||
class ExamplesOfNonlinearOneDimensionalTransforms(NumberLineScene):
|
||||
def construct(self):
|
||||
def sinx_plux_x((x, y, z)):
|
||||
return (np.sin(x) + 1.2*x, y, z)
|
||||
def shift_zero((x, y, z)):
|
||||
return (2*x+4, y, z)
|
||||
self.nonlinear = text_mobject("Not a Linear Transform")
|
||||
self.nonlinear.highlight("red").to_edge(UP)
|
||||
pairs = [
|
||||
(sinx_plux_x, "numbers don't remain evenly spaced"),
|
||||
(shift_zero, "zero does not remain fixed")
|
||||
]
|
||||
for func, explanation in pairs:
|
||||
self.dither()
|
||||
self.run_function(func, explanation)
|
||||
self.dither()
|
||||
|
||||
def run_function(self, function, explanation):
|
||||
self.clear()
|
||||
self.add(self.nonlinear)
|
||||
NumberLineScene.construct(self)
|
||||
words = text_mobject(explanation).highlight("red")
|
||||
words.next_to(self.nonlinear, DOWN, buff = 0.5)
|
||||
self.add(words)
|
||||
|
||||
self.play(
|
||||
ApplyPointwiseFunction(function, self.number_line),
|
||||
*[
|
||||
ApplyMethod(
|
||||
mob.shift,
|
||||
function(mob.get_center()) - mob.get_center()
|
||||
)
|
||||
for mob in self.number_mobs
|
||||
],
|
||||
run_time = 2.0
|
||||
)
|
||||
|
||||
|
||||
class ShowTwoThenThree(ShowMultiplication):
|
||||
args_list = []
|
||||
@staticmethod
|
||||
def args_to_string():
|
||||
return ""
|
||||
|
||||
def construct(self):
|
||||
NumberLineScene.construct(self, density = 10*DEFAULT_POINT_DENSITY_1D)
|
||||
self.copy_original_line()
|
||||
self.show_multiplication(2)
|
||||
self.dither()
|
||||
self.show_multiplication(3)
|
||||
self.dither()
|
||||
|
||||
|
||||
|
||||
########################################################
|
||||
|
||||
class TransformScene2D(Scene):
|
||||
def add_number_plane(self, density_factor, use_faded_lines = True):
|
||||
def add_number_plane(self, density_factor = 1, use_faded_lines = True):
|
||||
config = {
|
||||
"x_radius" : 2*SPACE_WIDTH,
|
||||
"y_radius" : 2*SPACE_HEIGHT,
|
||||
@ -131,13 +180,12 @@ class TransformScene2D(Scene):
|
||||
|
||||
class ShowMatrixTransform(TransformScene2D):
|
||||
args_list = [
|
||||
([[1, 2], [3, 4]], True, False),
|
||||
([[1, 3], [-2, 0]], False, False),
|
||||
([[1, 3], [-2, 0]], True, True),
|
||||
([[0, -1], [1, 0]], True, False),
|
||||
([[0, -1], [1, 0]], False, False),
|
||||
([[1, 0.5], [0.5, 1]], True, False),
|
||||
([[2, 0], [0, 2]], True, False),
|
||||
([[0.5, 0], [0, 0.5]], True, False),
|
||||
([[-1, 0], [0, -1]], True, False),
|
||||
([[-1, 0], [0, -1]], False, False),
|
||||
([[0, 1], [1, 0]], True, False),
|
||||
([[-2, 0], [-1, -1]], True, False),
|
||||
]
|
||||
@staticmethod
|
||||
def args_to_string(matrix, with_background, show_matrix):
|
||||
@ -157,6 +205,7 @@ class ShowMatrixTransform(TransformScene2D):
|
||||
self.add_x_y_arrows()
|
||||
else:
|
||||
self.add_number_plane(**number_plane_config)
|
||||
self.save_image()
|
||||
if show_matrix:
|
||||
self.add(matrix_mobject(matrix).to_corner(UP+LEFT))
|
||||
def func(mobject):
|
||||
@ -184,6 +233,199 @@ class ShowMatrixTransform(TransformScene2D):
|
||||
anims.append(Transform(arrow, new_arrow, **kwargs))
|
||||
self.play(*anims)
|
||||
self.dither()
|
||||
self.set_name(str(self) + self.args_to_string(matrix, with_background, show_matrix))
|
||||
self.save_image(os.path.join(MOVIE_DIR, MOVIE_PREFIX, "images"))
|
||||
|
||||
def get_density_factor(self, matrix):
|
||||
max_norm = max([
|
||||
abs(np.linalg.norm(column))
|
||||
for column in np.transpose(matrix)
|
||||
])
|
||||
return max(max_norm, 1)
|
||||
|
||||
def get_interpolation_function(self, matrix):
|
||||
def toggled_sign(n, i):
|
||||
return int(i%2 == 0)^int(n >= 0)
|
||||
rotational_components = [
|
||||
sign*np.arccos(matrix[i,i]/np.linalg.norm(matrix[:,i]))
|
||||
for i in [0, 1]
|
||||
for sign in [toggled_sign(matrix[1-i, i], i)]
|
||||
]
|
||||
average_rotation = sum(rotational_components)/2
|
||||
if abs(average_rotation) < np.pi / 2:
|
||||
return straight_path
|
||||
elif average_rotation > 0:
|
||||
return counterclockwise_path
|
||||
else:
|
||||
return clockwise_path
|
||||
|
||||
|
||||
class ExamplesOfTwoDimensionalLinearTransformations(ShowMatrixTransform):
|
||||
args_list = []
|
||||
@staticmethod
|
||||
def args_to_string():
|
||||
return ""
|
||||
|
||||
def construct(self):
|
||||
matrices = [
|
||||
[[1, 0.5],
|
||||
[0.5, 1]],
|
||||
[[0, -1],
|
||||
[2, 0]],
|
||||
[[1, 3],
|
||||
[-2, 0]],
|
||||
]
|
||||
for matrix in matrices:
|
||||
self.clear()
|
||||
ShowMatrixTransform.construct(self, matrix, False, False)
|
||||
|
||||
|
||||
class ExamplesOfNonlinearTwoDimensionalTransformations(Scene):
|
||||
def construct(self):
|
||||
Scene.construct(self)
|
||||
def squiggle((x, y, z)):
|
||||
return (x+np.sin(y), y+np.cos(x), z)
|
||||
def shift_zero((x, y, z)):
|
||||
return (2*x + 3*y + 4, -1*x+y+2, z)
|
||||
self.nonlinear = text_mobject("Nonlinear Transform")
|
||||
self.nonlinear.highlight("red").to_edge(UP)
|
||||
pairs = [
|
||||
(squiggle, "lines to not remain straight"),
|
||||
(shift_zero, "the origin does not remain fixed")
|
||||
]
|
||||
for function, explanation in pairs:
|
||||
self.apply_function(function, explanation)
|
||||
|
||||
|
||||
def apply_function(self, function, explanation):
|
||||
self.clear()
|
||||
number_plane = NumberPlane(
|
||||
x_radius = 1.5*SPACE_WIDTH,
|
||||
y_radius = 1.5*SPACE_HEIGHT,
|
||||
density = 3*DEFAULT_POINT_DENSITY_1D,
|
||||
)
|
||||
numbers = number_plane.get_coordinate_labels()
|
||||
words = text_mobject(explanation).highlight("red")
|
||||
words.next_to(self.nonlinear, DOWN, buff = 0.5)
|
||||
|
||||
self.add(number_plane, self.nonlinear, words, *numbers)
|
||||
self.dither()
|
||||
self.play(
|
||||
ApplyPointwiseFunction(function, number_plane),
|
||||
*[
|
||||
ApplyMethod(
|
||||
mob.shift,
|
||||
function(mob.get_center())-mob.get_center()
|
||||
)
|
||||
for mob in numbers
|
||||
],
|
||||
run_time = 2.0
|
||||
)
|
||||
self.dither(2)
|
||||
|
||||
|
||||
class TrickyExamplesOfNonlinearTwoDimensionalTransformations(Scene):
|
||||
def construct(self):
|
||||
number_plane = NumberPlane()
|
||||
phrase1, phrase2 = text_mobject([
|
||||
"These might look like they keep lines straight...",
|
||||
"but diagonal lines get curved"
|
||||
]).to_edge(UP).split()
|
||||
phrase2.highlight("red")
|
||||
diagonal = Line(
|
||||
DOWN*SPACE_HEIGHT+LEFT*SPACE_WIDTH,
|
||||
UP*SPACE_HEIGHT+RIGHT*SPACE_WIDTH
|
||||
)
|
||||
def sunrise((x, y, z)):
|
||||
return ((SPACE_HEIGHT+y)*x, y, z)
|
||||
|
||||
def squished((x, y, z)):
|
||||
return (x + np.sin(x), y+np.sin(y), z)
|
||||
|
||||
self.add(phrase1)
|
||||
self.run_function(sunrise, number_plane)
|
||||
self.run_function(squished, number_plane)
|
||||
self.add(phrase2)
|
||||
self.play(ShowCreation(diagonal))
|
||||
self.remove(diagonal)
|
||||
number_plane.add(diagonal)
|
||||
self.run_function(sunrise, number_plane)
|
||||
self.run_function(squished, number_plane)
|
||||
|
||||
|
||||
def run_function(self, function, plane):
|
||||
number_plane = deepcopy(plane)
|
||||
self.add(number_plane)
|
||||
self.dither()
|
||||
self.play(ApplyPointwiseFunction(function, number_plane, run_time = 2.0))
|
||||
self.dither(3)
|
||||
self.remove(number_plane)
|
||||
|
||||
|
||||
############# HORRIBLE! ##########################
|
||||
class ShowMatrixTransformHack(TransformScene2D):
|
||||
args_list = [
|
||||
([[1, 3], [-2, 0]], True, False),
|
||||
]
|
||||
@staticmethod
|
||||
def args_to_string(matrix, with_background, show_matrix):
|
||||
background_string = "WithBackground" if with_background else "WithoutBackground"
|
||||
show_string = "ShowingMatrix" if show_matrix else ""
|
||||
return matrix_to_string(matrix) + background_string + show_string
|
||||
|
||||
def construct(self, matrix, with_background, show_matrix):
|
||||
matrix = np.array(matrix)
|
||||
number_plane_config = {
|
||||
"density_factor" : self.get_density_factor(matrix)
|
||||
}
|
||||
if with_background:
|
||||
self.add_background()
|
||||
number_plane_config["use_faded_lines"] = False
|
||||
self.add_number_plane(**number_plane_config)
|
||||
self.add_x_y_arrows()
|
||||
else:
|
||||
self.add_number_plane(**number_plane_config)
|
||||
if show_matrix:
|
||||
self.add(matrix_mobject(matrix).to_corner(UP+LEFT))
|
||||
def func(mobject):
|
||||
mobject.points[:, :2] = np.dot(mobject.points[:, :2], np.transpose(matrix))
|
||||
return mobject
|
||||
dot = Dot((-1, 2, 0), color = "yellow")
|
||||
x_arrow_copy = deepcopy(self.x_arrow)
|
||||
y_arrow_copy = Arrow(LEFT, LEFT+2*UP, color = "red")
|
||||
|
||||
self.number_plane.add(dot)
|
||||
self.play(ApplyMethod(x_arrow_copy.rotate, np.pi))
|
||||
self.play(ShowCreation(y_arrow_copy))
|
||||
self.dither()
|
||||
self.remove(x_arrow_copy, y_arrow_copy)
|
||||
kwargs = {
|
||||
"run_time" : 2.0,
|
||||
"interpolation_function" : self.get_interpolation_function(matrix)
|
||||
}
|
||||
anims = [ApplyFunction(func, self.number_plane, **kwargs)]
|
||||
if hasattr(self, "x_arrow") and hasattr(self, "y_arrow"):
|
||||
for arrow, index in (self.x_arrow, 0), (self.y_arrow, 1):
|
||||
new_arrow = Arrow(
|
||||
ORIGIN,
|
||||
self.number_plane.num_pair_to_point(matrix[:,index]),
|
||||
color = arrow.get_color()
|
||||
)
|
||||
arrow.remove_tip()
|
||||
new_arrow.remove_tip()
|
||||
Mobject.align_data(arrow, new_arrow)
|
||||
arrow.add_tip()
|
||||
new_arrow.add_tip()
|
||||
anims.append(Transform(arrow, new_arrow, **kwargs))
|
||||
self.play(*anims)
|
||||
self.dither()
|
||||
|
||||
x_arrow_copy = deepcopy(self.x_arrow)
|
||||
y_arrow_copy = Arrow(LEFT+2*UP, 5*RIGHT+2*UP, color = "red")
|
||||
self.play(ApplyMethod(x_arrow_copy.rotate, np.pi))
|
||||
self.play(ShowCreation(y_arrow_copy))
|
||||
self.remove(x_arrow_copy, y_arrow_copy)
|
||||
self.dither(3)
|
||||
|
||||
def get_density_factor(self, matrix):
|
||||
max_norm = max([
|
||||
@ -207,8 +449,18 @@ class ShowMatrixTransform(TransformScene2D):
|
||||
return clockwise_path
|
||||
|
||||
|
||||
class Show90DegreeRotation(TransformScene2D):
|
||||
def construct(self):
|
||||
self.add_number_plane()
|
||||
self.add_background()
|
||||
self.add_x_y_arrows()
|
||||
|
||||
|
||||
self.dither()
|
||||
self.play(*[
|
||||
RotationAsTransform(mob, run_time = 2.0)
|
||||
for mob in self.number_plane, self.x_arrow, self.y_arrow
|
||||
])
|
||||
self.dither()
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user