mirror of
https://github.com/3b1b/manim.git
synced 2025-07-29 13:03:31 +08:00
591 lines
17 KiB
Python
591 lines
17 KiB
Python
from manimlib.imports import *
|
|
from old_projects.brachistochrone.curves import *
|
|
|
|
class RollAlongVector(Animation):
|
|
CONFIG = {
|
|
"rotation_vector" : OUT,
|
|
}
|
|
def __init__(self, mobject, vector, **kwargs):
|
|
radius = mobject.get_width()/2
|
|
radians = get_norm(vector)/radius
|
|
last_alpha = 0
|
|
digest_config(self, kwargs, locals())
|
|
Animation.__init__(self, mobject, **kwargs)
|
|
|
|
def interpolate_mobject(self, alpha):
|
|
d_alpha = alpha - self.last_alpha
|
|
self.last_alpha = alpha
|
|
self.mobject.rotate_in_place(
|
|
d_alpha*self.radians,
|
|
self.rotation_vector
|
|
)
|
|
self.mobject.shift(d_alpha*self.vector)
|
|
|
|
|
|
class CycloidScene(Scene):
|
|
CONFIG = {
|
|
"point_a" : 6*LEFT+3*UP,
|
|
"radius" : 2,
|
|
"end_theta" : 2*np.pi
|
|
}
|
|
def construct(self):
|
|
self.generate_cycloid()
|
|
self.generate_circle()
|
|
self.generate_ceiling()
|
|
|
|
def grow_parts(self):
|
|
self.play(*[
|
|
ShowCreation(mob)
|
|
for mob in (self.circle, self.ceiling)
|
|
])
|
|
|
|
def generate_cycloid(self):
|
|
self.cycloid = Cycloid(
|
|
point_a = self.point_a,
|
|
radius = self.radius,
|
|
end_theta = self.end_theta
|
|
)
|
|
|
|
def generate_circle(self, **kwargs):
|
|
self.circle = Circle(radius = self.radius, **kwargs)
|
|
self.circle.shift(self.point_a - self.circle.get_top())
|
|
radial_line = Line(
|
|
self.circle.get_center(), self.point_a
|
|
)
|
|
self.circle.add(radial_line)
|
|
|
|
def generate_ceiling(self):
|
|
self.ceiling = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT)
|
|
self.ceiling.shift(self.cycloid.get_top()[1]*UP)
|
|
|
|
def draw_cycloid(self, run_time = 3, *anims, **kwargs):
|
|
kwargs["run_time"] = run_time
|
|
self.play(
|
|
RollAlongVector(
|
|
self.circle,
|
|
self.cycloid.points[-1]-self.cycloid.points[0],
|
|
**kwargs
|
|
),
|
|
ShowCreation(self.cycloid, **kwargs),
|
|
*anims
|
|
)
|
|
|
|
def roll_back(self, run_time = 3, *anims, **kwargs):
|
|
kwargs["run_time"] = run_time
|
|
self.play(
|
|
RollAlongVector(
|
|
self.circle,
|
|
self.cycloid.points[0]-self.cycloid.points[- 1],
|
|
rotation_vector = IN,
|
|
**kwargs
|
|
),
|
|
ShowCreation(
|
|
self.cycloid,
|
|
rate_func = lambda t : smooth(1-t),
|
|
**kwargs
|
|
),
|
|
*anims
|
|
)
|
|
self.generate_cycloid()
|
|
|
|
|
|
class IntroduceCycloid(CycloidScene):
|
|
def construct(self):
|
|
CycloidScene.construct(self)
|
|
|
|
equation = TexMobject([
|
|
"\\dfrac{\\sin(\\theta)}{\\sqrt{y}}",
|
|
"= \\text{constant}"
|
|
])
|
|
sin_sqrt, const = equation.split()
|
|
new_eq = equation.copy()
|
|
new_eq.to_edge(UP, buff = 1.3)
|
|
cycloid_word = TextMobject("Cycloid")
|
|
arrow = Arrow(2*UP, cycloid_word)
|
|
arrow.reverse_points()
|
|
q_mark = TextMobject("?")
|
|
|
|
self.play(*list(map(ShimmerIn, equation.split())))
|
|
self.wait()
|
|
self.play(
|
|
ApplyMethod(equation.shift, 2.2*UP),
|
|
ShowCreation(arrow)
|
|
)
|
|
q_mark.next_to(sin_sqrt)
|
|
self.play(ShimmerIn(cycloid_word))
|
|
self.wait()
|
|
self.grow_parts()
|
|
self.draw_cycloid()
|
|
self.wait()
|
|
extra_terms = [const, arrow, cycloid_word]
|
|
self.play(*[
|
|
Transform(mob, q_mark)
|
|
for mob in extra_terms
|
|
])
|
|
self.remove(*extra_terms)
|
|
self.roll_back()
|
|
q_marks, arrows = self.get_q_marks_and_arrows(sin_sqrt)
|
|
self.draw_cycloid(3,
|
|
ShowCreation(q_marks),
|
|
ShowCreation(arrows)
|
|
)
|
|
self.wait()
|
|
|
|
def get_q_marks_and_arrows(self, mob, n_marks = 10):
|
|
circle = Circle().replace(mob)
|
|
q_marks, arrows = result = [Mobject(), Mobject()]
|
|
for x in range(n_marks):
|
|
index = (x+0.5)*self.cycloid.get_num_points()/n_marks
|
|
q_point = self.cycloid.points[index]
|
|
vect = q_point-mob.get_center()
|
|
start_point = circle.get_boundary_point(vect)
|
|
arrow = Arrow(
|
|
start_point, q_point,
|
|
color = BLUE_E
|
|
)
|
|
|
|
q_marks.add(TextMobject("?").shift(q_point))
|
|
arrows.add(arrow)
|
|
for mob in result:
|
|
mob.ingest_submobjects()
|
|
return result
|
|
|
|
|
|
class LeviSolution(CycloidScene):
|
|
CONFIG = {
|
|
"cycloid_fraction" : 0.25,
|
|
}
|
|
def construct(self):
|
|
CycloidScene.construct(self)
|
|
self.add(self.ceiling)
|
|
self.generate_points()
|
|
methods = [
|
|
self.draw_cycloid,
|
|
self.roll_into_position,
|
|
self.draw_p_and_c,
|
|
self.show_pendulum,
|
|
self.show_diameter,
|
|
self.show_theta,
|
|
self.show_similar_triangles,
|
|
self.show_sin_thetas,
|
|
self.show_y,
|
|
self.rearrange,
|
|
]
|
|
for method in methods:
|
|
method()
|
|
self.wait()
|
|
|
|
|
|
def generate_points(self):
|
|
index = int(self.cycloid_fraction*self.cycloid.get_num_points())
|
|
p_point = self.cycloid.points[index]
|
|
p_dot = Dot(p_point)
|
|
p_label = TexMobject("P")
|
|
p_label.next_to(p_dot, DOWN+LEFT)
|
|
c_point = self.point_a + self.cycloid_fraction*self.radius*2*np.pi*RIGHT
|
|
c_dot = Dot(c_point)
|
|
c_label = TexMobject("C")
|
|
c_label.next_to(c_dot, UP)
|
|
|
|
digest_locals(self)
|
|
|
|
def roll_into_position(self):
|
|
self.play(RollAlongVector(
|
|
self.circle,
|
|
(1-self.cycloid_fraction)*self.radius*2*np.pi*LEFT,
|
|
rotation_vector = IN,
|
|
run_time = 2
|
|
))
|
|
|
|
def draw_p_and_c(self):
|
|
radial_line = self.circle.submobjects[0] ##Hacky
|
|
self.play(Transform(radial_line, self.p_dot))
|
|
self.remove(radial_line)
|
|
self.add(self.p_dot)
|
|
self.play(ShimmerIn(self.p_label))
|
|
self.wait()
|
|
self.play(Transform(self.ceiling.copy(), self.c_dot))
|
|
self.play(ShimmerIn(self.c_label))
|
|
|
|
def show_pendulum(self, arc_angle = np.pi, arc_color = GREEN):
|
|
words = TextMobject(": Instantaneous center of rotation")
|
|
words.next_to(self.c_label)
|
|
line = Line(self.p_point, self.c_point)
|
|
line_angle = line.get_angle()+np.pi
|
|
line_length = line.get_length()
|
|
line.add(self.p_dot.copy())
|
|
line.get_center = lambda : self.c_point
|
|
tangent_line = Line(3*LEFT, 3*RIGHT)
|
|
tangent_line.rotate(line_angle-np.pi/2)
|
|
tangent_line.shift(self.p_point)
|
|
tangent_line.set_color(arc_color)
|
|
right_angle_symbol = Mobject(
|
|
Line(UP, UP+RIGHT),
|
|
Line(UP+RIGHT, RIGHT)
|
|
)
|
|
right_angle_symbol.scale(0.3)
|
|
right_angle_symbol.rotate(tangent_line.get_angle()+np.pi)
|
|
right_angle_symbol.shift(self.p_point)
|
|
|
|
self.play(ShowCreation(line))
|
|
self.play(ShimmerIn(words))
|
|
self.wait()
|
|
pairs = [
|
|
(line_angle, arc_angle/2),
|
|
(line_angle+arc_angle/2, -arc_angle),
|
|
(line_angle-arc_angle/2, arc_angle/2),
|
|
]
|
|
arcs = []
|
|
for start, angle in pairs:
|
|
arc = Arc(
|
|
angle = angle,
|
|
radius = line_length,
|
|
start_angle = start,
|
|
color = GREEN
|
|
)
|
|
arc.shift(self.c_point)
|
|
self.play(
|
|
ShowCreation(arc),
|
|
ApplyMethod(
|
|
line.rotate_in_place,
|
|
angle,
|
|
path_func = path_along_arc(angle)
|
|
),
|
|
run_time = 2
|
|
)
|
|
arcs.append(arc)
|
|
self.wait()
|
|
self.play(Transform(arcs[1], tangent_line))
|
|
self.add(tangent_line)
|
|
self.play(ShowCreation(right_angle_symbol))
|
|
self.wait()
|
|
|
|
self.tangent_line = tangent_line
|
|
self.right_angle_symbol = right_angle_symbol
|
|
self.pc_line = line
|
|
self.remove(words, *arcs)
|
|
|
|
def show_diameter(self):
|
|
exceptions = [
|
|
self.circle,
|
|
self.tangent_line,
|
|
self.pc_line,
|
|
self.right_angle_symbol
|
|
]
|
|
everything = set(self.mobjects).difference(exceptions)
|
|
everything_copy = Mobject(*everything).copy()
|
|
light_everything = everything_copy.copy()
|
|
dark_everything = everything_copy.copy()
|
|
dark_everything.fade(0.8)
|
|
bottom_point = np.array(self.c_point)
|
|
bottom_point += 2*self.radius*DOWN
|
|
diameter = Line(bottom_point, self.c_point)
|
|
brace = Brace(diameter, RIGHT)
|
|
diameter_word = TextMobject("Diameter")
|
|
d_mob = TexMobject("D")
|
|
diameter_word.next_to(brace)
|
|
d_mob.next_to(diameter)
|
|
|
|
self.remove(*everything)
|
|
self.play(Transform(everything_copy, dark_everything))
|
|
self.wait()
|
|
self.play(ShowCreation(diameter))
|
|
self.play(GrowFromCenter(brace))
|
|
self.play(ShimmerIn(diameter_word))
|
|
self.wait()
|
|
self.play(*[
|
|
Transform(mob, d_mob)
|
|
for mob in (brace, diameter_word)
|
|
])
|
|
self.remove(brace, diameter_word)
|
|
self.add(d_mob)
|
|
self.play(Transform(everything_copy, light_everything))
|
|
self.remove(everything_copy)
|
|
self.add(*everything)
|
|
|
|
self.d_mob = d_mob
|
|
self.bottom_point = bottom_point
|
|
|
|
def show_theta(self, radius = 1):
|
|
arc = Arc(
|
|
angle = self.tangent_line.get_angle()-np.pi/2,
|
|
radius = radius,
|
|
start_angle = np.pi/2
|
|
)
|
|
|
|
theta = TexMobject("\\theta")
|
|
theta.shift(1.5*arc.get_center())
|
|
Mobject(arc, theta).shift(self.bottom_point)
|
|
|
|
self.play(
|
|
ShowCreation(arc),
|
|
ShimmerIn(theta)
|
|
)
|
|
self.arc = arc
|
|
self.theta = theta
|
|
|
|
def show_similar_triangles(self):
|
|
y_point = np.array(self.p_point)
|
|
y_point[1] = self.point_a[1]
|
|
new_arc = Arc(
|
|
angle = self.tangent_line.get_angle()-np.pi/2,
|
|
radius = 0.5,
|
|
start_angle = np.pi
|
|
)
|
|
new_arc.shift(self.c_point)
|
|
new_theta = self.theta.copy()
|
|
new_theta.next_to(new_arc, LEFT)
|
|
new_theta.shift(0.1*DOWN)
|
|
kwargs = {
|
|
"stroke_width" : 2*DEFAULT_STROKE_WIDTH,
|
|
}
|
|
triangle1 = Polygon(
|
|
self.p_point, self.c_point, self.bottom_point,
|
|
color = MAROON,
|
|
**kwargs
|
|
)
|
|
triangle2 = Polygon(
|
|
y_point, self.p_point, self.c_point,
|
|
color = WHITE,
|
|
**kwargs
|
|
)
|
|
y_line = Line(self.p_point, y_point)
|
|
|
|
self.play(
|
|
Transform(self.arc.copy(), new_arc),
|
|
Transform(self.theta.copy(), new_theta),
|
|
run_time = 3
|
|
)
|
|
self.wait()
|
|
self.play(FadeIn(triangle1))
|
|
self.wait()
|
|
self.play(Transform(triangle1, triangle2))
|
|
self.play(ApplyMethod(triangle1.set_color, MAROON))
|
|
self.wait()
|
|
self.remove(triangle1)
|
|
self.add(y_line)
|
|
|
|
self.y_line = y_line
|
|
|
|
def show_sin_thetas(self):
|
|
pc = Line(self.p_point, self.c_point)
|
|
mob = Mobject(self.theta, self.d_mob).copy()
|
|
mob.ingest_submobjects()
|
|
triplets = [
|
|
(pc, "D\\sin(\\theta)", 0.5),
|
|
(self.y_line, "D\\sin^2(\\theta)", 0.7),
|
|
]
|
|
for line, tex, scale in triplets:
|
|
trig_mob = TexMobject(tex)
|
|
trig_mob.set_width(
|
|
scale*line.get_length()
|
|
)
|
|
trig_mob.shift(-1.2*trig_mob.get_top())
|
|
trig_mob.rotate(line.get_angle())
|
|
trig_mob.shift(line.get_center())
|
|
if line is self.y_line:
|
|
trig_mob.shift(0.1*UP)
|
|
|
|
self.play(Transform(mob, trig_mob))
|
|
self.add(trig_mob)
|
|
self.wait()
|
|
|
|
self.remove(mob)
|
|
self.d_sin_squared_theta = trig_mob
|
|
|
|
|
|
def show_y(self):
|
|
y_equals = TexMobject(["y", "="])
|
|
y_equals.shift(2*UP)
|
|
y_expression = TexMobject([
|
|
"D ", "\\sin", "^2", "(\\theta)"
|
|
])
|
|
y_expression.next_to(y_equals)
|
|
y_expression.shift(0.05*UP+0.1*RIGHT)
|
|
temp_expr = self.d_sin_squared_theta.copy()
|
|
temp_expr.rotate(-np.pi/2)
|
|
temp_expr.replace(y_expression)
|
|
y_mob = TexMobject("y")
|
|
y_mob.next_to(self.y_line, RIGHT)
|
|
y_mob.shift(0.2*UP)
|
|
|
|
self.play(
|
|
Transform(self.d_sin_squared_theta, temp_expr),
|
|
ShimmerIn(y_mob),
|
|
ShowCreation(y_equals)
|
|
)
|
|
self.remove(self.d_sin_squared_theta)
|
|
self.add(y_expression)
|
|
|
|
self.y_equals = y_equals
|
|
self.y_expression = y_expression
|
|
|
|
def rearrange(self):
|
|
sqrt_nudge = 0.2*LEFT
|
|
y, equals = self.y_equals.split()
|
|
d, sin, squared, theta = self.y_expression.split()
|
|
y_sqrt = TexMobject("\\sqrt{\\phantom{y}}")
|
|
d_sqrt = y_sqrt.copy()
|
|
y_sqrt.shift(y.get_center()+sqrt_nudge)
|
|
d_sqrt.shift(d.get_center()+sqrt_nudge)
|
|
|
|
self.play(
|
|
ShimmerIn(y_sqrt),
|
|
ShimmerIn(d_sqrt),
|
|
ApplyMethod(squared.shift, 4*UP),
|
|
ApplyMethod(theta.shift, 1.5* squared.get_width()*LEFT)
|
|
)
|
|
self.wait()
|
|
y_sqrt.add(y)
|
|
d_sqrt.add(d)
|
|
sin.add(theta)
|
|
|
|
sin_over = TexMobject("\\dfrac{\\phantom{\\sin(\\theta)}}{\\quad}")
|
|
sin_over.next_to(sin, DOWN, 0.15)
|
|
new_eq = equals.copy()
|
|
new_eq.next_to(sin_over, LEFT)
|
|
one_over = TexMobject("\\dfrac{1}{\\quad}")
|
|
one_over.next_to(new_eq, LEFT)
|
|
one_over.shift(
|
|
(sin_over.get_bottom()[1]-one_over.get_bottom()[1])*UP
|
|
)
|
|
|
|
self.play(
|
|
Transform(equals, new_eq),
|
|
ShimmerIn(sin_over),
|
|
ShimmerIn(one_over),
|
|
ApplyMethod(
|
|
d_sqrt.next_to, one_over, DOWN,
|
|
path_func = path_along_arc(-np.pi)
|
|
),
|
|
ApplyMethod(
|
|
y_sqrt.next_to, sin_over, DOWN,
|
|
path_func = path_along_arc(-np.pi)
|
|
),
|
|
run_time = 2
|
|
)
|
|
self.wait()
|
|
|
|
brace = Brace(d_sqrt, DOWN)
|
|
constant = TextMobject("Constant")
|
|
constant.next_to(brace, DOWN)
|
|
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
ShimmerIn(constant)
|
|
)
|
|
|
|
|
|
|
|
|
|
class EquationsForCycloid(CycloidScene):
|
|
def construct(self):
|
|
CycloidScene.construct(self)
|
|
equations = TexMobject([
|
|
"x(t) = Rt - R\\sin(t)",
|
|
"y(t) = -R + R\\cos(t)"
|
|
])
|
|
top, bottom = equations.split()
|
|
bottom.next_to(top, DOWN)
|
|
equations.center()
|
|
equations.to_edge(UP, buff = 1.3)
|
|
|
|
self.play(ShimmerIn(equations))
|
|
self.grow_parts()
|
|
self.draw_cycloid(rate_func=linear, run_time = 5)
|
|
self.wait()
|
|
|
|
class SlidingObject(CycloidScene, PathSlidingScene):
|
|
CONFIG = {
|
|
"show_time" : False,
|
|
"wait_and_add" : False
|
|
}
|
|
|
|
args_list = [(True,), (False,)]
|
|
|
|
@staticmethod
|
|
def args_to_string(with_words):
|
|
return "WithWords" if with_words else "WithoutWords"
|
|
|
|
@staticmethod
|
|
def string_to_args(string):
|
|
return string == "WithWords"
|
|
|
|
def construct(self, with_words):
|
|
CycloidScene.construct(self)
|
|
|
|
randy = Randolph()
|
|
randy.scale(RANDY_SCALE_FACTOR)
|
|
randy.shift(-randy.get_bottom())
|
|
central_randy = randy.copy()
|
|
start_randy = self.adjust_mobject_to_index(
|
|
randy.copy(), 1, self.cycloid.points
|
|
)
|
|
|
|
if with_words:
|
|
words1 = TextMobject("Trajectory due to gravity")
|
|
arrow = TexMobject("\\leftrightarrow")
|
|
words2 = TextMobject("Trajectory due \\emph{constantly} rotating wheel")
|
|
words1.next_to(arrow, LEFT)
|
|
words2.next_to(arrow, RIGHT)
|
|
words = Mobject(words1, arrow, words2)
|
|
words.set_width(FRAME_WIDTH-1)
|
|
words.to_edge(UP, buff = 0.2)
|
|
words.to_edge(LEFT)
|
|
|
|
self.play(ShowCreation(self.cycloid.copy()))
|
|
self.slide(randy, self.cycloid)
|
|
self.add(self.slider)
|
|
self.wait()
|
|
self.grow_parts()
|
|
self.draw_cycloid()
|
|
self.wait()
|
|
self.play(Transform(self.slider, start_randy))
|
|
self.wait()
|
|
self.roll_back()
|
|
self.wait()
|
|
if with_words:
|
|
self.play(*list(map(ShimmerIn, [words1, arrow, words2])))
|
|
self.wait()
|
|
self.remove(self.circle)
|
|
start_time = len(self.frames)*self.frame_duration
|
|
self.remove(self.slider)
|
|
self.slide(central_randy, self.cycloid)
|
|
end_time = len(self.frames)*self.frame_duration
|
|
self.play_over_time_range(
|
|
start_time,
|
|
end_time,
|
|
RollAlongVector(
|
|
self.circle,
|
|
self.cycloid.points[-1]-self.cycloid.points[0],
|
|
run_time = end_time-start_time,
|
|
rate_func=linear
|
|
)
|
|
)
|
|
self.add(self.circle, self.slider)
|
|
self.wait()
|
|
|
|
|
|
|
|
class RotateWheel(CycloidScene):
|
|
def construct(self):
|
|
CycloidScene.construct(self)
|
|
self.circle.center()
|
|
|
|
self.play(Rotating(
|
|
self.circle,
|
|
axis = OUT,
|
|
run_time = 5,
|
|
rate_func = smooth
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|