#![allow(non_camel_case_types)]
use std::ops;
use std::fmt;
use crate::utils::{left_side_scalar_multiplication};
use crate::utils::Float;
use crate::vect::vect3;
use crate::vect::Similar;
use crate::matrix::matrix3;
#[derive(Clone, Copy)]
pub struct quatern<T : Float> {
pub t : T,
pub v : vect3<T>
}
pub type quaternf = quatern<f32>;
pub type quaternd = quatern<f64>;
impl<T: Float> fmt::Debug for quatern<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Q({:?}, V({:?}, {:?}, {:?}))", self.t, self.v.x, self.v.y, self.v.z)
}
}
impl<T: Float> ops::Add<quatern<T>> for quatern<T> {
type Output = quatern<T>;
fn add(self, _rhs: quatern<T>) -> quatern<T> {
quatern::<T> {t: self.t + _rhs.t, v: self.v + _rhs.v}
}
}
impl<T: Float> ops::Sub<quatern<T>> for quatern<T> {
type Output = quatern<T>;
fn sub(self, _rhs: quatern<T>) -> quatern<T> {
quatern::<T> {t: self.t - _rhs.t, v: self.v - _rhs.v}
}
}
impl<T: Float> ops::Mul<T> for quatern<T> {
type Output = quatern<T>;
fn mul(self, _rhs: T) -> quatern<T> {
quatern::<T> {t: self.t * _rhs, v: self.v * _rhs}
}
}
left_side_scalar_multiplication!(quaternf,f32);
left_side_scalar_multiplication!(quaternd,f64);
impl<T: Float> ops::Neg for quatern<T> {
type Output = quatern<T>;
fn neg(self) -> quatern<T> {
quatern::<T> {t: -self.t, v: -self.v}
}
}
impl<T: Float> ops::Mul<quatern<T>> for quatern<T> {
type Output = quatern<T>;
fn mul(self, _rhs: quatern<T>) -> quatern<T> {
quatern::<T> {t: self.t * _rhs.t - self.v * _rhs.v, v: _rhs.v * self.t + self.v * _rhs.t + self.v % _rhs.v}
}
}
impl<T: Float> quatern<T> {
pub fn new(t: T, v: vect3<T>) -> quatern<T> {
quatern::<T> { t: t, v: v }
}
pub fn identity() -> quatern<T> {
quatern::<T>::new(T::one(), vect3::<T>::new(T::zero(), T::zero(), T::zero()))
}
pub fn dot(self, q: quatern<T>) -> T {
self.t * q.t + self.v * q.v
}
pub fn norm(self) -> T {
self.dot(self).sqrt()
}
pub fn unit(self) -> quatern<T> {
self * self.norm().recip()
}
pub fn conj(self) -> quatern<T> {
quatern::<T>::new(self.t, -self.v)
}
pub fn from<K: Float>(q: quatern<K>) -> quatern<T> {
quatern::<T>::new(T::from(q.t).unwrap(), vect3::<T>::from(q.v))
}
pub fn cast<K: Float>(self) -> quatern<K> {
quatern::<K>::new(K::from(self.t).unwrap(), self.v.cast::<K>())
}
pub fn assert_unit_length(self) {
if (self.dot(self) - T::one()).abs() > T::from(1e5).unwrap() * T::epsilon() {
error!("arael::quatern: not unit length, norm = {:?}", self.norm());
}
}
pub fn rotate(self, v: vect3<T>) -> vect3<T> {
(self * quatern::<T>::new(T::zero(), v) * self.conj()).v
}
pub fn rotation_matrix(self) -> matrix3<T> {
let (v, t) = (self.v, self.t);
let x2 = v.x * v.x;
let y2 = v.y * v.y;
let z2 = v.z * v.z;
matrix3::<T>::from_rows(
vect3::<T>::new(T::one() - T::two() * (y2 + z2), T::two()*(v.x*v.y - v.z*t), T::two()*(v.x*v.z + v.y*t)),
vect3::<T>::new( T::two()*(v.x*v.y + v.z*t), T::one() - T::two() * (x2 + z2), T::two()*(v.y*v.z - v.x*t)),
vect3::<T>::new( T::two()*(v.x*v.z - v.y*t), T::two()*(v.y*v.z + v.x*t), T::one() - T::two() * (x2 + y2))
)
}
pub fn get_axis_angle(self) -> (vect3<T>, T) {
let angle = (T::two() * self.t.safe_acos()).rad2rad();
let s2 = T::one() - self.t * self.t;
if s2 > T::epsilon() * T::epsilon() {
(self.v * s2.sqrt().recip(), angle)
} else {
(vect3::<T>::new(T::one(), T::zero(), T::zero()), T::zero())
}
}
pub fn get_euler_angles(self) -> vect3<T> {
let ea_x = T::atan2(T::two() * (self.t * self.v.x + self.v.y * self.v.z), T::one() - T::two() * (self.v.x * self.v.x + self.v.y * self.v.y));
let ea_z = T::atan2(T::two() * (self.t * self.v.z + self.v.x * self.v.y), T::one() - T::two() * (self.v.y * self.v.y + self.v.z * self.v.z));
let s = T::two() * (self.t * self.v.y - self.v.z * self.v.x);
if s >= T::one() {
vect3::<T>::new(ea_x, T::half_pi(), ea_z)
} else if s <= -T::one() {
vect3::<T>::new(ea_x, -T::half_pi(), ea_z)
} else {
vect3::<T>::new(ea_x, s.asin(), ea_z)
}
}
pub fn from_euler_angles(ea: vect3<T>) -> quatern<T> {
let ha = ea * T::half();
let (shax, chax) = ha.x.sin_cos();
let (shay, chay) = ha.y.sin_cos();
let (shaz, chaz) = ha.z.sin_cos();
quatern::<T>::new(
chax * chay * chaz + shax * shay * shaz,
vect3::<T>::new(
shax * chay * chaz - chax * shay * shaz,
chax * shay * chaz + shax * chay * shaz,
chax * chay * shaz - shax * shay * chaz
)
)
}
pub fn from_axis_angle(normal: vect3<T>, angle: T) -> quatern<T> {
let half_angle = T::half() * angle;
let (sin_half_angle, cos_half_angle) = half_angle.sin_cos();
quatern::<T>::new(cos_half_angle, normal * sin_half_angle)
}
pub fn pow(self, f: T) -> quatern<T> {
let (axis, angle) = self.get_axis_angle();
Self::from_axis_angle(axis, f * angle)
}
pub fn log(self) -> quatern<T> {
let (axis, angle) = self.get_axis_angle();
Self::new(T::zero(), axis * angle)
}
pub fn exp(self) -> quatern<T> {
let angle = self.v.norm();
if angle < T::epsilon() {
return Self::identity();
}
let axis = self.v * angle.recip();
Self::from_axis_angle(axis, angle)
}
pub fn from_two_vectors(from: vect3<T>, to: vect3<T>) -> quatern<T> {
from.assert_unit_length();
to.assert_unit_length();
let mut mid = (from + to) * T::half();
let mid_len2 = mid * mid;
if mid_len2 < T::epsilon() {
return Self::from_axis_angle(from.across(), T::pi())
}
mid = mid * mid_len2.sqrt().recip();
return Self::new(mid * to, mid % to);
}
pub fn slerp(from: quatern<T>, to: quatern<T>, f: T) -> quatern<T> {
from * (from.conj() * to).pow(f)
}
pub fn similar(self, other: quatern<T>) -> bool {
(self.t - other.t).abs() < T::from(10).unwrap() * (self.t.abs() + other.t.abs() + T::epsilon()) * T::epsilon() && self.v.similar(other.v)
}
}
pub use arael_sym::quaternsym;
#[cfg(test)]
mod tests {
use super::*;
use crate::vect::vect3d;
use crate::matrix::matrix3d;
#[test]
fn test() {
let q1 = quaternd::new(1.0, vect3d::new(4.0, 2.0, 2.0));
let q2 = quaternd::new(3.0, vect3d::new(-3.0, 1.0, 0.0));
assert_eq!(q1.t, 1.0); assert_eq!(q1.v.x, 4.0); assert_eq!(q1.v.y, 2.0); assert_eq!(q1.v.z, 2.0);
assert_eq!(q1.norm(), 5.0);
assert!((-q1).similar(quaternd::new(-1.0, vect3d::new(-4.0, -2.0, -2.0))));
assert!(q1.unit().similar(quaternd::new(1.0 / 5.0, vect3d::new(4.0 / 5.0, 2.0 / 5.0, 2.0 / 5.0))));
assert!((2.0 * q1).similar(quaternd::new(2.0, vect3d::new(8.0, 4.0, 4.0))));
assert!((q1 * 2.0).similar(quaternd::new(2.0, vect3d::new(8.0, 4.0, 4.0))));
assert!(q1.conj().similar(quaternd::new(1.0, vect3d::new(-4.0, -2.0, -2.0))));
assert!(q1.cast::<f32>().cast::<f64>().similar(q1));
assert!((q1 + q2).similar(quaternd::new(4.0, vect3d::new(1.0, 3.0, 2.0))));
assert!((q1 - q2).similar(quaternd::new(-2.0, vect3d::new(7.0, 1.0, 2.0))));
assert_eq!(q1.dot(q2), 3.0 - 12.0 + 2.0 + 0.0);
assert!((q1 * q2).similar(quaternd::new(13.0, vect3d::new(7.0, 1.0, 16.0))));
let v = vect3d::new(30.0, -10.0, 20.0);
let ea = vect3d::new(0.4, -0.26, 1.1);
assert!(quaternd::from_euler_angles(ea).rotate(v).similar(matrix3d::rotation_from_euler_angles(ea) * v));
assert!(quaternd::from_euler_angles(ea).get_euler_angles().similar(ea));
assert!((quaternd::from_euler_angles(vect3d::new(0.0, 0.0, ea.z)) * quaternd::from_euler_angles(vect3d::new(0.0, ea.y, 0.0)) * quaternd::from_euler_angles(vect3d::new(ea.x, 0.0, 0.0))).similar(quaternd::from_euler_angles(ea)));
let axis = vect3d::new(12.0, -1.0, 3.0).unit();
let angle = 0.234;
let (ret_axis, ret_angle) = quaternd::from_axis_angle(axis, angle).get_axis_angle();
assert!(axis.similar(ret_axis));
assert!((angle - ret_angle).abs() < 10.0*f64::EPSILON);
assert!((quaternd::from_axis_angle(axis, angle).rotate(v) - vect3d::new(30.282, -12.606, 18.002)).norm() < 0.001);
}
#[test]
fn test_exp_zero_vector() {
let q = quaternd::new(0.0, vect3d::new(0.0, 0.0, 0.0));
let r = q.exp();
assert!(r.t.is_finite());
assert!(r.v.is_finite());
assert!(r.similar(quaternd::identity()));
}
#[test]
fn test_log_exp_roundtrip() {
let axis = vect3d::new(1.0, 2.0, 3.0).unit();
let angle = 0.8;
let q = quaternd::from_axis_angle(axis, angle);
let roundtrip = q.log().exp();
assert!(q.similar(roundtrip));
}
#[test]
fn test_slerp_endpoints() {
let q1 = quaternd::from_euler_angles(vect3d::new(0.1, 0.2, 0.3));
let q2 = quaternd::from_euler_angles(vect3d::new(0.5, -0.1, 1.0));
assert!(quaternd::slerp(q1, q2, 0.0).similar(q1));
assert!(quaternd::slerp(q1, q2, 1.0).similar(q2));
}
#[test]
fn test_slerp_midpoint() {
let axis = vect3d::new(0.0, 0.0, 1.0);
let q1 = quaternd::from_axis_angle(axis, 0.0);
let q2 = quaternd::from_axis_angle(axis, 1.0);
let mid = quaternd::slerp(q1, q2, 0.5);
let (_, mid_angle) = mid.get_axis_angle();
assert!((mid_angle - 0.5).abs() < 1e-10);
}
#[test]
fn test_pow_identity() {
let axis = vect3d::new(1.0, 0.0, 0.0);
let q = quaternd::from_axis_angle(axis, 0.6);
assert!(q.pow(1.0).similar(q));
assert!(q.pow(0.0).similar(quaternd::identity()));
}
#[test]
fn test_from_two_vectors_opposite() {
let a = vect3d::new(1.0, 0.0, 0.0);
let b = vect3d::new(-1.0, 0.0, 0.0);
let q = quaternd::from_two_vectors(a, b);
let rotated = q.rotate(a);
assert!(rotated.similar(b));
}
#[test]
fn test_from_two_vectors_same() {
let a = vect3d::new(0.0, 1.0, 0.0);
let q = quaternd::from_two_vectors(a, a);
assert!(q.rotate(a).similar(a));
}
}