Moving thing files around after TOP project

This commit is contained in:
Grant Sanderson
2016-05-25 20:23:06 -07:00
parent 7905c111c3
commit 7147233e81
21 changed files with 7558 additions and 5 deletions

View File

@ -1,8 +1,8 @@
import os
import numpy as np
DEFAULT_HEIGHT = 1440
DEFAULT_WIDTH = 2560
DEFAULT_HEIGHT = 1080
DEFAULT_WIDTH = 1920
DEFAULT_FRAME_DURATION = 0.04
#There might be other configuration than pixel_shape later...
@ -15,7 +15,7 @@ MEDIUM_QUALITY_CAMERA_CONFIG = {
}
LOW_QUALITY_CAMERA_CONFIG = {
"pixel_shape" : (576, 1024),
"pixel_shape" : (480, 720),
}

View File

@ -505,7 +505,8 @@ class Mobject(object):
def repeat_submobject(self, submob):
return submob.copy()
def interpolate(self, mobject1, mobject2, alpha, path_func):
def interpolate(self, mobject1, mobject2,
alpha, path_func = straight_path):
"""
Turns target_mobject into an interpolation between mobject1
and mobject2.

View File

@ -0,0 +1,719 @@
import numpy as np
import itertools as it
from helpers import *
from mobject.tex_mobject import TexMobject, TextMobject, Brace
from mobject import Mobject
from mobject.image_mobject import \
ImageMobject, MobjectFromPixelArray
from topics.three_dimensions import Stars
from animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.playground import TurnInsideOut, Vibrate
from topics.geometry import *
from topics.characters import Randolph, Mathematician
from topics.functions import ParametricFunction, FunctionGraph
from topics.number_line import *
from mobject.region import Region, region_from_polygon_vertices
from scene import Scene
RANDY_SCALE_VAL = 0.3
class Cycloid(ParametricFunction):
CONFIG = {
"point_a" : 6*LEFT+3*UP,
"radius" : 2,
"end_theta" : 3*np.pi/2,
"density" : 5*DEFAULT_POINT_DENSITY_1D,
"color" : YELLOW
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
ParametricFunction.__init__(self, self.pos_func, **kwargs)
def pos_func(self, t):
T = t*self.end_theta
return self.point_a + self.radius * np.array([
T - np.sin(T),
np.cos(T) - 1,
0
])
class LoopTheLoop(ParametricFunction):
CONFIG = {
"color" : YELLOW_D,
"density" : 10*DEFAULT_POINT_DENSITY_1D
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
def func(t):
t = (6*np.pi/2)*(t-0.5)
return (t/2-np.sin(t))*RIGHT + \
(np.cos(t)+(t**2)/10)*UP
ParametricFunction.__init__(self, func, **kwargs)
class SlideWordDownCycloid(Animation):
CONFIG = {
"rate_func" : None,
"run_time" : 8
}
def __init__(self, word, **kwargs):
self.path = Cycloid(end_theta = np.pi)
word_mob = TextMobject(list(word))
end_word = word_mob.copy()
end_word.shift(-end_word.get_bottom())
end_word.shift(self.path.get_corner(DOWN+RIGHT))
end_word.shift(3*RIGHT)
self.end_letters = end_word.split()
for letter in word_mob.split():
letter.center()
letter.angle = 0
unit_interval = np.arange(0, 1, 1./len(word))
self.start_times = 0.5*(1-(unit_interval))
Animation.__init__(self, word_mob, **kwargs)
def update_mobject(self, alpha):
virtual_times = 2*(alpha - self.start_times)
cut_offs = [
0.1,
0.3,
0.7,
]
for letter, time, end_letter in zip(
self.mobject.split(), virtual_times, self.end_letters
):
time = max(time, 0)
time = min(time, 1)
if time < cut_offs[0]:
brightness = time/cut_offs[0]
letter.rgbs = brightness*np.ones(letter.rgbs.shape)
position = self.path.points[0]
angle = 0
elif time < cut_offs[1]:
alpha = (time-cut_offs[0])/(cut_offs[1]-cut_offs[0])
angle = -rush_into(alpha)*np.pi/2
position = self.path.points[0]
elif time < cut_offs[2]:
alpha = (time-cut_offs[1])/(cut_offs[2]-cut_offs[1])
index = int(alpha*self.path.get_num_points())
position = self.path.points[index]
try:
angle = angle_of_vector(
self.path.points[index+1] - \
self.path.points[index]
)
except:
angle = letter.angle
else:
alpha = (time-cut_offs[2])/(1-cut_offs[2])
start = self.path.points[-1]
end = end_letter.get_bottom()
position = interpolate(start, end, rush_from(alpha))
angle = 0
letter.shift(position-letter.get_bottom())
letter.rotate_in_place(angle-letter.angle)
letter.angle = angle
class BrachistochroneWordSliding(Scene):
def construct(self):
anim = SlideWordDownCycloid("Brachistochrone")
anim.path.gradient_highlight(WHITE, BLUE_E)
self.play(ShowCreation(anim.path))
self.play(anim)
self.dither()
self.play(
FadeOut(anim.path),
ApplyMethod(anim.mobject.center)
)
class PathSlidingScene(Scene):
CONFIG = {
"gravity" : 3,
"delta_t" : 0.05,
"dither_and_add" : True,
"show_time" : True,
}
def slide(self, mobject, path, roll = False, ceiling = None):
points = path.points
time_slices = self.get_time_slices(points, ceiling = ceiling)
curr_t = 0
last_index = 0
curr_index = 1
if self.show_time:
self.t_equals = TexMobject("t = ")
self.t_equals.shift(3.5*UP+4*RIGHT)
self.add(self.t_equals)
while curr_index < len(points):
self.slider = mobject.copy()
self.adjust_mobject_to_index(
self.slider, curr_index, points
)
if roll:
distance = np.linalg.norm(
points[curr_index] - points[last_index]
)
self.roll(mobject, distance)
self.add(self.slider)
if self.show_time:
self.write_time(curr_t)
self.dither(self.frame_duration)
self.remove(self.slider)
curr_t += self.delta_t
last_index = curr_index
while time_slices[curr_index] < curr_t:
curr_index += 1
if curr_index == len(points):
break
if self.dither_and_add:
self.add(self.slider)
self.dither()
else:
return self.slider
def get_time_slices(self, points, ceiling = None):
dt_list = np.zeros(len(points))
ds_list = np.apply_along_axis(
np.linalg.norm,
1,
points[1:]-points[:-1]
)
if ceiling is None:
ceiling = points[0, 1]
delta_y_list = np.abs(ceiling - points[1:,1])
delta_y_list += 0.001*(delta_y_list == 0)
v_list = self.gravity*np.sqrt(delta_y_list)
dt_list[1:] = ds_list / v_list
return np.cumsum(dt_list)
def adjust_mobject_to_index(self, mobject, index, points):
point_a, point_b = points[index-1], points[index]
while np.all(point_a == point_b):
index += 1
point_b = points[index]
theta = angle_of_vector(point_b - point_a)
mobject.rotate(theta)
mobject.shift(points[index])
self.midslide_action(point_a, theta)
return mobject
def midslide_action(self, point, angle):
pass
def write_time(self, time):
if hasattr(self, "time_mob"):
self.remove(self.time_mob)
digits = map(TexMobject, "%.2f"%time)
digits[0].next_to(self.t_equals, buff = 0.1)
for left, right in zip(digits, digits[1:]):
right.next_to(left, buff = 0.1, aligned_edge = DOWN)
self.time_mob = Mobject(*digits)
self.add(self.time_mob)
def roll(self, mobject, arc_length):
radius = mobject.get_width()/2
theta = arc_length / radius
mobject.rotate_in_place(-theta)
def add_cycloid_end_points(self):
cycloid = Cycloid()
point_a = Dot(cycloid.points[0])
point_b = Dot(cycloid.points[-1])
A = TexMobject("A").next_to(point_a, LEFT)
B = TexMobject("B").next_to(point_b, RIGHT)
self.add(point_a, point_b, A, B)
digest_locals(self)
class TryManyPaths(PathSlidingScene):
def construct(self):
randy = Randolph()
randy.shift(-randy.get_bottom())
self.slider = randy.copy()
randy.scale(RANDY_SCALE_VAL)
paths = self.get_paths()
point_a = Dot(paths[0].points[0])
point_b = Dot(paths[0].points[-1])
A = TexMobject("A").next_to(point_a, LEFT)
B = TexMobject("B").next_to(point_b, RIGHT)
for point, tex in [(point_a, A), (point_b, B)]:
self.play(ShowCreation(point))
self.play(ShimmerIn(tex))
self.dither()
curr_path = None
for path in paths:
new_slider = self.adjust_mobject_to_index(
randy.copy(), 1, path.points
)
if curr_path is None:
curr_path = path
self.play(ShowCreation(curr_path))
else:
self.play(Transform(curr_path, path))
self.play(Transform(self.slider, new_slider))
self.dither()
self.remove(self.slider)
self.slide(randy, curr_path)
self.clear()
self.add(point_a, point_b, A, B, curr_path)
text = self.get_text()
text.to_edge(UP)
self.play(ShimmerIn(text))
for path in paths:
self.play(Transform(
curr_path, path,
path_func = path_along_arc(np.pi/2),
run_time = 3
))
def get_text(self):
return TextMobject("Which path is fastest?")
def get_paths(self):
sharp_corner = Mobject(
Line(3*UP+LEFT, LEFT),
Arc(angle = np.pi/2, start_angle = np.pi),
Line(DOWN, DOWN+3*RIGHT)
).ingest_submobjects().highlight(GREEN)
paths = [
Arc(
angle = np.pi/2,
radius = 3,
start_angle = 4
),
LoopTheLoop(),
Line(7*LEFT, 7*RIGHT, color = RED_D),
sharp_corner,
FunctionGraph(
lambda x : 0.05*(x**2)+0.1*np.sin(2*x)
),
FunctionGraph(
lambda x : x**2,
x_min = -3,
x_max = 2,
density = 3*DEFAULT_POINT_DENSITY_1D
)
]
cycloid = Cycloid()
self.align_paths(paths, cycloid)
return paths + [cycloid]
def align_paths(self, paths, target_path):
start = target_path.points[0]
end = target_path.points[-1]
for path in paths:
path.position_endpoints_on(start, end)
class RollingRandolph(PathSlidingScene):
def construct(self):
randy = Randolph()
randy.scale(RANDY_SCALE_VAL)
randy.shift(-randy.get_bottom())
self.add_cycloid_end_points()
self.slide(randy, self.cycloid, roll = True)
class NotTheCircle(PathSlidingScene):
def construct(self):
self.add_cycloid_end_points()
start = self.point_a.get_center()
end = self.point_b.get_center()
angle = 2*np.pi/3
path = Arc(angle, radius = 3)
path.gradient_highlight(RED_D, WHITE)
radius = Line(ORIGIN, path.points[0])
randy = Randolph()
randy.scale(RANDY_SCALE_VAL)
randy.shift(-randy.get_bottom())
randy_copy = randy.copy()
words = TextMobject("Circular paths are good, \\\\ but still not the best")
words.shift(UP)
self.play(
ShowCreation(path),
ApplyMethod(
radius.rotate,
angle,
path_func = path_along_arc(angle)
)
)
self.play(FadeOut(radius))
self.play(
ApplyMethod(
path.position_endpoints_on, start, end,
path_func = path_along_arc(-angle)
),
run_time = 3
)
self.adjust_mobject_to_index(randy_copy, 1, path.points)
self.play(FadeIn(randy_copy))
self.remove(randy_copy)
self.slide(randy, path)
self.play(ShimmerIn(words))
self.dither()
class TransitionAwayFromSlide(PathSlidingScene):
def construct(self):
randy = Randolph()
randy.scale(RANDY_SCALE_VAL)
randy.shift(-randy.get_bottom())
self.add_cycloid_end_points()
arrow = Arrow(ORIGIN, 2*RIGHT)
arrows = Mobject(*[
arrow.copy().shift(vect)
for vect in 3*LEFT, ORIGIN, 3*RIGHT
])
arrows.shift(2*SPACE_WIDTH*RIGHT)
self.add(arrows)
self.add(self.cycloid)
self.slide(randy, self.cycloid)
everything = Mobject(*self.mobjects)
self.play(ApplyMethod(
everything.shift, 4*SPACE_WIDTH*LEFT,
run_time = 2,
rate_func = rush_into
))
class MinimalPotentialEnergy(Scene):
def construct(self):
horiz_radius = 5
vert_radius = 3
vert_axis = NumberLine(numerical_radius = vert_radius)
vert_axis.rotate(np.pi/2)
vert_axis.shift(horiz_radius*LEFT)
horiz_axis = NumberLine(
numerical_radius = 5,
numbers_with_elongated_ticks = []
)
axes = Mobject(horiz_axis, vert_axis)
graph = FunctionGraph(
lambda x : 0.4*(x-2)*(x+2)+3,
x_min = -2,
x_max = 2,
density = 3*DEFAULT_POINT_DENSITY_1D
)
graph.stretch_to_fit_width(2*horiz_radius)
graph.highlight(YELLOW)
min_point = Dot(graph.get_bottom())
nature_finds = TextMobject("Nature finds this point")
nature_finds.scale(0.5)
nature_finds.highlight(GREEN)
nature_finds.shift(2*RIGHT+3*UP)
arrow = Arrow(
nature_finds.get_bottom(), min_point,
color = GREEN
)
side_words_start = TextMobject("Parameter describing")
top_words, last_side_words = [
map(TextMobject, pair)
for pair in [
("Light's travel time", "Potential energy"),
("path", "mechanical state")
]
]
for word in top_words + last_side_words + [side_words_start]:
word.scale(0.7)
side_words_start.next_to(horiz_axis, DOWN)
side_words_start.to_edge(RIGHT)
for words in top_words:
words.next_to(vert_axis, UP)
words.to_edge(LEFT)
for words in last_side_words:
words.next_to(side_words_start, DOWN)
for words in top_words[1], last_side_words[1]:
words.highlight(RED)
self.add(
axes, top_words[0], side_words_start,
last_side_words[0]
)
self.play(ShowCreation(graph))
self.play(
ShimmerIn(nature_finds),
ShowCreation(arrow),
ShowCreation(min_point)
)
self.dither()
self.play(
FadeOut(top_words[0]),
FadeOut(last_side_words[0]),
GrowFromCenter(top_words[1]),
GrowFromCenter(last_side_words[1])
)
self.dither(3)
class WhatGovernsSpeed(PathSlidingScene):
CONFIG = {
"num_pieces" : 6,
"dither_and_add" : False,
"show_time" : False,
}
def construct(self):
randy = Randolph()
randy.scale(RANDY_SCALE_VAL)
randy.shift(-randy.get_bottom())
self.add_cycloid_end_points()
points = self.cycloid.points
ceiling = points[0, 1]
n = len(points)
broken_points = [
points[k*n/self.num_pieces:(k+1)*n/self.num_pieces]
for k in range(self.num_pieces)
]
words = TextMobject("""
What determines the speed\\\\
at each point?
""")
words.to_edge(UP)
self.add(self.cycloid)
sliders, vectors = [], []
for points in broken_points:
path = Mobject().add_points(points)
vect = points[-1] - points[-2]
magnitude = np.sqrt(ceiling - points[-1, 1])
vect = magnitude*vect/np.linalg.norm(vect)
slider = self.slide(randy, path, ceiling = ceiling)
vector = Vector(slider.get_center(), vect)
self.add(slider, vector)
sliders.append(slider)
vectors.append(vector)
self.dither()
self.play(ShimmerIn(words))
self.dither(3)
slider = sliders.pop(1)
vector = vectors.pop(1)
faders = sliders+vectors+[words]
self.play(*map(FadeOut, faders))
self.remove(*faders)
self.show_geometry(slider, vector)
def show_geometry(self, slider, vector):
point_a = self.point_a.get_center()
horiz_line = Line(point_a, point_a + 6*RIGHT)
ceil_point = point_a
ceil_point[0] = slider.get_center()[0]
vert_brace = Brace(
Mobject(Point(ceil_point), Point(slider.get_center())),
RIGHT,
buff = 0.5
)
vect_brace = Brace(slider)
vect_brace.stretch_to_fit_width(vector.get_length())
vect_brace.rotate(np.arctan(vector.get_slope()))
vect_brace.center().shift(vector.get_center())
nudge = 0.2*(DOWN+LEFT)
vect_brace.shift(nudge)
y_mob = TexMobject("y")
y_mob.next_to(vert_brace)
sqrt_y = TexMobject("k\\sqrt{y}")
sqrt_y.scale(0.5)
sqrt_y.shift(vect_brace.get_center())
sqrt_y.shift(3*nudge)
self.play(ShowCreation(horiz_line))
self.play(
GrowFromCenter(vert_brace),
ShimmerIn(y_mob)
)
self.play(
GrowFromCenter(vect_brace),
ShimmerIn(sqrt_y)
)
self.dither(3)
self.solve_energy()
def solve_energy(self):
loss_in_potential = TextMobject("Loss in potential: ")
loss_in_potential.shift(2*UP)
potential = TexMobject("m g y".split())
potential.next_to(loss_in_potential)
kinetic = TexMobject([
"\\dfrac{1}{2}","m","v","^2","="
])
kinetic.next_to(potential, LEFT)
nudge = 0.1*UP
kinetic.shift(nudge)
loss_in_potential.shift(nudge)
ms = Mobject(kinetic.split()[1], potential.split()[0])
two = TexMobject("2")
two.shift(ms.split()[1].get_center())
half = kinetic.split()[0]
sqrt = TexMobject("\\sqrt{\\phantom{2mg}}")
sqrt.shift(potential.get_center())
nudge = 0.2*LEFT
sqrt.shift(nudge)
squared = kinetic.split()[3]
equals = kinetic.split()[-1]
new_eq = equals.copy().next_to(kinetic.split()[2])
self.play(
Transform(
Point(loss_in_potential.get_left()),
loss_in_potential
),
*map(GrowFromCenter, potential.split())
)
self.dither(2)
self.play(
FadeOut(loss_in_potential),
GrowFromCenter(kinetic)
)
self.dither(2)
self.play(ApplyMethod(ms.shift, 5*UP))
self.dither()
self.play(Transform(
half, two,
path_func = counterclockwise_path()
))
self.dither()
self.play(
Transform(
squared, sqrt,
path_func = clockwise_path()
),
Transform(equals, new_eq)
)
self.dither(2)
class ThetaTInsteadOfXY(Scene):
def construct(self):
cycloid = Cycloid()
index = cycloid.get_num_points()/3
point = cycloid.points[index]
vect = cycloid.points[index+1]-point
vect /= np.linalg.norm(vect)
vect *= 3
vect_mob = Vector(point, vect)
dot = Dot(point)
xy = TexMobject("\\big( x(t), y(t) \\big)")
xy.next_to(dot, UP+RIGHT, buff = 0.1)
vert_line = Line(2*DOWN, 2*UP)
vert_line.shift(point)
angle = vect_mob.get_angle() + np.pi/2
arc = Arc(angle, radius = 1, start_angle = -np.pi/2)
arc.shift(point)
theta = TexMobject("\\theta(t)")
theta.next_to(arc, DOWN, buff = 0.1, aligned_edge = LEFT)
theta.shift(0.2*RIGHT)
self.play(ShowCreation(cycloid))
self.play(ShowCreation(dot))
self.play(ShimmerIn(xy))
self.dither()
self.play(
FadeOut(xy),
ShowCreation(vect_mob)
)
self.play(
ShowCreation(arc),
ShowCreation(vert_line),
ShimmerIn(theta)
)
self.dither()
class DefineCurveWithKnob(PathSlidingScene):
def construct(self):
self.knob = Circle(color = BLUE_D)
self.knob.add_line(UP, DOWN)
self.knob.to_corner(UP+RIGHT)
self.knob.shift(0.5*DOWN)
self.last_angle = np.pi/2
arrow = Vector(ORIGIN, RIGHT)
arrow.next_to(self.knob, LEFT)
words = TextMobject("Turn this knob over time to define the curve")
words.next_to(arrow, LEFT)
self.path = self.get_path()
self.path.shift(1.5*DOWN)
self.path.show()
self.path.highlight(BLACK)
randy = Randolph()
randy.scale(RANDY_SCALE_VAL)
randy.shift(-randy.get_bottom())
self.play(ShimmerIn(words))
self.play(ShowCreation(arrow))
self.play(ShowCreation(self.knob))
self.dither()
self.add(self.path)
self.slide(randy, self.path)
self.dither()
def get_path(self):
return Cycloid(end_theta = 2*np.pi)
def midslide_action(self, point, angle):
d_angle = angle-self.last_angle
self.knob.rotate_in_place(d_angle)
self.last_angle = angle
self.path.highlight(BLUE_D, lambda p : p[0] < point[0])
class WonkyDefineCurveWithKnob(DefineCurveWithKnob):
def get_path(self):
return ParametricFunction(
lambda t : t*RIGHT + (-0.2*t-np.sin(2*np.pi*t/6))*UP,
start = -7,
end = 10
)
class SlowDefineCurveWithKnob(DefineCurveWithKnob):
def get_path(self):
return ParametricFunction(
lambda t : t*RIGHT + (np.exp(-(t+2)**2)-0.2*np.exp(t-2)),
start = -4,
end = 4
)
class BumpyDefineCurveWithKnob(DefineCurveWithKnob):
def get_path(self):
result = FunctionGraph(
lambda x : 0.05*(x**2)+0.1*np.sin(2*x)
)
result.rotate(-np.pi/20)
result.scale(0.7)
result.shift(DOWN)
return result

View File

@ -0,0 +1,611 @@
import numpy as np
import itertools as it
from helpers import *
from mobject.tex_mobject import TexMobject, TextMobject, Brace
from mobject import Mobject, Mobject1D
from mobject.image_mobject import \
ImageMobject, MobjectFromPixelArray
from topics.three_dimensions import Stars
from animation import Animation
from animation.transform import *
from animation.simple_animations import *
from topics.geometry import *
from topics.characters import Randolph
from topics.functions import *
from mobject.region import Region
from scene import Scene
from scene.zoomed_scene import ZoomedScene
from camera import Camera
from brachistochrone.curves import *
class RollAlongVector(Animation):
CONFIG = {
"rotation_vector" : OUT,
}
def __init__(self, mobject, vector, **kwargs):
radius = mobject.get_width()/2
radians = np.linalg.norm(vector)/radius
last_alpha = 0
digest_config(self, kwargs, locals())
Animation.__init__(self, mobject, **kwargs)
def update_mobject(self, alpha):
d_alpha = alpha - self.last_alpha
self.last_alpha = alpha
self.mobject.rotate_in_place(
d_alpha*self.radians,
self.rotation_vector
)
self.mobject.shift(d_alpha*self.vector)
class CycloidScene(Scene):
CONFIG = {
"point_a" : 6*LEFT+3*UP,
"radius" : 2,
"end_theta" : 2*np.pi
}
def construct(self):
self.generate_cycloid()
self.generate_circle()
self.generate_ceiling()
def grow_parts(self):
self.play(*[
ShowCreation(mob)
for mob in self.circle, self.ceiling
])
def generate_cycloid(self):
self.cycloid = Cycloid(
point_a = self.point_a,
radius = self.radius,
end_theta = self.end_theta
)
def generate_circle(self, **kwargs):
self.circle = Circle(radius = self.radius, **kwargs)
self.circle.shift(self.point_a - self.circle.get_top())
radial_line = Line(
self.circle.get_center(), self.point_a
)
self.circle.add(radial_line)
def generate_ceiling(self):
self.ceiling = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT)
self.ceiling.shift(self.cycloid.get_top()[1]*UP)
def draw_cycloid(self, run_time = 3, *anims, **kwargs):
kwargs["run_time"] = run_time
self.play(
RollAlongVector(
self.circle,
self.cycloid.points[-1]-self.cycloid.points[0],
**kwargs
),
ShowCreation(self.cycloid, **kwargs),
*anims
)
def roll_back(self, run_time = 3, *anims, **kwargs):
kwargs["run_time"] = run_time
self.play(
RollAlongVector(
self.circle,
self.cycloid.points[0]-self.cycloid.points[- 1],
rotation_vector = IN,
**kwargs
),
ShowCreation(
self.cycloid,
rate_func = lambda t : smooth(1-t),
**kwargs
),
*anims
)
self.generate_cycloid()
class IntroduceCycloid(CycloidScene):
def construct(self):
CycloidScene.construct(self)
equation = TexMobject([
"\\dfrac{\\sin(\\theta)}{\\sqrt{y}}",
"= \\text{constant}"
])
sin_sqrt, const = equation.split()
new_eq = equation.copy()
new_eq.to_edge(UP, buff = 1.3)
cycloid_word = TextMobject("Cycloid")
arrow = Arrow(2*UP, cycloid_word)
arrow.reverse_points()
q_mark = TextMobject("?")
self.play(*map(ShimmerIn, equation.split()))
self.dither()
self.play(
ApplyMethod(equation.shift, 2.2*UP),
ShowCreation(arrow)
)
q_mark.next_to(sin_sqrt)
self.play(ShimmerIn(cycloid_word))
self.dither()
self.grow_parts()
self.draw_cycloid()
self.dither()
extra_terms = [const, arrow, cycloid_word]
self.play(*[
Transform(mob, q_mark)
for mob in extra_terms
])
self.remove(*extra_terms)
self.roll_back()
q_marks, arrows = self.get_q_marks_and_arrows(sin_sqrt)
self.draw_cycloid(3,
ShowCreation(q_marks),
ShowCreation(arrows)
)
self.dither()
def get_q_marks_and_arrows(self, mob, n_marks = 10):
circle = Circle().replace(mob)
q_marks, arrows = result = [Mobject(), Mobject()]
for x in range(n_marks):
index = (x+0.5)*self.cycloid.get_num_points()/n_marks
q_point = self.cycloid.points[index]
vect = q_point-mob.get_center()
start_point = circle.get_boundary_point(vect)
arrow = Arrow(
start_point, q_point,
color = BLUE_E
)
q_marks.add(TextMobject("?").shift(q_point))
arrows.add(arrow)
for mob in result:
mob.ingest_submobjects()
return result
class LeviSolution(CycloidScene):
CONFIG = {
"cycloid_fraction" : 0.25,
}
def construct(self):
CycloidScene.construct(self)
self.add(self.ceiling)
self.generate_points()
methods = [
self.draw_cycloid,
self.roll_into_position,
self.draw_p_and_c,
self.show_pendulum,
self.show_diameter,
self.show_theta,
self.show_similar_triangles,
self.show_sin_thetas,
self.show_y,
self.rearrange,
]
for method in methods:
method()
self.dither()
def generate_points(self):
index = int(self.cycloid_fraction*self.cycloid.get_num_points())
p_point = self.cycloid.points[index]
p_dot = Dot(p_point)
p_label = TexMobject("P")
p_label.next_to(p_dot, DOWN+LEFT)
c_point = self.point_a + self.cycloid_fraction*self.radius*2*np.pi*RIGHT
c_dot = Dot(c_point)
c_label = TexMobject("C")
c_label.next_to(c_dot, UP)
digest_locals(self)
def roll_into_position(self):
self.play(RollAlongVector(
self.circle,
(1-self.cycloid_fraction)*self.radius*2*np.pi*LEFT,
rotation_vector = IN,
run_time = 2
))
def draw_p_and_c(self):
radial_line = self.circle.submobjects[0] ##Hacky
self.play(Transform(radial_line, self.p_dot))
self.remove(radial_line)
self.add(self.p_dot)
self.play(ShimmerIn(self.p_label))
self.dither()
self.play(Transform(self.ceiling.copy(), self.c_dot))
self.play(ShimmerIn(self.c_label))
def show_pendulum(self, arc_angle = np.pi, arc_color = GREEN):
words = TextMobject(": Instantaneous center of rotation")
words.next_to(self.c_label)
line = Line(self.p_point, self.c_point)
line_angle = line.get_angle()+np.pi
line_length = line.get_length()
line.add(self.p_dot.copy())
line.get_center = lambda : self.c_point
tangent_line = Line(3*LEFT, 3*RIGHT)
tangent_line.rotate(line_angle-np.pi/2)
tangent_line.shift(self.p_point)
tangent_line.highlight(arc_color)
right_angle_symbol = Mobject(
Line(UP, UP+RIGHT),
Line(UP+RIGHT, RIGHT)
)
right_angle_symbol.scale(0.3)
right_angle_symbol.rotate(tangent_line.get_angle()+np.pi)
right_angle_symbol.shift(self.p_point)
self.play(ShowCreation(line))
self.play(ShimmerIn(words))
self.dither()
pairs = [
(line_angle, arc_angle/2),
(line_angle+arc_angle/2, -arc_angle),
(line_angle-arc_angle/2, arc_angle/2),
]
arcs = []
for start, angle in pairs:
arc = Arc(
angle = angle,
radius = line_length,
start_angle = start,
color = GREEN
)
arc.shift(self.c_point)
self.play(
ShowCreation(arc),
ApplyMethod(
line.rotate_in_place,
angle,
path_func = path_along_arc(angle)
),
run_time = 2
)
arcs.append(arc)
self.dither()
self.play(Transform(arcs[1], tangent_line))
self.add(tangent_line)
self.play(ShowCreation(right_angle_symbol))
self.dither()
self.tangent_line = tangent_line
self.right_angle_symbol = right_angle_symbol
self.pc_line = line
self.remove(words, *arcs)
def show_diameter(self):
exceptions = [
self.circle,
self.tangent_line,
self.pc_line,
self.right_angle_symbol
]
everything = set(self.mobjects).difference(exceptions)
everything_copy = Mobject(*everything).copy()
light_everything = everything_copy.copy()
dark_everything = everything_copy.copy()
dark_everything.fade(0.8)
bottom_point = np.array(self.c_point)
bottom_point += 2*self.radius*DOWN
diameter = Line(bottom_point, self.c_point)
brace = Brace(diameter, RIGHT)
diameter_word = TextMobject("Diameter")
d_mob = TexMobject("D")
diameter_word.next_to(brace)
d_mob.next_to(diameter)
self.remove(*everything)
self.play(Transform(everything_copy, dark_everything))
self.dither()
self.play(ShowCreation(diameter))
self.play(GrowFromCenter(brace))
self.play(ShimmerIn(diameter_word))
self.dither()
self.play(*[
Transform(mob, d_mob)
for mob in brace, diameter_word
])
self.remove(brace, diameter_word)
self.add(d_mob)
self.play(Transform(everything_copy, light_everything))
self.remove(everything_copy)
self.add(*everything)
self.d_mob = d_mob
self.bottom_point = bottom_point
def show_theta(self, radius = 1):
arc = Arc(
angle = self.tangent_line.get_angle()-np.pi/2,
radius = radius,
start_angle = np.pi/2
)
theta = TexMobject("\\theta")
theta.shift(1.5*arc.get_center())
Mobject(arc, theta).shift(self.bottom_point)
self.play(
ShowCreation(arc),
ShimmerIn(theta)
)
self.arc = arc
self.theta = theta
def show_similar_triangles(self):
y_point = np.array(self.p_point)
y_point[1] = self.point_a[1]
new_arc = Arc(
angle = self.tangent_line.get_angle()-np.pi/2,
radius = 0.5,
start_angle = np.pi
)
new_arc.shift(self.c_point)
new_theta = self.theta.copy()
new_theta.next_to(new_arc, LEFT)
new_theta.shift(0.1*DOWN)
kwargs = {
"stroke_width" : 2*DEFAULT_POINT_THICKNESS,
}
triangle1 = Polygon(
self.p_point, self.c_point, self.bottom_point,
color = MAROON,
**kwargs
)
triangle2 = Polygon(
y_point, self.p_point, self.c_point,
color = WHITE,
**kwargs
)
y_line = Line(self.p_point, y_point)
self.play(
Transform(self.arc.copy(), new_arc),
Transform(self.theta.copy(), new_theta),
run_time = 3
)
self.dither()
self.play(FadeIn(triangle1))
self.dither()
self.play(Transform(triangle1, triangle2))
self.play(ApplyMethod(triangle1.highlight, MAROON))
self.dither()
self.remove(triangle1)
self.add(y_line)
self.y_line = y_line
def show_sin_thetas(self):
pc = Line(self.p_point, self.c_point)
mob = Mobject(self.theta, self.d_mob).copy()
mob.ingest_submobjects()
triplets = [
(pc, "D\\sin(\\theta)", 0.5),
(self.y_line, "D\\sin^2(\\theta)", 0.7),
]
for line, tex, scale in triplets:
trig_mob = TexMobject(tex)
trig_mob.scale_to_fit_width(
scale*line.get_length()
)
trig_mob.shift(-1.2*trig_mob.get_top())
trig_mob.rotate(line.get_angle())
trig_mob.shift(line.get_center())
if line is self.y_line:
trig_mob.shift(0.1*UP)
self.play(Transform(mob, trig_mob))
self.add(trig_mob)
self.dither()
self.remove(mob)
self.d_sin_squared_theta = trig_mob
def show_y(self):
y_equals = TexMobject(["y", "="])
y_equals.shift(2*UP)
y_expression = TexMobject([
"D ", "\\sin", "^2", "(\\theta)"
])
y_expression.next_to(y_equals)
y_expression.shift(0.05*UP+0.1*RIGHT)
temp_expr = self.d_sin_squared_theta.copy()
temp_expr.rotate(-np.pi/2)
temp_expr.replace(y_expression)
y_mob = TexMobject("y")
y_mob.next_to(self.y_line, RIGHT)
y_mob.shift(0.2*UP)
self.play(
Transform(self.d_sin_squared_theta, temp_expr),
ShimmerIn(y_mob),
ShowCreation(y_equals)
)
self.remove(self.d_sin_squared_theta)
self.add(y_expression)
self.y_equals = y_equals
self.y_expression = y_expression
def rearrange(self):
sqrt_nudge = 0.2*LEFT
y, equals = self.y_equals.split()
d, sin, squared, theta = self.y_expression.split()
y_sqrt = TexMobject("\\sqrt{\\phantom{y}}")
d_sqrt = y_sqrt.copy()
y_sqrt.shift(y.get_center()+sqrt_nudge)
d_sqrt.shift(d.get_center()+sqrt_nudge)
self.play(
ShimmerIn(y_sqrt),
ShimmerIn(d_sqrt),
ApplyMethod(squared.shift, 4*UP),
ApplyMethod(theta.shift, 1.5* squared.get_width()*LEFT)
)
self.dither()
y_sqrt.add(y)
d_sqrt.add(d)
sin.add(theta)
sin_over = TexMobject("\\dfrac{\\phantom{\\sin(\\theta)}}{\\quad}")
sin_over.next_to(sin, DOWN, 0.15)
new_eq = equals.copy()
new_eq.next_to(sin_over, LEFT)
one_over = TexMobject("\\dfrac{1}{\\quad}")
one_over.next_to(new_eq, LEFT)
one_over.shift(
(sin_over.get_bottom()[1]-one_over.get_bottom()[1])*UP
)
self.play(
Transform(equals, new_eq),
ShimmerIn(sin_over),
ShimmerIn(one_over),
ApplyMethod(
d_sqrt.next_to, one_over, DOWN,
path_func = path_along_arc(-np.pi)
),
ApplyMethod(
y_sqrt.next_to, sin_over, DOWN,
path_func = path_along_arc(-np.pi)
),
run_time = 2
)
self.dither()
brace = Brace(d_sqrt, DOWN)
constant = TextMobject("Constant")
constant.next_to(brace, DOWN)
self.play(
GrowFromCenter(brace),
ShimmerIn(constant)
)
class EquationsForCycloid(CycloidScene):
def construct(self):
CycloidScene.construct(self)
equations = TexMobject([
"x(t) = Rt - R\\sin(t)",
"y(t) = -R + R\\cos(t)"
])
top, bottom = equations.split()
bottom.next_to(top, DOWN)
equations.center()
equations.to_edge(UP, buff = 1.3)
self.play(ShimmerIn(equations))
self.grow_parts()
self.draw_cycloid(rate_func = None, run_time = 5)
self.dither()
class SlidingObject(CycloidScene, PathSlidingScene):
CONFIG = {
"show_time" : False,
"dither_and_add" : False
}
args_list = [(True,), (False,)]
@staticmethod
def args_to_string(with_words):
return "WithWords" if with_words else "WithoutWords"
@staticmethod
def string_to_args(string):
return string == "WithWords"
def construct(self, with_words):
CycloidScene.construct(self)
randy = Randolph()
randy.scale(RANDY_SCALE_VAL)
randy.shift(-randy.get_bottom())
central_randy = randy.copy()
start_randy = self.adjust_mobject_to_index(
randy.copy(), 1, self.cycloid.points
)
if with_words:
words1 = TextMobject("Trajectory due to gravity")
arrow = TexMobject("\\leftrightarrow")
words2 = TextMobject("Trajectory due \\emph{constantly} rotating wheel")
words1.next_to(arrow, LEFT)
words2.next_to(arrow, RIGHT)
words = Mobject(words1, arrow, words2)
words.scale_to_fit_width(2*SPACE_WIDTH-1)
words.to_edge(UP, buff = 0.2)
words.to_edge(LEFT)
self.play(ShowCreation(self.cycloid.copy()))
self.slide(randy, self.cycloid)
self.add(self.slider)
self.dither()
self.grow_parts()
self.draw_cycloid()
self.dither()
self.play(Transform(self.slider, start_randy))
self.dither()
self.roll_back()
self.dither()
if with_words:
self.play(*map(ShimmerIn, [words1, arrow, words2]))
self.dither()
self.remove(self.circle)
start_time = len(self.frames)*self.frame_duration
self.remove(self.slider)
self.slide(central_randy, self.cycloid)
end_time = len(self.frames)*self.frame_duration
self.play_over_time_range(
start_time,
end_time,
RollAlongVector(
self.circle,
self.cycloid.points[-1]-self.cycloid.points[0],
run_time = end_time-start_time,
rate_func = None
)
)
self.add(self.circle, self.slider)
self.dither()
class RotateWheel(CycloidScene):
def construct(self):
CycloidScene.construct(self)
self.circle.center()
self.play(Rotating(
self.circle,
axis = OUT,
run_time = 5,
rate_func = smooth
))

View File

@ -0,0 +1,429 @@
import numpy as np
import itertools as it
import operator as op
import sys
import inspect
from PIL import Image
import cv2
import random
from scipy.spatial.distance import cdist
from scipy import ndimage
from helpers import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.image_mobject import \
ImageMobject, MobjectFromPixelArray
from mobject.tex_mobject import TextMobject, TexMobject
from animation.transform import \
Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\
FadeIn, FadeOut, GrowFromCenter, ShimmerIn, ApplyMethod
from animation.simple_animations import \
ShowCreation, Homotopy, PhaseFlow, ApplyToCenters, DelayByOrder
from animation.playground import TurnInsideOut, Vibrate
from topics.geometry import \
Line, Circle, Square, Grid, Rectangle, Arrow, Dot, Point
from topics.characters import Randolph, Mathematician, ThoughtBubble
from topics.functions import ParametricFunction
from topics.number_line import NumberPlane
from mobject.region import Region, region_from_polygon_vertices
from scene import Scene
DEFAULT_GAUSS_BLUR_CONFIG = {
"ksize" : (5, 5),
"sigmaX" : 6,
"sigmaY" : 6,
}
DEFAULT_CANNY_CONFIG = {
"threshold1" : 50,
"threshold2" : 100,
}
DEFAULT_BLUR_RADIUS = 0.5
DEFAULT_CONNECTED_COMPONENT_THRESHOLD = 25
def reverse_colors(nparray):
return nparray[:,:,[2, 1, 0]]
def show(nparray):
Image.fromarray(reverse_colors(nparray)).show()
def thicken(nparray):
height, width = nparray.shape
nparray = nparray.reshape((height, width, 1))
return np.repeat(nparray, 3, 2)
def sort_by_color(mob):
indices = np.argsort(np.apply_along_axis(
lambda p : -np.linalg.norm(p),
1,
mob.rgbs
))
mob.rgbs = mob.rgbs[indices]
mob.points = mob.points[indices]
def get_image_array(name):
image_files = os.listdir(IMAGE_DIR)
possibilities = filter(lambda s : s.startswith(name), image_files)
for possibility in possibilities:
try:
path = os.path.join(IMAGE_DIR, possibility)
image = Image.open(path)
image = image.convert('RGB')
return np.array(image)
except:
pass
raise Exception("Image for %s not found"%name)
def get_edges(image_array):
blurred = cv2.GaussianBlur(
image_array,
**DEFAULT_GAUSS_BLUR_CONFIG
)
edges = cv2.Canny(
blurred,
**DEFAULT_CANNY_CONFIG
)
return edges
def nearest_neighbor_align(mobject1, mobject2):
distance_matrix = cdist(mobject1.points, mobject2.points)
closest_point_indices = np.apply_along_axis(
np.argmin, 0, distance_matrix
)
new_mob1 = Mobject()
new_mob2 = Mobject()
for n in range(mobject1.get_num_points()):
indices = (closest_point_indices == n)
new_mob1.add_points(
[mobject1.points[n]]*sum(indices)
)
new_mob2.add_points(
mobject2.points[indices],
rgbs = mobject2.rgbs[indices]
)
return new_mob1, new_mob2
def get_connected_components(image_array,
blur_radius = DEFAULT_BLUR_RADIUS,
threshold = DEFAULT_CONNECTED_COMPONENT_THRESHOLD):
blurred_image = ndimage.gaussian_filter(image_array, blur_radius)
labels, component_count = ndimage.label(blurred_image > threshold)
return [
image_array * (labels == count)
for count in range(1, component_count+1)
]
def color_region(bw_region, colored_image):
return thicken(bw_region > 0) * colored_image
class TracePicture(Scene):
args_list = [
("Newton",),
("Mark_Levi",),
("Steven_Strogatz",),
("Pierre_de_Fermat",),
("Galileo_Galilei",),
("Jacob_Bernoulli",),
("Johann_Bernoulli2",),
("Old_Newton",)
]
@staticmethod
def args_to_string(name):
return name
@staticmethod
def string_to_args(name):
return name
def construct(self, name):
run_time = 20
scale_factor = 0.8
image_array = get_image_array(name)
edge_mobject = self.get_edge_mobject(image_array)
full_picture = MobjectFromPixelArray(image_array)
for mob in edge_mobject, full_picture:
# mob.stroke_width = 4
mob.scale(scale_factor)
mob.show()
self.play(
DelayByOrder(FadeIn(
full_picture,
run_time = run_time,
rate_func = squish_rate_func(smooth, 0.7, 1)
)),
ShowCreation(
edge_mobject,
run_time = run_time,
rate_func = None
)
)
self.remove(edge_mobject)
self.dither()
def get_edge_mobject(self, image_array):
edged_image = get_edges(image_array)
individual_edges = get_connected_components(edged_image)
colored_edges = [
color_region(edge, image_array)
for edge in individual_edges
]
colored_edge_mobject_list = [
MobjectFromPixelArray(colored_edge)
for colored_edge in colored_edges
]
random.shuffle(colored_edge_mobject_list)
edge_mobject = Mobject(*colored_edge_mobject_list)
edge_mobject.ingest_submobjects()
return edge_mobject
class JohannThinksHeIsBetter(Scene):
def construct(self):
names = [
"Johann_Bernoulli2",
"Jacob_Bernoulli",
"Gottfried_Wilhelm_von_Leibniz",
"Newton"
]
guys = [
ImageMobject(name, invert = False)
for name in names
]
johann = guys[0]
johann.scale(0.8)
pensive_johann = johann.copy()
pensive_johann.scale(0.25)
pensive_johann.to_corner(DOWN+LEFT)
comparitive_johann = johann.copy()
template = Square(side_length = 2)
comparitive_johann.replace(template)
comparitive_johann.shift(UP+LEFT)
greater_than = TexMobject(">")
greater_than.next_to(comparitive_johann)
for guy, name in zip(guys, names)[1:]:
guy.replace(template)
guy.next_to(greater_than)
name_mob = TextMobject(name.replace("_", " "))
name_mob.scale(0.5)
name_mob.next_to(guy, DOWN)
guy.name_mob = name_mob
guy.sort_points(lambda p : np.dot(p, DOWN+RIGHT))
bubble = ThoughtBubble(initial_width = 12)
bubble.stretch_to_fit_height(6)
bubble.ingest_submobjects()
bubble.pin_to(pensive_johann)
bubble.shift(DOWN)
point = Point(johann.get_corner(UP+RIGHT))
upper_point = Point(comparitive_johann.get_corner(UP+RIGHT))
lightbulb = ImageMobject("Lightbulb", invert = False)
lightbulb.scale(0.1)
lightbulb.sort_points(np.linalg.norm)
lightbulb.next_to(upper_point, RIGHT)
self.add(johann)
self.dither()
self.play(
Transform(johann, pensive_johann),
Transform(point, bubble),
run_time = 2
)
self.remove(point)
self.add(bubble)
weakling = guys[1]
self.play(
FadeIn(comparitive_johann),
ShowCreation(greater_than),
FadeIn(weakling)
)
self.dither(2)
for guy in guys[2:]:
self.play(DelayByOrder(Transform(
weakling, upper_point
)))
self.play(
FadeIn(guy),
ShimmerIn(guy.name_mob)
)
self.dither(3)
self.remove(guy.name_mob)
weakling = guy
self.play(FadeOut(weakling), FadeOut(greater_than))
self.play(ShowCreation(lightbulb))
self.dither()
self.play(FadeOut(comparitive_johann), FadeOut(lightbulb))
self.play(ApplyMethod(
Mobject(johann, bubble).scale, 10,
run_time = 3
))
class NewtonVsJohann(Scene):
def construct(self):
newton, johann = [
ImageMobject(name, invert = False).scale(0.5)
for name in "Newton", "Johann_Bernoulli2"
]
greater_than = TexMobject(">")
newton.next_to(greater_than, RIGHT)
johann.next_to(greater_than, LEFT)
self.add(johann, greater_than, newton)
for i in range(2):
kwargs = {
"path_func" : counterclockwise_path(),
"run_time" : 2
}
self.play(
ApplyMethod(newton.replace, johann, **kwargs),
ApplyMethod(johann.replace, newton, **kwargs),
)
self.dither()
class JohannThinksOfFermat(Scene):
def construct(self):
johann, fermat = [
ImageMobject(name, invert = False)
for name in "Johann_Bernoulli2", "Pierre_de_Fermat"
]
johann.scale(0.2)
johann.to_corner(DOWN+LEFT)
bubble = ThoughtBubble(initial_width = 12)
bubble.stretch_to_fit_height(6)
bubble.pin_to(johann)
bubble.shift(DOWN)
bubble.add_content(fermat)
fermat.scale_in_place(0.4)
self.add(johann, bubble)
self.dither()
self.play(FadeIn(fermat))
self.dither()
class MathematiciansOfEurope(Scene):
def construct(self):
europe = ImageMobject("Europe", use_cache = False)
self.add(europe)
self.freeze_background()
mathematicians = [
("Newton", [-1.75, -0.75, 0]),
("Jacob_Bernoulli",[-0.75, -1.75, 0]),
("Ehrenfried_von_Tschirnhaus",[0.5, -0.5, 0]),
("Gottfried_Wilhelm_von_Leibniz",[0.2, -1.75, 0]),
("Guillaume_de_L'Hopital", [-1.75, -1.25, 0]),
]
for name, point in mathematicians:
man = ImageMobject(name, invert = False)
if name == "Newton":
name = "Isaac_Newton"
name_mob = TextMobject(name.replace("_", " "))
name_mob.to_corner(UP+LEFT, buff=0.75)
self.add(name_mob)
man.scale_to_fit_height(4)
mobject = Point(man.get_corner(UP+LEFT))
self.play(Transform(mobject, man))
man.scale(0.2)
man.shift(point)
self.play(Transform(mobject, man))
self.remove(name_mob)
class OldNewtonIsDispleased(Scene):
def construct(self):
old_newton = ImageMobject("Old_Newton", invert = False)
old_newton.scale(0.8)
self.add(old_newton)
self.freeze_background()
words = TextMobject("Note the displeasure")
words.to_corner(UP+RIGHT)
face_point = 1.8*UP+0.5*LEFT
arrow = Arrow(words.get_bottom(), face_point)
self.play(ShimmerIn(words))
self.play(ShowCreation(arrow))
self.dither()
class NewtonConsideredEveryoneBeneathHim(Scene):
def construct(self):
mathematicians = [
ImageMobject(name, invert = False)
for name in [
"Old_Newton",
"Johann_Bernoulli2",
"Jacob_Bernoulli",
"Ehrenfried_von_Tschirnhaus",
"Gottfried_Wilhelm_von_Leibniz",
"Guillaume_de_L'Hopital",
]
]
newton = mathematicians.pop(0)
newton.scale(0.8)
new_newton = newton.copy()
new_newton.scale_to_fit_height(3)
new_newton.to_edge(UP)
for man in mathematicians:
man.scale_to_fit_width(1.7)
johann = mathematicians.pop(0)
johann.next_to(new_newton, DOWN)
last_left, last_right = johann, johann
for man, count in zip(mathematicians, it.count()):
if count%2 == 0:
man.next_to(last_left, LEFT)
last_left = man
else:
man.next_to(last_right, RIGHT)
last_right = man
self.play(
Transform(newton, new_newton),
GrowFromCenter(johann)
)
self.dither()
self.play(FadeIn(Mobject(*mathematicians)))
self.dither()

View File

@ -0,0 +1,344 @@
import numpy as np
import itertools as it
from helpers import *
from mobject.tex_mobject import TexMobject, TextMobject, Brace
from mobject import Mobject, Mobject1D
from mobject.image_mobject import \
ImageMobject, MobjectFromPixelArray
from topics.three_dimensions import Stars
from animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.playground import TurnInsideOut, Vibrate
from topics.geometry import *
from topics.characters import Randolph, Mathematician
from topics.functions import *
from topics.number_line import *
from mobject.region import Region, region_from_polygon_vertices
from scene import Scene
from scene.zoomed_scene import ZoomedScene
from brachistochrone.curves import Cycloid
class MultilayeredGlass(PhotonScene, ZoomedScene):
CONFIG = {
"num_discrete_layers" : 5,
"num_variables" : 3,
"top_color" : BLUE_E,
"bottom_color" : BLUE_A,
"zoomed_canvas_space_shape" : (5, 5),
"square_color" : GREEN_B,
}
def construct(self):
self.cycloid = Cycloid(end_theta = np.pi)
self.cycloid.highlight(YELLOW)
self.top = self.cycloid.get_top()[1]
self.bottom = self.cycloid.get_bottom()[1]-1
self.generate_layers()
self.generate_discrete_path()
photon_run = self.photon_run_along_path(
self.discrete_path,
run_time = 1,
rate_func = rush_into
)
self.continuous_to_smooth()
self.add(*self.layers)
self.show_layer_variables()
self.play(photon_run)
self.play(ShowCreation(self.discrete_path))
self.isolate_bend_points()
self.clear()
self.add(*self.layers)
self.show_main_equation()
self.ask_continuous_question()
def continuous_to_smooth(self):
self.add(*self.layers)
continuous = self.get_continuous_background()
self.add(continuous)
self.dither()
self.play(ShowCreation(
continuous,
rate_func = lambda t : smooth(1-t)
))
self.remove(continuous)
self.dither()
def get_continuous_background(self):
glass = FilledRectangle(
height = self.top-self.bottom,
width = 2*SPACE_WIDTH,
)
glass.sort_points(lambda p : -p[1])
glass.shift((self.top-glass.get_top()[1])*UP)
glass.gradient_highlight(self.top_color, self.bottom_color)
return glass
def generate_layer_info(self):
self.layer_thickness = float(self.top-self.bottom)/self.num_discrete_layers
self.layer_tops = np.arange(
self.top, self.bottom, -self.layer_thickness
)
top_rgb, bottom_rgb = [
np.array(Color(color).get_rgb())
for color in self.top_color, self.bottom_color
]
epsilon = 1./(self.num_discrete_layers-1)
self.layer_colors = [
Color(rgb = interpolate(top_rgb, bottom_rgb, alpha))
for alpha in np.arange(0, 1+epsilon, epsilon)
]
def generate_layers(self):
self.generate_layer_info()
def create_region(top, color):
return Region(
lambda x, y : (y < top) & (y > top-self.layer_thickness),
color = color
)
self.layers = [
create_region(top, color)
for top, color in zip(self.layer_tops, self.layer_colors)
]
def generate_discrete_path(self):
points = self.cycloid.points
tops = list(self.layer_tops)
tops.append(tops[-1]-self.layer_thickness)
indices = [
np.argmin(np.abs(points[:, 1]-top))
for top in tops
]
self.bend_points = points[indices[1:-1]]
self.path_angles = []
self.discrete_path = Mobject1D(
color = YELLOW,
density = 3*DEFAULT_POINT_DENSITY_1D
)
for start, end in zip(indices, indices[1:]):
start_point, end_point = points[start], points[end]
self.discrete_path.add_line(
start_point, end_point
)
self.path_angles.append(
angle_of_vector(start_point-end_point)-np.pi/2
)
self.discrete_path.add_line(
points[end], SPACE_WIDTH*RIGHT+(self.layer_tops[-1]-1)*UP
)
def show_layer_variables(self):
layer_top_pairs = zip(
self.layer_tops[:self.num_variables],
self.layer_tops[1:]
)
v_equations = []
start_ys = []
end_ys = []
center_paths = []
braces = []
for (top1, top2), x in zip(layer_top_pairs, it.count(1)):
eq_mob = TexMobject(
["v_%d"%x, "=", "\sqrt{\phantom{y_1}}"],
size = "\\Large"
)
midpoint = UP*(top1+top2)/2
eq_mob.shift(midpoint)
v_eq = eq_mob.split()
center_paths.append(Line(
midpoint+SPACE_WIDTH*LEFT,
midpoint+SPACE_WIDTH*RIGHT
))
brace_endpoints = Mobject(
Point(self.top*UP+x*RIGHT),
Point(top2*UP+x*RIGHT)
)
brace = Brace(brace_endpoints, RIGHT)
start_y = TexMobject("y_%d"%x, size = "\\Large")
end_y = start_y.copy()
start_y.next_to(brace, RIGHT)
end_y.shift(v_eq[-1].get_center())
end_y.shift(0.2*RIGHT)
v_equations.append(v_eq)
start_ys.append(start_y)
end_ys.append(end_y)
braces.append(brace)
for v_eq, path, time in zip(v_equations, center_paths, [2, 1, 0.5]):
photon_run = self.photon_run_along_path(
path,
rate_func = None
)
self.play(
ShimmerIn(v_eq[0]),
photon_run,
run_time = time
)
self.dither()
for start_y, brace in zip(start_ys, braces):
self.add(start_y)
self.play(GrowFromCenter(brace))
self.dither()
quads = zip(v_equations, start_ys, end_ys, braces)
self.equations = []
for v_eq, start_y, end_y, brace in quads:
self.remove(brace)
self.play(
ShowCreation(v_eq[1]),
ShowCreation(v_eq[2]),
Transform(start_y, end_y)
)
v_eq.append(start_y)
self.equations.append(Mobject(*v_eq))
def isolate_bend_points(self):
arc_radius = 0.1
self.activate_zooming()
little_square = self.get_zoomed_camera_mobject()
for index in range(3):
bend_point = self.bend_points[index]
line = Line(
bend_point+DOWN,
bend_point+UP,
color = WHITE,
density = self.zoom_factor*DEFAULT_POINT_DENSITY_1D
)
angle_arcs = []
for i, rotation in [(index, np.pi/2), (index+1, -np.pi/2)]:
arc = Arc(angle = self.path_angles[i])
arc.scale(arc_radius)
arc.rotate(rotation)
arc.shift(bend_point)
angle_arcs.append(arc)
thetas = []
for i in [index+1, index+2]:
theta = TexMobject("\\theta_%d"%i)
theta.scale(0.5/self.zoom_factor)
vert = UP if i == index+1 else DOWN
horiz = rotate_vector(vert, np.pi/2)
theta.next_to(
Point(bend_point),
horiz,
buff = 0.01
)
theta.shift(1.5*arc_radius*vert)
thetas.append(theta)
figure_marks = [line] + angle_arcs + thetas
self.play(ApplyMethod(
little_square.shift,
bend_point - little_square.get_center(),
run_time = 2
))
self.play(*map(ShowCreation, figure_marks))
self.dither()
equation_frame = little_square.copy()
equation_frame.scale(0.5)
equation_frame.shift(
little_square.get_corner(UP+RIGHT) - \
equation_frame.get_corner(UP+RIGHT)
)
equation_frame.scale_in_place(0.9)
self.show_snells(index+1, equation_frame)
self.remove(*figure_marks)
self.disactivate_zooming()
def show_snells(self, index, frame):
left_text, right_text = [
"\\dfrac{\\sin(\\theta_%d)}{\\phantom{\\sqrt{y_1}}}"%x
for x in index, index+1
]
left, equals, right = TexMobject(
[left_text, "=", right_text]
).split()
vs = []
sqrt_ys = []
for x, numerator in [(index, left), (index+1, right)]:
v, sqrt_y = [
TexMobject(
text, size = "\\Large"
).next_to(numerator, DOWN)
for text in "v_%d"%x, "\\sqrt{y_%d}"%x
]
vs.append(v)
sqrt_ys.append(sqrt_y)
start, end = [
Mobject(
left.copy(), mobs[0], equals.copy(), right.copy(), mobs[1]
).replace(frame)
for mobs in vs, sqrt_ys
]
self.add(start)
self.dither(2)
self.play(Transform(
start, end,
path_func = counterclockwise_path()
))
self.dither(2)
self.remove(start, end)
def show_main_equation(self):
self.equation = TexMobject("""
\\dfrac{\\sin(\\theta)}{\\sqrt{y}} =
\\text{constant}
""")
self.equation.shift(LEFT)
self.equation.shift(
(self.layer_tops[0]-self.equation.get_top())*UP
)
self.add(self.equation)
self.dither()
def ask_continuous_question(self):
continuous = self.get_continuous_background()
line = Line(
UP, DOWN,
density = self.zoom_factor*DEFAULT_POINT_DENSITY_1D
)
theta = TexMobject("\\theta")
theta.scale(0.5/self.zoom_factor)
self.play(
ShowCreation(continuous),
Animation(self.equation)
)
self.remove(*self.layers)
self.play(ShowCreation(self.cycloid))
self.activate_zooming()
little_square = self.get_zoomed_camera_mobject()
self.add(line)
indices = np.arange(
0, self.cycloid.get_num_points()-1, 10
)
for index in indices:
point = self.cycloid.points[index]
next_point = self.cycloid.points[index+1]
angle = angle_of_vector(point - next_point)
for mob in little_square, line:
mob.shift(point - mob.get_center())
arc = Arc(angle-np.pi/2, start_angle = np.pi/2)
arc.scale(0.1)
arc.shift(point)
self.add(arc)
if angle > np.pi/2 + np.pi/6:
vect_angle = interpolate(np.pi/2, angle, 0.5)
vect = rotate_vector(RIGHT, vect_angle)
theta.center()
theta.shift(point)
theta.shift(0.15*vect)
self.add(theta)
self.dither(self.frame_duration)
self.remove(arc)

View File

@ -0,0 +1,943 @@
import numpy as np
import itertools as it
from helpers import *
from mobject.tex_mobject import TexMobject, TextMobject, Brace
from mobject import Mobject, Mobject1D
from mobject.image_mobject import \
ImageMobject, MobjectFromPixelArray
from topics.three_dimensions import Stars
from animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.playground import TurnInsideOut, Vibrate
from topics.geometry import *
from topics.characters import Randolph, Mathematician
from topics.functions import *
from topics.number_line import *
from mobject.region import Region, region_from_polygon_vertices
from scene import Scene
from scene.zoomed_scene import ZoomedScene
from brachistochrone.curves import \
Cycloid, PathSlidingScene, RANDY_SCALE_VAL, TryManyPaths
class Lens(Arc):
CONFIG = {
"radius" : 2,
"angle" : np.pi/2,
"color" : BLUE_B,
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
Arc.__init__(self, self.angle, **kwargs)
def generate_points(self):
Arc.generate_points(self)
self.rotate(-np.pi/4)
self.shift(-self.get_left())
self.add_points(self.copy().rotate(np.pi).points)
class PhotonScene(Scene):
def wavify(self, mobject):
result = mobject.copy()
result.ingest_submobjects()
tangent_vectors = result.points[1:]-result.points[:-1]
lengths = np.apply_along_axis(
np.linalg.norm, 1, tangent_vectors
)
thick_lengths = lengths.repeat(3).reshape((len(lengths), 3))
unit_tangent_vectors = tangent_vectors/thick_lengths
rot_matrix = np.transpose(rotation_matrix(np.pi/2, OUT))
normal_vectors = np.dot(unit_tangent_vectors, rot_matrix)
# total_length = np.sum(lengths)
times = np.cumsum(lengths)
nudge_sizes = 0.1*np.sin(2*np.pi*times)
thick_nudge_sizes = nudge_sizes.repeat(3).reshape((len(nudge_sizes), 3))
nudges = thick_nudge_sizes*normal_vectors
result.points[1:] += nudges
return result
def photon_run_along_path(self, path, color = YELLOW, **kwargs):
if "rate_func" not in kwargs:
kwargs["rate_func"] = None
photon = self.wavify(path)
photon.highlight(color)
return ShowPassingFlash(photon, **kwargs)
class SimplePhoton(PhotonScene):
def construct(self):
text = TextMobject("Light")
text.to_edge(UP)
self.play(ShimmerIn(text))
self.play(self.photon_run_along_path(
Cycloid(), rate_func = None
))
self.dither()
class MultipathPhotonScene(PhotonScene):
CONFIG = {
"num_paths" : 5
}
def run_along_paths(self, **kwargs):
paths = self.get_paths()
colors = Color(YELLOW).range_to(WHITE, len(paths))
for path, color in zip(paths, colors):
path.highlight(color)
photon_runs = [
self.photon_run_along_path(path)
for path in paths
]
for photon_run, path in zip(photon_runs, paths):
self.play(
photon_run,
ShowCreation(
path,
rate_func = lambda t : 0.9*smooth(t)
),
**kwargs
)
self.dither()
def generate_paths(self):
raise Exception("Not Implemented")
class PhotonThroughLens(MultipathPhotonScene):
def construct(self):
self.lens = Lens()
self.add(self.lens)
self.run_along_paths()
def get_paths(self):
interval_values = np.arange(self.num_paths).astype('float')
interval_values /= (self.num_paths-1.)
first_contact = [
self.lens.point_from_proportion(0.4*v+0.55)
for v in reversed(interval_values)
]
second_contact = [
self.lens.point_from_proportion(0.3*v + 0.1)
for v in interval_values
]
focal_point = 2*RIGHT
return [
Mobject(
Line(SPACE_WIDTH*LEFT + fc[1]*UP, fc),
Line(fc, sc),
Line(sc, focal_point),
Line(focal_point, 6*focal_point-5*sc)
).ingest_submobjects()
for fc, sc in zip(first_contact, second_contact)
]
class TransitionToOptics(PhotonThroughLens):
def construct(self):
optics = TextMobject("Optics")
optics.to_edge(UP)
self.add(optics)
self.has_started = False
PhotonThroughLens.construct(self)
def play(self, *args, **kwargs):
if not self.has_started:
self.has_started = True
everything = Mobject(*self.mobjects)
vect = 2*SPACE_WIDTH*RIGHT
everything.shift(vect)
self.play(ApplyMethod(
everything.shift, -vect,
rate_func = rush_from
))
Scene.play(self, *args, **kwargs)
class PhotonOffMirror(MultipathPhotonScene):
def construct(self):
self.mirror = Line(*SPACE_HEIGHT*np.array([DOWN, UP]))
self.mirror.highlight(GREY)
self.add(self.mirror)
self.run_along_paths()
def get_paths(self):
interval_values = np.arange(self.num_paths).astype('float')
interval_values /= (self.num_paths-1)
anchor_points = [
self.mirror.point_from_proportion(0.6*v+0.3)
for v in interval_values
]
start_point = 5*LEFT+3*UP
end_points = []
for point in anchor_points:
vect = start_point-point
vect[1] *= -1
end_points.append(point+2*vect)
return [
Mobject(
Line(start_point, anchor_point),
Line(anchor_point, end_point)
).ingest_submobjects()
for anchor_point, end_point in zip(anchor_points, end_points)
]
class PhotonsInWater(MultipathPhotonScene):
def construct(self):
water = Region(lambda x, y : y < 0, color = BLUE_E)
self.add(water)
self.run_along_paths()
def get_paths(self):
x, y = -3, 3
start_point = x*RIGHT + y*UP
angles = np.arange(np.pi/18, np.pi/3, np.pi/18)
midpoints = y*np.arctan(angles)
end_points = midpoints + SPACE_HEIGHT*np.arctan(2*angles)
return [
Mobject(
Line(start_point, [midpoint, 0, 0]),
Line([midpoint, 0, 0], [end_point, -SPACE_HEIGHT, 0])
).ingest_submobjects()
for midpoint, end_point in zip(midpoints, end_points)
]
class ShowMultiplePathsScene(PhotonScene):
def construct(self):
text = TextMobject("Which path minimizes travel time?")
text.to_edge(UP)
self.generate_start_and_end_points()
point_a = Dot(self.start_point)
point_b = Dot(self.end_point)
A = TextMobject("A").next_to(point_a, UP)
B = TextMobject("B").next_to(point_b, DOWN)
paths = self.get_paths()
for point, letter in [(point_a, A), (point_b, B)]:
self.play(
ShowCreation(point),
ShimmerIn(letter)
)
self.play(ShimmerIn(text))
curr_path = paths[0].copy()
curr_path_copy = curr_path.copy().ingest_submobjects()
self.play(
self.photon_run_along_path(curr_path),
ShowCreation(curr_path_copy, rate_func = rush_into)
)
self.remove(curr_path_copy)
for path in paths[1:] + [paths[0]]:
self.play(Transform(curr_path, path, run_time = 4))
self.dither()
self.path = curr_path.ingest_submobjects()
def generate_start_and_end_points(self):
raise Exception("Not Implemented")
def get_paths(self):
raise Exception("Not implemented")
class ShowMultiplePathsThroughLens(ShowMultiplePathsScene):
def construct(self):
self.lens = Lens()
self.add(self.lens)
ShowMultiplePathsScene.construct(self)
def generate_start_and_end_points(self):
self.start_point = 3*LEFT + UP
self.end_point = 2*RIGHT
def get_paths(self):
alphas = [0.25, 0.4, 0.58, 0.75]
lower_right, upper_right, upper_left, lower_left = map(
self.lens.point_from_proportion, alphas
)
return [
Mobject(
Line(self.start_point, a),
Line(a, b),
Line(b, self.end_point)
).highlight(color)
for (a, b), color in zip(
[
(upper_left, upper_right),
(upper_left, lower_right),
(lower_left, lower_right),
(lower_left, upper_right),
],
Color(YELLOW).range_to(WHITE, 4)
)
]
class ShowMultiplePathsOffMirror(ShowMultiplePathsScene):
def construct(self):
mirror = Line(*SPACE_HEIGHT*np.array([DOWN, UP]))
mirror.highlight(GREY)
self.add(mirror)
ShowMultiplePathsScene.construct(self)
def generate_start_and_end_points(self):
self.start_point = 4*LEFT + 2*UP
self.end_point = 4*LEFT + 2*DOWN
def get_paths(self):
return [
Mobject(
Line(self.start_point, midpoint),
Line(midpoint, self.end_point)
).highlight(color)
for midpoint, color in zip(
[2*UP, 2*DOWN],
Color(YELLOW).range_to(WHITE, 2)
)
]
class ShowMultiplePathsInWater(ShowMultiplePathsScene):
def construct(self):
glass = Region(lambda x, y : y < 0, color = BLUE_E)
self.generate_start_and_end_points()
straight = Line(self.start_point, self.end_point)
slow = TextMobject("Slow")
slow.rotate(np.arctan(straight.get_slope()))
slow.shift(straight.points[int(0.7*straight.get_num_points())])
slow.shift(0.5*DOWN)
too_long = TextMobject("Too long")
too_long.shift(UP)
air = TextMobject("Air").shift(2*UP)
water = TextMobject("Water").shift(2*DOWN)
self.add(glass)
self.play(GrowFromCenter(air))
self.play(GrowFromCenter(water))
self.dither()
self.remove(air, water)
ShowMultiplePathsScene.construct(self)
self.play(
Transform(self.path, straight)
)
self.dither()
self.play(GrowFromCenter(slow))
self.dither()
self.remove(slow)
self.leftmost.ingest_submobjects()
self.play(Transform(self.path, self.leftmost, run_time = 3))
self.dither()
self.play(ShimmerIn(too_long))
self.dither()
def generate_start_and_end_points(self):
self.start_point = 3*LEFT + 2*UP
self.end_point = 3*RIGHT + 2*DOWN
def get_paths(self):
self.leftmost, self.rightmost = result = [
Mobject(
Line(self.start_point, midpoint),
Line(midpoint, self.end_point)
).highlight(color)
for midpoint, color in zip(
[3*LEFT, 3*RIGHT],
Color(YELLOW).range_to(WHITE, 2)
)
]
return result
class StraightLinesFastestInConstantMedium(PhotonScene):
def construct(self):
kwargs = {"size" : "\\Large"}
left = TextMobject("Speed of light is constant", **kwargs)
arrow = TexMobject("\\Rightarrow", **kwargs)
right = TextMobject("Staight path is fastest", **kwargs)
left.next_to(arrow, LEFT)
right.next_to(arrow, RIGHT)
squaggle, line = self.get_paths()
self.play(*map(ShimmerIn, [left, arrow, right]))
self.play(ShowCreation(squaggle))
self.play(self.photon_run_along_path(
squaggle, run_time = 2, rate_func = None
))
self.play(Transform(
squaggle, line,
path_func = path_along_arc(np.pi)
))
self.play(self.photon_run_along_path(line, rate_func = None))
self.dither()
def get_paths(self):
squaggle = ParametricFunction(
lambda t : (0.5*t+np.cos(t))*RIGHT+np.sin(t)*UP,
start = -np.pi,
end = 2*np.pi
)
squaggle.shift(2*UP)
start, end = squaggle.points[0], squaggle.points[-1]
line = Line(start, end)
result = [squaggle, line]
for mob in result:
mob.highlight(BLUE_D)
return result
class PhtonBendsInWater(PhotonScene, ZoomedScene):
def construct(self):
glass = Region(lambda x, y : y < 0, color = BLUE_E)
kwargs = {
"density" : self.zoom_factor*DEFAULT_POINT_DENSITY_1D
}
top_line = Line(SPACE_HEIGHT*UP+2*LEFT, ORIGIN, **kwargs)
extension = Line(ORIGIN, SPACE_HEIGHT*DOWN+2*RIGHT, **kwargs)
bottom_line = Line(ORIGIN, SPACE_HEIGHT*DOWN+RIGHT, **kwargs)
path1 = Mobject(top_line, extension)
path2 = Mobject(top_line, bottom_line)
for mob in path1, path2:
mob.ingest_submobjects()
extension.highlight(RED)
theta1 = np.arctan(bottom_line.get_slope())
theta2 = np.arctan(extension.get_slope())
arc = Arc(theta2-theta1, start_angle = theta1, radius = 2)
question_mark = TextMobject("$\\theta$?")
question_mark.shift(arc.get_center()+0.5*DOWN+0.25*RIGHT)
wave = self.wavify(path2)
wave.highlight(YELLOW)
wave.scale(0.5)
self.add(glass)
self.play(ShowCreation(path1))
self.play(Transform(path1, path2))
self.dither()
# self.activate_zooming()
self.dither()
self.play(ShowPassingFlash(
wave, run_time = 3, rate_func = None
))
self.dither()
self.play(ShowCreation(extension))
self.play(
ShowCreation(arc),
ShimmerIn(question_mark)
)
class LightIsFasterInAirThanWater(ShowMultiplePathsInWater):
def construct(self):
glass = Region(lambda x, y : y < 0, color = BLUE_E)
equation = TexMobject("v_{\\text{air}} > v_{\\text{water}}")
equation.to_edge(UP)
path = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT)
path1 = path.copy().shift(2*UP)
path2 = path.copy().shift(2*DOWN)
self.add(glass)
self.play(ShimmerIn(equation))
self.dither()
photon_runs = []
photon_runs.append(self.photon_run_along_path(
path1, rate_func = lambda t : min(1, 1.2*t)
))
photon_runs.append(self.photon_run_along_path(path2))
self.play(*photon_runs, **{"run_time" : 2})
self.dither()
class GeometryOfGlassSituation(ShowMultiplePathsInWater):
def construct(self):
glass = Region(lambda x, y : y < 0, color = BLUE_E)
self.generate_start_and_end_points()
left = self.start_point[0]*RIGHT
right = self.end_point[0]*RIGHT
start_x = interpolate(left, right, 0.2)
end_x = interpolate(left, right, 1.0)
left_line = Line(self.start_point, left, color = RED_D)
right_line = Line(self.end_point, right, color = RED_D)
h_1, h_2 = map(TexMobject, ["h_1", "h_2"])
h_1.next_to(left_line, LEFT)
h_2.next_to(right_line, RIGHT)
point_a = Dot(self.start_point)
point_b = Dot(self.end_point)
A = TextMobject("A").next_to(point_a, UP)
B = TextMobject("B").next_to(point_b, DOWN)
x = start_x
left_brace = Brace(Mobject(Point(left), Point(x)))
right_brace = Brace(Mobject(Point(x), Point(right)), UP)
x_mob = TexMobject("x")
x_mob.next_to(left_brace, DOWN)
w_minus_x = TexMobject("w-x")
w_minus_x.next_to(right_brace, UP)
top_line = Line(self.start_point, x)
bottom_line = Line(x, self.end_point)
top_dist = TexMobject("\\sqrt{h_1^2+x^2}")
top_dist.scale(0.5)
a = 0.3
n = top_line.get_num_points()
point = top_line.points[int(a*n)]
top_dist.next_to(Point(point), RIGHT, buff = 0.3)
bottom_dist = TexMobject("\\sqrt{h_2^2+(w-x)^2}")
bottom_dist.scale(0.5)
n = bottom_line.get_num_points()
point = bottom_line.points[int((1-a)*n)]
bottom_dist.next_to(Point(point), LEFT, buff = 1)
end_top_line = Line(self.start_point, end_x)
end_bottom_line = Line(end_x, self.end_point)
end_brace = Brace(Mobject(Point(left), Point(end_x)))
end_x_mob = TexMobject("x").next_to(end_brace, DOWN)
axes = Mobject(
NumberLine(),
NumberLine().rotate(np.pi/2).shift(7*LEFT)
)
graph = FunctionGraph(
lambda x : 0.4*(x+1)*(x-3)+4,
x_min = -2,
x_max = 4
)
graph.highlight(YELLOW)
Mobject(axes, graph).scale(0.2).to_corner(UP+RIGHT, buff = 1)
axes.add(TexMobject("x", size = "\\small").next_to(axes, RIGHT))
axes.add(TextMobject("Travel time", size = "\\small").next_to(
axes, UP
))
new_graph = graph.copy()
midpoint = new_graph.points[new_graph.get_num_points()/2]
new_graph.filter_out(lambda p : p[0] < midpoint[0])
new_graph.reverse_points()
pairs_for_end_transform = [
(mob, mob.copy())
for mob in top_line, bottom_line, left_brace, x_mob
]
self.add(glass, point_a, point_b, A, B)
line = Mobject(top_line, bottom_line).ingest_submobjects()
self.play(ShowCreation(line))
self.dither()
self.play(
GrowFromCenter(left_brace),
GrowFromCenter(x_mob)
)
self.play(
GrowFromCenter(right_brace),
GrowFromCenter(w_minus_x)
)
self.play(ShowCreation(left_line), ShimmerIn(h_1))
self.play(ShowCreation(right_line), GrowFromCenter(h_2))
self.play(ShimmerIn(top_dist))
self.play(GrowFromCenter(bottom_dist))
self.dither(3)
self.clear()
self.add(glass, point_a, point_b, A, B,
top_line, bottom_line, left_brace, x_mob)
self.play(ShowCreation(axes))
kwargs = {
"run_time" : 4,
}
self.play(*[
Transform(*pair, **kwargs)
for pair in [
(top_line, end_top_line),
(bottom_line, end_bottom_line),
(left_brace, end_brace),
(x_mob, end_x_mob)
]
]+[ShowCreation(graph, **kwargs)])
self.dither()
self.show_derivatives(graph)
line = self.show_derivatives(new_graph)
self.add(line)
self.play(*[
Transform(*pair, rate_func = lambda x : 0.3*smooth(x))
for pair in pairs_for_end_transform
])
self.dither()
def show_derivatives(self, graph, run_time = 2):
step = self.frame_duration/run_time
for a in smooth(np.arange(0, 1-step, step)):
index = int(a*graph.get_num_points())
p1, p2 = graph.points[index], graph.points[index+1]
line = Line(LEFT, RIGHT)
line.rotate(angle_of_vector(p2-p1))
line.shift(p1)
self.add(line)
self.dither(self.frame_duration)
self.remove(line)
return line
class Spring(Line):
CONFIG = {
"num_loops" : 5,
"loop_radius" : 0.3,
"color" : GREY
}
def generate_points(self):
## self.start, self.end
length = np.linalg.norm(self.end-self.start)
angle = angle_of_vector(self.end-self.start)
micro_radius = self.loop_radius/length
m = 2*np.pi*(self.num_loops+0.5)
def loop(t):
return micro_radius*(
RIGHT + np.cos(m*t)*LEFT + np.sin(m*t)*UP
)
new_epsilon = self.epsilon/(m*micro_radius)/length
self.add_points([
t*RIGHT + loop(t)
for t in np.arange(0, 1, new_epsilon)
])
self.scale(length/(1+2*micro_radius))
self.rotate(angle)
self.shift(self.start)
class SpringSetup(ShowMultiplePathsInWater):
def construct(self):
self.ring_shift_val = 6*RIGHT
self.slide_kwargs = {
"rate_func" : there_and_back,
"run_time" : 5
}
self.setup_background()
rod = Region(
lambda x, y : (abs(x) < 5) & (abs(y) < 0.05),
color = GOLD_E
)
ring = Arc(
angle = 11*np.pi/6,
start_angle = -11*np.pi/12,
radius = 0.2,
color = YELLOW
)
ring.shift(-self.ring_shift_val/2)
self.generate_springs(ring)
self.add_rod_and_ring(rod, ring)
self.slide_ring(ring)
self.dither()
self.add_springs()
self.add_force_definitions()
self.slide_system(ring)
self.show_horizontal_component(ring)
self.show_angles(ring)
self.show_equation()
def setup_background(self):
glass = Region(lambda x, y : y < 0, color = BLUE_E)
self.generate_start_and_end_points()
point_a = Dot(self.start_point)
point_b = Dot(self.end_point)
A = TextMobject("A").next_to(point_a, UP)
B = TextMobject("B").next_to(point_b, DOWN)
self.add(glass, point_a, point_b, A, B)
def generate_springs(self, ring):
self.start_springs, self.end_springs = [
Mobject(
Spring(self.start_point, r.get_top()),
Spring(self.end_point, r.get_bottom())
)
for r in ring, ring.copy().shift(self.ring_shift_val)
]
def add_rod_and_ring(self, rod, ring):
rod_word = TextMobject("Rod")
rod_word.next_to(Point(), UP)
ring_word = TextMobject("Ring")
ring_word.next_to(ring, UP)
self.dither()
self.add(rod)
self.play(ShimmerIn(rod_word))
self.dither()
self.remove(rod_word)
self.play(ShowCreation(ring))
self.play(ShimmerIn(ring_word))
self.dither()
self.remove(ring_word)
def slide_ring(self, ring):
self.play(ApplyMethod(
ring.shift, self.ring_shift_val,
**self.slide_kwargs
))
def add_springs(self):
colors = iter([BLACK, BLUE_E])
for spring in self.start_springs.split():
circle = Circle(color = colors.next())
circle.reverse_points()
circle.scale(spring.loop_radius)
circle.shift(spring.points[0])
self.play(Transform(circle, spring))
self.remove(circle)
self.add(spring)
self.dither()
def add_force_definitions(self):
top_force = TexMobject("F_1 = \\dfrac{1}{v_{\\text{air}}}")
bottom_force = TexMobject("F_2 = \\dfrac{1}{v_{\\text{water}}}")
top_spring, bottom_spring = self.start_springs.split()
top_force.next_to(top_spring)
bottom_force.next_to(bottom_spring, DOWN, buff = -0.5)
words = TextMobject("""
The force in a real spring is
proportional to that spring's length
""")
words.to_corner(UP+RIGHT)
for force in top_force, bottom_force:
self.play(GrowFromCenter(force))
self.dither()
self.play(ShimmerIn(words))
self.dither(3)
self.remove(top_force, bottom_force, words)
def slide_system(self, ring):
equilibrium_slide_kwargs = dict(self.slide_kwargs)
def jiggle_to_equilibrium(t):
return 0.7*(1+((1-t)**2)*(-np.cos(10*np.pi*t)))
equilibrium_slide_kwargs = {
"rate_func" : jiggle_to_equilibrium,
"run_time" : 3
}
start = Mobject(ring, self.start_springs)
end = Mobject(
ring.copy().shift(self.ring_shift_val),
self.end_springs
)
for kwargs in self.slide_kwargs, equilibrium_slide_kwargs:
self.play(Transform(start, end, **kwargs))
self.dither()
def show_horizontal_component(self, ring):
v_right = Vector(ring.get_top(), RIGHT)
v_left = Vector(ring.get_bottom(), LEFT)
self.play(*map(ShowCreation, [v_right, v_left]))
self.dither()
self.remove(v_right, v_left)
def show_angles(self, ring):
ring_center = ring.get_center()
lines, arcs, thetas = [], [], []
counter = it.count(1)
for point in self.start_point, self.end_point:
line = Line(point, ring_center, color = GREY)
angle = np.pi/2-np.abs(np.arctan(line.get_slope()))
arc = Arc(angle, radius = 0.5).rotate(np.pi/2)
if point is self.end_point:
arc.rotate(np.pi)
theta = TexMobject("\\theta_%d"%counter.next())
theta.scale(0.5)
theta.shift(2*arc.get_center())
arc.shift(ring_center)
theta.shift(ring_center)
lines.append(line)
arcs.append(arc)
thetas.append(theta)
vert_line = Line(2*UP, 2*DOWN)
vert_line.shift(ring_center)
top_spring, bottom_spring = self.start_springs.split()
self.play(
Transform(ring, Point(ring_center)),
Transform(top_spring, lines[0]),
Transform(bottom_spring, lines[1])
)
self.play(ShowCreation(vert_line))
anims = []
for arc, theta in zip(arcs, thetas):
anims += [
ShowCreation(arc),
GrowFromCenter(theta)
]
self.play(*anims)
self.dither()
def show_equation(self):
equation = TexMobject([
"\\left(\\dfrac{1}{\\phantom{v_air}}\\right)",
"\\sin(\\theta_1)",
"=",
"\\left(\\dfrac{1}{\\phantom{v_water}}\\right)",
"\\sin(\\theta_2)"
])
equation.to_corner(UP+RIGHT)
frac1, sin1, equals, frac2, sin2 = equation.split()
v_air, v_water = [
TexMobject("v_{\\text{%s}}"%s, size = "\\Large")
for s in "air", "water"
]
v_air.next_to(Point(frac1.get_center()), DOWN)
v_water.next_to(Point(frac2.get_center()), DOWN)
frac1.add(v_air)
frac2.add(v_water)
f1, f2 = [
TexMobject("F_%d"%d, size = "\\Large")
for d in 1, 2
]
f1.next_to(sin1, LEFT)
f2.next_to(equals, RIGHT)
sin2_start = sin2.copy().next_to(f2, RIGHT)
bar1 = TexMobject("\\dfrac{\\qquad}{\\qquad}")
bar2 = bar1.copy()
bar1.next_to(sin1, DOWN)
bar2.next_to(sin2, DOWN)
v_air_copy = v_air.copy().next_to(bar1, DOWN)
v_water_copy = v_water.copy().next_to(bar2, DOWN)
bars = Mobject(bar1, bar2)
new_eq = equals.copy().center().shift(bars.get_center())
snells = TextMobject("Snell's Law")
snells.highlight(YELLOW)
snells.shift(new_eq.get_center()[0]*RIGHT)
snells.shift(UP)
anims = []
for mob in f1, sin1, equals, f2, sin2_start:
anims.append(ShimmerIn(mob))
self.play(*anims)
self.dither()
for f, frac in (f1, frac1), (f2, frac2):
target = frac.copy().ingest_submobjects()
also = []
if f is f2:
also.append(Transform(sin2_start, sin2))
sin2 = sin2_start
self.play(Transform(f, target), *also)
self.remove(f)
self.add(frac)
self.dither()
self.play(
FadeOut(frac1),
FadeOut(frac2),
Transform(v_air, v_air_copy),
Transform(v_water, v_water_copy),
ShowCreation(bars),
Transform(equals, new_eq)
)
self.dither()
frac1 = Mobject(sin1, bar1, v_air)
frac2 = Mobject(sin2, bar2, v_water)
for frac, vect in (frac1, LEFT), (frac2, RIGHT):
self.play(ApplyMethod(
frac.next_to, equals, vect
))
self.dither()
self.play(ShimmerIn(snells))
self.dither()
class WhatGovernsTheSpeedOfLight(PhotonScene, PathSlidingScene):
def construct(self):
randy = Randolph()
randy.scale(RANDY_SCALE_VAL)
randy.shift(-randy.get_bottom())
self.add_cycloid_end_points()
self.add(self.cycloid)
self.slide(randy, self.cycloid)
self.play(self.photon_run_along_path(self.cycloid))
self.dither()
class WhichPathWouldLightTake(PhotonScene, TryManyPaths):
def construct(self):
words = TextMobject(
["Which path ", "would \\emph{light} take", "?"]
)
words.split()[1].highlight(YELLOW)
words.to_corner(UP+RIGHT)
self.add_cycloid_end_points()
anims = [
self.photon_run_along_path(
path,
rate_func = smooth
)
for path in self.get_paths()
]
self.play(anims[0], ShimmerIn(words))
for anim in anims[1:]:
self.play(anim)
class StateSnellsLaw(PhotonScene):
def construct(self):
point_a = 3*LEFT+3*UP
point_b = 1.5*RIGHT+3*DOWN
midpoint = ORIGIN
lines, arcs, thetas = [], [], []
counter = it.count(1)
for point in point_a, point_b:
line = Line(point, midpoint, color = RED_D)
angle = np.pi/2-np.abs(np.arctan(line.get_slope()))
arc = Arc(angle, radius = 0.5).rotate(np.pi/2)
if point is point_b:
arc.rotate(np.pi)
line.reverse_points()
theta = TexMobject("\\theta_%d"%counter.next())
theta.scale(0.5)
theta.shift(2*arc.get_center())
arc.shift(midpoint)
theta.shift(midpoint)
lines.append(line)
arcs.append(arc)
thetas.append(theta)
vert_line = Line(2*UP, 2*DOWN)
vert_line.shift(midpoint)
path = Mobject(*lines).ingest_submobjects()
glass = Region(lambda x, y : y < 0, color = BLUE_E)
self.add(glass)
equation = TexMobject([
"\\dfrac{\\sin(\\theta_1)}{v_{\\text{air}}}",
"=",
"\\dfrac{\\sin(\\theta_2)}{v_{\\text{water}}}",
])
equation.to_corner(UP+RIGHT)
exp1, equals, exp2 = equation.split()
snells_law = TextMobject("Snell's Law:")
snells_law.highlight(YELLOW)
snells_law.to_edge(UP)
self.play(ShimmerIn(snells_law))
self.dither()
self.play(ShowCreation(path))
self.play(self.photon_run_along_path(path))
self.dither()
self.play(ShowCreation(vert_line))
self.play(*map(ShowCreation, arcs))
self.play(*map(GrowFromCenter, thetas))
self.dither()
self.play(ShimmerIn(exp1))
self.dither()
self.play(*map(ShimmerIn, [equals, exp2]))
self.dither()

View File

@ -0,0 +1,512 @@
import numpy as np
import itertools as it
from helpers import *
from mobject.tex_mobject import TexMobject, TextMobject, Brace
from mobject import Mobject
from mobject.image_mobject import ImageMobject
from topics.three_dimensions import Stars
from animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.playground import TurnInsideOut, Vibrate
from topics.geometry import *
from topics.characters import *
from topics.functions import ParametricFunction, FunctionGraph
from topics.number_line import *
from mobject.region import Region, region_from_polygon_vertices
from topics.three_dimensions import Stars
from scene import Scene
from brachistochrone.curves import Cycloid
class PhysicalIntuition(Scene):
def construct(self):
n_terms = 4
def func((x, y, ignore)):
z = complex(x, y)
if (np.abs(x%1 - 0.5)<0.01 and y < 0.01) or np.abs(z)<0.01:
return ORIGIN
out_z = 1./(2*np.tan(np.pi*z)*(z**2))
return out_z.real*RIGHT - out_z.imag*UP
arrows = Mobject(*[
Arrow(ORIGIN, np.sqrt(2)*point)
for point in compass_directions(4, RIGHT+UP)
])
arrows.highlight(YELLOW)
arrows.ingest_submobjects()
all_arrows = Mobject(*[
arrows.copy().scale(0.3/(x)).shift(x*RIGHT)
for x in range(1, n_terms+2)
])
terms = TexMobject([
"\\dfrac{1}{%d^2} + "%(x+1)
for x in range(n_terms)
]+["\\cdots"])
terms.shift(2*UP)
plane = NumberPlane(color = BLUE_E)
axes = Mobject(NumberLine(), NumberLine().rotate(np.pi/2))
axes.highlight(WHITE)
for term in terms.split():
self.play(ShimmerIn(term, run_time = 0.5))
self.dither()
self.play(ShowCreation(plane), ShowCreation(axes))
self.play(*[
Transform(*pair)
for pair in zip(terms.split(), all_arrows.split())
])
self.play(PhaseFlow(
func, plane,
run_time = 5,
virtual_time = 8
))
class TimeLine(Scene):
def construct(self):
dated_events = [
{
"date" : 1696,
"text": "Johann Bernoulli poses Brachistochrone problem",
"picture" : "Johann_Bernoulli2"
},
{
"date" : 1662,
"text" : "Fermat states his principle of least time",
"picture" : "Pierre_de_Fermat"
}
]
speical_dates = [2016] + [
obj["date"] for obj in dated_events
]
centuries = range(1600, 2100, 100)
timeline = NumberLine(
numerical_radius = 300,
number_at_center = 1800,
unit_length_to_spatial_width = SPACE_WIDTH/100,
tick_frequency = 10,
numbers_with_elongated_ticks = centuries
)
timeline.add_numbers(*centuries)
centers = [
Point(timeline.number_to_point(year))
for year in speical_dates
]
timeline.add(*centers)
timeline.shift(-centers[0].get_center())
self.add(timeline)
self.dither()
run_times = iter([3, 1])
for point, event in zip(centers[1:], dated_events):
self.play(ApplyMethod(
timeline.shift, -point.get_center(),
run_time = run_times.next()
))
picture = ImageMobject(event["picture"], invert = False)
picture.scale_to_fit_width(2)
picture.to_corner(UP+RIGHT)
event_mob = TextMobject(event["text"])
event_mob.shift(2*LEFT+2*UP)
date_mob = TexMobject(str(event["date"]))
date_mob.scale(0.5)
date_mob.shift(0.6*UP)
line = Line(event_mob.get_bottom(), 0.2*UP)
self.play(
ShimmerIn(event_mob),
ShowCreation(line),
ShimmerIn(date_mob)
)
self.play(FadeIn(picture))
self.dither(3)
self.play(*map(FadeOut, [event_mob, date_mob, line, picture]))
class StayedUpAllNight(Scene):
def construct(self):
clock = Circle(radius = 2, color = WHITE)
clock.add(Dot(ORIGIN))
ticks = Mobject(*[
Line(1.8*vect, 2*vect, color = GREY)
for vect in compass_directions(12)
])
clock.add(ticks)
hour_hand = Line(ORIGIN, UP)
minute_hand = Line(ORIGIN, 1.5*UP)
clock.add(hour_hand, minute_hand)
clock.to_corner(UP+RIGHT)
hour_hand.get_center = lambda : clock.get_center()
minute_hand.get_center = lambda : clock.get_center()
solution = ImageMobject(
"Newton_brachistochrone_solution2",
use_cache = False
)
solution.stroke_width = 3
solution.highlight(GREY)
solution.scale_to_fit_width(5)
solution.to_corner(UP+RIGHT)
newton = ImageMobject("Old_Newton", invert = False)
newton.scale(0.8)
phil_trans = TextMobject("Philosophical Transactions")
rect = Rectangle(height = 6, width = 4.5, color = WHITE)
rect.to_corner(UP+RIGHT)
rect.shift(DOWN)
phil_trans.scale_to_fit_width(0.8*rect.get_width())
phil_trans.next_to(Point(rect.get_top()), DOWN)
new_solution = solution.copy()
new_solution.scale_to_fit_width(phil_trans.get_width())
new_solution.next_to(phil_trans, DOWN, buff = 1)
not_newton = TextMobject("-Totally not by Newton")
not_newton.scale_to_fit_width(2.5)
not_newton.next_to(new_solution, DOWN, aligned_edge = RIGHT)
phil_trans.add(rect)
newton_complaint = TextMobject([
"``I do not love to be",
" \\emph{dunned} ",
"and teased by foreigners''"
], size = "\\small")
newton_complaint.to_edge(UP, buff = 0.2)
dunned = newton_complaint.split()[1]
dunned.highlight()
dunned_def = TextMobject("(old timey term for making \\\\ demands on someone)")
dunned_def.scale(0.7)
dunned_def.next_to(phil_trans, LEFT)
dunned_def.shift(2*UP)
dunned_arrow = Arrow(dunned_def, dunned)
johann = ImageMobject("Johann_Bernoulli2", invert = False)
johann.scale(0.4)
johann.to_edge(LEFT)
johann.shift(DOWN)
johann_quote = TextMobject("``I recognize the lion by his claw''")
johann_quote.next_to(johann, UP, aligned_edge = LEFT)
self.play(ApplyMethod(newton.to_edge, LEFT))
self.play(ShowCreation(clock))
kwargs = {
"axis" : OUT,
"rate_func" : smooth
}
self.play(
Rotating(hour_hand, radians = -2*np.pi, **kwargs),
Rotating(minute_hand, radians = -12*2*np.pi, **kwargs),
run_time = 5
)
self.dither()
self.clear()
self.add(newton)
clock.ingest_submobjects()
self.play(Transform(clock, solution))
self.remove(clock)
self.add(solution)
self.dither()
self.play(
FadeIn(phil_trans),
Transform(solution, new_solution)
)
self.dither()
self.play(ShimmerIn(not_newton))
phil_trans.add(solution, not_newton)
self.dither()
self.play(*map(ShimmerIn, newton_complaint.split()))
self.dither()
self.play(
ShimmerIn(dunned_def),
ShowCreation(dunned_arrow)
)
self.dither()
self.remove(dunned_def, dunned_arrow)
self.play(FadeOut(newton_complaint))
self.remove(newton_complaint)
self.play(
FadeOut(newton),
GrowFromCenter(johann)
)
self.remove(newton)
self.dither()
self.play(ShimmerIn(johann_quote))
self.dither()
class ThetaTGraph(Scene):
def construct(self):
t_axis = NumberLine()
theta_axis = NumberLine().rotate(np.pi/2)
theta_mob = TexMobject("\\theta(t)")
t_mob = TexMobject("t")
theta_mob.next_to(theta_axis, RIGHT)
theta_mob.to_edge(UP)
t_mob.next_to(t_axis, UP)
t_mob.to_edge(RIGHT)
graph = ParametricFunction(
lambda t : 4*t*RIGHT + 2*smooth(t)*UP
)
line = Line(graph.points[0], graph.points[-1], color = WHITE)
q_mark = TextMobject("?")
q_mark.next_to(Point(graph.get_center()), LEFT)
stars = Stars(color = BLACK)
stars.scale(0.1).shift(q_mark.get_center())
squiggle = ParametricFunction(
lambda t : t*RIGHT + 0.2*t*(5-t)*(np.sin(t)**2)*UP,
start = 0,
end = 5
)
self.play(
ShowCreation(t_axis),
ShowCreation(theta_axis),
ShimmerIn(theta_mob),
ShimmerIn(t_mob)
)
self.play(
ShimmerIn(q_mark),
ShowCreation(graph)
)
self.dither()
self.play(
Transform(q_mark, stars),
Transform(graph, line)
)
self.dither()
self.play(Transform(graph, squiggle))
self.dither()
class SolutionsToTheBrachistochrone(Scene):
def construct(self):
r_range = np.arange(0.5, 2, 0.25)
cycloids = Mobject(*[
Cycloid(radius = r, end_theta=2*np.pi)
for r in r_range
])
lower_left = 2*DOWN+6*LEFT
lines = Mobject(*[
Line(
lower_left,
lower_left+5*r*np.cos(np.arctan(r))*RIGHT+2*r*np.sin(np.arctan(r))*UP
)
for r in r_range
])
nl = NumberLine(numbers_with_elongated_ticks = [])
x_axis = nl.copy().shift(3*UP)
y_axis = nl.copy().rotate(np.pi/2).shift(6*LEFT)
t_axis = nl.copy().shift(2*DOWN)
x_label = TexMobject("x")
x_label.next_to(x_axis, DOWN)
x_label.to_edge(RIGHT)
y_label = TexMobject("y")
y_label.next_to(y_axis, RIGHT)
y_label.shift(2*DOWN)
t_label = TexMobject("t")
t_label.next_to(t_axis, UP)
t_label.to_edge(RIGHT)
theta_label = TexMobject("\\theta")
theta_label.next_to(y_axis, RIGHT)
theta_label.to_edge(UP)
words = TextMobject("Boundary conditions?")
words.next_to(lines, RIGHT)
words.shift(2*UP)
self.play(ShowCreation(x_axis), ShimmerIn(x_label))
self.play(ShowCreation(y_axis), ShimmerIn(y_label))
self.play(ShowCreation(cycloids))
self.dither()
self.play(
Transform(cycloids, lines),
Transform(x_axis, t_axis),
Transform(x_label, t_label),
Transform(y_label, theta_label),
run_time = 2
)
self.dither()
self.play(ShimmerIn(words))
self.dither()
class VideoLayout(Scene):
def construct(self):
left, right = 5*LEFT, 5*RIGHT
top_words = TextMobject("The next 15 minutes of your life:")
top_words.to_edge(UP)
line = Line(left, right, color = BLUE_D)
for a in np.arange(0, 4./3, 1./3):
vect = interpolate(left, right, a)
line.add_line(vect+0.2*DOWN, vect+0.2*UP)
left_brace = Brace(
Mobject(
Point(left),
Point(interpolate(left, right, 2./3))
),
DOWN
)
right_brace = Brace(
Mobject(
Point(interpolate(left, right, 2./3)),
Point(right)
),
UP
)
left_brace.words = map(TextMobject, [
"Problem statement",
"History",
"Johann Bernoulli's cleverness"
])
curr = left_brace
right_brace.words = map(TextMobject, [
"Challenge",
"Mark Levi's cleverness",
])
for brace in left_brace, right_brace:
curr = brace
direction = DOWN if brace is left_brace else UP
for word in brace.words:
word.next_to(curr, direction)
curr = word
right_brace.words.reverse()
self.play(ShimmerIn(top_words))
self.play(ShowCreation(line))
for brace in left_brace, right_brace:
self.play(GrowFromCenter(brace))
self.dither()
for word in brace.words:
self.play(ShimmerIn(word))
self.dither()
class ShortestPathProblem(Scene):
def construct(self):
point_a, point_b = 3*LEFT, 3*RIGHT
dots = []
for point, char in [(point_a, "A"), (point_b, "B")]:
dot = Dot(point)
letter = TexMobject(char)
letter.next_to(dot, UP+LEFT)
dot.add(letter)
dots.append(dot)
path = ParametricFunction(
lambda t : (t/2 + np.cos(t))*RIGHT + np.sin(t)*UP,
start = -2*np.pi,
end = 2*np.pi
)
path.scale(6/(2*np.pi))
path.shift(point_a - path.points[0])
path.highlight(RED)
line = Line(point_a, point_b)
words = TextMobject("Shortest path from $A$ to $B$")
words.to_edge(UP)
self.play(
ShimmerIn(words),
*map(GrowFromCenter, dots)
)
self.play(ShowCreation(path))
self.play(Transform(
path, line,
path_func = path_along_arc(np.pi)
))
self.dither()
class MathBetterThanTalking(Scene):
def construct(self):
mathy = Mathematician()
mathy.to_corner(DOWN+LEFT)
bubble = ThoughtBubble()
bubble.pin_to(mathy)
bubble.write("Math $>$ Talking about math")
self.add(mathy)
self.play(ShowCreation(bubble))
self.play(ShimmerIn(bubble.content))
self.dither()
self.play(ApplyMethod(
mathy.blink,
rate_func = squish_rate_func(there_and_back, 0.4, 0.6)
))
class DetailsOfProofBox(Scene):
def construct(self):
rect = Rectangle(height = 4, width = 6, color = WHITE)
words = TextMobject("Details of proof")
words.to_edge(UP)
self.play(
ShowCreation(rect),
ShimmerIn(words)
)
self.dither()
class TalkedAboutSnellsLaw(Scene):
def construct(self):
randy = Randolph()
randy.to_corner(DOWN+LEFT)
morty = Mortimer()
morty.to_edge(DOWN+RIGHT)
randy.bubble = SpeechBubble().pin_to(randy)
morty.bubble = SpeechBubble().pin_to(morty)
phrases = [
"Let's talk about Snell's law",
"I love Snell's law",
"It's like running from \\\\ a beach into the ocean",
"It's like two constant \\\\ tension springs",
]
self.add(randy, morty)
talkers = it.cycle([randy, morty])
for talker, phrase in zip(talkers, phrases):
talker.bubble.write(phrase)
self.play(
FadeIn(talker.bubble),
ShimmerIn(talker.bubble.content)
)
self.play(ApplyMethod(
talker.blink,
rate_func = squish_rate_func(there_and_back)
))
self.dither()
self.remove(talker.bubble, talker.bubble.content)
class YetAnotherMarkLevi(Scene):
def construct(self):
words = TextMobject("Yet another bit of Mark Levi cleverness")
words.to_edge(UP)
levi = ImageMobject("Mark_Levi", invert = False)
levi.scale_to_fit_width(6)
levi.show()
self.add(levi)
self.play(ShimmerIn(words))
self.dither(2)

View File

@ -0,0 +1,498 @@
import numpy as np
import itertools as it
from helpers import *
from mobject.tex_mobject import TexMobject, TextMobject, Brace
from mobject import Mobject, Mobject1D
from mobject.image_mobject import \
ImageMobject, MobjectFromPixelArray
from topics.three_dimensions import Stars
from animation import Animation
from animation.transform import *
from animation.simple_animations import *
from topics.geometry import *
from topics.characters import Randolph
from topics.functions import *
from mobject.region import Region
from scene import Scene
from scene.zoomed_scene import ZoomedScene
from camera import Camera
from brachistochrone.light import PhotonScene
from brachistochrone.curves import *
class MultilayeredScene(Scene):
CONFIG = {
"n_layers" : 5,
"top_color" : BLUE_E,
"bottom_color" : BLUE_A,
"total_glass_height" : 5,
"top" : 3*UP,
"RectClass" : Rectangle #FilledRectangle
}
def get_layers(self, n_layers = None):
if n_layers is None:
n_layers = self.n_layers
width = 2*SPACE_WIDTH
height = float(self.total_glass_height)/n_layers
rgb_pair = [
np.array(Color(color).get_rgb())
for color in self.top_color, self.bottom_color
]
rgb_range = [
interpolate(*rgb_pair+[x])
for x in np.arange(0, 1, 1./n_layers)
]
tops = [
self.top + x*height*DOWN
for x in range(n_layers)
]
color = Color()
result = []
for top, rgb in zip(tops, rgb_range):
color.set_rgb(rgb)
rect = self.RectClass(
height = height,
width = width,
color = color
)
rect.shift(top-rect.get_top())
result.append(rect)
return result
def add_layers(self):
self.layers = self.get_layers()
self.add(*self.layers)
self.freeze_background()
def get_bottom(self):
return self.top + self.total_glass_height*DOWN
def get_continuous_glass(self):
result = self.RectClass(
width = 2*SPACE_WIDTH,
height = self.total_glass_height,
)
result.sort_points(lambda p : -p[1])
result.gradient_highlight(self.top_color, self.bottom_color)
result.shift(self.top-result.get_top())
return result
class TwoToMany(MultilayeredScene):
CONFIG = {
"RectClass" : FilledRectangle
}
def construct(self):
glass = self.get_glass()
layers = self.get_layers()
self.add(glass)
self.dither()
self.play(*[
FadeIn(
layer,
rate_func = squish_rate_func(smooth, x, 1)
)
for layer, x in zip(layers[1:], it.count(0, 0.2))
]+[
Transform(glass, layers[0])
])
self.dither()
def get_glass(self):
return self.RectClass(
height = SPACE_HEIGHT,
width = 2*SPACE_WIDTH,
color = BLUE_E
).shift(SPACE_HEIGHT*DOWN/2)
class RaceLightInLayers(MultilayeredScene, PhotonScene):
CONFIG = {
"RectClass" : FilledRectangle
}
def construct(self):
self.add_layers()
line = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT)
lines = [
line.copy().shift(layer.get_center())
for layer in self.layers
]
def rate_maker(x):
return lambda t : min(x*x*t, 1)
min_rate, max_rate = 1., 2.
rates = np.arange(min_rate, max_rate, (max_rate-min_rate)/self.n_layers)
self.play(*[
self.photon_run_along_path(
line,
rate_func = rate_maker(rate),
run_time = 2
)
for line, rate in zip(lines, rates)
])
class ShowDiscretePath(MultilayeredScene, PhotonScene):
CONFIG = {
"RectClass" : FilledRectangle
}
def construct(self):
self.add_layers()
self.cycloid = Cycloid(end_theta = np.pi)
self.generate_discrete_path()
self.play(ShowCreation(self.discrete_path))
self.dither()
self.play(self.photon_run_along_path(
self.discrete_path,
rate_func = rush_into,
run_time = 3
))
self.dither()
def generate_discrete_path(self):
points = self.cycloid.points
tops = [mob.get_top()[1] for mob in self.layers]
tops.append(tops[-1]-self.layers[0].get_height())
indices = [
np.argmin(np.abs(points[:, 1]-top))
for top in tops
]
self.bend_points = points[indices[1:-1]]
self.path_angles = []
self.discrete_path = Mobject1D(
color = WHITE,
density = 3*DEFAULT_POINT_DENSITY_1D
)
for start, end in zip(indices, indices[1:]):
start_point, end_point = points[start], points[end]
self.discrete_path.add_line(
start_point, end_point
)
self.path_angles.append(
angle_of_vector(start_point-end_point)-np.pi/2
)
self.discrete_path.add_line(
points[end], SPACE_WIDTH*RIGHT+(tops[-1]-0.5)*UP
)
class NLayers(MultilayeredScene):
CONFIG = {
"RectClass" : FilledRectangle
}
def construct(self):
self.add_layers()
brace = Brace(
Mobject(
Point(self.top),
Point(self.get_bottom())
),
RIGHT
)
n_layers = TextMobject("$n$ layers")
n_layers.next_to(brace)
self.dither()
self.add(brace)
self.show_frame()
self.play(
GrowFromCenter(brace),
GrowFromCenter(n_layers)
)
self.dither()
class ShowLayerVariables(MultilayeredScene, PhotonScene):
CONFIG = {
"RectClass" : FilledRectangle
}
def construct(self):
self.add_layers()
v_equations = []
start_ys = []
end_ys = []
center_paths = []
braces = []
for layer, x in zip(self.layers[:3], it.count(1)):
eq_mob = TexMobject(
["v_%d"%x, "=", "\sqrt{\phantom{y_1}}"],
size = "\\Large"
)
eq_mob.shift(layer.get_center()+2*LEFT)
v_eq = eq_mob.split()
v_eq[0].highlight(layer.get_color())
path = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT)
path.shift(layer.get_center())
brace_endpoints = Mobject(
Point(self.top),
Point(layer.get_bottom())
)
brace = Brace(brace_endpoints, RIGHT)
brace.shift(x*RIGHT)
start_y = TexMobject("y_%d"%x, size = "\\Large")
end_y = start_y.copy()
start_y.next_to(brace, RIGHT)
end_y.shift(v_eq[-1].get_center())
nudge = 0.2*RIGHT
end_y.shift(nudge)
v_equations.append(v_eq)
start_ys.append(start_y)
end_ys.append(end_y)
center_paths.append(path)
braces.append(brace)
for v_eq, path, time in zip(v_equations, center_paths, [2, 1, 0.5]):
photon_run = self.photon_run_along_path(
path,
rate_func = None
)
self.play(
FadeToColor(v_eq[0], WHITE),
photon_run,
run_time = time
)
self.dither()
starts = [0, 0.3, 0.6]
self.play(*it.chain(*[
[
GrowFromCenter(
mob,
rate_func=squish_rate_func(smooth, start, 1)
)
for mob, start in zip(mobs, starts)
]
for mobs in start_ys, braces
]))
self.dither()
triplets = zip(v_equations, start_ys, end_ys)
anims = []
for v_eq, start_y, end_y in triplets:
anims += [
ShowCreation(v_eq[1]),
ShowCreation(v_eq[2]),
Transform(start_y.copy(), end_y)
]
self.play(*anims)
self.dither()
class LimitingProcess(MultilayeredScene):
CONFIG = {
"RectClass" : FilledRectangle
}
def construct(self):
num_iterations = 3
layer_sets = [
self.get_layers((2**x)*self.n_layers)
for x in range(num_iterations)
]
glass_sets = [
Mobject(*[
Mobject(
*layer_sets[x][(2**x)*index:(2**x)*(index+1)]
)
for index in range(self.n_layers)
]).ingest_submobjects()
for x in range(num_iterations)
]
glass_sets.append(self.get_continuous_glass())
for glass_set in glass_sets:
glass_set.sort_points(lambda p : p[1])
curr_set = glass_sets[0]
self.add(curr_set)
for layer_set in glass_sets[1:]:
self.dither()
self.play(Transform(curr_set, layer_set))
self.dither()
class ShowLightAndSlidingObject(MultilayeredScene, TryManyPaths, PhotonScene):
CONFIG = {
"show_time" : False,
"dither_and_add" : False,
"RectClass" : FilledRectangle
}
def construct(self):
glass = self.get_continuous_glass()
self.play(ApplyMethod(glass.fade, 0.8))
self.freeze_background()
paths = self.get_paths()
for path in paths:
if path.get_height() > self.total_glass_height:
path.stretch(0.7, 1)
path.shift(self.top - path.get_top())
path.rgbs[:,2] = 0
loop = paths.pop(1) ##Bad!
randy = Randolph()
randy.scale(RANDY_SCALE_VAL)
randy.shift(-randy.get_bottom())
photon_run = self.photon_run_along_path(
loop,
rate_func = lambda t : smooth(1.2*t, 2),
run_time = 4.1
)
text = self.get_text().to_edge(UP, buff = 0.2)
self.play(ShowCreation(loop))
self.dither()
self.play(photon_run)
self.remove(photon_run.mobject)
randy = self.slide(randy, loop)
self.add(randy)
self.dither()
self.remove(randy)
self.play(ShimmerIn(text))
for path in paths:
self.play(Transform(
loop, path,
path_func = path_along_arc(np.pi/2),
run_time = 2
))
class ContinuouslyObeyingSnellsLaw(MultilayeredScene):
CONFIG = {
"arc_radius" : 0.5,
"RectClass" : FilledRectangle
}
def construct(self):
glass = self.get_continuous_glass()
self.add(glass)
self.freeze_background()
cycloid = Cycloid(end_theta = np.pi)
cycloid.highlight(YELLOW)
chopped_cycloid = cycloid.copy()
n = cycloid.get_num_points()
chopped_cycloid.filter_out(lambda p : p[1] > 1 and p[0] < 0)
chopped_cycloid.reverse_points()
self.play(ShowCreation(cycloid))
ref_mob = self.snells_law_at_every_point(cycloid, chopped_cycloid)
self.show_equation(chopped_cycloid, ref_mob)
def snells_law_at_every_point(self, cycloid, chopped_cycloid):
square = Square(side_length = 0.2, color = WHITE)
words = TextMobject(["Snell's law ", "everywhere"])
snells, rest = words.split()
colon = TextMobject(":")
words.next_to(square)
words.shift(0.3*UP)
combo = Mobject(square, words)
combo.get_center = lambda : square.get_center()
new_snells = snells.copy().center().to_edge(UP, buff = 1.5)
colon.next_to(new_snells)
colon.shift(0.05*DOWN)
self.play(MoveAlongPath(
combo, cycloid,
run_time = 5
))
self.play(MoveAlongPath(
combo, chopped_cycloid,
run_time = 4
))
dot = Dot(combo.get_center())
self.play(Transform(square, dot))
self.play(
Transform(snells, new_snells),
Transform(rest, colon)
)
self.dither()
return colon
def get_marks(self, point1, point2):
vert_line = Line(2*DOWN, 2*UP)
tangent_line = vert_line.copy()
theta = TexMobject("\\theta")
theta.scale(0.5)
angle = angle_of_vector(point1 - point2)
tangent_line.rotate(
angle - tangent_line.get_angle()
)
angle_from_vert = angle - np.pi/2
for mob in vert_line, tangent_line:
mob.shift(point1 - mob.get_center())
arc = Arc(angle_from_vert, start_angle = np.pi/2)
arc.scale(self.arc_radius)
arc.shift(point1)
vect_angle = angle_from_vert/2 + np.pi/2
vect = rotate_vector(RIGHT, vect_angle)
theta.center()
theta.shift(point1)
theta.shift(1.5*self.arc_radius*vect)
return arc, theta, vert_line, tangent_line
def show_equation(self, chopped_cycloid, ref_mob):
point2, point1 = chopped_cycloid.points[-2:]
arc, theta, vert_line, tangent_line = self.get_marks(
point1, point2
)
equation = TexMobject([
"\\sin(\\theta)",
"\\over \\sqrt{y}",
])
sin, sqrt_y = equation.split()
equation.next_to(ref_mob)
const = TexMobject(" = \\text{constant}")
const.next_to(equation)
ceil_point = np.array(point1)
ceil_point[1] = self.top[1]
brace = Brace(
Mobject(Point(point1), Point(ceil_point)),
RIGHT
)
y_mob = TexMobject("y").next_to(brace)
self.play(
GrowFromCenter(sin),
ShowCreation(arc),
GrowFromCenter(theta)
)
self.play(ShowCreation(vert_line))
self.play(ShowCreation(tangent_line))
self.dither()
self.play(
GrowFromCenter(sqrt_y),
GrowFromCenter(brace),
GrowFromCenter(y_mob)
)
self.dither()
self.play(Transform(
Point(const.get_left()), const
))
self.dither()

View File

@ -0,0 +1,572 @@
import numpy as np
import itertools as it
import os
from helpers import *
from mobject.tex_mobject import TexMobject, TextMobject, Brace
from mobject import Mobject
from mobject.image_mobject import \
ImageMobject, MobjectFromPixelArray
from topics.three_dimensions import Stars
from animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.playground import TurnInsideOut, Vibrate
from topics.geometry import *
from topics.characters import Randolph, Mathematician
from topics.functions import ParametricFunction, FunctionGraph
from topics.number_line import NumberPlane
from mobject.region import Region, region_from_polygon_vertices
from scene import Scene
from generate_logo import LogoGeneration
from brachistochrone.drawing_images import sort_by_color
class Intro(Scene):
def construct(self):
logo = ImageMobject("LogoGeneration", invert = False)
name_mob = TextMobject("3Blue1Brown").center()
name_mob.highlight("grey")
name_mob.shift(2*DOWN)
self.add(name_mob, logo)
new_text = TextMobject(["with ", "Steven Strogatz"])
new_text.next_to(name_mob, DOWN)
self.play(*[
ShimmerIn(part)
for part in new_text.split()
])
self.dither()
with_word, steve = new_text.split()
steve_copy = steve.copy().center().to_edge(UP)
# logo.sort_points(lambda p : -np.linalg.norm(p))
sort_by_color(logo)
self.play(
Transform(steve, steve_copy),
DelayByOrder(Transform(logo, Point())),
FadeOut(with_word),
FadeOut(name_mob),
run_time = 3
)
class IntroduceSteve(Scene):
def construct(self):
name = TextMobject("Steven Strogatz")
name.to_edge(UP)
contributions = TextMobject("Frequent Contributions")
contributions.scale(0.5).to_edge(RIGHT).shift(2*UP)
books_word = TextMobject("Books")
books_word.scale(0.5).to_edge(LEFT).shift(2*UP)
radio_lab, sci_fri, cornell, book2, book3, book4 = [
ImageMobject(filename, invert = False, filter_color = WHITE)
for filename in [
"radio_lab",
"science_friday",
"cornell",
"strogatz_book2",
"strogatz_book3",
"strogatz_book4",
]
]
book1 = ImageMobject("strogatz_book1", invert = False)
nyt = ImageMobject("new_york_times")
logos = [radio_lab, nyt, sci_fri]
books = [book1, book2, book3, book4]
sample_size = Square(side_length = 2)
last = contributions
for image in logos:
image.replace(sample_size)
image.next_to(last, DOWN)
last = image
sci_fri.scale_in_place(0.9)
shift_val = 0
sample_size.scale(0.75)
for book in books:
book.replace(sample_size)
book.next_to(books_word, DOWN)
book.shift(shift_val*(RIGHT+DOWN))
shift_val += 0.5
sample_size.scale(2)
cornell.replace(sample_size)
cornell.next_to(name, DOWN)
self.add(name)
self.play(FadeIn(cornell))
self.play(ShimmerIn(books_word))
for book in books:
book.shift(5*LEFT)
self.play(ApplyMethod(book.shift, 5*RIGHT))
self.play(ShimmerIn(contributions))
for logo in logos:
self.play(FadeIn(logo))
self.dither()
class ShowTweets(Scene):
def construct(self):
tweets = [
ImageMobject("tweet%d"%x, invert = False)
for x in range(1, 4)
]
for tweet in tweets:
tweet.scale(0.4)
tweets[0].to_corner(UP+LEFT)
tweets[1].next_to(tweets[0], RIGHT, aligned_edge = UP)
tweets[2].next_to(tweets[1], DOWN)
self.play(GrowFromCenter(tweets[0]))
for x in 1, 2:
self.play(
Transform(Point(tweets[x-1].get_center()), tweets[x]),
Animation(tweets[x-1])
)
self.dither()
class LetsBeHonest(Scene):
def construct(self):
self.play(ShimmerIn(TextMobject("""
Let's be honest about who benefits
from this collaboration...
""")))
self.dither()
class WhatIsTheBrachistochrone(Scene):
def construct(self):
self.play(ShimmerIn(TextMobject("""
So \\dots what is the Brachistochrone?
""")))
self.dither()
class DisectBrachistochroneWord(Scene):
def construct(self):
word = TextMobject(["Bra", "chis", "to", "chrone"])
original_word = word.copy()
dots = []
for part in word.split():
if dots:
part.next_to(dots[-1], buff = 0.06)
dot = TexMobject("\\cdot")
dot.next_to(part, buff = 0.06)
dots.append(dot)
dots = Mobject(*dots[:-1])
dots.shift(0.1*DOWN)
Mobject(word, dots).center()
overbrace1 = Brace(Mobject(*word.split()[:-1]), UP)
overbrace2 = Brace(word.split()[-1], UP)
shortest = TextMobject("Shortest")
shortest.next_to(overbrace1, UP)
shortest.highlight(YELLOW)
time = TextMobject("Time")
time.next_to(overbrace2, UP)
time.highlight(YELLOW)
chrono_example = TextMobject("""
As in ``Chronological'' \\\\
or ``Synchronize''
""")
chrono_example.scale(0.5)
chrono_example.to_edge(RIGHT)
chrono_example.shift(2*UP)
chrono_example.highlight(BLUE_D)
chrono_arrow = Arrow(
word.get_right(),
chrono_example.get_bottom(),
color = BLUE_D
)
brachy_example = TextMobject("As in . . . brachydactyly?")
brachy_example.scale(0.5)
brachy_example.to_edge(LEFT)
brachy_example.shift(2*DOWN)
brachy_example.highlight(GREEN)
brachy_arrow = Arrow(
word.get_left(),
brachy_example.get_top(),
color = GREEN
)
pronunciation = TextMobject(["/br", "e", "kist","e","kr$\\bar{o}$n/"])
pronunciation.split()[1].rotate_in_place(np.pi)
pronunciation.split()[3].rotate_in_place(np.pi)
pronunciation.scale(0.7)
pronunciation.shift(DOWN)
latin = TextMobject(list("Latin"))
greek = TextMobject(list("Greek"))
for mob in latin, greek:
mob.to_edge(LEFT)
question_mark = TextMobject("?").next_to(greek, buff = 0.1)
stars = Stars().highlight(BLACK)
stars.scale(0.5).shift(question_mark.get_center())
self.play(Transform(original_word, word), ShowCreation(dots))
self.play(ShimmerIn(pronunciation))
self.dither()
self.play(
GrowFromCenter(overbrace1),
GrowFromCenter(overbrace2)
)
self.dither()
self.play(ShimmerIn(latin))
self.play(FadeIn(question_mark))
self.play(Transform(
latin, greek,
path_func = counterclockwise_path()
))
self.dither()
self.play(Transform(question_mark, stars))
self.remove(stars)
self.dither()
self.play(ShimmerIn(shortest))
self.play(ShimmerIn(time))
for ex, ar in [(chrono_example, chrono_arrow), (brachy_example, brachy_arrow)]:
self.play(
ShowCreation(ar),
ShimmerIn(ex)
)
self.dither()
class OneSolutionTwoInsights(Scene):
def construct(self):
one_solution = TextMobject(["One ", "solution"])
two_insights = TextMobject(["Two ", " insights"])
two, insights = two_insights.split()
johann = ImageMobject("Johann_Bernoulli2", invert = False)
mark = ImageMobject("Mark_Levi", invert = False)
for mob in johann, mark:
mob.scale(0.4)
johann.next_to(insights, LEFT)
mark.next_to(johann, RIGHT)
name = TextMobject("Mark Levi").to_edge(UP)
self.play(*map(ShimmerIn, one_solution.split()))
self.dither()
for pair in zip(one_solution.split(), two_insights.split()):
self.play(Transform(*pair, path_func = path_along_arc(np.pi)))
self.dither()
self.clear()
self.add(two, insights)
for word, man in [(two, johann), (insights, mark)]:
self.play(
Transform(word, Point(word.get_left())),
GrowFromCenter(man)
)
self.dither()
self.clear()
self.play(ApplyMethod(mark.center))
self.play(ShimmerIn(name))
self.dither()
class CircleOfIdeas(Scene):
def construct(self):
words = map(TextMobject, [
"optics", "calculus", "mechanics", "geometry", "history"
])
words[0].highlight(YELLOW)
words[1].highlight(BLUE_D)
words[2].highlight(GREY)
words[3].highlight(GREEN)
words[4].highlight(MAROON)
brachistochrone = TextMobject("Brachistochrone")
displayed_words = []
for word in words:
anims = self.get_spinning_anims(displayed_words)
word.shift(3*RIGHT)
point = Point()
anims.append(Transform(point, word))
self.play(*anims)
self.remove(point)
self.add(word)
displayed_words.append(word)
self.play(*self.get_spinning_anims(displayed_words))
self.play(*[
Transform(
word, word.copy().highlight(BLACK).center().scale(0.1),
path_func = path_along_arc(np.pi),
rate_func = None,
run_time = 2
)
for word in displayed_words
]+[
GrowFromCenter(brachistochrone)
])
self.dither()
def get_spinning_anims(self, words, angle = np.pi/6):
anims = []
for word in words:
old_center = word.get_center()
new_center = rotate_vector(old_center, angle)
vect = new_center-old_center
anims.append(ApplyMethod(
word.shift, vect,
path_func = path_along_arc(angle),
rate_func = None
))
return anims
class FermatsPrincipleStatement(Scene):
def construct(self):
words = TextMobject([
"Fermat's principle:",
"""
If a beam of light travels
from point $A$ to $B$, it does so along the
fastest path possible.
"""
])
words.split()[0].highlight(BLUE)
everything = MobjectFromRegion(Region())
everything.scale(0.9)
angles = np.apply_along_axis(
angle_of_vector, 1, everything.points
)
norms = np.apply_along_axis(
np.linalg.norm, 1, everything.points
)
norms -= np.min(norms)
norms /= np.max(norms)
alphas = 0.25 + 0.75 * norms * (1 + np.sin(12*angles))/2
everything.rgbs = alphas.repeat(3).reshape((len(alphas), 3))
Mobject(everything, words).show()
everything.sort_points(np.linalg.norm)
self.add(words)
self.play(
DelayByOrder(FadeIn(everything, run_time = 3)),
Animation(words)
)
self.play(
ApplyMethod(everything.highlight, WHITE),
)
self.dither()
class VideoProgression(Scene):
def construct(self):
spacing = 2*UP
brachy, optics, light_in_two, snells, multi = words = [
TextMobject(text)
for text in [
"Brachistochrone",
"Optics",
"Light in two media",
"Snell's Law",
"Multilayered glass",
]
]
for mob in light_in_two, snells:
mob.shift(-spacing)
arrow1 = Arrow(brachy, optics)
arrow2 = Arrow(optics, snells)
point = Point(DOWN)
self.play(ShimmerIn(brachy))
self.dither()
self.play(
ApplyMethod(brachy.shift, spacing),
Transform(point, optics)
)
optics = point
arrow1 = Arrow(optics, brachy)
self.play(ShowCreation(arrow1))
self.dither()
arrow2 = Arrow(light_in_two, optics)
self.play(
ShowCreation(arrow2),
ShimmerIn(light_in_two)
)
self.dither()
self.play(
FadeOut(light_in_two),
GrowFromCenter(snells),
DelayByOrder(
ApplyMethod(arrow2.highlight, BLUE_D)
)
)
self.dither()
self.play(
FadeOut(optics),
GrowFromCenter(multi),
DelayByOrder(
ApplyMethod(arrow1.highlight, BLUE_D)
)
)
self.dither()
class BalanceCompetingFactors(Scene):
args_list = [
("Short", "Steep"),
("Minimal time \\\\ in water", "Short path")
]
@staticmethod
def args_to_string(*words):
return "".join([word.split(" ")[0] for word in words])
def construct(self, *words):
factor1, factor2 = [
TextMobject("Factor %d"%x).highlight(c)
for x, c in [
(1, RED_D),
(2, BLUE_D)
]
]
real_factor1, real_factor2 = map(TextMobject, words)
for word in factor1, factor2, real_factor1, real_factor2:
word.shift(0.2*UP-word.get_bottom())
for f1 in factor1, real_factor1:
f1.highlight(RED_D)
f1.shift(2*LEFT)
for f2 in factor2, real_factor2:
f2.highlight(BLUE_D)
f2.shift(2*RIGHT)
line = Line(
factor1.get_left(),
factor2.get_right()
)
line.center()
self.balancers = Mobject(factor1, factor2, line)
self.hidden_balancers = Mobject(real_factor1, real_factor2)
triangle = Polygon(RIGHT, np.sqrt(3)*UP, LEFT)
triangle.next_to(line, DOWN, buff = 0)
self.add(triangle, self.balancers)
self.rotate(1)
self.rotate(-2)
self.dither()
self.play(Transform(
factor1, real_factor1,
path_func = path_along_arc(np.pi/4)
))
self.rotate(2)
self.dither()
self.play(Transform(
factor2, real_factor2,
path_func = path_along_arc(np.pi/4)
))
self.rotate(-2)
self.dither()
self.rotate(1)
def rotate(self, factor):
angle = np.pi/11
self.play(Rotate(
self.balancers,
factor*angle,
run_time = abs(factor)
))
self.hidden_balancers.rotate(factor*angle)
class Challenge(Scene):
def construct(self):
self.add(TextMobject("""
Can you find a new solution to the
Brachistochrone problem by finding
an intuitive reason that time-minimizing
curves look like straight lines in
$t$-$\\theta$ space?
"""))
self.dither()
class Section1(Scene):
def construct(self):
self.add(TextMobject("Section 1: Johann Bernoulli's insight"))
self.dither()
class Section2(Scene):
def construct(self):
self.add(TextMobject(
"Section 2: Mark Levi's insight, and a challenge",
size = "\\large"
))
self.dither()
class NarratorInterjection(Scene):
def construct(self):
words1 = TexMobject("<\\text{Narrator interjection}>")
words2 = TexMobject("<\\!/\\text{Narrator interjection}>")
self.add(words1)
self.dither()
self.clear()
self.add(words2)
self.dither()
class ThisCouldBeTheEnd(Scene):
def construct(self):
words = TextMobject([
"This could be the end\\dots",
"but\\dots"
])
for part in words.split():
self.play(ShimmerIn(part))
self.dither()
class MyOwnChallenge(Scene):
def construct(self):
self.add(TextMobject("My own challenge:"))
self.dither()
class WarmupChallenge(Scene):
def construct(self):
self.add(TextMobject("\\large Warm-up challenge: Confirm this for yourself"))
self.dither()
class FindAnotherSolution(Scene):
def construct(self):
self.add(TextMobject("Find another brachistochrone solution\\dots"))
self.dither()
class ProofOfSnellsLaw(Scene):
def construct(self):
self.add(TextMobject("Proof of Snell's law:"))
self.dither()
class CondensedVersion(Scene):
def construct(self):
snells = TextMobject("Snell's")
snells.shift(-snells.get_left())
snells.to_edge(UP)
for vect in [RIGHT, RIGHT, LEFT, DOWN, DOWN, DOWN]:
snells.add(snells.copy().next_to(snells, vect))
snells.ingest_submobjects()
snells.show()
condensed = TextMobject("condensed")
self.add(snells)
self.dither()
self.play(DelayByOrder(
Transform(snells, condensed, run_time = 2)
))
self.dither()

View File

View File

@ -0,0 +1,347 @@
from mobject import Mobject, Point, Mobject1D
from scene import Scene
from animation.transform import \
Transform, ShimmerIn, FadeIn, FadeOut, ApplyMethod
from animation.simple_animations import \
ShowCreation, DelayByOrder
from topics.geometry import Line, Arc, Arrow
from mobject.tex_mobject import TexMobject, TextMobject
from helpers import *
from hilbert.curves import *
class Intro(TransformOverIncreasingOrders):
@staticmethod
def args_to_string(*args):
return ""
@staticmethod
def string_to_args(string):
raise Exception("string_to_args Not Implemented!")
def construct(self):
words1 = TextMobject(
"If you watched my video about Hilbert's space-filling curve\\dots"
)
words2 = TextMobject(
"\\dots you might be curious to see what a few other space-filling curves look like."
)
words2.scale(0.8)
for words in words1, words2:
words.to_edge(UP, buff = 0.2)
self.setup(HilbertCurve)
self.play(ShimmerIn(words1))
for x in range(4):
self.increase_order()
self.remove(words1)
self.increase_order(
ShimmerIn(words2)
)
for x in range(4):
self.increase_order()
class BringInPeano(Intro):
def construct(self):
words1 = TextMobject("""
For each one, see if you can figure out what
the pattern of construction is.
""")
words2 = TextMobject("""
This one is the Peano curve.
""")
words3 = TextMobject("""
It is the original space-filling curve.
""")
self.setup(PeanoCurve)
self.play(ShimmerIn(words1))
self.dither(5)
self.remove(words1)
self.add(words2.to_edge(UP))
for x in range(3):
self.increase_order()
self.remove(words2)
self.increase_order(ShimmerIn(words3.to_edge(UP)))
for x in range(2):
self.increase_order()
class FillOtherShapes(Intro):
def construct(self):
words1 = TextMobject("""
But of course, there's no reason we should limit
ourselves to filling in squares.
""")
words2 = TextMobject("""
Here's a simple triangle-filling curve I defined
in a style reflective of a Hilbert curve.
""")
words1.to_edge(UP)
words2.scale(0.8).to_edge(UP, buff = 0.2)
self.setup(TriangleFillingCurve)
self.play(ShimmerIn(words1))
for x in range(3):
self.increase_order()
self.remove(words1)
self.add(words2)
for x in range(5):
self.increase_order()
class SmallerFlowSnake(FlowSnake):
CONFIG = {
"radius" : 4
}
class MostDelightfulName(Intro):
def construct(self):
words1 = TextMobject("""
This one has the most delightful name,
thanks to mathematician/programmer Bill Gosper:
""")
words2 = TextMobject("``Flow Snake''")
words3 = TextMobject("""
What makes this one particularly interesting
is that the boundary itself is a fractal.
""")
for words in words1, words2, words3:
words.to_edge(UP)
self.setup(SmallerFlowSnake)
self.play(ShimmerIn(words1))
for x in range(3):
self.increase_order()
self.remove(words1)
self.add(words2)
for x in range(3):
self.increase_order()
self.remove(words2)
self.play(ShimmerIn(words3))
class SurpriseFractal(Intro):
def construct(self):
words = TextMobject("""
It might come as a surprise how some well-known
fractals can be described with curves.
""")
words.to_edge(UP)
self.setup(Sierpinski)
self.add(TextMobject("Speaking of other fractals\\dots"))
self.dither(3)
self.clear()
self.play(ShimmerIn(words))
for x in range(9):
self.increase_order()
class IntroduceKoch(Intro):
def construct(self):
words = map(TextMobject, [
"This is another famous fractal.",
"The ``Koch Snowflake''",
"Let's finish things off by seeing how to turn \
this into a space-filling curve"
])
for text in words:
text.to_edge(UP)
self.setup(KochCurve)
self.add(words[0])
for x in range(3):
self.increase_order()
self.remove(words[0])
self.add(words[1])
for x in range(4):
self.increase_order()
self.remove(words[1])
self.add(words[2])
self.dither(6)
class StraightKoch(KochCurve):
CONFIG = {
"axiom" : "A"
}
class SharperKoch(StraightKoch):
CONFIG = {
"angle" : 0.9*np.pi/2,
}
class DullerKoch(StraightKoch):
CONFIG = {
"angle" : np.pi/6,
}
class SpaceFillingKoch(StraightKoch):
CONFIG = {
"angle" : np.pi/2,
}
class FromKochToSpaceFilling(Scene):
def construct(self):
self.max_order = 7
self.revisit_koch()
self.show_angles()
self.show_change_side_by_side()
def revisit_koch(self):
words = map(TextMobject, [
"First, look at how one section of this curve is made.",
"This pattern of four lines is the ``seed''",
"With each iteration, every straight line is \
replaced with an appropriately small copy of the seed",
])
for text in words:
text.to_edge(UP)
self.add(words[0])
curve = StraightKoch(order = self.max_order)
self.play(Transform(
curve,
StraightKoch(order = 1),
run_time = 5
))
self.remove(words[0])
self.add(words[1])
self.dither(4)
self.remove(words[1])
self.add(words[2])
self.dither(3)
for order in range(2, self.max_order):
self.play(Transform(
curve,
StraightKoch(order = order)
))
if order == 2:
self.dither(2)
elif order == 3:
self.dither()
self.clear()
def show_angles(self):
words = TextMobject("""
Let's see what happens as we change
the angle in this seed
""")
words.to_edge(UP)
koch, sharper_koch, duller_koch = curves = [
CurveClass(order = 1)
for CurveClass in StraightKoch, SharperKoch, DullerKoch
]
arcs = [
Arc(
2*(np.pi/2 - curve.angle),
radius = r,
start_angle = np.pi+curve.angle
).shift(curve.points[curve.get_num_points()/2])
for curve, r in zip(curves, [0.6, 0.7, 0.4])
]
theta = TexMobject("\\theta")
theta.shift(arcs[0].get_center()+2.5*DOWN)
arrow = Arrow(theta, arcs[0])
self.add(words, koch)
self.play(ShowCreation(arcs[0]))
self.play(
ShowCreation(arrow),
ShimmerIn(theta)
)
self.dither(2)
self.remove(theta, arrow)
self.play(
Transform(koch, duller_koch),
Transform(arcs[0], arcs[2]),
)
self.play(
Transform(koch, sharper_koch),
Transform(arcs[0], arcs[1]),
)
self.clear()
def show_change_side_by_side(self):
seed = TextMobject("Seed")
seed.shift(3*LEFT+2*DOWN)
fractal = TextMobject("Fractal")
fractal.shift(3*RIGHT+2*DOWN)
words = map(TextMobject, [
"A sharper angle results in a richer curve",
"A more obtuse angle gives a sparser curve",
"And as the angle approaches 0\\dots",
"We have a new space-filling curve."
])
for text in words:
text.to_edge(UP)
sharper, duller, space_filling = [
CurveClass(order = 1).shift(3*LEFT)
for CurveClass in SharperKoch, DullerKoch, SpaceFillingKoch
]
shaper_f, duller_f, space_filling_f = [
CurveClass(order = self.max_order).shift(3*RIGHT)
for CurveClass in SharperKoch, DullerKoch, SpaceFillingKoch
]
self.add(words[0])
left_curve = SharperKoch(order = 1)
right_curve = SharperKoch(order = 1)
self.play(
Transform(left_curve, sharper),
ApplyMethod(right_curve.shift, 3*RIGHT),
)
self.play(
Transform(
right_curve,
SharperKoch(order = 2).shift(3*RIGHT)
),
ShimmerIn(seed),
ShimmerIn(fractal)
)
for order in range(3, self.max_order):
self.play(Transform(
right_curve,
SharperKoch(order = order).shift(3*RIGHT)
))
self.remove(words[0])
self.add(words[1])
kwargs = {
"run_time" : 4,
}
self.play(
Transform(left_curve, duller, **kwargs),
Transform(right_curve, duller_f, **kwargs)
)
self.dither()
kwargs["run_time"] = 7
kwargs["rate_func"] = None
self.remove(words[1])
self.add(words[2])
self.play(
Transform(left_curve, space_filling, **kwargs),
Transform(right_curve, space_filling_f, **kwargs)
)
self.remove(words[2])
self.add(words[3])
self.dither()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,310 @@
from mobject import Mobject, Point
from mobject.tex_mobject import \
TexMobject, TextMobject, Brace
from mobject.image_mobject import \
ImageMobject, MobjectFromRegion
from scene import Scene
from animation import Animation
from animation.transform import \
Transform, CounterclockwiseTransform, ApplyMethod,\
GrowFromCenter, ClockwiseTransform, ApplyPointwiseFunction,\
TransformAnimations, ShimmerIn, FadeOut, FadeIn
from animation.simple_animations import \
ShowCreation, DelayByOrder
from animation.playground import Vibrate
from topics.geometry import \
Line, Dot, Arrow, Grid, Square, Point, Polygon
from topics.characters import \
ThoughtBubble, SpeechBubble, Mathematician, Mortimer
from topics.number_line import UnitInterval
from topics.three_dimensions import Stars
from mobject.region import region_from_polygon_vertices
import displayer as disp
from hilbert.curves import \
TransformOverIncreasingOrders, FlowSnake, HilbertCurve, \
SnakeCurve, Sierpinski
from hilbert.section1 import get_mathy_and_bubble
from helpers import *
class SectionThree(Scene):
def construct(self):
self.add(TextMobject("A few words on the usefulness of infinite math"))
self.dither()
class InfiniteResultsFiniteWorld(Scene):
def construct(self):
left_words = TextMobject("Infinite result")
right_words = TextMobject("Finite world")
for words in left_words, right_words:
words.scale(0.8)
left_formula = TexMobject(
"\\sum_{n = 0}^{\\infty} 2^n = -1"
)
right_formula = TexMobject("111\\cdots111")
for formula in left_formula, right_formula:
formula.add(
Brace(formula, UP),
)
formula.ingest_submobjects()
right_overwords = TextMobject(
"\\substack{\
\\text{How computers} \\\\ \
\\text{represent $-1$}\
}"
).scale(1.5)
left_mobs = [left_words, left_formula]
right_mobs = [right_words, right_formula]
for mob in left_mobs:
mob.to_edge(RIGHT, buff = 1)
mob.shift(SPACE_WIDTH*LEFT)
for mob in right_mobs:
mob.to_edge(LEFT, buff = 1)
mob.shift(SPACE_WIDTH*RIGHT)
arrow = Arrow(left_words, right_words)
right_overwords.next_to(right_formula, UP)
self.play(ShimmerIn(left_words))
self.play(ShowCreation(arrow))
self.play(ShimmerIn(right_words))
self.dither()
self.play(
ShimmerIn(left_formula),
ApplyMethod(left_words.next_to, left_formula, UP)
)
self.dither()
self.play(
ShimmerIn(right_formula),
Transform(right_words, right_overwords)
)
self.dither()
self.finite_analog(
Mobject(left_formula, left_words),
arrow,
Mobject(right_formula, right_words)
)
def finite_analog(self, left_mob, arrow, right_mob):
self.clear()
self.add(left_mob, arrow, right_mob)
ex = TextMobject("\\times")
ex.highlight(RED)
# ex.shift(arrow.get_center())
middle = TexMobject(
"\\sum_{n=0}^N 2^n \\equiv -1 \\mod 2^{N+1}"
)
finite_analog = TextMobject("Finite analog")
finite_analog.scale(0.8)
brace = Brace(middle, UP)
finite_analog.next_to(brace, UP)
new_left = left_mob.copy().to_edge(LEFT)
new_right = right_mob.copy().to_edge(RIGHT)
left_arrow, right_arrow = [
Arrow(
mob1.get_right()[0]*RIGHT,
mob2.get_left()[0]*RIGHT,
buff = 0
)
for mob1, mob2 in [
(new_left, middle),
(middle, new_right)
]
]
for mob in ex, middle:
mob.sort_points(np.linalg.norm)
self.play(GrowFromCenter(ex))
self.dither()
self.play(
Transform(left_mob, new_left),
Transform(arrow.copy(), left_arrow),
DelayByOrder(Transform(ex, middle)),
Transform(arrow, right_arrow),
Transform(right_mob, new_right)
)
self.play(
GrowFromCenter(brace),
ShimmerIn(finite_analog)
)
self.dither()
self.equivalence(
left_mob,
left_arrow,
Mobject(middle, brace, finite_analog)
)
def equivalence(self, left_mob, arrow, right_mob):
self.clear()
self.add(left_mob, arrow, right_mob)
words = TextMobject("is equivalent to")
words.shift(0.25*LEFT)
words.highlight(BLUE)
new_left = left_mob.copy().shift(RIGHT)
new_right = right_mob.copy()
new_right.shift(
(words.get_right()[0]-\
right_mob.get_left()[0]+\
0.5
)*RIGHT
)
for mob in arrow, words:
mob.sort_points(np.linalg.norm)
self.play(
ApplyMethod(left_mob.shift, RIGHT),
Transform(arrow, words),
ApplyMethod(right_mob.to_edge, RIGHT)
)
self.dither()
class HilbertCurvesStayStable(Scene):
def construct(self):
scale_factor = 0.9
grid = Grid(4, 4, stroke_width = 1)
curve = HilbertCurve(order = 2)
for mob in grid, curve:
mob.scale(scale_factor)
words = TextMobject("""
Sequence of curves is stable
$\\leftrightarrow$ existence of limit curve
""", size = "\\normal")
words.scale(1.25)
words.to_edge(UP)
self.add(curve, grid)
self.dither()
for n in range(3, 7):
if n == 5:
self.play(ShimmerIn(words))
new_grid = Grid(2**n, 2**n, stroke_width = 1)
new_curve = HilbertCurve(order = n)
for mob in new_grid, new_curve:
mob.scale(scale_factor)
self.play(
ShowCreation(new_grid),
Animation(curve)
)
self.remove(grid)
grid = new_grid
self.play(Transform(curve, new_curve))
self.dither()
class InfiniteObjectsEncapsulateFiniteObjects(Scene):
def get_triangles(self):
triangle = Polygon(
LEFT/np.sqrt(3),
UP,
RIGHT/np.sqrt(3),
color = GREEN
)
triangles = Mobject(
triangle.copy().scale(0.5).shift(LEFT),
triangle,
triangle.copy().scale(0.3).shift(0.5*UP+RIGHT)
)
triangles.center()
return triangles
def construct(self):
words =[
TextMobject(text, size = "\\large")
for text in [
"Truths about infinite objects",
" encapsulate ",
"facts about finite objects"
]
]
words[0].highlight(RED)
words[1].next_to(words[0])
words[2].highlight(GREEN).next_to(words[1])
Mobject(*words).center().to_edge(UP)
infinite_objects = [
TexMobject(
"\\sum_{n=0}^\\infty",
size = "\\normal"
).highlight(RED_E),
Sierpinski(order = 8).scale(0.3),
TextMobject(
"$\\exists$ something infinite $\\dots$"
).highlight(RED_B)
]
finite_objects = [
TexMobject(
"\\sum_{n=0}^N",
size = "\\normal"
).highlight(GREEN_E),
self.get_triangles(),
TextMobject(
"$\\forall$ finite somethings $\\dots$"
).highlight(GREEN_B)
]
for infinite, finite, n in zip(infinite_objects, finite_objects, it.count(1, 2)):
infinite.next_to(words[0], DOWN, buff = n)
finite.next_to(words[2], DOWN, buff = n)
self.play(ShimmerIn(words[0]))
self.dither()
self.play(ShimmerIn(infinite_objects[0]))
self.play(ShowCreation(infinite_objects[1]))
self.play(ShimmerIn(infinite_objects[2]))
self.dither()
self.play(ShimmerIn(words[1]), ShimmerIn(words[2]))
self.play(ShimmerIn(finite_objects[0]))
self.play(ShowCreation(finite_objects[1]))
self.play(ShimmerIn(finite_objects[2]))
self.dither()
class StatementRemovedFromReality(Scene):
def construct(self):
mathy, bubble = get_mathy_and_bubble()
bubble.stretch_to_fit_width(4)
mathy.to_corner(DOWN+LEFT)
bubble.pin_to(mathy)
bubble.shift(LEFT)
morty = Mortimer()
morty.to_corner(DOWN+RIGHT)
morty_bubble = SpeechBubble()
morty_bubble.stretch_to_fit_width(4)
morty_bubble.pin_to(morty)
bubble.write("""
Did you know a curve \\\\
can fill all space?
""")
morty_bubble.write("Who cares?")
self.add(mathy, morty)
for bub, buddy in [(bubble, mathy), (morty_bubble, morty)]:
self.play(Transform(
Point(bub.get_tip()),
bub
))
self.play(ShimmerIn(bub.content))
self.play(ApplyMethod(
buddy.blink,
rate_func = squish_rate_func(there_and_back)
))

View File

@ -0,0 +1,83 @@
class NumberLineScene(Scene):
def construct(self, **number_line_config):
self.number_line = NumberLine(**number_line_config)
self.displayed_numbers = self.number_line.default_numbers_to_display()
self.number_mobs = self.number_line.get_number_mobjects(*self.displayed_numbers)
self.add(self.number_line, *self.number_mobs)
def zoom_in_on(self, number, zoom_factor, run_time = 2.0):
unit_length_to_spatial_width = self.number_line.unit_length_to_spatial_width*zoom_factor
radius = SPACE_WIDTH/unit_length_to_spatial_width
tick_frequency = 10**(np.floor(np.log10(radius)))
left_tick = tick_frequency*(np.ceil((number-radius)/tick_frequency))
new_number_line = NumberLine(
numerical_radius = radius,
unit_length_to_spatial_width = unit_length_to_spatial_width,
tick_frequency = tick_frequency,
leftmost_tick = left_tick,
number_at_center = number
)
new_displayed_numbers = new_number_line.default_numbers_to_display()
new_number_mobs = new_number_line.get_number_mobjects(*new_displayed_numbers)
transforms = []
additional_mobjects = []
squished_new_line = new_number_line.copy()
squished_new_line.scale(1.0/zoom_factor)
squished_new_line.shift(self.number_line.number_to_point(number))
squished_new_line.points[:,1] = self.number_line.number_to_point(0)[1]
transforms.append(Transform(squished_new_line, new_number_line))
for mob, num in zip(new_number_mobs, new_displayed_numbers):
point = Point(self.number_line.number_to_point(num))
point.shift(new_number_line.get_vertical_number_offset())
transforms.append(Transform(point, mob))
for mob in self.mobjects:
if mob == self.number_line:
new_mob = mob.copy()
new_mob.shift(-self.number_line.number_to_point(number))
new_mob.stretch(zoom_factor, 0)
transforms.append(Transform(mob, new_mob))
continue
mob_center = mob.get_center()
number_under_center = self.number_line.point_to_number(mob_center)
new_point = new_number_line.number_to_point(number_under_center)
new_point += mob_center[1]*UP
if mob in self.number_mobs:
transforms.append(Transform(mob, Point(new_point)))
else:
transforms.append(ApplyMethod(mob.shift, new_point - mob_center))
additional_mobjects.append(mob)
line_to_hide_pixelation = Line(
self.number_line.get_left(),
self.number_line.get_right(),
color = self.number_line.get_color()
)
self.add(line_to_hide_pixelation)
self.play(*transforms, run_time = run_time)
self.clear()
self.number_line = new_number_line
self.displayed_numbers = new_displayed_numbers
self.number_mobs = new_number_mobs
self.add(self.number_line, *self.number_mobs)
self.add(*additional_mobjects)
def show_multiplication(self, num, **kwargs):
if "path_func" not in kwargs:
if num > 0:
kwargs["path_func"] = straight_path
else:
kwargs["path_func"] = 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
])