Skip to main content

goud_engine/ecs/components/transform/
quat.rs

1//! Quaternion type for representing 3D rotations.
2
3use crate::core::math::{Quaternion, Vec3};
4use cgmath::InnerSpace;
5use std::f32::consts::PI;
6
7/// A quaternion for representing rotations.
8///
9/// Stored as (x, y, z, w) where:
10/// - (x, y, z) is the vector part (imaginary components)
11/// - w is the scalar part (real component)
12///
13/// The identity rotation is (0, 0, 0, 1).
14#[repr(C)]
15#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
16pub struct Quat {
17    /// The x-component of the vector part.
18    pub x: f32,
19    /// The y-component of the vector part.
20    pub y: f32,
21    /// The z-component of the vector part.
22    pub z: f32,
23    /// The scalar (real) part.
24    pub w: f32,
25}
26
27impl Quat {
28    /// Identity quaternion representing no rotation.
29    pub const IDENTITY: Quat = Quat {
30        x: 0.0,
31        y: 0.0,
32        z: 0.0,
33        w: 1.0,
34    };
35
36    /// Creates a new quaternion from components.
37    #[inline]
38    pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
39        Self { x, y, z, w }
40    }
41
42    /// Creates a quaternion from axis-angle representation.
43    ///
44    /// # Arguments
45    ///
46    /// * `axis` - The normalized rotation axis
47    /// * `angle` - The rotation angle in radians
48    ///
49    /// # Example
50    ///
51    /// ```
52    /// use goud_engine::ecs::components::transform::Quat;
53    /// use goud_engine::core::math::Vec3;
54    /// use std::f32::consts::PI;
55    ///
56    /// // Rotate 90 degrees around Y axis
57    /// let q = Quat::from_axis_angle(Vec3::unit_y(), PI / 2.0);
58    /// ```
59    #[inline]
60    pub fn from_axis_angle(axis: Vec3, angle: f32) -> Self {
61        let half_angle = angle * 0.5;
62        let s = half_angle.sin();
63        let c = half_angle.cos();
64        Self {
65            x: axis.x * s,
66            y: axis.y * s,
67            z: axis.z * s,
68            w: c,
69        }
70    }
71
72    /// Creates a quaternion from Euler angles (in radians).
73    ///
74    /// Uses XYZ rotation order (pitch, yaw, roll):
75    /// - X: pitch (rotation around right axis)
76    /// - Y: yaw (rotation around up axis)
77    /// - Z: roll (rotation around forward axis)
78    ///
79    /// # Example
80    ///
81    /// ```
82    /// use goud_engine::ecs::components::transform::Quat;
83    /// use std::f32::consts::PI;
84    ///
85    /// // 45 degree yaw rotation
86    /// let q = Quat::from_euler(0.0, PI / 4.0, 0.0);
87    /// ```
88    #[inline]
89    pub fn from_euler(pitch: f32, yaw: f32, roll: f32) -> Self {
90        // Using XYZ convention
91        let (sx, cx) = (pitch * 0.5).sin_cos();
92        let (sy, cy) = (yaw * 0.5).sin_cos();
93        let (sz, cz) = (roll * 0.5).sin_cos();
94
95        Self {
96            x: sx * cy * cz + cx * sy * sz,
97            y: cx * sy * cz - sx * cy * sz,
98            z: cx * cy * sz + sx * sy * cz,
99            w: cx * cy * cz - sx * sy * sz,
100        }
101    }
102
103    /// Creates a quaternion that rotates from one direction to another.
104    ///
105    /// Both vectors should be normalized.
106    #[inline]
107    pub fn from_rotation_arc(from: Vec3, to: Vec3) -> Self {
108        let from_cg: cgmath::Vector3<f32> = from.into();
109        let to_cg: cgmath::Vector3<f32> = to.into();
110
111        // Handle edge case where vectors are opposite
112        let dot = from_cg.dot(to_cg);
113        if dot < -0.999999 {
114            // Vectors are opposite, rotate 180 degrees around any perpendicular axis
115            let mut axis = cgmath::Vector3::unit_x().cross(from_cg);
116            if axis.magnitude2() < 0.000001 {
117                axis = cgmath::Vector3::unit_y().cross(from_cg);
118            }
119            axis = axis.normalize();
120            return Self::from_axis_angle(Vec3::from(axis), PI);
121        }
122
123        let cross = from_cg.cross(to_cg);
124        Self {
125            x: cross.x,
126            y: cross.y,
127            z: cross.z,
128            w: 1.0 + dot,
129        }
130        .normalize()
131    }
132
133    /// Returns the squared length of the quaternion.
134    #[inline]
135    pub fn length_squared(self) -> f32 {
136        self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w
137    }
138
139    /// Returns the length of the quaternion.
140    #[inline]
141    pub fn length(self) -> f32 {
142        self.length_squared().sqrt()
143    }
144
145    /// Returns a normalized version of this quaternion.
146    #[inline]
147    pub fn normalize(self) -> Self {
148        let len = self.length();
149        if len == 0.0 {
150            Self::IDENTITY
151        } else {
152            Self {
153                x: self.x / len,
154                y: self.y / len,
155                z: self.z / len,
156                w: self.w / len,
157            }
158        }
159    }
160
161    /// Returns the conjugate of this quaternion.
162    ///
163    /// For a unit quaternion, the conjugate is also the inverse.
164    #[inline]
165    pub fn conjugate(self) -> Self {
166        Self {
167            x: -self.x,
168            y: -self.y,
169            z: -self.z,
170            w: self.w,
171        }
172    }
173
174    /// Returns the inverse of this quaternion.
175    ///
176    /// For unit quaternions (normalized), this is equivalent to the conjugate.
177    #[inline]
178    pub fn inverse(self) -> Self {
179        let len_sq = self.length_squared();
180        if len_sq == 0.0 {
181            Self::IDENTITY
182        } else {
183            let conj = self.conjugate();
184            Self {
185                x: conj.x / len_sq,
186                y: conj.y / len_sq,
187                z: conj.z / len_sq,
188                w: conj.w / len_sq,
189            }
190        }
191    }
192
193    /// Multiplies two quaternions (combines rotations).
194    ///
195    /// This represents combining two rotations, where `self` is applied first,
196    /// then `other`. This is also available via the `*` operator.
197    #[inline]
198    pub fn multiply(self, other: Self) -> Self {
199        self * other
200    }
201
202    /// Rotates a vector by this quaternion.
203    #[inline]
204    pub fn rotate_vector(self, v: Vec3) -> Vec3 {
205        // q * v * q^-1, optimized formula
206        let qv = Vec3::new(self.x, self.y, self.z);
207        let uv = qv.cross(v);
208        let uuv = qv.cross(uv);
209        v + (uv * self.w + uuv) * 2.0
210    }
211
212    /// Spherical linear interpolation between two quaternions.
213    ///
214    /// This provides smooth interpolation between rotations.
215    #[inline]
216    pub fn slerp(self, other: Self, t: f32) -> Self {
217        let dot = self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w;
218
219        // If the dot product is negative, negate one quaternion to take shorter path
220        let (other, dot) = if dot < 0.0 {
221            (
222                Self {
223                    x: -other.x,
224                    y: -other.y,
225                    z: -other.z,
226                    w: -other.w,
227                },
228                -dot,
229            )
230        } else {
231            (other, dot)
232        };
233
234        // If quaternions are very close, use linear interpolation
235        if dot > 0.9995 {
236            return Self {
237                x: self.x + t * (other.x - self.x),
238                y: self.y + t * (other.y - self.y),
239                z: self.z + t * (other.z - self.z),
240                w: self.w + t * (other.w - self.w),
241            }
242            .normalize();
243        }
244
245        let theta_0 = dot.acos();
246        let theta = theta_0 * t;
247        let sin_theta = theta.sin();
248        let sin_theta_0 = theta_0.sin();
249
250        let s0 = (theta_0 - theta).cos() - dot * sin_theta / sin_theta_0;
251        let s1 = sin_theta / sin_theta_0;
252
253        Self {
254            x: self.x * s0 + other.x * s1,
255            y: self.y * s0 + other.y * s1,
256            z: self.z * s0 + other.z * s1,
257            w: self.w * s0 + other.w * s1,
258        }
259    }
260
261    /// Converts the quaternion to Euler angles (in radians).
262    ///
263    /// Returns (pitch, yaw, roll) using XYZ convention.
264    #[inline]
265    pub fn to_euler(self) -> (f32, f32, f32) {
266        // Roll (x-axis rotation)
267        let sinr_cosp = 2.0 * (self.w * self.x + self.y * self.z);
268        let cosr_cosp = 1.0 - 2.0 * (self.x * self.x + self.y * self.y);
269        let roll = sinr_cosp.atan2(cosr_cosp);
270
271        // Pitch (y-axis rotation)
272        let sinp = 2.0 * (self.w * self.y - self.z * self.x);
273        let pitch = if sinp.abs() >= 1.0 {
274            (PI / 2.0).copysign(sinp)
275        } else {
276            sinp.asin()
277        };
278
279        // Yaw (z-axis rotation)
280        let siny_cosp = 2.0 * (self.w * self.z + self.x * self.y);
281        let cosy_cosp = 1.0 - 2.0 * (self.y * self.y + self.z * self.z);
282        let yaw = siny_cosp.atan2(cosy_cosp);
283
284        (pitch, yaw, roll)
285    }
286
287    /// Returns the forward direction vector (negative Z axis after rotation).
288    #[inline]
289    pub fn forward(self) -> Vec3 {
290        self.rotate_vector(Vec3::new(0.0, 0.0, -1.0))
291    }
292
293    /// Returns the right direction vector (positive X axis after rotation).
294    #[inline]
295    pub fn right(self) -> Vec3 {
296        self.rotate_vector(Vec3::new(1.0, 0.0, 0.0))
297    }
298
299    /// Returns the up direction vector (positive Y axis after rotation).
300    #[inline]
301    pub fn up(self) -> Vec3 {
302        self.rotate_vector(Vec3::new(0.0, 1.0, 0.0))
303    }
304}
305
306impl Default for Quat {
307    #[inline]
308    fn default() -> Self {
309        Self::IDENTITY
310    }
311}
312
313// Implement Mul trait for quaternion multiplication
314impl std::ops::Mul for Quat {
315    type Output = Self;
316
317    /// Multiplies two quaternions, combining their rotations.
318    ///
319    /// `self * other` applies `self` first, then `other`.
320    #[inline]
321    fn mul(self, other: Self) -> Self {
322        Self {
323            x: self.w * other.x + self.x * other.w + self.y * other.z - self.z * other.y,
324            y: self.w * other.y - self.x * other.z + self.y * other.w + self.z * other.x,
325            z: self.w * other.z + self.x * other.y - self.y * other.x + self.z * other.w,
326            w: self.w * other.w - self.x * other.x - self.y * other.y - self.z * other.z,
327        }
328    }
329}
330
331// Conversion to/from cgmath quaternion
332impl From<Quaternion<f32>> for Quat {
333    #[inline]
334    fn from(q: Quaternion<f32>) -> Self {
335        Self {
336            x: q.v.x,
337            y: q.v.y,
338            z: q.v.z,
339            w: q.s,
340        }
341    }
342}
343
344impl From<Quat> for Quaternion<f32> {
345    #[inline]
346    fn from(q: Quat) -> Self {
347        Quaternion::new(q.w, q.x, q.y, q.z)
348    }
349}