Starting multilayered scenes

This commit is contained in:
Grant Sanderson 2016-03-17 23:54:42 -07:00
parent b7e53a0e8b
commit 6bb3a2c181
6 changed files with 929 additions and 373 deletions

View file

@ -144,17 +144,20 @@ class BrachistochroneWordSliding(Scene):
class PathSlidingScene(Scene): class PathSlidingScene(Scene):
CONFIG = { CONFIG = {
"gravity" : 3, "gravity" : 3,
"delta_t" : 0.05 "delta_t" : 0.05,
"dither_and_add" : True,
"show_time" : True,
} }
def slide(self, mobject, path, roll = False): def slide(self, mobject, path, roll = False, ceiling = None):
points = path.points points = path.points
time_slices = self.get_time_slices(points) time_slices = self.get_time_slices(points, ceiling = ceiling)
curr_t = 0 curr_t = 0
last_index = 0 last_index = 0
curr_index = 1 curr_index = 1
self.t_equals = TexMobject("t = ") if self.show_time:
self.t_equals.shift(3.5*UP+4*RIGHT) self.t_equals = TexMobject("t = ")
self.add(self.t_equals) self.t_equals.shift(3.5*UP+4*RIGHT)
self.add(self.t_equals)
while curr_index < len(points): while curr_index < len(points):
self.slider = mobject.copy() self.slider = mobject.copy()
self.adjust_mobject_to_index( self.adjust_mobject_to_index(
@ -166,7 +169,8 @@ class PathSlidingScene(Scene):
) )
self.roll(mobject, distance) self.roll(mobject, distance)
self.add(self.slider) self.add(self.slider)
self.write_time(curr_t) if self.show_time:
self.write_time(curr_t)
self.dither(self.frame_duration) self.dither(self.frame_duration)
self.remove(self.slider) self.remove(self.slider)
curr_t += self.delta_t curr_t += self.delta_t
@ -175,17 +179,22 @@ class PathSlidingScene(Scene):
curr_index += 1 curr_index += 1
if curr_index == len(points): if curr_index == len(points):
break break
self.add(self.slider) if self.dither_and_add:
self.dither() self.add(self.slider)
self.dither()
else:
return self.slider
def get_time_slices(self, points): def get_time_slices(self, points, ceiling = None):
dt_list = np.zeros(len(points)) dt_list = np.zeros(len(points))
ds_list = np.apply_along_axis( ds_list = np.apply_along_axis(
np.linalg.norm, np.linalg.norm,
1, 1,
points[1:]-points[:-1] points[1:]-points[:-1]
) )
delta_y_list = np.abs(points[0, 1] - points[1:,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) delta_y_list += 0.001*(delta_y_list == 0)
v_list = self.gravity*np.sqrt(delta_y_list) v_list = self.gravity*np.sqrt(delta_y_list)
dt_list[1:] = ds_list / v_list dt_list[1:] = ds_list / v_list
@ -257,7 +266,7 @@ class TryManyPaths(PathSlidingScene):
self.slide(randy, curr_path) self.slide(randy, curr_path)
self.clear() self.clear()
self.add(point_a, point_b, A, B, curr_path) self.add(point_a, point_b, A, B, curr_path)
text = TextMobject("Which path is fastest?") text = self.get_text()
text.to_edge(UP) text.to_edge(UP)
self.play(ShimmerIn(text)) self.play(ShimmerIn(text))
for path in paths: for path in paths:
@ -267,6 +276,9 @@ class TryManyPaths(PathSlidingScene):
run_time = 3 run_time = 3
)) ))
def get_text(self):
return TextMobject("Which path is fastest?")
def get_paths(self): def get_paths(self):
sharp_corner = Mobject( sharp_corner = Mobject(
Line(3*UP+LEFT, LEFT), Line(3*UP+LEFT, LEFT),
@ -298,7 +310,7 @@ class TryManyPaths(PathSlidingScene):
def align_paths(self, paths, target_path): def align_paths(self, paths, target_path):
start = target_path.points[0] start = target_path.points[0]
end = target_path.point[-1] end = target_path.points[-1]
for path in paths: for path in paths:
path.position_endpoints_on(start, end) path.position_endpoints_on(start, end)
@ -450,6 +462,140 @@ class MinimalPotentialEnergy(Scene):
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)

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

@ -21,7 +21,9 @@ from mobject.region import Region, region_from_polygon_vertices
from scene import Scene from scene import Scene
from scene.zoomed_scene import ZoomedScene from scene.zoomed_scene import ZoomedScene
from brachistochrone.curves import Cycloid from brachistochrone.curves import \
Cycloid, PathSlidingScene, RANDY_SCALE_VAL, TryManyPaths
class Lens(Arc): class Lens(Arc):
CONFIG = { CONFIG = {
@ -353,325 +355,6 @@ class ShowMultiplePathsInWater(ShowMultiplePathsScene):
return result return result
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)
class StraightLinesFastestInConstantMedium(PhotonScene): class StraightLinesFastestInConstantMedium(PhotonScene):
def construct(self): def construct(self):
kwargs = {"size" : "\\Large"} kwargs = {"size" : "\\Large"}
@ -928,8 +611,10 @@ class SpringSetup(ShowMultiplePathsInWater):
self.slide_ring(ring) self.slide_ring(ring)
self.dither() self.dither()
self.add_springs() self.add_springs()
self.add_force_definitions()
self.slide_system(ring) self.slide_system(ring)
self.balance_forces(ring) self.show_horizontal_component(ring)
self.show_angles(ring)
self.show_equation() self.show_equation()
@ -973,14 +658,8 @@ class SpringSetup(ShowMultiplePathsInWater):
)) ))
def add_springs(self): def add_springs(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)
colors = iter([BLACK, BLUE_E]) colors = iter([BLACK, BLUE_E])
for spring in top_spring, bottom_spring: for spring in self.start_springs.split():
circle = Circle(color = colors.next()) circle = Circle(color = colors.next())
circle.reverse_points() circle.reverse_points()
circle.scale(spring.loop_radius) circle.scale(spring.loop_radius)
@ -991,16 +670,28 @@ class SpringSetup(ShowMultiplePathsInWater):
self.add(spring) self.add(spring)
self.dither() 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: for force in top_force, bottom_force:
self.play(GrowFromCenter(force)) self.play(GrowFromCenter(force))
self.dither() self.dither()
self.remove(top_force, bottom_force) self.play(ShimmerIn(words))
self.dither(3)
self.remove(top_force, bottom_force, words)
def slide_system(self, ring): def slide_system(self, ring):
equilibrium_slide_kwargs = dict(self.slide_kwargs) equilibrium_slide_kwargs = dict(self.slide_kwargs)
def jiggle_to_equilibrium(t): def jiggle_to_equilibrium(t):
return 0.6*(1+((1-t)**2)*(-np.cos(10*np.pi*t))) return 0.7*(1+((1-t)**2)*(-np.cos(10*np.pi*t)))
equilibrium_slide_kwargs = { equilibrium_slide_kwargs = {
"rate_func" : jiggle_to_equilibrium, "rate_func" : jiggle_to_equilibrium,
"run_time" : 3 "run_time" : 3
@ -1013,8 +704,15 @@ class SpringSetup(ShowMultiplePathsInWater):
for kwargs in self.slide_kwargs, equilibrium_slide_kwargs: for kwargs in self.slide_kwargs, equilibrium_slide_kwargs:
self.play(Transform(start, end, **kwargs)) self.play(Transform(start, end, **kwargs))
self.dither() 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 balance_forces(self, ring): def show_angles(self, ring):
ring_center = ring.get_center() ring_center = ring.get_center()
lines, arcs, thetas = [], [], [] lines, arcs, thetas = [], [], []
counter = it.count(1) counter = it.count(1)
@ -1033,7 +731,7 @@ class SpringSetup(ShowMultiplePathsInWater):
lines.append(line) lines.append(line)
arcs.append(arc) arcs.append(arc)
thetas.append(theta) thetas.append(theta)
vert_line = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) vert_line = Line(2*UP, 2*DOWN)
vert_line.shift(ring_center) vert_line.shift(ring_center)
top_spring, bottom_spring = self.start_springs.split() top_spring, bottom_spring = self.start_springs.split()
@ -1043,50 +741,122 @@ class SpringSetup(ShowMultiplePathsInWater):
Transform(bottom_spring, lines[1]) Transform(bottom_spring, lines[1])
) )
self.play(ShowCreation(vert_line)) self.play(ShowCreation(vert_line))
self.dither() anims = []
for arc, theta in zip(arcs, thetas): for arc, theta in zip(arcs, thetas):
self.play(ShowCreation(arc)) anims += [
self.play(GrowFromCenter(theta)) ShowCreation(arc),
self.dither() GrowFromCenter(theta)
]
self.play(*anims)
self.dither()
def show_equation(self): def show_equation(self):
equation = TexMobject([ equation = TexMobject([
"F_1", "\\sin(\\theta_1)", "=", "\\left(\\dfrac{1}{\\phantom{v_air}}\\right)",
"F_2", "\\sin(\\theta_2)" "\\sin(\\theta_1)",
"=",
"\\left(\\dfrac{1}{\\phantom{v_water}}\\right)",
"\\sin(\\theta_2)"
]) ])
equation.shift(3*RIGHT+2*UP) equation.to_corner(UP+RIGHT)
f1, sin1, equals, f2, sin2 = equation.split() frac1, sin1, equals, frac2, sin2 = equation.split()
bar1 = TexMobject("\\dfrac{\\qquad}{\\qquad}")
bar2 = bar1.copy()
v_air, v_water = [ v_air, v_water = [
TexMobject("v_{\\text{%s}}"%s, size = "\\Large") TexMobject("v_{\\text{%s}}"%s, size = "\\Large")
for s in "air", "water" 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) bar1.next_to(sin1, DOWN)
v_air.next_to(bar1, DOWN) bar2.next_to(sin2, DOWN)
bar2.next_to(sin2, DOWN) v_air_copy = v_air.copy().next_to(bar1, DOWN)
v_water.next_to(bar2, DOWN) v_water_copy = v_water.copy().next_to(bar2, DOWN)
bars = Mobject(bar1, bar2) bars = Mobject(bar1, bar2)
new_eq = equals.copy().center().shift(bars.get_center()) new_eq = equals.copy().center().shift(bars.get_center())
snells = TextMobject("Snell's Law") snells = TextMobject("Snell's Law")
snells.highlight(YELLOW) snells.highlight(YELLOW)
snells.shift(new_eq.get_center()) snells.shift(new_eq.get_center()[0]*RIGHT)
snells.to_edge(UP) snells.shift(UP)
anims = []
for mob in equation.split(): for mob in f1, sin1, equals, f2, sin2_start:
self.play(GrowFromCenter(mob, run_time = 0.5)) anims.append(ShimmerIn(mob))
self.play(*anims)
self.dither()
for f, frac in (f1, frac1), (f2, frac2):
target = frac.copy().ingest_sub_mobjects()
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.dither()
self.play( self.play(
Transform(f1, v_air), FadeOut(frac1),
Transform(f2, v_water), FadeOut(frac2),
Transform(v_air, v_air_copy),
Transform(v_water, v_water_copy),
ShowCreation(bars), ShowCreation(bars),
Transform(equals, new_eq) Transform(equals, new_eq)
) )
self.dither() 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.play(ShimmerIn(snells))
self.dither() 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)

View file

@ -99,24 +99,29 @@ class TimeLine(Scene):
self.add(timeline) self.add(timeline)
self.dither() self.dither()
run_times = iter([3, 1])
for point, event in zip(centers[1:], dated_events): for point, event in zip(centers[1:], dated_events):
self.play(ApplyMethod( self.play(ApplyMethod(
timeline.shift, -point.get_center(), timeline.shift, -point.get_center(),
run_time = 3 run_time = run_times.next()
)) ))
picture = ImageMobject(event["picture"], invert = False) picture = ImageMobject(event["picture"], invert = False)
picture.scale_to_fit_width(2) picture.scale_to_fit_width(2)
picture.to_corner(UP+RIGHT) picture.to_corner(UP+RIGHT)
event_mob = TextMobject(event["text"]) event_mob = TextMobject(event["text"])
event_mob.shift(2*LEFT+2*UP) event_mob.shift(2*LEFT+2*UP)
arrow = Arrow(event_mob.get_bottom(), ORIGIN) 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( self.play(
ShimmerIn(event_mob), ShimmerIn(event_mob),
ShowCreation(arrow) ShowCreation(line),
ShimmerIn(date_mob)
) )
self.play(FadeIn(picture)) self.play(FadeIn(picture))
self.dither() self.dither(3)
self.play(*map(FadeOut, [event_mob, arrow, picture])) self.play(*map(FadeOut, [event_mob, date_mob, line, picture]))

View file

@ -0,0 +1,293 @@
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 *
#Two to many
#race light in each
#n layers
#v_1, v_2, v_3
#proportional to sqrt y_1, y_2, y_3
#limiting process
#show sliding object and light
#which path is fastest
#instantaneously obey snell's law
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 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)
]
aligned_layer_sets = [
Mobject(*[
Mobject(
*layer_sets[x][(2**x)*index:(2**x)*(index+1)]
).ingest_sub_mobjects()
for index in range(self.n_layers)
])
for x in range(num_iterations)
]
aligned_layer_sets.append(self.get_continuous_glass())
curr_set = aligned_layer_sets[0]
self.add(curr_set)
for layer_set in aligned_layer_sets[1:]:
self.dither()
self.play(Transform(curr_set, layer_set))
self.dither()

View file

@ -1,9 +1,7 @@
import numpy as np import numpy as np
import itertools as it
import operator as op import operator as op
import os import os
from PIL import Image from PIL import Image
from random import random
from copy import deepcopy from copy import deepcopy
from colour import Color from colour import Color