use core::{
fmt::{Debug, Display},
hash::Hash,
ops::{Add, AddAssign, Deref, DerefMut, Mul, MulAssign, Neg, Sub, SubAssign},
};
use crate::{
Aligned, Alignment, Scalar, SignedInteger, Unaligned, Vector,
constants::{Nan, One, Zero},
utils::{transmute_mut, transmute_ref},
};
mod float;
#[repr(transparent)]
pub struct Quaternion<T, A: Alignment>(Vector<4, T, A>)
where
T: Scalar;
pub type Quat<T> = Quaternion<T, Aligned>;
pub type QuatU<T> = Quaternion<T, Unaligned>;
impl<T, A: Alignment> Quaternion<T, A>
where
T: Scalar + Zero,
{
pub const ZERO: Self = Self::from_vec(Vector::ZERO);
}
impl<T, A: Alignment> Quaternion<T, A>
where
T: Scalar + Zero + One,
{
pub const IDENTITY: Self = Self::from_array([T::ZERO, T::ZERO, T::ZERO, T::ONE]);
}
impl<T, A: Alignment> Quaternion<T, A>
where
T: Scalar + Nan,
{
pub const NAN: Self = Self::from_vec(Vector::NAN);
}
impl<T, A: Alignment> Quaternion<T, A>
where
T: Scalar,
{
#[inline]
#[must_use]
pub const fn new(x: T, y: T, z: T, w: T) -> Self {
Self(Vector::<4, T, A>::new(x, y, z, w))
}
#[inline]
#[must_use]
pub const fn from_array(array: [T; 4]) -> Self {
Self(Vector::from_array(array))
}
#[inline]
#[must_use]
pub const fn from_vec(vec: Vector<4, T, A>) -> Self {
Self(vec)
}
#[inline]
#[must_use]
pub const fn to_alignment<A2: Alignment>(self) -> Quaternion<T, A2> {
Quaternion(self.0.to_alignment())
}
#[inline]
#[must_use]
pub const fn align(self) -> Quaternion<T, Aligned> {
Quaternion(self.0.align())
}
#[inline]
#[must_use]
pub const fn unalign(self) -> Quaternion<T, Unaligned> {
Quaternion(self.0.unalign())
}
#[inline]
#[must_use]
pub const fn to_array(self) -> [T; 4] {
self.0.to_array()
}
#[inline]
#[must_use]
pub const fn as_array_ref(&self) -> &[T; 4] {
self.0.as_array_ref()
}
#[inline]
#[must_use]
pub const fn as_array_mut(&mut self) -> &mut [T; 4] {
self.0.as_array_mut()
}
#[inline]
#[must_use]
pub const fn to_vec(self) -> Vector<4, T, A> {
self.0
}
#[inline]
#[must_use]
pub const fn as_vec_ref(&self) -> &Vector<4, T, A> {
&self.0
}
#[inline]
#[must_use]
pub const fn as_vec_mut(&mut self) -> &mut Vector<4, T, A> {
&mut self.0
}
#[inline]
#[must_use]
#[expect(private_bounds)]
pub const unsafe fn to_repr<T2>(self) -> Quaternion<T2, A>
where
T2: Scalar<Repr = T::Repr>,
T::Repr: SignedInteger,
{
unsafe { Quaternion(self.0.to_repr()) }
}
}
impl<T, A: Alignment> Clone for Quaternion<T, A>
where
T: Scalar,
{
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<T, A: Alignment> Copy for Quaternion<T, A> where T: Scalar {}
#[doc(hidden)]
#[repr(C)]
pub struct QuatFields<T> {
pub x: T,
pub y: T,
pub z: T,
pub w: T,
}
impl<T, A: Alignment> Deref for Quaternion<T, A>
where
T: Scalar,
{
type Target = QuatFields<T>;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { transmute_ref::<Quaternion<T, A>, QuatFields<T>>(self) }
}
}
impl<T, A: Alignment> DerefMut for Quaternion<T, A>
where
T: Scalar,
{
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { transmute_mut::<Quaternion<T, A>, QuatFields<T>>(self) }
}
}
impl<T, A: Alignment> Debug for Quaternion<T, A>
where
T: Scalar + Debug,
{
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("Quat")
.field(&self.x)
.field(&self.y)
.field(&self.z)
.field(&self.w)
.finish()
}
}
impl<T, A: Alignment> Display for Quaternion<T, A>
where
T: Scalar + Display,
{
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "({}, {}, {}, {})", self.x, self.y, self.z, self.w)
}
}
impl<T, A: Alignment> PartialEq for Quaternion<T, A>
where
T: Scalar + PartialEq,
{
#[inline]
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
#[expect(clippy::partialeq_ne_impl)]
#[inline]
fn ne(&self, other: &Self) -> bool {
self.0 != other.0
}
}
impl<T, A: Alignment> Eq for Quaternion<T, A> where T: Scalar + Eq {}
impl<T, A: Alignment> Hash for Quaternion<T, A>
where
T: Scalar + Hash,
{
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_array_ref().hash(state);
}
}
impl<T, A: Alignment> Default for Quaternion<T, A>
where
T: Scalar + Zero + One,
{
#[inline]
fn default() -> Self {
Self::IDENTITY
}
}
impl<T, A: Alignment> Neg for Quaternion<T, A>
where
T: Scalar + Neg<Output = T>,
{
type Output = Self;
#[inline]
#[track_caller]
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
impl<T, A: Alignment> Add for Quaternion<T, A>
where
T: Scalar + Add<Output = T>,
{
type Output = Self;
#[inline]
#[track_caller]
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl<T, A: Alignment> Sub for Quaternion<T, A>
where
T: Scalar + Sub<Output = T>,
{
type Output = Self;
#[inline]
#[track_caller]
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl<T, A: Alignment> Mul<T> for Quaternion<T, A>
where
T: Scalar + Mul<Output = T>,
{
type Output = Self;
#[inline]
#[track_caller]
fn mul(self, rhs: T) -> Self::Output {
Self(self.0 * rhs)
}
}
impl<T, A: Alignment> AddAssign for Quaternion<T, A>
where
T: Scalar + Add<Output = T>,
{
#[inline]
#[track_caller]
fn add_assign(&mut self, rhs: Self) {
*self = Self(self.0 + rhs.0);
}
}
impl<T, A: Alignment> SubAssign for Quaternion<T, A>
where
T: Scalar + Sub<Output = T>,
{
#[inline]
#[track_caller]
fn sub_assign(&mut self, rhs: Self) {
*self = Self(self.0 - rhs.0);
}
}
impl<T, A: Alignment> MulAssign<T> for Quaternion<T, A>
where
T: Scalar + Mul<Output = T>,
{
#[inline]
#[track_caller]
fn mul_assign(&mut self, rhs: T) {
*self = Self(self.0 * rhs);
}
}
#[cfg(test)]
mod tests {
use crate::{
Aligned, Quaternion, Unaligned, Vector,
utils::{assert_float_eq, for_parameters},
};
#[test]
fn test_layout() {
for_parameters!(|T: PrimitiveNumber, A| {
assert_eq!(size_of::<Quaternion<T, A>>(), size_of::<Vector<4, T, A>>());
assert_eq!(
align_of::<Quaternion<T, A>>(),
align_of::<Vector<4, T, A>>()
);
});
}
#[test]
fn test_zero() {
for_parameters!(|T: PrimitiveNumber, A| {
assert_eq!(
Quaternion::<T, A>::ZERO,
Quaternion::new(T::as_from(0), T::as_from(0), T::as_from(0), T::as_from(0))
);
});
}
#[test]
fn test_identity() {
for_parameters!(|T: PrimitiveNumber, A| {
assert_eq!(
Quaternion::<T, A>::IDENTITY,
Quaternion::new(T::as_from(0), T::as_from(0), T::as_from(0), T::as_from(1))
);
});
}
#[test]
fn test_nan() {
for_parameters!(|T: PrimitiveFloat, A| {
assert_float_eq!(
Quaternion::<T, A>::NAN,
Quaternion::new(T::NAN, T::NAN, T::NAN, T::NAN)
);
});
}
#[test]
fn test_from_array() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
Quaternion::<T, A>::from_array([x, y, z, w]),
Quaternion::new(x, y, z, w)
);
});
}
#[test]
fn test_from_vec() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
Quaternion::from_vec(Vector::<4, T, A>::new(x, y, z, w)),
Quaternion::new(x, y, z, w)
);
});
}
#[test]
fn test_to_alignment() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w).to_alignment(),
Quaternion::<T, Aligned>::new(x, y, z, w)
);
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w).to_alignment(),
Quaternion::<T, Unaligned>::new(x, y, z, w)
);
});
}
#[test]
fn test_align() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w).align(),
Quaternion::<T, Aligned>::new(x, y, z, w)
);
});
}
#[test]
fn test_unalign() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w).unalign(),
Quaternion::<T, Unaligned>::new(x, y, z, w)
);
});
}
#[test]
fn test_to_array() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(Quaternion::<T, A>::new(x, y, z, w).to_array(), [x, y, z, w]);
});
}
#[test]
fn test_as_array_ref() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w).as_array_ref(),
&[x, y, z, w]
);
});
}
#[test]
fn test_as_array_mut() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w).as_array_mut(),
&mut [x, y, z, w]
);
});
}
#[test]
fn test_to_vec() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w).to_vec(),
Vector::<4, T, A>::new(x, y, z, w)
);
});
}
#[test]
fn test_as_vec_ref() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w).as_vec_ref(),
&Vector::<4, T, A>::new(x, y, z, w)
);
});
}
#[test]
fn test_as_vec_mut() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w).as_vec_mut(),
&mut Vector::<4, T, A>::new(x, y, z, w)
);
});
}
#[test]
fn test_to_repr() {
for_parameters!(|A| {
assert_eq!(
unsafe { Quaternion::<i32, A>::new(0, 1, 2, 3).to_repr() },
Quaternion::<u32, A>::new(0, 1, 2, 3)
);
});
}
#[test]
fn test_deref() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(Quaternion::<T, A>::new(x, y, z, w).x, x);
assert_eq!(Quaternion::<T, A>::new(x, y, z, w).y, y);
assert_eq!(Quaternion::<T, A>::new(x, y, z, w).z, z);
assert_eq!(Quaternion::<T, A>::new(x, y, z, w).w, w);
});
}
#[test]
fn test_deref_mut() {
for_parameters!(|T: PrimitiveNumber, A| {
let [mut x, mut y, mut z, mut w] = std::array::from_fn(T::as_from);
assert_eq!(&mut Quaternion::<T, A>::new(x, y, z, w).x, &mut x);
assert_eq!(&mut Quaternion::<T, A>::new(x, y, z, w).y, &mut y);
assert_eq!(&mut Quaternion::<T, A>::new(x, y, z, w).z, &mut z);
assert_eq!(&mut Quaternion::<T, A>::new(x, y, z, w).w, &mut w);
});
}
#[test]
fn test_debug() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
format!("{:?}", Quaternion::<T, A>::new(x, y, z, w)),
format!("Quat({x:?}, {y:?}, {z:?}, {w:?})")
);
});
}
#[test]
fn test_display() {
for_parameters!(|T: PrimitiveNumber, A| {
let [x, y, z, w] = std::array::from_fn(T::as_from);
assert_eq!(
format!("{}", Quaternion::<T, A>::new(x, y, z, w)),
format!("({x}, {y}, {z}, {w})")
);
});
}
#[test]
fn test_eq() {
for_parameters!(|T: PrimitiveNumber, A, x, y, z| {
let w = if x > y { x } else { y };
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w) == Quaternion::<T, A>::new(z, w, y, x),
x == z && y == w && z == y && w == x
);
});
}
#[test]
fn test_ne() {
for_parameters!(|T: PrimitiveNumber, A, x, y, z| {
let w = if x > y { x } else { y };
assert_eq!(
Quaternion::<T, A>::new(x, y, z, w) != Quaternion::<T, A>::new(z, w, y, x),
x != z || y != w || z != y || w != x
);
});
}
#[test]
fn test_default() {
for_parameters!(|T: PrimitiveNumber, A| {
assert_eq!(Quaternion::<T, A>::default(), Quaternion::IDENTITY);
});
}
#[test]
fn test_neg() {
for_parameters!(|T: PrimitiveFloat, A, x, y, z| {
let w = T::max(x, y);
assert_float_eq!(
-Quaternion::<T, A>::new(x, y, z, w),
Quaternion::new(-x, -y, -z, -w)
);
});
}
#[test]
fn test_add() {
for_parameters!(|T: PrimitiveFloat, A, x, y, z| {
let w = T::max(x, y);
assert_float_eq!(
Quaternion::<T, A>::new(x, y, z, w) + Quaternion::<T, A>::new(z, w, y, x),
Quaternion::new(x + z, y + w, z + y, w + x)
);
});
}
#[test]
fn test_sub() {
for_parameters!(|T: PrimitiveFloat, A, x, y, z| {
let w = T::max(x, y);
assert_float_eq!(
Quaternion::<T, A>::new(x, y, z, w) - Quaternion::<T, A>::new(z, w, y, x),
Quaternion::new(x - z, y - w, z - y, w - x)
);
});
}
#[test]
fn test_mul() {
for_parameters!(|T: PrimitiveFloat, A, x, y, z| {
let w = T::max(x, y);
assert_float_eq!(
Quaternion::<T, A>::new(x, y, z, w) * w,
Quaternion::new(x * w, y * w, z * w, w * w)
);
});
}
#[test]
fn test_add_assign() {
for_parameters!(|T: PrimitiveFloat, A, x, y, z| {
let w = T::max(x, y);
let mut quat = Quaternion::<T, A>::new(x, y, z, w);
quat += Quaternion::<T, A>::new(z, w, y, x);
assert_float_eq!(quat, Quaternion::new(x + z, y + w, z + y, w + x));
});
}
#[test]
fn test_sub_assign() {
for_parameters!(|T: PrimitiveFloat, A, x, y, z| {
let w = T::max(x, y);
let mut quat = Quaternion::<T, A>::new(x, y, z, w);
quat -= Quaternion::<T, A>::new(z, w, y, x);
assert_float_eq!(quat, Quaternion::new(x - z, y - w, z - y, w - x));
});
}
#[test]
fn test_mul_assign() {
for_parameters!(|T: PrimitiveFloat, A, x, y, z| {
let w = T::max(x, y);
let mut quat = Quaternion::<T, A>::new(x, y, z, w);
quat *= w;
assert_float_eq!(quat, Quaternion::new(x * w, y * w, z * w, w * w));
});
}
}