use core::ops::{Add, Mul, Neg, Sub};
use crate::linear_algebra::{Matrix, Vector};
use crate::scalar::Numeric;
use crate::spatial::{small_angle, small_angle_sq};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Quaternion<T: Numeric> {
w: T,
x: T,
y: T,
z: T,
}
impl<T: Numeric> Quaternion<T> {
#[inline]
pub fn new(w: T, x: T, y: T, z: T) -> Self {
Quaternion { w, x, y, z }
}
#[inline]
pub fn identity() -> Self {
Quaternion {
w: T::ONE,
x: T::ZERO,
y: T::ZERO,
z: T::ZERO,
}
}
#[inline]
pub fn from_array(a: [T; 4]) -> Self {
Quaternion {
w: a[0],
x: a[1],
y: a[2],
z: a[3],
}
}
#[inline]
pub fn from_scalar_vector(w: T, v: Vector<3, T>) -> Self {
Quaternion {
w,
x: v[0],
y: v[1],
z: v[2],
}
}
#[inline]
pub fn w(self) -> T {
self.w
}
#[inline]
pub fn x(self) -> T {
self.x
}
#[inline]
pub fn y(self) -> T {
self.y
}
#[inline]
pub fn z(self) -> T {
self.z
}
#[inline]
pub fn vec(self) -> Vector<3, T> {
Vector::new([self.x, self.y, self.z])
}
#[inline]
pub fn as_array(self) -> [T; 4] {
[self.w, self.x, self.y, self.z]
}
#[inline]
pub fn conjugate(self) -> Self {
Quaternion {
w: self.w,
x: -self.x,
y: -self.y,
z: -self.z,
}
}
#[inline]
pub fn norm_squared(self) -> T {
self.w * self.w + self.x * self.x + self.y * self.y + self.z * self.z
}
#[inline]
pub fn norm(self) -> T {
self.norm_squared().sqrt()
}
#[inline]
pub fn dot(self, r: Self) -> T {
self.w * r.w + self.x * r.x + self.y * r.y + self.z * r.z
}
#[inline]
pub fn inverse(self) -> Self {
self.conjugate() * self.norm_squared().recip()
}
#[inline]
pub fn normalized(self) -> Self {
self * self.norm().recip()
}
#[inline]
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]
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]
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]
pub fn from_axis_angle(axis: Vector<3, 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;
Quaternion {
w: half.cos(),
x: axis[0] * s,
y: axis[1] * s,
z: axis[2] * s,
}
}
#[inline]
pub fn from_scaled_axis(rotvec: Vector<3, 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)
};
Quaternion {
w,
x: rotvec[0] * scale,
y: rotvec[1] * scale,
z: rotvec[2] * scale,
}
}
#[inline]
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]
pub fn try_from_rotation_matrix(m: Matrix<3, 3, T>) -> Option<Self> {
let quarter = T::from_f64(0.25);
let trace = m[(0, 0)] + m[(1, 1)] + m[(2, 2)];
let q = if trace > T::ZERO {
let s = (trace + T::ONE).sqrt() * T::TWO; Quaternion::new(
quarter * s,
(m[(2, 1)] - m[(1, 2)]) / s,
(m[(0, 2)] - m[(2, 0)]) / s,
(m[(1, 0)] - m[(0, 1)]) / s,
)
} else if m[(0, 0)] > m[(1, 1)] && m[(0, 0)] > m[(2, 2)] {
let s = (T::ONE + m[(0, 0)] - m[(1, 1)] - m[(2, 2)]).sqrt() * T::TWO; Quaternion::new(
(m[(2, 1)] - m[(1, 2)]) / s,
quarter * s,
(m[(0, 1)] + m[(1, 0)]) / s,
(m[(0, 2)] + m[(2, 0)]) / s,
)
} else if m[(1, 1)] > m[(2, 2)] {
let s = (T::ONE + m[(1, 1)] - m[(0, 0)] - m[(2, 2)]).sqrt() * T::TWO; Quaternion::new(
(m[(0, 2)] - m[(2, 0)]) / s,
(m[(0, 1)] + m[(1, 0)]) / s,
quarter * s,
(m[(1, 2)] + m[(2, 1)]) / s,
)
} else {
let s = (T::ONE + m[(2, 2)] - m[(0, 0)] - m[(1, 1)]).sqrt() * T::TWO; Quaternion::new(
(m[(1, 0)] - m[(0, 1)]) / s,
(m[(0, 2)] + m[(2, 0)]) / s,
(m[(1, 2)] + m[(2, 1)]) / s,
quarter * s,
)
};
q.try_normalized()
}
#[inline]
pub fn to_rotation_matrix(self) -> Matrix<3, 3, 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) -> (Vector<3, 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) -> Vector<3, 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]
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_sq::<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: Vector<3, T>) -> Vector<3, T> {
let p = Quaternion {
w: T::ZERO,
x: v[0],
y: v[1],
z: v[2],
};
let r = self * p * self.conjugate();
Vector::new([r.x, r.y, r.z])
}
#[inline]
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,
}
}
}