#![deny(missing_docs)]
#![no_std]
#[cfg(feature = "std")]
extern crate std;
use core::{
borrow::Borrow,
cmp::Ordering,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
};
use num_traits::{ConstOne, ConstZero, Inv, Num, One, Zero};
#[cfg(any(feature = "std", feature = "libm"))]
use {
core::num::FpCategory,
num_traits::{float::Float, FloatConst},
};
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Quaternion<T> {
pub w: T,
pub x: T,
pub y: T,
pub z: T,
}
pub type Q32 = Quaternion<f32>;
pub type Q64 = Quaternion<f64>;
impl<T> Quaternion<T> {
#[inline]
pub const fn new(w: T, x: T, y: T, z: T) -> Self {
Self { w, x, y, z }
}
}
impl<T> Quaternion<T>
where
T: ConstZero,
{
pub const ZERO: Self = Self::new(T::ZERO, T::ZERO, T::ZERO, T::ZERO);
}
impl<T> ConstZero for Quaternion<T>
where
T: ConstZero,
{
const ZERO: Self = Self::ZERO;
}
impl<T> Zero for Quaternion<T>
where
T: Zero,
{
#[inline]
fn zero() -> Self {
Self::new(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero())
}
#[inline]
fn is_zero(&self) -> bool {
self.w.is_zero() && self.x.is_zero() && self.y.is_zero() && self.z.is_zero()
}
#[inline]
fn set_zero(&mut self) {
self.w.set_zero();
self.x.set_zero();
self.y.set_zero();
self.z.set_zero();
}
}
impl<T> Quaternion<T>
where
T: ConstZero + ConstOne,
{
pub const ONE: Self = Self::new(T::ONE, T::ZERO, T::ZERO, T::ZERO);
pub const I: Self = Self::new(T::ZERO, T::ONE, T::ZERO, T::ZERO);
pub const J: Self = Self::new(T::ZERO, T::ZERO, T::ONE, T::ZERO);
pub const K: Self = Self::new(T::ZERO, T::ZERO, T::ZERO, T::ONE);
}
impl<T> ConstOne for Quaternion<T>
where
T: ConstZero + ConstOne + Num + Clone,
{
const ONE: Self = Self::ONE;
}
impl<T> One for Quaternion<T>
where
T: Num + Clone,
{
#[inline]
fn one() -> Self {
Self::new(One::one(), Zero::zero(), Zero::zero(), Zero::zero())
}
#[inline]
fn is_one(&self) -> bool {
self.w.is_one() && self.x.is_zero() && self.y.is_zero() && self.z.is_zero()
}
#[inline]
fn set_one(&mut self) {
self.w.set_one();
self.x.set_zero();
self.y.set_zero();
self.z.set_zero();
}
}
impl<T> Quaternion<T>
where
T: Zero + One,
{
#[inline]
pub fn i() -> Self {
Self::new(T::zero(), T::one(), T::zero(), T::zero())
}
#[inline]
pub fn j() -> Self {
Self::new(T::zero(), T::zero(), T::one(), T::zero())
}
#[inline]
pub fn k() -> Self {
Self::new(T::zero(), T::zero(), T::zero(), T::one())
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
T: Float,
{
#[inline]
pub fn nan() -> Self {
let nan = T::nan();
Self::new(nan, nan, nan, nan)
}
}
impl<T> Quaternion<T>
where
T: Clone + Mul<T, Output = T> + Add<T, Output = T>,
{
#[inline]
pub fn norm_sqr(&self) -> T {
(self.w.clone() * self.w.clone() + self.y.clone() * self.y.clone())
+ (self.x.clone() * self.x.clone() + self.z.clone() * self.z.clone())
}
}
impl<T> Quaternion<T>
where
T: Clone + Neg<Output = T>,
{
#[inline]
pub fn conj(&self) -> Self {
Self::new(
self.w.clone(),
-self.x.clone(),
-self.y.clone(),
-self.z.clone(),
)
}
}
impl<T> Quaternion<T>
where
for<'a> &'a Self: Inv<Output = Quaternion<T>>,
{
#[inline]
pub fn inv(&self) -> Self {
Inv::inv(self)
}
}
impl<T> Inv for &Quaternion<T>
where
T: Clone + Neg<Output = T> + Num,
{
type Output = Quaternion<T>;
#[inline]
fn inv(self) -> Self::Output {
let norm_sqr = self.norm_sqr();
Quaternion::new(
self.w.clone() / norm_sqr.clone(),
-self.x.clone() / norm_sqr.clone(),
-self.y.clone() / norm_sqr.clone(),
-self.z.clone() / norm_sqr,
)
}
}
impl<T> Inv for Quaternion<T>
where
for<'a> &'a Self: Inv<Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn inv(self) -> Self::Output {
Inv::inv(&self)
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
T: Float,
{
#[inline]
pub fn norm(self) -> T {
let Self { w, x, y, z } = self;
let norm_sqr = w * w + x * x + y * y + z * z;
if norm_sqr.is_normal() {
norm_sqr.sqrt()
} else {
self.handle_non_normal_cases(norm_sqr)
}
}
fn handle_non_normal_cases(self, norm_sqr: T) -> T {
debug_assert!(!norm_sqr.is_normal());
let s = T::min_positive_value();
if norm_sqr < s {
if self.is_zero() {
T::zero()
} else {
(self / s).fast_norm() * s
}
} else if norm_sqr.is_infinite() {
(self * s).fast_norm() / s
} else {
debug_assert!(norm_sqr.is_nan(), "norm_sqr is not NaN");
T::nan()
}
}
#[inline]
pub fn fast_norm(self) -> T {
self.norm_sqr().sqrt()
}
#[inline]
pub fn normalize(self) -> Option<UnitQuaternion<T>> {
let norm = self.norm();
match norm.classify() {
FpCategory::Normal | FpCategory::Subnormal => Some(UnitQuaternion(self / norm)),
_ => None,
}
}
}
impl<T> From<T> for Quaternion<T>
where
T: Zero,
{
#[inline]
fn from(a: T) -> Self {
Self::new(a, T::zero(), T::zero(), T::zero())
}
}
impl<'a, T> From<&'a T> for Quaternion<T>
where
T: Clone + Zero,
{
#[inline]
fn from(a: &T) -> Self {
From::from(a.clone())
}
}
impl<T> From<UnitQuaternion<T>> for Quaternion<T> {
#[inline]
fn from(q: UnitQuaternion<T>) -> Self {
q.0
}
}
impl<'a, T> From<&'a UnitQuaternion<T>> for &'a Quaternion<T> {
#[inline]
fn from(q: &'a UnitQuaternion<T>) -> Self {
&q.0
}
}
impl<T> Add<Quaternion<T>> for Quaternion<T>
where
T: Add<T, Output = T>,
{
type Output = Quaternion<T>;
#[inline]
fn add(self, rhs: Quaternion<T>) -> Self::Output {
Self::new(
self.w + rhs.w,
self.x + rhs.x,
self.y + rhs.y,
self.z + rhs.z,
)
}
}
impl<T> Add<T> for Quaternion<T>
where
T: Add<T, Output = T>,
{
type Output = Quaternion<T>;
#[inline]
fn add(self, rhs: T) -> Self::Output {
Self::new(self.w + rhs, self.x, self.y, self.z)
}
}
impl<T> Add<UnitQuaternion<T>> for Quaternion<T>
where
T: Add<T, Output = T>,
{
type Output = Quaternion<T>;
#[inline]
fn add(self, rhs: UnitQuaternion<T>) -> Self::Output {
self + rhs.0
}
}
impl<T> Sub<Quaternion<T>> for Quaternion<T>
where
T: Sub<T, Output = T>,
{
type Output = Quaternion<T>;
#[inline]
fn sub(self, rhs: Quaternion<T>) -> Self::Output {
Self::new(
self.w - rhs.w,
self.x - rhs.x,
self.y - rhs.y,
self.z - rhs.z,
)
}
}
impl<T> Sub<T> for Quaternion<T>
where
T: Sub<T, Output = T>,
{
type Output = Quaternion<T>;
#[inline]
fn sub(self, rhs: T) -> Self::Output {
Self::new(self.w - rhs, self.x, self.y, self.z)
}
}
impl<T> Sub<UnitQuaternion<T>> for Quaternion<T>
where
T: Sub<T, Output = T>,
{
type Output = Quaternion<T>;
#[inline]
fn sub(self, rhs: UnitQuaternion<T>) -> Self::Output {
self - rhs.0
}
}
impl<T> Mul<Quaternion<T>> for Quaternion<T>
where
T: Add<T, Output = T> + Sub<T, Output = T> + Mul<T, Output = T> + Clone,
{
type Output = Quaternion<T>;
#[inline]
fn mul(self, rhs: Quaternion<T>) -> Self::Output {
let a = self.w.clone() * rhs.w.clone()
- self.x.clone() * rhs.x.clone()
- self.y.clone() * rhs.y.clone()
- self.z.clone() * rhs.z.clone();
let b = self.w.clone() * rhs.x.clone()
+ self.x.clone() * rhs.w.clone()
+ self.y.clone() * rhs.z.clone()
- self.z.clone() * rhs.y.clone();
let c = self.w.clone() * rhs.y.clone() - self.x.clone() * rhs.z.clone()
+ self.y.clone() * rhs.w.clone()
+ self.z.clone() * rhs.x.clone();
let d = self.w * rhs.z + self.x * rhs.y - self.y * rhs.x + self.z * rhs.w;
Self::new(a, b, c, d)
}
}
impl<T> Mul<T> for Quaternion<T>
where
T: Mul<T, Output = T> + Clone,
{
type Output = Quaternion<T>;
#[inline]
fn mul(self, rhs: T) -> Self::Output {
Self::new(
self.w * rhs.clone(),
self.x * rhs.clone(),
self.y * rhs.clone(),
self.z * rhs,
)
}
}
impl<T> Mul<UnitQuaternion<T>> for Quaternion<T>
where
Quaternion<T>: Mul<Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn mul(self, rhs: UnitQuaternion<T>) -> Self::Output {
self * rhs.0
}
}
impl<T> Div<Quaternion<T>> for Quaternion<T>
where
T: Num + Clone + Neg<Output = T>,
{
type Output = Quaternion<T>;
#[allow(clippy::suspicious_arithmetic_impl)]
#[inline]
fn div(self, rhs: Quaternion<T>) -> Self::Output {
self * rhs.inv()
}
}
impl<T> Div<T> for Quaternion<T>
where
T: Div<T, Output = T> + Clone,
{
type Output = Quaternion<T>;
#[inline]
fn div(self, rhs: T) -> Self::Output {
Self::new(
self.w / rhs.clone(),
self.x / rhs.clone(),
self.y / rhs.clone(),
self.z / rhs,
)
}
}
impl<T> Div<UnitQuaternion<T>> for Quaternion<T>
where
Quaternion<T>: Mul<Output = Quaternion<T>>,
T: Neg<Output = T> + Clone,
{
type Output = Quaternion<T>;
#[allow(clippy::suspicious_arithmetic_impl)]
#[inline]
fn div(self, rhs: UnitQuaternion<T>) -> Self::Output {
self * rhs.0.conj()
}
}
macro_rules! impl_bin_op_assign {
(impl $bin_op_assign_trait:ident, $bin_op_assign:ident, $bin_op_trait:ident, $bin_op:ident) => {
impl<T, S> $bin_op_assign_trait<S> for Quaternion<T>
where
Self: $bin_op_trait<S, Output = Self> + Clone,
{
#[inline]
fn $bin_op_assign(&mut self, other: S) {
*self = self.clone().$bin_op(other);
}
}
};
}
impl_bin_op_assign!(impl AddAssign, add_assign, Add, add);
impl_bin_op_assign!(impl SubAssign, sub_assign, Sub, sub);
impl_bin_op_assign!(impl MulAssign, mul_assign, Mul, mul);
impl_bin_op_assign!(impl DivAssign, div_assign, Div, div);
macro_rules! impl_op_with_ref {
(impl<$T:ident> $bin_op_trait:ident::$bin_op:ident for $lhs_type:ty, $rhs_type:ty) => {
impl<$T> $bin_op_trait<&$rhs_type> for $lhs_type
where
Self: $bin_op_trait<$rhs_type>,
$rhs_type: Clone,
{
type Output = <Self as $bin_op_trait<$rhs_type>>::Output;
fn $bin_op(self, rhs: &$rhs_type) -> Self::Output {
self.$bin_op(rhs.clone())
}
}
impl<$T> $bin_op_trait<&$rhs_type> for &$lhs_type
where
$lhs_type: $bin_op_trait<$rhs_type> + Clone,
$rhs_type: Clone,
{
type Output = <$lhs_type as $bin_op_trait<$rhs_type>>::Output;
fn $bin_op(self, rhs: &$rhs_type) -> Self::Output {
self.clone().$bin_op(rhs.clone())
}
}
impl<$T> $bin_op_trait<$rhs_type> for &$lhs_type
where
$lhs_type: $bin_op_trait<$rhs_type> + Clone,
{
type Output = <$lhs_type as $bin_op_trait<$rhs_type>>::Output;
fn $bin_op(self, rhs: $rhs_type) -> Self::Output {
self.clone().$bin_op(rhs)
}
}
};
}
impl_op_with_ref!(impl<T> Add::add for Quaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Sub::sub for Quaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Mul::mul for Quaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Div::div for Quaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Add::add for Quaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Sub::sub for Quaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Mul::mul for Quaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Div::div for Quaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Add::add for Quaternion<T>, T);
impl_op_with_ref!(impl<T> Sub::sub for Quaternion<T>, T);
impl_op_with_ref!(impl<T> Mul::mul for Quaternion<T>, T);
impl_op_with_ref!(impl<T> Div::div for Quaternion<T>, T);
macro_rules! impl_ops_lhs_real {
($($real:ty),*) => {
$(
impl Add<Quaternion<$real>> for $real {
type Output = Quaternion<$real>;
#[inline]
fn add(self, mut rhs: Quaternion<$real>) -> Self::Output {
rhs.w += self;
rhs
}
}
impl Sub<Quaternion<$real>> for $real {
type Output = Quaternion<$real>;
#[inline]
fn sub(self, rhs: Quaternion<$real>) -> Self::Output {
let zero = <$real>::zero();
Self::Output::new(self - rhs.w, zero - rhs.x, zero - rhs.y, zero - rhs.z)
}
}
impl Mul<Quaternion<$real>> for $real {
type Output = Quaternion<$real>;
#[inline]
fn mul(self, rhs: Quaternion<$real>) -> Self::Output {
Self::Output::new(self * rhs.w, self * rhs.x, self * rhs.y, self * rhs.z)
}
}
)*
};
}
impl_ops_lhs_real!(usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, f32, f64);
impl Div<Q32> for f32 {
type Output = Q32;
#[inline]
fn div(mut self, rhs: Q32) -> Self::Output {
self /= rhs.norm_sqr();
Self::Output::new(self * rhs.w, self * -rhs.x, self * -rhs.y, self * -rhs.z)
}
}
impl Div<Q64> for f64 {
type Output = Q64;
#[inline]
fn div(mut self, rhs: Q64) -> Self::Output {
self /= rhs.norm_sqr();
Self::Output::new(self * rhs.w, self * -rhs.x, self * -rhs.y, self * -rhs.z)
}
}
impl<T> Neg for Quaternion<T>
where
T: Neg<Output = T>,
{
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
Self::new(-self.w, -self.x, -self.y, -self.z)
}
}
impl<T> Quaternion<T>
where
T: Add<T, Output = T> + Mul<T, Output = T>,
{
#[inline]
pub fn dot(self, other: Self) -> T {
self.w * other.w + self.y * other.y + (self.x * other.x + self.z * other.z)
}
}
impl<T> Quaternion<T>
where
T: Num + Clone,
{
pub fn powu(&self, mut n: u32) -> Self {
if n == 0 {
Self::one()
} else {
let mut base = self.clone();
while n & 1 == 0 {
n /= 2;
base = base.clone() * base;
}
if n == 1 {
return base;
}
let mut acc = base.clone();
while n > 1 {
n /= 2;
base = base.clone() * base;
if n & 1 == 1 {
acc *= base.clone();
}
}
acc
}
}
}
impl<T> Quaternion<T>
where
T: Clone + Num + Neg<Output = T>,
{
#[inline]
pub fn powi(&self, n: i32) -> Self {
if n >= 0 {
self.powu(n as u32)
} else {
self.inv().powu(n.wrapping_neg() as u32)
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> Quaternion<T>
where
T: Float + FloatConst,
{
pub fn exp(self) -> Self {
let one = T::one();
let two = one + one;
let four = two + two;
let half = one / two;
let quarter = one / four;
let inf = T::infinity();
let result_norm = self.w.exp();
match result_norm.partial_cmp(&inf) {
Some(Ordering::Less) => {
if result_norm.is_zero() {
return Self::zero();
}
let sqr_angle = self.x * self.x + self.y * self.y + self.z * self.z;
if sqr_angle <= T::epsilon() {
let w = result_norm;
let x = result_norm * self.x;
let y = result_norm * self.y;
let z = result_norm * self.z;
Self::new(w, x, y, z)
} else {
let angle = sqr_angle.sqrt();
let cos_angle = angle.cos();
let sinc_angle = angle.sin() / angle;
let w = result_norm * cos_angle;
let x = result_norm * self.x * sinc_angle;
let y = result_norm * self.y * sinc_angle;
let z = result_norm * self.z * sinc_angle;
Self::new(w, x, y, z)
}
}
Some(_) => {
let map = |a: T| {
if a.is_zero() {
a
} else {
inf.copysign(a)
}
};
let sqr_angle = self.x * self.x + self.y * self.y + self.z * self.z;
if sqr_angle < T::PI() * T::PI() * quarter {
Self::new(inf, map(self.x), map(self.y), map(self.z))
} else if sqr_angle.is_finite() {
let angle = sqr_angle.sqrt();
let angle_revolutions_fract = (angle * T::FRAC_1_PI() * half).fract();
let cos_angle_signum = (angle_revolutions_fract - half).abs() - quarter;
let sin_angle_signum = one.copysign(half - angle_revolutions_fract);
Self::new(
inf.copysign(cos_angle_signum),
map(self.x) * sin_angle_signum,
map(self.y) * sin_angle_signum,
map(self.z) * sin_angle_signum,
)
} else {
assert!(sqr_angle.is_infinite() || sqr_angle.is_nan());
Self::nan()
}
}
None => {
debug_assert!(result_norm.is_nan());
Self::nan()
}
}
}
fn is_finite(&self) -> bool {
self.w.is_finite() && self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
}
pub fn ln(self) -> Self {
let sqr_norm_im = self.x * self.x + self.y * self.y + self.z * self.z;
let norm_sqr = self.w * self.w + sqr_norm_im;
match norm_sqr.classify() {
FpCategory::Normal => {
let w = norm_sqr.ln() * T::from(0.5).expect("Conversion failed");
if sqr_norm_im <= self.w * self.w * T::epsilon() {
if self.w.is_sign_positive() {
let x = self.x / self.w;
let y = self.y / self.w;
let z = self.z / self.w;
Self::new(w, x, y, z)
} else if self.x.is_zero() && self.y.is_zero() && self.z.is_zero() {
Self::new(w, T::PI().copysign(self.x), self.y, self.z)
} else {
let norm_im = if sqr_norm_im.is_normal() {
sqr_norm_im.sqrt()
} else {
let f = T::min_positive_value().sqrt();
let xf = self.x / f;
let yf = self.y / f;
let zf = self.z / f;
let sqr_sum = xf * xf + yf * yf + zf * zf;
sqr_sum.sqrt() * f
};
let f = T::PI() / norm_im + self.w.recip();
Self::new(w, f * self.x, f * self.y, f * self.z)
}
} else {
let norm_im = if sqr_norm_im.is_normal() {
sqr_norm_im.sqrt()
} else {
let f = T::min_positive_value().sqrt();
let xf = self.x / f;
let yf = self.y / f;
let zf = self.z / f;
let sqr_sum = xf * xf + yf * yf + zf * zf;
sqr_sum.sqrt() * f
};
let angle = norm_im.atan2(self.w);
let x = self.x * angle / norm_im;
let y = self.y * angle / norm_im;
let z = self.z * angle / norm_im;
Self::new(w, x, y, z)
}
}
FpCategory::Zero if self.is_zero() => {
let x = if self.w.is_sign_positive() {
self.x
} else {
T::PI().copysign(self.x)
};
Self::new(T::neg_infinity(), x, self.y, self.z)
}
FpCategory::Nan => Self::nan(),
FpCategory::Infinite => {
if self.is_finite() {
let factor = T::one() / T::max_value().sqrt();
(self * factor).ln() - factor.ln()
} else {
let f = |r: T| {
if r.is_infinite() {
r.signum()
} else {
T::zero().copysign(r)
}
};
let q = Self::new(f(self.w), f(self.x), f(self.y), f(self.z));
q.ln() + T::infinity()
}
}
_ => {
let factor = T::one() / T::min_positive_value();
(self * factor).ln() - factor.ln()
}
}
}
pub fn sqrt(self) -> Self {
let zero = T::zero();
let one = T::one();
let two = one + one;
let half = one / two;
let inf = T::infinity();
let s = one / T::min_positive_value(); let norm_sqr = self.norm_sqr();
match norm_sqr.classify() {
FpCategory::Normal => {
let norm = norm_sqr.sqrt();
if self.w.is_sign_positive() {
let wx2 = ((self.w + norm) * two).sqrt();
Self::new(wx2 * half, self.x / wx2, self.y / wx2, self.z / wx2)
} else {
let im_norm_sqr = self.y * self.y + (self.x * self.x + self.z * self.z);
if im_norm_sqr >= T::min_positive_value() {
let wx2 = (im_norm_sqr * two / (norm - self.w)).sqrt();
Self::new(wx2 * half, self.x / wx2, self.y / wx2, self.z / wx2)
} else if self.x.is_zero() && self.y.is_zero() && self.z.is_zero() {
Self::new(zero, (-self.w).sqrt().copysign(self.x), self.y, self.z)
} else {
let sx = s * self.x;
let sy = s * self.y;
let sz = s * self.z;
let im_norm = (sy * sy + (sx * sx + sz * sz)).sqrt() / s;
let w = im_norm / (half * (norm - self.w)).sqrt();
Self::new(w * half, self.x / w, self.y / w, self.z / w)
}
}
}
FpCategory::Zero if self.is_zero() => Self::new(zero, self.x, self.y, self.z),
FpCategory::Infinite => {
if self.w == inf
|| self.x.is_infinite()
|| self.y.is_infinite()
|| self.z.is_infinite()
{
let f = |a: T| if a.is_infinite() { a } else { zero.copysign(a) };
Self::new(inf, f(self.x), f(self.y), f(self.z))
} else if self.w == -inf {
Self::new(
zero,
inf.copysign(self.x),
zero.copysign(self.y),
zero.copysign(self.z),
)
} else {
(self / s).sqrt() * s.sqrt()
}
}
FpCategory::Nan => Self::nan(),
_ => {
(self * s).sqrt() / s.sqrt()
}
}
}
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for Quaternion<T>
where
T: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
(&self.w, &self.x, &self.y, &self.z).serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T> serde::Deserialize<'de> for Quaternion<T>
where
T: serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let (w, x, y, z) = serde::Deserialize::deserialize(deserializer)?;
Ok(Self::new(w, x, y, z))
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct UnitQuaternion<T>(Quaternion<T>);
pub type UQ32 = UnitQuaternion<f32>;
pub type UQ64 = UnitQuaternion<f64>;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct EulerAngles<T> {
pub roll: T,
pub pitch: T,
pub yaw: T,
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for EulerAngles<T>
where
T: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
(&self.roll, &self.pitch, &self.yaw).serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T> serde::Deserialize<'de> for EulerAngles<T>
where
T: serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let (roll, pitch, yaw) = serde::Deserialize::deserialize(deserializer)?;
Ok(Self { roll, pitch, yaw })
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
T: Float,
{
pub fn from_euler_angles(roll: T, pitch: T, yaw: T) -> Self {
let half = T::one() / (T::one() + T::one());
let (sr, cr) = (roll * half).sin_cos();
let (sp, cp) = (pitch * half).sin_cos();
let (sy, cy) = (yaw * half).sin_cos();
Self(Quaternion::new(
cr * cp * cy + sr * sp * sy,
sr * cp * cy - cr * sp * sy,
cr * sp * cy + sr * cp * sy,
cr * cp * sy - sr * sp * cy,
))
}
#[cfg(feature = "unstable")]
pub fn from_euler_angles_struct(angles: EulerAngles<T>) -> Self {
let EulerAngles { roll, pitch, yaw } = angles;
Self::from_euler_angles(roll, pitch, yaw)
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
T: Float + FloatConst,
{
pub fn to_euler_angles(&self) -> EulerAngles<T> {
let &Self(Quaternion { w, x, y, z }) = self;
let one = T::one();
let two = one + one;
let epsilon = T::epsilon();
let half_pi = T::FRAC_PI_2();
let sin_pitch = two * (w * y - z * x);
if sin_pitch.abs() >= one - epsilon {
let pitch = if sin_pitch >= one - epsilon {
half_pi } else {
-half_pi };
let roll = T::zero();
let yaw = T::atan2(two * (x * y + w * z), one - two * (y * y + z * z));
EulerAngles { roll, pitch, yaw }
} else {
let pitch = sin_pitch.asin();
let roll = T::atan2(two * (w * x + y * z), one - two * (x * x + y * y));
let yaw = T::atan2(two * (w * z + x * y), one - two * (y * y + z * z));
EulerAngles { roll, pitch, yaw }
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
T: Float,
{
pub fn from_rotation_vector(v: &[T; 3]) -> Self {
let sqr_norm = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
let two = T::one() + T::one();
match sqr_norm.classify() {
FpCategory::Normal => {
let norm = sqr_norm.sqrt();
let (sine, cosine) = (norm / two).sin_cos();
let f = sine / norm;
Self(Quaternion::new(cosine, v[0] * f, v[1] * f, v[2] * f))
}
FpCategory::Zero | FpCategory::Subnormal => Self(Quaternion::new(
T::one(),
v[0] / two,
v[1] / two,
v[2] / two,
)),
FpCategory::Nan | FpCategory::Infinite => Self(Quaternion::nan()),
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
T: Float + FloatConst,
{
pub fn to_rotation_vector(&self) -> [T; 3] {
let q = self.as_quaternion();
let one = T::one();
let two = one + one;
let epsilon = T::epsilon();
let small_abs_real_part = q.w.abs() < T::from(0.9).unwrap();
let sin_half_angle = if small_abs_real_part {
(one - q.w * q.w).sqrt()
} else {
(q.x * q.x + q.y * q.y + q.z * q.z).sqrt()
};
let angle = two
* if small_abs_real_part {
q.w.acos()
} else if q.w.is_sign_positive() {
if sin_half_angle < epsilon.sqrt() {
return [two * q.x, two * q.y, two * q.z];
}
sin_half_angle.asin()
} else {
if sin_half_angle.is_zero() {
return [two * T::PI(), T::zero(), T::zero()];
}
let pi_minus_half_angle = sin_half_angle.asin();
T::PI() - pi_minus_half_angle
};
let x = q.x / sin_half_angle;
let y = q.y / sin_half_angle;
let z = q.z / sin_half_angle;
[x * angle, y * angle, z * angle]
}
}
impl<T> UnitQuaternion<T>
where
T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + One + Clone,
{
#[inline]
pub fn to_rotation_matrix3x3(self) -> [T; 9] {
let two = T::one() + T::one();
let Self(Quaternion { w, x, y, z }) = self;
let wx = two.clone() * w.clone() * x.clone();
let wy = two.clone() * w.clone() * y.clone();
let wz = two.clone() * w * z.clone();
let xx = two.clone() * x.clone() * x.clone();
let xy = two.clone() * x.clone() * y.clone();
let xz = two.clone() * x * z.clone();
let yy = two.clone() * y.clone() * y.clone();
let yz = two.clone() * y * z.clone();
let zz = two * z.clone() * z;
[
T::one() - yy.clone() - zz.clone(),
xy.clone() + wz.clone(),
xz.clone() - wy.clone(),
xy - wz,
T::one() - xx.clone() - zz,
yz.clone() + wx.clone(),
xz + wy,
yz - wx,
T::one() - xx - yy,
]
}
}
pub trait ReadMat3x3<T> {
fn at(&self, row: usize, col: usize) -> &T;
}
impl<T> ReadMat3x3<T> for [T; 9] {
fn at(&self, row: usize, col: usize) -> &T {
&self[col + 3 * row]
}
}
impl<T> ReadMat3x3<T> for [[T; 3]; 3] {
fn at(&self, row: usize, col: usize) -> &T {
&self[row][col]
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
T: Float,
{
pub fn from_rotation_matrix3x3(mat: &impl ReadMat3x3<T>) -> UnitQuaternion<T> {
let zero = T::zero();
let one = T::one();
let two = one + one;
let quarter = one / (two * two);
let m00 = mat.at(0, 0);
let m11 = mat.at(1, 1);
let m22 = mat.at(2, 2);
let trace: T = *m00 + *m11 + *m22;
if trace > zero {
let s = (trace + one).sqrt() * two; let qw = quarter * s;
let qx = (*mat.at(1, 2) - *mat.at(2, 1)) / s;
let qy = (*mat.at(2, 0) - *mat.at(0, 2)) / s;
let qz = (*mat.at(0, 1) - *mat.at(1, 0)) / s;
Self(Quaternion::new(qw, qx, qy, qz))
} else if (m00 > m11) && (m00 > m22) {
let s = (one + *m00 - *m11 - *m22).sqrt() * two; let qw = (*mat.at(1, 2) - *mat.at(2, 1)) / s;
let qx = quarter * s;
let qy = (*mat.at(1, 0) + *mat.at(0, 1)) / s;
let qz = (*mat.at(2, 0) + *mat.at(0, 2)) / s;
if *mat.at(1, 2) >= *mat.at(2, 1) {
Self(Quaternion::new(qw, qx, qy, qz))
} else {
Self(Quaternion::new(-qw, -qx, -qy, -qz))
}
} else if m11 > m22 {
let s = (one + *m11 - *m00 - *m22).sqrt() * two; let qw = (*mat.at(2, 0) - *mat.at(0, 2)) / s;
let qx = (*mat.at(1, 0) + *mat.at(0, 1)) / s;
let qy = quarter * s;
let qz = (*mat.at(2, 1) + *mat.at(1, 2)) / s;
if *mat.at(2, 0) >= *mat.at(0, 2) {
Self(Quaternion::new(qw, qx, qy, qz))
} else {
Self(Quaternion::new(-qw, -qx, -qy, -qz))
}
} else {
let s = (one + *m22 - *m00 - *m11).sqrt() * two; let qw = (*mat.at(0, 1) - *mat.at(1, 0)) / s;
let qx = (*mat.at(2, 0) + *mat.at(0, 2)) / s;
let qy = (*mat.at(2, 1) + *mat.at(1, 2)) / s;
let qz = quarter * s;
if *mat.at(0, 1) >= *mat.at(1, 0) {
Self(Quaternion::new(qw, qx, qy, qz))
} else {
Self(Quaternion::new(-qw, -qx, -qy, -qz))
}
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
T: Float,
{
fn make_perpendicular_unit_vector(a: &[T; 3]) -> [T; 3] {
let zero = T::zero();
let a_sqr = [a[0] * a[0], a[1] * a[1], a[2] * a[2]];
if a_sqr[0] <= a_sqr[1] {
if a_sqr[0] <= a_sqr[2] {
let norm = (a_sqr[1] + a_sqr[2]).sqrt();
[zero, a[2] / norm, -a[1] / norm]
} else {
let norm = (a_sqr[0] + a_sqr[1]).sqrt();
[a[1] / norm, -a[0] / norm, zero]
}
} else if a_sqr[1] <= a_sqr[2] {
let norm = (a_sqr[0] + a_sqr[2]).sqrt();
[a[2] / norm, zero, -a[0] / norm]
} else {
let norm = (a_sqr[0] + a_sqr[1]).sqrt();
[a[1] / norm, -a[0] / norm, zero]
}
}
pub fn from_two_vectors(a: &[T; 3], b: &[T; 3]) -> UnitQuaternion<T> {
let zero = T::zero();
let one = T::one();
let two = one + one;
let half = one / two;
let a_norm = a[0].hypot(a[1].hypot(a[2]));
let b_norm = b[0].hypot(b[1].hypot(b[2]));
if a_norm.is_zero() || b_norm.is_zero() {
return UnitQuaternion::one();
}
let a = [a[0] / a_norm, a[1] / a_norm, a[2] / a_norm];
let b = [b[0] / b_norm, b[1] / b_norm, b[2] / b_norm];
let v = [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
];
let d = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
let wx2 = if d >= -half {
(two * d + two).sqrt()
} else {
let v_norm_sqr = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
if v_norm_sqr.is_zero() {
let [x, y, z] = Self::make_perpendicular_unit_vector(&a);
return UnitQuaternion(Quaternion::new(zero, x, y, z));
}
(v_norm_sqr / (half - d * half)).sqrt()
};
UnitQuaternion(Quaternion::new(
wx2 / two,
v[0] / wx2,
v[1] / wx2,
v[2] / wx2,
))
}
}
impl<T> Default for UnitQuaternion<T>
where
T: Num + Clone,
{
#[inline]
fn default() -> Self {
Self::one()
}
}
impl<T> UnitQuaternion<T>
where
T: ConstZero + ConstOne,
{
pub const ONE: Self = Self(Quaternion::ONE);
pub const I: Self = Self(Quaternion::I);
pub const J: Self = Self(Quaternion::J);
pub const K: Self = Self(Quaternion::K);
}
impl<T> ConstOne for UnitQuaternion<T>
where
T: ConstZero + ConstOne + Num + Clone,
{
const ONE: Self = Self::ONE;
}
impl<T> One for UnitQuaternion<T>
where
T: Num + Clone,
{
#[inline]
fn one() -> Self {
Self(Quaternion::one())
}
#[inline]
fn is_one(&self) -> bool {
self.0.is_one()
}
#[inline]
fn set_one(&mut self) {
self.0.set_one();
}
}
impl<T> UnitQuaternion<T>
where
T: Zero + One,
{
#[inline]
pub fn i() -> Self {
Self(Quaternion::i())
}
#[inline]
pub fn j() -> Self {
Self(Quaternion::j())
}
#[inline]
pub fn k() -> Self {
Self(Quaternion::k())
}
}
impl<T> UnitQuaternion<T> {
#[inline]
pub fn into_quaternion(self) -> Quaternion<T> {
self.0
}
#[inline]
pub fn as_quaternion(&self) -> &Quaternion<T> {
&self.0
}
}
impl<T> Borrow<Quaternion<T>> for UnitQuaternion<T> {
fn borrow(&self) -> &Quaternion<T> {
self.as_quaternion()
}
}
impl<T> UnitQuaternion<T>
where
T: Clone + Neg<Output = T>,
{
#[inline]
pub fn conj(&self) -> Self {
Self(self.0.conj())
}
}
impl<T> UnitQuaternion<T>
where
T: Clone + Neg<Output = T>,
{
#[inline]
pub fn inv(&self) -> Self {
self.conj()
}
}
impl<T> Inv for &UnitQuaternion<T>
where
T: Clone + Neg<Output = T>,
{
type Output = UnitQuaternion<T>;
#[inline]
fn inv(self) -> Self::Output {
self.conj()
}
}
impl<T> Inv for UnitQuaternion<T>
where
T: Clone + Neg<Output = T>,
{
type Output = UnitQuaternion<T>;
#[inline]
fn inv(self) -> Self::Output {
self.conj()
}
}
impl<T> Add<UnitQuaternion<T>> for UnitQuaternion<T>
where
Quaternion<T>: Add<Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn add(self, rhs: UnitQuaternion<T>) -> Self::Output {
self.0 + rhs.0
}
}
impl<T> Add<Quaternion<T>> for UnitQuaternion<T>
where
Quaternion<T>: Add<Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn add(self, rhs: Quaternion<T>) -> Self::Output {
self.0 + rhs
}
}
impl<T> Add<T> for UnitQuaternion<T>
where
Quaternion<T>: Add<T, Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn add(self, rhs: T) -> Self::Output {
self.0 + rhs
}
}
impl Add<UQ32> for f32 {
type Output = Q32;
#[inline]
fn add(self, rhs: UQ32) -> Self::Output {
self + rhs.0
}
}
impl Add<UQ64> for f64 {
type Output = Q64;
#[inline]
fn add(self, rhs: UQ64) -> Self::Output {
self + rhs.0
}
}
impl<T> Sub<UnitQuaternion<T>> for UnitQuaternion<T>
where
Quaternion<T>: Sub<Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn sub(self, rhs: UnitQuaternion<T>) -> Self::Output {
self.0 - rhs.0
}
}
impl<T> Sub<Quaternion<T>> for UnitQuaternion<T>
where
Quaternion<T>: Sub<Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn sub(self, rhs: Quaternion<T>) -> Self::Output {
self.0 - rhs
}
}
impl<T> Sub<T> for UnitQuaternion<T>
where
Quaternion<T>: Sub<T, Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn sub(self, rhs: T) -> Self::Output {
self.0 - rhs
}
}
impl Sub<UQ32> for f32 {
type Output = Q32;
#[inline]
fn sub(self, rhs: UQ32) -> Self::Output {
self - rhs.0
}
}
impl Sub<UQ64> for f64 {
type Output = Q64;
#[inline]
fn sub(self, rhs: UQ64) -> Self::Output {
self - rhs.0
}
}
impl<T> Mul<UnitQuaternion<T>> for UnitQuaternion<T>
where
Quaternion<T>: Mul<Output = Quaternion<T>>,
{
type Output = UnitQuaternion<T>;
#[inline]
fn mul(self, rhs: UnitQuaternion<T>) -> Self::Output {
Self(self.0 * rhs.0)
}
}
impl<T> Mul<Quaternion<T>> for UnitQuaternion<T>
where
Quaternion<T>: Mul<Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn mul(self, rhs: Quaternion<T>) -> Self::Output {
self.0 * rhs
}
}
impl<T> Mul<T> for UnitQuaternion<T>
where
Quaternion<T>: Mul<T, Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn mul(self, rhs: T) -> Self::Output {
self.0 * rhs
}
}
impl Mul<UQ32> for f32 {
type Output = Q32;
#[inline]
fn mul(self, rhs: UQ32) -> Self::Output {
self * rhs.0
}
}
impl Mul<UQ64> for f64 {
type Output = Q64;
#[inline]
fn mul(self, rhs: UQ64) -> Self::Output {
self * rhs.0
}
}
impl<T> Div<UnitQuaternion<T>> for UnitQuaternion<T>
where
Quaternion<T>: Mul<Output = Quaternion<T>>,
T: Clone + Neg<Output = T>,
{
type Output = UnitQuaternion<T>;
#[allow(clippy::suspicious_arithmetic_impl)]
#[inline]
fn div(self, rhs: UnitQuaternion<T>) -> Self::Output {
self * rhs.conj()
}
}
impl<T> Div<Quaternion<T>> for UnitQuaternion<T>
where
Quaternion<T>: Div<Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn div(self, rhs: Quaternion<T>) -> Self::Output {
self.0 / rhs
}
}
impl<T> Div<T> for UnitQuaternion<T>
where
Quaternion<T>: Div<T, Output = Quaternion<T>>,
{
type Output = Quaternion<T>;
#[inline]
fn div(self, rhs: T) -> Self::Output {
self.0 / rhs
}
}
impl Div<UQ32> for f32 {
type Output = Q32;
#[allow(clippy::suspicious_arithmetic_impl)]
#[inline]
fn div(self, rhs: UQ32) -> Self::Output {
self * rhs.inv().0
}
}
impl Div<UQ64> for f64 {
type Output = Q64;
#[allow(clippy::suspicious_arithmetic_impl)]
#[inline]
fn div(self, rhs: UQ64) -> Self::Output {
self * rhs.inv().0
}
}
impl_op_with_ref!(impl<T> Add::add for UnitQuaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Sub::sub for UnitQuaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Mul::mul for UnitQuaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Div::div for UnitQuaternion<T>, UnitQuaternion<T>);
impl_op_with_ref!(impl<T> Add::add for UnitQuaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Sub::sub for UnitQuaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Mul::mul for UnitQuaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Div::div for UnitQuaternion<T>, Quaternion<T>);
impl_op_with_ref!(impl<T> Add::add for UnitQuaternion<T>, T);
impl_op_with_ref!(impl<T> Sub::sub for UnitQuaternion<T>, T);
impl_op_with_ref!(impl<T> Mul::mul for UnitQuaternion<T>, T);
impl_op_with_ref!(impl<T> Div::div for UnitQuaternion<T>, T);
impl<T> Neg for UnitQuaternion<T>
where
T: Neg<Output = T>,
{
type Output = UnitQuaternion<T>;
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
impl<T> UnitQuaternion<T>
where
T: Add<T, Output = T> + Mul<T, Output = T>,
{
#[inline]
pub fn dot(self, other: Self) -> T {
self.0.dot(other.0)
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
T: Float,
{
#[inline]
pub fn adjust_norm(self) -> Self {
self.0
.normalize()
.expect("Unit quaternion value too inaccurate. Cannot renormalize.")
}
}
impl<T> UnitQuaternion<T>
where
T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Clone,
{
pub fn rotate_vector(self, vector: [T; 3]) -> [T; 3] {
let q = self.into_quaternion();
let [vx, vy, vz] = vector;
let q_inv_v = Quaternion::<T>::new(
q.x.clone() * vx.clone() + q.y.clone() * vy.clone() + q.z.clone() * vz.clone(),
q.w.clone() * vx.clone() - q.y.clone() * vz.clone() + q.z.clone() * vy.clone(),
q.w.clone() * vy.clone() + q.x.clone() * vz.clone() - q.z.clone() * vx.clone(),
q.w.clone() * vz - q.x.clone() * vy + q.y.clone() * vx,
);
let result = q_inv_v * q;
[result.x, result.y, result.z]
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
T: Float,
{
pub fn slerp(&self, other: &Self, t: T) -> Self {
let one = T::one();
let dot = self.dot(*other);
let (dot, other) = if dot.is_sign_positive() {
(dot, *other)
} else {
(-dot, -*other)
};
let threshold = one - T::epsilon().sqrt();
if dot > threshold {
return Self(*self + (other - *self) * t);
}
let theta_0 = dot.acos();
let theta = theta_0 * t;
let sin_theta = theta.sin();
let sin_theta_0 = theta_0.sin();
let s0 = ((one - t) * theta_0).sin() / sin_theta_0;
let s1 = sin_theta / sin_theta_0;
Self(*self * s0 + other * s1)
}
}
#[cfg(any(feature = "std", feature = "libm"))]
impl<T> UnitQuaternion<T>
where
T: Float + FloatConst,
{
pub fn sqrt(self) -> Self {
let zero = T::zero();
let one = T::one();
let two = one + one;
let half = one / two;
let UnitQuaternion(c) = self;
if c.w >= -half {
let wx2 = (c.w * two + two).sqrt();
UnitQuaternion(Quaternion::new(wx2 * half, c.x / wx2, c.y / wx2, c.z / wx2))
} else {
let im_norm_sqr = c.y * c.y + (c.x * c.x + c.z * c.z);
if im_norm_sqr >= T::min_positive_value() {
let wx2 = (im_norm_sqr * two / (one - c.w)).sqrt();
UnitQuaternion(Quaternion::new(wx2 * half, c.x / wx2, c.y / wx2, c.z / wx2))
} else if c.x.is_zero() && c.y.is_zero() && c.z.is_zero() {
UnitQuaternion(Quaternion::new(zero, one.copysign(c.x), c.y, c.z))
} else {
let s = one / T::min_positive_value();
let sx = s * c.x;
let sy = s * c.y;
let sz = s * c.z;
let im_norm = (sy * sy + (sx * sx + sz * sz)).sqrt() / s;
UnitQuaternion(Quaternion::new(
im_norm * half,
c.x / im_norm,
c.y / im_norm,
c.z / im_norm,
))
}
}
}
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for UnitQuaternion<T>
where
T: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T> serde::Deserialize<'de> for UnitQuaternion<T>
where
T: serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let q = serde::Deserialize::deserialize(deserializer)?;
Ok(Self(q))
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "std")]
use core::hash::Hash;
#[cfg(feature = "std")]
use core::hash::Hasher;
#[cfg(feature = "std")]
use std::collections::hash_map::DefaultHasher;
use num_traits::ConstOne;
use num_traits::ConstZero;
use num_traits::FloatConst;
use num_traits::Inv;
use num_traits::One;
use num_traits::Zero;
#[cfg(any(feature = "std", feature = "libm", feature = "serde"))]
use crate::EulerAngles;
use crate::Quaternion;
use crate::UnitQuaternion;
use crate::Q32;
use crate::Q64;
use crate::UQ32;
use crate::UQ64;
#[test]
fn test_new() {
let q = Quaternion::new(1.0, 2.0, 3.0, 4.0);
assert_eq!(q.w, 1.0);
assert_eq!(q.x, 2.0);
assert_eq!(q.y, 3.0);
assert_eq!(q.z, 4.0);
}
#[test]
fn test_zero_constant() {
let q1 = Q32::ZERO;
let q2 = Q32::default();
assert_eq!(q1, q2);
}
#[test]
fn test_const_zero_trait() {
let q3 = <Q64 as ConstZero>::ZERO;
let q4 = Q64::default();
assert_eq!(q3, q4);
}
#[test]
fn test_zero_trait() {
let q1 = Q32::zero();
let q2 = Q32::default();
assert_eq!(q1, q2);
assert!(q1.is_zero());
}
#[test]
fn test_zero_trait_set_zero() {
let mut q = Quaternion::new(1.0, 2.0, 3.0, 4.0);
q.set_zero();
assert!(q.is_zero());
}
#[test]
fn test_one_constant() {
assert_eq!(Q32::ONE, Q32::new(1.0, 0.0, 0.0, 0.0))
}
#[test]
fn test_i_constant() {
assert_eq!(Q64::I, Q64::new(0.0, 1.0, 0.0, 0.0))
}
#[test]
fn test_j_constant() {
assert_eq!(Q32::J, Q32::new(0.0, 0.0, 1.0, 0.0))
}
#[test]
fn test_k_constant() {
assert_eq!(Q64::K, Q64::new(0.0, 0.0, 0.0, 1.0))
}
#[test]
fn test_const_one_trait() {
assert_eq!(<Q32 as ConstOne>::ONE, Q32::new(1.0, 0.0, 0.0, 0.0))
}
#[test]
fn test_one_trait_one() {
assert_eq!(<Q64 as One>::one(), Q64::ONE);
assert!(Q64::ONE.is_one());
}
#[test]
fn test_one_trait_set_one() {
let mut q = Q32::new(2.0, 3.0, 4.0, 5.0);
q.set_one();
assert!(q.is_one());
}
#[test]
fn test_i_func() {
assert_eq!(Q64::i(), Q64::new(0.0, 1.0, 0.0, 0.0));
}
#[test]
fn test_j_func() {
assert_eq!(Q64::j(), Q64::new(0.0, 0.0, 1.0, 0.0));
}
#[test]
fn test_k_func() {
assert_eq!(Q64::k(), Q64::new(0.0, 0.0, 0.0, 1.0));
}
#[test]
fn test_norm_sqr() {
assert_eq!(Q32::ZERO.norm_sqr(), 0.0);
assert_eq!(Q64::ONE.norm_sqr(), 1.0);
assert_eq!(Q32::I.norm_sqr(), 1.0);
assert_eq!(Q64::J.norm_sqr(), 1.0);
assert_eq!(Q32::K.norm_sqr(), 1.0);
assert_eq!(Q64::new(-8.0, 4.0, 2.0, -1.0).norm_sqr(), 85.0);
assert_eq!(Q32::new(1.0, -2.0, 3.0, -4.0).norm_sqr(), 30.0);
}
#[test]
fn test_conj() {
assert_eq!(Q64::ONE.conj(), Q64::ONE);
assert_eq!(Q32::I.conj(), -Q32::I);
assert_eq!(Q64::J.conj(), -Q64::J);
assert_eq!(Q32::K.conj(), -Q32::K);
assert_eq!(
Q64::new(-8.0, 4.0, 2.0, -1.0).conj(),
Q64::new(-8.0, -4.0, -2.0, 1.0)
);
assert_eq!(
Q32::new(1.0, -2.0, 3.0, -4.0).conj(),
Q32::new(1.0, 2.0, -3.0, 4.0)
);
}
#[test]
fn test_inv_func() {
assert_eq!(Q64::ONE.inv(), Q64::ONE);
assert_eq!(Q32::I.inv(), -Q32::I);
assert_eq!(Q64::J.inv(), -Q64::J);
assert_eq!(Q32::K.inv(), -Q32::K);
assert_eq!(
Q64::new(1.0, 1.0, -1.0, -1.0).inv(),
Q64::new(0.25, -0.25, 0.25, 0.25)
);
assert_eq!(
Q32::new(1.0, -2.0, 2.0, -4.0).inv(),
Q32::new(0.04, 0.08, -0.08, 0.16)
);
}
#[test]
fn test_inv_trait_for_ref() {
assert_eq!(Inv::inv(&Q64::ONE), Q64::ONE);
assert_eq!(Inv::inv(&Q32::I), -Q32::I);
assert_eq!(Inv::inv(&Q64::J), -Q64::J);
assert_eq!(Inv::inv(&Q32::K), -Q32::K);
assert_eq!(
Inv::inv(&Q64::new(1.0, 1.0, -1.0, -1.0)),
Q64::new(0.25, -0.25, 0.25, 0.25)
);
assert_eq!(
Inv::inv(&Q32::new(1.0, -2.0, 2.0, -4.0)),
Q32::new(0.04, 0.08, -0.08, 0.16)
);
}
#[test]
fn test_inv_trait_for_val() {
assert_eq!(Inv::inv(Q64::ONE), Q64::ONE);
assert_eq!(Inv::inv(Q32::I), -Q32::I);
assert_eq!(Inv::inv(Q64::J), -Q64::J);
assert_eq!(Inv::inv(Q32::K), -Q32::K);
assert_eq!(
Inv::inv(Q64::new(1.0, 1.0, -1.0, -1.0)),
Q64::new(0.25, -0.25, 0.25, 0.25)
);
assert_eq!(
Inv::inv(Q32::new(1.0, -2.0, 2.0, -4.0)),
Q32::new(0.04, 0.08, -0.08, 0.16)
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_norm_normal_values() {
let q = Q64::new(1.0, 2.0, 3.0, 4.0);
assert_eq!(q.norm(), 30.0f64.sqrt());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_norm_zero_quaternion() {
let q = Q32::new(0.0, 0.0, 0.0, 0.0);
assert_eq!(q.norm(), 0.0, "Norm of zero quaternion should be 0");
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_norm_subnormal_values() {
let s = f64::MIN_POSITIVE;
let q = Q64::new(s, s, s, s);
assert!(
(q.norm() - 2.0 * s).abs() <= 2.0 * s * f64::EPSILON,
"Norm of subnormal is computed correctly"
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_norm_infinite_values() {
let inf = f32::INFINITY;
assert_eq!(Q32::new(inf, 1.0, 1.0, 1.0).norm(), inf);
assert_eq!(Q32::new(1.0, inf, 1.0, 1.0).norm(), inf);
assert_eq!(Q32::new(1.0, 1.0, inf, 1.0).norm(), inf);
assert_eq!(Q32::new(1.0, 1.0, 1.0, inf).norm(), inf);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_norm_nan_values() {
let nan = f32::NAN;
assert!(Q32::new(nan, 1.0, 1.0, 1.0).norm().is_nan());
assert!(Q32::new(1.0, nan, 1.0, 1.0).norm().is_nan());
assert!(Q32::new(1.0, 1.0, nan, 1.0).norm().is_nan());
assert!(Q32::new(1.0, 1.0, 1.0, nan).norm().is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_fast_norm_normal_values() {
let q = Q64 {
w: 1.1,
x: 2.7,
y: 3.4,
z: 4.9,
};
assert_eq!(
q.fast_norm(),
q.norm(),
"Fast norm is equal to norm for normal floating point values"
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_fast_norm_zero_quaternion() {
assert_eq!(
Q32::zero().fast_norm(),
0.0,
"Fast norm of zero quaternion should be 0"
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_fast_norm_infinite_values() {
let inf = f32::INFINITY;
assert_eq!(Q32::new(inf, 1.0, 1.0, 1.0).fast_norm(), inf);
assert_eq!(Q32::new(1.0, inf, 1.0, 1.0).fast_norm(), inf);
assert_eq!(Q32::new(1.0, 1.0, inf, 1.0).fast_norm(), inf);
assert_eq!(Q32::new(1.0, 1.0, 1.0, inf).fast_norm(), inf);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_fast_norm_nan_values() {
let nan = f32::NAN;
assert!(Q32::new(nan, 1.0, 1.0, 1.0).fast_norm().is_nan());
assert!(Q32::new(1.0, nan, 1.0, 1.0).fast_norm().is_nan());
assert!(Q32::new(1.0, 1.0, nan, 1.0).fast_norm().is_nan());
assert!(Q32::new(1.0, 1.0, 1.0, nan).fast_norm().is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_fast_norm_for_norm_sqr_underflow() {
let s = f64::MIN_POSITIVE;
let q = Q64::new(s, s, s, s);
assert_eq!(q.fast_norm(), 0.0);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_fast_norm_for_norm_sqr_overflow() {
let s = f32::MAX / 16.0;
let q = Q32::new(s, s, s, s);
assert_eq!(q.fast_norm(), f32::INFINITY);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_normalize() {
assert_eq!(Q64::ONE.normalize().unwrap(), UQ64::ONE);
assert_eq!(Q32::I.normalize().unwrap(), UQ32::I);
assert_eq!(Q64::J.normalize().unwrap(), UQ64::J);
assert_eq!(Q32::K.normalize().unwrap(), UQ32::K);
assert_eq!(
Q64::new(9.0, 12.0, -12.0, -16.0)
.normalize()
.unwrap()
.into_quaternion(),
Q64::new(0.36, 0.48, -0.48, -0.64)
);
assert_eq!(
Q32::new(-1.0, -1.0, 1.0, -1.0)
.normalize()
.unwrap()
.into_quaternion(),
Q32::new(-0.5, -0.5, 0.5, -0.5)
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_normalize_zero_infinity_nan() {
assert_eq!(Q64::ZERO.normalize(), None);
assert_eq!(Q64::new(f64::INFINITY, 0.0, 0.0, 0.0).normalize(), None);
assert_eq!(Q64::new(0.0, f64::NEG_INFINITY, 0.0, 0.0).normalize(), None);
assert_eq!(Q64::new(0.0, 0.0, f64::NEG_INFINITY, 0.0).normalize(), None);
assert_eq!(Q64::new(0.0, 0.0, 0.0, f64::INFINITY).normalize(), None);
assert_eq!(Q64::new(f64::NAN, 0.0, 0.0, 0.0).normalize(), None);
assert_eq!(Q64::new(0.0, f64::NAN, 0.0, 0.0).normalize(), None);
assert_eq!(Q64::new(0.0, 0.0, f64::NAN, 0.0).normalize(), None);
assert_eq!(Q64::new(0.0, 0.0, 0.0, f64::NAN).normalize(), None);
assert_eq!(
Q64::new(f64::INFINITY, f64::NAN, 1.0, 0.0).normalize(),
None
);
assert_eq!(
Q64::new(1.0, 0.0, f64::INFINITY, f64::NAN).normalize(),
None
);
}
#[test]
fn test_from_underlying_type_val() {
assert_eq!(Q64::from(-5.0), Q64::new(-5.0, 0.0, 0.0, 0.0));
assert_eq!(Into::<Q32>::into(42.0), Q32::new(42.0, 0.0, 0.0, 0.0));
}
#[allow(clippy::needless_borrows_for_generic_args)]
#[test]
fn test_from_underlying_type_ref() {
assert_eq!(Q64::from(&-5.0), Q64::new(-5.0, 0.0, 0.0, 0.0));
assert_eq!(Into::<Q32>::into(&42.0), Q32::new(42.0, 0.0, 0.0, 0.0));
}
#[test]
fn test_from_unit_quaternion() {
assert_eq!(Q32::from(UQ32::ONE), Q32::ONE);
assert_eq!(Q64::from(UQ64::I), Q64::I);
assert_eq!(Q32::from(UQ32::J), Q32::J);
assert_eq!(Q64::from(UQ64::K), Q64::K);
}
#[allow(clippy::needless_borrows_for_generic_args)]
#[test]
fn test_from_unit_quaternion_ref() {
assert_eq!(<&Q32 as From<&UQ32>>::from(&UQ32::ONE), &Q32::ONE);
assert_eq!(<&Q64 as From<&UQ64>>::from(&UQ64::I), &Q64::I);
assert_eq!(<&Q32 as From<&UQ32>>::from(&UQ32::J), &Q32::J);
assert_eq!(<&Q64 as From<&UQ64>>::from(&UQ64::K), &Q64::K);
}
#[test]
fn test_add_quaternion() {
assert_eq!(Q32::ONE + Q32::J, Q32::new(1.0, 0.0, 1.0, 0.0));
assert_eq!(
Q64::new(1.0, 2.0, 3.0, 4.0) + Q64::new(1.0, 3.0, 10.0, -5.0),
Q64::new(2.0, 5.0, 13.0, -1.0)
);
}
#[test]
fn test_add_real() {
assert_eq!(Q32::I + 1.0, Q32::new(1.0, 1.0, 0.0, 0.0));
assert_eq!(
Q32::new(1.0, 2.0, 3.0, 4.0) + 42.0,
Q32::new(43.0, 2.0, 3.0, 4.0)
);
}
#[test]
fn test_sub_quaternion() {
assert_eq!(Q32::ONE - Q32::J, Q32::new(1.0, 0.0, -1.0, 0.0));
assert_eq!(
Q64::new(1.0, 2.0, 3.0, 4.0) - Q64::new(1.0, 3.0, 10.0, -5.0),
Q64::new(0.0, -1.0, -7.0, 9.0)
);
}
#[test]
fn test_sub_real() {
assert_eq!(Q32::I - 1.0, Q32::new(-1.0, 1.0, 0.0, 0.0));
assert_eq!(
Q32::new(1.0, 2.0, 3.0, 4.0) - 42.0,
Q32::new(-41.0, 2.0, 3.0, 4.0)
);
}
#[test]
fn test_mul_quaternion() {
assert_eq!(Q32::ONE * Q32::ONE, Q32::ONE);
assert_eq!(Q32::ONE * Q32::I, Q32::I);
assert_eq!(Q32::ONE * Q32::J, Q32::J);
assert_eq!(Q32::ONE * Q32::K, Q32::K);
assert_eq!(Q32::I * Q32::ONE, Q32::I);
assert_eq!(Q32::J * Q32::ONE, Q32::J);
assert_eq!(Q32::K * Q32::ONE, Q32::K);
assert_eq!(Q32::I * Q32::I, -Q32::ONE);
assert_eq!(Q32::J * Q32::J, -Q32::ONE);
assert_eq!(Q32::K * Q32::K, -Q32::ONE);
assert_eq!(Q32::I * Q32::J, Q32::K);
assert_eq!(Q32::J * Q32::K, Q32::I);
assert_eq!(Q32::K * Q32::I, Q32::J);
assert_eq!(Q32::J * Q32::I, -Q32::K);
assert_eq!(Q32::K * Q32::J, -Q32::I);
assert_eq!(Q32::I * Q32::K, -Q32::J);
assert_eq!(
Q64::new(1.0, 2.0, 3.0, 4.0) * Q64::new(1.0, 3.0, 10.0, -5.0),
Q64::new(-15.0, -50.0, 35.0, 10.0)
);
}
#[test]
fn test_mul_real() {
assert_eq!(Q32::I * 1.0, Q32::I);
assert_eq!(
Q32::new(1.0, 2.0, 3.0, 4.0) * 42.0,
Q32::new(42.0, 84.0, 126.0, 168.0)
);
}
#[test]
fn test_mul_quaternion_by_unit_quaternion() {
assert_eq!(Q32::I * UQ32::J, Q32::K);
assert_eq!(Q64::J * UQ64::K, Q64::I);
assert_eq!(
Q32::new(1.0, 2.0, 3.0, 4.0) * UQ32::K,
Q32::new(-4.0, 3.0, -2.0, 1.0)
);
}
#[test]
fn test_div_quaternion() {
assert_eq!(Q32::ONE / Q32::ONE * Q32::ONE, Q32::ONE);
assert_eq!(Q32::ONE / Q32::I * Q32::I, Q32::ONE);
assert_eq!(Q32::ONE / Q32::J * Q32::J, Q32::ONE);
assert_eq!(Q32::ONE / Q32::K * Q32::K, Q32::ONE);
assert_eq!(Q32::I / Q32::ONE * Q32::ONE, Q32::I);
assert_eq!(Q32::J / Q32::ONE * Q32::ONE, Q32::J);
assert_eq!(Q32::K / Q32::ONE * Q32::ONE, Q32::K);
assert_eq!(Q32::I / Q32::I * Q32::I, Q32::I);
assert_eq!(Q32::J / Q32::J * Q32::J, Q32::J);
assert_eq!(Q32::K / Q32::K * Q32::K, Q32::K);
assert_eq!(Q32::I / Q32::J * Q32::J, Q32::I);
assert_eq!(Q32::J / Q32::K * Q32::K, Q32::J);
assert_eq!(Q32::K / Q32::I * Q32::I, Q32::K);
assert_eq!(Q32::J / Q32::I * Q32::I, Q32::J);
assert_eq!(Q32::K / Q32::J * Q32::J, Q32::K);
assert_eq!(Q32::I / Q32::K * Q32::K, Q32::I);
let q = Q64::new(1.0, 2.0, 3.0, 4.0);
let r = Q64::new(1.0, 3.0, 10.0, -5.0);
assert!((q / r * r - q).norm_sqr() < f64::EPSILON);
}
#[test]
fn test_div_real() {
assert_eq!(Q32::I * 1.0, Q32::I);
assert_eq!(
Q32::new(1.0, 2.0, 3.0, 4.0) / 4.0,
Q32::new(0.25, 0.5, 0.75, 1.0)
);
}
#[test]
fn test_div_quaternion_by_unit_quaternion() {
assert_eq!(Q32::I / UQ32::J, -Q32::K);
assert_eq!(Q64::J / UQ64::K, -Q64::I);
assert_eq!(
Q32::new(1.0, 2.0, 3.0, 4.0) / UQ32::K,
Q32::new(4.0, -3.0, 2.0, -1.0)
);
}
#[test]
fn test_add_assign() {
let mut q = Q32::new(1.0, 2.0, 3.0, 4.0);
q += 4.0;
assert_eq!(q, Quaternion::new(5.0, 2.0, 3.0, 4.0));
}
#[test]
fn test_sub_assign() {
let mut q = Q64::new(1.0, 2.0, 3.0, 4.0);
q -= Q64::new(4.0, 8.0, 6.0, 1.0);
assert_eq!(q, Quaternion::new(-3.0, -6.0, -3.0, 3.0));
}
#[test]
fn test_mul_assign() {
let mut q = Q32::new(1.0, 2.0, 3.0, 4.0);
q *= Q32::I;
assert_eq!(q, Quaternion::new(-2.0, 1.0, 4.0, -3.0));
}
#[test]
fn test_div_assign() {
let mut q = Quaternion::new(1.0f32, 2.0f32, 3.0f32, 4.0f32);
q /= 4.0f32;
assert_eq!(q, Quaternion::new(0.25f32, 0.5f32, 0.75f32, 1.0f32));
}
#[test]
#[allow(clippy::op_ref)]
fn test_add_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
assert_eq!(lhs + rhs, &lhs + rhs);
assert_eq!(lhs + rhs, lhs + &rhs);
assert_eq!(lhs + rhs, &lhs + &rhs);
}
#[test]
#[allow(clippy::op_ref)]
fn test_sub_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
assert_eq!(lhs - rhs, &lhs - rhs);
assert_eq!(lhs - rhs, lhs - &rhs);
assert_eq!(lhs - rhs, &lhs - &rhs);
}
#[test]
#[allow(clippy::op_ref)]
fn test_mul_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
assert_eq!(lhs * rhs, &lhs * rhs);
assert_eq!(lhs * rhs, lhs * &rhs);
assert_eq!(lhs * rhs, &lhs * &rhs);
}
#[test]
#[allow(clippy::op_ref)]
fn test_div_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
assert_eq!(lhs / rhs, &lhs / rhs);
assert_eq!(lhs / rhs, lhs / &rhs);
assert_eq!(lhs / rhs, &lhs / &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_add_unit_quaternion_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
assert_eq!(lhs + rhs, &lhs + rhs);
assert_eq!(lhs + rhs, lhs + &rhs);
assert_eq!(lhs + rhs, &lhs + &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_sub_unit_quaternion_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
assert_eq!(lhs - rhs, &lhs - rhs);
assert_eq!(lhs - rhs, lhs - &rhs);
assert_eq!(lhs - rhs, &lhs - &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_mul_unit_quaternion_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
assert_eq!(lhs * rhs, &lhs * rhs);
assert_eq!(lhs * rhs, lhs * &rhs);
assert_eq!(lhs * rhs, &lhs * &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_div_unit_quaternion_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
assert_eq!(lhs / rhs, &lhs / rhs);
assert_eq!(lhs / rhs, lhs / &rhs);
assert_eq!(lhs / rhs, &lhs / &rhs);
}
#[test]
#[allow(clippy::op_ref)]
fn test_add_real_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = 5.0;
assert_eq!(lhs + rhs, &lhs + rhs);
assert_eq!(lhs + rhs, lhs + &rhs);
assert_eq!(lhs + rhs, &lhs + &rhs);
}
#[test]
#[allow(clippy::op_ref)]
fn test_sub_real_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = 5.0;
assert_eq!(lhs - rhs, &lhs - rhs);
assert_eq!(lhs - rhs, lhs - &rhs);
assert_eq!(lhs - rhs, &lhs - &rhs);
}
#[test]
#[allow(clippy::op_ref)]
fn test_mul_real_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = 5.0;
assert_eq!(lhs * rhs, &lhs * rhs);
assert_eq!(lhs * rhs, lhs * &rhs);
assert_eq!(lhs * rhs, &lhs * &rhs);
}
#[test]
#[allow(clippy::op_ref)]
fn test_div_real_with_ref() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let rhs = 5.0;
assert_eq!(lhs / rhs, &lhs / rhs);
assert_eq!(lhs / rhs, lhs / &rhs);
assert_eq!(lhs / rhs, &lhs / &rhs);
}
#[test]
fn test_add_lhs_real() {
assert_eq!(42.0 + Quaternion::I, Quaternion::new(42.0, 1.0, 0.0, 0.0));
assert_eq!(1 + Quaternion::new(2, 4, 6, 8), Quaternion::new(3, 4, 6, 8));
}
#[test]
fn test_sub_lhs_real() {
assert_eq!(42.0 - Quaternion::I, Quaternion::new(42.0, -1.0, 0.0, 0.0));
assert_eq!(
1 - Quaternion::new(2, 4, 6, 8),
Quaternion::new(-1, -4, -6, -8)
);
}
#[test]
fn test_mul_lhs_real() {
assert_eq!(42.0 * Quaternion::I, Quaternion::new(0.0, 42.0, 0.0, 0.0));
assert_eq!(2 * Quaternion::new(1, 2, 3, 4), Quaternion::new(2, 4, 6, 8));
}
#[test]
fn test_div_lhs_real() {
assert_eq!(
42.0f32 / Quaternion::I,
Quaternion::new(0.0, -42.0, 0.0, 0.0)
);
assert_eq!(
4.0f64 / Quaternion::new(1.0, 1.0, 1.0, 1.0),
Quaternion::new(1.0, -1.0, -1.0, -1.0)
);
}
#[test]
fn test_neg() {
assert_eq!(
-Q64::new(1.0, -2.0, 3.0, -4.0),
Q64::new(-1.0, 2.0, -3.0, 4.0)
);
}
#[test]
fn test_powu() {
for q in [
Q32::ONE,
Q32::ZERO,
Q32::I,
Q32::new(1.0, 1.0, 1.0, 1.0),
Q32::new(1.0, 2.0, -3.0, 4.0),
] {
let mut expected = Q32::ONE;
for e in 0..16 {
assert_eq!(q.powu(e), expected);
expected *= q;
}
}
}
#[test]
fn test_powi() {
for q in [
Q32::ONE,
Q32::I,
Q32::new(1.0, 1.0, 1.0, 1.0),
Q32::new(1.0, 2.0, -3.0, 4.0),
] {
let mut expected = Q32::ONE;
for e in 0..16 {
assert_eq!(q.powi(e), expected);
assert!(
(q.powi(-e) - expected.inv()).norm_sqr() / expected.norm_sqr() < f32::EPSILON
);
expected *= q;
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_zero_quaternion() {
assert_eq!(Q32::ZERO.exp(), Q32::ONE);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_real_part_only() {
assert_eq!(Q32::ONE.exp(), core::f32::consts::E.into())
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_imaginary_part_only() {
assert!(
(Q64::I.exp() - Q64::new(1.0f64.cos(), 1.0f64.sin(), 0.0, 0.0)).norm() <= f64::EPSILON
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_complex_quaternion() {
let q = Q32::new(1.0, 1.0, 1.0, 1.0);
let exp_q = q.exp();
let expected_norm = 1.0f32.exp();
let angle = 3.0f32.sqrt();
assert!(
(exp_q
- Q32::new(
expected_norm * angle.cos(),
expected_norm * angle.sin() / angle,
expected_norm * angle.sin() / angle,
expected_norm * angle.sin() / angle
))
.norm()
<= 2.0 * expected_norm * f32::EPSILON
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_negative_real_part() {
let q = Q64::new(-1000.0, 0.0, f64::INFINITY, f64::NAN);
let exp_q = q.exp();
assert_eq!(exp_q, Q64::zero());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_nan_input() {
let q = Q32::new(f32::NAN, 1.0, 1.0, 1.0);
let exp_q = q.exp();
assert!(exp_q.w.is_nan());
assert!(exp_q.x.is_nan());
assert!(exp_q.y.is_nan());
assert!(exp_q.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_large_imaginary_norm() {
let q = Q32::new(1.0, 1e30, 1e30, 1e30);
let exp_q = q.exp();
assert!(exp_q.w.is_nan());
assert!(exp_q.x.is_nan());
assert!(exp_q.y.is_nan());
assert!(exp_q.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_infinite_real_part() {
let inf = f64::INFINITY;
let q = Quaternion::new(inf, 1.0, 1.0, 1.0);
let exp_q = q.exp();
assert_eq!(exp_q.w, -inf);
assert_eq!(exp_q.x, inf);
assert_eq!(exp_q.y, inf);
assert_eq!(exp_q.z, inf);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_infinite_imaginary_part() {
let q = Q32::new(1.0, f32::INFINITY, 0.0, 0.0);
let exp_q = q.exp();
assert!(exp_q.w.is_nan());
assert!(exp_q.x.is_nan());
assert!(exp_q.y.is_nan());
assert!(exp_q.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_small_imaginary_norm() {
let epsilon = f32::EPSILON;
let q = Quaternion::new(0.5, epsilon, epsilon, epsilon);
let exp_q = q.exp();
let result_norm = q.w.exp();
let expected_exp_q = Quaternion::new(
result_norm,
result_norm * q.x,
result_norm * q.y,
result_norm * q.z,
);
assert!((exp_q - expected_exp_q).norm() <= epsilon);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_infinite_result_angle_greater_than_90_degrees() {
let angle = f32::PI() * 0.75; let q = Q32::new(f32::INFINITY, angle, 0.0, 0.0);
let exp_q = q.exp();
assert_eq!(exp_q.w, -f32::INFINITY);
assert_eq!(exp_q.x, f32::INFINITY);
assert_eq!(exp_q.y, 0.0);
assert_eq!(exp_q.z, 0.0);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_exp_infinite_result_angle_greater_than_180_degrees() {
let angle = f64::PI() * 1.25; let q = Q64::new(f64::INFINITY, 0.0, angle, 0.0);
let exp_q = q.exp();
assert_eq!(exp_q.w, -f64::INFINITY);
assert_eq!(exp_q.x, 0.0);
assert_eq!(exp_q.y, -f64::INFINITY);
assert_eq!(exp_q.z, 0.0);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_ln_normal_case() {
let q = Quaternion::new(1.0, 2.0, 3.0, 4.0);
let ln_q = q.ln();
assert!((q.w - 30.0f64.ln() / 2.0) <= 4.0 * f64::EPSILON);
assert!((ln_q.z / ln_q.x - q.z / q.x) <= 2.0 * f64::EPSILON);
assert!((ln_q.y / ln_q.x - q.y / q.x) <= 2.0 * f64::EPSILON);
assert!((ln_q.x.hypot(ln_q.y.hypot(ln_q.z)) - 29.0f64.sqrt().atan()) <= 4.0 * f64::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_ln_positive_real_axis() {
let q = Quaternion::new(1.0, 1e-10, 1e-10, 1e-10);
let ln_q = q.ln();
let expected = Quaternion::new(0.0, 1e-10, 1e-10, 1e-10); assert!((ln_q - expected).norm() <= 1e-11);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_ln_negative_real_axis() {
let q = Q32::new(-1.0, 0.0, 0.0, 0.0);
let ln_q = q.ln();
let expected = Q32::new(0.0, core::f32::consts::PI, 0.0, 0.0); assert!((ln_q - expected).norm() <= core::f32::consts::PI * f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_ln_zero() {
let q = Q32::new(0.0, 0.0, 0.0, 0.0);
let ln_q = q.ln();
let expected = f32::NEG_INFINITY.into();
assert_eq!(ln_q, expected);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_ln_negative_zero() {
let q = Q64::new(-0.0, 0.0, 0.0, 0.0);
let ln_q = q.ln();
let expected = Q64::new(f64::NEG_INFINITY, core::f64::consts::PI, 0.0, 0.0);
assert_eq!(ln_q.w, expected.w);
assert!((ln_q.x - expected.x).abs() <= core::f64::consts::PI * f64::EPSILON);
assert_eq!(ln_q.y, expected.y);
assert_eq!(ln_q.z, expected.z);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_ln_nan() {
let q = Quaternion::new(f32::NAN, 1.0, 1.0, 1.0);
let ln_q = q.ln();
assert!(ln_q.w.is_nan());
assert!(ln_q.x.is_nan());
assert!(ln_q.y.is_nan());
assert!(ln_q.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_ln_infinite() {
let q = Q32::new(f32::INFINITY, 1.0, 1.0, 1.0);
let ln_q = q.ln();
let expected = Quaternion::new(f32::INFINITY, 0.0, 0.0, 0.0); assert_eq!(ln_q, expected);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_ln_finite_and_infinite() {
use num_traits::Signed;
let q = Quaternion::new(1.0, f32::INFINITY, -f32::INFINITY, -1.0);
let ln_q = q.ln();
let expected = Quaternion::new(
f32::INFINITY,
core::f32::consts::PI / 8.0f32.sqrt(),
-core::f32::consts::PI / 8.0f32.sqrt(),
0.0,
);
assert_eq!(ln_q.w, expected.w);
assert!((ln_q.x - expected.x).abs() <= 4.0f32 * f32::EPSILON);
assert!((ln_q.y - expected.y).abs() <= 4.0f32 * f32::EPSILON);
assert_eq!(ln_q.z, 0.0);
assert!(ln_q.z.is_negative());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_normal() {
let q = Q64::new(1.0, 2.0, 3.0, 4.0);
assert!(((q * q).sqrt() - q).norm() <= q.norm() * f64::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_zero() {
assert_eq!(Q32::ZERO.sqrt(), Q32::ZERO);
let zero = Q32::new(-0.0, 0.0, -0.0, -0.0);
assert!(zero.sqrt().w.is_sign_positive());
assert!(zero.sqrt().x.is_sign_positive());
assert!(zero.sqrt().y.is_sign_negative());
assert!(zero.sqrt().z.is_sign_negative());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_negative_real() {
let q = Q64::new(-4.0, -0.0, 0.0, 0.0);
let sqrt_q = q.sqrt();
let expected = Q64::new(0.0, -2.0, 0.0, 0.0);
assert_eq!(sqrt_q, expected);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_nan() {
let q = Q32::new(f32::NAN, 0.0, 0.0, 0.0);
let sqrt_q = q.sqrt();
assert!(sqrt_q.w.is_nan());
assert!(sqrt_q.x.is_nan());
assert!(sqrt_q.y.is_nan());
assert!(sqrt_q.z.is_nan());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_infinity() {
let q = Q64::new(f64::INFINITY, -0.0, -0.0, 0.0);
let sqrt_q = q.sqrt();
assert!(sqrt_q.w.is_infinite());
assert!(sqrt_q.x.is_zero());
assert!(sqrt_q.y.is_zero());
assert!(sqrt_q.z.is_zero());
assert!(sqrt_q.w.is_sign_positive());
assert!(sqrt_q.x.is_sign_negative());
assert!(sqrt_q.y.is_sign_negative());
assert!(sqrt_q.z.is_sign_positive());
let q = Q32::new(0.0, 0.0, -f32::INFINITY, -0.0);
let sqrt_q = q.sqrt();
assert!(sqrt_q.w.is_infinite());
assert!(sqrt_q.x.is_zero());
assert!(sqrt_q.y.is_infinite());
assert!(sqrt_q.z.is_zero());
assert!(sqrt_q.w.is_sign_positive());
assert!(sqrt_q.x.is_sign_positive());
assert!(sqrt_q.y.is_sign_negative());
assert!(sqrt_q.z.is_sign_negative());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_negative_infinity_real() {
let q = Quaternion::new(-f64::INFINITY, 0.0, -1.0, 0.0);
let sqrt_q = q.sqrt();
assert!(sqrt_q.w.is_zero());
assert!(sqrt_q.x.is_infinite());
assert!(sqrt_q.y.is_zero());
assert!(sqrt_q.z.is_zero());
assert!(sqrt_q.w.is_sign_positive());
assert!(sqrt_q.x.is_sign_positive());
assert!(sqrt_q.y.is_sign_negative());
assert!(sqrt_q.z.is_sign_positive());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_commutativity_with_conjugate() {
let q = Q32::new(1.0, 2.0, 3.0, 4.0);
assert_eq!(q.conj().sqrt(), q.sqrt().conj());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_subnormal_values() {
let subnormal = f64::MIN_POSITIVE / 2.0;
let q = Quaternion::new(subnormal, subnormal, subnormal, subnormal);
let sqrt_q = q.sqrt();
let norm_sqr = sqrt_q.norm_sqr();
assert!((norm_sqr - f64::MIN_POSITIVE).abs() <= 4.0 * subnormal * f64::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_mixed_infinities() {
let q = Q32::new(
-f32::INFINITY,
-f32::INFINITY,
f32::INFINITY,
-f32::INFINITY,
);
let sqrt_q = q.sqrt();
assert_eq!(sqrt_q.w, f32::INFINITY);
assert_eq!(sqrt_q.x, -f32::INFINITY);
assert_eq!(sqrt_q.y, f32::INFINITY);
assert_eq!(sqrt_q.z, -f32::INFINITY);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_positive_real() {
let q = Q64::new(4.0, 0.0, 0.0, 0.0);
let sqrt_q = q.sqrt();
let expected = Q64::new(2.0, 0.0, 0.0, 0.0);
assert_eq!(sqrt_q, expected);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_purely_imaginary() {
let q = Q32::new(0.0, 3.0, 4.0, 0.0);
let sqrt_q = q.sqrt();
assert!(sqrt_q.w > 0.0);
assert!((sqrt_q * sqrt_q - q).norm() <= 2.0 * q.norm() * f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_negative_imaginary() {
let q = Q64::new(0.0, -3.0, -4.0, 0.0);
let sqrt_q = q.sqrt();
assert!(sqrt_q.w > 0.0);
assert!((sqrt_q * sqrt_q - q).norm() <= 16.0 * q.norm() * f64::EPSILON);
}
#[cfg(feature = "std")]
fn compute_hash(val: impl Hash) -> u64 {
let mut hasher = DefaultHasher::new();
val.hash(&mut hasher);
hasher.finish()
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_quaternion() {
let q = Q32::new(1.0, 2.0, 3.0, 4.0);
let serialized = serde_json::to_string(&q).expect("Failed to serialize quaternion");
let deserialized: Quaternion<f32> =
serde_json::from_str(&serialized).expect("Failed to deserialize quaternion");
assert_eq!(q, deserialized);
}
#[cfg(feature = "std")]
#[test]
fn test_hash_of_unit_quaternion_equals_hash_of_inner_quaternion() {
assert_eq!(
compute_hash(UnitQuaternion::<u32>::ONE),
compute_hash(Quaternion::<u32>::ONE)
);
assert_eq!(
compute_hash(UnitQuaternion::<i32>::I),
compute_hash(Quaternion::<i32>::I)
);
assert_eq!(
compute_hash(UnitQuaternion::<isize>::J),
compute_hash(Quaternion::<isize>::J)
);
assert_eq!(
compute_hash(UnitQuaternion::<i128>::K),
compute_hash(Quaternion::<i128>::K)
);
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_euler_angles() {
let angles = EulerAngles {
roll: 1.0,
pitch: 2.0,
yaw: 3.0,
};
let serialized = serde_json::to_string(&angles).expect("Failed to serialize angles");
let deserialized: EulerAngles<f64> =
serde_json::from_str(&serialized).expect("Failed to deserialize angles");
assert_eq!(angles, deserialized);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_from_euler_angles() {
assert!(
(UQ32::from_euler_angles(core::f32::consts::PI, 0.0, 0.0).into_quaternion() - Q32::I)
.norm()
< f32::EPSILON
);
assert!(
(UQ64::from_euler_angles(0.0, core::f64::consts::PI, 0.0).into_quaternion() - Q64::J)
.norm()
< f64::EPSILON
);
assert!(
(UQ32::from_euler_angles(0.0, 0.0, core::f32::consts::PI).into_quaternion() - Q32::K)
.norm()
< f32::EPSILON
);
assert!(
(UQ64::from_euler_angles(1.0, 2.0, 3.0).into_quaternion()
- (UQ64::from_euler_angles(0.0, 0.0, 3.0)
* UQ64::from_euler_angles(0.0, 2.0, 0.0)
* UQ64::from_euler_angles(1.0, 0.0, 0.0))
.into_quaternion())
.norm()
< 4.0 * f64::EPSILON
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_to_euler_angles() {
let test_data = [
Q64::new(1.0, 0.0, 0.0, 0.0),
Q64::new(0.0, 1.0, 0.0, 0.0),
Q64::new(0.0, 0.0, 1.0, 0.0),
Q64::new(0.0, 0.0, 0.0, 1.0),
Q64::new(1.0, 1.0, 1.0, 1.0),
Q64::new(1.0, -2.0, 3.0, -4.0),
Q64::new(4.0, 3.0, 2.0, 1.0),
];
for q in test_data.into_iter().map(|q| q.normalize().unwrap()) {
let EulerAngles { roll, pitch, yaw } = q.to_euler_angles();
let p = UQ64::from_euler_angles(roll, pitch, yaw);
assert!((p - q).norm() < f64::EPSILON);
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_from_rotation_vector() {
assert!(
(UQ32::from_rotation_vector(&[core::f32::consts::PI, 0.0, 0.0]) - Q32::I).norm()
< f32::EPSILON
);
assert!(
(UQ64::from_rotation_vector(&[0.0, core::f64::consts::PI, 0.0]) - Q64::J).norm()
< f64::EPSILON
);
assert!(
(UQ32::from_rotation_vector(&[0.0, 0.0, core::f32::consts::PI]) - Q32::K).norm()
< f32::EPSILON
);
let x = 2.0 * core::f64::consts::FRAC_PI_3 * (1.0f64 / 3.0).sqrt();
assert!(
(UQ64::from_rotation_vector(&[x, x, x]) - Q64::new(0.5, 0.5, 0.5, 0.5)).norm()
< 4.0 * f64::EPSILON
);
assert!(
(UQ64::from_rotation_vector(&[-x, x, -x]) - Q64::new(0.5, -0.5, 0.5, -0.5)).norm()
< 4.0 * f64::EPSILON
);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_to_rotation_vector_zero_rotation() {
assert_eq!(UQ32::ONE.to_rotation_vector(), [0.0, 0.0, 0.0]);
assert_eq!(UQ64::ONE.to_rotation_vector(), [0.0, 0.0, 0.0]);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_to_rotation_vector_90_degree_rotation_x_axis() {
let q = Q32::new(1.0, 1.0, 0.0, 0.0).normalize().unwrap();
let rotation_vector = q.to_rotation_vector();
assert!((rotation_vector[0] - core::f32::consts::FRAC_PI_2).abs() < f32::EPSILON);
assert!((rotation_vector[1]).abs() < f32::EPSILON);
assert!((rotation_vector[2]).abs() < f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_to_rotation_vector_180_degree_rotation_y_axis() {
let q = UQ64::J;
let rotation_vector = q.to_rotation_vector();
assert!((rotation_vector[0]).abs() < f64::EPSILON);
assert!((rotation_vector[1] - core::f64::consts::PI).abs() < f64::EPSILON);
assert!((rotation_vector[2]).abs() < f64::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_to_rotation_vector_180_degree_rotation_arbitrary_axis() {
let q = Q32::new(0.0, 1.0, 1.0, 1.0).normalize().unwrap();
let rotation_vector = q.to_rotation_vector();
let expected = [
core::f32::consts::PI / (1.0f32 + 1.0 + 1.0).sqrt(),
core::f32::consts::PI / (1.0f32 + 1.0 + 1.0).sqrt(),
core::f32::consts::PI / (1.0f32 + 1.0 + 1.0).sqrt(),
];
assert!((rotation_vector[0] - expected[0]).abs() < 4.0 * f32::EPSILON);
assert!((rotation_vector[1] - expected[1]).abs() < 4.0 * f32::EPSILON);
assert!((rotation_vector[2] - expected[2]).abs() < 4.0 * f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_to_rotation_vector_small_rotation() {
let angle = 1e-6f32;
let q = Q32::new((angle / 2.0).cos(), (angle / 2.0).sin(), 0.0, 0.0)
.normalize()
.unwrap();
let rotation_vector = q.to_rotation_vector();
assert!((rotation_vector[0] - angle).abs() < f32::EPSILON);
assert!((rotation_vector[1]).abs() < f32::EPSILON);
assert!((rotation_vector[2]).abs() < f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_to_rotation_vector_general_case() {
for q in [
[1.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
[1.0, 1.0, 0.0, 0.0],
[1.0, 1.0, 1.0, 0.0],
[1.0, 1.0, 1.0, 1.0],
[0.0, 1.0, 1.0, -1.0],
[1.0, 0.0, 2.0, 5.0],
[1.0, 0.0, 1.0e-10, 2.0e-10],
[-1.0, 0.0, 0.0, 0.0],
[1.0, f64::EPSILON, 0.0, 0.0],
[1.0, 0.0, 0.0, f64::MIN_POSITIVE],
[
-1.0,
3.0 * f64::MIN_POSITIVE,
2.0 * f64::MIN_POSITIVE,
f64::MIN_POSITIVE,
],
]
.into_iter()
.map(|[w, x, y, z]| Q64::new(w, x, y, z).normalize().unwrap())
{
let rot = q.to_rotation_vector();
let p = UQ64::from_rotation_vector(&rot);
assert!((p - q).norm() <= 6.0 * f64::EPSILON);
}
}
#[test]
fn test_rotation_matrix_identity() {
let q = UQ64::ONE;
let rot_matrix = q.to_rotation_matrix3x3();
let expected = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
assert_eq!(rot_matrix, expected);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_rotation_matrix_90_degrees_x() {
let q = Q64::new(1.0, 1.0, 0.0, 0.0).normalize().unwrap();
let rot_matrix = q.to_rotation_matrix3x3();
let expected = [
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0,
];
for (r, e) in rot_matrix.iter().zip(expected) {
assert!((r - e).abs() <= f64::EPSILON);
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_rotation_matrix_90_degrees_y() {
let q = Q64::new(1.0, 0.0, 1.0, 0.0).normalize().unwrap();
let rot_matrix = q.to_rotation_matrix3x3();
let expected = [
0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0,
];
for (r, e) in rot_matrix.iter().zip(expected) {
assert!((r - e).abs() <= f64::EPSILON);
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_rotation_matrix_90_degrees_z() {
let q = Q64::new(1.0, 0.0, 0.0, 1.0).normalize().unwrap();
let rot_matrix = q.to_rotation_matrix3x3();
let expected = [
0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
];
for (r, e) in rot_matrix.iter().zip(expected) {
assert!((r - e).abs() <= f64::EPSILON);
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_rotation_matrix_120_degrees_xyz() {
let q = Q64::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap();
let rot_matrix = q.to_rotation_matrix3x3();
let expected = [
0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0,
];
for (r, e) in rot_matrix.iter().zip(expected) {
assert!((r - e).abs() <= f64::EPSILON);
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_rotation_matrix_general() {
let q = Q64::new(-1.0, 2.0, -3.0, 4.0).normalize().unwrap();
let rot_matrix = q.to_rotation_matrix3x3();
let [x1, y1, z1] = q.rotate_vector([1.0, 0.0, 0.0]);
let [x2, y2, z2] = q.rotate_vector([0.0, 1.0, 0.0]);
let [x3, y3, z3] = q.rotate_vector([0.0, 0.0, 1.0]);
let expected = [
x1, x2, x3, y1, y2, y3, z1, z2, z3,
];
for (r, e) in rot_matrix.iter().zip(expected) {
assert!((r - e).abs() <= f64::EPSILON);
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_identity_matrix() {
let identity: [[f32; 3]; 3] = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let q = UQ32::from_rotation_matrix3x3(&identity);
let expected = UQ32::ONE;
assert_eq!(q, expected);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_rotation_x() {
let angle = core::f32::consts::PI / 5.0;
let rotation_x: [[f32; 3]; 3] = [
[1.0, 0.0, 0.0],
[0.0, angle.cos(), angle.sin()],
[0.0, -angle.sin(), angle.cos()],
];
let q = UQ32::from_rotation_matrix3x3(&rotation_x);
let expected = UQ32::from_rotation_vector(&[angle, 0.0, 0.0]);
assert!((q - expected).norm() <= f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_rotation_y() {
let angle = 4.0 * core::f32::consts::PI / 5.0;
let rotation_y: [[f32; 3]; 3] = [
[angle.cos(), 0.0, -angle.sin()],
[0.0, 1.0, 0.0],
[angle.sin(), 0.0, angle.cos()],
];
let q = UQ32::from_rotation_matrix3x3(&rotation_y);
let expected = UQ32::from_rotation_vector(&[0.0, angle, 0.0]);
assert!((q - expected).norm() <= f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_rotation_z() {
let angle = 3.0 * core::f64::consts::PI / 5.0;
let rotation_z: [[f64; 3]; 3] = [
[angle.cos(), angle.sin(), 0.0],
[-angle.sin(), angle.cos(), 0.0],
[0.0, 0.0, 1.0],
];
let q = UQ64::from_rotation_matrix3x3(&rotation_z);
let expected = UQ64::from_rotation_vector(&[0.0, 0.0, angle]);
assert!((q - expected).norm() <= f64::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_arbitrary_rotation() {
let arbitrary_rotation: [[f32; 3]; 3] = [[0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 0.0]];
let q = UnitQuaternion::from_rotation_matrix3x3(&arbitrary_rotation);
let expected = Q32::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap();
assert!((q - expected).norm() <= f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_flat_array() {
let angle = core::f32::consts::PI / 2.0;
let rotation_z: [f32; 9] = [0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0];
let q = UQ32::from_rotation_matrix3x3(&rotation_z);
let expected = UQ32::from_rotation_vector(&[0.0, 0.0, angle]);
assert!((q - expected).norm() <= f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_from_rotation_to_rotation() {
let mat = [
0.36f64, 0.864, -0.352, 0.48, 0.152, 0.864, 0.8, -0.48, -0.36,
];
let q = UnitQuaternion::from_rotation_matrix3x3(&mat);
let restored_mat = q.to_rotation_matrix3x3();
for (x, e) in restored_mat.iter().zip(mat) {
assert!((x - e).abs() <= 4.0 * f64::EPSILON);
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_zero_vector_a() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let q = UnitQuaternion::from_two_vectors(&a, &b);
assert_eq!(q, UnitQuaternion::one());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_zero_vector_b() {
let a = [1.0, 0.0, 0.0];
let b = [0.0, 0.0, 0.0];
let q = UnitQuaternion::from_two_vectors(&a, &b);
assert_eq!(q, UnitQuaternion::one());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_parallel_vectors() {
let a = [1.0, 0.0, 0.0];
let b = [2.0, 0.0, 0.0];
let q = UnitQuaternion::from_two_vectors(&a, &b);
assert_eq!(q, UnitQuaternion::one());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_opposite_vectors() {
let a = [1.0, 0.0, 0.0];
let b = [-1.0, 0.0, 0.0];
let q = UnitQuaternion::from_two_vectors(&a, &b);
assert_eq!(q.as_quaternion().w, 0.0);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_perpendicular_vectors() {
let a = [1.0f32, 0.0, 0.0];
let b = [0.0f32, 1.0, 0.0];
let q = UQ32::from_two_vectors(&a, &b);
let expected = Q32::new(1.0, 0.0, 0.0, 1.0).normalize().unwrap();
assert!((q - expected).norm() <= 2.0 * f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_non_normalized_vectors() {
let a = [0.0, 3.0, 0.0];
let b = [0.0, 5.0, 5.0];
let q = UQ64::from_two_vectors(&a, &b);
let expected = Q64::new(1.0, core::f64::consts::FRAC_PI_8.tan(), 0.0, 0.0)
.normalize()
.unwrap();
assert!((q - expected).norm() <= 2.0 * f64::EPSILON);
let a = [0.0, 3.0, 0.0];
let b = [0.0, -5.0, 5.0];
let q = UQ64::from_two_vectors(&a, &b);
let expected = Q64::new(1.0, (3.0 * core::f64::consts::FRAC_PI_8).tan(), 0.0, 0.0)
.normalize()
.unwrap();
assert!((q - expected).norm() <= 2.0 * f64::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_same_vector() {
let a = [1.0, 1.0, 1.0];
let q = UnitQuaternion::from_two_vectors(&a, &a);
assert_eq!(q, UnitQuaternion::one());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_arbitrary_vectors() {
let a = [1.0, 2.0, 3.0];
let b = [4.0, 5.0, 6.0];
let q = UQ64::from_two_vectors(&a, &b);
let v = [-3.0, 6.0, -3.0]; let v_norm = 54.0f64.sqrt();
let dir = [v[0] / v_norm, v[1] / v_norm, v[2] / v_norm];
let cos_angle = (a[0] * b[0] + a[1] * b[1] + a[2] * b[2])
/ ((a[0] * a[0] + a[1] * a[1] + a[2] * a[2])
* (b[0] * b[0] + b[1] * b[1] + b[2] * b[2]))
.sqrt();
let angle = cos_angle.acos();
let expected =
UQ64::from_rotation_vector(&[dir[0] * angle, dir[1] * angle, dir[2] * angle]);
assert!((q - expected).norm() <= 2.0 * f64::EPSILON);
}
#[test]
fn test_default_unit_quaternion() {
assert_eq!(UQ32::default().into_quaternion(), Q32::ONE);
}
#[test]
fn test_constant_one() {
assert_eq!(UQ32::ONE.into_quaternion(), Q32::ONE);
assert_eq!(
UnitQuaternion::<i32>::ONE.into_quaternion(),
Quaternion::<i32>::ONE
);
}
#[test]
fn test_constant_i() {
assert_eq!(UQ32::I.into_quaternion(), Q32::I);
}
#[test]
fn test_constant_j() {
assert_eq!(UQ32::J.into_quaternion(), Q32::J);
}
#[test]
fn test_constant_k() {
assert_eq!(UQ32::K.into_quaternion(), Q32::K);
}
#[test]
fn test_const_one() {
assert_eq!(<UQ32 as ConstOne>::ONE.into_quaternion(), Q32::ONE);
}
#[test]
fn test_one_trait() {
assert_eq!(<UQ32 as One>::one().into_quaternion(), Q32::ONE);
assert!(UQ64::ONE.is_one());
assert!(!UQ64::I.is_one());
assert!(!UQ64::J.is_one());
assert!(!UQ64::K.is_one());
let mut uq = UQ32::I;
uq.set_one();
assert!(uq.is_one());
}
#[test]
fn test_unit_quaternion_i_func() {
assert_eq!(UQ32::i().into_quaternion(), Q32::i());
}
#[test]
fn test_unit_quaternion_j_func() {
assert_eq!(UQ32::j().into_quaternion(), Q32::j());
}
#[test]
fn test_unit_quaternion_k_func() {
assert_eq!(UQ32::k().into_quaternion(), Q32::k());
}
#[test]
fn test_unit_quaternion_conj() {
assert_eq!(UQ32::ONE.conj(), UQ32::ONE);
assert_eq!(UQ64::I.conj(), -UQ64::I);
assert_eq!(UQ32::J.conj(), -UQ32::J);
assert_eq!(UQ64::K.conj(), -UQ64::K);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_unit_quaternion_conj_with_normalize() {
assert_eq!(
Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj(),
Q32::new(1.0, -2.0, -3.0, -4.0).normalize().unwrap()
)
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_unit_quaternion_inv_func() {
assert_eq!(
Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().inv(),
Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj()
)
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_unit_quaternion_inv_trait() {
assert_eq!(
<UQ32 as Inv>::inv(Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap()),
Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj()
)
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_unit_quaternion_ref_inv_trait() {
assert_eq!(
<&UQ32 as Inv>::inv(&Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap()),
Q32::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap().conj()
)
}
#[test]
fn test_unit_quaternion_add() {
assert_eq!(UQ32::I + UQ32::J, Q32::new(0.0, 1.0, 1.0, 0.0));
}
#[test]
fn test_unit_quaternion_add_quaternion() {
assert_eq!(UQ32::J + Q32::K, Q32::new(0.0, 0.0, 1.0, 1.0));
}
#[test]
fn test_unit_quaternion_add_underlying() {
assert_eq!(UQ32::J + 2.0f32, Q32::new(2.0, 0.0, 1.0, 0.0));
}
#[test]
fn test_f32_add_unit_quaternion() {
assert_eq!(3.0f32 + UQ32::K, Q32::new(3.0, 0.0, 0.0, 1.0));
}
#[test]
fn test_f64_add_unit_quaternion() {
assert_eq!(4.0f64 + UQ64::I, Q64::new(4.0, 1.0, 0.0, 0.0));
}
#[test]
fn test_unit_quaternion_sub() {
assert_eq!(UQ32::I - UQ32::J, Q32::new(0.0, 1.0, -1.0, 0.0));
}
#[test]
fn test_unit_quaternion_sub_quaternion() {
assert_eq!(UQ32::J - Q32::K, Q32::new(0.0, 0.0, 1.0, -1.0));
}
#[test]
fn test_unit_quaternion_sub_underlying() {
assert_eq!(UQ32::J - 2.0f32, Q32::new(-2.0, 0.0, 1.0, 0.0));
}
#[test]
fn test_f32_sub_unit_quaternion() {
assert_eq!(3.0f32 - UQ32::K, Q32::new(3.0, 0.0, 0.0, -1.0));
}
#[test]
fn test_f64_sub_unit_quaternion() {
assert_eq!(4.0f64 - UQ64::I, Q64::new(4.0, -1.0, 0.0, 0.0));
}
#[test]
fn test_unit_quaternion_mul() {
assert_eq!(UQ32::I * UQ32::J, UQ32::K);
}
#[test]
fn test_unit_quaternion_mul_quaternion() {
assert_eq!(UQ32::J * Q32::K, Q32::new(0.0, 1.0, 0.0, 0.0));
}
#[test]
fn test_unit_quaternion_mul_underlying() {
assert_eq!(UQ32::J * 2.0f32, Q32::new(0.0, 0.0, 2.0, 0.0));
}
#[test]
fn test_f32_mul_unit_quaternion() {
assert_eq!(3.0f32 * UQ32::K, Q32::new(0.0, 0.0, 0.0, 3.0));
}
#[test]
fn test_f64_mul_unit_quaternion() {
assert_eq!(4.0f64 * UQ64::I, Q64::new(0.0, 4.0, 0.0, 0.0));
}
#[test]
fn test_unit_quaternion_div() {
assert_eq!(UQ32::I / UQ32::J, -UQ32::K);
}
#[test]
fn test_unit_quaternion_div_quaternion() {
assert_eq!(UQ32::J / Q32::K, Q32::new(0.0, -1.0, 0.0, 0.0));
}
#[test]
fn test_unit_quaternion_div_underlying() {
assert_eq!(UQ32::J / 2.0f32, Q32::new(0.0, 0.0, 0.5, 0.0));
}
#[test]
fn test_f32_div_unit_quaternion() {
assert_eq!(3.0f32 / UQ32::K, Q32::new(0.0, 0.0, 0.0, -3.0));
}
#[test]
fn test_f64_div_unit_quaternion() {
assert_eq!(4.0f64 / UQ64::I, Q64::new(0.0, -4.0, 0.0, 0.0));
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_add_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
assert_eq!(lhs + rhs, &lhs + rhs);
assert_eq!(lhs + rhs, lhs + &rhs);
assert_eq!(lhs + rhs, &lhs + &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_sub_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
assert_eq!(lhs - rhs, &lhs - rhs);
assert_eq!(lhs - rhs, lhs - &rhs);
assert_eq!(lhs - rhs, &lhs - &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_mul_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
assert_eq!(lhs * rhs, &lhs * rhs);
assert_eq!(lhs * rhs, lhs * &rhs);
assert_eq!(lhs * rhs, &lhs * &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_div_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0).normalize().unwrap();
assert_eq!(lhs / rhs, &lhs / rhs);
assert_eq!(lhs / rhs, lhs / &rhs);
assert_eq!(lhs / rhs, &lhs / &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_add_quaternion_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
assert_eq!(lhs + rhs, &lhs + rhs);
assert_eq!(lhs + rhs, lhs + &rhs);
assert_eq!(lhs + rhs, &lhs + &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_sub_quaternion_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
assert_eq!(lhs - rhs, &lhs - rhs);
assert_eq!(lhs - rhs, lhs - &rhs);
assert_eq!(lhs - rhs, &lhs - &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_mul_quaternion_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
assert_eq!(lhs * rhs, &lhs * rhs);
assert_eq!(lhs * rhs, lhs * &rhs);
assert_eq!(lhs * rhs, &lhs * &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_div_quaternion_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = Quaternion::new(5.0, 6.0, 7.0, 8.0);
assert_eq!(lhs / rhs, &lhs / rhs);
assert_eq!(lhs / rhs, lhs / &rhs);
assert_eq!(lhs / rhs, &lhs / &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_add_real_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = 5.0;
assert_eq!(lhs + rhs, &lhs + rhs);
assert_eq!(lhs + rhs, lhs + &rhs);
assert_eq!(lhs + rhs, &lhs + &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_sub_real_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = 5.0;
assert_eq!(lhs - rhs, &lhs - rhs);
assert_eq!(lhs - rhs, lhs - &rhs);
assert_eq!(lhs - rhs, &lhs - &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_mul_real_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = 5.0;
assert_eq!(lhs * rhs, &lhs * rhs);
assert_eq!(lhs * rhs, lhs * &rhs);
assert_eq!(lhs * rhs, &lhs * &rhs);
}
#[test]
#[cfg(any(feature = "std", feature = "libm"))]
#[allow(clippy::op_ref)]
fn test_div_real_with_ref_unit_quaternion() {
let lhs = Quaternion::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let rhs = 5.0;
assert_eq!(lhs / rhs, &lhs / rhs);
assert_eq!(lhs / rhs, lhs / &rhs);
assert_eq!(lhs / rhs, &lhs / &rhs);
}
#[test]
fn test_unit_quaternion_neg() {
assert_eq!(
(-UQ32::ONE).into_quaternion(),
Q32::new(-1.0, 0.0, 0.0, 0.0)
);
assert_eq!((-UQ32::I).into_quaternion(), Q32::new(0.0, -1.0, 0.0, 0.0));
assert_eq!((-UQ32::J).into_quaternion(), Q32::new(0.0, 0.0, -1.0, 0.0));
assert_eq!((-UQ32::K).into_quaternion(), Q32::new(0.0, 0.0, 0.0, -1.0));
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_unit_quaternion_adjust_norm() {
let mut q = UQ32::from_euler_angles(1.0, 0.5, 1.5);
for _ in 0..25 {
q = q * q;
}
assert!((q.into_quaternion().norm() - 1.0).abs() > 0.5);
assert!((q.adjust_norm().into_quaternion().norm() - 1.0).abs() <= 2.0 * f32::EPSILON);
}
#[test]
fn test_unit_quaternion_rotate_vector_units() {
let v = [1.0, 2.0, 3.0];
assert_eq!(UQ32::I.rotate_vector(v), [1.0, -2.0, -3.0]);
assert_eq!(UQ32::J.rotate_vector(v), [-1.0, 2.0, -3.0]);
assert_eq!(UQ32::K.rotate_vector(v), [-1.0, -2.0, 3.0]);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_unit_quaternion_rotate_vector_normalized() {
let q = Q32::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap();
let v = [1.0, 2.0, 3.0];
let result = q.rotate_vector(v);
assert_eq!(result, [2.0, 3.0, 1.0]);
}
#[cfg(any(feature = "std", feature = "libm"))]
fn generate_unit_quaternion_data() -> impl Iterator<Item = UQ32> {
[
UQ32::ONE,
UQ32::I,
UQ32::J,
UQ32::K,
Q32::new(1.0, 1.0, 1.0, 1.0).normalize().unwrap(),
Q32::new(10.0, 1.0, 1.0, 1.0).normalize().unwrap(),
Q32::new(1.0, 10.0, 1.0, 1.0).normalize().unwrap(),
Q32::new(1.0, 1.0, 3.0, 4.0).normalize().unwrap(),
Q32::new(1.0, -1.0, 3.0, -4.0).normalize().unwrap(),
]
.into_iter()
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_slerp_t_zero() {
for q1 in generate_unit_quaternion_data() {
for q2 in generate_unit_quaternion_data() {
let result = q1.slerp(&q2, 0.0);
assert!((result - q1).norm() <= f32::EPSILON);
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_slerp_t_one() {
use core::cmp::Ordering;
for q1 in generate_unit_quaternion_data() {
for q2 in generate_unit_quaternion_data() {
let result = q1.slerp(&q2, 1.0);
match q1.dot(q2).partial_cmp(&0.0) {
Some(Ordering::Greater) => assert!((result - q2).norm() <= f32::EPSILON),
Some(Ordering::Less) => assert!((result + q2).norm() <= f32::EPSILON),
_ => {}
}
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_slerp_t_half() {
use core::cmp::Ordering;
for q1 in generate_unit_quaternion_data() {
for q2 in generate_unit_quaternion_data() {
let result = q1.slerp(&q2, 0.5);
let dot_sign = match q1.dot(q2).partial_cmp(&0.0) {
Some(Ordering::Greater) => 1.0,
Some(Ordering::Less) => -1.0,
_ => continue, };
assert!((result - (q1 + dot_sign * q2).normalize().unwrap()).norm() <= f32::EPSILON)
}
}
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_slerp_small_angle() {
let q1 = UQ32::ONE;
let q2 = Q32::new(999_999.0, 1.0, 0.0, 0.0).normalize().unwrap();
let t = 0.5;
let result = q1.slerp(&q2, t);
let expected = Q32::new(999_999.75, 0.5, 0.0, 0.0).normalize().unwrap();
assert!((result - expected).norm() <= f32::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_of_identity() {
assert_eq!(UQ32::ONE.sqrt(), UQ32::ONE);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_of_negative_identity() {
let q = Q64::new(-1.0, 0.0, -0.0, -0.0).normalize().unwrap();
assert_eq!(q.sqrt(), UQ64::I);
assert!(q.sqrt().0.w.is_sign_positive());
assert!(q.sqrt().0.y.is_sign_negative());
assert!(q.sqrt().0.z.is_sign_negative());
let q = Q64::new(-1.0, -0.0, 0.0, 0.0).normalize().unwrap();
assert_eq!(q.sqrt(), -UQ64::I);
assert!(q.sqrt().0.w.is_sign_positive());
assert!(q.sqrt().0.y.is_sign_positive());
assert!(q.sqrt().0.z.is_sign_positive());
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_general_case() {
let c = Q64::new(1.0, 2.0, -3.0, 4.0).normalize().unwrap();
let q = c.sqrt();
assert!((q * q - c).norm() <= f64::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_with_negative_real_part() {
let c = Q64::new(-4.0, 2.0, -3.0, 1.0).normalize().unwrap();
let q = c.sqrt();
assert!((q * q - c).norm() <= f64::EPSILON);
}
#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn test_sqrt_with_subnormal_imaginary_parts() {
let min_positive = f64::MIN_POSITIVE;
let q = UnitQuaternion(Quaternion::new(
-1.0,
min_positive,
min_positive,
min_positive,
));
let result = q.sqrt();
let expected = UnitQuaternion(Quaternion::new(
min_positive * 0.75f64.sqrt(),
(1.0f64 / 3.0).sqrt(),
(1.0f64 / 3.0).sqrt(),
(1.0f64 / 3.0).sqrt(),
));
assert!((result.0.w - expected.0.w).abs() <= 2.0 * expected.0.w * f64::EPSILON);
assert!((result.0.x - expected.0.x).abs() <= 2.0 * expected.0.x * f64::EPSILON);
assert!((result.0.y - expected.0.y).abs() <= 2.0 * expected.0.y * f64::EPSILON);
assert!((result.0.z - expected.0.z).abs() <= 2.0 * expected.0.z * f64::EPSILON);
}
#[cfg(all(feature = "serde", any(feature = "std", feature = "libm")))]
#[test]
fn test_serde_unit_quaternion() {
let q = Q64::new(1.0, 2.0, 3.0, 4.0).normalize().unwrap();
let serialized = serde_json::to_string(&q).expect("Failed to serialize quaternion");
let deserialized: UQ64 =
serde_json::from_str(&serialized).expect("Failed to deserialize quaternion");
assert_eq!(q, deserialized);
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_unit_quaternion_k() {
let q = UQ64::K;
let serialized = serde_json::to_string(&q).expect("Failed to serialize quaternion");
let deserialized: UQ64 =
serde_json::from_str(&serialized).expect("Failed to deserialize quaternion");
assert_eq!(q, deserialized);
}
}