Final tweaks to quaternion animations...I hope

This commit is contained in:
Grant Sanderson 2018-09-05 15:24:02 -07:00
parent a1e21a4606
commit b18537b1bb

View file

@ -97,6 +97,12 @@ def stereo_project(mobject, axis=0, r=1, outer_r=10, **kwargs):
return mobject
def updating_mobject_from_func(func):
mob = func()
mob.add_updater(lambda m: mob.become(func()))
return mob
class Linus(VGroup):
CONFIG = {
"body_config": {
@ -288,6 +294,7 @@ class StereoProjectedSphereFromHypersphere(StereoProjectedSphere):
"axis": 0,
},
"radius": 2,
"multiply_from_right": False,
}
def __init__(self, quaternion=None, null_axis=0, **kwargs):
@ -301,7 +308,10 @@ class StereoProjectedSphereFromHypersphere(StereoProjectedSphere):
def q_mult_projection_func(self, u, v):
point = list(Sphere.func(self, u, v))
point.insert(self.null_axis, 0)
post_q_mult = q_mult(self.quaternion, point)
if self.multiply_from_right:
post_q_mult = q_mult(point, self.quaternion)
else:
post_q_mult = q_mult(self.quaternion, point)
projected = list(self.radius * stereo_project_point(
post_q_mult, **self.stereo_project_config
))
@ -316,12 +326,13 @@ class StereoProjectedCircleFromHypersphere(CheckeredCircle):
CONFIG = {
"n_pieces": 48,
"radius": 2,
"max_length": 2 * FRAME_WIDTH,
"max_r": 20,
"max_length": 3 * FRAME_WIDTH,
"max_r": 50,
"basis_vectors": [
[1, 0, 0, 0],
[0, 1, 0, 0],
]
],
"multiply_from_right": False,
}
def __init__(self, quaternion=None, **kwargs):
@ -344,7 +355,10 @@ class StereoProjectedCircleFromHypersphere(CheckeredCircle):
def projection(self, point):
q1 = self.quaternion
q2 = np.dot(self.pre_positioning_matrix, point[:2]).flatten()
new_q = q_mult(q1, q2)
if self.multiply_from_right:
new_q = q_mult(q2, q1)
else:
new_q = q_mult(q1, q2)
projected = stereo_project_point(
new_q, axis=0, r=self.radius,
)
@ -1118,6 +1132,74 @@ class RotateCubeThreeTimes(SpecialThreeDScene):
self.wait(7)
class QuantumSpin(Scene):
def construct(self):
title = Title("Two-state system")
electron = Dot(color=BLUE)
electron.set_height(1)
electron.set_sheen(0.3, UL)
electron.set_fill(opacity=0.8)
kwargs = {
"use_rectangular_stem": False,
"path_arc": PI,
}
curved_arrows = VGroup(
Arrow(RIGHT, LEFT, **kwargs),
Arrow(LEFT, RIGHT, **kwargs),
)
curved_arrows.set_color(LIGHT_GREY)
curved_arrows.set_stroke(width=2)
y_arrow = Vector(UP)
y_arrow.set_color(RED)
y_ca = curved_arrows.copy()
y_ca.rotate(70 * DEGREES, LEFT)
y_group = VGroup(y_ca[0], y_arrow, electron.copy(), y_ca[1])
x_arrow = y_arrow.copy().rotate(90 * DEGREES, IN, about_point=ORIGIN)
x_arrow.set_color(GREEN)
x_ca = curved_arrows.copy()
x_ca.rotate(70 * DEGREES, UP)
x_group = VGroup(x_ca[0], x_arrow, electron.copy(), x_ca[1])
z_ca = curved_arrows.copy()
z_group = VGroup(electron.copy(), z_ca, Dot(color=BLUE))
groups = VGroup(x_group, y_group, z_group)
groups.arrange_submobjects(RIGHT, buff=1.5)
groups.move_to(UP)
y_ca.rotation = Rotating(
y_ca, axis=rotate_vector(OUT, 70 * DEGREES, LEFT)
)
x_ca.rotation = Rotating(
x_ca, axis=rotate_vector(OUT, 70 * DEGREES, UP)
)
z_ca.rotation = Rotating(z_ca, axis=OUT)
rotations = [ca.rotation for ca in [x_ca, y_ca, z_ca]]
matrices = VGroup(
Matrix([["0", "1"], ["1", "0"]]),
Matrix([["0", "-i"], ["i", "0"]]),
Matrix([["1", "0"], ["0", "-1"]]),
)
for matrix, group in zip(matrices, groups):
matrix.next_to(group, DOWN)
for matrix in matrices[1:]:
matrix.align_to(matrices[0], DOWN)
self.add(title, groups)
self.play()
self.play(
LaggedStart(FadeInFromDown, matrices, run_time=3),
*rotations
)
for x in range(2):
self.play(*rotations)
class HereWeTackle4d(TeacherStudentsScene):
def construct(self):
titles = VGroup(
@ -2252,6 +2334,7 @@ class OneDegreeOfFreedomForRotation(Scene):
right_dot = Dot(color=WHITE)
right_dot.move_to(circle.get_right())
circle.add(moving_r_line, right_dot)
center_dot = Dot(color=WHITE)
def get_angle():
return moving_r_line.get_angle() % TAU
@ -2275,7 +2358,7 @@ class OneDegreeOfFreedomForRotation(Scene):
arc = get_arc()
arc.add_updater(lambda m: m.become(get_arc()))
self.add(circle, r_line, angle_label, arc)
self.add(circle, center_dot, r_line, angle_label, arc)
angles = IntroduceStereographicProjection.CONFIG.get(
"example_angles"
)
@ -2572,8 +2655,13 @@ class IntroduceStereographicProjection(MovingCameraScene):
def get_sample_circle_points(self):
plane = self.plane
n = self.n_sample_points
rotater = 1
if hasattr(self, "circle_shadow"):
point = self.circle_shadow[0].points[0]
rotater = complex(*point[:2])
rotater /= abs(rotater)
numbers = [
np.exp(complex(0, TAU * x / n))
rotater * np.exp(complex(0, TAU * x / n))
for x in range(-(n // 2) + 1, n // 2)
]
return [
@ -2591,11 +2679,9 @@ class IntroduceStereographicProjection(MovingCameraScene):
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)
length = line.get_length()
line.scale(20 / length, about_point=neg_one_point)
line.set_stroke(YELLOW, np.clip(length, 0, 1))
return lines
def project(self, point):
@ -2835,7 +2921,7 @@ class ShowRotationUnderStereographicProjection(IntroduceStereographicProjection)
neg_one_dot = Dot(neg_one_point)
neg_one_dot.set_fill(YELLOW)
lines = self.get_lines()
lines = updating_mobject_from_func(self.get_lines)
def generate_dot_updater(circle_piece):
return lambda d: d.move_to(circle_piece.points[0])
@ -3146,13 +3232,23 @@ class IntroduceThreeDNumbers(SpecialThreeDScene):
plane = self.plane
x_line = Line(LEFT, RIGHT).set_width(FRAME_WIDTH)
colored_ = Line(LEFT, RIGHT).set_width(FRAME_WIDTH)
y_line = Line(DOWN, UP).set_height(FRAME_WIDTH)
z_line = Line(IN, OUT).set_depth(FRAME_WIDTH)
x_line.set_stroke(GREEN, 5)
colored_.set_stroke(GREEN, 5)
y_line.set_stroke(RED, 5)
z_line.set_stroke(YELLOW, 5)
coord_lines = VGroup(x_line, y_line, z_line)
colored_coord_lines = VGroup(colored_, y_line, z_line)
coord_lines = VGroup(
plane.axes[0], plane.axes[1], z_axis.main_line,
)
for i1, i2 in [(0, 2), (1, 0), (2, 1)]:
coord_lines[i1].target = coord_lines[i2].copy()
new_title = TextMobject("Imaginary plane")
new_title.replace(self.title)
new_title.move_to(self.title)
self.add(z_axis, plane, z_axis_top)
self.move_camera(
@ -3184,13 +3280,17 @@ class IntroduceThreeDNumbers(SpecialThreeDScene):
label.target.next_to(
2 * y * UP, RIGHT, 2 * SMALL_BUFF
)
self.remove(z_axis_top)
self.play(
LaggedStart(MoveToTarget, group, lag_ratio=0.8),
LaggedStart(MoveToTarget, coord_lines, lag_ratio=0.8),
FadeOut(self.title),
FadeIn(new_title),
run_time=3
)
self.add(z_axis_top)
self.wait(3)
for line, wait in zip(coord_lines, [False, True, True]):
for line, wait in zip(colored_coord_lines, [False, True, True]):
self.play(
ShowCreationThenDestruction(line),
run_time=2
@ -3264,7 +3364,7 @@ class MentionImpossibilityOf3dNumbers(TeacherStudentsScene):
why.next_to(self.students[1], UP)
self.teacher_says(
"Such 3d number \\\\ have no good \\\\ multiplication rule",
"Such 3d numbers \\\\ have no good \\\\ multiplication rule",
bubble_kwargs={"width": 4, "height": 3},
)
self.change_all_student_modes("confused")
@ -3360,6 +3460,7 @@ class TwoDStereographicProjection(IntroduceFelix):
0, TAU, TAU / 16,
),
"n_sample_rotation_cycles": 2,
"lift_labels": True,
}
def construct(self):
@ -3376,7 +3477,7 @@ class TwoDStereographicProjection(IntroduceFelix):
sphere = self.sphere = self.get_sphere()
c2p = axes.coords_to_point
labels = VGroup(
labels = self.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),
@ -3388,10 +3489,11 @@ class TwoDStereographicProjection(IntroduceFelix):
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
))
if self.lift_labels:
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
@ -3811,9 +3913,10 @@ class FelixViewOfProjection(TwoDStereographicProjection):
TexMobject("-i"),
TexMobject("j"),
TexMobject("-j"),
TexMobject("1"),
)
coords = [(1, 0), (-1, 0), (0, 1), (0, -1)]
vects = [DOWN, DOWN, RIGHT, RIGHT]
coords = [(1, 0), (-1, 0), (0, 1), (0, -1), (0, 0)]
vects = [DOWN, DOWN, RIGHT, RIGHT, 0.25 * DR]
for label, coords, vect in zip(labels, coords, vects):
point = axes.coords_to_point(*coords)
label.next_to(point, vect, buff=MED_SMALL_BUFF)
@ -3890,6 +3993,10 @@ class FelixViewOfProjection(TwoDStereographicProjection):
class ShowRotationsJustWithReferenceCircles(TwoDStereographicProjection):
CONFIG = {
"flat_view": False,
}
def construct(self):
self.add_parts(run_time=1)
self.begin_ambient_camera_rotation(rate=0.03)
@ -3925,6 +4032,8 @@ class ShowRotationsJustWithReferenceCircles(TwoDStereographicProjection):
labels = VGroup(*map(TexMobject, ["0", "2i", "3i"]))
labels.set_shade_in_3d(True)
if self.flat_view:
labels.fade(1)
for label, x in zip(labels, [0, 2, 3]):
label.next_to(
axes.coords_to_point(x, 0, 0), DR, SMALL_BUFF
@ -4086,6 +4195,8 @@ class ShowRotationsJustWithReferenceCircles(TwoDStereographicProjection):
circle = CheckeredCircle(colors=colors, n_pieces=48)
circle.set_shade_in_3d(True)
circle.match_width(sphere)
if self.flat_view:
circle[::2].fade(1)
return circle
@ -4101,6 +4212,40 @@ class ShowRotationsJustWithReferenceCircles(TwoDStereographicProjection):
return result
class ReferernceSpheresFelixView(ShowRotationsJustWithReferenceCircles):
CONFIG = {
"flat_view": True,
"lift_labels": False,
}
def add_parts(self, **kwargs):
ShowRotationsJustWithReferenceCircles.add_parts(self, **kwargs)
one = TexMobject("1")
one.next_to(ORIGIN, DR, SMALL_BUFF)
self.add(one)
def get_default_camera_position(self):
return {}
def move_camera(self, **kwargs):
kwargs["phi"] = 0
kwargs["theta"] = -90 * DEGREES
ShowRotationsJustWithReferenceCircles.move_camera(self, **kwargs)
def begin_ambient_camera_rotation(self, rate):
self.ambient_camera_rotation = ContinualAnimation(VectorizedPoint())
def capture_mobjects_in_camera(self, mobjects, **kwargs):
mobs_on_xy = [
sm
for sm in self.camera.extract_mobject_family_members(
mobjects, only_those_with_points=True
)
if abs(sm.get_center()[2]) < 0.001
]
return Scene.capture_mobjects_in_camera(self, mobs_on_xy, **kwargs)
class IntroduceQuaternions(Scene):
def construct(self):
self.compare_three_number_systems()
@ -4786,12 +4931,29 @@ class HypersphereStereographicProjection(SpecialThreeDScene):
def show_quaternions_with_nonzero_real_part(self):
# Positive real part
self.set_quat([1, 1, 2, 0])
self.set_quat(
[1, 1, 2, 0],
added_anims=[
ApplyMethod(
self.sphere.copy().scale, 0,
remover=True
)
]
)
self.wait(2)
self.set_quat([4, 0, -1, -1])
self.wait(2)
# Negative real part
self.set_quat([-1, 1, 2, 0])
self.set_quat(
[-1, 1, 2, 0],
added_anims=[
ApplyFunction(
lambda s: s.scale(10).fade(1),
self.sphere.copy(),
remover=True
)
]
)
self.wait(2)
self.set_quat([-2, 0, -1, 1])
self.wait(2)
@ -5035,6 +5197,18 @@ class HypersphereStereographicProjection(SpecialThreeDScene):
# return sphere
class MissingLabels(Scene):
def construct(self):
labels = VGroup(
TexMobject("i").move_to(UR),
TexMobject("1").move_to(0.3 * DOWN),
TexMobject("-i").move_to(DOWN + 1.2 * LEFT),
TexMobject("-j").move_to(1.7 * RIGHT + 0.8 * DOWN),
)
labels.set_background_stroke(width=0)
self.add(labels)
class RuleOfQuaternionMultiplicationOverlay(Scene):
def construct(self):
q_mob, times_mob, p_mob = q_times_p = TexMobject(
@ -5664,18 +5838,18 @@ class ShowJMultiplication(ShowMultiplicationBy135Example):
circle = self.circle_ik.deepcopy()
circle.clear_updaters()
self.play(FadeInFromLarge(circle, remover=True))
m_tracker.set_value([1, 0, 0, 0])
m_tracker.set_value([-1, 0, 0, 0])
q_tracker.set_value([0, 1, 0, 0])
self.wait()
self.play(
m_tracker.set_value, [0, 0, 1, 0],
m_tracker.set_value, [0, 0, -1, 0],
q_tracker.set_value, [0, 0, 0, -1],
*updates,
run_time=run_time,
)
self.wait(2)
self.play(
m_tracker.set_value, [-1, 0, 1e-3, 0],
m_tracker.set_value, [1, 0, -1e-3, 0],
q_tracker.set_value, [0, -1, 0, 0],
*updates,
run_time=run_time,
@ -5913,6 +6087,220 @@ class RotationsOfCube(SpecialThreeDScene):
self.wait(5)
class OneFinalPoint(TeacherStudentsScene):
def construct(self):
self.teacher_says("One final point!")
self.change_student_modes("happy", "tease", "thinking")
self.wait(3)
class MultiplicationFromTheRight(Scene):
def construct(self):
i, dot, j = product = TexMobject("i", "\\cdot", "j")
product.set_height(1.5)
product.to_edge(UP, buff=LARGE_BUFF)
i.set_color(GREEN)
j.set_color(RED)
i_rect = SurroundingRectangle(i)
j_rect = SurroundingRectangle(j)
i_rect.match_height(j_rect, about_edge=UP, stretch=True)
VGroup(i_rect, j_rect).set_color(WHITE)
action_words = TextMobject("Think of as \\\\ an action")
point_words = TextMobject("Think of as \\\\ a point")
action_words.next_to(i_rect, LEFT)
point_words.next_to(j_rect, RIGHT)
arrow = Arrow(
i.get_top(),
j.get_top(),
path_arc=-PI,
use_rectangular_stem=False,
)
arrow.set_stroke(width=2)
self.add(product)
self.play(ShowCreation(arrow))
self.play(
FadeIn(i_rect),
Write(action_words)
)
self.wait()
self.play(
FadeIn(j_rect),
Write(point_words)
)
self.wait(2)
self.play(arrow.flip)
self.play(Swap(point_words, action_words))
class MultiplicationByJFromRight(ShowJMultiplication):
def show_multiplication(self):
self.set_camera_orientation(theta=-80 * DEGREES)
q_tracker = self.q_tracker
m_tracker = self.multiplier_tracker
def normalize_tracker(t):
t.set_value(normalize(t.get_value()))
updates = [
UpdateFromFunc(tracker, normalize_tracker)
for tracker in (q_tracker, m_tracker)
]
run_time = self.run_time_per_rotation
m_values = [[1, 0, 0, 0]]
q_values = [[0, 1, 0, 0]]
for values in m_values, q_values:
for x in range(4):
values.append(
q_mult(values[-1], [0, 0, 1, 0])
)
for m_val, q_val in zip(m_values, q_values):
self.play(
m_tracker.set_value, m_val,
q_tracker.set_value, q_val,
*updates,
run_time=run_time,
)
self.wait(2)
def get_projected_sphere(self, *args, **kwargs):
kwargs["multiply_from_right"] = True
return ShowJMultiplication.get_projected_sphere(
self, *args, **kwargs
)
def get_projected_circle(self, *args, **kwargs):
kwargs["multiply_from_right"] = True
return ShowJMultiplication.get_projected_circle(
self, *args, **kwargs
)
class HowQuaternionsRotate3dPoints(Scene):
def construct(self):
title = TextMobject(
"Coming up:\\\\",
"How quaternions act on 3d points"
)
title.to_edge(UP)
expression = TexMobject(
"q", "\\cdot", "p", "\\cdot", "q^{-1}"
)
expression.scale(2)
expression.set_color_by_tex("q", PINK)
expression.set_color_by_tex("p", YELLOW)
right_arrow = Arrow(
expression[0].get_top(),
expression[2].get_top(),
path_arc=-PI,
use_rectangular_stem=False,
color=WHITE,
)
right_arrow.set_stroke(width=4)
left_arrow = right_arrow.copy()
left_arrow.flip(about_point=expression[2].get_top())
left_arrow.shift(SMALL_BUFF * RIGHT)
self.add(title)
self.play(Write(expression))
self.play(ShowCreation(right_arrow))
self.play(ShowCreation(left_arrow))
self.wait()
class HoldUpQuanta(TeacherStudentsScene):
def construct(self):
logo = ImageMobject("Quanta logo")
logo.set_width(6)
logo.next_to(self.teacher, UR)
logo.to_edge(RIGHT, buff=2)
words = TextMobject("Associated quaternion post")
words.to_edge(UP, buff=LARGE_BUFF)
arrow = Arrow(logo.get_top(), words.get_bottom())
self.play(
FadeInFromDown(logo),
self.teacher.change, "raise_right_hand"
)
self.change_all_student_modes("hooray")
self.play(
GrowArrow(arrow),
GrowFromPoint(words, arrow.get_start())
)
self.wait(3)
class ShareWithFriends(PiCreatureScene):
def construct(self):
pi1, pi2 = self.pi_creatures
self.pi_creature_says(
pi1, "Come learn about \\\\ quaternions!",
bubble_kwargs={
"direction": LEFT,
"width": 4,
"height": 3,
},
target_mode="hooray",
look_at_arg=pi2.eyes,
added_anims=[
ApplyMethod(
pi2.change, "confused",
run_time=1.5,
rate_func=squish_rate_func(smooth, 0.3, 1)
)
]
)
bubble = ThoughtBubble(
direction=RIGHT,
width=4,
height=4,
)
bubble.pin_to(pi2)
bubble.write("What's that, some\\\\kind of particle?")
bubble.resize_to_content()
VGroup(bubble, bubble.content).shift_onto_screen(buff=SMALL_BUFF)
self.play(
pi2.look_at, pi1.eyes,
pi1.look_at, pi2.eyes,
ShowCreation(bubble),
Write(bubble.content),
)
self.wait(1)
self.play(
RemovePiCreatureBubble(pi1, target_mode="raise_right_hand")
)
self.wait()
time_words = TextMobject("Oh man...\\\\30 minutes")
time_words.move_to(bubble.content)
self.play(
Animation(VectorizedPoint().next_to(pi1, UL, LARGE_BUFF)),
pi2.change, "sad",
FadeOutAndShift(bubble.content, DOWN),
FadeInFromDown(time_words, DOWN),
)
self.wait(7)
def create_pi_creatures(self):
pi1 = PiCreature(color=BLUE)
pi1.to_edge(DOWN)
pi1.flip()
pi1.shift(2.5 * RIGHT)
pi2 = PiCreature(color=RED)
pi2.to_edge(DOWN)
pi2.shift(2 * LEFT)
return [pi1, pi2]
class QuaternionEndscreen(PatreonEndScreen):
CONFIG = {
"specific_patrons": [
@ -6000,7 +6388,7 @@ class QuaternionEndscreen(PatreonEndScreen):
"Dave B",
"Delton Ding",
"Thomas Tarler",
"1st",
"1stViewMaths",
"Jacob Magnuson",
"Clark Gaebel",
"Mathias Jansson",