mirror of
https://github.com/3b1b/manim.git
synced 2025-08-01 17:29:06 +08:00
538 lines
16 KiB
Python
538 lines
16 KiB
Python
from helpers import *
|
|
|
|
from mobject.tex_mobject import TexMobject
|
|
from mobject import Mobject
|
|
from mobject.image_mobject import ImageMobject
|
|
from mobject.vectorized_mobject import *
|
|
|
|
from animation.animation import Animation
|
|
from animation.transform import *
|
|
from animation.simple_animations import *
|
|
from animation.playground import *
|
|
from topics.geometry import *
|
|
from topics.characters import *
|
|
from topics.functions import *
|
|
from topics.fractals import *
|
|
from topics.number_line import *
|
|
from topics.combinatorics import *
|
|
from topics.numerals import *
|
|
from topics.three_dimensions import *
|
|
from scene import Scene
|
|
from camera import Camera
|
|
from mobject.svg_mobject import *
|
|
from mobject.tex_mobject import *
|
|
|
|
|
|
class ClosedLoopScene(Scene):
|
|
CONFIG = {
|
|
"loop_anchor_points" : [
|
|
3*RIGHT,
|
|
2*RIGHT+UP,
|
|
3*RIGHT + 3*UP,
|
|
UP,
|
|
2*UP+LEFT,
|
|
2*LEFT + 2*UP,
|
|
3*LEFT,
|
|
2*LEFT+DOWN,
|
|
3*LEFT+2*DOWN,
|
|
2*DOWN+RIGHT,
|
|
LEFT+DOWN,
|
|
],
|
|
"square_vertices" : [
|
|
2*RIGHT+UP,
|
|
2*UP+LEFT,
|
|
2*LEFT+DOWN,
|
|
2*DOWN+RIGHT
|
|
],
|
|
"rect_vertices" : [
|
|
0*RIGHT + 1*UP,
|
|
-1*RIGHT + 2*UP,
|
|
-3*RIGHT + 0*UP,
|
|
-2*RIGHT + -1*UP,
|
|
],
|
|
"dot_color" : YELLOW,
|
|
"connecting_lines_color" : BLUE,
|
|
}
|
|
def setup(self):
|
|
self.dots = VGroup()
|
|
self.connecting_lines = VGroup()
|
|
self.add_loop()
|
|
|
|
def add_loop(self):
|
|
self.loop = self.get_default_loop()
|
|
self.add(self.loop)
|
|
|
|
def get_default_loop(self):
|
|
loop = VMobject()
|
|
loop.set_points_smoothly(
|
|
self.loop_anchor_points + [self.loop_anchor_points[0]]
|
|
)
|
|
return loop
|
|
|
|
def get_square(self):
|
|
return Polygon(*self.square_vertices)
|
|
|
|
def get_rect_vertex_dots(self, square = False):
|
|
if square:
|
|
vertices = self.square_vertices
|
|
else:
|
|
vertices = self.rect_vertices
|
|
dots = VGroup(*[Dot(v) for v in vertices])
|
|
dots.highlight(self.dot_color)
|
|
return dots
|
|
|
|
def add_dot(self, dot):
|
|
self.add_dots(dot)
|
|
|
|
def add_dots(self, *dots):
|
|
self.dots.add(*dots)
|
|
self.add(self.dots)
|
|
|
|
def add_rect_dots(self, square = False):
|
|
self.add_dots(*self.get_rect_vertex_dots())
|
|
|
|
def add_dots_at_alphas(self, *alphas):
|
|
self.add_dots(*[
|
|
Dot(
|
|
self.loop.point_from_proportion(alpha),
|
|
color = self.dot_color
|
|
)
|
|
for alpha in alphas
|
|
])
|
|
|
|
def add_connecting_lines(self, cyclic = False):
|
|
if cyclic:
|
|
pairs = adjascent_pairs(self.dots)
|
|
else:
|
|
n_pairs = len(list(self.dots))/2
|
|
pairs = zip(self.dots[:n_pairs], self.dots[n_pairs:])
|
|
for d1, d2 in pairs:
|
|
line = Line(
|
|
d1.get_center(), d2.get_center(),
|
|
stroke_width = 6
|
|
)
|
|
line.start_dot = d1
|
|
line.end_dot = d2
|
|
line.update_anim = UpdateFromFunc(
|
|
line,
|
|
lambda l : l.put_start_and_end_on(
|
|
l.start_dot.get_center(),
|
|
l.end_dot.get_center()
|
|
)
|
|
)
|
|
self.connecting_lines.add(line)
|
|
self.connecting_lines.highlight(self.connecting_lines_color)
|
|
self.add(self.connecting_lines, self.dots)
|
|
|
|
def get_line_anims(self):
|
|
return [
|
|
line.update_anim
|
|
for line in self.connecting_lines
|
|
] + [Animation(self.dots)]
|
|
|
|
def get_dot_alphas(self, dots = None, precision = 0.005):
|
|
if dots == None:
|
|
dots = self.dots
|
|
alphas = []
|
|
alpha_range = np.arange(0, 1, precision)
|
|
loop_points = np.array(map(self.loop.point_from_proportion, alpha_range))
|
|
for dot in dots:
|
|
vects = loop_points - dot.get_center()
|
|
norms = np.apply_along_axis(np.linalg.norm, 1, vects)
|
|
index = np.argmin(norms)
|
|
alphas.append(alpha_range[index])
|
|
return alphas
|
|
|
|
def let_dots_wonder(self, run_time = 5, random_seed = None, added_anims = []):
|
|
if random_seed is not None:
|
|
np.random.seed(random_seed)
|
|
start_alphas = self.get_dot_alphas()
|
|
alpha_rates = 0.1 + 0.1*np.random.random(len(list(self.dots)))
|
|
def generate_rate_func(start, rate):
|
|
return lambda t : (start + t*rate*run_time)%1
|
|
anims = [
|
|
MoveAlongPath(
|
|
dot,
|
|
self.loop,
|
|
rate_func = generate_rate_func(start, rate)
|
|
)
|
|
for dot, start, rate in zip(self.dots, start_alphas, alpha_rates)
|
|
]
|
|
anims += self.get_line_anims()
|
|
anims += added_anims
|
|
self.play(*anims, run_time = run_time)
|
|
|
|
def move_dots_to_alphas(self, alphas, run_time = 3):
|
|
assert(len(alphas) == len(list(self.dots)))
|
|
start_alphas = self.get_dot_alphas()
|
|
def generate_rate_func(start_alpha, alpha):
|
|
return lambda t : interpolate(start_alpha, alpha, smooth(t))
|
|
anims = [
|
|
MoveAlongPath(
|
|
dot, self.loop,
|
|
rate_func = generate_rate_func(sa, a),
|
|
run_time = run_time,
|
|
)
|
|
for dot, sa, a in zip(self.dots, start_alphas, alphas)
|
|
]
|
|
anims += self.get_line_anims()
|
|
self.play(*anims)
|
|
|
|
def transform_loop(self, target_loop, **kwargs):
|
|
alphas = self.get_dot_alphas()
|
|
dot_anims = []
|
|
for dot, alpha in zip(self.dots, alphas):
|
|
dot.generate_target()
|
|
dot.target.move_to(target_loop.point_from_proportion(alpha))
|
|
dot_anims.append(MoveToTarget(dot))
|
|
self.play(
|
|
Transform(self.loop, target_loop),
|
|
*dot_anims + self.get_line_anims(),
|
|
**kwargs
|
|
)
|
|
self.remove(self.loop)
|
|
self.loop = target_loop
|
|
self.add(self.loop)
|
|
|
|
def find_square(self):
|
|
alpha_quads = list(it.combinations(
|
|
np.arange(0, 1, 0.02) , 4
|
|
))
|
|
quads = np.array([
|
|
[
|
|
self.loop.point_from_proportion(alpha)
|
|
for alpha in quad
|
|
]
|
|
for quad in alpha_quads
|
|
])
|
|
scores = self.square_scores(quads)
|
|
index = np.argmin(scores)
|
|
return quads[index]
|
|
|
|
def square_scores(self, all_quads):
|
|
midpoint_diffs = np.apply_along_axis(
|
|
np.linalg.norm, 1,
|
|
0.5*(all_quads[:,0] + all_quads[:,2]) - 0.5*(all_quads[:,1] + all_quads[:,3])
|
|
)
|
|
vects1 = all_quads[:,0] - all_quads[:,2]
|
|
vects2 = all_quads[:,1] - all_quads[:,3]
|
|
distances1 = np.apply_along_axis(np.linalg.norm, 1, vects1)
|
|
distances2 = np.apply_along_axis(np.linalg.norm, 1, vects2)
|
|
distance_diffs = np.abs(distances1 - distances2)
|
|
midpoint_diffs /= distances1
|
|
distance_diffs /= distances2
|
|
|
|
buffed_d1s = np.repeat(distances1, 3).reshape(vects1.shape)
|
|
buffed_d2s = np.repeat(distances2, 3).reshape(vects2.shape)
|
|
unit_v1s = vects1/buffed_d1s
|
|
unit_v2s = vects2/buffed_d2s
|
|
dots = np.abs(unit_v1s[:,0]*unit_v2s[:,0] + unit_v1s[:,1]*unit_v2s[:,1] + unit_v1s[:,2]*unit_v2s[:,2])
|
|
|
|
return midpoint_diffs + distance_diffs + dots
|
|
|
|
|
|
|
|
#############################
|
|
|
|
class Introduction(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.play(self.get_teacher().change_mode, "hooray")
|
|
self.random_blink()
|
|
self.teacher_says("")
|
|
for pi in self.get_students():
|
|
pi.generate_target()
|
|
pi.target.change_mode("happy")
|
|
pi.target.look_at(self.get_teacher().bubble)
|
|
self.play(*map(MoveToTarget, self.get_students()))
|
|
self.random_blink(3)
|
|
self.teacher_says(
|
|
"Here's why \\\\ I'm excited...",
|
|
pi_creature_target_mode = "hooray"
|
|
)
|
|
for pi in self.get_students():
|
|
pi.target.look_at(self.get_teacher().eyes)
|
|
self.play(*map(MoveToTarget, self.get_students()))
|
|
self.dither()
|
|
|
|
class WhenIWasAKid(TeacherStudentsScene):
|
|
def construct(self):
|
|
children = self.get_children()
|
|
speaker = self.get_speaker()
|
|
|
|
self.prepare_everyone(children, speaker)
|
|
self.transition_from_previous_scene(children, speaker)
|
|
self.students = children
|
|
self.teacher = speaker
|
|
self.run_class()
|
|
self.grow_up()
|
|
|
|
def transition_from_previous_scene(self, children, speaker):
|
|
self.play(self.get_teacher().change_mode, "hooray", run_time = 0)
|
|
self.change_student_modes(*["happy"]*3)
|
|
|
|
speaker.look_at(children)
|
|
me = children[-1]
|
|
self.play(
|
|
FadeOut(self.get_students()),
|
|
Transform(self.get_teacher(), me)
|
|
)
|
|
self.remove(self.get_teacher())
|
|
self.add(me)
|
|
self.play(*map(FadeIn, children[:-1] + [speaker]))
|
|
self.random_blink()
|
|
|
|
def run_class(self):
|
|
children = self.students
|
|
speaker = self.teacher
|
|
title = TextMobject("Topology")
|
|
title.to_edge(UP)
|
|
|
|
self.random_blink()
|
|
self.play(self.teacher.change_mode, "speaking")
|
|
self.play(Write(title))
|
|
self.random_blink()
|
|
pi1, pi2, pi3, me = children
|
|
self.play(pi1.change_mode, "raise_right_hand")
|
|
self.random_blink()
|
|
self.play(
|
|
pi2.change_mode, "confused",
|
|
pi3.change_mode, "happy",
|
|
pi2.look_at, pi3.eyes,
|
|
pi3.look_at, pi2.eyes,
|
|
)
|
|
self.random_blink()
|
|
self.play(me.change_mode, "pondering")
|
|
self.dither()
|
|
self.random_blink(2)
|
|
self.play(pi1.change_mode, "raise_left_hand")
|
|
self.dither()
|
|
self.play(pi2.change_mode, "erm")
|
|
self.random_blink()
|
|
self.student_says(
|
|
"How is this math?",
|
|
student_index = -1,
|
|
pi_creature_target_mode = "pleading",
|
|
width = 5,
|
|
height = 3,
|
|
direction = RIGHT
|
|
)
|
|
self.play(
|
|
pi1.change_mode, "pondering",
|
|
pi2.change_mode, "pondering",
|
|
pi3.change_mode, "pondering",
|
|
)
|
|
self.play(speaker.change_mode, "pondering")
|
|
self.random_blink()
|
|
|
|
def grow_up(self):
|
|
me = self.students[-1]
|
|
self.students.remove(me)
|
|
morty = Mortimer(mode = "pondering")
|
|
morty.flip()
|
|
morty.move_to(me, aligned_edge = DOWN)
|
|
morty.to_edge(LEFT)
|
|
morty.look(RIGHT)
|
|
|
|
self.play(
|
|
Transform(me, morty),
|
|
*map(FadeOut, [
|
|
self.students, self.teacher,
|
|
me.bubble, me.bubble.content
|
|
])
|
|
)
|
|
self.remove(me)
|
|
self.add(morty)
|
|
self.play(Blink(morty))
|
|
self.dither()
|
|
self.play(morty.change_mode, "hooray")
|
|
self.dither()
|
|
|
|
|
|
def prepare_everyone(self, children, speaker):
|
|
self.everyone = list(children) + [speaker]
|
|
for pi in self.everyone:
|
|
pi.bubble = None
|
|
|
|
def get_children(self):
|
|
colors = [MAROON_E, YELLOW_D, PINK, GREY_BROWN]
|
|
children = VGroup(*[
|
|
BabyPiCreature(color = color)
|
|
for color in colors
|
|
])
|
|
children.arrange_submobjects(RIGHT)
|
|
children.to_edge(DOWN, buff = LARGE_BUFF)
|
|
children.to_edge(LEFT)
|
|
return children
|
|
|
|
def get_speaker(self):
|
|
speaker = Mathematician(mode = "happy")
|
|
speaker.flip()
|
|
speaker.to_edge(DOWN, buff = LARGE_BUFF)
|
|
speaker.to_edge(RIGHT)
|
|
return speaker
|
|
|
|
def get_everyone(self):
|
|
if hasattr(self, "everyone"):
|
|
return self.everyone
|
|
else:
|
|
return TeacherStudentsScene.get_everyone(self)
|
|
|
|
class FormingTheMobiusStrip(Scene):
|
|
def construct(self):
|
|
pass
|
|
|
|
class DrawLineOnMobiusStrip(Scene):
|
|
def construct(self):
|
|
pass
|
|
|
|
class MugIntoTorus(Scene):
|
|
def construct(self):
|
|
pass
|
|
|
|
class DefineInscribedSquareProblem(ClosedLoopScene):
|
|
def construct(self):
|
|
self.draw_loop()
|
|
self.cycle_through_shapes()
|
|
self.ask_about_rectangles()
|
|
|
|
def draw_loop(self):
|
|
self.title = TextMobject("Inscribed", "square", "problem")
|
|
self.title.to_edge(UP)
|
|
|
|
#Draw loop
|
|
self.remove(self.loop)
|
|
self.play(Write(self.title))
|
|
self.dither()
|
|
self.play(ShowCreation(
|
|
self.loop,
|
|
run_time = 3,
|
|
rate_func = None
|
|
))
|
|
self.dither()
|
|
self.add_rect_dots(square = True)
|
|
self.play(ShowCreation(self.dots, run_time = 2))
|
|
self.dither()
|
|
self.add_connecting_lines(cyclic = True)
|
|
self.play(
|
|
ShowCreation(
|
|
self.connecting_lines,
|
|
submobject_mode = "all_at_once",
|
|
run_time = 2
|
|
),
|
|
Animation(self.dots)
|
|
)
|
|
self.dither(2)
|
|
|
|
def cycle_through_shapes(self):
|
|
circle = Circle(radius = 2.5, color = WHITE)
|
|
ellipse = circle.copy()
|
|
ellipse.stretch(1.5, 0)
|
|
ellipse.stretch(0.7, 1)
|
|
ellipse.rotate(-np.pi/2)
|
|
ellipse.scale_to_fit_height(4)
|
|
pi_loop = TexMobject("\\pi")[0]
|
|
pi_loop.set_fill(opacity = 0)
|
|
pi_loop.set_stroke(
|
|
color = WHITE,
|
|
width = DEFAULT_POINT_THICKNESS
|
|
)
|
|
pi_loop.scale_to_fit_height(4)
|
|
randy = Randolph()
|
|
randy.look(DOWN)
|
|
randy.scale_to_fit_width(pi_loop.get_width())
|
|
randy.move_to(pi_loop, aligned_edge = DOWN)
|
|
randy.body.set_fill(opacity = 0)
|
|
randy.mouth.set_stroke(width = 0)
|
|
|
|
self.transform_loop(circle)
|
|
self.dither()
|
|
odd_eigths = np.linspace(1./8, 7./8, 4)
|
|
self.move_dots_to_alphas(odd_eigths)
|
|
self.dither()
|
|
for nudge in 0.1, -0.1, 0:
|
|
self.move_dots_to_alphas(odd_eigths+nudge)
|
|
self.dither()
|
|
self.transform_loop(ellipse)
|
|
self.dither()
|
|
nudge = 0.055
|
|
self.move_dots_to_alphas(
|
|
odd_eigths + [nudge, -nudge, nudge, -nudge]
|
|
)
|
|
self.dither(2)
|
|
self.transform_loop(pi_loop)
|
|
self.let_dots_wonder()
|
|
randy_anims = [
|
|
FadeIn(randy),
|
|
Animation(randy),
|
|
Blink(randy),
|
|
Animation(randy),
|
|
Blink(randy, rate_func = smooth)
|
|
]
|
|
for anim in randy_anims:
|
|
self.let_dots_wonder(
|
|
run_time = 1,
|
|
random_seed = 0,
|
|
added_anims = [anim]
|
|
)
|
|
self.remove(randy)
|
|
self.transform_loop(self.get_default_loop())
|
|
|
|
def ask_about_rectangles(self):
|
|
morty = Mortimer()
|
|
morty.next_to(ORIGIN, DOWN)
|
|
morty.to_edge(RIGHT)
|
|
|
|
new_title = TextMobject("Inscribed", "rectangle", "problem")
|
|
new_title.highlight_by_tex("rectangle", YELLOW)
|
|
new_title.to_edge(UP)
|
|
rect_dots = self.get_rect_vertex_dots()
|
|
rect_alphas = self.get_dot_alphas(rect_dots)
|
|
|
|
self.play(FadeIn(morty))
|
|
self.play(morty.change_mode, "speaking")
|
|
self.play(Transform(self.title, new_title))
|
|
self.move_dots_to_alphas(rect_alphas)
|
|
self.dither()
|
|
self.play(morty.change_mode, "hooray")
|
|
self.play(Blink(morty))
|
|
self.dither()
|
|
self.play(FadeOut(self.connecting_lines))
|
|
self.connecting_lines = VGroup()
|
|
self.play(morty.change_mode, "plain")
|
|
|
|
dot_pairs = [
|
|
VGroup(self.dots[i], self.dots[j])
|
|
for i, j in (0, 2), (1, 3)
|
|
]
|
|
pair_colors = MAROON_B, PURPLE_C
|
|
diag_lines = [
|
|
Line(d1.get_center(), d2.get_center(), color = c)
|
|
for (d1, d2), c in zip(dot_pairs, pair_colors)
|
|
]
|
|
|
|
for pair, line in zip(dot_pairs, diag_lines):
|
|
self.play(
|
|
FadeIn(line),
|
|
pair.highlight, line.get_color(),
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|