Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion manim/utils/space_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,16 @@ def angle_axis_from_quaternion(quaternion: Sequence[float]) -> Sequence[float]:
Returns
-------
Sequence[float]
Gives the angle and axis
The angle, in the range ``[0, PI]``, and the axis it is measured about.
"""
axis = normalize(quaternion[1:], fall_back=np.array([1, 0, 0]))
angle = 2 * np.arccos(quaternion[0])
if angle > TAU / 2:
# A rotation by ``angle`` about ``axis`` is a rotation by
# ``TAU - angle`` about ``-axis``, so the axis has to be flipped
# along with the angle.
angle = TAU - angle
axis = -axis
return angle, axis


Expand Down
28 changes: 28 additions & 0 deletions tests/module/utils/test_space_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import numpy as np
import pytest
from scipy.spatial.transform import Rotation

from manim.utils.space_ops import *
from manim.utils.space_ops import shoelace
Expand Down Expand Up @@ -54,6 +55,33 @@ def test_rotation_matrices():
)


@pytest.mark.parametrize(
"angle",
# the first three are at or below PI, where no folding happens
[0.5, 2.0, np.pi, 3.5, 4.0, 5.5, 2 * np.pi - 0.01],
)
def test_angle_axis_from_quaternion(angle):
axis = normalize(np.array([1.0, -2.0, 3.0]))
quaternion = quaternion_from_angle_axis(angle, axis)
result_angle, result_axis = angle_axis_from_quaternion(quaternion)

# the returned pair must describe the same rotation as the input
np.testing.assert_allclose(
rotation_matrix(result_angle, result_axis),
rotation_matrix(angle, axis),
atol=1e-8,
)

# ... expressed the way scipy's Rotation.as_rotvec expresses it, i.e. with
# the angle in [0, PI] and the direction carried by the axis
rotation_vector = Rotation.from_quat([*quaternion[1:], quaternion[0]]).as_rotvec()
assert 0 <= result_angle <= np.pi
np.testing.assert_allclose(result_angle, np.linalg.norm(rotation_vector), atol=1e-8)
np.testing.assert_allclose(
result_axis, rotation_vector / np.linalg.norm(rotation_vector), atol=1e-8
)


def test_angle_of_vector():
assert angle_of_vector(np.array([1, 1, 1])) == np.pi / 4
assert (
Expand Down