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