mirror of
https://github.com/3b1b/manim.git
synced 2025-09-01 00:48:45 +00:00
Starting multilayered scenes
This commit is contained in:
parent
b7e53a0e8b
commit
6bb3a2c181
6 changed files with 929 additions and 373 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
344
brachistochrone/graveyard.py
Normal file
344
brachistochrone/graveyard.py
Normal 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)
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
293
brachistochrone/multilayered.py
Normal file
293
brachistochrone/multilayered.py
Normal 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()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue