mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 22:57:44 +00:00
Starting Brachistochrone animations, sliding path is doable
This commit is contained in:
parent
468303c309
commit
d804704ae7
1 changed files with 304 additions and 0 deletions
304
brachistochrone.py
Normal file
304
brachistochrone.py
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import numpy as np
|
||||
import itertools as it
|
||||
import operator as op
|
||||
import sys
|
||||
import inspect
|
||||
from PIL import Image
|
||||
import cv2
|
||||
import random
|
||||
from scipy.spatial.distance import cdist
|
||||
from scipy import ndimage
|
||||
|
||||
from helpers import *
|
||||
|
||||
from mobject.tex_mobject import TexMobject, TextMobject
|
||||
from mobject import Mobject
|
||||
from mobject.image_mobject import \
|
||||
MobjectFromRegion, ImageMobject, MobjectFromPixelArray
|
||||
|
||||
from animation.transform import \
|
||||
Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\
|
||||
FadeIn, FadeOut, GrowFromCenter, ApplyFunction, ApplyMethod, \
|
||||
ShimmerIn
|
||||
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, \
|
||||
Arc
|
||||
from topics.characters import Randolph, Mathematician
|
||||
from topics.functions import ParametricFunction, FunctionGraph
|
||||
from topics.number_line import NumberPlane
|
||||
from region import Region, region_from_polygon_vertices
|
||||
from scene import Scene
|
||||
|
||||
RANDY_SCALE_VAL = 0.3
|
||||
|
||||
###########
|
||||
|
||||
def wavify(mobject):
|
||||
tangent_vectors = mobject.points[1:]-mobject.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 = mobject.copy()
|
||||
result.points[1:] += nudges
|
||||
return result
|
||||
|
||||
|
||||
###########
|
||||
|
||||
class Cycloid(ParametricFunction):
|
||||
DEFAULT_CONFIG = {
|
||||
"point_a" : 6*LEFT+3*UP,
|
||||
"radius" : 2,
|
||||
"end_theta" : 3*np.pi/2,
|
||||
"density" : 5*DEFAULT_POINT_DENSITY_1D,
|
||||
"color" : BLUE_D
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
ParametricFunction.__init__(self, self.pos_func, **kwargs)
|
||||
|
||||
def pos_func(self, t):
|
||||
T = t*self.end_theta
|
||||
return self.point_a + self.radius * np.array([
|
||||
T - np.sin(T),
|
||||
np.cos(T) - 1,
|
||||
0
|
||||
])
|
||||
|
||||
class LoopTheLoop(ParametricFunction):
|
||||
DEFAULT_CONFIG = {
|
||||
"color" : YELLOW_D,
|
||||
"density" : 20*DEFAULT_POINT_DENSITY_1D
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
pre_func = lambda t : [
|
||||
t**3 - 1.5*t,
|
||||
t**2 + 0.6*(t**2 - 4)*(t**2 - 1),
|
||||
0
|
||||
]
|
||||
ParametricFunction.__init__(
|
||||
self,
|
||||
lambda t : pre_func(4*t-2),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
class PathSlidingScene(Scene):
|
||||
DEFAULT_CONFIG = {
|
||||
"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)
|
||||
curr_t = 0
|
||||
last_index = 0
|
||||
curr_index = 1
|
||||
self.t_equals = TexMobject("t = ")
|
||||
self.t_equals.shift(3.5*UP+4*RIGHT)
|
||||
self.add(self.t_equals)
|
||||
while curr_index < len(points):
|
||||
self.slider = mobject.copy()
|
||||
self.adjust_mobject_to_index(
|
||||
self.slider, curr_index, points
|
||||
)
|
||||
if roll:
|
||||
distance = np.linalg.norm(
|
||||
points[curr_index] - points[last_index]
|
||||
)
|
||||
self.roll(mobject, distance)
|
||||
self.add(self.slider)
|
||||
self.write_time(curr_t)
|
||||
self.dither(self.frame_duration)
|
||||
self.remove(self.slider)
|
||||
curr_t += self.delta_t
|
||||
last_index = curr_index
|
||||
while time_slices[curr_index] < curr_t:
|
||||
curr_index += 1
|
||||
if curr_index == len(points):
|
||||
break
|
||||
self.add(self.slider)
|
||||
self.dither()
|
||||
|
||||
def write_time(self, time):
|
||||
if hasattr(self, "time_mob"):
|
||||
self.remove(self.time_mob)
|
||||
digits = map(TexMobject, "%.2f"%time)
|
||||
digits[0].next_to(self.t_equals, buff = 0.1)
|
||||
for left, right in zip(digits, digits[1:]):
|
||||
right.next_to(left, buff = 0.1, aligned_edge = DOWN)
|
||||
self.time_mob = Mobject(*digits)
|
||||
self.add(self.time_mob)
|
||||
|
||||
def roll(self, mobject, arc_length):
|
||||
radius = mobject.get_width()/2
|
||||
theta = arc_length / radius
|
||||
mobject.rotate_in_place(-theta)
|
||||
|
||||
class TryManyPaths(PathSlidingScene):
|
||||
def construct(self):
|
||||
randy = Randolph()
|
||||
randy.shift(-randy.get_bottom())
|
||||
self.slider = randy.copy()
|
||||
randy.scale(RANDY_SCALE_VAL)
|
||||
paths = self.get_paths()
|
||||
point_a = Dot(paths[0].points[0])
|
||||
point_b = Dot(paths[0].points[-1])
|
||||
A = TexMobject("A").next_to(point_a, LEFT)
|
||||
B = TexMobject("B").next_to(point_b, RIGHT)
|
||||
for point, tex in [(point_a, A), (point_b, B)]:
|
||||
self.play(ShowCreation(point))
|
||||
self.play(ShimmerIn(tex))
|
||||
self.dither()
|
||||
curr_path = None
|
||||
for path in paths:
|
||||
new_slider = self.adjust_mobject_to_index(
|
||||
randy.copy(), 1, path.points
|
||||
)
|
||||
if curr_path is None:
|
||||
curr_path = path
|
||||
self.play(ShowCreation(curr_path))
|
||||
else:
|
||||
self.play(Transform(curr_path, path))
|
||||
self.play(Transform(self.slider, new_slider))
|
||||
self.dither()
|
||||
self.remove(self.slider)
|
||||
self.slide(randy, curr_path)
|
||||
self.clear()
|
||||
self.add(point_a, point_b, A, B, curr_path)
|
||||
text = TextMobject("Which path is fastest?")
|
||||
text.to_edge(UP)
|
||||
self.play(ShimmerIn(text))
|
||||
for path in paths:
|
||||
self.play(Transform(
|
||||
curr_path, path,
|
||||
path_func = path_along_arc(np.pi/2),
|
||||
run_time = 3
|
||||
))
|
||||
|
||||
def get_paths(self):
|
||||
sharp_corner = Mobject(
|
||||
Line(3*UP+LEFT, LEFT),
|
||||
Arc(angle = np.pi/2, start_angle = np.pi),
|
||||
Line(DOWN, DOWN+3*RIGHT)
|
||||
).ingest_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
|
||||
),
|
||||
sharp_corner,
|
||||
FunctionGraph(
|
||||
lambda x : x**2,
|
||||
x_min = -3,
|
||||
x_max = 2,
|
||||
density = 3*DEFAULT_POINT_DENSITY_1D
|
||||
)
|
||||
]
|
||||
cycloid = Cycloid()
|
||||
self.align_paths(paths, cycloid)
|
||||
return paths + [cycloid]
|
||||
|
||||
def align_paths(self, paths, target_path):
|
||||
def path_displacement(path):
|
||||
return path.points[-1]-path.points[0]
|
||||
target = path_displacement(target_path)
|
||||
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])
|
||||
|
||||
|
||||
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(point_a, point_b, A, B, cycloid)
|
||||
self.slide(randy, cycloid, roll = True)
|
||||
|
||||
|
||||
|
||||
class SimplePhoton(Scene):
|
||||
def construct(self):
|
||||
photon = wavify(Cycloid())
|
||||
photon.highlight(YELLOW)
|
||||
shaddow = photon.copy().highlight(BLACK)
|
||||
|
||||
self.play(
|
||||
ShowCreation(photon, rate_func = None),
|
||||
ShowCreation(
|
||||
shaddow,
|
||||
rate_func = lambda t : max(0, t-0.1)
|
||||
)
|
||||
)
|
||||
self.dither()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Reference in a new issue