Skip to main content

gemath/
mat4.rs

1use crate::vec3::Vec3;
2use crate::vec4::{Vec4, Meters, Pixels, World, Local, Screen};
3use core::ops::{Add, Mul, Sub};
4use core::marker::PhantomData;
5use crate::math;
6#[cfg(feature = "unit_vec")]
7use crate::unit_vec::UnitVec3;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct Mat4<Unit: Copy = (), Space: Copy = ()> {
12    pub x_col: Vec4<Unit, Space>,
13    pub y_col: Vec4<Unit, Space>,
14    pub z_col: Vec4<Unit, Space>,
15    pub w_col: Vec4<Unit, Space>,
16    #[cfg_attr(feature = "serde", serde(skip))]
17    pub _unit: PhantomData<Unit>,
18    #[cfg_attr(feature = "serde", serde(skip))]
19    pub _space: PhantomData<Space>,
20}
21
22// Type aliases for common units and spaces
23pub type Mat4f32 = Mat4<(),()>;
24pub type Mat4Meters = Mat4<Meters,()>;
25pub type Mat4Pixels = Mat4<Pixels,()>;
26pub type Mat4World = Mat4<(),World>;
27pub type Mat4Local = Mat4<(),Local>;
28pub type Mat4Screen = Mat4<(),Screen>;
29pub type Mat4MetersWorld = Mat4<Meters,World>;
30pub type Mat4PixelsScreen = Mat4<Pixels,Screen>;
31
32impl<Unit: Copy, Space: Copy> Mat4<Unit, Space> {
33    pub const ZERO: Self = Self {
34        x_col: Vec4::ZERO,
35        y_col: Vec4::ZERO,
36        z_col: Vec4::ZERO,
37        w_col: Vec4::ZERO,
38        _unit: PhantomData,
39        _space: PhantomData,
40    };
41
42    pub const IDENTITY: Self = Self {
43        x_col: Vec4::new(1.0, 0.0, 0.0, 0.0),
44        y_col: Vec4::new(0.0, 1.0, 0.0, 0.0),
45        z_col: Vec4::new(0.0, 0.0, 1.0, 0.0),
46        w_col: Vec4::new(0.0, 0.0, 0.0, 1.0),
47        _unit: PhantomData,
48        _space: PhantomData,
49    };
50
51    #[inline]
52    pub const fn new(x_col: Vec4<Unit, Space>, y_col: Vec4<Unit, Space>, z_col: Vec4<Unit, Space>, w_col: Vec4<Unit, Space>) -> Self {
53        Self { x_col, y_col, z_col, w_col, _unit: PhantomData, _space: PhantomData }
54    }
55
56    /// Creates a matrix from individual elements, assuming column-major order.
57    #[inline]
58    pub const fn from_cols_array(data: &[f32; 16]) -> Self {
59        Self {
60            x_col: Vec4::new(data[0], data[1], data[2], data[3]),
61            y_col: Vec4::new(data[4], data[5], data[6], data[7]),
62            z_col: Vec4::new(data[8], data[9], data[10], data[11]),
63            w_col: Vec4::new(data[12], data[13], data[14], data[15]),
64            _unit: PhantomData,
65            _space: PhantomData,
66        }
67    }
68
69    /// Creates a matrix from individual elements, assuming row-major order for input.
70    #[inline]
71    pub const fn from_rows(
72        r0c0: f32,
73        r0c1: f32,
74        r0c2: f32,
75        r0c3: f32,
76        r1c0: f32,
77        r1c1: f32,
78        r1c2: f32,
79        r1c3: f32,
80        r2c0: f32,
81        r2c1: f32,
82        r2c2: f32,
83        r2c3: f32,
84        r3c0: f32,
85        r3c1: f32,
86        r3c2: f32,
87        r3c3: f32,
88    ) -> Self {
89        Self {
90            x_col: Vec4::new(r0c0, r1c0, r2c0, r3c0),
91            y_col: Vec4::new(r0c1, r1c1, r2c1, r3c1),
92            z_col: Vec4::new(r0c2, r1c2, r2c2, r3c2),
93            w_col: Vec4::new(r0c3, r1c3, r2c3, r3c3),
94            _unit: PhantomData,
95            _space: PhantomData,
96        }
97    }
98
99    /// Creates a translation matrix.
100    #[inline]
101    pub const fn from_translation(v: Vec3) -> Self {
102        Self {
103            x_col: Vec4::new(1.0, 0.0, 0.0, 0.0),
104            y_col: Vec4::new(0.0, 1.0, 0.0, 0.0),
105            z_col: Vec4::new(0.0, 0.0, 1.0, 0.0),
106            w_col: Vec4::new(v.x, v.y, v.z, 1.0),
107            _unit: PhantomData,
108            _space: PhantomData,
109        }
110    }
111
112    /// Creates a rotation matrix from an axis and an angle (typed radians).
113    #[inline]
114    pub fn from_axis_angle_radians(axis: Vec3, angle: crate::angle::Radians) -> Self {
115        let c = math::cos(angle.0);
116        let s = math::sin(angle.0);
117
118        let axis_norm = axis.normalize(); // Corresponds to `axis` after gb_vec3_norm in C++
119        let t = axis_norm * (1.0 - c); // Corresponds to `t` in C++
120
121        // Reminder: Self is column-major: x_col, y_col, z_col, w_col
122        // C++ `rot[col_idx][row_idx]`
123        // rot[0][0] -> x_col.x
124        // rot[0][1] -> x_col.y
125        // rot[1][0] -> y_col.x
126
127        let r0c0 = c + t.x * axis_norm.x;
128        let r1c0 = t.y * axis_norm.x - s * axis_norm.z;
129        let r2c0 = t.z * axis_norm.x + s * axis_norm.y;
130
131        let r0c1 = t.x * axis_norm.y + s * axis_norm.z;
132        let r1c1 = c + t.y * axis_norm.y;
133        let r2c1 = t.z * axis_norm.y - s * axis_norm.x;
134
135        let r0c2 = t.x * axis_norm.z - s * axis_norm.y;
136        let r1c2 = t.y * axis_norm.z + s * axis_norm.x;
137        let r2c2 = c + t.z * axis_norm.z;
138
139        Self {
140            x_col: Vec4::new(r0c0, r1c0, r2c0, 0.0),
141            y_col: Vec4::new(r0c1, r1c1, r2c1, 0.0),
142            z_col: Vec4::new(r0c2, r1c2, r2c2, 0.0),
143            w_col: Vec4::new(0.0, 0.0, 0.0, 1.0),
144            _unit: PhantomData,
145            _space: PhantomData,
146        }
147    }
148
149    /// Creates a rotation matrix from an axis and an angle (typed degrees).
150    #[inline]
151    pub fn from_axis_angle_deg(axis: Vec3, angle: crate::angle::Degrees) -> Self {
152        Self::from_axis_angle_radians(axis, angle.to_radians())
153    }
154
155    /// Creates a rotation matrix from a **unit** axis and a typed radians angle.
156    #[inline]
157    #[cfg(feature = "unit_vec")]
158    pub fn from_unit_axis_angle_radians(axis: UnitVec3<Unit, Space>, angle: crate::angle::Radians) -> Self {
159        // Build through quaternion to keep rotation convention consistent.
160        Self::from_quat(crate::quat::Quat::from_unit_axis_angle_radians(axis, angle))
161    }
162
163    /// Creates a rotation matrix from a **unit** axis and a typed degrees angle.
164    #[inline]
165    #[cfg(feature = "unit_vec")]
166    pub fn from_unit_axis_angle_deg(axis: UnitVec3<Unit, Space>, angle: crate::angle::Degrees) -> Self {
167        Self::from_unit_axis_angle_radians(axis, angle.to_radians())
168    }
169
170    /// Creates a rotation matrix around the X axis (typed radians).
171    ///
172    /// This matches the crate's quaternion rotation convention:
173    /// `Mat4::from_rotation_x_radians(a) == Mat4::from_quat(Quat::from_axis_angle_radians(X, a))`.
174    #[inline]
175    pub fn from_rotation_x_radians(angle: crate::angle::Radians) -> Self {
176        Self::from_quat(crate::quat::Quat::from_axis_angle_radians(Vec3::new(1.0, 0.0, 0.0), angle))
177    }
178
179    /// Creates a rotation matrix around the X axis (typed degrees).
180    #[inline]
181    pub fn from_rotation_x_deg(angle: crate::angle::Degrees) -> Self {
182        Self::from_rotation_x_radians(angle.to_radians())
183    }
184
185    /// Creates a rotation matrix around the Y axis (typed radians).
186    ///
187    /// This matches the crate's quaternion rotation convention:
188    /// `Mat4::from_rotation_y_radians(a) == Mat4::from_quat(Quat::from_axis_angle_radians(Y, a))`.
189    #[inline]
190    pub fn from_rotation_y_radians(angle: crate::angle::Radians) -> Self {
191        Self::from_quat(crate::quat::Quat::from_axis_angle_radians(Vec3::new(0.0, 1.0, 0.0), angle))
192    }
193
194    /// Creates a rotation matrix around the Y axis (typed degrees).
195    #[inline]
196    pub fn from_rotation_y_deg(angle: crate::angle::Degrees) -> Self {
197        Self::from_rotation_y_radians(angle.to_radians())
198    }
199
200    /// Creates a rotation matrix around the Z axis (typed radians).
201    ///
202    /// This matches the crate's quaternion rotation convention:
203    /// `Mat4::from_rotation_z_radians(a) == Mat4::from_quat(Quat::from_axis_angle_radians(Z, a))`.
204    #[inline]
205    pub fn from_rotation_z_radians(angle: crate::angle::Radians) -> Self {
206        Self::from_quat(crate::quat::Quat::from_axis_angle_radians(Vec3::new(0.0, 0.0, 1.0), angle))
207    }
208
209    /// Creates a rotation matrix around the Z axis (typed degrees).
210    #[inline]
211    pub fn from_rotation_z_deg(angle: crate::angle::Degrees) -> Self {
212        Self::from_rotation_z_radians(angle.to_radians())
213    }
214
215    /// Creates a scale matrix.
216    #[inline]
217    pub const fn from_scale(v: Vec3) -> Self {
218        Self {
219            x_col: Vec4::new(v.x, 0.0, 0.0, 0.0),
220            y_col: Vec4::new(0.0, v.y, 0.0, 0.0),
221            z_col: Vec4::new(0.0, 0.0, v.z, 0.0),
222            w_col: Vec4::new(0.0, 0.0, 0.0, 1.0),
223            _unit: PhantomData,
224            _space: PhantomData,
225        }
226    }
227
228    /// Returns the transpose of the matrix.
229    #[inline]
230    pub const fn transpose(&self) -> Self {
231        Self {
232            x_col: Vec4::new(self.x_col.x, self.y_col.x, self.z_col.x, self.w_col.x),
233            y_col: Vec4::new(self.x_col.y, self.y_col.y, self.z_col.y, self.w_col.y),
234            z_col: Vec4::new(self.x_col.z, self.y_col.z, self.z_col.z, self.w_col.z),
235            w_col: Vec4::new(self.x_col.w, self.y_col.w, self.z_col.w, self.w_col.w),
236            _unit: PhantomData,
237            _space: PhantomData,
238        }
239    }
240
241    /// Calculates the inverse of the matrix.
242    /// Returns `None` if the matrix is not invertible (determinant is close to zero).
243    /// The implementation is a direct translation of the C++ gb_mat4_inverse function.
244    #[inline]
245    pub fn inverse(&self) -> Option<Self> {
246        let m = self; // for brevity, matching C++ style where `m` is `in`
247
248        let sf00 = m.z_col.z * m.w_col.w - m.w_col.z * m.z_col.w;
249        let sf01 = m.z_col.y * m.w_col.w - m.w_col.y * m.z_col.w;
250        let sf02 = m.z_col.y * m.w_col.z - m.w_col.y * m.z_col.z;
251        let sf03 = m.z_col.x * m.w_col.w - m.w_col.x * m.z_col.w;
252        let sf04 = m.z_col.x * m.w_col.z - m.w_col.x * m.z_col.z;
253        let sf05 = m.z_col.x * m.w_col.y - m.w_col.x * m.z_col.y; // Used in inverse
254
255        let sf06 = m.y_col.z * m.w_col.w - m.w_col.z * m.y_col.w;
256        let sf07 = m.y_col.y * m.w_col.w - m.w_col.y * m.y_col.w;
257        let sf08 = m.y_col.y * m.w_col.z - m.w_col.y * m.y_col.z;
258        let sf09 = m.y_col.x * m.w_col.w - m.w_col.x * m.y_col.w;
259        let sf10 = m.y_col.x * m.w_col.z - m.w_col.x * m.y_col.z;
260        let sf11 = m.y_col.y * m.w_col.w - m.w_col.y * m.y_col.w; // Same as sf07, used in inverse
261        let sf12 = m.y_col.x * m.w_col.y - m.w_col.x * m.y_col.y;
262
263        let sf13 = m.y_col.z * m.z_col.w - m.z_col.z * m.y_col.w;
264        let sf14 = m.y_col.y * m.z_col.w - m.z_col.y * m.y_col.w;
265        let sf15 = m.y_col.y * m.z_col.z - m.z_col.y * m.y_col.z;
266        let sf16 = m.y_col.x * m.z_col.w - m.z_col.x * m.y_col.w;
267        let sf17 = m.y_col.x * m.z_col.z - m.z_col.x * m.y_col.z;
268        let sf18 = m.y_col.x * m.z_col.y - m.z_col.x * m.y_col.y;
269
270        let mut o = Self::ZERO; // Output matrix, initialized to zero
271
272        o.x_col.x = m.y_col.y * sf00 - m.y_col.z * sf01 + m.y_col.w * sf02;
273        o.y_col.x = -m.y_col.x * sf00 + m.y_col.z * sf03 - m.y_col.w * sf04;
274        o.z_col.x = m.y_col.x * sf01 - m.y_col.y * sf03 + m.y_col.w * sf05;
275        o.w_col.x = -m.y_col.x * sf02 + m.y_col.y * sf04 - m.y_col.z * sf05;
276
277        o.x_col.y = -m.x_col.y * sf00 + m.x_col.z * sf01 - m.x_col.w * sf02;
278        o.y_col.y = m.x_col.x * sf00 - m.x_col.z * sf03 + m.x_col.w * sf04;
279        o.z_col.y = -m.x_col.x * sf01 + m.x_col.y * sf03 - m.x_col.w * sf05;
280        o.w_col.y = m.x_col.x * sf02 - m.x_col.y * sf04 + m.x_col.z * sf05;
281
282        o.x_col.z = m.x_col.y * sf06 - m.x_col.z * sf07 + m.x_col.w * sf08;
283        o.y_col.z = -m.x_col.x * sf06 + m.x_col.z * sf09 - m.x_col.w * sf10;
284        o.z_col.z = m.x_col.x * sf11 - m.x_col.y * sf09 + m.x_col.w * sf12;
285        o.w_col.z = -m.x_col.x * sf08 + m.x_col.y * sf10 - m.x_col.z * sf12;
286
287        o.x_col.w = -m.x_col.y * sf13 + m.x_col.z * sf14 - m.x_col.w * sf15;
288        o.y_col.w = m.x_col.x * sf13 - m.x_col.z * sf16 + m.x_col.w * sf17;
289        o.z_col.w = -m.x_col.x * sf14 + m.x_col.y * sf16 - m.x_col.w * sf18;
290        o.w_col.w = m.x_col.x * sf15 - m.x_col.y * sf17 + m.x_col.z * sf18;
291
292        let det_check = m.x_col.x * o.x_col.x
293            + m.x_col.y * o.y_col.x
294            + m.x_col.z * o.z_col.x
295            + m.x_col.w * o.w_col.x;
296
297        if det_check.abs() < 1e-7 {
298            // EPSILON
299            return None;
300        }
301
302        let ood = 1.0 / det_check;
303
304        o.x_col = o.x_col * ood;
305        o.y_col = o.y_col * ood;
306        o.z_col = o.z_col * ood;
307        o.w_col = o.w_col * ood;
308
309        Some(o)
310    }
311
312    /// Alias for [`Mat4::inverse`].
313    #[inline]
314    pub fn try_inverse(&self) -> Option<Self> {
315        self.inverse()
316    }
317
318    /// Calculates the determinant of the matrix.
319    #[inline]
320    pub fn determinant(&self) -> f32 {
321        let m = self;
322        let m00 = m.x_col.x;
323        let m01 = m.y_col.x;
324        let m02 = m.z_col.x;
325        let m03 = m.w_col.x;
326        let m10 = m.x_col.y;
327        let m11 = m.y_col.y;
328        let m12 = m.z_col.y;
329        let m13 = m.w_col.y;
330        let m20 = m.x_col.z;
331        let m21 = m.y_col.z;
332        let m22 = m.z_col.z;
333        let m23 = m.w_col.z;
334        let m30 = m.x_col.w;
335        let m31 = m.y_col.w;
336        let m32 = m.z_col.w;
337        let m33 = m.w_col.w;
338
339        #[inline]
340        fn det3(
341            a00: f32, a01: f32, a02: f32,
342            a10: f32, a11: f32, a12: f32,
343            a20: f32, a21: f32, a22: f32,
344        ) -> f32 {
345            a00 * (a11 * a22 - a12 * a21)
346                - a01 * (a10 * a22 - a12 * a20)
347                + a02 * (a10 * a21 - a11 * a20)
348        }
349
350        m00 * det3(m11, m12, m13, m21, m22, m23, m31, m32, m33)
351            - m01 * det3(m10, m12, m13, m20, m22, m23, m30, m32, m33)
352            + m02 * det3(m10, m11, m13, m20, m21, m23, m30, m31, m33)
353            - m03 * det3(m10, m11, m12, m20, m21, m22, m30, m31, m32)
354    }
355
356    /// Creates a left-handed orthographic projection matrix with a depth range of [0, 1].
357    /// (DirectX style: z will be mapped to 0 for near plane, 1 for far plane).
358    #[inline]
359    pub fn orthographic_lh_zo(
360        left: f32,
361        right: f32,
362        bottom: f32,
363        top: f32,
364        near: f32,
365        far: f32,
366    ) -> Self {
367        let r_minus_l = right - left;
368        let t_minus_b = top - bottom;
369        let f_minus_n = far - near;
370
371        // Avoid division by zero if any range is zero.
372        // The C++ code doesn't explicitly handle this, but it's good practice.
373        // For now, we'll assume valid inputs as per typical library behavior.
374
375        let mut m = Self::ZERO;
376
377        m.x_col.x = 2.0 / r_minus_l;
378        m.y_col.y = 2.0 / t_minus_b;
379        m.z_col.z = 1.0 / f_minus_n; // For LH_ZO
380        m.w_col.w = 1.0;
381
382        m.w_col.x = -(right + left) / r_minus_l;
383        m.w_col.y = -(top + bottom) / t_minus_b;
384        m.w_col.z = -near / f_minus_n; // For LH_ZO
385
386        m
387    }
388
389    /// Creates a left-handed perspective projection matrix with a depth range of [0, 1].
390    /// (DirectX style: z will be mapped to 0 for near plane, 1 for far plane after perspective divide).
391    #[inline]
392    pub fn perspective_lh_zo(
393        fovy_radians: f32,
394        aspect_ratio: f32,
395        near_plane: f32,
396        far_plane: f32,
397    ) -> Self {
398        let tan_half_fovy = math::tan(0.5 * fovy_radians);
399
400        // Avoid division by zero if aspect_ratio or tan_half_fovy is zero, or near == far.
401        // For now, assume valid inputs.
402
403        let mut m = Self::ZERO;
404
405        m.x_col.x = 1.0 / (aspect_ratio * tan_half_fovy);
406        m.y_col.y = 1.0 / tan_half_fovy;
407        m.z_col.z = far_plane / (far_plane - near_plane);
408        m.z_col.w = 1.0; // Puts view-space Z into W for perspective divide
409        m.w_col.z = -near_plane * far_plane / (far_plane - near_plane);
410        // m.w_col.w is 0.0 from ZERO initialization, which is correct.
411
412        m
413    }
414
415    /// Creates a left-handed look-at view matrix.
416    /// Translates gb_mat4_look_at directly.
417    #[inline]
418    pub fn look_at_lh(eye: Vec3, target: Vec3, world_up: Vec3) -> Self {
419        let f = (target - eye).normalize(); // Corresponds to CamZ or `f` in C++
420        let s = f.cross(world_up).normalize(); // Corresponds to -CamX or `s` in C++ (f.cross(up) is -CamX if up is Y and f is Z)
421        let u = s.cross(f); // Corresponds to CamY or `u` in C++ ((-CamX).cross(CamZ) is CamY)
422
423        // The C++ code gb_mat4_look_at is equivalent to:
424        // Mat4::from_rows(
425        //     s.x, u.x, -f.x, -s.dot(eye),
426        //     s.y, u.y, -f.y, -u.dot(eye),
427        //     s.z, u.z, -f.z,  f.dot(eye),  <-- note +f.dot(eye) from C++
428        //     0.0, 0.0,  0.0,  1.0
429        // );
430        // And then transposing it if from_rows created a row-major matrix.
431        // Our Mat4 struct is column-major. The C++ gbMat4 stores columns.
432        // gbFloat4 *m = gb_float44_m(out); -> m[col_idx][row_idx]
433        // m[0][0] = s.x;  // x_col.x
434        // m[1][0] = s.y;  // y_col.x
435        // m[2][0] = s.z;  // z_col.x
436        // m[3][0] = -s.dot(eye); // w_col.x
437        // ... this means the columns of the matrix are [s,u,-f] for rotation part, and translation derived from them.
438
439        Self {
440            x_col: Vec4::new(s.x, u.x, -f.x, 0.0),
441            y_col: Vec4::new(s.y, u.y, -f.y, 0.0),
442            z_col: Vec4::new(s.z, u.z, -f.z, 0.0),
443            w_col: Vec4::new(-s.dot(eye), -u.dot(eye), f.dot(eye), 1.0),
444            _unit: PhantomData,
445            _space: PhantomData,
446        }
447    }
448
449    /// Returns the i-th column (0, 1, 2, or 3).
450    pub const fn col(&self, i: usize) -> Option<Vec4<Unit, Space>> {
451        match i {
452            0 => Some(self.x_col),
453            1 => Some(self.y_col),
454            2 => Some(self.z_col),
455            3 => Some(self.w_col),
456            _ => None,
457        }
458    }
459
460    /// Sets the i-th column (0, 1, 2, or 3).
461    pub fn set_col(&mut self, i: usize, v: Vec4<Unit, Space>) {
462        match i {
463            0 => self.x_col = v,
464            1 => self.y_col = v,
465            2 => self.z_col = v,
466            3 => self.w_col = v,
467            _ => {}
468        }
469    }
470
471    /// Returns the i-th row (0, 1, 2, or 3).
472    pub const fn row(&self, i: usize) -> Option<Vec4<Unit, Space>> {
473        match i {
474            0 => Some(Vec4::new(
475                self.x_col.x,
476                self.y_col.x,
477                self.z_col.x,
478                self.w_col.x,
479            )),
480            1 => Some(Vec4::new(
481                self.x_col.y,
482                self.y_col.y,
483                self.z_col.y,
484                self.w_col.y,
485            )),
486            2 => Some(Vec4::new(
487                self.x_col.z,
488                self.y_col.z,
489                self.z_col.z,
490                self.w_col.z,
491            )),
492            3 => Some(Vec4::new(
493                self.x_col.w,
494                self.y_col.w,
495                self.z_col.w,
496                self.w_col.w,
497            )),
498            _ => None,
499        }
500    }
501
502    /// Sets the i-th row (0, 1, 2, or 3).
503    pub fn set_row(&mut self, i: usize, v: Vec4<Unit, Space>) {
504        match i {
505            0 => {
506                self.x_col.x = v.x;
507                self.y_col.x = v.y;
508                self.z_col.x = v.z;
509                self.w_col.x = v.w;
510            }
511            1 => {
512                self.x_col.y = v.x;
513                self.y_col.y = v.y;
514                self.z_col.y = v.z;
515                self.w_col.y = v.w;
516            }
517            2 => {
518                self.x_col.z = v.x;
519                self.y_col.z = v.y;
520                self.z_col.z = v.z;
521                self.w_col.z = v.w;
522            }
523            3 => {
524                self.x_col.w = v.x;
525                self.y_col.w = v.y;
526                self.z_col.w = v.z;
527                self.w_col.w = v.w;
528            }
529            _ => {}
530        }
531    }
532
533    /// Returns true if the matrix is orthonormal (rotation part only).
534    pub fn is_orthonormal(&self) -> bool {
535        let c0 = self.x_col.xyz();
536        let c1 = self.y_col.xyz();
537        let c2 = self.z_col.xyz();
538        (c0.length() - 1.0).abs() < 1e-5
539            && (c1.length() - 1.0).abs() < 1e-5
540            && (c2.length() - 1.0).abs() < 1e-5
541            && (c0.dot(c1)).abs() < 1e-5
542            && (c0.dot(c2)).abs() < 1e-5
543            && (c1.dot(c2)).abs() < 1e-5
544    }
545
546    /// Returns a shear matrix with 6 shear factors.
547    pub const fn from_shear(shxy: f32, shxz: f32, shyx: f32, shyz: f32, shzx: f32, shzy: f32) -> Self {
548        // Shear matrix: https://en.wikipedia.org/wiki/Shear_matrix
549        // | 1 shxy shxz 0 |
550        // | shyx 1 shyz 0 |
551        // | shzx shzy 1 0 |
552        // | 0 0 0 1 |
553        Self {
554            x_col: Vec4::new(1.0, shyx, shzx, 0.0),
555            y_col: Vec4::new(shxy, 1.0, shzy, 0.0),
556            z_col: Vec4::new(shxz, shyz, 1.0, 0.0),
557            w_col: Vec4::new(0.0, 0.0, 0.0, 1.0),
558            _unit: PhantomData,
559            _space: PhantomData,
560        }
561    }
562
563    /// Creates a Mat4 from a quaternion (rotation part only, no translation).
564    pub fn from_quat(q: crate::quat::Quat<Unit, Space>) -> Self {
565        let (x, y, z, w) = (q.x, q.y, q.z, q.w);
566        let x2 = x + x;
567        let y2 = y + y;
568        let z2 = z + z;
569        let xx = x * x2;
570        let yy = y * y2;
571        let zz = z * z2;
572        let xy = x * y2;
573        let xz = x * z2;
574        let yz = y * z2;
575        let wx = w * x2;
576        let wy = w * y2;
577        let wz = w * z2;
578        Self {
579            x_col: Vec4::new(1.0 - (yy + zz), xy + wz, xz - wy, 0.0),
580            y_col: Vec4::new(xy - wz, 1.0 - (xx + zz), yz + wx, 0.0),
581            z_col: Vec4::new(xz + wy, yz - wx, 1.0 - (xx + yy), 0.0),
582            w_col: Vec4::new(0.0, 0.0, 0.0, 1.0),
583            _unit: PhantomData,
584            _space: PhantomData,
585        }
586    }
587
588    /// Converts the rotation part of this matrix to a quaternion.
589    pub fn to_quat(&self) -> crate::quat::Quat {
590        // Use the same logic as Quat::from_mat4 but for 3x3 part
591        let m00 = self.x_col.x;
592        let m01 = self.x_col.y;
593        let m02 = self.x_col.z;
594        let m10 = self.y_col.x;
595        let m11 = self.y_col.y;
596        let m12 = self.y_col.z;
597        let m20 = self.z_col.x;
598        let m21 = self.z_col.y;
599        let m22 = self.z_col.z;
600        let four_x_squared_minus_1 = m00 - m11 - m22;
601        let four_y_squared_minus_1 = m11 - m00 - m22;
602        let four_z_squared_minus_1 = m22 - m00 - m11;
603        let four_w_squared_minus_1 = m00 + m11 + m22;
604        let mut biggest_index = 0;
605        let mut four_biggest_squared_minus_1 = four_w_squared_minus_1;
606        if four_x_squared_minus_1 > four_biggest_squared_minus_1 {
607            four_biggest_squared_minus_1 = four_x_squared_minus_1;
608            biggest_index = 1;
609        }
610        if four_y_squared_minus_1 > four_biggest_squared_minus_1 {
611            four_biggest_squared_minus_1 = four_y_squared_minus_1;
612            biggest_index = 2;
613        }
614        if four_z_squared_minus_1 > four_biggest_squared_minus_1 {
615            four_biggest_squared_minus_1 = four_z_squared_minus_1;
616            biggest_index = 3;
617        }
618        let biggest_value = math::sqrt(four_biggest_squared_minus_1 + 1.0) * 0.5;
619        let mult = 0.25 / biggest_value;
620        let mut q = crate::quat::Quat::ZERO;
621        match biggest_index {
622            0 => {
623                q.w = biggest_value;
624                q.x = (m12 - m21) * mult;
625                q.y = (m20 - m02) * mult;
626                q.z = (m01 - m10) * mult;
627            }
628            1 => {
629                q.w = (m12 - m21) * mult;
630                q.x = biggest_value;
631                q.y = (m01 + m10) * mult;
632                q.z = (m20 + m02) * mult;
633            }
634            2 => {
635                q.w = (m20 - m02) * mult;
636                q.x = (m01 + m10) * mult;
637                q.y = biggest_value;
638                q.z = (m12 + m21) * mult;
639            }
640            3 => {
641                q.w = (m01 - m10) * mult;
642                q.x = (m20 + m02) * mult;
643                q.y = (m12 + m21) * mult;
644                q.z = biggest_value;
645            }
646            _ => unreachable!(),
647        }
648        q
649    }
650
651    /// Transforms a point (Vec3, w=1) by this matrix.
652    pub fn transform_point(&self, p: Vec3) -> Vec3 {
653        let v = Vec4::new(p.x, p.y, p.z, 1.0);
654        let r = *self * v;
655        Vec3::new(r.x, r.y, r.z)
656    }
657
658    /// Transforms a direction vector (Vec3, w=0) by this matrix.
659    pub fn transform_vector(&self, v: Vec3) -> Vec3 {
660        let v4 = Vec4::new(v.x, v.y, v.z, 0.0);
661        let r = *self * v4;
662        Vec3::new(r.x, r.y, r.z)
663    }
664
665    /// Decomposes this matrix into translation, rotation (as quaternion), and scale.
666    pub fn decompose(&self) -> (Vec3, crate::quat::Quat, Vec3) {
667        // Extract translation
668        let translation = Vec3::new(self.w_col.x, self.w_col.y, self.w_col.z);
669        // Extract scale from column lengths
670        let sx = self.x_col.xyz().length();
671        let sy = self.y_col.xyz().length();
672        let sz = self.z_col.xyz().length();
673        let scale = Vec3::new(sx, sy, sz);
674        // Remove scale from rotation part
675        let mut rot = *self;
676        rot.x_col = rot.x_col / sx;
677        rot.y_col = rot.y_col / sy;
678        rot.z_col = rot.z_col / sz;
679        let rotation = rot.to_quat();
680        (translation, rotation, scale)
681    }
682
683    /// Composes a Mat4 from translation, rotation (as quaternion), and scale.
684    pub fn compose(translation: Vec3, rotation: crate::quat::Quat<Unit, Space>, scale: Vec3) -> Self {
685        let rot = Self::from_quat(rotation);
686        let mut m = rot;
687        m.x_col = m.x_col * scale.x;
688        m.y_col = m.y_col * scale.y;
689        m.z_col = m.z_col * scale.z;
690        m.w_col = Vec4::new(translation.x, translation.y, translation.z, 1.0);
691        m
692    }
693
694    /// Alias for [`Mat4::compose`].
695    ///
696    /// This name is common in other math libraries: TRS = Translation * Rotation * Scale.
697    #[inline]
698    pub fn from_trs(translation: Vec3, rotation: crate::quat::Quat<Unit, Space>, scale: Vec3) -> Self {
699        Self::compose(translation, rotation, scale)
700    }
701}
702
703impl<Unit: Copy, Space: Copy> Add for Mat4<Unit, Space> {
704    type Output = Self;
705    #[inline]
706    fn add(self, rhs: Self) -> Self::Output {
707        Self {
708            x_col: self.x_col + rhs.x_col,
709            y_col: self.y_col + rhs.y_col,
710            z_col: self.z_col + rhs.z_col,
711            w_col: self.w_col + rhs.w_col,
712            _unit: PhantomData,
713            _space: PhantomData,
714        }
715    }
716}
717
718impl<Unit: Copy, Space: Copy> Sub for Mat4<Unit, Space> {
719    type Output = Self;
720    #[inline]
721    fn sub(self, rhs: Self) -> Self::Output {
722        Self {
723            x_col: self.x_col - rhs.x_col,
724            y_col: self.y_col - rhs.y_col,
725            z_col: self.z_col - rhs.z_col,
726            w_col: self.w_col - rhs.w_col,
727            _unit: PhantomData,
728            _space: PhantomData,
729        }
730    }
731}
732
733impl<Unit: Copy, Space: Copy> Mul<Mat4<Unit, Space>> for Mat4<Unit, Space> {
734    type Output = Self;
735    #[inline]
736    fn mul(self, rhs: Mat4<Unit, Space>) -> Self::Output {
737        // Based on gb_mat4_mul which calls gb_float44_mul
738        // out[j][i] = mat1[0][i]*mat2[j][0] +
739        //             mat1[1][i]*mat2[j][1] +
740        //             mat1[2][i]*mat2[j][2] +
741        //             mat1[3][i]*mat2[j][3];
742        // where mat1 is `self` and mat2 is `rhs`.
743        // `i` is row index, `j` is column index.
744
745        // x_col (j=0)
746        let x_col_x = self.x_col.x * rhs.x_col.x
747            + self.y_col.x * rhs.x_col.y
748            + self.z_col.x * rhs.x_col.z
749            + self.w_col.x * rhs.x_col.w; // i=0
750        let x_col_y = self.x_col.y * rhs.x_col.x
751            + self.y_col.y * rhs.x_col.y
752            + self.z_col.y * rhs.x_col.z
753            + self.w_col.y * rhs.x_col.w; // i=1
754        let x_col_z = self.x_col.z * rhs.x_col.x
755            + self.y_col.z * rhs.x_col.y
756            + self.z_col.z * rhs.x_col.z
757            + self.w_col.z * rhs.x_col.w; // i=2
758        let x_col_w = self.x_col.w * rhs.x_col.x
759            + self.y_col.w * rhs.x_col.y
760            + self.z_col.w * rhs.x_col.z
761            + self.w_col.w * rhs.x_col.w; // i=3
762
763        // y_col (j=1)
764        let y_col_x = self.x_col.x * rhs.y_col.x
765            + self.y_col.x * rhs.y_col.y
766            + self.z_col.x * rhs.y_col.z
767            + self.w_col.x * rhs.y_col.w; // i=0
768        let y_col_y = self.x_col.y * rhs.y_col.x
769            + self.y_col.y * rhs.y_col.y
770            + self.z_col.y * rhs.y_col.z
771            + self.w_col.y * rhs.y_col.w; // i=1
772        let y_col_z = self.x_col.z * rhs.y_col.x
773            + self.y_col.z * rhs.y_col.y
774            + self.z_col.z * rhs.y_col.z
775            + self.w_col.z * rhs.y_col.w; // i=2
776        let y_col_w = self.x_col.w * rhs.y_col.x
777            + self.y_col.w * rhs.y_col.y
778            + self.z_col.w * rhs.y_col.z
779            + self.w_col.w * rhs.y_col.w; // i=3
780
781        // z_col (j=2)
782        let z_col_x = self.x_col.x * rhs.z_col.x
783            + self.y_col.x * rhs.z_col.y
784            + self.z_col.x * rhs.z_col.z
785            + self.w_col.x * rhs.z_col.w; // i=0
786        let z_col_y = self.x_col.y * rhs.z_col.x
787            + self.y_col.y * rhs.z_col.y
788            + self.z_col.y * rhs.z_col.z
789            + self.w_col.y * rhs.z_col.w; // i=1
790        let z_col_z = self.x_col.z * rhs.z_col.x
791            + self.y_col.z * rhs.z_col.y
792            + self.z_col.z * rhs.z_col.z
793            + self.w_col.z * rhs.z_col.w; // i=2
794        let z_col_w = self.x_col.w * rhs.z_col.x
795            + self.y_col.w * rhs.z_col.y
796            + self.z_col.w * rhs.z_col.z
797            + self.w_col.w * rhs.z_col.w; // i=3
798
799        // w_col (j=3)
800        let w_col_x = self.x_col.x * rhs.w_col.x
801            + self.y_col.x * rhs.w_col.y
802            + self.z_col.x * rhs.w_col.z
803            + self.w_col.x * rhs.w_col.w; // i=0
804        let w_col_y = self.x_col.y * rhs.w_col.x
805            + self.y_col.y * rhs.w_col.y
806            + self.z_col.y * rhs.w_col.z
807            + self.w_col.y * rhs.w_col.w; // i=1
808        let w_col_z = self.x_col.z * rhs.w_col.x
809            + self.y_col.z * rhs.w_col.y
810            + self.z_col.z * rhs.w_col.z
811            + self.w_col.z * rhs.w_col.w; // i=2
812        let w_col_w = self.x_col.w * rhs.w_col.x
813            + self.y_col.w * rhs.w_col.y
814            + self.z_col.w * rhs.w_col.z
815            + self.w_col.w * rhs.w_col.w; // i=3
816
817        Self {
818            x_col: Vec4::new(x_col_x, x_col_y, x_col_z, x_col_w),
819            y_col: Vec4::new(y_col_x, y_col_y, y_col_z, y_col_w),
820            z_col: Vec4::new(z_col_x, z_col_y, z_col_z, z_col_w),
821            w_col: Vec4::new(w_col_x, w_col_y, w_col_z, w_col_w),
822            _unit: PhantomData,
823            _space: PhantomData,
824        }
825    }
826}
827
828impl<Unit: Copy, Space: Copy> Mul<Vec4<Unit, Space>> for Mat4<Unit, Space> {
829    type Output = Vec4<Unit, Space>;
830    #[inline]
831    fn mul(self, rhs: Vec4<Unit, Space>) -> Self::Output {
832        // Based on gb_mat4_mul_vec4 which calls gb_float44_mul_vec4
833        // out->x = m[0][0]*v.x + m[1][0]*v.y + m[2][0]*v.z + m[3][0]*v.w;
834        // out->y = m[0][1]*v.x + m[1][1]*v.y + m[2][1]*v.z + m[3][1]*v.w;
835        // out->z = m[0][2]*v.x + m[1][2]*v.y + m[2][2]*v.z + m[3][2]*v.w;
836        // out->w = m[0][3]*v.x + m[1][3]*v.y + m[2][3]*v.z + m[3][3]*v.w;
837        // where m[col][row]
838        // self.x_col.x is m[0][0], self.x_col.y is m[0][1]
839        // self.y_col.x is m[1][0], self.y_col.y is m[1][1]
840        let x = self.x_col.x * rhs.x
841            + self.y_col.x * rhs.y
842            + self.z_col.x * rhs.z
843            + self.w_col.x * rhs.w;
844        let y = self.x_col.y * rhs.x
845            + self.y_col.y * rhs.y
846            + self.z_col.y * rhs.z
847            + self.w_col.y * rhs.w;
848        let z = self.x_col.z * rhs.x
849            + self.y_col.z * rhs.y
850            + self.z_col.z * rhs.z
851            + self.w_col.z * rhs.w;
852        let w = self.x_col.w * rhs.x
853            + self.y_col.w * rhs.y
854            + self.z_col.w * rhs.z
855            + self.w_col.w * rhs.w;
856        Vec4::new(x, y, z, w)
857    }
858}
859
860impl<Unit: Copy, Space: Copy> Mul<f32> for Mat4<Unit, Space> {
861    type Output = Self;
862    #[inline]
863    fn mul(self, rhs: f32) -> Self::Output {
864        Self {
865            x_col: self.x_col * rhs,
866            y_col: self.y_col * rhs,
867            z_col: self.z_col * rhs,
868            w_col: self.w_col * rhs,
869            _unit: PhantomData,
870            _space: PhantomData,
871        }
872    }
873}
874
875impl<Unit: Copy, Space: Copy> Mul<Mat4<Unit, Space>> for f32 {
876    type Output = Mat4<Unit, Space>;
877    #[inline]
878    fn mul(self, rhs: Mat4<Unit, Space>) -> Self::Output {
879        rhs * self // Reuse Mat4 * f32
880    }
881}
882
883// TODO: Implement various transform constructors (handedness variants, typed angles, etc.)