3b1b-manim/brachistochrone/light.py
2016-03-15 20:03:23 -07:00

1100 lines
36 KiB
Python

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 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_sub_mobjects()
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_sub_mobjects()
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_sub_mobjects()
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_sub_mobjects()
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_sub_mobjects()
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_sub_mobjects()
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_sub_mobjects()
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 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):
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_sub_mobjects()
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 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_sub_mobjects()
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.slide_system(ring)
self.balance_forces(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):
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])
for spring in top_spring, bottom_spring:
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()
for force in top_force, bottom_force:
self.play(GrowFromCenter(force))
self.dither()
self.remove(top_force, bottom_force)
def slide_system(self, ring):
equilibrium_slide_kwargs = dict(self.slide_kwargs)
def jiggle_to_equilibrium(t):
return 0.6*(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 balance_forces(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(SPACE_HEIGHT*UP, SPACE_HEIGHT*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))
self.dither()
for arc, theta in zip(arcs, thetas):
self.play(ShowCreation(arc))
self.play(GrowFromCenter(theta))
self.dither()
def show_equation(self):
equation = TexMobject([
"F_1", "\\sin(\\theta_1)", "=",
"F_2", "\\sin(\\theta_2)"
])
equation.shift(3*RIGHT+2*UP)
f1, sin1, equals, f2, sin2 = equation.split()
bar1 = TexMobject("\\dfrac{\\qquad}{\\qquad}")
bar2 = bar1.copy()
v_air, v_water = [
TexMobject("v_{\\text{%s}}"%s, size = "\\Large")
for s in "air", "water"
]
bar1.next_to(sin1, DOWN)
v_air.next_to(bar1, DOWN)
bar2.next_to(sin2, DOWN)
v_water.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())
snells.to_edge(UP)
for mob in equation.split():
self.play(GrowFromCenter(mob, run_time = 0.5))
self.dither()
self.play(
Transform(f1, v_air),
Transform(f2, v_water),
ShowCreation(bars),
Transform(equals, new_eq)
)
self.dither()
self.play(ShimmerIn(snells))
self.dither()