mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Added (somewhat large) ProveEllipse scene
This commit is contained in:
parent
a76bea6d72
commit
0f9e1796c7
1 changed files with 890 additions and 17 deletions
|
@ -122,14 +122,7 @@ class ShowEmergingEllipse(Scene):
|
|||
rot_words = TextMobject("Rotate $90^\\circ$ \\\\ about center")
|
||||
rot_words.next_to(line_dot, RIGHT)
|
||||
|
||||
elbow = VGroup(Line(UP, UL), Line(UL, LEFT))
|
||||
elbow.set_stroke(width=1)
|
||||
elbow.scale(0.2, about_point=ORIGIN)
|
||||
elbow.rotate(
|
||||
line.get_angle() - 90 * DEGREES,
|
||||
about_point=ORIGIN
|
||||
)
|
||||
elbow.shift(line.get_center())
|
||||
elbow = self.get_elbow(line)
|
||||
|
||||
eccentric_words = TextMobject("``Eccentric'' point")
|
||||
eccentric_words.next_to(circle.get_center(), DOWN)
|
||||
|
@ -223,6 +216,17 @@ class ShowEmergingEllipse(Scene):
|
|||
width=self.ghost_lines_stroke_width
|
||||
)
|
||||
|
||||
def get_elbow(self, line):
|
||||
elbow = VGroup(Line(UP, UL), Line(UL, LEFT))
|
||||
elbow.set_stroke(width=1)
|
||||
elbow.scale(0.2, about_point=ORIGIN)
|
||||
elbow.rotate(
|
||||
line.get_angle() - 90 * DEGREES,
|
||||
about_point=ORIGIN
|
||||
)
|
||||
elbow.shift(line.get_center())
|
||||
return elbow
|
||||
|
||||
def get_ellipse(self):
|
||||
center = self.circle.get_center()
|
||||
e_point = self.get_eccentricity_point()
|
||||
|
@ -331,7 +335,7 @@ class FeynmanFame(Scene):
|
|||
objects = VGroup(safe, bongo)
|
||||
|
||||
feynman_smile = ImageMobject("Feynman_Los_Alamos")
|
||||
feynman_smile.match_width(objects)
|
||||
feynman_smile.scale_to_fit_height(4)
|
||||
feynman_smile.next_to(objects, DOWN)
|
||||
|
||||
VGroup(objects, feynman_smile).next_to(ORIGIN, RIGHT)
|
||||
|
@ -902,40 +906,909 @@ class AskAboutInfiniteIntelligence(TeacherStudentsScene):
|
|||
|
||||
class ShowEllipseDefiningProperty(Scene):
|
||||
CONFIG = {
|
||||
"camera_config": {"background_opacity": 1},
|
||||
"ellipse_color": BLUE,
|
||||
"a": 4.0,
|
||||
"b": 3.0,
|
||||
"distance_labels_scale_factor": 1.0,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_ellipse()
|
||||
self.add_focal_lines()
|
||||
self.add_distance_labels()
|
||||
self.show_constant_sum()
|
||||
self.label_foci()
|
||||
self.label_focal_sum()
|
||||
|
||||
def add_ellipse(self):
|
||||
a = self.a
|
||||
b = self.b
|
||||
c = np.sqrt(a**2 - b**2)
|
||||
ellipse = Circle(radius=a, color=self.ellipse_color)
|
||||
ellipse.stretch(fdiv(b, a), dim=1)
|
||||
self.foci = [c * LEFT, c * RIGHT]
|
||||
ellipse.to_edge(LEFT, buff=LARGE_BUFF)
|
||||
self.ellipse = ellipse
|
||||
self.add(ellipse)
|
||||
|
||||
def add_focal_lines(self):
|
||||
pass
|
||||
push_pins = VGroup(*[
|
||||
SVGMobject(
|
||||
file_name="push_pin",
|
||||
color=LIGHT_GREY,
|
||||
fill_opacity=0.8,
|
||||
height=0.5,
|
||||
).move_to(point, DR).shift(0.05 * RIGHT)
|
||||
for point in self.get_foci()
|
||||
])
|
||||
|
||||
dot = Dot()
|
||||
dot.scale(0.5)
|
||||
position_tracker = ValueTracker(0.125)
|
||||
dot_update = ContinualUpdateFromFunc(
|
||||
dot,
|
||||
lambda d: d.move_to(
|
||||
self.ellipse.point_from_proportion(
|
||||
position_tracker.get_value() % 1
|
||||
)
|
||||
)
|
||||
)
|
||||
position_tracker_wander = ContinualMovement(
|
||||
position_tracker, rate=0.05,
|
||||
)
|
||||
|
||||
lines, lines_update_animation = self.get_focal_lines_and_update(
|
||||
self.get_foci, dot
|
||||
)
|
||||
|
||||
self.add_foreground_mobjects(push_pins, dot)
|
||||
self.add(dot_update)
|
||||
self.play(LaggedStart(
|
||||
FadeInAndShiftFromDirection, push_pins,
|
||||
lambda m: (m, 2 * UP + LEFT),
|
||||
run_time=1,
|
||||
lag_ratio=0.75
|
||||
))
|
||||
self.play(ShowCreation(lines))
|
||||
self.add(lines_update_animation)
|
||||
self.add(position_tracker_wander)
|
||||
self.wait(2)
|
||||
|
||||
self.position_tracker = position_tracker
|
||||
self.focal_lines = lines
|
||||
|
||||
def add_distance_labels(self):
|
||||
pass
|
||||
lines = self.focal_lines
|
||||
colors = [YELLOW, PINK]
|
||||
|
||||
def show_constant_sum(self):
|
||||
pass
|
||||
distance_labels, distance_labels_animation = \
|
||||
self.get_distance_labels_and_update(lines, colors)
|
||||
|
||||
sum_expression, numbers, number_updates = \
|
||||
self.get_sum_expression_and_update(
|
||||
lines, colors, lambda mob: mob.to_corner(UR)
|
||||
)
|
||||
|
||||
sum_expression_fading_rect = BackgroundRectangle(
|
||||
sum_expression, fill_opacity=1
|
||||
)
|
||||
|
||||
sum_rect = SurroundingRectangle(numbers[-1])
|
||||
constant_words = TextMobject("Stays constant")
|
||||
constant_words.next_to(sum_rect, DOWN, aligned_edge=RIGHT)
|
||||
VGroup(sum_rect, constant_words).set_color(BLUE)
|
||||
|
||||
self.add(distance_labels_animation)
|
||||
self.add(*number_updates)
|
||||
self.add(sum_expression)
|
||||
self.add_foreground_mobjects(sum_expression_fading_rect)
|
||||
self.play(
|
||||
VFadeIn(distance_labels),
|
||||
FadeOut(sum_expression_fading_rect),
|
||||
)
|
||||
self.remove_foreground_mobject(sum_expression_fading_rect)
|
||||
self.wait(7)
|
||||
self.play(
|
||||
ShowCreation(sum_rect),
|
||||
Write(constant_words)
|
||||
)
|
||||
self.wait(7)
|
||||
self.play(FadeOut(sum_rect), FadeOut(constant_words))
|
||||
|
||||
self.sum_expression = sum_expression
|
||||
self.sum_rect = sum_rect
|
||||
|
||||
def label_foci(self):
|
||||
pass
|
||||
foci = self.get_foci()
|
||||
focus_words = VGroup(*[
|
||||
TextMobject("Focus").next_to(focus, DOWN)
|
||||
for focus in foci
|
||||
])
|
||||
foci_word = TextMobject("Foci")
|
||||
foci_word.move_to(focus_words)
|
||||
foci_word.shift(MED_SMALL_BUFF * UP)
|
||||
connecting_lines = VGroup(*[
|
||||
Arrow(
|
||||
foci_word.get_edge_center(-edge),
|
||||
focus_word.get_edge_center(edge),
|
||||
buff=MED_SMALL_BUFF,
|
||||
stroke_width=2,
|
||||
)
|
||||
for focus_word, edge in zip(focus_words, [LEFT, RIGHT])
|
||||
])
|
||||
|
||||
translation = TextMobject(
|
||||
"``Foco'' $\\rightarrow$ Fireplace"
|
||||
)
|
||||
translation.to_edge(RIGHT)
|
||||
translation.shift(UP)
|
||||
sun = ImageMobject("sun", height=0.5)
|
||||
sun.move_to(foci[0])
|
||||
sun_animation = SunAnimation(sun)
|
||||
|
||||
self.play(FadeInFromDown(focus_words))
|
||||
self.wait(2)
|
||||
self.play(
|
||||
ReplacementTransform(focus_words.copy(), foci_word),
|
||||
)
|
||||
self.play(*map(ShowCreation, connecting_lines))
|
||||
for word in list(focus_words) + [foci_word]:
|
||||
word.add_background_rectangle()
|
||||
self.add_foreground_mobjects(word)
|
||||
self.wait(4)
|
||||
self.play(Write(translation))
|
||||
self.wait(2)
|
||||
self.play(GrowFromCenter(sun))
|
||||
self.add(sun_animation)
|
||||
self.wait(8)
|
||||
|
||||
def label_focal_sum(self):
|
||||
sum_rect = self.sum_rect
|
||||
|
||||
focal_sum = TextMobject("``Focal sum''")
|
||||
focal_sum.scale(1.5)
|
||||
focal_sum.next_to(sum_rect, DOWN, aligned_edge=RIGHT)
|
||||
VGroup(sum_rect, focal_sum).set_color(RED)
|
||||
|
||||
footnote = TextMobject(
|
||||
"""
|
||||
\\Large
|
||||
*This happens to equal the longest distance
|
||||
across the ellipse, so perhaps the more standard
|
||||
terminology would be ``major axis'', but I want
|
||||
some terminology that conveys the idea of adding
|
||||
two distances to the foci.
|
||||
""",
|
||||
alignment="",
|
||||
)
|
||||
footnote.scale_to_fit_width(5)
|
||||
footnote.to_corner(DR)
|
||||
footnote.set_stroke(WHITE, 0.5)
|
||||
|
||||
self.play(FadeInFromDown(focal_sum))
|
||||
self.play(Write(sum_rect))
|
||||
self.wait()
|
||||
self.play(FadeIn(footnote))
|
||||
self.wait(2)
|
||||
self.play(FadeOut(footnote))
|
||||
self.wait(8)
|
||||
|
||||
# Helpers
|
||||
def get_foci(self):
|
||||
ellipse = self.ellipse
|
||||
a = ellipse.get_width() / 2
|
||||
b = ellipse.get_height() / 2
|
||||
c = np.sqrt(a**2 - b**2)
|
||||
center = ellipse.get_center()
|
||||
return [
|
||||
center + c * RIGHT,
|
||||
center + c * LEFT,
|
||||
]
|
||||
|
||||
def get_focal_lines_and_update(self, get_foci, focal_sum_point):
|
||||
lines = VGroup(Line(LEFT, RIGHT), Line(LEFT, RIGHT))
|
||||
lines.set_stroke(width=2)
|
||||
|
||||
def update_lines(lines):
|
||||
foci = get_foci()
|
||||
for line, focus in zip(lines, foci):
|
||||
line.put_start_and_end_on(
|
||||
focus, focal_sum_point.get_center()
|
||||
)
|
||||
lines[1].rotate(np.pi)
|
||||
lines_update_animation = ContinualUpdateFromFunc(
|
||||
lines, update_lines
|
||||
)
|
||||
return lines, lines_update_animation
|
||||
|
||||
def get_distance_labels_and_update(self, lines, colors):
|
||||
distance_labels = VGroup(
|
||||
DecimalNumber(0), DecimalNumber(0),
|
||||
)
|
||||
for label in distance_labels:
|
||||
label.scale(self.distance_labels_scale_factor)
|
||||
|
||||
def update_distance_labels(labels):
|
||||
for label, line, color in zip(labels, lines, colors):
|
||||
angle = -line.get_angle()
|
||||
if np.abs(angle) > 90 * DEGREES:
|
||||
angle = 180 * DEGREES + angle
|
||||
line.rotate(angle, about_point=ORIGIN)
|
||||
new_decimal = DecimalNumber(line.get_length())
|
||||
new_decimal.scale(
|
||||
self.distance_labels_scale_factor
|
||||
)
|
||||
max_width = 0.6 * line.get_width()
|
||||
if new_decimal.get_width() > max_width:
|
||||
new_decimal.scale_to_fit_width(max_width)
|
||||
new_decimal.next_to(line, UP, SMALL_BUFF)
|
||||
new_decimal.set_color(color)
|
||||
VGroup(new_decimal, line).rotate(
|
||||
-angle, about_point=ORIGIN
|
||||
)
|
||||
label.submobjects = list(new_decimal.submobjects)
|
||||
|
||||
distance_labels_animation = ContinualUpdateFromFunc(
|
||||
distance_labels, update_distance_labels
|
||||
)
|
||||
|
||||
return distance_labels, distance_labels_animation
|
||||
|
||||
def get_sum_expression_and_update(self, lines, colors, sum_position_func):
|
||||
sum_expression = TexMobject("0.00", "+", "0.00", "=", "0.00")
|
||||
sum_position_func(sum_expression)
|
||||
number_refs = sum_expression.get_parts_by_tex("0.00")
|
||||
number_refs.set_fill(opacity=0)
|
||||
numbers = VGroup(*[DecimalNumber(0) for ref in number_refs])
|
||||
for number, color in zip(numbers, colors):
|
||||
number.set_color(color)
|
||||
|
||||
# Not the most elegant...
|
||||
number_updates = [
|
||||
ContinualChangingDecimal(
|
||||
numbers[0], lambda a: lines[0].get_length(),
|
||||
position_update_func=lambda m: m.move_to(
|
||||
number_refs[1], LEFT
|
||||
)
|
||||
),
|
||||
ContinualChangingDecimal(
|
||||
numbers[1], lambda a: lines[1].get_length(),
|
||||
position_update_func=lambda m: m.move_to(
|
||||
number_refs[0], LEFT
|
||||
)
|
||||
),
|
||||
ContinualChangingDecimal(
|
||||
numbers[2], lambda a: sum(map(Line.get_length, lines)),
|
||||
position_update_func=lambda m: m.move_to(
|
||||
number_refs[2], LEFT
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
return sum_expression, numbers, number_updates
|
||||
|
||||
|
||||
class GeometryProofLand(Scene):
|
||||
def construct(self):
|
||||
word = TextMobject("Geometry proof land")
|
||||
word.rotate(-90 * DEGREES)
|
||||
word.scale(0.25)
|
||||
word.shift(3 * RIGHT)
|
||||
word.apply_complex_function(np.exp)
|
||||
word.rotate(90 * DEGREES)
|
||||
word.scale_to_fit_width(9)
|
||||
word.center()
|
||||
word.to_edge(UP)
|
||||
colors = [
|
||||
PINK, RED, YELLOW, GREEN, GREEN_A, BLUE,
|
||||
MAROON_E, MAROON_B, YELLOW, BLUE,
|
||||
]
|
||||
word.set_color_by_gradient(*colors)
|
||||
|
||||
word_outlines = word.deepcopy()
|
||||
word_outlines.set_fill(opacity=0)
|
||||
word_outlines.set_stroke(WHITE, 1)
|
||||
random.shuffle(colors)
|
||||
word_outlines.set_color_by_gradient(*colors)
|
||||
|
||||
circles = VGroup()
|
||||
for letter in word:
|
||||
circle = Circle()
|
||||
# circle = letter.copy()
|
||||
circle.replace(letter, dim_to_match=1)
|
||||
circle.scale(3)
|
||||
circle.set_stroke(WHITE, 0)
|
||||
circle.set_fill(letter.get_color(), 0)
|
||||
circles.add(circle)
|
||||
circle.target = letter
|
||||
|
||||
self.play(
|
||||
LaggedStart(MoveToTarget, circles),
|
||||
run_time=2
|
||||
)
|
||||
self.play(LaggedStart(
|
||||
ShowCreationThenDestruction, word_outlines,
|
||||
run_time=4
|
||||
))
|
||||
self.wait()
|
||||
|
||||
|
||||
class ProveEllipse(ShowEmergingEllipse, ShowEllipseDefiningProperty):
|
||||
CONFIG = {
|
||||
"eccentricity_vector": 1.5 * RIGHT,
|
||||
"ellipse_color": PINK,
|
||||
"distance_labels_scale_factor": 0.7,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.setup_ellipse()
|
||||
self.hypothesize_foci()
|
||||
self.setup_and_show_focal_sum()
|
||||
self.show_circle_radius()
|
||||
self.limit_to_just_one_line()
|
||||
self.look_at_perpendicular_bisector()
|
||||
self.show_orbiting_planet()
|
||||
|
||||
def setup_ellipse(self):
|
||||
circle = self.circle = self.get_circle()
|
||||
circle.to_edge(LEFT)
|
||||
ep = self.get_eccentricity_point()
|
||||
ep_dot = self.ep_dot = Dot(ep, color=YELLOW)
|
||||
lines = self.lines = self.get_lines()
|
||||
for line in lines:
|
||||
line.save_state()
|
||||
ghost_lines = self.ghost_lines = self.get_ghost_lines(lines)
|
||||
ellipse = self.ellipse = self.get_ellipse()
|
||||
|
||||
self.add(ghost_lines, circle, lines, ep_dot)
|
||||
self.play(
|
||||
LaggedStart(MoveToTarget, lines),
|
||||
Animation(ep_dot),
|
||||
)
|
||||
self.play(ShowCreation(ellipse))
|
||||
self.wait()
|
||||
|
||||
def hypothesize_foci(self):
|
||||
circle = self.circle
|
||||
ghost_lines = self.ghost_lines
|
||||
ghost_lines_copy = ghost_lines.copy().set_stroke(YELLOW, 3)
|
||||
|
||||
center = circle.get_center()
|
||||
center_dot = Dot(center, color=RED)
|
||||
# ep = self.get_eccentricity_point()
|
||||
ep_dot = self.ep_dot
|
||||
dots = VGroup(center_dot, ep_dot)
|
||||
|
||||
center_label = TextMobject("Circle center")
|
||||
ep_label = TextMobject("Eccentric point")
|
||||
labels = VGroup(center_label, ep_label)
|
||||
vects = [UL, DR]
|
||||
arrows = VGroup()
|
||||
for label, dot, vect in zip(labels, dots, vects):
|
||||
label.next_to(dot, vect, MED_LARGE_BUFF)
|
||||
label.match_color(dot)
|
||||
label.add_to_back(
|
||||
label.copy().set_stroke(BLACK, 5)
|
||||
)
|
||||
arrow = Arrow(
|
||||
label.get_corner(-vect),
|
||||
dot.get_corner(vect),
|
||||
buff=SMALL_BUFF
|
||||
)
|
||||
arrow.match_color(dot)
|
||||
arrow.add_to_back(arrow.copy().set_stroke(BLACK, 5))
|
||||
arrows.add(arrow)
|
||||
|
||||
labels_target = labels.copy()
|
||||
labels_target.arrange_submobjects(
|
||||
DOWN, aligned_edge=LEFT
|
||||
)
|
||||
guess_start = TextMobject("Guess: Foci = ")
|
||||
brace = Brace(labels_target, LEFT)
|
||||
full_guess = VGroup(guess_start, brace, labels_target)
|
||||
full_guess.arrange_submobjects(RIGHT)
|
||||
full_guess.to_corner(UR)
|
||||
|
||||
self.play(
|
||||
FadeInFromDown(labels[1]),
|
||||
GrowArrow(arrows[1]),
|
||||
)
|
||||
self.play(LaggedStart(
|
||||
ShowPassingFlash, ghost_lines_copy
|
||||
))
|
||||
self.wait()
|
||||
self.play(ReplacementTransform(circle.copy(), center_dot))
|
||||
self.add_foreground_mobjects(dots)
|
||||
self.play(
|
||||
FadeInFromDown(labels[0]),
|
||||
GrowArrow(arrows[0]),
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
Write(guess_start),
|
||||
GrowFromCenter(brace),
|
||||
run_time=1
|
||||
)
|
||||
self.play(
|
||||
ReplacementTransform(labels.copy(), labels_target)
|
||||
)
|
||||
self.wait()
|
||||
self.play(FadeOut(labels), FadeOut(arrows))
|
||||
|
||||
self.center_dot = center_dot
|
||||
|
||||
def setup_and_show_focal_sum(self):
|
||||
circle = self.circle
|
||||
ellipse = self.ellipse
|
||||
|
||||
focal_sum_point = VectorizedPoint()
|
||||
focal_sum_point.move_to(circle.get_top())
|
||||
dots = [self.ep_dot, self.center_dot]
|
||||
colors = map(Mobject.get_color, dots)
|
||||
|
||||
def get_foci():
|
||||
return map(Mobject.get_center, dots)
|
||||
|
||||
focal_lines, focal_lines_update_animation = \
|
||||
self.get_focal_lines_and_update(get_foci, focal_sum_point)
|
||||
distance_labels, distance_labels_update_animation = \
|
||||
self.get_distance_labels_and_update(focal_lines, colors)
|
||||
sum_expression, numbers, number_updates = \
|
||||
self.get_sum_expression_and_update(
|
||||
focal_lines, colors,
|
||||
lambda mob: mob.to_edge(RIGHT).shift(UP)
|
||||
)
|
||||
|
||||
to_add = self.focal_sum_things_to_add = [
|
||||
focal_lines_update_animation,
|
||||
distance_labels_update_animation,
|
||||
sum_expression,
|
||||
] + list(number_updates)
|
||||
|
||||
self.play(
|
||||
ShowCreation(focal_lines),
|
||||
Write(distance_labels),
|
||||
FadeIn(sum_expression),
|
||||
Write(numbers),
|
||||
run_time=1
|
||||
)
|
||||
self.wait()
|
||||
self.add(*to_add)
|
||||
|
||||
points = [
|
||||
ellipse.get_bottom(),
|
||||
circle.point_from_proportion(0.9),
|
||||
ellipse.get_top(),
|
||||
ellipse.point_from_proportion(0.7),
|
||||
]
|
||||
for point in points:
|
||||
self.play(
|
||||
focal_sum_point.move_to, point
|
||||
)
|
||||
self.wait()
|
||||
self.remove(*to_add)
|
||||
self.play(*map(FadeOut, [
|
||||
focal_lines, distance_labels,
|
||||
sum_expression, numbers
|
||||
]))
|
||||
|
||||
self.set_variables_as_attrs(
|
||||
focal_lines, focal_lines_update_animation,
|
||||
focal_sum_point,
|
||||
distance_labels, distance_labels_update_animation,
|
||||
sum_expression,
|
||||
numbers, number_updates
|
||||
)
|
||||
|
||||
def show_circle_radius(self):
|
||||
circle = self.circle
|
||||
center = circle.get_center()
|
||||
point = circle.get_right()
|
||||
color = GREEN
|
||||
radius = Line(center, point, color=color)
|
||||
radius_measurement = DecimalNumber(radius.get_length())
|
||||
radius_measurement.set_color(color)
|
||||
radius_measurement.next_to(radius, UP, SMALL_BUFF)
|
||||
radius_measurement.add_to_back(
|
||||
radius_measurement.copy().set_stroke(BLACK, 5)
|
||||
)
|
||||
group = VGroup(radius, radius_measurement)
|
||||
group.rotate(30 * DEGREES, about_point=center)
|
||||
|
||||
self.play(ShowCreation(radius))
|
||||
self.play(Write(radius_measurement))
|
||||
self.wait()
|
||||
self.play(Rotating(
|
||||
group,
|
||||
rate_func=smooth,
|
||||
run_time=7,
|
||||
about_point=center
|
||||
))
|
||||
self.play(FadeOut(group))
|
||||
|
||||
def limit_to_just_one_line(self):
|
||||
lines = self.lines
|
||||
ghost_lines = self.ghost_lines
|
||||
ep_dot = self.ep_dot
|
||||
|
||||
index = int(0.2 * len(lines))
|
||||
line = lines[index]
|
||||
ghost_line = ghost_lines[index]
|
||||
to_fade = VGroup(*list(lines) + list(ghost_lines))
|
||||
to_fade.remove(line, ghost_line)
|
||||
|
||||
P_dot = Dot(line.saved_state.get_end())
|
||||
P_label = TexMobject("P")
|
||||
P_label.next_to(P_dot, UP, SMALL_BUFF)
|
||||
|
||||
self.add_foreground_mobjects(self.ellipse)
|
||||
self.play(LaggedStart(Restore, lines))
|
||||
self.play(
|
||||
FadeOut(to_fade),
|
||||
line.set_stroke, {"width": 3},
|
||||
ReplacementTransform(ep_dot.copy(), P_dot),
|
||||
)
|
||||
self.play(FadeInFromDown(P_label))
|
||||
self.wait()
|
||||
|
||||
for l in lines:
|
||||
l.generate_target()
|
||||
l.target.rotate(
|
||||
90 * DEGREES,
|
||||
about_point=l.get_center()
|
||||
)
|
||||
|
||||
self.set_variables_as_attrs(
|
||||
line, ghost_line,
|
||||
P_dot, P_label
|
||||
)
|
||||
|
||||
def look_at_perpendicular_bisector(self):
|
||||
# Alright, this method's gonna blow up. Let's go!
|
||||
circle = self.circle
|
||||
ellipse = self.ellipse
|
||||
ellipse.save_state()
|
||||
lines = self.lines
|
||||
line = self.line
|
||||
ghost_lines = self.ghost_lines
|
||||
ghost_line = self.ghost_line
|
||||
P_dot = self.P_dot
|
||||
P_label = self.P_label
|
||||
|
||||
elbow = self.get_elbow(line)
|
||||
self.play(
|
||||
MoveToTarget(line, path_arc=90 * DEGREES),
|
||||
ShowCreation(elbow)
|
||||
)
|
||||
|
||||
# Perpendicular bisector label
|
||||
label = TextMobject("``Perpendicular bisector''")
|
||||
label.scale(0.75)
|
||||
label.set_color(YELLOW)
|
||||
label.next_to(ORIGIN, UP, MED_SMALL_BUFF)
|
||||
label.add_background_rectangle()
|
||||
angle = line.get_angle() + np.pi
|
||||
label.rotate(angle, about_point=ORIGIN)
|
||||
label.shift(line.get_center())
|
||||
|
||||
# Dot defining Q point
|
||||
Q_dot = Dot(color=GREEN)
|
||||
Q_dot.move_to(self.focal_sum_point)
|
||||
focal_sum_point_animation = NormalAnimationAsContinualAnimation(
|
||||
MaintainPositionRelativeTo(
|
||||
self.focal_sum_point, Q_dot
|
||||
)
|
||||
)
|
||||
self.add(focal_sum_point_animation)
|
||||
Q_dot.move_to(line.point_from_proportion(0.9))
|
||||
Q_dot.save_state()
|
||||
|
||||
Q_label = TexMobject("Q")
|
||||
Q_label.scale(0.7)
|
||||
Q_label.match_color(Q_dot)
|
||||
Q_label.add_to_back(Q_label.copy().set_stroke(BLACK, 5))
|
||||
Q_label.next_to(Q_dot, UL, buff=0)
|
||||
Q_label_animation = NormalAnimationAsContinualAnimation(
|
||||
MaintainPositionRelativeTo(Q_label, Q_dot)
|
||||
)
|
||||
|
||||
# Pretty hacky...
|
||||
def distance_label_shift_update(label):
|
||||
line = self.focal_lines[0]
|
||||
if line.get_end()[0] > line.get_start()[0]:
|
||||
vect = label.get_center() - line.get_center()
|
||||
label.shift(-2 * vect)
|
||||
distance_label_shift_update_animation = ContinualUpdateFromFunc(
|
||||
self.distance_labels[0],
|
||||
distance_label_shift_update
|
||||
)
|
||||
self.focal_sum_things_to_add.append(
|
||||
distance_label_shift_update_animation
|
||||
)
|
||||
|
||||
# Define QP line
|
||||
QP_line = Line(LEFT, RIGHT)
|
||||
QP_line.match_style(self.focal_lines)
|
||||
QP_line_update = ContinualUpdateFromFunc(
|
||||
QP_line, lambda l: l.put_start_and_end_on(
|
||||
Q_dot.get_center(), P_dot.get_center(),
|
||||
)
|
||||
)
|
||||
|
||||
QE_line = Line(LEFT, RIGHT)
|
||||
QE_line.set_stroke(YELLOW, 3)
|
||||
QE_line_update = ContinualUpdateFromFunc(
|
||||
QE_line, lambda l: l.put_start_and_end_on(
|
||||
Q_dot.get_center(),
|
||||
self.get_eccentricity_point()
|
||||
)
|
||||
)
|
||||
|
||||
# Define similar triangles
|
||||
triangles = VGroup(*[
|
||||
Polygon(
|
||||
Q_dot.get_center(),
|
||||
line.get_center(),
|
||||
end_point,
|
||||
fill_opacity=1,
|
||||
)
|
||||
for end_point in [
|
||||
P_dot.get_center(),
|
||||
self.get_eccentricity_point()
|
||||
]
|
||||
])
|
||||
triangles.set_color_by_gradient(PINK, GREEN)
|
||||
triangles.set_stroke(WHITE, 2)
|
||||
|
||||
# Add even more distant label updates
|
||||
def distance_label_rotate_update(label):
|
||||
QE_line_update.update(0)
|
||||
angle = QP_line.get_angle() - QE_line.get_angle()
|
||||
label.rotate(angle, about_point=Q_dot.get_center())
|
||||
return label
|
||||
|
||||
distance_label_rotate_update_animation = ContinualUpdateFromFunc(
|
||||
self.distance_labels[0],
|
||||
distance_label_rotate_update
|
||||
)
|
||||
|
||||
# Hook up line to P to P_dot
|
||||
radial_line = DashedLine(ORIGIN, 3 * RIGHT)
|
||||
radial_line_update = UpdateFromFunc(
|
||||
radial_line, lambda l: l.put_start_and_end_on(
|
||||
circle.get_center(),
|
||||
P_dot.get_center()
|
||||
)
|
||||
)
|
||||
|
||||
def put_dot_at_intersection(dot):
|
||||
point = line_intersection(
|
||||
line.get_start_and_end(),
|
||||
radial_line.get_start_and_end()
|
||||
)
|
||||
dot.move_to(point)
|
||||
return dot
|
||||
|
||||
keep_Q_dot_at_intersection = UpdateFromFunc(
|
||||
Q_dot, put_dot_at_intersection
|
||||
)
|
||||
Q_dot.restore()
|
||||
|
||||
ghost_line_update_animation = UpdateFromFunc(
|
||||
ghost_line, lambda l: l.put_start_and_end_on(
|
||||
self.get_eccentricity_point(),
|
||||
P_dot.get_center()
|
||||
)
|
||||
)
|
||||
|
||||
def update_perp_bisector(line):
|
||||
line.scale(ghost_line.get_length() / line.get_length())
|
||||
line.rotate(ghost_line.get_angle() - line.get_angle())
|
||||
line.rotate(90 * DEGREES)
|
||||
line.move_to(ghost_line)
|
||||
perp_bisector_update_animation = UpdateFromFunc(
|
||||
line, update_perp_bisector
|
||||
)
|
||||
elbow_update_animation = UpdateFromFunc(
|
||||
elbow,
|
||||
lambda e: Transform(e, self.get_elbow(line)).update(1)
|
||||
)
|
||||
|
||||
P_dot_movement_updates = [
|
||||
radial_line_update,
|
||||
keep_Q_dot_at_intersection,
|
||||
MaintainPositionRelativeTo(
|
||||
P_label, P_dot
|
||||
),
|
||||
ghost_line_update_animation,
|
||||
perp_bisector_update_animation,
|
||||
elbow_update_animation,
|
||||
]
|
||||
|
||||
# Comment for tangency
|
||||
sum_rect = SurroundingRectangle(
|
||||
self.numbers[-1]
|
||||
)
|
||||
tangency_comment = TextMobject(
|
||||
"Always $\\ge$ radius"
|
||||
)
|
||||
tangency_comment.next_to(
|
||||
sum_rect, DOWN,
|
||||
aligned_edge=RIGHT
|
||||
)
|
||||
VGroup(sum_rect, tangency_comment).set_color(GREEN)
|
||||
|
||||
# Why is this needed?!?
|
||||
self.add(*self.focal_sum_things_to_add)
|
||||
self.wait(0)
|
||||
self.remove(*self.focal_sum_things_to_add)
|
||||
|
||||
# Show label
|
||||
self.play(Write(label))
|
||||
self.wait()
|
||||
|
||||
# Show Q_dot moving about a little
|
||||
self.play(
|
||||
FadeOut(label),
|
||||
FadeIn(self.focal_lines),
|
||||
FadeIn(self.distance_labels),
|
||||
FadeIn(self.sum_expression),
|
||||
FadeIn(self.numbers),
|
||||
ellipse.set_stroke, {"width": 0.5},
|
||||
)
|
||||
self.add(*self.focal_sum_things_to_add)
|
||||
self.play(
|
||||
FadeInFromDown(Q_label),
|
||||
GrowFromCenter(Q_dot)
|
||||
)
|
||||
self.add_foreground_mobjects(Q_dot)
|
||||
self.add(Q_label_animation)
|
||||
self.play(
|
||||
Q_dot.move_to, line.point_from_proportion(0.05),
|
||||
rate_func=there_and_back,
|
||||
run_time=4
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Show similar triangles
|
||||
self.play(
|
||||
FadeIn(triangles[0]),
|
||||
ShowCreation(QP_line),
|
||||
Animation(elbow),
|
||||
)
|
||||
self.add(QP_line_update)
|
||||
for i in range(3):
|
||||
self.play(
|
||||
FadeIn(triangles[(i + 1) % 2]),
|
||||
FadeOut(triangles[i % 2]),
|
||||
Animation(self.distance_labels),
|
||||
Animation(elbow)
|
||||
)
|
||||
self.play(
|
||||
FadeOut(triangles[1]),
|
||||
Animation(self.distance_labels)
|
||||
)
|
||||
|
||||
# Move first distance label
|
||||
# (boy, this got messy...hopefully no one ever has
|
||||
# to read this.)
|
||||
angle = QP_line.get_angle() - QE_line.get_angle()
|
||||
Q_point = Q_dot.get_center()
|
||||
for x in range(2):
|
||||
self.play(ShowCreationThenDestruction(QE_line))
|
||||
distance_label_copy = self.distance_labels[0].copy()
|
||||
self.play(
|
||||
ApplyFunction(
|
||||
distance_label_rotate_update,
|
||||
distance_label_copy,
|
||||
path_arc=angle
|
||||
),
|
||||
Rotate(QE_line, angle, about_point=Q_point)
|
||||
)
|
||||
self.play(FadeOut(QE_line))
|
||||
self.remove(distance_label_copy)
|
||||
self.add(distance_label_rotate_update_animation)
|
||||
self.focal_sum_things_to_add.append(
|
||||
distance_label_rotate_update_animation
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
Q_dot.move_to, line.point_from_proportion(0),
|
||||
run_time=4,
|
||||
rate_func=there_and_back
|
||||
)
|
||||
|
||||
# Trace out ellipse
|
||||
self.play(ShowCreation(radial_line))
|
||||
self.wait()
|
||||
self.play(
|
||||
ApplyFunction(put_dot_at_intersection, Q_dot),
|
||||
run_time=3,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
Rotating(
|
||||
P_dot,
|
||||
about_point=circle.get_center(),
|
||||
rate_func=bezier([0, 0, 1, 1]),
|
||||
run_time=10,
|
||||
),
|
||||
ellipse.restore,
|
||||
*P_dot_movement_updates
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Talk through tangency
|
||||
self.play(
|
||||
ShowCreation(sum_rect),
|
||||
Write(tangency_comment),
|
||||
)
|
||||
points = [line.get_end(), line.get_start(), Q_dot.get_center()]
|
||||
run_times = [1, 3, 2]
|
||||
for point, run_time in zip(points, run_times):
|
||||
self.play(Q_dot.move_to, point, run_time=run_time)
|
||||
self.wait()
|
||||
|
||||
self.remove(*self.focal_sum_things_to_add)
|
||||
self.play(*map(FadeOut, [
|
||||
radial_line,
|
||||
QP_line,
|
||||
P_dot, P_label,
|
||||
Q_dot, Q_label,
|
||||
elbow,
|
||||
self.distance_labels,
|
||||
self.numbers,
|
||||
self.sum_expression,
|
||||
sum_rect,
|
||||
tangency_comment,
|
||||
]))
|
||||
self.wait()
|
||||
|
||||
# Show all lines
|
||||
lines.remove(line)
|
||||
ghost_lines.remove(ghost_line)
|
||||
for line in lines:
|
||||
line.generate_target()
|
||||
line.target.rotate(90 * DEGREES)
|
||||
self.play(
|
||||
LaggedStart(FadeIn, ghost_lines),
|
||||
LaggedStart(FadeIn, lines),
|
||||
)
|
||||
self.play(LaggedStart(MoveToTarget, lines))
|
||||
self.wait()
|
||||
|
||||
def show_orbiting_planet(self):
|
||||
ellipse = self.ellipse
|
||||
ep_dot = self.ep_dot
|
||||
planet = ImageMobject("earth")
|
||||
planet.scale_to_fit_height(0.25)
|
||||
orbit = Orbiting(planet, ep_dot, ellipse)
|
||||
|
||||
lines = self.lines
|
||||
|
||||
def update_lines(lines):
|
||||
for gl, line in zip(self.ghost_lines, lines):
|
||||
intersection = line_intersection(
|
||||
[self.circle.get_center(), gl.get_end()],
|
||||
line.get_start_and_end()
|
||||
)
|
||||
distance = np.linalg.norm(
|
||||
intersection - planet.get_center()
|
||||
)
|
||||
if distance < 0.025:
|
||||
line.set_color(BLUE)
|
||||
self.add(line)
|
||||
else:
|
||||
line.set_color(WHITE)
|
||||
|
||||
lines_update_animation = ContinualUpdateFromFunc(
|
||||
lines, update_lines
|
||||
)
|
||||
|
||||
self.add(orbit)
|
||||
self.add(lines_update_animation)
|
||||
self.add_foreground_mobjects(planet)
|
||||
self.wait(12)
|
||||
|
||||
|
||||
class EndOfGeometryProofiness(Scene):
|
||||
def construct(self):
|
||||
pass
|
||||
|
|
Loading…
Add table
Reference in a new issue