mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
590 lines
18 KiB
Python
590 lines
18 KiB
Python
#!/usr/bin/env python
|
|
|
|
import numpy as np
|
|
import itertools as it
|
|
from copy import deepcopy
|
|
import sys
|
|
|
|
|
|
from animation import *
|
|
from mobject import *
|
|
from constants import *
|
|
from region import *
|
|
from scene import Scene
|
|
from script_wrapper import command_line_create_scene
|
|
from generate_logo import LogoGeneration
|
|
|
|
POEM_LINES = """Fixed poorly in notation with that two,
|
|
you shine so loud that you deserve a name.
|
|
Late though we are to make a change, it's true,
|
|
We can extol you 'til you have pi's fame.
|
|
One might object, ``Conventions matter not!
|
|
Great formulae cast truths transcending names.''
|
|
I've noticed, though, how language molds my thoughts;
|
|
the natural terms make heart and head the same.
|
|
So lose the two inside your autograph,
|
|
then guide our thoughts without your ``better'' half.
|
|
Wonders math imparts become so neat
|
|
when phrased with you, and pi remains off-screen.
|
|
Sine and exp both cycle to your beat.
|
|
Jive with Fourier, and forms are clean.
|
|
``Wait! Area of circles'', pi would say,
|
|
``sticks oddly to one half when tau's preferred.''
|
|
More to you then! For write it in this way,
|
|
then links to triangles can be inferred.
|
|
Nix pi, then all within geometry
|
|
shines clean and clear, as if by poetry.""".split("\n")
|
|
|
|
DIGIT_TO_WORD = {
|
|
'0' : "Zero",
|
|
'1' : "One",
|
|
'2' : "Two",
|
|
'3' : "Three",
|
|
'4' : "Four",
|
|
'5' : "Five",
|
|
'6' : "Six",
|
|
'7' : "Seven",
|
|
'8' : "Eight",
|
|
'9' : "Nine",
|
|
}
|
|
|
|
FORMULAE = [
|
|
"e^{x + \\tau i} = e^{x}",
|
|
"&\\Leftrightarrow",
|
|
"e^{x + 2\\pi i} = e^{x} \\\\",
|
|
"A = \\frac{1}{2} \\tau r^2",
|
|
"&\\Leftrightarrow",
|
|
"A = \\pi r^2 \\\\",
|
|
"n! \\sim \\sqrt{\\tau n}\\left(\\frac{n}{e}\\right)^n",
|
|
"&\\Leftrightarrow",
|
|
"n! \\sim \\sqrt{2\\pi n}\\left(\\frac{n}{e}\\right)^n \\\\",
|
|
# "\\sum_{n = 0}^\\infty \\frac{(-1)^n}{2n+1} = \\frac{\\tau}{8}",
|
|
# "&\\Leftrightarrow",
|
|
# "\\sum_{n = 0}^\\infty \\frac{(-1)^n}{2n+1} = \\frac{\\pi}{4} \\\\",
|
|
]
|
|
|
|
DIGITS = map(str, list("62831853071795864769"))
|
|
DIGITS[1] = "." + DIGITS[1] #2->.2
|
|
|
|
BUFF = 1.0
|
|
|
|
MOVIE_PREFIX = "tau_poem/"
|
|
|
|
class Welcome(LogoGeneration):
|
|
def construct(self):
|
|
text = "Happy $\\tau$ Day, from 3Blue1Brown!"
|
|
self.add(TextMobject(text).to_edge(UP))
|
|
LogoGeneration.construct(self)
|
|
|
|
class HappyTauDayWords(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Happy Tau Day Everybody!").scale(2)
|
|
tau = TauCreature().move_to(2*LEFT + UP)
|
|
pi = PiCreature().move_to(2*RIGHT + 3*DOWN)
|
|
pi.highlight("red")
|
|
self.add(words, tau, pi)
|
|
self.dither()
|
|
self.play(BlinkPiCreature(tau))
|
|
self.play(BlinkPiCreature(pi))
|
|
|
|
class TauPoem(Scene):
|
|
args_list = map(lambda x : (x,), range(len(POEM_LINES)))
|
|
@staticmethod
|
|
def args_to_string(line_num, *ignore):
|
|
return str(line_num)
|
|
|
|
def __init__(self, line_num, *args, **kwargs):
|
|
self.line_num = line_num
|
|
self.anim_kwargs = {
|
|
"run_time" : 4.0,
|
|
}
|
|
self.line_num_to_method = {
|
|
0 : self.line0,
|
|
1 : self.line1,
|
|
2 : self.line2,
|
|
3 : self.line3,
|
|
4 : self.line4,
|
|
5 : self.line5,
|
|
6 : self.line6,
|
|
7 : self.line7,
|
|
8 : self.line8,
|
|
9 : self.line9,
|
|
10 : self.line10,
|
|
11 : self.line11,
|
|
12 : self.line12,
|
|
13 : self.line13,
|
|
14 : self.line14,
|
|
15 : self.line15,
|
|
16 : self.line16,
|
|
17 : self.line17,
|
|
18 : self.line18,
|
|
19 : self.line19,
|
|
}
|
|
Scene.__init__(self, *args, **kwargs)
|
|
|
|
def construct(self):
|
|
self.add_line_and_number()
|
|
self.line_num_to_method[self.line_num]()
|
|
self.first_word_to_last_digit()
|
|
|
|
def add_line_and_number(self):
|
|
self.first_digits, new_digit, last_digits = TexMobject([
|
|
"".join(DIGITS[:self.line_num]),
|
|
DIGITS[self.line_num],
|
|
"".join(DIGITS[(self.line_num+1):]),
|
|
]).to_edge(UP, buff=0.2).split()
|
|
line_str = POEM_LINES[self.line_num]
|
|
if self.line_num == 0:
|
|
index = line_str.index("ed ")
|
|
elif self.line_num == 10:
|
|
index = line_str.index("ders")
|
|
else:
|
|
index = line_str.index(" ")
|
|
first_word, rest_of_line = TextMobject(
|
|
[line_str[:index], line_str[index:]]
|
|
).to_edge(UP).shift(BUFF*DOWN).split()
|
|
first_word.shift(0.15*RIGHT) #Stupid
|
|
number_word = TextMobject(DIGIT_TO_WORD[DIGITS[self.line_num][-1]])
|
|
number_word.shift(first_word.get_center())
|
|
number_word.shift(BUFF * UP / 2)
|
|
|
|
kwargs = {
|
|
"rate_func" : squish_rate_func(smooth),
|
|
}
|
|
self.add(first_word, rest_of_line, self.first_digits)
|
|
self.first_word = first_word
|
|
self.number_word = number_word
|
|
self.new_digit = new_digit
|
|
|
|
def first_word_to_last_digit(self):
|
|
if self.line_num == 19:
|
|
shift_val = SPACE_HEIGHT*DOWN
|
|
self.new_digit.shift(shift_val)
|
|
self.play(ApplyMethod(
|
|
self.first_digits.shift, shift_val, run_time = 2.0
|
|
))
|
|
self.dither(2)
|
|
self.play_over_time_range(0, 2,
|
|
Transform(
|
|
deepcopy(self.first_word), self.number_word,
|
|
rate_func = squish_rate_func(smooth)
|
|
)
|
|
)
|
|
self.play_over_time_range(2, 4,
|
|
Transform(
|
|
self.number_word, self.new_digit,
|
|
rate_func = squish_rate_func(smooth)
|
|
)
|
|
)
|
|
|
|
def line0(self):
|
|
two, pi = TexMobject(["2", "\\pi"]).scale(2).split()
|
|
self.add(two, pi)
|
|
two_copy = deepcopy(two).rotate(np.pi/10).highlight("yellow")
|
|
self.play(Transform(
|
|
two, two_copy,
|
|
rate_func = squish_rate_func(
|
|
lambda t : wiggle(t),
|
|
0.4, 0.9,
|
|
),
|
|
**self.anim_kwargs
|
|
))
|
|
|
|
def line1(self):
|
|
two_pi = TexMobject(["2", "\\pi"]).scale(2)
|
|
tau = TauCreature()
|
|
tau.to_symbol()
|
|
sphere = Mobject()
|
|
sphere.interpolate(
|
|
two_pi,
|
|
Sphere().highlight("yellow"),
|
|
0.8
|
|
)
|
|
self.add(two_pi)
|
|
self.dither()
|
|
self.play(CounterclockwiseTransform(
|
|
two_pi, sphere,
|
|
rate_func = lambda t : 2*smooth(t/2)
|
|
))
|
|
self.remove(two_pi)
|
|
self.play(CounterclockwiseTransform(
|
|
sphere, tau,
|
|
rate_func = lambda t : 2*(smooth(t/2+0.5)-0.5)
|
|
))
|
|
self.remove(sphere)
|
|
self.add(tau)
|
|
self.dither()
|
|
|
|
def line2(self):
|
|
tau = TauCreature()
|
|
tau.make_sad()
|
|
tau.mouth.points = np.array(sorted(
|
|
tau.mouth.points,
|
|
lambda p0, p1 : cmp(p0[0], p1[0])
|
|
))
|
|
blinked = deepcopy(tau).blink()
|
|
for eye in blinked.eyes:
|
|
eye.highlight("black")
|
|
self.add(*set(tau.parts).difference(tau.white_parts))
|
|
self.play(*[
|
|
Transform(*eyes)
|
|
for eyes in zip(blinked.eyes, tau.eyes)
|
|
])
|
|
self.play(ShowCreation(tau.mouth))
|
|
self.dither(2)
|
|
|
|
def line3(self):
|
|
tau = TauCreature().make_sad()
|
|
pi = PiCreature()
|
|
self.add(*tau.parts)
|
|
self.dither()
|
|
self.play(
|
|
Transform(tau.leg, pi.left_leg),
|
|
ShowCreation(pi.right_leg),
|
|
run_time = 1.0,
|
|
)
|
|
self.play(*[
|
|
Transform(*parts)
|
|
for parts in zip(tau.white_parts, pi.white_parts)
|
|
])
|
|
self.remove(*tau.parts + pi.parts)
|
|
self.play(BlinkPiCreature(pi))
|
|
|
|
def pi_speaking(self, text):
|
|
pi = PiCreature()
|
|
pi.highlight("red").give_straight_face()
|
|
pi.shift(3*DOWN + LEFT)
|
|
bubble = SpeechBubble().speak_from(pi)
|
|
bubble.write(text)
|
|
return pi, bubble
|
|
|
|
def line4(self):
|
|
pi, bubble = self.pi_speaking("Conventions matter \\\\ not!")
|
|
self.add(pi)
|
|
self.dither()
|
|
self.play(Transform(
|
|
Point(bubble.tip).highlight("black"),
|
|
bubble
|
|
))
|
|
|
|
|
|
def line5(self):
|
|
pi, bubble = self.pi_speaking("""
|
|
Great formulae cast \\\\
|
|
truths transcending \\\\
|
|
names.
|
|
""")
|
|
self.add(pi, bubble)
|
|
|
|
formulae = TexMobject(FORMULAE, size = "\\small")
|
|
formulae.scale(1.25)
|
|
formulae.to_corner([1, -1, 0])
|
|
self.play(FadeIn(formulae))
|
|
|
|
def line6(self):
|
|
bubble = ThoughtBubble()
|
|
self.play(ApplyFunction(
|
|
lambda p : 2 * p / np.linalg.norm(p),
|
|
bubble,
|
|
rate_func = wiggle,
|
|
run_time = 3.0,
|
|
))
|
|
|
|
def line7(self):
|
|
bubble = ThoughtBubble()
|
|
heart = ImageMobject("heart")
|
|
heart.scale(0.5).shift(DOWN).highlight("red")
|
|
for mob in bubble, heart:
|
|
mob.sort_points(np.linalg.norm)
|
|
|
|
self.add(bubble)
|
|
self.dither()
|
|
self.remove(bubble)
|
|
bubble_copy = deepcopy(bubble)
|
|
self.play(CounterclockwiseTransform(bubble_copy, heart))
|
|
self.dither()
|
|
self.remove(bubble_copy)
|
|
self.play(CounterclockwiseTransform(heart, bubble))
|
|
self.dither()
|
|
|
|
|
|
def line8(self):
|
|
pi = PiCreature().give_straight_face()
|
|
tau = TauCreature()
|
|
two = ImageMobject("big2").scale(0.5).shift(1.6*LEFT + 0.1*DOWN)
|
|
|
|
self.add(two, *pi.parts)
|
|
self.dither()
|
|
self.play(
|
|
Transform(pi.left_leg, tau.leg),
|
|
Transform(
|
|
pi.right_leg,
|
|
Point(pi.right_leg.points[0,:]).highlight("black")
|
|
),
|
|
Transform(pi.mouth, tau.mouth),
|
|
CounterclockwiseTransform(
|
|
two,
|
|
Dot(two.get_center()).highlight("black")
|
|
)
|
|
)
|
|
|
|
def line9(self):
|
|
tau = TauCreature()
|
|
pi = PiCreature().highlight("red").give_straight_face()
|
|
pi.scale(0.2).move_to(tau.arm.points[-1,:])
|
|
point = Point(pi.get_center()).highlight("black")
|
|
vanish_local = 3*(LEFT + UP)
|
|
new_pi = deepcopy(pi)
|
|
new_pi.scale(0.01)
|
|
new_pi.rotate(np.pi)
|
|
new_pi.shift(vanish_local)
|
|
Mobject.highlight(new_pi, "black")
|
|
|
|
self.add(tau)
|
|
self.dither()
|
|
self.play(Transform(point, pi))
|
|
self.remove(point)
|
|
self.add(pi)
|
|
self.play(WaveArm(tau),Transform(pi, new_pi))
|
|
|
|
def line10(self):
|
|
formulae = TexMobject(FORMULAE, "\\small")
|
|
formulae.scale(1.5).to_edge(DOWN)
|
|
self.add(formulae)
|
|
|
|
def line11(self):
|
|
formulae = TexMobject(FORMULAE, "\\small")
|
|
formulae.scale(1.5).to_edge(DOWN)
|
|
formulae = formulae.split()
|
|
f_copy = deepcopy(formulae)
|
|
for mob, count in zip(f_copy, it.count()):
|
|
if count%3 == 0:
|
|
mob.to_edge(LEFT).shift(RIGHT*(SPACE_WIDTH-1))
|
|
else:
|
|
mob.shift(2*SPACE_WIDTH*RIGHT)
|
|
self.play(*[
|
|
Transform(*mobs, run_time = 2.0)
|
|
for mobs in zip(formulae, f_copy)
|
|
])
|
|
|
|
def line12(self):
|
|
interval_size = 0.5
|
|
axes_center = SPACE_WIDTH*LEFT/2
|
|
grid_center = SPACE_WIDTH*RIGHT/2
|
|
radius = SPACE_WIDTH / 2.0
|
|
axes = Axes(
|
|
radius = radius,
|
|
interval_size = interval_size
|
|
)
|
|
axes.shift(axes_center)
|
|
def sine_curve(t):
|
|
t += 1
|
|
result = np.array((-np.pi*t, np.sin(np.pi*t), 0))
|
|
result *= interval_size
|
|
result += axes_center
|
|
return result
|
|
sine = ParametricFunction(sine_curve)
|
|
sine_period = Line(
|
|
axes_center,
|
|
axes_center + 2*np.pi*interval_size*RIGHT
|
|
)
|
|
grid = Grid(radius = radius).shift(grid_center)
|
|
circle = Circle().scale(interval_size).shift(grid_center)
|
|
grid.add(TexMobject("e^{ix}").shift(grid_center+UP+RIGHT))
|
|
circle.highlight("white")
|
|
tau_line = Line(
|
|
*[np.pi*interval_size*vect for vect in LEFT, RIGHT],
|
|
density = 5*DEFAULT_POINT_DENSITY_1D
|
|
)
|
|
tau_line.highlight("red")
|
|
tau = TexMobject("\\tau")
|
|
tau.shift(tau_line.get_center() + 0.5*UP)
|
|
|
|
self.add(axes, grid)
|
|
self.play(
|
|
TransformAnimations(
|
|
ShowCreation(sine),
|
|
ShowCreation(deepcopy(sine).shift(2*np.pi*interval_size*RIGHT)),
|
|
run_time = 2.0,
|
|
rate_func = smooth
|
|
),
|
|
ShowCreation(circle)
|
|
)
|
|
self.play(
|
|
CounterclockwiseTransform(sine_period, tau_line),
|
|
CounterclockwiseTransform(circle, deepcopy(tau_line)),
|
|
FadeOut(axes),
|
|
FadeOut(grid),
|
|
FadeOut(sine),
|
|
FadeIn(tau),
|
|
)
|
|
self.dither()
|
|
|
|
|
|
def line13(self):
|
|
formula = form_start, two_pi, form_end = TexMobject([
|
|
"\\hat{f^{(n)}}(\\xi) = (",
|
|
"2\\pi",
|
|
"i\\xi)^n \\hat{f}(\\xi)"
|
|
]).shift(DOWN).split()
|
|
tau = TauCreature().center()
|
|
tau.scale(two_pi.get_width()/tau.get_width())
|
|
tau.shift(two_pi.get_center()+0.2*UP)
|
|
tau.rewire_part_attributes()
|
|
|
|
self.add(*formula)
|
|
self.dither()
|
|
self.play(CounterclockwiseTransform(two_pi, tau))
|
|
self.remove(two_pi)
|
|
self.play(BlinkPiCreature(tau))
|
|
self.dither()
|
|
|
|
def line14(self):
|
|
pi, bubble = self.pi_speaking(
|
|
"Wait! Area \\\\ of circles"
|
|
)
|
|
self.add(pi)
|
|
self.play(
|
|
Transform(Point(bubble.tip).highlight("black"), bubble)
|
|
)
|
|
|
|
def line15(self):
|
|
pi, bubble = self.pi_speaking(
|
|
"sticks oddly \\\\ to one half when \\\\ tau's preferred."
|
|
)
|
|
formula = form_start, half, form_end = TexMobject([
|
|
"A = ",
|
|
"\\frac{1}{2}",
|
|
"\\tau r^2"
|
|
]).split()
|
|
|
|
self.add(pi, bubble, *formula)
|
|
self.dither(2)
|
|
self.play(ApplyMethod(half.highlight, "yellow"))
|
|
|
|
def line16(self):
|
|
self.add(TexMobject(
|
|
"\\frac{1}{2}\\tau r^2"
|
|
).scale(2).shift(DOWN))
|
|
|
|
def line17(self):
|
|
circle = Dot(
|
|
radius = 1,
|
|
density = 4*DEFAULT_POINT_DENSITY_1D
|
|
)
|
|
blue_rgb = np.array(Color("blue").get_rgb())
|
|
white_rgb = np.ones(3)
|
|
circle.rgbs = np.array([
|
|
alpha * blue_rgb + (1 - alpha) * white_rgb
|
|
for alpha in np.arange(0, 1, 1.0/len(circle.rgbs))
|
|
])
|
|
for index in range(circle.points.shape[0]):
|
|
circle.rgbs
|
|
def trianglify((x, y, z)):
|
|
norm = np.linalg.norm((x, y, z))
|
|
comp = complex(x, y)*complex(0, 1)
|
|
return (
|
|
norm * np.log(comp).imag,
|
|
-norm,
|
|
0
|
|
)
|
|
tau_r = TexMobject("\\tau r").shift(1.3*DOWN)
|
|
r = TexMobject("r").shift(0.2*RIGHT + 0.7*DOWN)
|
|
lines = [
|
|
Line(DOWN+np.pi*LEFT, DOWN+np.pi*RIGHT),
|
|
Line(ORIGIN, DOWN)
|
|
]
|
|
for line in lines:
|
|
line.highlight("red")
|
|
|
|
self.play(ApplyFunction(trianglify, circle, run_time = 2.0))
|
|
self.add(tau_r, r)
|
|
self.play(*[
|
|
ShowCreation(line, run_time = 1.0)
|
|
for line in lines
|
|
])
|
|
self.dither()
|
|
|
|
def line18(self):
|
|
tau = TauCreature()
|
|
tau.shift_eyes()
|
|
tau.move_to(DOWN)
|
|
pi = PiCreature()
|
|
pi.highlight("red")
|
|
pi.move_to(DOWN + 3*LEFT)
|
|
mad_tau = deepcopy(tau).make_mean()
|
|
mad_tau.arm.wag(0.5*UP, LEFT, 2.0)
|
|
sad_pi = deepcopy(pi).shift_eyes().make_sad()
|
|
blinked_tau = deepcopy(tau).blink()
|
|
blinked_pi = deepcopy(pi).blink()
|
|
|
|
self.add(*pi.parts + tau.parts)
|
|
self.dither(0.8)
|
|
self.play(*[
|
|
Transform(*eyes, run_time = 0.2, rate_func = rush_into)
|
|
for eyes in [
|
|
(tau.left_eye, blinked_tau.left_eye),
|
|
(tau.right_eye, blinked_tau.right_eye),
|
|
]
|
|
])
|
|
self.remove(tau.left_eye, tau.right_eye)
|
|
self.play(*[
|
|
Transform(*eyes, run_time = 0.2, rate_func = rush_from)
|
|
for eyes in [
|
|
(blinked_tau.left_eye, mad_tau.left_eye),
|
|
(blinked_tau.right_eye, mad_tau.right_eye),
|
|
]
|
|
])
|
|
self.remove(blinked_tau.left_eye, blinked_tau.right_eye)
|
|
self.add(mad_tau.left_eye, mad_tau.right_eye)
|
|
self.play(
|
|
Transform(tau.arm, mad_tau.arm),
|
|
Transform(tau.mouth, mad_tau.mouth),
|
|
run_time = 0.5
|
|
)
|
|
self.remove(*tau.parts + blinked_tau.parts)
|
|
self.add(*mad_tau.parts)
|
|
|
|
self.play(*[
|
|
Transform(*eyes, run_time = 0.2, rate_func = rush_into)
|
|
for eyes in [
|
|
(pi.left_eye, blinked_pi.left_eye),
|
|
(pi.right_eye, blinked_pi.right_eye),
|
|
]
|
|
])
|
|
self.remove(pi.left_eye, pi.right_eye)
|
|
self.play(*[
|
|
Transform(*eyes, run_time = 0.2, rate_func = rush_from)
|
|
for eyes in [
|
|
(blinked_pi.left_eye, sad_pi.left_eye),
|
|
(blinked_pi.right_eye, sad_pi.right_eye),
|
|
]
|
|
] + [
|
|
Transform(pi.mouth, sad_pi.mouth, run_time = 0.2)
|
|
])
|
|
self.remove(*blinked_pi.parts + pi.parts + sad_pi.parts)
|
|
self.play(
|
|
WalkPiCreature(sad_pi, DOWN+4*LEFT),
|
|
run_time = 1.0
|
|
)
|
|
self.dither()
|
|
|
|
def line19(self):
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
command_line_create_scene(MOVIE_PREFIX)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|