Finished Tau Poem

This commit is contained in:
Grant Sanderson
2015-06-27 04:49:10 -07:00
parent 1dc7a5421d
commit 400aa0aada
15 changed files with 872 additions and 110 deletions

View File

@ -68,9 +68,9 @@ class Animation(object):
def update(self, alpha):
if alpha < 0:
alpha = 0
alpha = 0.0
if alpha > 1:
alpha = 1
alpha = 1.0
self.update_mobject(self.alpha_func(alpha))
def filter_out(self, *filter_functions):
@ -111,5 +111,40 @@ class Animation(object):
self.update(1)
class Succession(Animation):
def __init__(self, *animations, **kwargs):
if "run_time" in kwargs:
run_time = kwargs.pop("run_time")
else:
run_time = sum([anim.run_time for anim in animations])
self.num_anims = len(animations)
self.anims = animations
mobject = animations[0].mobject
Animation.__init__(self, mobject, run_time = run_time, **kwargs)
def __str__(self):
return self.__class__.__name__ + \
"".join(map(str, self.anims))
def update(self, alpha):
scaled_alpha = alpha*self.num_anims
self.mobject = self.anims
for index in range(len(self.anims)):
self.anims[index].update(scaled_alpha - index)

View File

@ -158,12 +158,11 @@ class WalkPiCreature(Animation):
class BlinkPiCreature(Transform):
def __init__(self, pi_creature, run_time = 0.2, *args, **kwargs):
def __init__(self, pi_creature, *args, **kwargs):
blinked = deepcopy(pi_creature).blink()
Transform.__init__(
self, pi_creature, blinked,
alpha_func = there_and_back,
run_time = run_time,
alpha_func = squish_alpha_func(there_and_back),
*args, **kwargs
)

View File

@ -150,11 +150,14 @@ class ComplexFunction(ApplyFunction):
#Todo, abstract away function naming'
#Fuck this is cool!
class TransformAnimations(Transform):
def __init__(self, start_anim, end_anim,
alpha_func = squish_alpha_func(high_inflection_0_to_1),
**kwargs):
if "run_time" in kwargs:
run_time = kwargs.pop("run_time")
for anim in start_anim, end_anim:
anim.set_run_time(run_time)
self.start_anim, self.end_anim = start_anim, end_anim
Transform.__init__(
self,

View File

@ -29,7 +29,7 @@ SPACE_WIDTH = SPACE_HEIGHT * DEFAULT_WIDTH / DEFAULT_HEIGHT
#All in seconds
DEFAULT_FRAME_DURATION = 0.04
DEFAULT_ANIMATION_RUN_TIME = 3.0
DEFAULT_ANIMATION_RUN_TIME = 1.0
DEFAULT_TRANSFORM_RUN_TIME = 1.0
DEFAULT_DITHER_TIME = 1.0

View File

@ -106,15 +106,21 @@ def make_even_by_cycling(iterable_1, iterable_2):
[cycle2.next() for x in range(length)]
)
### Alpha Functions ###
def sigmoid(x):
return 1.0/(1 + np.exp(-x))
### Alpha Functions ###
def high_inflection_0_to_1(t, inflection = 10.0):
error = sigmoid(-inflection / 2)
return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error)
def rush_into(t):
return 2*high_inflection_0_to_1(t/2)
def rush_from(t):
return 2*high_inflection_0_to_1(t/2+0.5) - 1
def there_and_back(t, inflection = 10.0):
new_t = 2*t if t < 0.5 else 2*(1 - t)
return high_inflection_0_to_1(new_t, inflection)
@ -128,9 +134,9 @@ def wiggle(t, wiggles = 2):
def squish_alpha_func(func, a = 0.4, b = 0.6):
def result(t):
if t < a:
return 0
return func(0)
elif t > b:
return 1
return func(1)
else:
return func((t-a)/(b-a))
return result

View File

@ -9,8 +9,9 @@ from simple_mobjects import *
class PiCreature(Mobject):
DEFAULT_COLOR = "blue"
def __init__(self, color = DEFAULT_COLOR, **kwargs):
Mobject.__init__(self, color = color, **kwargs)
def __init__(self, **kwargs):
Mobject.__init__(self, **kwargs)
color = self.DEFAULT_COLOR
scale_val = 0.5
mouth_to_eyes_distance = 0.25
part_names = [
@ -43,7 +44,8 @@ class PiCreature(Mobject):
self.right_eye.get_center()/2 -
(0, mouth_to_eyes_distance, 0)
)
self.eyes = [self.left_eye, self.right_eye]
self.legs = [self.left_leg, self.right_leg]
for part in parts:
self.add(part)
self.parts = parts
@ -64,11 +66,16 @@ class PiCreature(Mobject):
part.rgbs = self.rgbs[curr:curr+n_points,:]
curr += n_points
def reload_from_parts(self):
self.rewire_part_attributes(self_from_parts = True)
def highlight(self, color):
def highlight(self, color, condition = None):
if condition is not None:
Mobject.highlight(self, color, condition)
return self
for part in set(self.parts).difference(self.white_parts):
part.highlight(color)
self.rewire_part_attributes(self_from_parts = True)
self.reload_from_parts()
return self
def move_to(self, destination):
@ -78,6 +85,7 @@ class PiCreature(Mobject):
0
))
self.shift(destination-bottom)
self.rewire_part_attributes()
return self
def give_frown(self):
@ -85,7 +93,7 @@ class PiCreature(Mobject):
self.mouth.center()
self.mouth.apply_function(lambda (x, y, z) : (x, -y, z))
self.mouth.shift(center)
self.rewire_part_attributes(self_from_parts = True)
self.reload_from_parts()
return self
def give_straight_face(self):
@ -97,7 +105,33 @@ class PiCreature(Mobject):
self.parts[self.parts.index(self.mouth)] = new_mouth
self.white_parts[self.white_parts.index(self.mouth)] = new_mouth
self.mouth = new_mouth
self.rewire_part_attributes(self_from_parts = True)
self.reload_from_parts()
return self
def get_eye_center(self):
return center_of_mass([
self.left_eye.get_center(),
self.right_eye.get_center()
]) + 0.04*RIGHT + 0.02*UP
def make_mean(self):
eye_x, eye_y = self.get_eye_center()[:2]
def should_delete((x, y, z)):
return y - eye_y > 0.3*abs(x - eye_x)
for eye in self.left_eye, self.right_eye:
eye.highlight("black", should_delete)
self.give_straight_face()
return self
def make_sad(self):
eye_x, eye_y = self.get_eye_center()[:2]
eye_y += 0.15
def should_delete((x, y, z)):
return y - eye_y > -0.3*abs(x - eye_x)
for eye in self.left_eye, self.right_eye:
eye.highlight("black", should_delete)
self.give_frown()
self.reload_from_parts()
return self
def get_step_intermediate(self, pi_creature):
@ -122,22 +156,48 @@ class PiCreature(Mobject):
eye.apply_function(
lambda (x, y, z) : (x, bottom, z)
)
self.rewire_part_attributes(self_from_parts = True)
self.reload_from_parts()
return self
def shift_eyes(self):
for eye in self.left_eye, self.right_eye:
center = eye.get_center()
eye.center()
eye.rotate(np.pi, UP)
eye.shift(center)
self.reload_from_parts()
return self
def to_symbol(self):
for white_part in self.white_parts:
self.parts.remove(white_part)
self.reload_from_parts()
return self
class Randolph(PiCreature):
pass #Nothing more than an alternative name
class Mortimer(PiCreature):
DEFAULT_COLOR = DARK_BROWN
def __init__(self, *args, **kwargs):
PiCreature.__init__(self, *args, **kwargs)
self.highlight(DARK_BROWN)
# self.highlight(DARK_BROWN)
self.give_straight_face()
self.rotate(np.pi, UP)
self.rewire_part_attributes()
class TauCreature(PiCreature):
def __init__(self, **kwargs):
leg_shift_val = 0.25
leg_wag_val = 0.2
PiCreature.__init__(self, **kwargs)
self.parts.remove(self.right_leg)
self.left_leg.shift(leg_shift_val*RIGHT)
self.left_leg.wag(leg_wag_val*RIGHT, DOWN)
self.leg = self.left_leg
self.reload_from_parts()

View File

@ -114,5 +114,18 @@ class NumberLine(Mobject1D):
for y in np.arange(-self.tick_size, self.tick_size, self.epsilon)
])
class Axes(CompoundMobject):
def __init__(self, *args, **kwargs):
x_axis = NumberLine(*args, **kwargs)
y_axis = NumberLine(*args, **kwargs).rotate(np.pi/2, OUT)
CompoundMobject.__init__(self, x_axis, y_axis)

View File

@ -90,10 +90,19 @@ class SpeechBubble(ImageMobject):
def write(self, text):
smidgeon = 0.1*UP + 0.2*self.direction
self.clear()
self.text = text_mobject(text)
self.text.scale(0.75*self.get_width() / self.text.get_width())
self.text.shift(self.get_center() + smidgeon)
self.add(self.text)
def clear(self):
if not hasattr(self, "text"):
return
num_text_points = self.text.points.shape[0]
self.points = self.points[:num_text_points]
self.rgbs = self.rgbs[:num_text_points]
self.text = Mobject()
class ThoughtBubble(ImageMobject):
def __init__(self, *args, **kwargs):
@ -117,19 +126,15 @@ class VideoIcon(ImageMobject):
self.center()
def text_mobject(text, size = "\\Large"):
return tex_mobjects(text, size, TEMPLATE_TEXT_FILE)
return tex_mobject(text, size, TEMPLATE_TEXT_FILE)
#Purely redundant function to make singulars and plurals sensible
def tex_mobject(expression, size = "\\Huge"):
return tex_mobjects(expression, size)
def tex_mobjects(expression,
def tex_mobject(expression,
size = "\\Huge",
template_tex_file = TEMPLATE_TEX_FILE):
images = tex_to_image(expression, size, template_tex_file)
if isinstance(images, list):
#TODO, is checking listiness really the best here?
return CompoundMobject(*map(ImageMobject, images)).center().split()
return CompoundMobject(*map(ImageMobject, images)).center()
else:
return ImageMobject(images).center()

View File

@ -171,14 +171,16 @@ class Mobject(object):
return (result.real, result.imag, 0)
return self.apply_function(point_map)
def highlight(self, color = "red", condition = lambda x : True):
def highlight(self, color = "red", condition = None):
"""
Condition is function which takes in one arguments, (x, y, z).
"""
#TODO, Should self.color change?
rgb = Color(color).get_rgb()
if condition:
to_change = np.apply_along_axis(condition, 1, self.points)
self.rgbs[to_change, :] *= 0
self.rgbs[to_change, :] += Color(color).get_rgb()
self.rgbs[to_change, :] = rgb
else:
self.rgbs[:,:] = rgb
return self
def fade(self, brightness = 0.5):
@ -191,6 +193,15 @@ class Mobject(object):
self.rgbs = self.rgbs[to_eliminate]
return self
def sort_points(self, function = lambda p : p[0]):
"""
function is any map from R^3 to R
"""
self.points = np.array(sorted(
self.points,
lambda *points : cmp(*map(function, points))
))
### Getters ###
def get_num_points(self):

View File

@ -16,13 +16,8 @@ from script_wrapper import command_line_create_scene
class SampleScene(Scene):
def construct(self):
randy = Randolph()
self.add(randy)
self.dither()
self.animate(BlinkPiCreature(randy))
self.dither()
self.animate(WaveArm(randy))
self.dither()
tauy = TauCreature()
self.animate(ApplyMethod(tauy.make_sad))

View File

@ -115,6 +115,29 @@ class Scene(object):
progress_bar.finish()
return self
def animate_over_time_range(self, t0, t1, animation):
needed_scene_time = max(abs(t0), abs(t1))
existing_scene_time = len(self.frames)*self.frame_duration
if existing_scene_time < needed_scene_time:
self.dither(needed_scene_time - existing_scene_time)
existing_scene_time = needed_scene_time
#So negative values may be used
if t0 < 0:
t0 = float(t0)%existing_scene_time
if t1 < 0:
t1 = float(t1)%existing_scene_time
t0, t1 = min(t0, t1), max(t0, t1)
for t in np.arange(t0, t1, self.frame_duration):
animation.update((t-t0)/(t1 - t0))
index = int(t/self.frame_duration)
self.frames[index] = disp.paint_mobject(
animation.mobject, self.frames[index]
)
animation.clean_up()
return self
def count(self, items, item_type = "mobject", *args, **kwargs):
if item_type == "mobject":
self.count_mobjects(items, *args, **kwargs)
@ -205,9 +228,7 @@ class Scene(object):
self.dither(end_dither_time)
disp.write_to_gif(self, name or str(self))
def write_to_movie(self, name = None,
end_dither_time = DEFAULT_DITHER_TIME):
self.dither(end_dither_time)
def write_to_movie(self, name = None):
disp.write_to_movie(self, name or str(self))
def show_frame(self):

View File

@ -107,9 +107,9 @@ class IntroduceGraph(GraphScene):
for pair in [(4, 5), (0, 5), (1, 5), (7, 1), (8, 3)]
]
connected, planar, graph = CompoundMobject(*text_mobject([
connected, planar, graph = text_mobject([
"Connected ", "Planar ", "Graph"
])).to_edge(UP).split()
]).to_edge(UP).split()
not_okay = text_mobject("Not Okay").highlight("red")
planar_explanation = text_mobject("""
(``Planar'' just means we can draw it without
@ -181,7 +181,7 @@ class PlanarGraphDefinition(Scene):
Not, quote, planar, end_quote = text_mobject([
"Not \\\\", "``", "Planar", "''",
# "no matter how \\\\ hard you try"
])
]).split()
shift_val = CompoundMobject(Not, planar).to_corner().get_center()
Not.highlight("red").shift(shift_val)
graphs = [
@ -1073,7 +1073,10 @@ class MortimerCannotTraverseCycle(GraphScene):
class TwoPropertiesOfSpanningTree(Scene):
def construct(self):
spanning, tree = text_mobject(["Spanning ", "Tree"], size = "\\Huge")
spanning, tree = text_mobject(
["Spanning ", "Tree"],
size = "\\Huge"
).split()
spanning_explanation = text_mobject("""
Touches every vertex
""").shift(spanning.get_center() + 2*DOWN)
@ -1158,7 +1161,7 @@ class FinalSum(Scene):
"(\\text{Number of Randolph's Edges}) + 1 &= V \\\\ \n",
"(\\text{Number of Mortimer's Edges}) + 1 &= F \\\\ \n",
" \\Downarrow \\\\", "E","+","2","&=","V","+","F",
], size = "\\large")
], size = "\\large").split()
for line in lines[:2] + [CompoundMobject(*lines[2:])]:
self.add(line)
self.dither()

View File

@ -1,44 +1,64 @@
#!/usr/bin/env python
from PIL import Image
import numpy as np
import itertools as it
from copy import deepcopy
import sys
from animation import *
from mobject import *
from constants import *
from helpers import *
from scene import *
import itertools as it
import os
from region import *
from scene import Scene
from script_wrapper import command_line_create_scene
class LogoGeneration(Scene):
LOGO_RADIUS = 1.5
INNER_RADIUS_RATIO = 0.55
CIRCLE_DENSITY = 100
CIRCLE_BLUE = "skyblue"
SPHERE_DENSITY = 50
SPHERE_BLUE = DARK_BLUE
CIRCLE_SPHERE_INTERPOLATION = 0.3
LOGO_RADIUS = 1.5
if __name__ == '__main__':
circle = Circle(density = 100, color = 'skyblue').repeat(5).scale(LOGO_RADIUS)
sphere = Sphere(density = 50, color = DARK_BLUE).scale(LOGO_RADIUS)
def construct(self):
circle = Circle(
density = self.CIRCLE_DENSITY,
color = self.CIRCLE_BLUE
).repeat(5).scale(self.LOGO_RADIUS)
sphere = Sphere(
density = self.SPHERE_DENSITY,
color = self.SPHERE_BLUE
).scale(self.LOGO_RADIUS)
sphere.rotate(-np.pi / 7, [1, 0, 0])
sphere.rotate(-np.pi / 7)
alpha = 0.3
iris = Mobject()
Mobject.interpolate(circle, sphere, iris, alpha)
Mobject.interpolate(
circle, sphere, iris,
self.CIRCLE_SPHERE_INTERPOLATION
)
for mob, color in [(iris, LIGHT_BROWN), (circle, DARK_BROWN)]:
mob.highlight(color, lambda (x, y, z) : x < 0 and y > 0)
mob.highlight("black", lambda point: np.linalg.norm(point) < 0.55*LOGO_RADIUS)
mob.highlight(
"black",
lambda point: np.linalg.norm(point) < \
self.INNER_RADIUS_RATIO*self.LOGO_RADIUS
)
name = text_mobject("3Blue1Brown").center()
name.highlight("grey")
name.shift(2*DOWN)
name = tex_mobject(r"\text{3Blue1Brown}").center()
name.highlight("gray")
name.shift((0, -2, 0))
sc = Scene()
sc.animate(Transform(
self.animate(Transform(
circle, iris,
run_time = DEFAULT_ANIMATION_RUN_TIME
))
sc.add(name)
sc.dither()
sc.frames = drag_pixels(sc.frames)
sc.write_to_movie("LogoGeneration", end_dither_time = 0)
self.add(name)
self.dither()
print "Dragging pixels..."
self.frames = drag_pixels(self.frames)
# index = int(DEFAULT_ANIMATION_RUN_TIME / DEFAULT_ANIMATION_PAUSE_TIME)
# create_eye.frames[index].save(LOGO_PATH)
if __name__ == "__main__":
command_line_create_scene()

View File

@ -149,7 +149,7 @@ class MoserPattern(CircleScene):
def __init__(self, radians, *args, **kwargs):
CircleScene.__init__(self, radians, *args, **kwargs)
self.remove(*self.dots + self.lines + [self.n_equals])
n_equals, num = tex_mobjects(["n=", "10"])
n_equals, num = tex_mobject(["n=", "10"]).split()
for mob in n_equals, num:
mob.shift((-SPACE_WIDTH + 1.5, SPACE_HEIGHT - 1.5, 0))
self.add(n_equals)
@ -183,7 +183,7 @@ class HardProblemsSimplerQuestions(Scene):
for sym in ["n", "2", "3"]
])
# not_that_hard = text_mobject("(maybe not that hard...)").scale(0.5)
fermat2, fermat2_jargon = tex_mobjects([
fermat2, fermat2_jargon = tex_mobject([
r"&x^2 + y^2 = z^2 \\",
r"""
&(3, 4, 5) \\
@ -193,14 +193,14 @@ class HardProblemsSimplerQuestions(Scene):
(m^2 - &n^2, 2mn, m^2 + n^2) \\
&\quad \vdots
"""
])
fermat3, fermat3_jargon = tex_mobjects([
]).split()
fermat3, fermat3_jargon = tex_mobject([
r"&x^3 + y^3 = z^3\\",
r"""
&y^3 = (z - x)(z - \omega x)(z - \omega^2 x) \\
&\mathds{Z}[\omega] \text{ is a UFD...}
"""
])
]).split()
for mob in [fermat2, fermat3, fermat["2"], fermat["3"],
fermat2_jargon, fermat3_jargon]:
mob.scale(scale_factor)
@ -332,10 +332,10 @@ class CountIntersectionPoints(CircleScene):
scale_factor = 0.4
text = tex_mobject(r"\text{How Many Intersection Points?}", size = size)
n = len(radians)
formula, answer = tex_mobjects([
formula, answer = tex_mobject([
r"{%d \choose 4} = \frac{%d(%d - 1)(%d - 2)(%d-3)}{1\cdot 2\cdot 3 \cdot 4}="%(n, n, n, n, n),
str(choose(n, 4))
])
]).split()
text.scale(scale_factor).shift(text_center)
self.add(text)
self.count(intersection_dots, mode="show", num_offset = ORIGIN)
@ -492,7 +492,7 @@ class IllustrateNChooseK(Scene):
Scene.__init__(self, *args, **kwargs)
nrange = range(1, n+1)
tuples = list(it.combinations(nrange, k))
nrange_mobs = tex_mobjects([str(n) + r'\;' for n in nrange])
nrange_mobs = tex_mobject([str(n) + r'\;' for n in nrange]).split()
tuple_mobs = tex_mobjects(
[
(r'\\&' if c%(20//k) == 0 else r'\;\;') + str(p)
@ -831,10 +831,10 @@ class CannotDirectlyApplyEulerToMoser(CircleScene):
def __init__(self, radians, *args, **kwargs):
CircleScene.__init__(self, radians, *args, **kwargs)
self.remove(self.n_equals)
n_equals, intersection_count = tex_mobjects([
n_equals, intersection_count = tex_mobject([
r"&n = %d\\"%len(radians),
r"&{%d \choose 4} = %d"%(len(radians), choose(len(radians), 4))
])
]).split()
shift_val = self.n_equals.get_center() - n_equals.get_center()
for mob in n_equals, intersection_count:
mob.shift(shift_val)
@ -877,10 +877,10 @@ class ShowMoserGraphLines(CircleScene):
radians = list(set(map(lambda x : x%(2*np.pi), radians)))
radians.sort()
CircleScene.__init__(self, radians, *args, **kwargs)
n, plus_n_choose_4 = tex_mobjects(["n", "+{n \\choose 4}"])
n_choose_2, plus_2_n_choose_4, plus_n = tex_mobjects([
n, plus_n_choose_4 = tex_mobject(["n", "+{n \\choose 4}"]).split()
n_choose_2, plus_2_n_choose_4, plus_n = tex_mobject([
r"{n \choose 2}",r"&+2{n \choose 4}\\",r"&+n"
])
]).split()
for mob in n, plus_n_choose_4, n_choose_2, plus_2_n_choose_4, plus_n:
mob.shift((SPACE_WIDTH - 2, SPACE_HEIGHT-1, 0))
self.chop_lines_at_intersection_points()
@ -1028,19 +1028,19 @@ class ApplyEulerToMoser(CircleScene):
equals, two, two1, n, n1, nc2, nc4, nc41]
V[1], minus[1], E[1], plus[1], F[1], equals[1], two[1] = \
tex_mobjects(["V", "-", "E", "+", "F", "=", "2"])
tex_mobject(["V", "-", "E", "+", "F", "=", "2"]).split()
F[2], equals[2], E[2], minus[2], V[2], plus[2], two[2] = \
tex_mobjects(["F", "=", "E", "-", "V", "+", "2"])
tex_mobject(["F", "=", "E", "-", "V", "+", "2"]).split()
F[3], equals[3], E[3], minus[3], n[3], minus1[3], nc4[3], plus[3], two[3] = \
tex_mobjects(["F", "=", "E", "-", "n", "-", r"{n \choose 4}", "+", "2"])
tex_mobject(["F", "=", "E", "-", "n", "-", r"{n \choose 4}", "+", "2"]).split()
F[4], equals[4], nc2[4], plus1[4], two1[4], nc41[4], plus2[4], n1[4], minus[4], n[4], minus1[4], nc4[4], plus[4], two[4] = \
tex_mobjects(["F", "=", r"{n \choose 2}", "+", "2", r"{n \choose 4}", "+", "n","-", "n", "-", r"{n \choose 4}", "+", "2"])
tex_mobject(["F", "=", r"{n \choose 2}", "+", "2", r"{n \choose 4}", "+", "n","-", "n", "-", r"{n \choose 4}", "+", "2"]).split()
F[5], equals[5], nc2[5], plus1[5], two1[5], nc41[5], minus1[5], nc4[5], plus[5], two[5] = \
tex_mobjects(["F", "=", r"{n \choose 2}", "+", "2", r"{n \choose 4}", "-", r"{n \choose 4}", "+", "2"])
tex_mobject(["F", "=", r"{n \choose 2}", "+", "2", r"{n \choose 4}", "-", r"{n \choose 4}", "+", "2"]).split()
F[6], equals[6], nc2[6], plus1[6], nc4[6], plus[6], two[6] = \
tex_mobjects(["F", "=", r"{n \choose 2}", "+", r"{n \choose 4}", "+", "2"])
tex_mobject(["F", "=", r"{n \choose 2}", "+", r"{n \choose 4}", "+", "2"]).split()
F[7], equals[7], two[7], plus[7], nc2[7], plus1[7], nc4[7] = \
tex_mobjects(["F", "=", "2", "+", r"{n \choose 2}", "+", r"{n \choose 4}"])
tex_mobject(["F", "=", "2", "+", r"{n \choose 2}", "+", r"{n \choose 4}"]).split()
shift_val = (0, 3, 0)
for d in dicts:
if not d:
@ -1423,9 +1423,9 @@ class MoserSolutionInPascal(PascalsTriangleScene):
term_color = "green"
self.generate_n_choose_k_mobs()
self.remove(*[self.coords_to_mobs[n0][k0] for n0, k0 in self.coords])
terms = one, plus0, n_choose_2, plus1, n_choose_4 = tex_mobjects([
terms = one, plus0, n_choose_2, plus1, n_choose_4 = tex_mobject([
"1", "+", r"{%d \choose 2}"%n, "+", r"{%d \choose 4}"%n
])
]).split()
target_terms = []
for k in range(len(terms)):
if k%2 == 0 and k <= n:
@ -1537,9 +1537,9 @@ class ExplainNChoose2Formula(Scene):
nums[x].shift((0, x*height, 0))
nums_compound = CompoundMobject(*nums)
nums_compound.shift(a_mob.get_center() - nums[0].get_center())
n_mob, n_minus_1, over_2 = tex_mobjects([
n_mob, n_minus_1, over_2 = tex_mobject([
str(n), "(%d-1)"%n, r"\over{2}"
])
]).split()
for part in n_mob, n_minus_1, over_2:
part.shift((SPACE_WIDTH-1.5, SPACE_HEIGHT-1, 0))
@ -1603,10 +1603,10 @@ class ExplainNChoose4Formula(Scene):
)
quad_mobs = tuple_mobs[1::2]
parens = CompoundMobject(*tuple_mobs[0::2])
form_mobs = tex_mobjects([
form_mobs = tex_mobject([
str(n), "(%d-1)"%n, "(%d-2)"%n,"(%d-3)"%n,
r"\over {4 \cdot 3 \cdot 2 \cdot 1}"
])
]).split()
form_mobs = CompoundMobject(*form_mobs).scale(0.7).shift((4, 3, 0)).split()
nums = [tex_mobject(str(k)) for k in range(1, n+1)]
height = 1.5*nums[0].get_height()

591
scripts/tau_poem.py Normal file
View File

@ -0,0 +1,591 @@
#!/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(text_mobject(text).to_edge(UP))
LogoGeneration.construct(self)
class HappyTauDayWords(Scene):
def construct(self):
words = text_mobject("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.animate(BlinkPiCreature(tau))
self.animate(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 = tex_mobject([
"".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 = text_mobject(
[line_str[:index], line_str[index:]]
).to_edge(UP).shift(BUFF*DOWN).split()
first_word.shift(0.15*RIGHT) #Stupid
number_word = text_mobject(DIGIT_TO_WORD[DIGITS[self.line_num][-1]])
number_word.shift(first_word.get_center())
number_word.shift(BUFF * UP / 2)
kwargs = {
"alpha_func" : squish_alpha_func(high_inflection_0_to_1),
}
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.animate(ApplyMethod(
self.first_digits.shift, shift_val, run_time = 2.0
))
self.dither(2)
self.animate_over_time_range(0, 2,
Transform(
deepcopy(self.first_word), self.number_word,
alpha_func = squish_alpha_func(high_inflection_0_to_1)
)
)
self.animate_over_time_range(2, 4,
Transform(
self.number_word, self.new_digit,
alpha_func = squish_alpha_func(high_inflection_0_to_1)
)
)
def line0(self):
two, pi = tex_mobject(["2", "\\pi"]).scale(2).split()
self.add(two, pi)
two_copy = deepcopy(two).rotate(np.pi/10).highlight("yellow")
self.animate(Transform(
two, two_copy,
alpha_func = squish_alpha_func(
lambda t : wiggle(t),
0.4, 0.9,
),
**self.anim_kwargs
))
def line1(self):
two_pi = tex_mobject(["2", "\\pi"]).scale(2)
tau = TauCreature()
tau.to_symbol()
sphere = Mobject()
Mobject.interpolate(
two_pi,
Sphere().highlight("yellow"),
sphere,
0.8
)
self.add(two_pi)
self.dither()
self.animate(SemiCircleTransform(
two_pi, sphere,
alpha_func = lambda t : 2*high_inflection_0_to_1(t/2)
))
self.remove(two_pi)
self.animate(SemiCircleTransform(
sphere, tau,
alpha_func = lambda t : 2*(high_inflection_0_to_1(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.animate(*[
Transform(*eyes)
for eyes in zip(blinked.eyes, tau.eyes)
])
self.animate(ShowCreation(tau.mouth))
self.dither(2)
def line3(self):
tau = TauCreature().make_sad()
pi = PiCreature()
self.add(*tau.parts)
self.dither()
self.animate(
Transform(tau.leg, pi.left_leg),
ShowCreation(pi.right_leg),
run_time = 1.0,
)
self.animate(*[
Transform(*parts)
for parts in zip(tau.white_parts, pi.white_parts)
])
self.remove(*tau.parts + pi.parts)
self.animate(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.animate(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 = tex_mobject(FORMULAE, size = "\\small")
formulae.scale(1.25)
formulae.to_corner([1, -1, 0])
self.animate(FadeIn(formulae))
def line6(self):
bubble = ThoughtBubble()
self.animate(ApplyFunction(
lambda p : 2 * p / np.linalg.norm(p),
bubble,
alpha_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.animate(SemiCircleTransform(bubble_copy, heart))
self.dither()
self.remove(bubble_copy)
self.animate(SemiCircleTransform(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.animate(
Transform(pi.left_leg, tau.leg),
Transform(
pi.right_leg,
Point(pi.right_leg.points[0,:]).highlight("black")
),
Transform(pi.mouth, tau.mouth),
SemiCircleTransform(
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.animate(Transform(point, pi))
self.remove(point)
self.add(pi)
self.animate(WaveArm(tau),Transform(pi, new_pi))
def line10(self):
formulae = tex_mobject(FORMULAE, "\\small")
formulae.scale(1.5).to_edge(DOWN)
self.add(formulae)
def line11(self):
formulae = tex_mobject(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.animate(*[
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(tex_mobject("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 = tex_mobject("\\tau")
tau.shift(tau_line.get_center() + 0.5*UP)
self.add(axes, grid)
self.animate(
TransformAnimations(
ShowCreation(sine),
ShowCreation(deepcopy(sine).shift(2*np.pi*interval_size*RIGHT)),
run_time = 2.0,
alpha_func = high_inflection_0_to_1
),
ShowCreation(circle)
)
self.animate(
SemiCircleTransform(sine_period, tau_line),
SemiCircleTransform(circle, deepcopy(tau_line)),
FadeOut(axes),
FadeOut(grid),
FadeOut(sine),
FadeIn(tau),
)
self.dither()
def line13(self):
formula = form_start, two_pi, form_end = tex_mobject([
"\\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.animate(SemiCircleTransform(two_pi, tau))
self.remove(two_pi)
self.animate(BlinkPiCreature(tau))
self.dither()
def line14(self):
pi, bubble = self.pi_speaking(
"Wait! Area \\\\ of circles"
)
self.add(pi)
self.animate(
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 = tex_mobject([
"A = ",
"\\frac{1}{2}",
"\\tau r^2"
]).split()
self.add(pi, bubble, *formula)
self.dither(2)
self.animate(ApplyMethod(half.highlight, "yellow"))
def line16(self):
self.add(tex_mobject(
"\\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 = tex_mobject("\\tau r").shift(1.3*DOWN)
r = tex_mobject("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.animate(ApplyFunction(trianglify, circle, run_time = 2.0))
self.add(tau_r, r)
self.animate(*[
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.animate(*[
Transform(*eyes, run_time = 0.2, alpha_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.animate(*[
Transform(*eyes, run_time = 0.2, alpha_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.animate(
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.animate(*[
Transform(*eyes, run_time = 0.2, alpha_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.animate(*[
Transform(*eyes, run_time = 0.2, alpha_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.animate(
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)