mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Messy pile of brachistochrone work
This commit is contained in:
parent
9262c395ce
commit
19e3c7f849
6 changed files with 627 additions and 424 deletions
|
@ -153,29 +153,6 @@ class PathSlidingScene(Scene):
|
|||
"gravity" : 3,
|
||||
"delta_t" : 0.05
|
||||
}
|
||||
def get_time_slices(self, points):
|
||||
dt_list = np.zeros(len(points))
|
||||
ds_list = np.apply_along_axis(
|
||||
np.linalg.norm,
|
||||
1,
|
||||
points[1:]-points[:-1]
|
||||
)
|
||||
delta_y_list = np.abs(points[0, 1] - 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])
|
||||
return mobject
|
||||
|
||||
def slide(self, mobject, path, roll = False):
|
||||
points = path.points
|
||||
time_slices = self.get_time_slices(points)
|
||||
|
@ -208,6 +185,29 @@ class PathSlidingScene(Scene):
|
|||
self.add(self.slider)
|
||||
self.dither()
|
||||
|
||||
def get_time_slices(self, points):
|
||||
dt_list = np.zeros(len(points))
|
||||
ds_list = np.apply_along_axis(
|
||||
np.linalg.norm,
|
||||
1,
|
||||
points[1:]-points[:-1]
|
||||
)
|
||||
delta_y_list = np.abs(points[0, 1] - 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])
|
||||
return mobject
|
||||
|
||||
def write_time(self, time):
|
||||
if hasattr(self, "time_mob"):
|
||||
self.remove(self.time_mob)
|
||||
|
@ -223,6 +223,16 @@ class PathSlidingScene(Scene):
|
|||
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()
|
||||
|
@ -271,17 +281,17 @@ class TryManyPaths(PathSlidingScene):
|
|||
Line(DOWN, DOWN+3*RIGHT)
|
||||
).ingest_sub_mobjects().highlight(GREEN)
|
||||
paths = [
|
||||
Line(7*LEFT, 7*RIGHT, color = RED_D),
|
||||
LoopTheLoop(),
|
||||
FunctionGraph(
|
||||
lambda x : 0.05*(x**2)+0.1*np.sin(2*x)
|
||||
),
|
||||
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,
|
||||
|
@ -294,40 +304,60 @@ class TryManyPaths(PathSlidingScene):
|
|||
return paths + [cycloid]
|
||||
|
||||
def align_paths(self, paths, target_path):
|
||||
def path_displacement(path):
|
||||
return path.points[-1]-path.points[0]
|
||||
target = path_displacement(target_path)
|
||||
start = target_path.points[0]
|
||||
end = target_path.point[-1]
|
||||
for path in paths:
|
||||
vect = path_displacement(path)
|
||||
path.scale(np.linalg.norm(target)/np.linalg.norm(vect))
|
||||
path.rotate(
|
||||
angle_of_vector(target) - \
|
||||
angle_of_vector(vect)
|
||||
)
|
||||
path.shift(target_path.points[0]-path.points[0])
|
||||
path.position_endpoints_on(start, end)
|
||||
|
||||
|
||||
class RollingRandolph(PathSlidingScene):
|
||||
def construct(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)
|
||||
randy = Randolph()
|
||||
randy.scale(RANDY_SCALE_VAL)
|
||||
randy.shift(-randy.get_bottom())
|
||||
self.add_cycloid_end_points()
|
||||
self.slide(randy, self.cycloid, roll = True)
|
||||
|
||||
self.add(point_a, point_b, A, B, cycloid)
|
||||
self.slide(randy, 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()
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import numpy as np
|
||||
import itertools as it
|
||||
import operator as op
|
||||
|
@ -17,16 +15,17 @@ from mobject.tex_mobject import TexMobject
|
|||
from mobject import Mobject
|
||||
from mobject.image_mobject import \
|
||||
MobjectFromRegion, ImageMobject, MobjectFromPixelArray
|
||||
from mobject.tex_mobject import TextMobject, TexMobject
|
||||
|
||||
from animation.transform import \
|
||||
Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\
|
||||
FadeIn, FadeOut, GrowFromCenter
|
||||
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
|
||||
from topics.characters import Randolph, Mathematician, ThoughtBubble
|
||||
from topics.functions import ParametricFunction
|
||||
from topics.number_line import NumberPlane
|
||||
from region import Region, region_from_polygon_vertices
|
||||
|
@ -134,7 +133,7 @@ class TracePicture(Scene):
|
|||
("Steven_Strogatz",),
|
||||
("Pierre_de_Fermat",),
|
||||
("Galileo_Galilei",),
|
||||
("Jakob_Bernoulli",),
|
||||
("Jacob_Bernoulli",),
|
||||
("Johann_Bernoulli2",),
|
||||
]
|
||||
|
||||
|
@ -191,16 +190,110 @@ class TracePicture(Scene):
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class JohannThinksHeIsBetter(Scene):
|
||||
def construct(self):
|
||||
names = [
|
||||
"Johann_Bernoulli2",
|
||||
"Jacob_Bernoulli",
|
||||
"Gottfried_Wilhelm_von_Leibniz",
|
||||
]
|
||||
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_sub_mobjects()
|
||||
bubble.pin_to(pensive_johann)
|
||||
bubble.shift(DOWN)
|
||||
point = Point(johann.get_corner(UP+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, guy)),
|
||||
ShimmerIn(guy.name_mob)
|
||||
)
|
||||
self.dither(3)
|
||||
self.remove(guy.name_mob)
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
|
@ -4,7 +4,7 @@ import itertools as it
|
|||
from helpers import *
|
||||
|
||||
from mobject.tex_mobject import TexMobject, TextMobject, Brace
|
||||
from mobject import Mobject
|
||||
from mobject import Mobject, Mobject1D
|
||||
from mobject.image_mobject import \
|
||||
MobjectFromRegion, ImageMobject, MobjectFromPixelArray
|
||||
from topics.three_dimensions import Stars
|
||||
|
@ -237,7 +237,7 @@ class ShowMultiplePathsThroughLens(ShowMultiplePathsScene):
|
|||
self.end_point = 2*RIGHT
|
||||
|
||||
def get_paths(self):
|
||||
alphas = [0.2, 0.45, 0.55, 0.8]
|
||||
alphas = [0.25, 0.4, 0.58, 0.75]
|
||||
lower_right, upper_right, upper_left, lower_left = map(
|
||||
self.lens.point_from_proportion, alphas
|
||||
)
|
||||
|
@ -306,6 +306,219 @@ class ShowMultiplePathsInGlass(ShowMultiplePathsScene):
|
|||
]
|
||||
|
||||
|
||||
class MultilayeredGlass(PhotonScene):
|
||||
DEFAULT_CONFIG = {
|
||||
"num_discrete_layers" : 5,
|
||||
"num_variables" : 3,
|
||||
"top_color" : BLUE_E,
|
||||
"bottom_color" : BLUE_A,
|
||||
}
|
||||
def construct(self):
|
||||
self.cycloid = Cycloid(end_theta = np.pi)
|
||||
self.top = self.cycloid.get_top()[1]
|
||||
self.bottom = self.cycloid.get_bottom()[1]-1
|
||||
self.generate_layer_regions()
|
||||
self.generate_discrete_path()
|
||||
photon_run = self.photon_run_along_path(
|
||||
self.augmented_path,
|
||||
run_time = 1,
|
||||
rate_func = rush_into
|
||||
)
|
||||
|
||||
# self.continuous_to_smooth()
|
||||
self.paint_layers()
|
||||
self.show_layer_variables()
|
||||
self.play(photon_run)
|
||||
self.play(ShowCreation(self.discrete_path))
|
||||
self.isolate_bend_points()
|
||||
# self.dither()
|
||||
|
||||
def continuous_to_smooth(self):
|
||||
continuous = self.get_continuous_background()
|
||||
layers = Mobject(*[
|
||||
MobjectFromRegion(region, color)
|
||||
for region, color in zip(
|
||||
self.layer_regions, self.layer_colors
|
||||
)
|
||||
])
|
||||
layers.ingest_sub_mobjects()
|
||||
|
||||
self.play(FadeIn(continuous))
|
||||
self.play(Transform(continuous, layers))
|
||||
self.remove(continuous)
|
||||
self.paint_layers()
|
||||
self.dither()
|
||||
|
||||
def paint_layers(self):
|
||||
# for region, color in zip(self.layer_regions, self.layer_colors):
|
||||
# self.highlight_region(region, color)
|
||||
for top, color in zip(self.layer_tops, self.layer_colors):
|
||||
self.add(Line(
|
||||
SPACE_WIDTH*LEFT+top*UP, SPACE_WIDTH*RIGHT+top*UP,
|
||||
color = color
|
||||
))
|
||||
|
||||
def get_continuous_background(self):
|
||||
glass = MobjectFromRegion(Region(
|
||||
lambda x, y : (y < self.top) & (y > self.bottom)
|
||||
))
|
||||
glass.gradient_highlight(self.top_color, self.bottom_color)
|
||||
glass.scale_in_place(0.99)
|
||||
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_layer_regions(self):
|
||||
self.generate_layer_info()
|
||||
self.layer_regions = [
|
||||
Region(lambda x, y : (y < top) & (y > top-self.layer_thickness))
|
||||
for top in self.layer_tops
|
||||
]
|
||||
|
||||
def generate_discrete_path(self):
|
||||
points = self.cycloid.points
|
||||
indices = [
|
||||
np.argmin(np.abs(points[:, 1]-top))
|
||||
for top in self.layer_tops
|
||||
]
|
||||
self.bend_points = points[indices[1:-1]]
|
||||
self.discrete_path = Mobject1D(color = YELLOW)
|
||||
for start, end in zip(indices, indices[1:]):
|
||||
self.discrete_path.add_line(
|
||||
points[start], points[end]
|
||||
)
|
||||
self.augmented_path = self.discrete_path.copy()
|
||||
self.augmented_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):
|
||||
start_y.highlight(BLACK)
|
||||
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):
|
||||
little_square = Square(side_length = 4, color = WHITE)
|
||||
little_square.scale(0.25)
|
||||
little_square.shift(self.bend_points[0])
|
||||
big_square = little_square.copy()
|
||||
big_square.scale(4)
|
||||
big_square.to_corner(UP+RIGHT)
|
||||
|
||||
|
||||
first_time = True
|
||||
for bend_point in self.bend_points:
|
||||
if first_time:
|
||||
self.play(ShowCreation(little_square))
|
||||
first_time = False
|
||||
else:
|
||||
self.remove(lines, big_square)
|
||||
self.play(ApplyMethod(
|
||||
little_square.shift,
|
||||
bend_point - little_square.get_center()
|
||||
))
|
||||
lines = self.lines_connecting_squares(little_square, big_square)
|
||||
self.play(
|
||||
ShowCreation(lines),
|
||||
ShowCreation(big_square)
|
||||
)
|
||||
self.dither(2)
|
||||
|
||||
|
||||
|
||||
def lines_connecting_squares(self, square1, square2):
|
||||
return Mobject(*[
|
||||
Line(
|
||||
square1.get_corner(vect),
|
||||
square2.get_corner(vect),
|
||||
)
|
||||
for vect in [UP+LEFT, DOWN+LEFT]
|
||||
]).highlight(square1.get_color())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class MultilayeredGlassZoomIn(Scene):
|
||||
def construct(self, layer_number):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import numpy as np
|
||||
import itertools as it
|
||||
import os
|
||||
|
||||
from helpers import *
|
||||
|
||||
|
@ -26,13 +27,96 @@ from topics.functions import ParametricFunction, FunctionGraph
|
|||
from topics.number_line import NumberPlane
|
||||
from 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)
|
||||
|
||||
Mobject(*logos+books+[cornell]).show()
|
||||
|
||||
self.add(name)
|
||||
self.play(FadeIn(cornell))
|
||||
self.play(ShimmerIn(contributions))
|
||||
for logo in logos:
|
||||
self.play(FadeIn(logo))
|
||||
self.play(ShimmerIn(books_word))
|
||||
for book in books:
|
||||
book.shift(5*LEFT)
|
||||
self.play(ApplyMethod(book.shift, 5*RIGHT))
|
||||
self.dither()
|
||||
|
||||
|
||||
class DisectBrachistochroneWord(Scene):
|
||||
def construct(self):
|
||||
word = TextMobject(
|
||||
["Bra", "chis", "to", "chrone"]
|
||||
)
|
||||
word = TextMobject(["Bra", "chis", "to", "chrone"])
|
||||
original_word = word.copy()
|
||||
dots = []
|
||||
for part in word.split():
|
||||
|
@ -52,13 +136,29 @@ class DisectBrachistochroneWord(Scene):
|
|||
time = TextMobject("Time")
|
||||
time.next_to(overbrace2, UP)
|
||||
time.highlight(YELLOW)
|
||||
chrono_example = TextMobject("As in ``Chronological''")
|
||||
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.split()[-1], chrono_example)
|
||||
chrono_arrow.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)
|
||||
|
@ -94,9 +194,119 @@ class DisectBrachistochroneWord(Scene):
|
|||
self.dither()
|
||||
self.play(ShimmerIn(shortest))
|
||||
self.play(ShimmerIn(time))
|
||||
self.dither()
|
||||
for ex, ar in [(chrono_example, chrono_arrow), (brachy_example, brachy_arrow)]:
|
||||
self.play(
|
||||
ShowCreation(chrono_arrow),
|
||||
ShimmerIn(chrono_example)
|
||||
ShowCreation(ar),
|
||||
ShimmerIn(ex)
|
||||
)
|
||||
self.dither()
|
||||
|
||||
|
||||
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):
|
||||
all_topics = [
|
||||
TextMobject(word, size = "\\Huge")
|
||||
for word in [
|
||||
"Brachistochrone",
|
||||
"Light through \\\\ multilayered glass",
|
||||
"Light from \\\\ air into glass",
|
||||
"Lifeguard problem",
|
||||
"Springs and rod",
|
||||
"Snell's Law",
|
||||
"Cycloid",
|
||||
"Mark Levi's cleverness",
|
||||
]
|
||||
]
|
||||
brachy, multi_glass, bi_glass, lifeguard, springs, \
|
||||
snell, cycloid, levi = all_topics
|
||||
positions = [
|
||||
(multi_glass, brachy, DOWN, "both"),
|
||||
(bi_glass, multi_glass, LEFT, "to"),
|
||||
(lifeguard, bi_glass, UP, "both"),
|
||||
(springs, bi_glass, DOWN, "both"),
|
||||
(snell, springs, RIGHT, "from"),
|
||||
(cycloid, multi_glass, RIGHT, "from"),
|
||||
(cycloid, snell, UP+RIGHT, "from"),
|
||||
(levi, cycloid, UP, "to"),
|
||||
]
|
||||
arrows = []
|
||||
for mob1, mob2, direction, arrow_type in positions:
|
||||
hasarrow = hasattr(mob1, "arrow")
|
||||
if not hasarrow:
|
||||
mob1.next_to(mob2, direction, buff = 3)
|
||||
arrow = Arrow(mob1, mob2)
|
||||
if arrow_type in ["to", "from"]:
|
||||
arrow.highlight(GREEN)
|
||||
if arrow_type == "from":
|
||||
arrow.rotate_in_place(np.pi)
|
||||
elif arrow_type == "both":
|
||||
arrow.highlight(BLUE_D)
|
||||
arrow.add(arrow.copy().rotate_in_place(np.pi))
|
||||
arrows.append(arrow)
|
||||
if hasarrow:
|
||||
mob1.arrow.add(arrow)
|
||||
else:
|
||||
mob1.arrow = arrow
|
||||
everything = Mobject(*all_topics+arrows)
|
||||
everything.scale(0.7)
|
||||
everything.center()
|
||||
everything.to_edge(UP)
|
||||
everything.show()
|
||||
|
||||
self.add(brachy)
|
||||
for mob in all_topics[1:]:
|
||||
self.play(ApplyMethod(mob.highlight, YELLOW))
|
||||
self.play(ShowCreation(mob.arrow))
|
||||
self.dither()
|
||||
mob.highlight(WHITE)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ class ImageMobject(Mobject):
|
|||
os.path.join(IMAGE_DIR, image_file),
|
||||
os.path.join(IMAGE_DIR, image_file + ".jpg"),
|
||||
os.path.join(IMAGE_DIR, image_file + ".png"),
|
||||
os.path.join(IMAGE_DIR, image_file + ".gif"),
|
||||
]
|
||||
for path in possible_paths:
|
||||
if os.path.exists(path):
|
||||
|
|
|
@ -1,344 +0,0 @@
|
|||
from topics import *
|
||||
from animation import *
|
||||
|
||||
|
||||
def half_plane():
|
||||
plane = NumberPlane(
|
||||
x_radius = SPACE_WIDTH/2,
|
||||
x_unit_to_spatial_width = 0.5,
|
||||
y_unit_to_spatial_height = 0.5,
|
||||
x_faded_line_frequency = 0,
|
||||
y_faded_line_frequency = 0,
|
||||
density = 4*DEFAULT_POINT_DENSITY_1D,
|
||||
)
|
||||
plane.add_coordinates(
|
||||
x_vals = range(-6, 7, 2),
|
||||
y_vals = range(-6, 7, 2)
|
||||
)
|
||||
return plane
|
||||
|
||||
class SingleVariableFunction(Scene):
|
||||
args_list = [
|
||||
(lambda x : x**2 - 3, "ShiftedSquare", True),
|
||||
(lambda x : x**2 - 3, "ShiftedSquare", False),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def args_to_string(func, name, separate_lines):
|
||||
return name + ("SeparateLines" if separate_lines else "")
|
||||
|
||||
def construct(self, func, name, separate_lines):
|
||||
base_line = NumberLine(color = "grey")
|
||||
moving_line = NumberLine(
|
||||
tick_frequency = 1,
|
||||
density = 3*DEFAULT_POINT_DENSITY_1D
|
||||
)
|
||||
base_line.add_numbers()
|
||||
def point_function((x, y, z)):
|
||||
return (func(x), y, z)
|
||||
target = moving_line.copy().apply_function(point_function)
|
||||
|
||||
transform_config = {
|
||||
"run_time" : 3,
|
||||
"path_func" : path_along_arc(np.pi/4)
|
||||
}
|
||||
|
||||
if separate_lines:
|
||||
numbers = moving_line.get_number_mobjects(*range(-7, 7))
|
||||
negative_numbers = []
|
||||
for number in numbers:
|
||||
number.highlight(GREEN_E)
|
||||
number.shift(-2*moving_line.get_vertical_number_offset())
|
||||
center = number.get_center()
|
||||
target_num = number.copy()
|
||||
target_num.shift(point_function(center) - center)
|
||||
target.add(target_num)
|
||||
if center[0] < -0.5:
|
||||
negative_numbers.append(number)
|
||||
moving_line.add(*numbers)
|
||||
base_line.shift(DOWN)
|
||||
target.shift(DOWN)
|
||||
moving_line.shift(UP)
|
||||
|
||||
self.add(base_line, moving_line)
|
||||
self.dither(3)
|
||||
self.play(Transform(moving_line, target, **transform_config))
|
||||
if separate_lines:
|
||||
self.play(*[
|
||||
ApplyMethod(mob.shift, 0.4*UP)
|
||||
for mob in negative_numbers
|
||||
])
|
||||
self.dither(3)
|
||||
|
||||
|
||||
class LineToPlaneFunction(Scene):
|
||||
args_list = [
|
||||
(lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", []),
|
||||
(lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", [
|
||||
("0", "(1, 0)", 0),
|
||||
("\\frac{\\pi}{2}", "(0, \\pi / 4)", np.pi/2),
|
||||
("\\pi", "(-1, 0)", np.pi),
|
||||
])
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def args_to_string(func, name, numbers_to_follow):
|
||||
return name + ("FollowingNumbers" if numbers_to_follow else "")
|
||||
|
||||
def construct(self, func, name, numbers_to_follow):
|
||||
line = NumberLine(
|
||||
unit_length_to_spatial_width = 0.5,
|
||||
tick_frequency = 1,
|
||||
number_at_center = 6,
|
||||
numerical_radius = 6,
|
||||
numbers_with_elongated_ticks = [0, 12],
|
||||
density = 3*DEFAULT_POINT_DENSITY_1D
|
||||
)
|
||||
line.to_edge(LEFT)
|
||||
line_copy = line.copy()
|
||||
line.add_numbers(*range(0, 14, 2))
|
||||
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
|
||||
plane = half_plane()
|
||||
plane.sub_mobjects = []
|
||||
plane.filter_out(
|
||||
lambda (x, y, z) : abs(x) > 0.1 and abs(y) > 0.1
|
||||
)
|
||||
plane.shift(0.5*SPACE_WIDTH*RIGHT)
|
||||
self.add(line, divider, plane)
|
||||
|
||||
def point_function(point):
|
||||
x, y = func(line.point_to_number(point))
|
||||
return plane.num_pair_to_point((x, y))
|
||||
|
||||
target = line_copy.copy().apply_function(point_function)
|
||||
target.highlight()
|
||||
anim_config = {"run_time" : 3}
|
||||
anims = [Transform(line_copy, target, **anim_config)]
|
||||
|
||||
colors = iter([BLUE_B, GREEN_D, RED_D])
|
||||
for input_tex, output_tex, number in numbers_to_follow:
|
||||
center = line.number_to_point(number)
|
||||
dot = Dot(center, color = colors.next())
|
||||
anims.append(ApplyMethod(
|
||||
dot.shift,
|
||||
point_function(center) - center,
|
||||
**anim_config
|
||||
))
|
||||
label = TexMobject(input_tex)
|
||||
label.shift(center + 2*UP)
|
||||
arrow = Arrow(label, dot)
|
||||
self.add(label)
|
||||
self.play(ShowCreation(arrow), ShowCreation(dot))
|
||||
self.dither()
|
||||
self.remove(arrow, label)
|
||||
|
||||
|
||||
self.dither(2)
|
||||
self.play(*anims)
|
||||
self.dither()
|
||||
|
||||
for input_tex, output_tex, number in numbers_to_follow:
|
||||
point = plane.num_pair_to_point(func(number))
|
||||
label = TexMobject(output_tex)
|
||||
side_shift = LEFT if number == np.pi else RIGHT
|
||||
label.shift(point, 2*UP, side_shift)
|
||||
arrow = Arrow(label, point)
|
||||
self.add(label)
|
||||
self.play(ShowCreation(arrow))
|
||||
self.dither(2)
|
||||
self.remove(arrow, label)
|
||||
|
||||
class PlaneToPlaneFunctionSeparatePlanes(Scene):
|
||||
args_list = [
|
||||
(lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic")
|
||||
]
|
||||
@staticmethod
|
||||
def args_to_string(func, name):
|
||||
return name
|
||||
|
||||
def construct(self, func, name):
|
||||
shift_factor = 0.55
|
||||
in_plane = half_plane().shift(shift_factor*SPACE_WIDTH*LEFT)
|
||||
out_plane = half_plane().shift(shift_factor*SPACE_WIDTH*RIGHT)
|
||||
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
|
||||
self.add(in_plane, out_plane, divider)
|
||||
|
||||
plane_copy = in_plane.copy()
|
||||
plane_copy.sub_mobjects = []
|
||||
|
||||
def point_function(point):
|
||||
result = np.array(func((point*2 + 2*shift_factor*SPACE_WIDTH*RIGHT)[:2]))
|
||||
result = np.append(result/2, [0])
|
||||
return result + shift_factor*SPACE_WIDTH*RIGHT
|
||||
|
||||
target = plane_copy.copy().apply_function(point_function)
|
||||
target.highlight(GREEN_B)
|
||||
|
||||
anim_config = {"run_time" : 5}
|
||||
|
||||
self.dither()
|
||||
self.play(Transform(plane_copy, target, **anim_config))
|
||||
self.dither()
|
||||
|
||||
class PlaneToPlaneFunction(Scene):
|
||||
args_list = [
|
||||
(lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic")
|
||||
]
|
||||
@staticmethod
|
||||
def args_to_string(func, name):
|
||||
return name
|
||||
|
||||
def construct(self, func, name):
|
||||
plane = NumberPlane()
|
||||
background = NumberPlane(color = "grey")
|
||||
background.add_coordinates()
|
||||
anim_config = {"run_time" : 3}
|
||||
|
||||
def point_function(point):
|
||||
return np.append(func(point[:2]), [0])
|
||||
|
||||
self.add(background, plane)
|
||||
self.dither(2)
|
||||
self.play(ApplyPointwiseFunction(point_function, plane, **anim_config))
|
||||
self.dither(3)
|
||||
|
||||
class PlaneToLineFunction(Scene):
|
||||
args_list = [
|
||||
(lambda (x, y) : x**2 + y**2, "Bowl"),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def args_to_string(func, name):
|
||||
return name
|
||||
|
||||
def construct(self, func, name):
|
||||
line = NumberLine(
|
||||
color = GREEN,
|
||||
unit_length_to_spatial_width = 0.5,
|
||||
tick_frequency = 1,
|
||||
number_at_center = 6,
|
||||
numerical_radius = 6,
|
||||
numbers_with_elongated_ticks = [0, 12],
|
||||
).to_edge(RIGHT)
|
||||
line.add_numbers()
|
||||
plane = half_plane().to_edge(LEFT, buff = 0)
|
||||
|
||||
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
|
||||
line_left = line.number_to_point(0)
|
||||
def point_function(point):
|
||||
shifter = 0.5*SPACE_WIDTH*RIGHT
|
||||
return func((point+shifter)[:2])*RIGHT + line_left
|
||||
|
||||
self.add(line, plane, divider)
|
||||
self.dither()
|
||||
plane.sub_mobjects = []
|
||||
self.play(ApplyPointwiseFunction(point_function, plane))
|
||||
self.dither()
|
||||
|
||||
|
||||
|
||||
class PlaneToSpaceFunction(Scene):
|
||||
args_list = [
|
||||
(lambda (x, y) : (x*x, x*y, y*y), "Quadratic"),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def args_to_string(func, name):
|
||||
return name
|
||||
|
||||
def construct(self, func, name):
|
||||
plane = half_plane().shift(0.5*SPACE_WIDTH*LEFT)
|
||||
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
|
||||
axes = XYZAxes()
|
||||
axes.filter_out(lambda p : np.linalg.norm(p) > 3)
|
||||
rot_kwargs = {
|
||||
"run_time" : 3,
|
||||
"radians" : 0.3*np.pi,
|
||||
"axis" : [0.1, 1, 0.1],
|
||||
}
|
||||
axes.to_edge(RIGHT).shift(DOWN)
|
||||
dampening_factor = 0.1
|
||||
def point_function((x, y, z)):
|
||||
return dampening_factor*np.array(func((x, y)))
|
||||
target = NumberPlane().apply_function(point_function)
|
||||
target.highlight("yellow")
|
||||
target.shift(axes.get_center())
|
||||
|
||||
self.add(plane, divider, axes)
|
||||
self.play(Rotating(axes, **rot_kwargs))
|
||||
|
||||
target.rotate_in_place(rot_kwargs["radians"])
|
||||
self.play(
|
||||
TransformAnimations(
|
||||
Animation(plane.copy()),
|
||||
Rotating(target, **rot_kwargs),
|
||||
rate_func = smooth
|
||||
),
|
||||
Rotating(axes, **rot_kwargs)
|
||||
)
|
||||
axes.add(target)
|
||||
self.clear()
|
||||
self.add(plane, divider, axes)
|
||||
self.play(Rotating(axes, **rot_kwargs))
|
||||
self.clear()
|
||||
for i in range(5):
|
||||
self.play(Rotating(axes, **rot_kwargs))
|
||||
|
||||
|
||||
class SpaceToSpaceFunction(Scene):
|
||||
args_list = [
|
||||
(lambda (x, y, z) : (y*z, x*z, x*y), "Quadratic"),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def args_to_string(func, name):
|
||||
return name
|
||||
|
||||
def construct(self, func, name):
|
||||
space = SpaceGrid()
|
||||
rot_kwargs = {
|
||||
"run_time" : 10,
|
||||
"radians" : 2*np.pi/5,
|
||||
"axis" : [0.1, 1, 0.1],
|
||||
"in_place" : False,
|
||||
}
|
||||
axes = XYZAxes()
|
||||
target = space.copy().apply_function(func)
|
||||
|
||||
self.play(
|
||||
TransformAnimations(
|
||||
Rotating(space, **rot_kwargs),
|
||||
Rotating(target, **rot_kwargs),
|
||||
rate_func = squish_rate_func(smooth, 0.3, 0.7)
|
||||
),
|
||||
Rotating(axes, **rot_kwargs)
|
||||
)
|
||||
axes.add(space)
|
||||
self.play(Rotating(axes, **rot_kwargs))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Reference in a new issue