mirror of
https://github.com/3b1b/manim.git
synced 2025-08-02 02:35:22 +08:00
Many filler animations for Brachistochrone
This commit is contained in:
@ -23,6 +23,9 @@ from camera import Camera
|
|||||||
from brachistochrone.curves import *
|
from brachistochrone.curves import *
|
||||||
|
|
||||||
class RollAlongVector(Animation):
|
class RollAlongVector(Animation):
|
||||||
|
CONFIG = {
|
||||||
|
"rotation_vector" : OUT,
|
||||||
|
}
|
||||||
def __init__(self, mobject, vector, **kwargs):
|
def __init__(self, mobject, vector, **kwargs):
|
||||||
radius = mobject.get_width()/2
|
radius = mobject.get_width()/2
|
||||||
radians = np.linalg.norm(vector)/radius
|
radians = np.linalg.norm(vector)/radius
|
||||||
@ -33,7 +36,10 @@ class RollAlongVector(Animation):
|
|||||||
def update_mobject(self, alpha):
|
def update_mobject(self, alpha):
|
||||||
d_alpha = alpha - self.last_alpha
|
d_alpha = alpha - self.last_alpha
|
||||||
self.last_alpha = alpha
|
self.last_alpha = alpha
|
||||||
self.mobject.rotate_in_place(d_alpha*self.radians)
|
self.mobject.rotate_in_place(
|
||||||
|
d_alpha*self.radians,
|
||||||
|
self.rotation_vector
|
||||||
|
)
|
||||||
self.mobject.shift(d_alpha*self.vector)
|
self.mobject.shift(d_alpha*self.vector)
|
||||||
|
|
||||||
|
|
||||||
@ -73,7 +79,7 @@ class CycloidScene(Scene):
|
|||||||
self.ceiling = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT)
|
self.ceiling = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT)
|
||||||
self.ceiling.shift(self.cycloid.get_top()[1]*UP)
|
self.ceiling.shift(self.cycloid.get_top()[1]*UP)
|
||||||
|
|
||||||
def draw_cycloid(self, run_time = 3, **kwargs):
|
def draw_cycloid(self, run_time = 3, *anims, **kwargs):
|
||||||
kwargs["run_time"] = run_time
|
kwargs["run_time"] = run_time
|
||||||
self.play(
|
self.play(
|
||||||
RollAlongVector(
|
RollAlongVector(
|
||||||
@ -81,39 +87,477 @@ class CycloidScene(Scene):
|
|||||||
self.cycloid.points[-1]-self.cycloid.points[0],
|
self.cycloid.points[-1]-self.cycloid.points[0],
|
||||||
**kwargs
|
**kwargs
|
||||||
),
|
),
|
||||||
ShowCreation(self.cycloid, **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):
|
class IntroduceCycloid(CycloidScene):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
CycloidScene.construct(self)
|
CycloidScene.construct(self)
|
||||||
|
|
||||||
equation = TexMobject("""
|
equation = TexMobject([
|
||||||
\\dfrac{\\sin(\\theta)}{\\sqrt{y}} =
|
"\\dfrac{\\sin(\\theta)}{\\sqrt{y}}",
|
||||||
\\text{constant}
|
"= \\text{constant}"
|
||||||
""")
|
])
|
||||||
|
sin_sqrt, const = equation.split()
|
||||||
new_eq = equation.copy()
|
new_eq = equation.copy()
|
||||||
new_eq.to_edge(UP, buff = 1.3)
|
new_eq.to_edge(UP, buff = 1.3)
|
||||||
cycloid_word = TextMobject("Cycloid")
|
cycloid_word = TextMobject("Cycloid")
|
||||||
arrow = Arrow(2*UP, cycloid_word)
|
arrow = Arrow(2*UP, cycloid_word)
|
||||||
arrow.reverse_points()
|
arrow.reverse_points()
|
||||||
|
q_mark = TextMobject("?")
|
||||||
|
|
||||||
self.play(ShimmerIn(equation))
|
self.play(*map(ShimmerIn, equation.split()))
|
||||||
self.dither()
|
self.dither()
|
||||||
self.play(
|
self.play(
|
||||||
ApplyMethod(equation.shift, 2.2*UP),
|
ApplyMethod(equation.shift, 2.2*UP),
|
||||||
ShowCreation(arrow)
|
ShowCreation(arrow)
|
||||||
)
|
)
|
||||||
|
q_mark.next_to(sin_sqrt)
|
||||||
self.play(ShimmerIn(cycloid_word))
|
self.play(ShimmerIn(cycloid_word))
|
||||||
self.dither()
|
self.dither()
|
||||||
self.grow_parts()
|
self.grow_parts()
|
||||||
self.draw_cycloid()
|
self.draw_cycloid()
|
||||||
self.dither()
|
self.dither()
|
||||||
|
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.dither()
|
||||||
|
|
||||||
|
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_sub_mobjects()
|
||||||
|
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.dither()
|
||||||
|
|
||||||
|
|
||||||
|
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.sub_mobjects[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.dither()
|
||||||
|
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.highlight(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.dither()
|
||||||
|
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.dither()
|
||||||
|
self.play(Transform(arcs[1], tangent_line))
|
||||||
|
self.add(tangent_line)
|
||||||
|
self.play(ShowCreation(right_angle_symbol))
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
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.dither()
|
||||||
|
self.play(ShowCreation(diameter))
|
||||||
|
self.play(GrowFromCenter(brace))
|
||||||
|
self.play(ShimmerIn(diameter_word))
|
||||||
|
self.dither()
|
||||||
|
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 = {
|
||||||
|
"point_thickness" : 2*DEFAULT_POINT_THICKNESS,
|
||||||
|
}
|
||||||
|
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.dither()
|
||||||
|
self.play(FadeIn(triangle1))
|
||||||
|
self.dither()
|
||||||
|
self.play(Transform(triangle1, triangle2))
|
||||||
|
self.play(ApplyMethod(triangle1.highlight, MAROON))
|
||||||
|
self.dither()
|
||||||
|
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_sub_mobjects()
|
||||||
|
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.scale_to_fit_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.dither()
|
||||||
|
|
||||||
|
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.dither()
|
||||||
|
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.dither()
|
||||||
|
|
||||||
|
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)
|
||||||
|
""")
|
||||||
|
equations.shift(2*UP)
|
||||||
|
|
||||||
|
self.play(ShimmerIn(equations))
|
||||||
|
self.grow_parts()
|
||||||
|
self.draw_cycloid(rate_func = None, run_time = 5)
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
|
class SlidingObject(CycloidScene, PathSlidingScene):
|
||||||
|
CONFIG = {
|
||||||
|
"show_time" : False,
|
||||||
|
}
|
||||||
|
def construct(self):
|
||||||
|
CycloidScene.construct(self)
|
||||||
|
|
||||||
|
randy = Randolph()
|
||||||
|
randy.scale(RANDY_SCALE_VAL)
|
||||||
|
randy.shift(-randy.get_bottom())
|
||||||
|
start_randy = self.adjust_mobject_to_index(
|
||||||
|
randy.copy(), 1, self.cycloid.points
|
||||||
|
)
|
||||||
|
|
||||||
|
self.play(ShowCreation(self.cycloid))
|
||||||
|
self.slide(randy, self.cycloid)
|
||||||
|
self.dither()
|
||||||
|
self.grow_parts()
|
||||||
|
self.draw_cycloid()
|
||||||
|
self.dither()
|
||||||
|
self.play(Transform(self.slider, start_randy))
|
||||||
|
self.dither()
|
||||||
|
self.roll_back()
|
||||||
|
self.dither()
|
||||||
|
radial_line = self.circle.sub_mobjects[0]
|
||||||
|
self.circle.add(self.slider)
|
||||||
|
self.circle.get_center = lambda : radial_line.get_start_and_end()[0]
|
||||||
|
self.draw_cycloid()
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@ from scene import Scene
|
|||||||
|
|
||||||
DEFAULT_GAUSS_BLUR_CONFIG = {
|
DEFAULT_GAUSS_BLUR_CONFIG = {
|
||||||
"ksize" : (5, 5),
|
"ksize" : (5, 5),
|
||||||
"sigmaX" : 10,
|
"sigmaX" : 6,
|
||||||
"sigmaY" : 10,
|
"sigmaY" : 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_CANNY_CONFIG = {
|
DEFAULT_CANNY_CONFIG = {
|
||||||
"threshold1" : 75,
|
"threshold1" : 50,
|
||||||
"threshold2" : 150,
|
"threshold2" : 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_BLUR_RADIUS = 0.5
|
DEFAULT_BLUR_RADIUS = 0.5
|
||||||
@ -135,6 +135,7 @@ class TracePicture(Scene):
|
|||||||
("Galileo_Galilei",),
|
("Galileo_Galilei",),
|
||||||
("Jacob_Bernoulli",),
|
("Jacob_Bernoulli",),
|
||||||
("Johann_Bernoulli2",),
|
("Johann_Bernoulli2",),
|
||||||
|
("Old_Newton",)
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -314,6 +315,109 @@ class JohannThinksOfFermat(Scene):
|
|||||||
self.dither()
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
|
class MathematiciansOfEurope(Scene):
|
||||||
|
def construct(self):
|
||||||
|
europe = ImageMobject("1700_Europe", invert = False)
|
||||||
|
self.add(europe)
|
||||||
|
self.freeze_background()
|
||||||
|
|
||||||
|
mathematicians = [
|
||||||
|
("Newton", [-1.6, 0.6, 0]),
|
||||||
|
("Jacob_Bernoulli",[-1, -0.75, 0]),
|
||||||
|
("Ehrenfried_von_Tschirnhaus",[-0.5, 0.2, 0]),
|
||||||
|
("Gottfried_Wilhelm_von_Leibniz",[-0.1, -0.75, 0]),
|
||||||
|
("Guillaume_de_L'Hopital", [-1.5, -0.5, 0]),
|
||||||
|
]
|
||||||
|
|
||||||
|
for name, point in mathematicians:
|
||||||
|
man = ImageMobject(name, invert = False)
|
||||||
|
name_mob = TextMobject(name.replace("_", " "))
|
||||||
|
name_mob.to_corner(UP+LEFT, buff=0.75)
|
||||||
|
man.scale_to_fit_height(4)
|
||||||
|
mobject = Point(man.get_corner(UP+LEFT))
|
||||||
|
self.play(
|
||||||
|
DelayByOrder(Transform(mobject, man)),
|
||||||
|
ShimmerIn(name_mob)
|
||||||
|
)
|
||||||
|
man.scale(0.2)
|
||||||
|
man.shift(point)
|
||||||
|
self.play(Transform(mobject, man))
|
||||||
|
self.remove(name_mob)
|
||||||
|
|
||||||
|
class OldNewtonIsDispleased(Scene):
|
||||||
|
def construct(self):
|
||||||
|
old_newton = ImageMobject("Old_Newton", invert = False)
|
||||||
|
old_newton.scale(0.8)
|
||||||
|
self.add(old_newton)
|
||||||
|
self.freeze_background()
|
||||||
|
|
||||||
|
words = TextMobject("Note the displeasure")
|
||||||
|
words.to_corner(UP+RIGHT)
|
||||||
|
face_point = 1.8*UP+0.5*LEFT
|
||||||
|
arrow = Arrow(words.get_bottom(), face_point)
|
||||||
|
|
||||||
|
|
||||||
|
self.play(ShimmerIn(words))
|
||||||
|
self.play(ShowCreation(arrow))
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
|
class NewtonConsideredEveryoneBeneathHim(Scene):
|
||||||
|
def construct(self):
|
||||||
|
mathematicians = [
|
||||||
|
ImageMobject(name, invert = False)
|
||||||
|
for name in [
|
||||||
|
"Old_Newton",
|
||||||
|
"Johann_Bernoulli2",
|
||||||
|
"Jacob_Bernoulli",
|
||||||
|
"Ehrenfried_von_Tschirnhaus",
|
||||||
|
"Gottfried_Wilhelm_von_Leibniz",
|
||||||
|
"Guillaume_de_L'Hopital",
|
||||||
|
]
|
||||||
|
]
|
||||||
|
newton = mathematicians.pop(0)
|
||||||
|
newton.scale(0.8)
|
||||||
|
new_newton = newton.copy()
|
||||||
|
new_newton.scale_to_fit_height(3)
|
||||||
|
new_newton.to_edge(UP)
|
||||||
|
for man in mathematicians:
|
||||||
|
man.scale_to_fit_width(1.7)
|
||||||
|
johann = mathematicians.pop(0)
|
||||||
|
johann.next_to(new_newton, DOWN)
|
||||||
|
last_left, last_right = johann, johann
|
||||||
|
for man, count in zip(mathematicians, it.count()):
|
||||||
|
if count%2 == 0:
|
||||||
|
man.next_to(last_left, LEFT)
|
||||||
|
last_left = man
|
||||||
|
else:
|
||||||
|
man.next_to(last_right, RIGHT)
|
||||||
|
last_right = man
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
Transform(newton, new_newton),
|
||||||
|
GrowFromCenter(johann)
|
||||||
|
)
|
||||||
|
self.dither()
|
||||||
|
self.play(FadeIn(Mobject(*mathematicians)))
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -431,6 +431,27 @@ class PhtonBendsInWater(PhotonScene, ZoomedScene):
|
|||||||
ShimmerIn(question_mark)
|
ShimmerIn(question_mark)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class LightIsFasterInAirThanWater(ShowMultiplePathsInWater):
|
||||||
|
def construct(self):
|
||||||
|
glass = Region(lambda x, y : y < 0, color = BLUE_E)
|
||||||
|
equation = TexMobject("v_{\\text{air}} > v_{\\text{water}}")
|
||||||
|
equation.to_edge(UP)
|
||||||
|
path = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT)
|
||||||
|
path1 = path.copy().shift(2*UP)
|
||||||
|
path2 = path.copy().shift(2*DOWN)
|
||||||
|
|
||||||
|
self.add(glass)
|
||||||
|
self.play(ShimmerIn(equation))
|
||||||
|
self.dither()
|
||||||
|
photon_runs = []
|
||||||
|
photon_runs.append(self.photon_run_along_path(
|
||||||
|
path1, rate_func = lambda t : min(1, 1.2*t)
|
||||||
|
))
|
||||||
|
photon_runs.append(self.photon_run_along_path(path2))
|
||||||
|
self.play(*photon_runs, **{"run_time" : 2})
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
class GeometryOfGlassSituation(ShowMultiplePathsInWater):
|
class GeometryOfGlassSituation(ShowMultiplePathsInWater):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
glass = Region(lambda x, y : y < 0, color = BLUE_E)
|
glass = Region(lambda x, y : y < 0, color = BLUE_E)
|
||||||
|
@ -13,7 +13,7 @@ from animation.transform import *
|
|||||||
from animation.simple_animations import *
|
from animation.simple_animations import *
|
||||||
from animation.playground import TurnInsideOut, Vibrate
|
from animation.playground import TurnInsideOut, Vibrate
|
||||||
from topics.geometry import *
|
from topics.geometry import *
|
||||||
from topics.characters import Randolph, Mathematician
|
from topics.characters import *
|
||||||
from topics.functions import ParametricFunction, FunctionGraph
|
from topics.functions import ParametricFunction, FunctionGraph
|
||||||
from topics.number_line import *
|
from topics.number_line import *
|
||||||
from mobject.region import Region, region_from_polygon_vertices
|
from mobject.region import Region, region_from_polygon_vertices
|
||||||
@ -124,6 +124,117 @@ class TimeLine(Scene):
|
|||||||
self.play(*map(FadeOut, [event_mob, date_mob, line, picture]))
|
self.play(*map(FadeOut, [event_mob, date_mob, line, picture]))
|
||||||
|
|
||||||
|
|
||||||
|
class StayedUpAllNight(Scene):
|
||||||
|
def construct(self):
|
||||||
|
clock = Circle(radius = 2, color = WHITE)
|
||||||
|
clock.add(Dot(ORIGIN))
|
||||||
|
ticks = Mobject(*[
|
||||||
|
Line(1.8*vect, 2*vect, color = GREY)
|
||||||
|
for vect in compass_directions(12)
|
||||||
|
])
|
||||||
|
clock.add(ticks)
|
||||||
|
hour_hand = Line(ORIGIN, UP)
|
||||||
|
minute_hand = Line(ORIGIN, 1.5*UP)
|
||||||
|
clock.add(hour_hand, minute_hand)
|
||||||
|
clock.to_corner(UP+RIGHT)
|
||||||
|
hour_hand.get_center = lambda : clock.get_center()
|
||||||
|
minute_hand.get_center = lambda : clock.get_center()
|
||||||
|
|
||||||
|
solution = ImageMobject(
|
||||||
|
"Newton_brachistochrone_solution2",
|
||||||
|
use_cache = False
|
||||||
|
)
|
||||||
|
solution.point_thickness = 3
|
||||||
|
solution.highlight(GREY)
|
||||||
|
solution.scale_to_fit_width(5)
|
||||||
|
solution.to_corner(UP+RIGHT)
|
||||||
|
newton = ImageMobject("Old_Newton", invert = False)
|
||||||
|
newton.scale(0.8)
|
||||||
|
phil_trans = TextMobject("Philosophical Transactions")
|
||||||
|
rect = Rectangle(height = 6, width = 4.5, color = WHITE)
|
||||||
|
rect.to_corner(UP+RIGHT)
|
||||||
|
rect.shift(DOWN)
|
||||||
|
phil_trans.scale_to_fit_width(0.8*rect.get_width())
|
||||||
|
phil_trans.next_to(Point(rect.get_top()), DOWN)
|
||||||
|
new_solution = solution.copy()
|
||||||
|
new_solution.scale_to_fit_width(phil_trans.get_width())
|
||||||
|
new_solution.next_to(phil_trans, DOWN, buff = 1)
|
||||||
|
not_newton = TextMobject("-Totally not by Newton")
|
||||||
|
not_newton.scale_to_fit_width(2.5)
|
||||||
|
not_newton.next_to(new_solution, DOWN, aligned_edge = RIGHT)
|
||||||
|
phil_trans.add(rect)
|
||||||
|
|
||||||
|
newton_complaint = TextMobject([
|
||||||
|
"``I do not love to be",
|
||||||
|
" \\emph{dunned} ",
|
||||||
|
"and teased by foreigners''"
|
||||||
|
], size = "\\small")
|
||||||
|
newton_complaint.to_edge(UP, buff = 0.2)
|
||||||
|
dunned = newton_complaint.split()[1]
|
||||||
|
dunned.highlight()
|
||||||
|
dunned_def = TextMobject("(old timey term for making \\\\ demands on someone)")
|
||||||
|
dunned_def.scale(0.7)
|
||||||
|
dunned_def.next_to(phil_trans, LEFT)
|
||||||
|
dunned_def.shift(2*UP)
|
||||||
|
dunned_arrow = Arrow(dunned_def, dunned)
|
||||||
|
|
||||||
|
johann = ImageMobject("Johann_Bernoulli2", invert = False)
|
||||||
|
johann.scale(0.4)
|
||||||
|
johann.to_edge(LEFT)
|
||||||
|
johann.shift(DOWN)
|
||||||
|
johann_quote = TextMobject("``I recognize the lion by his claw''")
|
||||||
|
johann_quote.next_to(johann, UP, aligned_edge = LEFT)
|
||||||
|
|
||||||
|
self.play(ApplyMethod(newton.to_edge, LEFT))
|
||||||
|
self.play(ShowCreation(clock))
|
||||||
|
kwargs = {
|
||||||
|
"axis" : OUT,
|
||||||
|
"rate_func" : smooth
|
||||||
|
}
|
||||||
|
self.play(
|
||||||
|
Rotating(hour_hand, radians = -2*np.pi, **kwargs),
|
||||||
|
Rotating(minute_hand, radians = -12*2*np.pi, **kwargs),
|
||||||
|
run_time = 5
|
||||||
|
)
|
||||||
|
self.dither()
|
||||||
|
self.clear()
|
||||||
|
self.add(newton)
|
||||||
|
clock.ingest_sub_mobjects()
|
||||||
|
self.play(Transform(clock, solution))
|
||||||
|
self.remove(clock)
|
||||||
|
self.add(solution)
|
||||||
|
self.dither()
|
||||||
|
self.play(
|
||||||
|
FadeIn(phil_trans),
|
||||||
|
Transform(solution, new_solution)
|
||||||
|
)
|
||||||
|
self.dither()
|
||||||
|
self.play(ShimmerIn(not_newton))
|
||||||
|
phil_trans.add(solution, not_newton)
|
||||||
|
self.dither()
|
||||||
|
self.play(*map(ShimmerIn, newton_complaint.split()))
|
||||||
|
self.dither()
|
||||||
|
self.play(
|
||||||
|
ShimmerIn(dunned_def),
|
||||||
|
ShowCreation(dunned_arrow)
|
||||||
|
)
|
||||||
|
self.dither()
|
||||||
|
self.remove(dunned_def, dunned_arrow)
|
||||||
|
self.play(FadeOut(newton_complaint))
|
||||||
|
self.remove(newton_complaint)
|
||||||
|
self.play(
|
||||||
|
FadeOut(newton),
|
||||||
|
GrowFromCenter(johann)
|
||||||
|
)
|
||||||
|
self.remove(newton)
|
||||||
|
self.dither()
|
||||||
|
self.play(ShimmerIn(johann_quote))
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
|
class ThetaTSigmoidGraph(Scene):
|
||||||
|
def construct(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -131,3 +242,14 @@ class TimeLine(Scene):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,6 +137,50 @@ class RaceLightInLayers(MultilayeredScene, PhotonScene):
|
|||||||
for line, rate in zip(lines, rates)
|
for line, rate in zip(lines, rates)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
class ShowDiscretePath(MultilayeredScene, PhotonScene):
|
||||||
|
CONFIG = {
|
||||||
|
"RectClass" : FilledRectangle
|
||||||
|
}
|
||||||
|
def construct(self):
|
||||||
|
self.add_layers()
|
||||||
|
self.cycloid = Cycloid(end_theta = np.pi)
|
||||||
|
|
||||||
|
self.generate_discrete_path()
|
||||||
|
self.play(ShowCreation(self.discrete_path))
|
||||||
|
self.dither()
|
||||||
|
self.play(self.photon_run_along_path(
|
||||||
|
self.discrete_path,
|
||||||
|
rate_func = rush_into,
|
||||||
|
run_time = 3
|
||||||
|
))
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_discrete_path(self):
|
||||||
|
points = self.cycloid.points
|
||||||
|
tops = [mob.get_top()[1] for mob in self.layers]
|
||||||
|
tops.append(tops[-1]-self.layers[0].get_height())
|
||||||
|
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 = WHITE,
|
||||||
|
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+(tops[-1]-0.5)*UP
|
||||||
|
)
|
||||||
|
|
||||||
class NLayers(MultilayeredScene):
|
class NLayers(MultilayeredScene):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
@ -337,23 +381,22 @@ class ContinuouslyObeyingSnellsLaw(MultilayeredScene):
|
|||||||
chopped_cycloid.reverse_points()
|
chopped_cycloid.reverse_points()
|
||||||
|
|
||||||
|
|
||||||
# self.play(ShowCreation(cycloid))
|
self.play(ShowCreation(cycloid))
|
||||||
# ref_mob = self.snells_law_at_every_point(cycloid, chopped_cycloid)
|
ref_mob = self.snells_law_at_every_point(cycloid, chopped_cycloid)
|
||||||
ref_mob = Point()
|
|
||||||
self.show_equation(chopped_cycloid, ref_mob)
|
self.show_equation(chopped_cycloid, ref_mob)
|
||||||
|
|
||||||
def snells_law_at_every_point(self, cycloid, chopped_cycloid):
|
def snells_law_at_every_point(self, cycloid, chopped_cycloid):
|
||||||
square = Square(side_length = 0.2, color = WHITE)
|
square = Square(side_length = 0.2, color = WHITE)
|
||||||
words = TextMobject(["Snell's law", " at every point"], use_cache = False)
|
words = TextMobject(["Snell's law ", " at every point"])
|
||||||
words.show()
|
|
||||||
snells, rest = words.split()
|
snells, rest = words.split()
|
||||||
colon = TextMobject(":")
|
colon = TextMobject(":")
|
||||||
words.next_to(square)
|
words.next_to(square)
|
||||||
words.shift(0.3*UP)
|
words.shift(0.3*UP)
|
||||||
combo = Mobject(square, words)
|
combo = Mobject(square, words)
|
||||||
combo.get_center = lambda : square.get_center()
|
combo.get_center = lambda : square.get_center()
|
||||||
new_snells = snells.copy().center().to_edge(UP, buff = 1.3)
|
new_snells = snells.copy().center().to_edge(UP, buff = 1.5)
|
||||||
colon.next_to(new_snells)
|
colon.next_to(new_snells)
|
||||||
|
colon.shift(0.05*DOWN)
|
||||||
|
|
||||||
self.play(MoveAlongPath(
|
self.play(MoveAlongPath(
|
||||||
combo, cycloid,
|
combo, cycloid,
|
||||||
@ -416,10 +459,13 @@ class ContinuouslyObeyingSnellsLaw(MultilayeredScene):
|
|||||||
)
|
)
|
||||||
y_mob = TexMobject("y").next_to(brace)
|
y_mob = TexMobject("y").next_to(brace)
|
||||||
|
|
||||||
self.play(GrowFromCenter(sin))
|
self.play(
|
||||||
|
GrowFromCenter(sin),
|
||||||
|
ShowCreation(arc),
|
||||||
|
GrowFromCenter(theta)
|
||||||
|
)
|
||||||
self.play(ShowCreation(vert_line))
|
self.play(ShowCreation(vert_line))
|
||||||
self.play(ShowCreation(tangent_line))
|
self.play(ShowCreation(tangent_line))
|
||||||
self.play(ShowCreation(arc), GrowFromCenter(theta))
|
|
||||||
self.dither()
|
self.dither()
|
||||||
self.play(
|
self.play(
|
||||||
GrowFromCenter(sqrt_y),
|
GrowFromCenter(sqrt_y),
|
||||||
|
@ -133,6 +133,14 @@ class LetsBeHonest(Scene):
|
|||||||
self.dither()
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
|
class WhatIsTheBrachistochrone(Scene):
|
||||||
|
def construct(self):
|
||||||
|
self.play(ShimmerIn(TextMobject("""
|
||||||
|
So \\dots what is the Brachistochrone?
|
||||||
|
""")))
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
|
||||||
class DisectBrachistochroneWord(Scene):
|
class DisectBrachistochroneWord(Scene):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
word = TextMobject(["Bra", "chis", "to", "chrone"])
|
word = TextMobject(["Bra", "chis", "to", "chrone"])
|
||||||
|
@ -312,7 +312,7 @@ class Mobject(object):
|
|||||||
return self.scale(height/self.get_height())
|
return self.scale(height/self.get_height())
|
||||||
|
|
||||||
def replace(self, mobject, stretch = False):
|
def replace(self, mobject, stretch = False):
|
||||||
if mobject.get_num_points() == 0:
|
if not mobject.get_num_points() and not mobject.sub_mobjects:
|
||||||
raise Warning("Attempting to replace mobject with no points")
|
raise Warning("Attempting to replace mobject with no points")
|
||||||
return self
|
return self
|
||||||
if stretch:
|
if stretch:
|
||||||
|
@ -20,7 +20,7 @@ class TexMobject(Mobject):
|
|||||||
def generate_points(self):
|
def generate_points(self):
|
||||||
image_files = tex_to_image_files(
|
image_files = tex_to_image_files(
|
||||||
self.expression,
|
self.expression,
|
||||||
self.size,
|
self.size,
|
||||||
self.template_tex_file
|
self.template_tex_file
|
||||||
)
|
)
|
||||||
for image_file in image_files:
|
for image_file in image_files:
|
||||||
@ -56,7 +56,6 @@ class Brace(TexMobject):
|
|||||||
self.shift(left - self.points[0] + self.buff*DOWN)
|
self.shift(left - self.points[0] + self.buff*DOWN)
|
||||||
for mob in mobject, self:
|
for mob in mobject, self:
|
||||||
mob.rotate(angle)
|
mob.rotate(angle)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def tex_hash(expression, size):
|
def tex_hash(expression, size):
|
||||||
|
@ -88,7 +88,7 @@ class Line(Mobject1D):
|
|||||||
|
|
||||||
def get_angle(self):
|
def get_angle(self):
|
||||||
start, end = self.get_start_and_end()
|
start, end = self.get_start_and_end()
|
||||||
return angle_of_vector(start-end)
|
return angle_of_vector(end-start)
|
||||||
|
|
||||||
class Arrow(Line):
|
class Arrow(Line):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
|
Reference in New Issue
Block a user