"""Matrix transformations."""
import numpy as np
from numpy.typing import NDArray
from .types import EulerAngles, Quaternion, RotationMatrix, TransformMatrix, Vector3D
[docs]
def create_rotation_matrix(rotation: EulerAngles) -> RotationMatrix:
"""Convert euler angles (XYZ) to 3Γ3 rotation matrix."""
angles = np.radians(rotation)
cx, cy, cz = np.cos(angles)
sx, sy, sz = np.sin(angles)
return np.array(
[
[cy * cz, -cy * sz, sy],
[cx * sz + cz * sx * sy, cx * cz - sx * sy * sz, -cy * sx],
[sx * sz - cx * cz * sy, cz * sx + cx * sy * sz, cx * cy],
],
dtype=np.float32,
)
[docs]
def euler_to_quat(rotation: EulerAngles) -> Quaternion:
"""Convert euler angles (XYZ) to quaternion (XYZW)."""
x, y, z = np.radians(rotation)
hx, hy, hz = x * 0.5, y * 0.5, z * 0.5
cx, cy, cz = np.cos([hx, hy, hz])
sx, sy, sz = np.sin([hx, hy, hz])
return np.array(
[
sx * cy * cz - cx * sy * sz,
cx * sy * cz + sx * cy * sz,
cx * cy * sz - sx * sy * cz,
cx * cy * cz + sx * sy * sz,
],
dtype=np.float32,
)
[docs]
def quaternions_to_euler(rotations: NDArray[np.float32]) -> EulerAngles:
"""Convert quaternion keyframes (XYZW) to continuous XYZ euler angles."""
x, y, z, w = np.moveaxis(rotations, -1, 0)
roll = np.arctan2(2.0 * (w * x + y * z), 1.0 - 2.0 * (x * x + y * y))
pitch = np.arcsin(np.clip(2.0 * (w * y - z * x), -1.0, 1.0))
yaw = np.arctan2(2.0 * (w * z + x * y), 1.0 - 2.0 * (y * y + z * z))
angles = np.stack((roll, pitch, yaw), axis=-1)
return np.degrees(np.unwrap(angles, axis=0)).astype(np.float32)