mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Finished(?) 3d stereographic projection animations
This commit is contained in:
parent
ce1eb4eb15
commit
8e163a7bcb
1 changed files with 553 additions and 28 deletions
|
@ -16,10 +16,12 @@ def get_three_d_scene_config(high_quality=True):
|
|||
# "tick_frequency": 0.5,
|
||||
"tick_frequency": 1,
|
||||
"numbers_with_elongated_ticks": [0, 1, 2],
|
||||
"stroke_width": 2,
|
||||
}
|
||||
},
|
||||
"sphere_config": {
|
||||
"radius": 2,
|
||||
"resolution": (24, 48),
|
||||
}
|
||||
}
|
||||
lq_added_config = {
|
||||
|
@ -30,7 +32,7 @@ def get_three_d_scene_config(high_quality=True):
|
|||
"num_axis_pieces": 1,
|
||||
},
|
||||
"sphere_config": {
|
||||
"resolution": (4, 12),
|
||||
# "resolution": (4, 12),
|
||||
}
|
||||
}
|
||||
if high_quality:
|
||||
|
@ -52,7 +54,7 @@ def q_mult(q1, q2):
|
|||
return np.array([w, x, y, z])
|
||||
|
||||
|
||||
def stereo_project_point(point, axis=0, r=1, max_norm=100):
|
||||
def stereo_project_point(point, axis=0, r=1, max_norm=10000):
|
||||
point = fdiv(point * r, point[axis] + r)
|
||||
point[axis] = 0
|
||||
norm = get_norm(point)
|
||||
|
@ -223,6 +225,69 @@ class CheckeredCircle(Circle):
|
|||
self[i::n_colors].set_color(color)
|
||||
|
||||
|
||||
class StereoProjectedSphere(Sphere):
|
||||
CONFIG = {
|
||||
"stereo_project_config": {
|
||||
"axis": 2,
|
||||
},
|
||||
"max_r": 20,
|
||||
"max_center": 32,
|
||||
"max_width": FRAME_WIDTH,
|
||||
"radius": 1,
|
||||
}
|
||||
|
||||
def __init__(self, rotation_matrix=None, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if rotation_matrix is None:
|
||||
rotation_matrix = np.identity(3)
|
||||
self.rotation_matrix = rotation_matrix
|
||||
|
||||
self.stereo_project_config["r"] = self.radius
|
||||
ParametricSurface.__init__(
|
||||
self, self.post_projection_func, **kwargs
|
||||
)
|
||||
# self.handle_outer_patch()
|
||||
self.submobjects.sort(
|
||||
key=lambda m: -m.get_width()
|
||||
)
|
||||
self.fade_far_out_submobjects()
|
||||
|
||||
def post_projection_func(self, u, v):
|
||||
point = self.radius * Sphere.func(self, u, v)
|
||||
rot_point = np.dot(point, self.rotation_matrix.T)
|
||||
result = stereo_project_point(
|
||||
rot_point, **self.stereo_project_config
|
||||
)
|
||||
if np.any(np.abs(result) == np.inf):
|
||||
return self.func(u + 0.001, v)
|
||||
return result
|
||||
|
||||
def fade_far_out_submobjects(self, max_center=None, max_width=None):
|
||||
if max_center is None:
|
||||
max_center = self.max_center
|
||||
if max_width is None:
|
||||
max_width = self.max_width
|
||||
for submob in self.submobjects:
|
||||
if get_norm(submob.get_center()) > max_center:
|
||||
submob.fade(1)
|
||||
if submob.get_width() > max_width:
|
||||
submob.fade(1)
|
||||
return self
|
||||
|
||||
# def handle_outer_patch(self):
|
||||
# submobs_around_origin = []
|
||||
# for submob in self.submobjects:
|
||||
# anchors = submob.get_anchors()
|
||||
# if np.all(np.apply_along_axis(get_norm, 1, anchors) > self.max_r):
|
||||
# submob.points[:, :] = 0
|
||||
# winding_number = get_winding_number(anchors)
|
||||
# if abs(winding_number) > 0.1:
|
||||
# submobs_around_origin.append(submob)
|
||||
# if submobs_around_origin:
|
||||
# widths = [sm.get_width() for sm in submobs_around_origin]
|
||||
# outer_patch = submobs_around_origin[np.argmax(widths)]
|
||||
|
||||
|
||||
# Abstract scenes
|
||||
class SpecialThreeDScene(ThreeDScene):
|
||||
CONFIG = {
|
||||
|
@ -2433,7 +2498,7 @@ class SphereExamplePointsDecimal(Scene):
|
|||
class TwoDStereographicProjection(IntroduceFelix):
|
||||
CONFIG = {
|
||||
"camera_config": {
|
||||
"exponential_projection": True,
|
||||
"exponential_projection": False,
|
||||
},
|
||||
"sphere_sample_point_u_range": np.arange(
|
||||
0, PI, PI / 16,
|
||||
|
@ -2441,29 +2506,53 @@ class TwoDStereographicProjection(IntroduceFelix):
|
|||
"sphere_sample_point_v_range": np.arange(
|
||||
0, TAU, TAU / 16,
|
||||
),
|
||||
"n_sample_rotation_cycles": 2,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_parts()
|
||||
# self.talk_through_sphere()
|
||||
self.talk_through_sphere()
|
||||
self.draw_projection_lines()
|
||||
self.show_point_at_infinity()
|
||||
self.show_a_few_rotations()
|
||||
self.show_reference_circles()
|
||||
|
||||
def add_parts(self):
|
||||
def add_parts(self, run_time=1):
|
||||
felix = self.felix = self.pi_creature
|
||||
felix.shift(1.5 * DL)
|
||||
axes = self.axes = self.get_axes()
|
||||
sphere = self.sphere = self.get_sphere()
|
||||
|
||||
felix.shift(1.5 * DL)
|
||||
c2p = axes.coords_to_point
|
||||
labels = VGroup(
|
||||
TexMobject("i").next_to(c2p(1, 0, 0), DR, SMALL_BUFF),
|
||||
TexMobject("-i").next_to(c2p(-1, 0, 0), DL, SMALL_BUFF),
|
||||
TexMobject("j").next_to(c2p(0, 1, 0), UL, SMALL_BUFF),
|
||||
TexMobject("-j").next_to(c2p(0, -1, 0), DL, SMALL_BUFF),
|
||||
TexMobject("1").rotate(
|
||||
90 * DEGREES, RIGHT,
|
||||
).next_to(c2p(0, 0, 1), RIGHT + OUT, SMALL_BUFF),
|
||||
TexMobject("-1").rotate(
|
||||
90 * DEGREES, RIGHT,
|
||||
).next_to(c2p(0, 0, -1), RIGHT + IN, SMALL_BUFF),
|
||||
)
|
||||
for sm in labels[:4].family_members_with_points():
|
||||
sm.add(VectorizedPoint(
|
||||
0.25 * DOWN + 0.25 * OUT
|
||||
))
|
||||
labels.set_stroke(width=0, background=True)
|
||||
for submob in labels.get_family():
|
||||
submob.shade_in_3d = True
|
||||
|
||||
self.add(felix, axes, sphere)
|
||||
self.add(felix, axes, sphere, labels)
|
||||
self.move_camera(
|
||||
**self.get_default_camera_position(),
|
||||
run_time=run_time
|
||||
)
|
||||
self.begin_ambient_camera_rotation(rate=0.01)
|
||||
self.play(felix.change, "pondering", sphere)
|
||||
self.play(
|
||||
felix.change, "pondering", sphere,
|
||||
run_time=run_time,
|
||||
)
|
||||
|
||||
def talk_through_sphere(self):
|
||||
point = VectorizedPoint(OUT)
|
||||
|
@ -2524,10 +2613,12 @@ class TwoDStereographicProjection(IntroduceFelix):
|
|||
shade_in_3d=True
|
||||
)
|
||||
|
||||
xy_plane = sphere.copy()
|
||||
xy_plane = StereoProjectedSphere(
|
||||
u_max=15 * PI / 16,
|
||||
**self.sphere_config
|
||||
)
|
||||
xy_plane.set_fill(WHITE, 0.25)
|
||||
xy_plane.set_stroke(width=0)
|
||||
self.project_mobject(xy_plane)
|
||||
|
||||
point_mob = VectorizedPoint(2 * OUT)
|
||||
point_mob.add_updater(
|
||||
|
@ -2562,6 +2653,7 @@ class TwoDStereographicProjection(IntroduceFelix):
|
|||
def get_projection_dot(sphere_point):
|
||||
projection = self.project_point(sphere_point)
|
||||
dot = Dot(projection, shade_in_3d=True)
|
||||
dot.add(VectorizedPoint(dot.get_center() + 0.1 * OUT))
|
||||
dot.set_fill(WHITE)
|
||||
return dot
|
||||
|
||||
|
@ -2595,20 +2687,17 @@ class TwoDStereographicProjection(IntroduceFelix):
|
|||
dot.copy(), projection_dot
|
||||
))
|
||||
|
||||
def get_point():
|
||||
return 2 * normalize(point_mob.get_location())
|
||||
|
||||
dot.add_updater(
|
||||
lambda d: d.become(get_sphere_dot(
|
||||
point_mob.get_location()
|
||||
))
|
||||
lambda d: d.become(get_sphere_dot(get_point()))
|
||||
)
|
||||
line.add_updater(
|
||||
lambda l: l.become(get_projection_line(
|
||||
point_mob.get_location()
|
||||
))
|
||||
lambda l: l.become(get_projection_line(get_point()))
|
||||
)
|
||||
projection_dot.add_updater(
|
||||
lambda d: d.become(get_projection_dot(
|
||||
point_mob.get_location()
|
||||
))
|
||||
lambda d: d.become(get_projection_dot(get_point()))
|
||||
)
|
||||
|
||||
self.play(
|
||||
|
@ -2656,8 +2745,8 @@ class TwoDStereographicProjection(IntroduceFelix):
|
|||
u_max=PI / 2 + 0.01,
|
||||
resolution=(1, 24),
|
||||
)
|
||||
# for submob in circle.get_family():
|
||||
# submob.shade_in_3d = True
|
||||
for submob in circle:
|
||||
submob.add(VectorizedPoint(1.5 * submob.get_center()))
|
||||
circle.set_fill(YELLOW)
|
||||
circle_path = Circle(radius=2)
|
||||
circle_path.rotate(-90 * DEGREES)
|
||||
|
@ -2678,6 +2767,9 @@ class TwoDStereographicProjection(IntroduceFelix):
|
|||
south_hemisphere = self.get_sphere()
|
||||
n = len(south_hemisphere)
|
||||
south_hemisphere.remove(*south_hemisphere[:n // 2])
|
||||
south_hemisphere.remove(
|
||||
*south_hemisphere[-3 * sphere.resolution[1] // 2:]
|
||||
)
|
||||
south_hemisphere.generate_target()
|
||||
self.project_mobject(south_hemisphere.target)
|
||||
south_hemisphere.set_fill(opacity=0.8)
|
||||
|
@ -2697,20 +2789,135 @@ class TwoDStereographicProjection(IntroduceFelix):
|
|||
|
||||
self.projected_sphere = VGroup(
|
||||
north_hemisphere,
|
||||
circle,
|
||||
south_hemisphere,
|
||||
)
|
||||
|
||||
self.equator = circle
|
||||
self.point_mob = point_mob
|
||||
|
||||
def show_point_at_infinity(self):
|
||||
pass
|
||||
points = list(compass_directions(
|
||||
12, start_vect=rotate_vector(RIGHT, 7.5 * DEGREES)
|
||||
))
|
||||
points.pop(7)
|
||||
points.pop(2)
|
||||
arrows = VGroup(*[
|
||||
Arrow(6 * p, 9 * p)
|
||||
for p in points
|
||||
])
|
||||
arrows.set_fill(YELLOW)
|
||||
neg_ones = VGroup(*[
|
||||
TexMobject("-1").next_to(arrow.get_start(), -p)
|
||||
for p, arrow in zip(points, arrows)
|
||||
])
|
||||
neg_ones.set_stroke(width=0, background=True)
|
||||
|
||||
sphere_arcs = VGroup()
|
||||
for angle in np.arange(0, TAU, TAU / 12):
|
||||
arc = Arc(PI, radius=2)
|
||||
arc.set_stroke(RED)
|
||||
arc.rotate(PI / 2, axis=DOWN, about_point=ORIGIN)
|
||||
arc.rotate(angle, axis=OUT, about_point=ORIGIN)
|
||||
sphere_arcs.add(arc)
|
||||
sphere_arcs.set_stroke(RED)
|
||||
|
||||
self.play(
|
||||
LaggedStart(GrowArrow, arrows),
|
||||
LaggedStart(Write, neg_ones)
|
||||
)
|
||||
self.wait(3)
|
||||
self.play(
|
||||
FadeOut(self.projected_sphere),
|
||||
FadeOut(arrows),
|
||||
FadeOut(neg_ones),
|
||||
)
|
||||
for x in range(2):
|
||||
self.play(
|
||||
ShowCreationThenDestruction(
|
||||
sphere_arcs,
|
||||
submobject_mode="all_at_once",
|
||||
run_time=3,
|
||||
)
|
||||
)
|
||||
|
||||
def show_a_few_rotations(self):
|
||||
pass
|
||||
sphere = self.sphere
|
||||
felix = self.felix
|
||||
point_mob = self.point_mob
|
||||
point_mob.add_updater(
|
||||
lambda m: m.move_to(sphere.get_all_points()[0])
|
||||
)
|
||||
coord_point_mobs = VGroup(
|
||||
VectorizedPoint(RIGHT),
|
||||
VectorizedPoint(UP),
|
||||
VectorizedPoint(OUT),
|
||||
)
|
||||
for pm in coord_point_mobs:
|
||||
pm.shade_in_3d = True
|
||||
|
||||
def show_reference_circles(self):
|
||||
pass
|
||||
def get_rot_matrix():
|
||||
return np.array([
|
||||
pm.get_location()
|
||||
for pm in coord_point_mobs
|
||||
]).T
|
||||
|
||||
def get_projected_sphere():
|
||||
result = StereoProjectedSphere(
|
||||
get_rot_matrix(),
|
||||
max_r=10,
|
||||
**self.sphere_config,
|
||||
)
|
||||
result.set_fill(opacity=0.2)
|
||||
result.fade_far_out_submobjects(32)
|
||||
for submob in result:
|
||||
if submob.get_center()[1] < -11:
|
||||
submob.fade(1)
|
||||
return result
|
||||
|
||||
projected_sphere = get_projected_sphere()
|
||||
projected_sphere.add_updater(
|
||||
lambda m: m.become(get_projected_sphere())
|
||||
)
|
||||
|
||||
def get_projected_equator():
|
||||
equator = CheckeredCircle(
|
||||
n_pieces=24,
|
||||
radius=2,
|
||||
)
|
||||
for submob in equator.get_family():
|
||||
submob.shade_in_3d = True
|
||||
equator.set_stroke(YELLOW, 5)
|
||||
equator.apply_matrix(get_rot_matrix())
|
||||
self.project_mobject(equator)
|
||||
return equator
|
||||
|
||||
projected_equator = get_projected_equator()
|
||||
projected_equator.add_updater(
|
||||
lambda m: m.become(get_projected_equator())
|
||||
)
|
||||
|
||||
self.add(sphere, projected_sphere)
|
||||
self.play(
|
||||
sphere.set_fill_by_checkerboard,
|
||||
BLUE_E, BLUE_D, {"opacity": 0.8},
|
||||
FadeIn(projected_sphere)
|
||||
)
|
||||
sphere.add(coord_point_mobs)
|
||||
sphere.add(self.equator)
|
||||
self.add(projected_equator)
|
||||
pairs = self.get_sample_rotation_angle_axis_pairs()
|
||||
for x in range(self.n_sample_rotation_cycles):
|
||||
for angle, axis in pairs:
|
||||
self.play(
|
||||
Rotate(
|
||||
sphere, angle=angle, axis=axis,
|
||||
about_point=ORIGIN,
|
||||
run_time=3,
|
||||
),
|
||||
felix.change, "confused",
|
||||
)
|
||||
self.wait()
|
||||
|
||||
self.projected_sphere = projected_sphere
|
||||
|
||||
#
|
||||
def project_mobject(self, mobject):
|
||||
|
@ -2718,3 +2925,321 @@ class TwoDStereographicProjection(IntroduceFelix):
|
|||
|
||||
def project_point(self, point):
|
||||
return stereo_project_point(point, axis=2, r=2)
|
||||
|
||||
def get_sample_rotation_angle_axis_pairs(self):
|
||||
return SphereExamplePointsDecimal.CONFIG.get(
|
||||
"point_rotation_angle_axis_pairs"
|
||||
)
|
||||
|
||||
|
||||
class FelixViewOfProjection(TwoDStereographicProjection):
|
||||
CONFIG = {}
|
||||
|
||||
def construct(self):
|
||||
self.add_axes()
|
||||
self.show_a_few_rotations()
|
||||
|
||||
def add_axes(self):
|
||||
axes = Axes(
|
||||
number_line_config={
|
||||
"unit_size": 2,
|
||||
"color": WHITE,
|
||||
}
|
||||
)
|
||||
labels = VGroup(
|
||||
TexMobject("i"),
|
||||
TexMobject("-i"),
|
||||
TexMobject("j"),
|
||||
TexMobject("-j"),
|
||||
)
|
||||
coords = [(1, 0), (-1, 0), (0, 1), (0, -1)]
|
||||
vects = [DOWN, DOWN, RIGHT, RIGHT]
|
||||
for label, coords, vect in zip(labels, coords, vects):
|
||||
point = axes.coords_to_point(*coords)
|
||||
label.next_to(point, vect, buff=MED_SMALL_BUFF)
|
||||
|
||||
self.add(axes, labels)
|
||||
self.pi_creature.change("confused")
|
||||
|
||||
def show_a_few_rotations(self):
|
||||
felix = self.pi_creature
|
||||
coord_point_mobs = VGroup([
|
||||
VectorizedPoint(point)
|
||||
for point in [RIGHT, UP, OUT]
|
||||
])
|
||||
|
||||
def get_rot_matrix():
|
||||
return np.array([
|
||||
pm.get_location()
|
||||
for pm in coord_point_mobs
|
||||
]).T
|
||||
|
||||
def get_projected_sphere():
|
||||
return StereoProjectedSphere(
|
||||
get_rot_matrix(),
|
||||
**self.sphere_config,
|
||||
)
|
||||
|
||||
def get_projected_equator():
|
||||
equator = Circle(radius=2, num_anchors=24)
|
||||
equator.set_stroke(YELLOW, 5)
|
||||
equator.apply_matrix(get_rot_matrix())
|
||||
self.project_mobject(equator)
|
||||
return equator
|
||||
|
||||
projected_sphere = get_projected_sphere()
|
||||
projected_sphere.add_updater(
|
||||
lambda m: m.become(get_projected_sphere())
|
||||
)
|
||||
|
||||
equator = get_projected_equator()
|
||||
equator.add_updater(
|
||||
lambda m: m.become(get_projected_equator())
|
||||
)
|
||||
|
||||
dot = Dot(color=PINK)
|
||||
dot.add_updater(
|
||||
lambda d: d.move_to(
|
||||
self.project_point(
|
||||
np.dot(2 * OUT, get_rot_matrix().T)
|
||||
)
|
||||
)
|
||||
)
|
||||
hand = Hand()
|
||||
hand.add_updater(
|
||||
lambda h: h.move_to(dot.get_center(), LEFT)
|
||||
)
|
||||
felix.add_updater(lambda f: f.look_at(dot))
|
||||
|
||||
self.add(projected_sphere)
|
||||
self.add(equator)
|
||||
self.add(dot)
|
||||
self.add(hand)
|
||||
|
||||
pairs = self.get_sample_rotation_angle_axis_pairs()
|
||||
for x in range(self.n_sample_rotation_cycles):
|
||||
for angle, axis in pairs:
|
||||
self.play(
|
||||
Rotate(
|
||||
coord_point_mobs, angle=angle, axis=axis,
|
||||
about_point=ORIGIN,
|
||||
run_time=3,
|
||||
),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class ShowRotationsJustWithReferenceCircles(TwoDStereographicProjection):
|
||||
def construct(self):
|
||||
self.add_parts(run_time=1)
|
||||
self.begin_ambient_camera_rotation(rate=0.03)
|
||||
self.edit_parts()
|
||||
self.show_1i_circle()
|
||||
self.show_1j_circle()
|
||||
self.show_random_circle()
|
||||
self.show_rotations()
|
||||
|
||||
def edit_parts(self):
|
||||
sphere = self.sphere
|
||||
xy_plane = StereoProjectedSphere(u_max=15 * PI / 16)
|
||||
xy_plane.set_fill(WHITE, 0.2)
|
||||
xy_plane.set_stroke(width=0, opacity=0)
|
||||
|
||||
self.add(xy_plane, sphere)
|
||||
self.play(
|
||||
FadeIn(xy_plane),
|
||||
sphere.set_fill, BLUE_E, {"opacity": 0.2},
|
||||
sphere.set_stroke, {"width": 0.1, "opacity": 0.5}
|
||||
)
|
||||
|
||||
def show_1i_circle(self):
|
||||
axes = self.axes
|
||||
|
||||
circle = self.get_circle(GREEN_E, GREEN)
|
||||
circle.rotate(TAU / 4, RIGHT)
|
||||
circle.rotate(TAU / 4, DOWN)
|
||||
|
||||
projected = self.get_projected_circle(circle)
|
||||
|
||||
labels = VGroup(*map(TexMobject, ["0", "2i", "3i"]))
|
||||
labels.set_shade_in_3d(True)
|
||||
for label, x in zip(labels, [0, 2, 3]):
|
||||
label.next_to(
|
||||
axes.coords_to_point(x, 0, 0), DR, SMALL_BUFF
|
||||
)
|
||||
|
||||
self.play(ShowCreation(circle, run_time=3))
|
||||
self.wait()
|
||||
self.play(ReplacementTransform(
|
||||
circle.copy(), projected,
|
||||
run_time=3
|
||||
))
|
||||
self.axes.x_axis.pieces.set_stroke(width=0)
|
||||
self.wait(7)
|
||||
self.move_camera(
|
||||
phi=60 * DEGREES,
|
||||
)
|
||||
self.play(
|
||||
LaggedStart(
|
||||
FadeInFrom, labels,
|
||||
lambda m: (m, UP)
|
||||
)
|
||||
)
|
||||
self.wait(2)
|
||||
self.play(FadeOut(labels))
|
||||
|
||||
self.one_i_circle = circle
|
||||
self.projected_one_i_circle = projected
|
||||
|
||||
def show_1j_circle(self):
|
||||
circle = self.get_circle(RED_E, RED)
|
||||
circle.rotate(TAU / 4, DOWN)
|
||||
|
||||
projected = self.get_projected_circle(circle)
|
||||
|
||||
self.move_camera(theta=-170 * DEGREES)
|
||||
self.play(ShowCreation(circle, run_time=3))
|
||||
self.wait()
|
||||
self.play(ReplacementTransform(
|
||||
circle.copy(), projected, run_time=3
|
||||
))
|
||||
self.axes.y_axis.pieces.set_stroke(width=0)
|
||||
self.wait(3)
|
||||
|
||||
self.one_j_circle = circle
|
||||
self.projected_one_j_circle = projected
|
||||
|
||||
def show_random_circle(self):
|
||||
sphere = self.sphere
|
||||
|
||||
circle = self.get_circle(BLUE_E, BLUE)
|
||||
circle.set_width(2 * sphere.radius * np.sin(30 * DEGREES))
|
||||
circle.shift(sphere.radius * np.cos(30 * DEGREES) * OUT)
|
||||
circle.rotate(150 * DEGREES, DOWN, about_point=ORIGIN)
|
||||
|
||||
projected = self.get_projected_circle(circle)
|
||||
|
||||
self.move_camera(phi=130 * DEGREES)
|
||||
self.play(ShowCreation(circle, run_time=2))
|
||||
self.move_camera(phi=60 * DEGREES)
|
||||
self.play(ReplacementTransform(
|
||||
circle.copy(), projected,
|
||||
run_time=2
|
||||
))
|
||||
self.wait(3)
|
||||
self.play(
|
||||
FadeOut(circle),
|
||||
FadeOut(projected),
|
||||
)
|
||||
|
||||
def show_rotations(self):
|
||||
sphere = self.sphere
|
||||
c1i = self.one_i_circle
|
||||
pc1i = self.projected_one_i_circle
|
||||
c1j = self.one_j_circle
|
||||
pc1j = self.projected_one_j_circle
|
||||
cij = self.get_circle(YELLOW_E, YELLOW)
|
||||
pcij = self.get_projected_circle(cij)
|
||||
|
||||
circles = VGroup(c1i, c1j, cij)
|
||||
x_axis = self.axes.x_axis
|
||||
y_axis = self.axes.y_axis
|
||||
|
||||
arrow = Arrow(
|
||||
2 * RIGHT, 2 * UP,
|
||||
buff=SMALL_BUFF,
|
||||
path_arc=PI,
|
||||
use_rectangular_stem=False,
|
||||
)
|
||||
arrow.set_stroke(LIGHT_GREY, 3)
|
||||
arrow.tip.set_fill(LIGHT_GREY)
|
||||
arrows = VGroup(arrow, *[
|
||||
arrow.copy().rotate(angle, about_point=ORIGIN)
|
||||
for angle in np.arange(TAU / 4, TAU, TAU / 4)
|
||||
])
|
||||
arrows.rotate(TAU / 4, RIGHT, about_point=ORIGIN)
|
||||
arrows.rotate(TAU / 2, OUT, about_point=ORIGIN)
|
||||
arrows.rotate(TAU / 4, UP, about_point=ORIGIN)
|
||||
arrows.space_out_submobjects(1.2)
|
||||
|
||||
self.play(FadeInFromLarge(cij))
|
||||
sphere.add(circles)
|
||||
|
||||
pc1i.add_updater(
|
||||
lambda c: c.become(self.get_projected_circle(c1i))
|
||||
)
|
||||
pc1j.add_updater(
|
||||
lambda c: c.become(self.get_projected_circle(c1j))
|
||||
)
|
||||
pcij.add_updater(
|
||||
lambda c: c.become(self.get_projected_circle(cij))
|
||||
)
|
||||
self.add(pcij)
|
||||
|
||||
# About j-axis
|
||||
self.play(ShowCreation(arrows, run_time=3, rate_func=None))
|
||||
self.wait(3)
|
||||
for x in range(2):
|
||||
y_axis.pieces.set_stroke(width=2)
|
||||
self.play(
|
||||
Rotate(sphere, 90 * DEGREES, axis=UP),
|
||||
run_time=4,
|
||||
)
|
||||
y_axis.pieces.set_stroke(width=0)
|
||||
self.wait(2)
|
||||
|
||||
# About i axis
|
||||
self.move_camera(theta=-45 * DEGREES)
|
||||
self.play(Rotate(arrows, TAU / 4, axis=OUT))
|
||||
self.wait(2)
|
||||
for x in range(2):
|
||||
x_axis.pieces.set_stroke(width=2)
|
||||
self.play(
|
||||
Rotate(sphere, -90 * DEGREES, axis=RIGHT),
|
||||
run_time=4,
|
||||
)
|
||||
x_axis.pieces.set_stroke(width=0)
|
||||
self.wait(2)
|
||||
self.wait(2)
|
||||
|
||||
# About real axis
|
||||
self.move_camera(
|
||||
theta=-135 * DEGREES,
|
||||
added_anims=[FadeOut(arrows)]
|
||||
)
|
||||
self.ambient_camera_rotation.rate = 0.01
|
||||
for x in range(2):
|
||||
x_axis.pieces.set_stroke(width=2)
|
||||
y_axis.pieces.set_stroke(width=2)
|
||||
self.play(
|
||||
Rotate(sphere, 90 * DEGREES, axis=OUT),
|
||||
run_time=4,
|
||||
)
|
||||
self.wait(2)
|
||||
x_axis.pieces.set_stroke(width=0)
|
||||
y_axis.pieces.set_stroke(width=0)
|
||||
|
||||
#
|
||||
def get_circle(self, *colors):
|
||||
sphere = self.sphere
|
||||
circle = CheckeredCircle(colors=colors, n_pieces=48)
|
||||
circle.set_shade_in_3d(True)
|
||||
circle.match_width(sphere)
|
||||
|
||||
return circle
|
||||
|
||||
def get_projected_circle(self, circle):
|
||||
result = circle.deepcopy()
|
||||
self.project_mobject(result)
|
||||
for sm in result:
|
||||
if sm.get_width() > FRAME_WIDTH:
|
||||
sm.fade(1)
|
||||
if sm.get_height() > FRAME_HEIGHT:
|
||||
sm.fade(1)
|
||||
return result
|
||||
|
||||
|
||||
class IntroduceQuaternions(Scene):
|
||||
def construct(self):
|
||||
self.compare_three_number_systems()
|
||||
self.mention_four_perpendicular_axes()
|
||||
|
|
Loading…
Add table
Reference in a new issue