aboutsummaryrefslogtreecommitdiff
path: root/src/core/traits/quaternion.rs
blob: 8a3836c8621e38c333454b68b8c6039723f4488b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use crate::core::{
    storage::XYZ,
    traits::{
        scalar::{FloatEx, NumEx},
        vector::*,
    },
};

pub trait Quaternion<T: FloatEx>: FloatVector4<T> {
    type SIMDVector3;

    #[inline]
    fn from_axis_angle(axis: XYZ<T>, angle: T) -> Self {
        glam_assert!(FloatVector3::is_normalized(axis));
        let (s, c) = (angle * T::HALF).sin_cos();
        let v = axis.mul_scalar(s);
        Self::new(v.x, v.y, v.z, c)
    }

    #[inline]
    fn from_rotation_x(angle: T) -> Self {
        let (s, c) = (angle * T::HALF).sin_cos();
        Self::new(s, T::ZERO, T::ZERO, c)
    }

    #[inline]
    fn from_rotation_y(angle: T) -> Self {
        let (s, c) = (angle * T::HALF).sin_cos();
        Self::new(T::ZERO, s, T::ZERO, c)
    }

    #[inline]
    fn from_rotation_z(angle: T) -> Self {
        let (s, c) = (angle * T::HALF).sin_cos();
        Self::new(T::ZERO, T::ZERO, s, c)
    }

    /// From the columns of a 3x3 rotation matrix.
    #[inline]
    fn from_rotation_axes(x_axis: XYZ<T>, y_axis: XYZ<T>, z_axis: XYZ<T>) -> Self {
        // Based on https://github.com/microsoft/DirectXMath `XM$quaternionRotationMatrix`
        // TODO: sse2 version
        let (m00, m01, m02) = x_axis.into_tuple();
        let (m10, m11, m12) = y_axis.into_tuple();
        let (m20, m21, m22) = z_axis.into_tuple();
        if m22 <= T::ZERO {
            // x^2 + y^2 >= z^2 + w^2
            let dif10 = m11 - m00;
            let omm22 = T::ONE - m22;
            if dif10 <= T::ZERO {
                // x^2 >= y^2
                let four_xsq = omm22 - dif10;
                let inv4x = T::HALF / four_xsq.sqrt();
                Self::new(
                    four_xsq * inv4x,
                    (m01 + m10) * inv4x,
                    (m02 + m20) * inv4x,
                    (m12 - m21) * inv4x,
                )
            } else {
                // y^2 >= x^2
                let four_ysq = omm22 + dif10;
                let inv4y = T::HALF / four_ysq.sqrt();
                Self::new(
                    (m01 + m10) * inv4y,
                    four_ysq * inv4y,
                    (m12 + m21) * inv4y,
                    (m20 - m02) * inv4y,
                )
            }
        } else {
            // z^2 + w^2 >= x^2 + y^2
            let sum10 = m11 + m00;
            let opm22 = T::ONE + m22;
            if sum10 <= T::ZERO {
                // z^2 >= w^2
                let four_zsq = opm22 - sum10;
                let inv4z = T::HALF / four_zsq.sqrt();
                Self::new(
                    (m02 + m20) * inv4z,
                    (m12 + m21) * inv4z,
                    four_zsq * inv4z,
                    (m01 - m10) * inv4z,
                )
            } else {
                // w^2 >= z^2
                let four_wsq = opm22 + sum10;
                let inv4w = T::HALF / four_wsq.sqrt();
                Self::new(
                    (m12 - m21) * inv4w,
                    (m20 - m02) * inv4w,
                    (m01 - m10) * inv4w,
                    four_wsq * inv4w,
                )
            }
        }
    }

    fn to_axis_angle(self) -> (XYZ<T>, T) {
        // const EPSILON: f32 = 1.0e-8;
        // const EPSILON_SQUARED: f32 = EPSILON * EPSILON;
        let (x, y, z, w) = Vector4::into_tuple(self);
        let angle = w.acos_approx() * T::TWO;
        let scale_sq = NumEx::max(T::ONE - w * w, T::ZERO);
        // TODO: constants for epslions?
        if scale_sq >= T::from_f32(1.0e-8 * 1.0e-8) {
            (XYZ { x, y, z }.mul_scalar(scale_sq.sqrt().recip()), angle)
        } else {
            (Vector3Const::X, angle)
        }
    }

    #[inline]
    fn is_near_identity(self) -> bool {
        // Based on https://github.com/nfrechette/rtm `rtm::quat_near_identity`
        let threshold_angle = T::from_f64(0.002_847_144_6);
        // Because of floating point precision, we cannot represent very small rotations.
        // The closest f32 to 1.0 that is not 1.0 itself yields:
        // 0.99999994.acos() * 2.0  = 0.000690533954 rad
        //
        // An error threshold of 1.e-6 is used by default.
        // (1.0 - 1.e-6).acos() * 2.0 = 0.00284714461 rad
        // (1.0 - 1.e-7).acos() * 2.0 = 0.00097656250 rad
        //
        // We don't really care about the angle value itself, only if it's close to 0.
        // This will happen whenever quat.w is close to 1.0.
        // If the quat.w is close to -1.0, the angle will be near 2*PI which is close to
        // a negative 0 rotation. By forcing quat.w to be positive, we'll end up with
        // the shortest path.
        let positive_w_angle = self.as_ref_xyzw().w.abs().acos_approx() * T::TWO;
        positive_w_angle < threshold_angle
    }

    fn conjugate(self) -> Self;
    fn lerp(self, end: Self, s: T) -> Self;
    fn slerp(self, end: Self, s: T) -> Self;
    fn mul_quaternion(self, other: Self) -> Self;
    fn mul_vector3(self, other: XYZ<T>) -> XYZ<T>;
    fn mul_float4_as_vector3(self, other: Self::SIMDVector3) -> Self::SIMDVector3;
}