diff --git a/active_projects/quaternions.py b/active_projects/quaternions.py index 191ee427..484e4447 100644 --- a/active_projects/quaternions.py +++ b/active_projects/quaternions.py @@ -4778,7 +4778,8 @@ class HypersphereStereographicProjection(SpecialThreeDScene): [-1, 1, 0, 0], [0, 0, 1, 1], [0, 1, -1, 1], - ] + ], + "unit_labels_scale_factor": 1, } def construct(self): @@ -4827,6 +4828,7 @@ class HypersphereStereographicProjection(SpecialThreeDScene): return label label.add_updater(update_label) + self.pink_dot_label = label def get_pq_point(): point = self.project_quaternion(q_tracker.get_value()) @@ -4841,9 +4843,12 @@ class HypersphereStereographicProjection(SpecialThreeDScene): def get_pq_line(): point = get_pq_point() norm = get_norm(point) + origin = self.axes.coords_to_point(0, 0, 0) if norm > dot_radius: + point -= origin point *= (norm - dot_radius) / norm - result = Line(ORIGIN, point) + point += origin + result = Line(origin, point) result.set_stroke(width=1) return result @@ -5114,6 +5119,7 @@ class HypersphereStereographicProjection(SpecialThreeDScene): labels = VGroup() for tex, coords, vect in tex_coords_vects: label = TexMobject(tex) + label.scale(self.unit_labels_scale_factor) label.rotate(90 * DEGREES, RIGHT) label.next_to(c2p(*coords), vect, SMALL_BUFF) labels.add(label) @@ -5333,10 +5339,12 @@ class RuleOfQuaternionMultiplication(HypersphereStereographicProjection): def add_unit_labels(self): labels = self.unit_labels = self.get_unit_labels() one_label = TexMobject("1") + one_label.scale(self.unit_labels_scale_factor) one_label.set_shade_in_3d(True) one_label.rotate(90 * DEGREES, RIGHT) one_label.next_to(ORIGIN, IN + RIGHT, SMALL_BUFF) - self.add(labels, one_label) + labels.add(one_label) + self.add(labels) def show_multiplication_by_i_on_circle_1i(self): m_tracker = self.multiplier_tracker @@ -6420,27 +6428,95 @@ class QuaternionEndscreen(PatreonEndScreen): } -class Thumbnail(RuleOfQuaternionMultiplication): +class ThumbnailP1(RuleOfQuaternionMultiplication): CONFIG = { "three_d_axes_config": { "num_axis_pieces": 20, - } + }, + "unit_labels_scale_factor": 1.5, + "quaternion": [1, 0, 0, 0], } def construct(self): self.setup_all_trackers() - quat = normalize([-0.5, 0.5, -0.5, 0.5]) - self.multiplier_tracker.set_value(quat) - self.q_tracker.set_value(quat) - sphere = self.get_projected_sphere(0, solid=False) - # self.specially_color_sphere(sphere) - # sphere.set_fill(opacity=0.5) - sphere.set_fill_by_checkerboard(BLUE_E, BLUE, opacity=0.8) - for face in sphere: + self.remove(self.pink_dot_label) + q_tracker = self.q_tracker + m_tracker = self.multiplier_tracker + + # quat = normalize([-0.5, 0.5, -0.5, 0.5]) + quat = normalize(self.quaternion) + m_tracker.set_value(quat) + q_tracker.set_value(quat) + proj_sphere = self.get_projected_sphere(0, solid=False) + # self.specially_color_sphere(proj_sphere) + proj_sphere.set_color_by_gradient( + BLUE, YELLOW + ) + proj_sphere.set_stroke(WHITE) + proj_sphere.set_fill(opacity=0.4) + for i, face in enumerate(proj_sphere): + alpha = i / len(proj_sphere) + opacity = 0.7 * (1 - there_and_back(alpha)) + face.set_fill(opacity=opacity) + + # unit_sphere = self.get_projected_sphere(0, quaternion=[1, 0, 0, 0], solid=False) + # self.specially_color_sphere(unit_sphere) + # unit_sphere.set_stroke(width=0) + # proj_sphere.set_fill_by_checkerboard(BLUE_E, BLUE, opacity=0.8) + for face in proj_sphere: face.points = face.points[::-1] + max_r = np.max(np.apply_along_axis(get_norm, 1, face.points)) + if max_r > 30: + face.fade(1) + + for label in self.unit_labels: + label.set_shade_in_3d(False) + label.set_background_stroke(color=BLACK, width=2) + + self.add(proj_sphere) + # self.add(unit_sphere) + + for mobject in self.mobjects: + try: + mobject.shift(IN) + except ValueError: + pass self.set_camera_orientation( phi=70 * DEGREES, theta=-110 * DEGREES, ) - self.add(sphere) + + +class ThumbnailP2(ThumbnailP1): + CONFIG = { + "quaternion": [0, 1, 0, 0], + } + + +class ThumbnailOverlay(Scene): + def construct(self): + title = TextMobject("Quaternions") + title.set_width(8) + title.to_edge(UP) + v_line = Line(DOWN, UP) + v_line.set_height(FRAME_HEIGHT) + + title.set_background_stroke(color=BLACK, width=1) + + rect = BackgroundRectangle(title[4:6]) + rect.set_fill(opacity=1) + rect.stretch(0.9, 0) + rect.stretch(1.1, 1) + title.add_to_back(BackgroundRectangle(title[0])) + title.add_to_back(rect) + + arrow = Arrow(LEFT, RIGHT) + arrow.scale(1.5) + arrow.tip.scale(2) + arrow.set_stroke(width=10) + arrow.set_color(YELLOW) + + self.add(v_line) + self.add(arrow) + self.add(title) diff --git a/scene/three_d_scene.py b/scene/three_d_scene.py index 133f6d89..430de9fa 100644 --- a/scene/three_d_scene.py +++ b/scene/three_d_scene.py @@ -74,9 +74,11 @@ class ThreeDScene(Scene): return moving_mobjects def add_fixed_orientation_mobjects(self, *mobjects, **kwargs): + self.add(*mobjects) self.camera.add_fixed_orientation_mobjects(*mobjects, **kwargs) def add_fixed_in_frame_mobjects(self, *mobjects): + self.add(*mobjects) self.camera.add_fixed_in_frame_mobjects(*mobjects) def remove_fixed_orientation_mobjects(self, *mobjects): diff --git a/utils/space_ops.py b/utils/space_ops.py index 07d8ea49..821e86ac 100644 --- a/utils/space_ops.py +++ b/utils/space_ops.py @@ -7,13 +7,46 @@ from constants import TAU from functools import reduce from utils.iterables import adjacent_pairs -# Matrix operations - def get_norm(vect): return sum([x**2 for x in vect])**0.5 +# Quaternions +# TODO, implement quaternion type + + +def quaternion_mult(q1, q2): + w1, x1, y1, z1 = q1 + w2, x2, y2, z2 = q2 + return np.array([ + w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2, + w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2, + w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2, + w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2, + ]) + + +def quaternion_from_angle_axis(angle, axis): + return np.append( + np.cos(angle / 2), + np.sin(angle / 2) * normalize(axis) + ) + + +def quaternion_conjugate(quaternion): + result = np.array(quaternion) + result[1:] *= -1 + return result + + +def rotate_vector(vector, angle, axis=OUT): + quat = quaternion_from_angle_axis(angle, axis) + quat_inv = quaternion_conjugate(quat) + product = reduce(quaternion_mult, [quat, vector, quat_inv]) + return product[1:] + + def thick_diagonal(dim, thickness=2): row_indices = np.arange(dim).repeat(dim).reshape((dim, dim)) col_indices = np.transpose(row_indices) @@ -64,10 +97,6 @@ def z_to_vector(vector): return np.dot(rotation_about_z(theta), phi_down) -def rotate_vector(vector, angle, axis=OUT): - return np.dot(rotation_matrix(angle, axis), vector) - - def angle_between(v1, v2): return np.arccos(np.dot( v1 / get_norm(v1),