mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Up to ShowRotationUnderStereographicProjection
This commit is contained in:
parent
731748df4c
commit
6e1f9cd507
1 changed files with 597 additions and 81 deletions
|
@ -61,53 +61,37 @@ def stereo_project_point(point, axis=0, r=1, max_norm=1000):
|
||||||
return point
|
return point
|
||||||
|
|
||||||
|
|
||||||
# TODO, improve this function...by a lot.
|
def stereo_project(mobject, axis=0, r=1, outer_r=10, **kwargs):
|
||||||
def stereo_project(mobject, axis=0, r=1, **kwargs):
|
epsilon = 1
|
||||||
epsilon = 0.01
|
|
||||||
for submob in mobject.family_members_with_points():
|
for submob in mobject.family_members_with_points():
|
||||||
eq_neg_1 = submob.points[:, axis] == -r
|
points = submob.points
|
||||||
if np.any(eq_neg_1):
|
n = len(points)
|
||||||
# Whoof, pretty messy, and not general.
|
for i in range(n):
|
||||||
# Clean this up?
|
if points[i, axis] == -r:
|
||||||
N = len(eq_neg_1)
|
js = it.chain(
|
||||||
neg_1_indices = np.arange(N)[eq_neg_1]
|
range(i + 1, n),
|
||||||
|
range(i - 1, -1, -1)
|
||||||
if len(neg_1_indices) == 2:
|
|
||||||
# Dumb hack
|
|
||||||
submob.points = np.array([
|
|
||||||
*submob.points[3:-1],
|
|
||||||
*submob.points[0:4],
|
|
||||||
])
|
|
||||||
eq_neg_1 = submob.points[:, axis] == -r
|
|
||||||
neg_1_indices = np.arange(N)[eq_neg_1]
|
|
||||||
|
|
||||||
i = neg_1_indices[0]
|
|
||||||
p1 = interpolate(
|
|
||||||
submob.points[i - 1],
|
|
||||||
submob.points[i],
|
|
||||||
1 - epsilon
|
|
||||||
)
|
)
|
||||||
p2 = interpolate(
|
for j in js:
|
||||||
submob.points[i],
|
if points[j, axis] == -r:
|
||||||
submob.points[i + 1],
|
continue
|
||||||
epsilon
|
else:
|
||||||
)
|
vect = points[j] - points[i]
|
||||||
submob.points = np.array([
|
points[i] += epsilon * vect
|
||||||
*submob.points[:i],
|
break
|
||||||
p1,
|
|
||||||
interpolate(p1, p2, 1 / 3),
|
|
||||||
interpolate(p1, p2, 2 / 3),
|
|
||||||
p2,
|
|
||||||
*submob.points[i + 1:],
|
|
||||||
])
|
|
||||||
submob.apply_function(
|
submob.apply_function(
|
||||||
lambda p: stereo_project_point(
|
lambda p: stereo_project_point(p, axis, r, **kwargs)
|
||||||
p, axis=axis, **kwargs
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# If all points are outside a certain range, this
|
||||||
|
# shouldn't be displayed
|
||||||
norms = np.apply_along_axis(get_norm, 1, submob.points)
|
norms = np.apply_along_axis(get_norm, 1, submob.points)
|
||||||
if np.all(norms > FRAME_WIDTH):
|
if np.all(norms > outer_r):
|
||||||
submob.points[:] = 0
|
# TODO, instead set opacity?
|
||||||
|
# submob.points[:, :] = 0
|
||||||
|
submob.set_fill(opacity=0)
|
||||||
|
submob.set_stroke(opacity=0)
|
||||||
|
|
||||||
return mobject
|
return mobject
|
||||||
|
|
||||||
|
|
||||||
|
@ -199,6 +183,9 @@ class PushPin(SVGMobject):
|
||||||
SVGMobject.__init__(self, **kwargs)
|
SVGMobject.__init__(self, **kwargs)
|
||||||
self.rotate(20 * DEGREES)
|
self.rotate(20 * DEGREES)
|
||||||
|
|
||||||
|
def pin_to(self, point):
|
||||||
|
self.move_to(point, DR)
|
||||||
|
|
||||||
|
|
||||||
class Hand(SVGMobject):
|
class Hand(SVGMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
|
@ -229,8 +216,6 @@ class CheckeredCircle(Circle):
|
||||||
for i, color in enumerate(self.colors):
|
for i, color in enumerate(self.colors):
|
||||||
self[i::n_colors].set_color(color)
|
self[i::n_colors].set_color(color)
|
||||||
|
|
||||||
# self.set_width(2 * self.radius)
|
|
||||||
# self.set_stroke(width=self.stroke_width)
|
|
||||||
|
|
||||||
# Abstract scenes
|
# Abstract scenes
|
||||||
class SpecialThreeDScene(ThreeDScene):
|
class SpecialThreeDScene(ThreeDScene):
|
||||||
|
@ -1374,71 +1359,78 @@ class StereographicProjectionTitle(Scene):
|
||||||
class IntroduceStereographicProjection(MovingCameraScene):
|
class IntroduceStereographicProjection(MovingCameraScene):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"n_sample_points": 16,
|
"n_sample_points": 16,
|
||||||
|
"circle_config": {
|
||||||
|
"n_pieces": 16,
|
||||||
|
"radius": 2,
|
||||||
|
"stroke_width": 7,
|
||||||
|
},
|
||||||
|
"example_angles": [
|
||||||
|
30 * DEGREES,
|
||||||
|
120 * DEGREES,
|
||||||
|
240 * DEGREES,
|
||||||
|
80 * DEGREES,
|
||||||
|
-60 * DEGREES,
|
||||||
|
135 * DEGREES,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
def construct(self):
|
def construct(self):
|
||||||
self.setup_plane()
|
self.setup_plane()
|
||||||
self.draw_lines()
|
self.draw_lines()
|
||||||
self.describe_individual_points()
|
self.describe_individual_points()
|
||||||
self.point_at_infinity()
|
self.remind_that_most_points_are_not_projected()
|
||||||
|
|
||||||
def setup_plane(self):
|
def setup_plane(self):
|
||||||
plane = self.plane = ComplexPlane(
|
self.plane = self.get_plane()
|
||||||
unit_size=2,
|
self.circle = self.get_circle()
|
||||||
color=GREY,
|
self.circle_shadow = self.get_circle_shadow()
|
||||||
secondary_color=DARK_GREY,
|
|
||||||
x_radius=FRAME_WIDTH,
|
|
||||||
y_radius=FRAME_HEIGHT,
|
|
||||||
)
|
|
||||||
plane.add_coordinates()
|
|
||||||
circle = self.circle = CheckeredCircle(radius=2)
|
|
||||||
circle.set_stroke(width=7)
|
|
||||||
circle_shadow = self.circle_shadow = circle.copy()
|
|
||||||
circle_shadow.set_stroke(opacity=0.5)
|
|
||||||
|
|
||||||
self.add(plane)
|
self.add(self.plane)
|
||||||
self.add(circle_shadow)
|
self.add(self.circle_shadow)
|
||||||
self.add(circle)
|
self.add(self.circle)
|
||||||
|
|
||||||
def draw_lines(self):
|
def draw_lines(self):
|
||||||
plane = self.plane
|
plane = self.plane
|
||||||
circle = self.circle
|
circle = self.circle
|
||||||
for part in circle:
|
circle.save_state()
|
||||||
part.generate_target()
|
circle.generate_target()
|
||||||
self.project_mobject(part.target)
|
self.project_mobject(circle.target)
|
||||||
|
|
||||||
circle_points = self.get_sample_circle_points()
|
circle_points = self.get_sample_circle_points()
|
||||||
dots = VGroup(*[Dot(p) for p in circle_points])
|
dots = VGroup(*[Dot(p) for p in circle_points])
|
||||||
dots.set_sheen(-0.2, DR)
|
dots.set_sheen(-0.2, DR)
|
||||||
|
dots.set_stroke(DARK_GREY, 2, background=True)
|
||||||
|
arrows = VGroup()
|
||||||
for dot in dots:
|
for dot in dots:
|
||||||
dot.scale(0.75)
|
dot.scale(0.75)
|
||||||
dot.generate_target()
|
dot.generate_target()
|
||||||
dot.target.move_to(
|
dot.target.move_to(
|
||||||
self.project(dot.get_center())
|
self.project(dot.get_center())
|
||||||
)
|
)
|
||||||
|
arrow = Arrow(
|
||||||
|
dot.get_center(),
|
||||||
|
dot.target.get_center(),
|
||||||
|
)
|
||||||
|
arrows.add(arrow)
|
||||||
neg_one_point = plane.number_to_point(-1)
|
neg_one_point = plane.number_to_point(-1)
|
||||||
neg_one_dot = Dot(neg_one_point)
|
neg_one_dot = Dot(neg_one_point)
|
||||||
neg_one_dot.set_fill(YELLOW)
|
neg_one_dot.set_fill(YELLOW)
|
||||||
|
|
||||||
lines = VGroup(*[
|
lines = self.get_lines()
|
||||||
Line(neg_one_point, point)
|
|
||||||
for point in circle_points
|
|
||||||
])
|
|
||||||
for line in lines:
|
|
||||||
line.scale(
|
|
||||||
10 / line.get_length(),
|
|
||||||
about_point=neg_one_point
|
|
||||||
)
|
|
||||||
line.set_stroke(YELLOW, 1)
|
|
||||||
|
|
||||||
special_index = self.n_sample_points // 2 + 1
|
special_index = self.n_sample_points // 2 + 1
|
||||||
line = lines[special_index]
|
line = lines[special_index]
|
||||||
dot = dots[special_index]
|
dot = dots[special_index]
|
||||||
|
arrow = arrows[special_index]
|
||||||
dot_target_outline = dot.target.copy()
|
dot_target_outline = dot.target.copy()
|
||||||
dot_target_outline.set_stroke(RED, 2)
|
dot_target_outline.set_stroke(RED, 2)
|
||||||
dot_target_outline.set_fill(opacity=0)
|
dot_target_outline.set_fill(opacity=0)
|
||||||
dot_target_outline.scale(1.5)
|
dot_target_outline.scale(1.5)
|
||||||
|
|
||||||
|
v_line = Line(UP, DOWN)
|
||||||
|
v_line.set_height(FRAME_HEIGHT)
|
||||||
|
v_line.set_stroke(RED, 5)
|
||||||
|
|
||||||
self.play(LaggedStart(FadeInFromLarge, dots))
|
self.play(LaggedStart(FadeInFromLarge, dots))
|
||||||
self.play(FadeInFromLarge(neg_one_dot))
|
self.play(FadeInFromLarge(neg_one_dot))
|
||||||
self.add(lines, neg_one_dot, dots)
|
self.add(lines, neg_one_dot, dots)
|
||||||
|
@ -1449,23 +1441,184 @@ class IntroduceStereographicProjection(MovingCameraScene):
|
||||||
line.set_stroke, {"width": 4},
|
line.set_stroke, {"width": 4},
|
||||||
)
|
)
|
||||||
self.play(ShowCreation(dot_target_outline))
|
self.play(ShowCreation(dot_target_outline))
|
||||||
|
self.play(ShowCreationThenDestruction(v_line))
|
||||||
self.play(MoveToTarget(dot))
|
self.play(MoveToTarget(dot))
|
||||||
self.wait()
|
self.wait()
|
||||||
self.play(
|
self.play(
|
||||||
lines.set_stroke, {"width": 1},
|
lines.set_stroke, {"width": 1},
|
||||||
FadeOut(dot_target_outline),
|
FadeOut(dot_target_outline),
|
||||||
LaggedStart(MoveToTarget, dots),
|
MoveToTarget(circle),
|
||||||
LaggedStart(MoveToTarget, circle),
|
*map(MoveToTarget, dots),
|
||||||
|
run_time=2,
|
||||||
)
|
)
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
def describe_individual_points(self):
|
self.lines = lines
|
||||||
pass
|
self.dots = dots
|
||||||
|
|
||||||
def point_at_infinity(self):
|
def describe_individual_points(self):
|
||||||
pass
|
plane = self.plane
|
||||||
|
one_point, zero_point, i_point, neg_i_point, neg_one_point = [
|
||||||
|
plane.number_to_point(n)
|
||||||
|
for n in [1, 0, complex(0, 1), complex(0, -1), -1]
|
||||||
|
]
|
||||||
|
i_pin = PushPin()
|
||||||
|
i_pin.pin_to(i_point)
|
||||||
|
neg_i_pin = PushPin()
|
||||||
|
neg_i_pin.pin_to(neg_i_point)
|
||||||
|
|
||||||
|
dot = Dot()
|
||||||
|
dot.set_stroke(RED, 3)
|
||||||
|
dot.set_fill(opacity=0)
|
||||||
|
dot.scale(1.5)
|
||||||
|
dot.move_to(one_point)
|
||||||
|
|
||||||
|
arc1 = Arc(angle=TAU / 4, radius=2)
|
||||||
|
arc2 = Arc(
|
||||||
|
angle=85 * DEGREES, radius=2,
|
||||||
|
start_angle=TAU / 4,
|
||||||
|
)
|
||||||
|
arc3 = Arc(
|
||||||
|
angle=-85 * DEGREES, radius=2,
|
||||||
|
start_angle=-TAU / 4,
|
||||||
|
)
|
||||||
|
VGroup(arc1, arc2, arc3).set_stroke(RED)
|
||||||
|
|
||||||
|
frame = self.camera_frame
|
||||||
|
frame_height_tracker = ValueTracker(frame.get_height())
|
||||||
|
frame_height_growth = ContinualGrowValue(
|
||||||
|
frame_height_tracker, rate=0.4
|
||||||
|
)
|
||||||
|
|
||||||
|
neg_one_tangent = VGroup(
|
||||||
|
Line(ORIGIN, UP),
|
||||||
|
Line(ORIGIN, DOWN),
|
||||||
|
)
|
||||||
|
neg_one_tangent.set_height(25)
|
||||||
|
neg_one_tangent.set_stroke(YELLOW, 5)
|
||||||
|
neg_one_tangent.move_to(neg_one_point)
|
||||||
|
|
||||||
|
self.play(ShowCreation(dot))
|
||||||
|
self.wait()
|
||||||
|
self.play(dot.move_to, zero_point)
|
||||||
|
self.wait()
|
||||||
|
dot.move_to(i_point)
|
||||||
|
self.play(ShowCreation(dot))
|
||||||
|
self.play(FadeInFrom(i_pin, UL))
|
||||||
|
self.wait()
|
||||||
|
self.play(
|
||||||
|
dot.move_to, neg_i_point,
|
||||||
|
path_arc=-60 * DEGREES
|
||||||
|
)
|
||||||
|
self.play(FadeInFrom(neg_i_pin, UL))
|
||||||
|
self.wait()
|
||||||
|
self.play(
|
||||||
|
dot.move_to, one_point,
|
||||||
|
path_arc=-60 * DEGREES
|
||||||
|
)
|
||||||
|
frame.add_updater(
|
||||||
|
lambda m: m.set_height(frame_height_tracker.get_value())
|
||||||
|
)
|
||||||
|
|
||||||
|
triplets = [
|
||||||
|
(arc1, i_point, TAU / 4),
|
||||||
|
(arc2, neg_one_point, TAU / 4),
|
||||||
|
(arc3, neg_one_point, -TAU / 4),
|
||||||
|
]
|
||||||
|
for arc, point, path_arc in triplets:
|
||||||
|
self.play(
|
||||||
|
ShowCreation(arc),
|
||||||
|
dot.move_to, point, path_arc=path_arc,
|
||||||
|
run_time=2
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.play(
|
||||||
|
ApplyFunction(self.project_mobject, arc, run_time=2)
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.play(FadeOut(arc))
|
||||||
|
self.wait()
|
||||||
|
if arc is arc1:
|
||||||
|
self.add(frame, frame_height_growth)
|
||||||
|
elif arc is arc2:
|
||||||
|
self.play(dot.move_to, neg_i_point)
|
||||||
|
frame_height_growth.begin_wind_down()
|
||||||
|
self.wait(2)
|
||||||
|
self.play(*map(ShowCreation, neg_one_tangent))
|
||||||
|
self.wait()
|
||||||
|
self.play(FadeOut(neg_one_tangent))
|
||||||
|
self.wait(2)
|
||||||
|
frame.clear_updaters()
|
||||||
|
self.play(
|
||||||
|
frame.set_height, FRAME_HEIGHT,
|
||||||
|
self.lines.set_stroke, {"width": 0.5},
|
||||||
|
FadeOut(self.dots),
|
||||||
|
FadeOut(dot),
|
||||||
|
run_time=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
def remind_that_most_points_are_not_projected(self):
|
||||||
|
plane = self.plane
|
||||||
|
circle = self.circle
|
||||||
|
|
||||||
|
sample_values = [0, complex(1, 1), complex(-2, -1)]
|
||||||
|
sample_points = [
|
||||||
|
plane.number_to_point(value)
|
||||||
|
for value in sample_values
|
||||||
|
]
|
||||||
|
sample_dots = VGroup(*[Dot(p) for p in sample_points])
|
||||||
|
sample_dots.set_fill(GREEN)
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
FadeOut(self.lines),
|
||||||
|
Restore(circle),
|
||||||
|
)
|
||||||
|
|
||||||
|
for value, dot in zip(sample_values, sample_dots):
|
||||||
|
cross = Cross(dot)
|
||||||
|
cross.scale(2)
|
||||||
|
label = Integer(value)
|
||||||
|
label.next_to(dot, UR, SMALL_BUFF)
|
||||||
|
self.play(
|
||||||
|
FadeInFromLarge(dot, 3),
|
||||||
|
FadeInFromDown(label)
|
||||||
|
)
|
||||||
|
self.play(ShowCreation(cross))
|
||||||
|
self.play(*map(FadeOut, [dot, cross, label]))
|
||||||
|
self.wait()
|
||||||
|
self.play(
|
||||||
|
FadeIn(self.lines),
|
||||||
|
MoveToTarget(circle, run_time=2),
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
|
||||||
# Helpers
|
# Helpers
|
||||||
|
def get_plane(self):
|
||||||
|
plane = ComplexPlane(
|
||||||
|
unit_size=2,
|
||||||
|
color=GREY,
|
||||||
|
secondary_color=DARK_GREY,
|
||||||
|
x_radius=FRAME_WIDTH,
|
||||||
|
y_radius=FRAME_HEIGHT,
|
||||||
|
stroke_width=2,
|
||||||
|
)
|
||||||
|
plane.add_coordinates()
|
||||||
|
return plane
|
||||||
|
|
||||||
|
def get_circle(self):
|
||||||
|
circle = CheckeredCircle(
|
||||||
|
**self.circle_config
|
||||||
|
)
|
||||||
|
circle.set_stroke(width=7)
|
||||||
|
return circle
|
||||||
|
|
||||||
|
def get_circle_shadow(self):
|
||||||
|
circle_shadow = CheckeredCircle(
|
||||||
|
**self.circle_config
|
||||||
|
)
|
||||||
|
circle_shadow.set_stroke(opacity=0.65)
|
||||||
|
return circle_shadow
|
||||||
|
|
||||||
def get_sample_circle_points(self):
|
def get_sample_circle_points(self):
|
||||||
plane = self.plane
|
plane = self.plane
|
||||||
n = self.n_sample_points
|
n = self.n_sample_points
|
||||||
|
@ -1478,8 +1631,371 @@ class IntroduceStereographicProjection(MovingCameraScene):
|
||||||
for number in numbers
|
for number in numbers
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_lines(self):
|
||||||
|
plane = self.plane
|
||||||
|
neg_one_point = plane.number_to_point(-1)
|
||||||
|
circle_points = self.get_sample_circle_points()
|
||||||
|
|
||||||
|
lines = VGroup(*[
|
||||||
|
Line(neg_one_point, point)
|
||||||
|
for point in circle_points
|
||||||
|
])
|
||||||
|
for line in lines:
|
||||||
|
line.scale(
|
||||||
|
20 / line.get_length(),
|
||||||
|
about_point=neg_one_point
|
||||||
|
)
|
||||||
|
line.set_stroke(YELLOW, 1)
|
||||||
|
return lines
|
||||||
|
|
||||||
def project(self, point):
|
def project(self, point):
|
||||||
return stereo_project_point(point, axis=0, r=2)
|
return stereo_project_point(point, axis=0, r=2)
|
||||||
|
|
||||||
def project_mobject(self, mobject):
|
def project_mobject(self, mobject):
|
||||||
return stereo_project(mobject, axis=0, r=2)
|
return stereo_project(mobject, axis=0, r=2, outer_r=6)
|
||||||
|
|
||||||
|
|
||||||
|
class IntroduceStereographicProjectionLinusView(IntroduceStereographicProjection):
|
||||||
|
def construct(self):
|
||||||
|
self.describe_individual_points()
|
||||||
|
self.point_at_infinity()
|
||||||
|
self.show_90_degree_rotation()
|
||||||
|
self.talk_through_90_degree_rotation()
|
||||||
|
self.show_four_rotations()
|
||||||
|
self.show_example_angles()
|
||||||
|
|
||||||
|
def describe_individual_points(self):
|
||||||
|
plane = self.plane = self.get_plane()
|
||||||
|
circle = self.circle = self.get_circle()
|
||||||
|
linus = self.linus =self.get_linus()
|
||||||
|
|
||||||
|
angles = np.arange(-135, 180, 45) * DEGREES
|
||||||
|
sample_numbers = [
|
||||||
|
np.exp(complex(0, angle))
|
||||||
|
for angle in angles
|
||||||
|
]
|
||||||
|
sample_points = [
|
||||||
|
plane.number_to_point(number)
|
||||||
|
for number in sample_numbers
|
||||||
|
]
|
||||||
|
projected_sample_points = [
|
||||||
|
self.project(point)
|
||||||
|
for point in sample_points
|
||||||
|
]
|
||||||
|
dots = VGroup(*[Dot() for x in range(8)])
|
||||||
|
dots.set_fill(WHITE)
|
||||||
|
dots.set_stroke(BLACK, 1)
|
||||||
|
|
||||||
|
def generate_dot_updater(circle_piece):
|
||||||
|
return lambda d: d.move_to(circle_piece.points[0])
|
||||||
|
|
||||||
|
for dot, piece in zip(dots, circle[::(len(circle) // 8)]):
|
||||||
|
dot.add_updater(generate_dot_updater(piece))
|
||||||
|
|
||||||
|
stot = "\\frac{\\sqrt{2}}{2}"
|
||||||
|
labels_tex = [
|
||||||
|
"\\left(-{}-{}i\\right)".format(stot, stot),
|
||||||
|
"-i",
|
||||||
|
"\\left({}-{}i\\right)".format(stot, stot),
|
||||||
|
"1",
|
||||||
|
"\\left({}+{}i\\right)".format(stot, stot),
|
||||||
|
"i",
|
||||||
|
"\\left(-{}+{}i\\right)".format(stot, stot),
|
||||||
|
]
|
||||||
|
labels = VGroup(*[TexMobject(tex) for tex in labels_tex])
|
||||||
|
vects = it.cycle([RIGHT, RIGHT])
|
||||||
|
arrows = VGroup()
|
||||||
|
for label, point, vect in zip(labels, projected_sample_points, vects):
|
||||||
|
arrow = Arrow(vect, ORIGIN)
|
||||||
|
arrow.next_to(point, vect, 2 * SMALL_BUFF)
|
||||||
|
arrows.add(arrow)
|
||||||
|
label.set_stroke(width=0, background=True)
|
||||||
|
if stot in label.get_tex_string():
|
||||||
|
label.set_height(0.8)
|
||||||
|
else:
|
||||||
|
label.set_height(0.5)
|
||||||
|
label.next_to(arrow, vect, SMALL_BUFF)
|
||||||
|
|
||||||
|
frame = self.camera_frame
|
||||||
|
frame.set_height(12)
|
||||||
|
|
||||||
|
self.add(linus)
|
||||||
|
self.add(circle, *dots)
|
||||||
|
self.play(
|
||||||
|
ApplyFunction(self.project_mobject, circle),
|
||||||
|
run_time=2
|
||||||
|
)
|
||||||
|
self.play(linus.change, "confused")
|
||||||
|
self.wait()
|
||||||
|
for i in [1, 0]:
|
||||||
|
self.play(
|
||||||
|
LaggedStart(GrowArrow, arrows[i::2]),
|
||||||
|
LaggedStart(Write, labels[i::2])
|
||||||
|
)
|
||||||
|
self.play(Blink(linus))
|
||||||
|
|
||||||
|
self.dots = dots
|
||||||
|
|
||||||
|
def point_at_infinity(self):
|
||||||
|
circle = self.circle
|
||||||
|
linus = self.linus
|
||||||
|
|
||||||
|
label = TextMobject(
|
||||||
|
"$-1$ is \\\\ at $\\pm \\infty$"
|
||||||
|
)
|
||||||
|
label.scale(2)
|
||||||
|
label.next_to(circle, LEFT, buff=1.25)
|
||||||
|
arrows = VGroup(*[
|
||||||
|
Vector(3 * v + 0.0 * RIGHT).next_to(label, v, buff=MED_LARGE_BUFF)
|
||||||
|
for v in [UP, DOWN]
|
||||||
|
])
|
||||||
|
arrows.set_color(YELLOW)
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
Write(label),
|
||||||
|
linus.change, "awe", label,
|
||||||
|
*map(GrowArrow, arrows)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.neg_one_label = VGroup(label, arrows)
|
||||||
|
|
||||||
|
def show_90_degree_rotation(self):
|
||||||
|
angle_tracker = ValueTracker(0)
|
||||||
|
circle = self.circle
|
||||||
|
linus = self.linus
|
||||||
|
hand = Hand()
|
||||||
|
hand.flip()
|
||||||
|
one_dot = self.dots[0]
|
||||||
|
hand.add_updater(
|
||||||
|
lambda h: h.move_to(one_dot.get_center(), RIGHT)
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_circle(circle):
|
||||||
|
angle = angle_tracker.get_value()
|
||||||
|
new_circle = self.get_circle()
|
||||||
|
new_circle.rotate(angle)
|
||||||
|
self.project_mobject(new_circle)
|
||||||
|
circle.become(new_circle)
|
||||||
|
|
||||||
|
circle.add_updater(update_circle)
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
FadeIn(hand),
|
||||||
|
one_dot.set_fill, RED,
|
||||||
|
)
|
||||||
|
for angle in 90 * DEGREES, 0:
|
||||||
|
self.play(
|
||||||
|
ApplyMethod(
|
||||||
|
angle_tracker.set_value, angle,
|
||||||
|
run_time=3,
|
||||||
|
),
|
||||||
|
linus.change, "confused", hand
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.play(Blink(linus))
|
||||||
|
|
||||||
|
self.hand = hand
|
||||||
|
self.angle_tracker = angle_tracker
|
||||||
|
|
||||||
|
def talk_through_90_degree_rotation(self):
|
||||||
|
linus = self.linus
|
||||||
|
dots = self.dots
|
||||||
|
one_dot = dots[0]
|
||||||
|
i_dot = dots[2]
|
||||||
|
neg_i_dot = dots[-2]
|
||||||
|
|
||||||
|
kwargs1 = {
|
||||||
|
"use_rectangular_stem": False,
|
||||||
|
"path_arc": -90 * DEGREES,
|
||||||
|
"buff": SMALL_BUFF,
|
||||||
|
}
|
||||||
|
kwargs2 = dict(kwargs1)
|
||||||
|
kwargs2["path_arc"] = -40 * DEGREES
|
||||||
|
arrows = VGroup(
|
||||||
|
Arrow(one_dot, i_dot, **kwargs1),
|
||||||
|
Arrow(i_dot, 6 * UP + LEFT, **kwargs2),
|
||||||
|
Arrow(6 * DOWN + LEFT, neg_i_dot, **kwargs2),
|
||||||
|
Arrow(neg_i_dot, one_dot, **kwargs1)
|
||||||
|
)
|
||||||
|
arrows.set_stroke(WHITE, 3)
|
||||||
|
one_to_i, i_to_neg_1, neg_one_to_neg_i, neg_i_to_one = arrows
|
||||||
|
|
||||||
|
for arrow in arrows:
|
||||||
|
self.play(
|
||||||
|
ShowCreation(arrow),
|
||||||
|
linus.look_at, arrow
|
||||||
|
)
|
||||||
|
self.wait(2)
|
||||||
|
|
||||||
|
self.arrows = arrows
|
||||||
|
|
||||||
|
def show_four_rotations(self):
|
||||||
|
angle_tracker = self.angle_tracker
|
||||||
|
linus = self.linus
|
||||||
|
hand = self.hand
|
||||||
|
linus.add_updater(lambda l: l.look_at(hand))
|
||||||
|
linus.add_updater(lambda l: l.eyes.next_to(l.body, UP, 0))
|
||||||
|
|
||||||
|
for angle in np.arange(TAU / 4, 5 * TAU / 4, TAU / 4):
|
||||||
|
self.play(
|
||||||
|
ApplyMethod(
|
||||||
|
angle_tracker.set_value, angle,
|
||||||
|
run_time=3,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.play(FadeOut(self.arrows))
|
||||||
|
|
||||||
|
def show_example_angles(self):
|
||||||
|
angle_tracker = self.angle_tracker
|
||||||
|
angle_tracker.set_value(0)
|
||||||
|
|
||||||
|
for angle in self.example_angles:
|
||||||
|
self.play(
|
||||||
|
ApplyMethod(
|
||||||
|
angle_tracker.set_value, angle,
|
||||||
|
run_time=2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
#
|
||||||
|
def get_linus(self):
|
||||||
|
linus = Linus()
|
||||||
|
linus.move_to(3 * RIGHT)
|
||||||
|
linus.to_edge(DOWN)
|
||||||
|
linus.look_at(ORIGIN)
|
||||||
|
return linus
|
||||||
|
|
||||||
|
|
||||||
|
class ShowRotationUnderStereographicProjection(IntroduceStereographicProjection):
|
||||||
|
def construct(self):
|
||||||
|
self.setup_plane()
|
||||||
|
self.apply_projection()
|
||||||
|
self.show_90_degree_rotation()
|
||||||
|
self.talk_through_90_degree_rotation()
|
||||||
|
self.show_four_rotations()
|
||||||
|
self.show_example_angles()
|
||||||
|
|
||||||
|
def apply_projection(self):
|
||||||
|
plane = self.plane
|
||||||
|
circle = self.circle
|
||||||
|
neg_one_point = plane.number_to_point(-1)
|
||||||
|
neg_one_dot = Dot(neg_one_point)
|
||||||
|
neg_one_dot.set_fill(YELLOW)
|
||||||
|
|
||||||
|
lines = self.get_lines()
|
||||||
|
|
||||||
|
def generate_dot_updater(circle_piece):
|
||||||
|
return lambda d: d.move_to(circle_piece.points[0])
|
||||||
|
|
||||||
|
for circ, color in [(self.circle_shadow, RED), (self.circle, WHITE)]:
|
||||||
|
for piece in circ[::(len(circ) // 8)]:
|
||||||
|
dot = Dot(color=color)
|
||||||
|
dot.set_fill(opacity=circ.get_stroke_opacity())
|
||||||
|
dot.add_updater(generate_dot_updater(piece))
|
||||||
|
self.add(dot)
|
||||||
|
|
||||||
|
self.add(lines, neg_one_dot)
|
||||||
|
self.play(*map(ShowCreation, lines))
|
||||||
|
self.play(
|
||||||
|
ApplyFunction(self.project_mobject, circle),
|
||||||
|
lines.set_stroke, {"width": 0.5},
|
||||||
|
self.camera_frame.set_height, 12,
|
||||||
|
run_time=2
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
def show_90_degree_rotation(self):
|
||||||
|
circle = self.circle
|
||||||
|
circle_shadow = self.circle_shadow
|
||||||
|
|
||||||
|
def get_rotated_one_point():
|
||||||
|
return circle_shadow[0].points[0]
|
||||||
|
|
||||||
|
def get_angle():
|
||||||
|
return angle_of_vector(get_rotated_one_point())
|
||||||
|
|
||||||
|
self.get_angle = get_angle
|
||||||
|
|
||||||
|
one_dot = Dot(color=RED)
|
||||||
|
one_dot.add_updater(
|
||||||
|
lambda m: m.move_to(get_rotated_one_point())
|
||||||
|
)
|
||||||
|
hand = Hand()
|
||||||
|
hand.move_to(one_dot.get_center(), LEFT)
|
||||||
|
|
||||||
|
def update_circle(circle):
|
||||||
|
new_circle = self.get_circle()
|
||||||
|
new_circle.rotate(get_angle())
|
||||||
|
self.project_mobject(new_circle)
|
||||||
|
circle.become(new_circle)
|
||||||
|
|
||||||
|
circle.add_updater(update_circle)
|
||||||
|
|
||||||
|
self.add(one_dot, hand)
|
||||||
|
hand.add_updater(
|
||||||
|
lambda h: h.move_to(one_dot.get_center(), LEFT)
|
||||||
|
)
|
||||||
|
self.play(
|
||||||
|
FadeInFrom(hand, RIGHT),
|
||||||
|
FadeInFromLarge(one_dot, 3),
|
||||||
|
)
|
||||||
|
for angle in 90 * DEGREES, -90 * DEGREES:
|
||||||
|
self.play(
|
||||||
|
Rotate(circle_shadow, angle, run_time=3),
|
||||||
|
)
|
||||||
|
self.wait(2)
|
||||||
|
|
||||||
|
def talk_through_90_degree_rotation(self):
|
||||||
|
plane = self.plane
|
||||||
|
points = [
|
||||||
|
plane.number_to_point(z)
|
||||||
|
for z in [1, complex(0, 1), -1, complex(0, -1)]
|
||||||
|
]
|
||||||
|
arrows = VGroup()
|
||||||
|
for p1, p2 in adjacent_pairs(points):
|
||||||
|
arrow = Arrow(
|
||||||
|
p1, p2, path_arc=180 * DEGREES,
|
||||||
|
use_rectangular_stem=False,
|
||||||
|
)
|
||||||
|
arrow.set_stroke(LIGHT_GREY, width=3)
|
||||||
|
arrow.tip.set_fill(LIGHT_GREY)
|
||||||
|
arrows.add(arrow)
|
||||||
|
|
||||||
|
for arrow in arrows:
|
||||||
|
self.play(ShowCreation(arrow))
|
||||||
|
self.wait(2)
|
||||||
|
|
||||||
|
self.arrows = arrows
|
||||||
|
|
||||||
|
def show_four_rotations(self):
|
||||||
|
circle_shadow = self.circle_shadow
|
||||||
|
for x in range(4):
|
||||||
|
self.play(
|
||||||
|
Rotate(circle_shadow, TAU / 4, run_time=3)
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.play(FadeOut(self.arrows))
|
||||||
|
|
||||||
|
def show_example_angles(self):
|
||||||
|
circle_shadow = self.circle_shadow
|
||||||
|
angle_label = Integer(0, unit="^\\circ")
|
||||||
|
angle_label.scale(1.5)
|
||||||
|
angle_label.next_to(
|
||||||
|
circle_shadow.get_top(), UR,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.play(FadeInFromDown(angle_label))
|
||||||
|
self.add(angle_label)
|
||||||
|
for angle in self.example_angles:
|
||||||
|
d_angle = angle - self.get_angle()
|
||||||
|
self.play(
|
||||||
|
Rotate(circle_shadow, d_angle),
|
||||||
|
ChangingDecimal(
|
||||||
|
angle_label,
|
||||||
|
lambda a: (self.get_angle() % TAU) / DEGREES
|
||||||
|
),
|
||||||
|
run_time=2
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
|
Loading…
Add table
Reference in a new issue