use core::ops::{Add, Mul, Neg, Sub};
use crate::linear_algebra::{Matrix, Matrix3D, Vector, Vector3D};
use crate::scalar::Numeric;
use crate::spatial::{small_angle, small_angle_sq};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Quaternion<T: Numeric = f64> {
w: T,
x: T,
y: T,
z: T,
}
#[inline]
#[must_use]
fn unit_direction<T: Numeric>(v: Vector<3, T>) -> Option<Vector<3, T>> {
if !v.is_finite() {
return None;
}
let [x, y, z] = *v.as_array();
let scale = x.abs().max(y.abs()).max(z.abs());
if scale == T::ZERO {
return None;
}
(v / scale).try_normalized()
}
impl<T: Numeric> Quaternion<T> {
#[inline]
#[must_use]
pub fn new(w: T, x: T, y: T, z: T) -> Self {
Quaternion { w, x, y, z }
}
#[inline]
#[must_use]
pub fn identity() -> Self {
Quaternion {
w: T::ONE,
x: T::ZERO,
y: T::ZERO,
z: T::ZERO,
}
}
#[inline]
#[must_use]
pub fn from_array(a: [T; 4]) -> Self {
let [w, x, y, z] = a;
Quaternion { w, x, y, z }
}
#[inline]
#[must_use]
pub fn from_scalar_vector(w: T, v: Vector3D<T>) -> Self {
let [x, y, z] = *v.as_array();
Quaternion { w, x, y, z }
}
#[inline]
#[must_use]
pub fn w(self) -> T {
self.w
}
#[inline]
#[must_use]
pub fn x(self) -> T {
self.x
}
#[inline]
#[must_use]
pub fn y(self) -> T {
self.y
}
#[inline]
#[must_use]
pub fn z(self) -> T {
self.z
}
#[inline]
pub fn vec(self) -> Vector3D<T> {
Vector::new([self.x, self.y, self.z])
}
#[inline]
#[must_use]
pub fn as_array(self) -> [T; 4] {
[self.w, self.x, self.y, self.z]
}
#[inline]
#[must_use]
pub fn conjugate(self) -> Self {
Quaternion {
w: self.w,
x: -self.x,
y: -self.y,
z: -self.z,
}
}
#[inline]
#[must_use]
pub fn norm_squared(self) -> T {
self.w * self.w + self.x * self.x + self.y * self.y + self.z * self.z
}
#[inline]
#[must_use]
pub fn norm(self) -> T {
self.norm_squared().sqrt()
}
#[inline]
#[must_use]
pub fn dot(self, r: Self) -> T {
self.w * r.w + self.x * r.x + self.y * r.y + self.z * r.z
}
#[inline]
#[must_use]
pub fn inverse(self) -> Self {
self.conjugate() * self.norm_squared().recip()
}
#[inline]
#[must_use]
pub fn normalized(self) -> Self {
self * self.norm().recip()
}
#[inline]
#[must_use]
pub fn try_normalized(self) -> Option<Self> {
let n = self.norm();
if !n.is_finite() || n <= T::EPSILON {
None
} else {
Some(self * n.recip())
}
}
#[inline]
#[must_use]
pub fn exp(self) -> Self {
let vn_sq = self.x * self.x + self.y * self.y + self.z * self.z;
let ew = self.w.exp();
let (cos_v, sinc_v) = if vn_sq < small_angle_sq::<T>() {
(T::ONE - vn_sq / T::TWO, T::ONE - vn_sq / T::from_f64(6.0))
} else {
let vn = vn_sq.sqrt();
(vn.cos(), vn.sin() / vn)
};
let s = ew * sinc_v;
Quaternion {
w: ew * cos_v,
x: self.x * s,
y: self.y * s,
z: self.z * s,
}
}
#[inline]
#[must_use]
pub fn ln(self) -> Self {
let n = self.norm();
let vn = (self.x * self.x + self.y * self.y + self.z * self.z).sqrt();
let coeff = if vn < small_angle::<T>() && self.w > T::ZERO {
n.recip()
} else {
vn.atan2(self.w) / vn
};
Quaternion {
w: n.ln(),
x: self.x * coeff,
y: self.y * coeff,
z: self.z * coeff,
}
}
#[inline]
#[must_use]
pub fn from_axis_angle(axis: Vector3D<T>, angle: T) -> Self {
let an = axis.dot(axis).sqrt();
if an <= T::EPSILON {
return Self::identity();
}
let half = angle * T::HALF;
let s = half.sin() / an;
let [ax, ay, az] = *axis.as_array();
Quaternion {
w: half.cos(),
x: ax * s,
y: ay * s,
z: az * s,
}
}
#[inline]
#[must_use]
pub fn from_scaled_axis(rotvec: Vector3D<T>) -> Self {
let theta_sq = rotvec.dot(rotvec);
let (w, scale) = if theta_sq < small_angle_sq::<T>() {
(
T::ONE - theta_sq / T::from_f64(8.0),
T::HALF - theta_sq / T::from_f64(48.0),
)
} else {
let theta = theta_sq.sqrt();
let half = theta * T::HALF;
(half.cos(), half.sin() / theta)
};
let [rx, ry, rz] = *rotvec.as_array();
Quaternion {
w,
x: rx * scale,
y: ry * scale,
z: rz * scale,
}
}
#[inline]
#[must_use]
pub fn from_euler_zyx(roll: T, pitch: T, yaw: T) -> Self {
let (cr, sr) = ((roll * T::HALF).cos(), (roll * T::HALF).sin());
let (cp, sp) = ((pitch * T::HALF).cos(), (pitch * T::HALF).sin());
let (cy, sy) = ((yaw * T::HALF).cos(), (yaw * T::HALF).sin());
Quaternion {
w: cr * cp * cy + sr * sp * sy,
x: sr * cp * cy - cr * sp * sy,
y: cr * sp * cy + sr * cp * sy,
z: cr * cp * sy - sr * sp * cy,
}
}
#[inline]
#[must_use]
pub fn from_two_vectors(from: Vector<3, T>, to: Vector<3, T>) -> Self {
let (Some(a), Some(b)) = (unit_direction(from), unit_direction(to)) else {
return Self::identity();
};
let h = a + b;
let h_sq = h.norm_squared();
if h_sq == T::ZERO {
let [ax, ay, az] = *a.as_array();
let principal = if ax.abs() <= ay.abs() && ax.abs() <= az.abs() {
Vector::new([T::ONE, T::ZERO, T::ZERO])
} else if ay.abs() <= az.abs() {
Vector::new([T::ZERO, T::ONE, T::ZERO])
} else {
Vector::new([T::ZERO, T::ZERO, T::ONE])
};
return Self::from_scalar_vector(T::ZERO, a.cross(principal).normalized());
}
Self::from_scalar_vector(h_sq * T::HALF, a.cross(h)).normalized()
}
#[inline]
#[must_use]
pub fn try_from_rotation_matrix(m: Matrix3D<T>) -> Option<Self> {
let quarter = T::from_f64(0.25);
let [[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]] = m.into_array();
let trace = m00 + m11 + m22;
let q = if trace > T::ZERO {
let s = (trace + T::ONE).sqrt() * T::TWO; Quaternion::new(
quarter * s,
(m21 - m12) / s,
(m02 - m20) / s,
(m10 - m01) / s,
)
} else if m00 > m11 && m00 > m22 {
let s = (T::ONE + m00 - m11 - m22).sqrt() * T::TWO; Quaternion::new(
(m21 - m12) / s,
quarter * s,
(m01 + m10) / s,
(m02 + m20) / s,
)
} else if m11 > m22 {
let s = (T::ONE + m11 - m00 - m22).sqrt() * T::TWO; Quaternion::new(
(m02 - m20) / s,
(m01 + m10) / s,
quarter * s,
(m12 + m21) / s,
)
} else {
let s = (T::ONE + m22 - m00 - m11).sqrt() * T::TWO; Quaternion::new(
(m10 - m01) / s,
(m02 + m20) / s,
(m12 + m21) / s,
quarter * s,
)
};
q.try_normalized()
}
#[inline]
pub fn to_rotation_matrix(self) -> Matrix3D<T> {
let (w, x, y, z) = (self.w, self.x, self.y, self.z);
let two = T::TWO;
Matrix::new([
[
T::ONE - two * (y * y + z * z),
two * (x * y - w * z),
two * (x * z + w * y),
],
[
two * (x * y + w * z),
T::ONE - two * (x * x + z * z),
two * (y * z - w * x),
],
[
two * (x * z - w * y),
two * (y * z + w * x),
T::ONE - two * (x * x + y * y),
],
])
}
#[inline]
pub fn to_axis_angle(self) -> (Vector3D<T>, T) {
let q = if self.w < T::ZERO { -self } else { self };
let vn = (q.x * q.x + q.y * q.y + q.z * q.z).sqrt();
if vn <= T::EPSILON {
return (Vector::new([T::ONE, T::ZERO, T::ZERO]), T::ZERO);
}
let inv = vn.recip();
(
Vector::new([q.x * inv, q.y * inv, q.z * inv]),
T::TWO * vn.atan2(q.w),
)
}
#[inline]
pub fn to_scaled_axis(self) -> Vector3D<T> {
let q = if self.w < T::ZERO { -self } else { self };
let vn = (q.x * q.x + q.y * q.y + q.z * q.z).sqrt();
let coeff = if vn < small_angle::<T>() {
T::TWO
} else {
(T::TWO * vn.atan2(q.w)) / vn
};
Vector::new([q.x * coeff, q.y * coeff, q.z * coeff])
}
#[inline]
#[must_use]
pub fn to_euler_zyx(self) -> (T, T, T) {
let (w, x, y, z) = (self.w, self.x, self.y, self.z);
let two = T::TWO;
let sinp = two * (w * y - z * x);
if sinp.abs() >= T::ONE - small_angle::<T>() {
let pitch = (T::PI * T::HALF).copysign(sinp);
let a = two * x.atan2(w);
let yaw = if sinp > T::ZERO { -a } else { a };
return (T::ZERO, pitch, yaw);
}
let roll = (two * (w * x + y * z)).atan2(T::ONE - two * (x * x + y * y));
let pitch = sinp.asin();
let yaw = (two * (w * z + x * y)).atan2(T::ONE - two * (y * y + z * z));
(roll, pitch, yaw)
}
#[inline]
pub fn transform_point(self, v: Vector3D<T>) -> Vector3D<T> {
let [x, y, z] = *v.as_array();
let p = Quaternion {
w: T::ZERO,
x,
y,
z,
};
let r = self * p * self.conjugate();
Vector::new([r.x, r.y, r.z])
}
#[inline]
pub fn inverse_transform_point(self, v: Vector<3, T>) -> Vector<3, T> {
self.conjugate().transform_point(v)
}
#[inline]
#[must_use]
pub fn rotation_angle_to(self, other: Self) -> T {
let r = self.conjugate() * other;
let vn = (r.x * r.x + r.y * r.y + r.z * r.z).sqrt();
T::TWO * vn.atan2(r.w.abs())
}
#[inline]
#[must_use]
pub fn slerp(self, other: Self, t: T) -> Self {
let mut d = self.dot(other);
let mut q2 = other;
if d < T::ZERO {
d = -d;
q2 = -q2;
}
if d > T::ONE - T::EPSILON {
return (self * (T::ONE - t) + q2 * t).normalized();
}
let theta = d.acos();
let sin_theta = theta.sin();
let s0 = ((T::ONE - t) * theta).sin() / sin_theta;
let s1 = (t * theta).sin() / sin_theta;
(self * s0 + q2 * s1).normalized()
}
}
impl<T: Numeric> Add for Quaternion<T> {
type Output = Self;
#[inline]
fn add(self, r: Self) -> Self {
Quaternion {
w: self.w + r.w,
x: self.x + r.x,
y: self.y + r.y,
z: self.z + r.z,
}
}
}
impl<T: Numeric> Sub for Quaternion<T> {
type Output = Self;
#[inline]
fn sub(self, r: Self) -> Self {
Quaternion {
w: self.w - r.w,
x: self.x - r.x,
y: self.y - r.y,
z: self.z - r.z,
}
}
}
impl<T: Numeric> Neg for Quaternion<T> {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Quaternion {
w: -self.w,
x: -self.x,
y: -self.y,
z: -self.z,
}
}
}
impl<T: Numeric> Mul<T> for Quaternion<T> {
type Output = Self;
#[inline]
fn mul(self, s: T) -> Self {
Quaternion {
w: self.w * s,
x: self.x * s,
y: self.y * s,
z: self.z * s,
}
}
}
impl<T: Numeric> Mul for Quaternion<T> {
type Output = Self;
#[inline]
fn mul(self, r: Self) -> Self {
Quaternion {
w: self.w * r.w - self.x * r.x - self.y * r.y - self.z * r.z,
x: self.w * r.x + self.x * r.w + self.y * r.z - self.z * r.y,
y: self.w * r.y - self.x * r.z + self.y * r.w + self.z * r.x,
z: self.w * r.z + self.x * r.y - self.y * r.x + self.z * r.w,
}
}
}
impl<T: Numeric> Default for Quaternion<T> {
fn default() -> Self {
Self::identity()
}
}