#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Quat {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
#[allow(dead_code)]
pub fn quat(x: f32, y: f32, z: f32, w: f32) -> Quat {
Quat { x, y, z, w }
}
#[allow(dead_code)]
pub fn quat_identity() -> Quat {
quat(0.0, 0.0, 0.0, 1.0)
}
#[allow(dead_code)]
pub fn quat_norm(q: &Quat) -> f32 {
(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w).sqrt()
}
#[allow(dead_code)]
pub fn quat_normalize(q: &Quat) -> Quat {
let n = quat_norm(q);
if n < 1e-10 {
return quat_identity();
}
quat(q.x / n, q.y / n, q.z / n, q.w / n)
}
#[allow(dead_code)]
pub fn quat_conjugate(q: &Quat) -> Quat {
quat(-q.x, -q.y, -q.z, q.w)
}
#[allow(dead_code)]
pub fn quat_inverse(q: &Quat) -> Quat {
let n2 = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w;
if n2 < 1e-10 {
return quat_identity();
}
quat(-q.x / n2, -q.y / n2, -q.z / n2, q.w / n2)
}
#[allow(dead_code)]
pub fn quat_mul(a: &Quat, b: &Quat) -> Quat {
quat(
a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
)
}
#[allow(dead_code)]
pub fn quat_dot(a: &Quat, b: &Quat) -> f32 {
a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
}
#[allow(dead_code)]
pub fn quat_slerp(a: &Quat, b: &Quat, t: f32) -> Quat {
let t = t.clamp(0.0, 1.0);
let mut dot = quat_dot(a, b).clamp(-1.0, 1.0);
let mut b2 = *b;
if dot < 0.0 {
b2 = quat(-b.x, -b.y, -b.z, -b.w);
dot = -dot;
}
if dot > 0.9995 {
return quat_normalize(&quat(
a.x + t * (b2.x - a.x),
a.y + t * (b2.y - a.y),
a.z + t * (b2.z - a.z),
a.w + t * (b2.w - a.w),
));
}
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;
quat(
s0 * a.x + s1 * b2.x,
s0 * a.y + s1 * b2.y,
s0 * a.z + s1 * b2.z,
s0 * a.w + s1 * b2.w,
)
}
#[allow(dead_code)]
pub fn quat_from_axis_angle(axis: [f32; 3], angle: f32) -> Quat {
let half = angle * 0.5;
let s = half.sin();
quat(axis[0] * s, axis[1] * s, axis[2] * s, half.cos())
}
#[allow(dead_code)]
pub fn quat_rotate_vec(q: &Quat, v: [f32; 3]) -> [f32; 3] {
let vq = quat(v[0], v[1], v[2], 0.0);
let qc = quat_conjugate(q);
let r = quat_mul(&quat_mul(q, &vq), &qc);
[r.x, r.y, r.z]
}
#[allow(dead_code)]
pub fn quat_approx_eq(a: &Quat, b: &Quat, eps: f32) -> bool {
(a.x - b.x).abs() < eps
&& (a.y - b.y).abs() < eps
&& (a.z - b.z).abs() < eps
&& (a.w - b.w).abs() < eps
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
#[test]
fn test_identity_norm() {
let id = quat_identity();
assert!((quat_norm(&id) - 1.0).abs() < 1e-5);
}
#[test]
fn test_normalize() {
let q = quat(1.0, 1.0, 1.0, 1.0);
let n = quat_normalize(&q);
assert!((quat_norm(&n) - 1.0).abs() < 1e-5);
}
#[test]
fn test_conjugate() {
let q = quat(1.0, 2.0, 3.0, 4.0);
let c = quat_conjugate(&q);
assert_eq!(c.x, -1.0);
assert_eq!(c.y, -2.0);
assert_eq!(c.z, -3.0);
assert_eq!(c.w, 4.0);
}
#[test]
fn test_mul_identity() {
let id = quat_identity();
let q = quat_normalize(&quat(1.0, 0.0, 0.0, 1.0));
let result = quat_mul(&q, &id);
assert!(quat_approx_eq(&result, &q, 1e-5));
}
#[test]
fn test_rotate_x_axis_90deg() {
let axis = [0.0f32, 0.0, 1.0];
let q = quat_from_axis_angle(axis, PI / 2.0);
let v = quat_rotate_vec(&q, [1.0, 0.0, 0.0]);
assert!(v[0].abs() < 1e-5);
assert!((v[1] - 1.0).abs() < 1e-5);
}
#[test]
fn test_slerp_t0() {
let a = quat_identity();
let b = quat_from_axis_angle([0.0, 1.0, 0.0], PI / 2.0);
let r = quat_slerp(&a, &b, 0.0);
assert!(quat_approx_eq(&r, &a, 1e-5));
}
#[test]
fn test_slerp_t1() {
let a = quat_identity();
let b = quat_from_axis_angle([0.0, 1.0, 0.0], PI / 2.0);
let r = quat_slerp(&a, &b, 1.0);
assert!(quat_approx_eq(&r, &b, 1e-5));
}
#[test]
fn test_inverse_times_self_is_identity() {
let q = quat_normalize(&quat(1.0, 2.0, 3.0, 4.0));
let inv = quat_inverse(&q);
let result = quat_mul(&q, &inv);
assert!(quat_approx_eq(&result, &quat_identity(), 1e-5));
}
#[test]
fn test_dot_product() {
let id = quat_identity();
let d = quat_dot(&id, &id);
assert!((d - 1.0).abs() < 1e-5);
}
#[test]
fn test_axis_angle_roundtrip_norm() {
let q = quat_from_axis_angle([1.0, 0.0, 0.0], PI / 3.0);
assert!((quat_norm(&q) - 1.0).abs() < 1e-5);
}
}