use core::ops::{Add, Div, Mul, Neg, Sub};
use crate::{
Vector,
algebra::{
ApproxEqAbs, ApproxEqRel, Dot, Identity, Inverse, Length, LengthSquared, Lerp, Normalize,
},
numeric::{Clamp, NegOne, One, Sqrt, Trigonometry, Two, Zero},
quaternion::Quaternion,
};
impl<T> Quaternion<T>
where
T: Zero + One,
{
pub const IDENTITY: Self = Self::new(T::ZERO, T::ZERO, T::ZERO, T::ONE);
}
impl<T> Identity for Quaternion<T>
where
T: Zero + One,
{
const IDENTITY: Self = Quaternion::IDENTITY;
}
impl<T> Quaternion<T>
where
T: Copy + ApproxEqAbs<Tolerance = T> + Neg<Output = T>,
{
const DEFAULT_TOLERANCE_ABS: T = T::DEFAULT_TOLERANCE_ABS;
#[inline]
pub fn approx_eq_abs_tol(self, rhs: Self, tolerance: T) -> bool {
let lhs = self.into_tuple();
let rhs = rhs.into_tuple();
lhs.approx_eq_abs_tol(rhs, tolerance) || lhs.approx_eq_abs_tol(-rhs, tolerance)
}
#[inline]
pub fn approx_eq_abs(self, rhs: Self) -> bool {
self.approx_eq_abs_tol(rhs, Self::DEFAULT_TOLERANCE_ABS)
}
}
impl<T> ApproxEqAbs for Quaternion<T>
where
T: Copy + ApproxEqAbs<Tolerance = T> + Neg<Output = T>,
{
type Tolerance = T;
const DEFAULT_TOLERANCE_ABS: Self::Tolerance = T::DEFAULT_TOLERANCE_ABS;
#[inline]
fn approx_eq_abs_tol(self, other: Self, tol: Self::Tolerance) -> bool {
Quaternion::approx_eq_abs_tol(self, other, tol)
}
}
impl<T> Quaternion<T>
where
T: Copy + ApproxEqRel<Tolerance = T> + Neg<Output = T>,
{
const DEFAULT_TOLERANCE_REL: T = T::DEFAULT_TOLERANCE_REL;
#[inline]
pub fn approx_eq_rel_tol(self, rhs: Self, tolerance: T) -> bool {
let lhs = self.into_tuple();
let rhs = rhs.into_tuple();
lhs.approx_eq_rel_tol(rhs, tolerance) || lhs.approx_eq_rel_tol(-rhs, tolerance)
}
#[inline]
pub fn approx_eq_rel(self, rhs: Self) -> bool {
self.approx_eq_rel_tol(rhs, Self::DEFAULT_TOLERANCE_REL)
}
}
impl<T> ApproxEqRel for Quaternion<T>
where
T: Copy + ApproxEqRel<Tolerance = T> + Neg<Output = T>,
{
type Tolerance = T;
const DEFAULT_TOLERANCE_REL: Self::Tolerance = T::DEFAULT_TOLERANCE_REL;
#[inline]
fn approx_eq_rel_tol(self, other: Self, tol: Self::Tolerance) -> bool {
Quaternion::approx_eq_rel_tol(self, other, tol)
}
}
impl<T> Quaternion<T>
where
T: Copy + Mul<Output = T> + Add<Output = T>,
{
#[inline]
pub fn length_squared(self) -> T {
self.into_tuple().length_squared()
}
#[inline]
pub fn length(self) -> T
where
T: Sqrt,
{
self.length_squared().sqrt()
}
#[inline]
pub fn normalize(self) -> Self
where
T: Sqrt + Div<Output = T>,
{
self / self.length()
}
}
impl<T> LengthSquared for Quaternion<T>
where
T: Copy + Mul<Output = T> + Add<Output = T>,
{
type Output = T;
#[inline]
fn length_squared(self) -> Self::Output {
Quaternion::length_squared(self)
}
}
impl<T> Length for Quaternion<T>
where
T: Copy + Mul<Output = T> + Add<Output = T> + Sqrt,
{
#[inline]
fn length(self) -> Self::Output {
Quaternion::length(self)
}
}
impl<T> Normalize for Quaternion<T>
where
T: Copy + Mul<Output = T> + Add<Output = T> + Div<Output = T> + Sqrt,
{
#[inline]
fn normalize(self) -> Self {
Quaternion::normalize(self)
}
}
impl<T> Quaternion<T>
where
T: Copy + Mul<Output = T> + Add<Output = T>,
{
#[inline]
pub fn dot(self, rhs: Self) -> T {
self.into_tuple().dot(rhs.into_tuple())
}
}
impl<T> Dot for Quaternion<T>
where
T: Copy + Mul<Output = T> + Add<Output = T>,
{
type Output = T;
#[inline]
fn dot(self, rhs: Self) -> Self::Output {
Quaternion::dot(self, rhs)
}
}
impl<T> Quaternion<T>
where
T: Copy + Neg<Output = T>,
{
#[inline]
pub fn conjugate(self) -> Self {
Self::new(-self.x, -self.y, -self.z, self.w)
}
}
impl<T> Quaternion<T>
where
T: Copy + Mul<Output = T> + Add<Output = T> + Div<Output = T> + Neg<Output = T>,
{
#[inline]
pub fn inverse(self) -> Self {
self.conjugate() / self.length_squared()
}
}
impl<T> Inverse for Quaternion<T>
where
T: Copy + Mul<Output = T> + Add<Output = T> + Div<Output = T> + Neg<Output = T>,
{
#[inline]
fn inverse(self) -> Self {
Quaternion::inverse(self)
}
}
impl<T> Quaternion<T>
where
T: Copy + Mul<Output = T> + Add<Output = T> + Sub<Output = T>,
{
#[inline]
pub fn compose(self, rhs: Self) -> Self {
let (x1, y1, z1, w1) = (self.x, self.y, self.z, self.w);
let (x2, y2, z2, w2) = (rhs.x, rhs.y, rhs.z, rhs.w);
let x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2;
let y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2;
let z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2;
let w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2;
Self::new(x, y, z, w)
}
}
impl<T> Quaternion<T>
where
T: Copy + Mul<Output = T> + Add<Output = T> + Sub<Output = T> + Two,
{
#[inline]
pub fn rotate_vector(self, rhs: Vector<T, 3>) -> Vector<T, 3> {
let u = Vector::<T, 3>::new(self.x, self.y, self.z);
let w = self.w;
let uv = u.cross(rhs);
let uuv = u.cross(uv);
rhs + uv * (w * T::TWO) + uuv * T::TWO
}
}
impl<T> Quaternion<T>
where
T: Copy + Sub<Output = T> + Mul<Output = T> + Add<Output = T>,
{
#[inline]
pub fn lerp(self, rhs: Self, t: T) -> Self {
self + (rhs - self) * t
}
}
impl<T> Lerp for Quaternion<T>
where
T: Copy + Sub<Output = T> + Mul<Output = T> + Add<Output = T>,
{
type Scalar = T;
#[inline]
fn lerp(self, rhs: Self, t: Self::Scalar) -> Self {
Quaternion::lerp(self, rhs, t)
}
}
impl<T> Quaternion<T>
where
T: Copy
+ Sub<Output = T>
+ Mul<Output = T>
+ Add<Output = T>
+ Div<Output = T>
+ Neg<Output = T>
+ Zero
+ Sqrt
+ PartialOrd,
{
#[inline]
pub fn nlerp(self, mut rhs: Self, t: T) -> Self {
if self.dot(rhs) < T::ZERO {
rhs = -rhs;
}
self.lerp(rhs, t).normalize()
}
}
impl<T> Quaternion<T>
where
T: Copy
+ Sub<Output = T>
+ Add<Output = T>
+ Mul<Output = T>
+ Div<Output = T>
+ Neg<Output = T>
+ Sqrt
+ Trigonometry
+ Zero
+ One
+ NegOne
+ Clamp
+ PartialOrd
+ ApproxEqAbs<Tolerance = T>,
{
#[inline]
pub fn slerp(self, mut rhs: Self, t: T) -> Self {
let mut dot = self.dot(rhs);
if dot < T::ZERO {
rhs = -rhs;
dot = -dot;
}
dot = dot.clamp_value(T::NEG_ONE, T::ONE);
if dot.approx_eq_abs(T::ONE) {
return self.nlerp(rhs, t);
}
let theta = dot.acos();
let sin_theta = theta.sin();
let a = ((T::ONE - t) * theta).sin() / sin_theta;
let b = (t * theta).sin() / sin_theta;
self * a + rhs * b
}
}
impl<T> Quaternion<T>
where
T: Copy + Mul<Output = T> + Div<Output = T> + Trigonometry + Two,
{
#[inline]
pub fn from_axis_angle(axis: Vector<T, 3>, angle: T) -> Self {
let half_angle = angle / T::TWO;
let (sin, cos) = half_angle.sin_cos();
let axis = axis * sin;
Self::new(axis.x, axis.y, axis.z, cos)
}
}
impl<T> Quaternion<T>
where
T: Copy
+ Mul<Output = T>
+ Div<Output = T>
+ Trigonometry
+ Zero
+ One
+ Two
+ NegOne
+ Clamp
+ ApproxEqAbs<Tolerance = T>,
{
#[inline]
pub fn to_axis_angle(self) -> (Vector<T, 3>, T) {
let half_angle = self.w.clamp_value(T::NEG_ONE, T::ONE).acos();
let angle = half_angle * T::TWO;
let sin_half_angle = half_angle.sin();
if sin_half_angle.approx_eq_abs(T::ZERO) {
return (Vector::<T, 3>::new(T::ONE, T::ZERO, T::ZERO), T::ZERO);
}
let axis = Vector::<T, 3>::new(self.x, self.y, self.z) / sin_half_angle;
(axis, angle)
}
}