use std::ops::{Add, Mul, Neg, Sub};
use super::VersorAction;
#[repr(C, align(16))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "vulkan", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct Quaternion {
pub w: f32, pub x: f32, pub y: f32, pub z: f32, }
impl Default for Quaternion {
#[inline(always)]
fn default() -> Self {
Self::identity()
}
}
impl Quaternion {
#[inline(always)]
pub const fn identity() -> Self {
Self { w: 1.0, x: 0.0, y: 0.0, z: 0.0 }
}
#[inline(always)]
pub const fn new(w: f32, x: f32, y: f32, z: f32) -> Self {
Self { w, x, y, z }
}
pub fn from_axis_angle(axis: &[f32; 3], angle_rad: f32) -> Self {
let half = angle_rad * 0.5;
let s = half.sin();
let norm = (axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]).sqrt();
let scale = if norm > 1e-12 { s / norm } else { 0.0 };
Self {
w: half.cos(),
x: axis[0] * scale,
y: axis[1] * scale,
z: axis[2] * scale,
}
}
#[inline(always)]
pub fn norm_sq(&self) -> f32 {
self.w * self.w + self.x * self.x + self.y * self.y + self.z * self.z
}
#[inline(always)]
pub fn norm(&self) -> f32 {
self.norm_sq().sqrt()
}
#[inline(always)]
pub fn conjugate(&self) -> Self {
Self {
w: self.w,
x: -self.x,
y: -self.y,
z: -self.z,
}
}
#[inline]
pub fn inverse(&self) -> Self {
let n_sq = self.norm_sq();
assert!(n_sq > 1e-15, "Cannot invert zero-norm quaternion");
let inv = 1.0 / n_sq;
Self {
w: self.w * inv,
x: -self.x * inv,
y: -self.y * inv,
z: -self.z * inv,
}
}
#[inline]
pub fn normalize(&self) -> Self {
let n = self.norm();
if n > 1e-12 {
let inv = 1.0 / n;
Self {
w: self.w * inv,
x: self.x * inv,
y: self.y * inv,
z: self.z * inv,
}
} else {
Self::identity()
}
}
#[inline]
pub fn hamilton_product(&self, rhs: &Self) -> Self {
Self {
w: self.w * rhs.w - self.x * rhs.x - self.y * rhs.y - self.z * rhs.z,
x: self.w * rhs.x + self.x * rhs.w + self.y * rhs.z - self.z * rhs.y,
y: self.w * rhs.y - self.x * rhs.z + self.y * rhs.w + self.z * rhs.x,
z: self.w * rhs.z + self.x * rhs.y - self.y * rhs.x + self.z * rhs.w,
}
}
#[inline]
pub fn rotate_vector3(&self, v: &[f32; 3]) -> [f32; 3] {
let qv = [self.x, self.y, self.z];
let t = [
2.0 * (qv[1] * v[2] - qv[2] * v[1]),
2.0 * (qv[2] * v[0] - qv[0] * v[2]),
2.0 * (qv[0] * v[1] - qv[1] * v[0]),
];
[
v[0] + self.w * t[0] + (qv[1] * t[2] - qv[2] * t[1]),
v[1] + self.w * t[1] + (qv[2] * t[0] - qv[0] * t[2]),
v[2] + self.w * t[2] + (qv[0] * t[1] - qv[1] * t[0]),
]
}
pub fn slerp(q0: Self, mut q1: Self, t: f32) -> Self {
let mut dot = q0.w * q1.w + q0.x * q1.x + q0.y * q1.y + q0.z * q1.z;
if dot < 0.0 {
q1 = -q1;
dot = -dot;
}
let dot = dot.clamp(-1.0, 1.0);
if dot > 0.9995 {
let res = q0 * (1.0 - t) + q1 * t;
return res.normalize();
}
let theta_0 = dot.acos();
let theta = theta_0 * t;
let sin_theta = theta.sin();
let sin_theta_0 = theta_0.sin();
let s0 = (theta_0 - theta).sin() / sin_theta_0;
let s1 = sin_theta / sin_theta_0;
(q0 * s0) + (q1 * s1)
}
}
impl VersorAction<[f32; 3]> for Quaternion {
#[inline]
fn transform_in_place(&self, target: &mut [f32; 3]) {
*target = self.rotate_vector3(target);
}
}
impl Mul for Quaternion {
type Output = Self;
#[inline(always)]
fn mul(self, rhs: Self) -> Self {
self.hamilton_product(&rhs)
}
}
impl Add for Quaternion {
type Output = Self;
#[inline(always)]
fn add(self, rhs: Self) -> Self {
Self {
w: self.w + rhs.w,
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}
impl Sub for Quaternion {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: Self) -> Self {
Self {
w: self.w - rhs.w,
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
impl Mul<f32> for Quaternion {
type Output = Self;
#[inline(always)]
fn mul(self, s: f32) -> Self {
Self {
w: self.w * s,
x: self.x * s,
y: self.y * s,
z: self.z * s,
}
}
}
impl Neg for Quaternion {
type Output = Self;
#[inline(always)]
fn neg(self) -> Self {
Self {
w: -self.w,
x: -self.x,
y: -self.y,
z: -self.z,
}
}
}