Skip to main content

rs_math3d/
quaternion.rs

1// Copyright 2020-Present (c) Raja Lehtihet & Wael El Oraiby
2//
3// Redistribution and use in source and binary forms, with or without
4// modification, are permitted provided that the following conditions are met:
5//
6// 1. Redistributions of source code must retain the above copyright notice,
7// this list of conditions and the following disclaimer.
8//
9// 2. Redistributions in binary form must reproduce the above copyright notice,
10// this list of conditions and the following disclaimer in the documentation
11// and/or other materials provided with the distribution.
12//
13// 3. Neither the name of the copyright holder nor the names of its contributors
14// may be used to endorse or promote products derived from this software without
15// specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27// POSSIBILITY OF SUCH DAMAGE.
28//! Quaternion math for rotations and orientation.
29
30use crate::matrix::{Matrix3, Matrix4};
31use crate::scalar::*;
32use crate::vector::{FloatVector, Vector, Vector3};
33use core::ops::*;
34use num_traits::{One, Zero};
35
36#[repr(C)]
37#[derive(Copy, Clone, Debug, Default)]
38/// A quaternion representing rotation or orientation.
39pub struct Quat<T: Scalar> {
40    /// X component (imaginary).
41    pub x: T,
42    /// Y component (imaginary).
43    pub y: T,
44    /// Z component (imaginary).
45    pub z: T,
46    /// W component (real).
47    pub w: T,
48}
49
50impl<T: FloatScalar> Quat<T> {
51    #[allow(clippy::too_many_arguments)]
52    fn from_rotation_matrix(
53        m00: T,
54        m01: T,
55        m02: T,
56        m10: T,
57        m11: T,
58        m12: T,
59        m20: T,
60        m21: T,
61        m22: T,
62    ) -> Self {
63        let t = <T as One>::one() + m00 + m11 + m22;
64
65        if t > <T as Zero>::zero() {
66            let s = T::tsqrt(t) * T::two();
67
68            let x = (m21 - m12) / s;
69            let y = (m02 - m20) / s;
70            let z = (m10 - m01) / s;
71            let w = T::quarter() * s;
72            Self::new(x, y, z, w)
73        } else if m00 > m11 && m00 > m22 {
74            let s = T::tsqrt(<T as One>::one() + m00 - m11 - m22) * T::two();
75            let x = T::quarter() * s;
76            let y = (m10 + m01) / s;
77            let z = (m02 + m20) / s;
78            let w = (m21 - m12) / s;
79            Self::new(x, y, z, w)
80        } else if m11 > m22 {
81            let s = T::tsqrt(<T as One>::one() + m11 - m00 - m22) * T::two();
82            let x = (m10 + m01) / s;
83            let y = T::quarter() * s;
84            let z = (m21 + m12) / s;
85            let w = (m02 - m20) / s;
86            Self::new(x, y, z, w)
87        } else {
88            let s = T::tsqrt(<T as One>::one() + m22 - m00 - m11) * T::two();
89            let x = (m02 + m20) / s;
90            let y = (m21 + m12) / s;
91            let z = T::quarter() * s;
92            let w = (m10 - m01) / s;
93            Self::new(x, y, z, w)
94        }
95    }
96
97    /// Returns the identity quaternion (no rotation).
98    pub fn identity() -> Self {
99        Self {
100            x: <T as Zero>::zero(),
101            y: <T as Zero>::zero(),
102            z: <T as Zero>::zero(),
103            w: <T as One>::one(),
104        }
105    }
106    /// Creates a quaternion from components.
107    pub fn new(x: T, y: T, z: T, w: T) -> Self {
108        Self { x, y, z, w }
109    }
110
111    /// Computes the dot product of two quaternions.
112    ///
113    /// ```text
114    /// q₁ · q₂ = x₁x₂ + y₁y₂ + z₁z₂ + w₁w₂
115    /// ```
116    pub fn dot(l: &Self, r: &Self) -> T {
117        l.x * r.x + l.y * r.y + l.z * r.z + l.w * r.w
118    }
119
120    /// Computes the length (magnitude) of the quaternion.
121    ///
122    /// ```text
123    /// ||q|| = √(x² + y² + z² + w²)
124    /// ```
125    pub fn length(&self) -> T {
126        T::tsqrt(Self::dot(self, self))
127    }
128
129    /// Returns the conjugate of the quaternion.
130    ///
131    /// The conjugate inverts the rotation:
132    /// ```text
133    /// q* = -xi - yj - zk + w
134    /// ```
135    pub fn conjugate(q: &Self) -> Self {
136        Self::new(-q.x, -q.y, -q.z, q.w)
137    }
138
139    /// Normalizes the quaternion to unit length.
140    ///
141    /// Unit quaternions represent valid rotations:
142    /// ```text
143    /// q̂ = q / ||q||
144    /// ```
145    pub fn normalize(q: &Self) -> Self {
146        let ql = q.length();
147        if ql > <T as Zero>::zero() {
148            *q / ql
149        } else {
150            *q
151        }
152    }
153
154    /// Negates all components of the quaternion.
155    ///
156    /// Note: -q and q represent the same rotation (double cover property).
157    pub fn neg(q: &Self) -> Self {
158        Self::new(-q.x, -q.y, -q.z, -q.w)
159    }
160    /// Adds two quaternions component-wise.
161    pub fn add(l: &Self, r: &Self) -> Self {
162        Self::new(l.x + r.x, l.y + r.y, l.z + r.z, l.w + r.w)
163    }
164    /// Subtracts two quaternions component-wise.
165    pub fn sub(l: &Self, r: &Self) -> Self {
166        Self::new(l.x - r.x, l.y - r.y, l.z - r.z, l.w - r.w)
167    }
168    /// Multiplies two quaternions (Hamilton product).
169    pub fn mul(l: &Self, r: &Self) -> Self {
170        let x = l.w * r.x + l.x * r.w + l.y * r.z - l.z * r.y;
171        let y = l.w * r.y + l.y * r.w + l.z * r.x - l.x * r.z;
172        let z = l.w * r.z + l.z * r.w + l.x * r.y - l.y * r.x;
173        let w = l.w * r.w - l.x * r.x - l.y * r.y - l.z * r.z;
174        Self::new(x, y, z, w)
175    }
176
177    /// Multiplies a quaternion by a scalar.
178    pub fn mulf(l: &Self, r: T) -> Self {
179        Self::new(l.x * r, l.y * r, l.z * r, l.w * r)
180    }
181    /// Multiplies a scalar by a quaternion.
182    pub fn fmul(l: T, r: &Self) -> Self {
183        Self::new(l * r.x, l * r.y, l * r.z, l * r.w)
184    }
185    /// Divides a quaternion by a scalar.
186    pub fn divf(l: &Self, r: T) -> Self {
187        Self::new(l.x / r, l.y / r, l.z / r, l.w / r)
188    }
189    /// Divides a scalar by a quaternion component-wise.
190    pub fn fdiv(l: T, r: &Self) -> Self {
191        Self::new(l / r.x, l / r.y, l / r.z, l / r.w)
192    }
193    /// Returns the multiplicative inverse of the quaternion.
194    ///
195    /// For non-zero quaternions: q⁻¹ = conjugate(q) / |q|².
196    /// Returns the input unchanged if the norm is too small.
197    pub fn inverse(q: &Self) -> Self {
198        let denom = Self::dot(q, q);
199        if denom > T::epsilon() {
200            Self::conjugate(q) / denom
201        } else {
202            *q
203        }
204    }
205
206    /// Converts the quaternion to a 3x3 rotation matrix.
207    ///
208    /// The conversion formula:
209    /// ```text
210    /// R = I + 2s(q×) + 2(q×)²
211    /// ```
212    /// where q× is the cross-product matrix of the vector part.
213    pub fn mat3(&self) -> Matrix3<T> {
214        let xx = self.x * self.x;
215        let xy = self.x * self.y;
216        let xz = self.x * self.z;
217        let xw = self.x * self.w;
218        let yy = self.y * self.y;
219        let yz = self.y * self.z;
220        let yw = self.y * self.w;
221        let zz = self.z * self.z;
222        let zw = self.z * self.w;
223
224        let m00 = <T as One>::one() - T::two() * (yy + zz);
225        let m10 = T::two() * (xy + zw);
226        let m20 = T::two() * (xz - yw);
227        let m01 = T::two() * (xy - zw);
228        let m11 = <T as One>::one() - T::two() * (xx + zz);
229        let m21 = T::two() * (yz + xw);
230        let m02 = T::two() * (xz + yw);
231        let m12 = T::two() * (yz - xw);
232        let m22 = <T as One>::one() - T::two() * (xx + yy);
233
234        Matrix3::new(m00, m10, m20, m01, m11, m21, m02, m12, m22)
235    }
236
237    /// Converts the quaternion to a 4x4 transformation matrix.
238    ///
239    /// The resulting matrix represents a pure rotation with no translation.
240    /// The bottom-right element is 1 for homogeneous coordinates.
241    pub fn mat4(&self) -> Matrix4<T> {
242        let xx = self.x * self.x;
243        let xy = self.x * self.y;
244        let xz = self.x * self.z;
245        let xw = self.x * self.w;
246        let yy = self.y * self.y;
247        let yz = self.y * self.z;
248        let yw = self.y * self.w;
249        let zz = self.z * self.z;
250        let zw = self.z * self.w;
251
252        let m00 = <T as One>::one() - T::two() * (yy + zz);
253        let m10 = T::two() * (xy + zw);
254        let m20 = T::two() * (xz - yw);
255        let m01 = T::two() * (xy - zw);
256        let m11 = <T as One>::one() - T::two() * (xx + zz);
257        let m21 = T::two() * (yz + xw);
258        let m02 = T::two() * (xz + yw);
259        let m12 = T::two() * (yz - xw);
260        let m22 = <T as One>::one() - T::two() * (xx + yy);
261
262        Matrix4::new(
263            m00,
264            m10,
265            m20,
266            <T as Zero>::zero(),
267            m01,
268            m11,
269            m21,
270            <T as Zero>::zero(),
271            m02,
272            m12,
273            m22,
274            <T as Zero>::zero(),
275            <T as Zero>::zero(),
276            <T as Zero>::zero(),
277            <T as Zero>::zero(),
278            <T as One>::one(),
279        )
280    }
281
282    /// Converts the quaternion to axis-angle representation.
283    ///
284    /// Returns:
285    /// - `axis`: The normalized rotation axis
286    /// - `angle`: The rotation angle in radians
287    ///
288    /// For a quaternion q = cos(θ/2) + sin(θ/2)(xi + yj + zk):
289    /// - axis = normalize(x, y, z)
290    /// - angle = 2 * acos(w)
291    ///
292    /// If the rotation is near-zero, returns a default axis of (1, 0, 0).
293    pub fn to_axis_angle(&self) -> (Vector3<T>, T) {
294        let nq = Self::normalize(self);
295        let one = <T as One>::one();
296        let mut cos_a = nq.w;
297        cos_a = T::min(one, T::max(-one, cos_a));
298        let angle = T::tacos(cos_a) * T::two();
299        let axis_vec = Vector3::new(nq.x, nq.y, nq.z);
300        let axis_len = axis_vec.length();
301        if axis_len <= T::epsilon() {
302            (
303                Vector3::new(one, <T as Zero>::zero(), <T as Zero>::zero()),
304                angle,
305            )
306        } else {
307            (axis_vec / axis_len, angle)
308        }
309    }
310
311    /// Creates a quaternion from a 3x3 rotation matrix.
312    pub fn of_matrix3(m: &Matrix3<T>) -> Self {
313        Self::from_rotation_matrix(
314            m.col[0].x, m.col[1].x, m.col[2].x, m.col[0].y, m.col[1].y, m.col[2].y, m.col[0].z,
315            m.col[1].z, m.col[2].z,
316        )
317    }
318
319    /// Creates a quaternion from a 4x4 rotation matrix.
320    pub fn of_matrix4(m: &Matrix4<T>) -> Self {
321        Self::from_rotation_matrix(
322            m.col[0].x, m.col[1].x, m.col[2].x, m.col[0].y, m.col[1].y, m.col[2].y, m.col[0].z,
323            m.col[1].z, m.col[2].z,
324        )
325    }
326
327    /// Creates a quaternion from an axis and angle.
328    ///
329    /// # Parameters
330    /// - `axis`: The rotation axis (will be normalized)
331    /// - `angle`: The rotation angle in radians
332    /// - `epsilon`: Minimum axis length to treat as valid
333    ///
334    /// The quaternion is constructed as:
335    /// ```text
336    /// q = cos(θ/2) + sin(θ/2) * normalize(axis)
337    /// ```
338    ///
339    /// # Returns
340    /// - `Some(quaternion)` for a valid axis
341    /// - `None` if the axis length is too small
342    pub fn of_axis_angle(axis: &Vector3<T>, angle: T, epsilon: T) -> Option<Self> {
343        let len_sq = Vector3::dot(axis, axis);
344        if len_sq <= epsilon * epsilon {
345            return None;
346        }
347        let half_angle = angle * T::half();
348        let sin_a = T::tsin(half_angle);
349        let cos_a = T::tcos(half_angle);
350        let inv_len = <T as One>::one() / len_sq.tsqrt();
351        let n = *axis * inv_len * sin_a;
352
353        let x = n.x;
354        let y = n.y;
355        let z = n.z;
356        let w = cos_a;
357        Some(Self::new(x, y, z, w))
358    }
359}
360
361impl<T: FloatScalar> Add for Quat<T> {
362    type Output = Quat<T>;
363    fn add(self, rhs: Self) -> Self {
364        Self::add(&self, &rhs)
365    }
366}
367
368impl<T: FloatScalar> Sub for Quat<T> {
369    type Output = Quat<T>;
370    fn sub(self, rhs: Self) -> Self {
371        Self::sub(&self, &rhs)
372    }
373}
374
375impl<T: FloatScalar> Mul for Quat<T> {
376    type Output = Quat<T>;
377    fn mul(self, rhs: Self) -> Self {
378        Self::mul(&self, &rhs)
379    }
380}
381
382impl<T: FloatScalar> Div<T> for Quat<T> {
383    type Output = Quat<T>;
384    fn div(self, t: T) -> Self {
385        Self::divf(&self, t)
386    }
387}
388
389impl<T: FloatScalar> Mul<T> for Quat<T> {
390    type Output = Quat<T>;
391    fn mul(self, t: T) -> Self {
392        Self::mulf(&self, t)
393    }
394}
395
396macro_rules! impl_scalar {
397    ($Op:ident, $op:ident, $Opop:ident, $t:ident) => {
398        impl $Op<Quat<$t>> for $t {
399            type Output = Quat<$t>;
400            fn $op(self, q: Quat<$t>) -> Quat<$t> {
401                Quat::$Opop(self, &q)
402            }
403        }
404    };
405}
406
407impl_scalar!(Div, div, fdiv, f32);
408impl_scalar!(Mul, mul, fmul, f32);
409impl_scalar!(Div, div, fdiv, f64);
410impl_scalar!(Mul, mul, fmul, f64);
411
412#[cfg(test)]
413mod tests {
414    use core::f32::consts::{FRAC_PI_2, FRAC_PI_4, PI};
415
416    use crate::matrix::*;
417    use crate::scalar::*;
418    use crate::vector::*;
419    use crate::Quat;
420
421    fn quat_axis_angle_f32(axis: &Vector3<f32>, angle: f32) -> Quat<f32> {
422        Quat::of_axis_angle(axis, angle, EPS_F32).expect("axis length too small")
423    }
424
425    fn quat_axis_angle_f64(axis: &Vector3<f64>, angle: f64) -> Quat<f64> {
426        Quat::of_axis_angle(axis, angle, EPS_F64).expect("axis length too small")
427    }
428
429    fn mat3_axis_angle_f32(axis: &Vector3<f32>, angle: f32) -> Matrix3<f32> {
430        Matrix3::of_axis_angle(axis, angle, EPS_F32).expect("axis length too small")
431    }
432
433    fn assert_vec3_close_f32(actual: Vector3<f32>, expected: Vector3<f32>) {
434        assert!((actual - expected).length() < f32::epsilon());
435    }
436
437    fn assert_vec3_close_f64(actual: Vector3<f64>, expected: Vector3<f64>) {
438        assert!((actual - expected).length() < f64::epsilon());
439    }
440
441    fn assert_scalar_close_f32(actual: f32, expected: f32) {
442        assert!((actual - expected).abs() < f32::epsilon());
443    }
444
445    fn assert_scalar_close_f64(actual: f64, expected: f64) {
446        assert!((actual - expected).abs() < f64::epsilon());
447    }
448
449    #[test]
450    fn test_identity() {
451        let q = Quat::<f32>::identity();
452        let m = q.mat3();
453
454        let x_axis = m.col[0];
455        let y_axis = m.col[1];
456        let z_axis = m.col[2];
457
458        assert_vec3_close_f32(x_axis, Vector3::new(1.0, 0.0, 0.0));
459        assert_vec3_close_f32(y_axis, Vector3::new(0.0, 1.0, 0.0));
460        assert_vec3_close_f32(z_axis, Vector3::new(0.0, 0.0, 1.0));
461    }
462
463    #[test]
464    fn test_identity_conjugate() {
465        let q = Quat::conjugate(&Quat::<f32>::identity());
466        let m = q.mat3();
467
468        let x_axis = m.col[0];
469        let y_axis = m.col[1];
470        let z_axis = m.col[2];
471
472        assert_vec3_close_f32(x_axis, Vector3::new(1.0, 0.0, 0.0));
473        assert_vec3_close_f32(y_axis, Vector3::new(0.0, 1.0, 0.0));
474        assert_vec3_close_f32(z_axis, Vector3::new(0.0, 0.0, 1.0));
475    }
476
477    #[test]
478    fn test_rot_180() {
479        let v = Vector3::<f32>::new(1.0, 0.0, 0.0);
480        let a = PI;
481
482        let q = quat_axis_angle_f32(&v, a);
483        let m = q.mat3();
484
485        let x_axis = m.col[0];
486        let y_axis = m.col[1];
487        let z_axis = m.col[2];
488
489        assert_vec3_close_f32(x_axis, Vector3::new(1.0, 0.0, 0.0));
490        assert_vec3_close_f32(y_axis, Vector3::new(0.0, -1.0, 0.0));
491        assert_vec3_close_f32(z_axis, Vector3::new(0.0, 0.0, -1.0));
492
493        let m2 = mat3_axis_angle_f32(&v, a);
494
495        let x2_axis = m2.col[0];
496        let y2_axis = m2.col[1];
497        let z2_axis = m2.col[2];
498
499        assert_vec3_close_f32(x_axis, x2_axis);
500        assert_vec3_close_f32(y_axis, y2_axis);
501        assert_vec3_close_f32(z_axis, z2_axis);
502    }
503
504    #[test]
505    fn test_rot_90() {
506        let v = Vector3::<f32>::new(1.0, 0.0, 0.0);
507        let a = FRAC_PI_2;
508
509        let q = quat_axis_angle_f32(&v, a);
510        let m = q.mat3();
511
512        let x_axis = m.col[0];
513        let y_axis = m.col[1];
514        let z_axis = m.col[2];
515
516        assert_vec3_close_f32(x_axis, Vector3::new(1.0, 0.0, 0.0));
517        assert_vec3_close_f32(y_axis, Vector3::new(0.0, 0.0, 1.0));
518        assert_vec3_close_f32(z_axis, Vector3::new(0.0, -1.0, 0.0));
519
520        let m2 = mat3_axis_angle_f32(&v, a);
521
522        let x2_axis = m2.col[0];
523        let y2_axis = m2.col[1];
524        let z2_axis = m2.col[2];
525
526        assert_vec3_close_f32(x_axis, x2_axis);
527        assert_vec3_close_f32(y_axis, y2_axis);
528        assert_vec3_close_f32(z_axis, z2_axis);
529    }
530
531    #[test]
532    fn test_rot_45() {
533        let v = Vector3::<f32>::new(1.0, 0.0, 0.0);
534        let a = FRAC_PI_4;
535
536        let q = quat_axis_angle_f32(&v, a);
537        let m = q.mat3();
538
539        let x_axis = m.col[0];
540        let y_axis = m.col[1];
541        let z_axis = m.col[2];
542
543        assert_vec3_close_f32(x_axis, Vector3::new(1.0, 0.0, 0.0));
544        assert_vec3_close_f32(
545            y_axis,
546            Vector3::new(0.0, f32::sqrt(2.0) / 2.0, f32::sqrt(2.0) / 2.0),
547        );
548        assert_vec3_close_f32(
549            z_axis,
550            Vector3::new(0.0, -f32::sqrt(2.0) / 2.0, f32::sqrt(2.0) / 2.0),
551        );
552
553        let m2 = mat3_axis_angle_f32(&v, a);
554
555        let x2_axis = m2.col[0];
556        let y2_axis = m2.col[1];
557        let z2_axis = m2.col[2];
558
559        assert_vec3_close_f32(x_axis, x2_axis);
560        assert_vec3_close_f32(y_axis, y2_axis);
561        assert_vec3_close_f32(z_axis, z2_axis);
562    }
563
564    #[test]
565    fn test_rot_composed_45() {
566        let v = Vector3::<f32>::new(1.0, 0.0, 0.0);
567        let a_u = FRAC_PI_4;
568        let a_f = PI;
569
570        let q_u = quat_axis_angle_f32(&v, a_u);
571        let q_f = quat_axis_angle_f32(&v, a_f);
572
573        let m = q_f.mat3();
574
575        let x_axis = m.col[0];
576        let y_axis = m.col[1];
577        let z_axis = m.col[2];
578
579        let q_t = q_u * q_u * q_u * q_u;
580        let m2 = q_t.mat3();
581
582        let x2_axis = m2.col[0];
583        let y2_axis = m2.col[1];
584        let z2_axis = m2.col[2];
585
586        assert_vec3_close_f32(x_axis, x2_axis);
587        assert_vec3_close_f32(y_axis, y2_axis);
588        assert_vec3_close_f32(z_axis, z2_axis);
589    }
590
591    #[test]
592    fn test_rot_composed_120() {
593        let v = Vector3::<f32>::new(1.0, 0.0, 0.0);
594        let a_u1 = FRAC_PI_2;
595        let a_u2 = PI / 6.0;
596        let a_f = FRAC_PI_2 + PI / 6.0;
597
598        let q_u1 = quat_axis_angle_f32(&v, a_u1);
599        let q_u2 = quat_axis_angle_f32(&v, a_u2);
600        let q_f = quat_axis_angle_f32(&v, a_f);
601
602        let m = q_f.mat3();
603
604        let x_axis = m.col[0];
605        let y_axis = m.col[1];
606        let z_axis = m.col[2];
607
608        let q_t = q_u2 * q_u1;
609        let m2 = q_t.mat3();
610
611        let x2_axis = m2.col[0];
612        let y2_axis = m2.col[1];
613        let z2_axis = m2.col[2];
614
615        assert_vec3_close_f32(x_axis, x2_axis);
616        assert_vec3_close_f32(y_axis, y2_axis);
617        assert_vec3_close_f32(z_axis, z2_axis);
618    }
619
620    #[test]
621    fn test_rot_x_rot_y() {
622        let vx = Vector3::<f32>::new(1.0, 0.0, 0.0);
623        let vy = Vector3::<f32>::new(0.0, 1.0, 0.0);
624        let vz = Vector3::<f32>::new(0.0, 0.0, 1.0);
625
626        let v4x = Vector4::<f32>::new(1.0, 0.0, 0.0, 1.0);
627        let v4y = Vector4::<f32>::new(0.0, 1.0, 0.0, 1.0);
628        let v4z = Vector4::<f32>::new(0.0, 0.0, 1.0, 1.0);
629
630        let a = FRAC_PI_2;
631
632        let q_ux = quat_axis_angle_f32(&vx, a);
633        let q_uy = quat_axis_angle_f32(&vy, a);
634        let q_uz = quat_axis_angle_f32(&vz, a);
635
636        let mx = q_ux.mat3();
637        let m4x = q_ux.mat4();
638
639        let px = (m4x * v4x).xyz();
640        let py = (m4x * v4y).xyz();
641        let pz = (m4x * v4z).xyz();
642
643        let x_axis = mx.col[0];
644        let y_axis = mx.col[1];
645        let z_axis = mx.col[2];
646
647        assert_vec3_close_f32(x_axis, vx);
648        assert_vec3_close_f32(y_axis, vz);
649        assert_vec3_close_f32(z_axis, -vy);
650
651        assert_vec3_close_f32(px, vx);
652        assert_vec3_close_f32(py, vz);
653        assert_vec3_close_f32(pz, -vy);
654
655        // rotate x, then y
656        let q_yx = q_uy * q_ux;
657        let myx = q_yx.mat3();
658        let m4yx = q_yx.mat4();
659
660        let px = (m4yx * v4x).xyz();
661        let py = (m4yx * v4y).xyz();
662        let pz = (m4yx * v4z).xyz();
663
664        let x_axis = myx.col[0];
665        let y_axis = myx.col[1];
666        let z_axis = myx.col[2];
667
668        assert_vec3_close_f32(x_axis, -vz);
669        assert_vec3_close_f32(y_axis, vx);
670        assert_vec3_close_f32(z_axis, -vy);
671
672        assert_vec3_close_f32(px, -vz);
673        assert_vec3_close_f32(py, vx);
674        assert_vec3_close_f32(pz, -vy);
675
676        // rotate x, then y, then z
677        let q_zyx = q_uz * q_uy * q_ux;
678        let mzyx = q_zyx.mat3();
679
680        let m4zyx = q_zyx.mat4();
681
682        let px = (m4zyx * v4x).xyz();
683        let py = (m4zyx * v4y).xyz();
684        let pz = (m4zyx * v4z).xyz();
685
686        let x_axis = mzyx.col[0];
687        let y_axis = mzyx.col[1];
688        let z_axis = mzyx.col[2];
689
690        assert_vec3_close_f32(x_axis, -vz);
691        assert_vec3_close_f32(y_axis, vy);
692        assert_vec3_close_f32(z_axis, vx);
693
694        assert_vec3_close_f32(px, -vz);
695        assert_vec3_close_f32(py, vy);
696        assert_vec3_close_f32(pz, vx);
697    }
698
699    #[test]
700    fn test_axis_angle_f32() {
701        let v = Vector3::normalize(&Vector3::<f32>::new(1.0, 2.0, 3.0));
702        let q0 = quat_axis_angle_f32(&v, 3.0);
703        let (v2, a) = q0.to_axis_angle();
704        assert_vec3_close_f32(v, v2);
705        assert_scalar_close_f32(a, 3.0);
706    }
707
708    #[test]
709    fn test_axis_angle_f64() {
710        let v = Vector3::normalize(&Vector3::<f64>::new(1.0, 2.0, 3.0));
711        let q0 = quat_axis_angle_f64(&v, 3.0);
712        let (v2, a) = q0.to_axis_angle();
713        assert_vec3_close_f64(v, v2);
714        assert_scalar_close_f64(a, 3.0);
715    }
716
717    #[test]
718    fn test_axis_angle_zero_axis_none() {
719        let axis = Vector3::<f32>::new(0.0, 0.0, 0.0);
720        assert!(Quat::of_axis_angle(&axis, 1.0, EPS_F32).is_none());
721    }
722
723    #[test]
724    fn test_quaternion_normalization() {
725        // Test normalizing a non-unit quaternion
726        let q = Quat::<f32>::new(1.0, 2.0, 3.0, 4.0);
727        let nq = Quat::normalize(&q);
728        let len = nq.length();
729        assert!((len - 1.0).abs() < f32::epsilon());
730
731        // Test normalizing zero quaternion (edge case)
732        let q_zero = Quat::<f32>::new(0.0, 0.0, 0.0, 0.0);
733        let nq_zero = Quat::normalize(&q_zero);
734        assert_eq!(nq_zero.x, 0.0);
735        assert_eq!(nq_zero.y, 0.0);
736        assert_eq!(nq_zero.z, 0.0);
737        assert_eq!(nq_zero.w, 0.0);
738
739        // Test already normalized quaternion
740        let q_unit = Quat::<f32>::identity();
741        let nq_unit = Quat::normalize(&q_unit);
742        assert!((nq_unit.length() - 1.0).abs() < f32::epsilon());
743    }
744
745    #[test]
746    fn test_quaternion_inverse() {
747        let q = quat_axis_angle_f32(&Vector3::new(0.0, 1.0, 0.0), FRAC_PI_2);
748        let q_inv = Quat::inverse(&q);
749        let product = q * q_inv;
750
751        // Quaternion times its inverse should be identity
752        assert!((product.x).abs() < 0.001);
753        assert!((product.y).abs() < 0.001);
754        assert!((product.z).abs() < 0.001);
755        assert!((product.w - 1.0).abs() < 0.001);
756    }
757
758    #[test]
759    fn test_quaternion_inverse_non_unit() {
760        let q = Quat::<f32>::new(1.0, 2.0, 3.0, 4.0);
761        let q_inv = Quat::inverse(&q);
762        let product = q * q_inv;
763
764        assert!(product.x.abs() < 0.001);
765        assert!(product.y.abs() < 0.001);
766        assert!(product.z.abs() < 0.001);
767        assert!((product.w - 1.0).abs() < 0.001);
768    }
769
770    #[test]
771    fn test_quaternion_multiplication() {
772        // Test multiplication properties
773        let q1 = quat_axis_angle_f32(&Vector3::new(1.0, 0.0, 0.0), 0.5);
774        let q2 = quat_axis_angle_f32(&Vector3::new(0.0, 1.0, 0.0), 0.5);
775        let q3 = quat_axis_angle_f32(&Vector3::new(0.0, 0.0, 1.0), 0.5);
776
777        // Test associativity: (q1 * q2) * q3 == q1 * (q2 * q3)
778        let left = (q1 * q2) * q3;
779        let right = q1 * (q2 * q3);
780        assert!((left.x - right.x).abs() < f32::epsilon());
781        assert!((left.y - right.y).abs() < f32::epsilon());
782        assert!((left.z - right.z).abs() < f32::epsilon());
783        assert!((left.w - right.w).abs() < f32::epsilon());
784
785        // Test identity multiplication
786        let identity = Quat::<f32>::identity();
787        let result = q1 * identity;
788        assert!((result.x - q1.x).abs() < f32::epsilon());
789        assert!((result.y - q1.y).abs() < f32::epsilon());
790        assert!((result.z - q1.z).abs() < f32::epsilon());
791        assert!((result.w - q1.w).abs() < f32::epsilon());
792    }
793
794    #[test]
795    fn test_quaternion_addition_subtraction() {
796        let q1 = Quat::<f32>::new(1.0, 2.0, 3.0, 4.0);
797        let q2 = Quat::<f32>::new(5.0, 6.0, 7.0, 8.0);
798
799        let sum = q1 + q2;
800        assert_eq!(sum.x, 6.0);
801        assert_eq!(sum.y, 8.0);
802        assert_eq!(sum.z, 10.0);
803        assert_eq!(sum.w, 12.0);
804
805        let diff = q2 - q1;
806        assert_eq!(diff.x, 4.0);
807        assert_eq!(diff.y, 4.0);
808        assert_eq!(diff.z, 4.0);
809        assert_eq!(diff.w, 4.0);
810    }
811
812    #[test]
813    fn test_quaternion_scalar_multiplication() {
814        let q = Quat::<f32>::new(1.0, 2.0, 3.0, 4.0);
815        let scalar = 2.0;
816
817        let result1 = q * scalar;
818        assert_eq!(result1.x, 2.0);
819        assert_eq!(result1.y, 4.0);
820        assert_eq!(result1.z, 6.0);
821        assert_eq!(result1.w, 8.0);
822
823        let result2 = scalar * q;
824        assert_eq!(result2.x, 2.0);
825        assert_eq!(result2.y, 4.0);
826        assert_eq!(result2.z, 6.0);
827        assert_eq!(result2.w, 8.0);
828
829        let result3 = q / scalar;
830        assert_eq!(result3.x, 0.5);
831        assert_eq!(result3.y, 1.0);
832        assert_eq!(result3.z, 1.5);
833        assert_eq!(result3.w, 2.0);
834    }
835
836    #[test]
837    fn test_to_axis_angle_edge_cases() {
838        // Test near-zero rotation (identity quaternion)
839        let q_identity = Quat::<f32>::identity();
840        let (axis, angle) = q_identity.to_axis_angle();
841        assert!((angle).abs() < 0.001);
842        assert!((axis.x - 1.0).abs() < 0.001);
843        assert!(axis.y.abs() < 0.001);
844        assert!(axis.z.abs() < 0.001);
845
846        // Test clamped w slightly above 1 does not produce NaN
847        let q_clamped = Quat::<f32>::new(0.0, 0.0, 0.0, 1.00001);
848        let (axis_clamped, angle_clamped) = q_clamped.to_axis_angle();
849        assert!(!angle_clamped.is_nan());
850        assert!((axis_clamped.length() - 1.0).abs() < 0.01);
851        assert!(Vector3::dot(&axis_clamped, &Vector3::new(1.0, 0.0, 0.0)) > 0.99);
852
853        // Test 180-degree rotation
854        let q_180 = quat_axis_angle_f32(&Vector3::new(0.0, 1.0, 0.0), PI);
855        let (axis_180, angle_180) = q_180.to_axis_angle();
856        assert!((angle_180 - PI).abs() < 0.001);
857        assert!((axis_180.y - 1.0).abs() < 0.001);
858
859        // Test very small rotation
860        let small_angle = 0.001;
861        let q_small = quat_axis_angle_f32(&Vector3::new(1.0, 0.0, 0.0), small_angle);
862        let (axis_small, angle_small) = q_small.to_axis_angle();
863        assert!((angle_small - small_angle).abs() < 0.0001);
864        assert!((axis_small.length() - 1.0).abs() < 0.01);
865        assert!(Vector3::dot(&axis_small, &Vector3::new(1.0, 0.0, 0.0)) > 0.99);
866    }
867
868    #[test]
869    fn test_matrix_quaternion_conversion() {
870        // Test round-trip conversion: quaternion -> matrix -> quaternion
871        let angles = [0.0, 0.5, 1.0, FRAC_PI_2, PI];
872        let axes = [
873            Vector3::<f32>::new(1.0, 0.0, 0.0),
874            Vector3::<f32>::new(0.0, 1.0, 0.0),
875            Vector3::<f32>::new(0.0, 0.0, 1.0),
876            Vector3::<f32>::normalize(&Vector3::new(1.0, 1.0, 1.0)),
877        ];
878
879        for angle in &angles {
880            for axis in &axes {
881                let q1 = quat_axis_angle_f32(axis, *angle);
882                let mat = q1.mat3();
883                let q2 = Quat::of_matrix3(&mat);
884
885                // Account for quaternion double cover (q and -q represent same rotation)
886                let dot_product = Quat::dot(&q1, &q2);
887                let q2_adjusted = if dot_product < 0.0 {
888                    Quat::neg(&q2)
889                } else {
890                    q2
891                };
892
893                assert!((q1.x - q2_adjusted.x).abs() < 0.001);
894                assert!((q1.y - q2_adjusted.y).abs() < 0.001);
895                assert!((q1.z - q2_adjusted.z).abs() < 0.001);
896                assert!((q1.w - q2_adjusted.w).abs() < 0.001);
897            }
898        }
899    }
900
901    #[test]
902    fn test_quaternion_matrix_roundtrip() {
903        let q = quat_axis_angle_f32(&Vector3::new(0.0, 1.0, 0.0), 1.234);
904        let mat = q.mat3();
905        let q2 = Quat::of_matrix3(&mat);
906        let dot = Quat::dot(&q, &q2);
907        let q2 = if dot < 0.0 { Quat::neg(&q2) } else { q2 };
908
909        assert!((q.x - q2.x).abs() < 0.001);
910        assert!((q.y - q2.y).abs() < 0.001);
911        assert!((q.z - q2.z).abs() < 0.001);
912        assert!((q.w - q2.w).abs() < 0.001);
913    }
914
915    #[test]
916    fn test_matrix4_quaternion_roundtrip() {
917        let q = quat_axis_angle_f32(&Vector3::new(0.0, 0.0, 1.0), 0.75);
918        let mat4 = q.mat4();
919        let q2 = Quat::of_matrix4(&mat4);
920        let dot = Quat::dot(&q, &q2);
921        let q2 = if dot < 0.0 { Quat::neg(&q2) } else { q2 };
922
923        assert!((q.x - q2.x).abs() < 0.001);
924        assert!((q.y - q2.y).abs() < 0.001);
925        assert!((q.z - q2.z).abs() < 0.001);
926        assert!((q.w - q2.w).abs() < 0.001);
927    }
928
929    #[test]
930    fn test_quaternion_conjugate() {
931        let q = Quat::<f32>::new(1.0, 2.0, 3.0, 4.0);
932        let conj = Quat::conjugate(&q);
933
934        assert_eq!(conj.x, -1.0);
935        assert_eq!(conj.y, -2.0);
936        assert_eq!(conj.z, -3.0);
937        assert_eq!(conj.w, 4.0);
938
939        // Test that conjugate of conjugate is original
940        let conj_conj = Quat::conjugate(&conj);
941        assert_eq!(conj_conj.x, q.x);
942        assert_eq!(conj_conj.y, q.y);
943        assert_eq!(conj_conj.z, q.z);
944        assert_eq!(conj_conj.w, q.w);
945    }
946
947    #[test]
948    fn test_quaternion_dot_product() {
949        let q1 = Quat::<f32>::new(1.0, 2.0, 3.0, 4.0);
950        let q2 = Quat::<f32>::new(5.0, 6.0, 7.0, 8.0);
951
952        let dot = Quat::dot(&q1, &q2);
953        let expected = 1.0 * 5.0 + 2.0 * 6.0 + 3.0 * 7.0 + 4.0 * 8.0;
954        assert_eq!(dot, expected);
955
956        // Test dot product with itself equals length squared
957        let self_dot = Quat::dot(&q1, &q1);
958        let length_squared = q1.length() * q1.length();
959        assert!((self_dot - length_squared).abs() < 0.0001);
960    }
961}