khora_core/math/
matrix.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Defines the `Mat3` and `Mat4` types and associated operations.
16
17use super::{Quaternion, Vec2, Vec3, Vec4, EPSILON};
18use std::ops::{Index, IndexMut, Mul};
19
20// --- Mat3 ---
21
22/// A 3x3 column-major matrix, typically used for 2D affine transformations (scale, rotation).
23///
24/// While it can represent any 3x3 matrix, its primary role in a 3D engine is often
25/// as the upper-left rotation and scale part of a `Mat4`.
26#[derive(Debug, Clone, Copy, PartialEq)]
27#[repr(C)]
28pub struct Mat3 {
29    /// The columns of the matrix. `cols[0]` is the first column, and so on.
30    pub cols: [Vec3; 3],
31}
32
33impl Mat3 {
34    /// The 3x3 identity matrix.
35    pub const IDENTITY: Self = Self {
36        cols: [Vec3::X, Vec3::Y, Vec3::Z],
37    };
38
39    /// A 3x3 matrix with all elements set to 0.
40    pub const ZERO: Self = Self {
41        cols: [Vec3::ZERO; 3],
42    };
43
44    /// Creates a new matrix from three column vectors.
45    #[inline]
46    pub fn from_cols(c0: Vec3, c1: Vec3, c2: Vec3) -> Self {
47        Self { cols: [c0, c1, c2] }
48    }
49
50    /// Returns a row of the matrix as a `Vec3`.
51    #[allow(dead_code)]
52    #[inline]
53    fn get_row(&self, index: usize) -> Vec3 {
54        Vec3 {
55            x: self.cols[0].get(index),
56            y: self.cols[1].get(index),
57            z: self.cols[2].get(index),
58        }
59    }
60
61    /// Creates a 2D scaling matrix.
62    ///
63    /// The Z-axis scale is set to 1.0, making it a no-op in that dimension.
64    #[inline]
65    pub fn from_scale_vec2(scale: Vec2) -> Self {
66        Self::from_scale(Vec3::new(scale.x, scale.y, 1.0))
67    }
68
69    /// Creates a 3D scaling matrix.
70    #[inline]
71    pub fn from_scale(scale: Vec3) -> Self {
72        Self {
73            cols: [
74                Vec3::new(scale.x, 0.0, 0.0),
75                Vec3::new(0.0, scale.y, 0.0),
76                Vec3::new(0.0, 0.0, scale.z),
77            ],
78        }
79    }
80
81    /// Creates a matrix for a rotation around the X-axis.
82    ///
83    /// # Arguments
84    ///
85    /// * `angle_radians`: The angle of rotation in radians.
86    #[inline]
87    pub fn from_rotation_x(angle_radians: f32) -> Self {
88        let (s, c) = angle_radians.sin_cos();
89        Self {
90            cols: [
91                Vec3::new(1.0, 0.0, 0.0),
92                Vec3::new(0.0, c, s),
93                Vec3::new(0.0, -s, c),
94            ],
95        }
96    }
97
98    /// Creates a matrix for a right-handed rotation around the Y-axis.
99    ///
100    /// # Arguments
101    ///
102    /// * `angle_radians`: The angle of rotation in radians.
103    #[inline]
104    pub fn from_rotation_y(angle_radians: f32) -> Self {
105        let (s, c) = angle_radians.sin_cos();
106        Self {
107            cols: [
108                Vec3::new(c, 0.0, -s),
109                Vec3::new(0.0, 1.0, 0.0),
110                Vec3::new(s, 0.0, c),
111            ],
112        }
113    }
114
115    /// Creates a matrix for a rotation around the Z-axis.
116    ///
117    /// # Arguments
118    ///
119    /// * `angle_radians`: The angle of rotation in radians.
120    #[inline]
121    pub fn from_rotation_z(angle_radians: f32) -> Self {
122        let (s, c) = angle_radians.sin_cos();
123        Self {
124            cols: [
125                Vec3::new(c, s, 0.0),
126                Vec3::new(-s, c, 0.0),
127                Vec3::new(0.0, 0.0, 1.0),
128            ],
129        }
130    }
131
132    /// Creates a rotation matrix from a normalized axis and an angle.
133    ///
134    /// # Arguments
135    ///
136    /// * `axis`: The axis of rotation. Must be a unit vector.
137    /// * `angle_radians`: The angle of rotation in radians.
138    #[inline]
139    pub fn from_axis_angle(axis: Vec3, angle_radians: f32) -> Self {
140        let (s, c) = angle_radians.sin_cos();
141        let t = 1.0 - c;
142        let x = axis.x;
143        let y = axis.y;
144        let z = axis.z;
145        Self {
146            cols: [
147                Vec3::new(t * x * x + c, t * x * y + s * z, t * x * z - s * y),
148                Vec3::new(t * y * x - s * z, t * y * y + c, t * y * z + s * x),
149                Vec3::new(t * z * x + s * y, t * z * y - s * x, t * z * z + c),
150            ],
151        }
152    }
153
154    /// Creates a rotation matrix from a quaternion.
155    /// The quaternion is normalized before conversion to ensure a valid rotation matrix.
156    #[inline]
157    pub fn from_quat(q: Quaternion) -> Self {
158        let q = q.normalize();
159        let x = q.x;
160        let y = q.y;
161        let z = q.z;
162        let w = q.w;
163        let x2 = x + x;
164        let y2 = y + y;
165        let z2 = z + z;
166        let xx = x * x2;
167        let xy = x * y2;
168        let xz = x * z2;
169        let yy = y * y2;
170        let yz = y * z2;
171        let zz = z * z2;
172        let wx = w * x2;
173        let wy = w * y2;
174        let wz = w * z2;
175
176        Self::from_cols(
177            Vec3::new(1.0 - (yy + zz), xy + wz, xz - wy),
178            Vec3::new(xy - wz, 1.0 - (xx + zz), yz + wx),
179            Vec3::new(xz + wy, yz - wx, 1.0 - (xx + yy)),
180        )
181    }
182
183    /// Creates a `Mat3` from the upper-left 3x3 corner of a [`Mat4`].
184    /// This effectively extracts the rotation and scale components, discarding translation.
185    #[inline]
186    pub fn from_mat4(m4: &Mat4) -> Self {
187        Self::from_cols(
188            m4.cols[0].truncate(),
189            m4.cols[1].truncate(),
190            m4.cols[2].truncate(),
191        )
192    }
193
194    /// Computes the determinant of the matrix.
195    ///
196    /// The determinant is a scalar value indicating the volume scaling factor of the
197    /// linear transformation. A determinant of 0 means the matrix is not invertible.
198    #[inline]
199    pub fn determinant(&self) -> f32 {
200        let c0 = self.cols[0];
201        let c1 = self.cols[1];
202        let c2 = self.cols[2];
203        c0.x * (c1.y * c2.z - c2.y * c1.z) - c1.x * (c0.y * c2.z - c2.y * c0.z)
204            + c2.x * (c0.y * c1.z - c1.y * c0.z)
205    }
206
207    /// Returns the transpose of the matrix, where rows and columns are swapped.
208    #[inline]
209    pub fn transpose(&self) -> Self {
210        Self::from_cols(
211            Vec3::new(self.cols[0].x, self.cols[1].x, self.cols[2].x),
212            Vec3::new(self.cols[0].y, self.cols[1].y, self.cols[2].y),
213            Vec3::new(self.cols[0].z, self.cols[1].z, self.cols[2].z),
214        )
215    }
216
217    /// Computes the inverse of the matrix.
218    ///
219    /// If the matrix is not invertible (i.e., its determinant is close to zero),
220    /// this method returns `None`.
221    pub fn inverse(&self) -> Option<Self> {
222        let c0 = self.cols[0];
223        let c1 = self.cols[1];
224        let c2 = self.cols[2];
225        let m00 = c1.y * c2.z - c2.y * c1.z;
226        let m10 = c2.y * c0.z - c0.y * c2.z;
227        let m20 = c0.y * c1.z - c1.y * c0.z;
228        let det = c0.x * m00 + c1.x * m10 + c2.x * m20;
229
230        if det.abs() < EPSILON {
231            return None;
232        }
233
234        let inv_det = 1.0 / det;
235        let m01 = c2.x * c1.z - c1.x * c2.z;
236        let m11 = c0.x * c2.z - c2.x * c0.z;
237        let m21 = c1.x * c0.z - c0.x * c1.z;
238        let m02 = c1.x * c2.y - c2.x * c1.y;
239        let m12 = c2.x * c0.y - c0.x * c2.y;
240        let m22 = c0.x * c1.y - c1.x * c0.y;
241
242        Some(Self::from_cols(
243            Vec3::new(m00, m10, m20) * inv_det,
244            Vec3::new(m01, m11, m21) * inv_det,
245            Vec3::new(m02, m12, m22) * inv_det,
246        ))
247    }
248
249    /// Converts this `Mat3` into a [`Mat4`], preserving its values in the upper-left corner.
250    /// The new fourth column and row are set to `(0, 0, 0, 1)`.
251    #[inline]
252    pub fn to_mat4(&self) -> Mat4 {
253        Mat4::from_cols(
254            Vec4::from_vec3(self.cols[0], 0.0),
255            Vec4::from_vec3(self.cols[1], 0.0),
256            Vec4::from_vec3(self.cols[2], 0.0),
257            Vec4::W,
258        )
259    }
260}
261
262// --- Operator Overloads ---
263
264impl Default for Mat3 {
265    /// Returns the 3x3 identity matrix.
266    #[inline]
267    fn default() -> Self {
268        Self::IDENTITY
269    }
270}
271
272impl Mul<Mat3> for Mat3 {
273    type Output = Self;
274    /// Multiplies this matrix by another `Mat3`.
275    #[inline]
276    fn mul(self, rhs: Mat3) -> Self::Output {
277        Self::from_cols(self * rhs.cols[0], self * rhs.cols[1], self * rhs.cols[2])
278    }
279}
280
281impl Mul<Vec3> for Mat3 {
282    type Output = Vec3;
283    /// Transforms a `Vec3` by this matrix.
284    #[inline]
285    fn mul(self, v: Vec3) -> Self::Output {
286        self.cols[0] * v.x + self.cols[1] * v.y + self.cols[2] * v.z
287    }
288}
289
290impl Index<usize> for Mat3 {
291    type Output = Vec3;
292    /// Allows accessing a matrix column by index.
293    #[inline]
294    fn index(&self, index: usize) -> &Self::Output {
295        &self.cols[index]
296    }
297}
298
299impl IndexMut<usize> for Mat3 {
300    /// Allows mutably accessing a matrix column by index.
301    #[inline]
302    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
303        &mut self.cols[index]
304    }
305}
306
307// --- Mat4 ---
308
309/// A 4x4 column-major matrix, used for 3D affine transformations.
310///
311/// This is the primary type for representing transformations (translation, rotation,
312/// scale) in 3D space. It is also used for camera view and projection matrices.
313/// The memory layout is column-major, which is compatible with modern graphics APIs
314/// like Vulkan, Metal, and DirectX.
315#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
316#[repr(C)]
317pub struct Mat4 {
318    /// The columns of the matrix. `cols[0]` is the first column, and so on.
319    pub cols: [Vec4; 4],
320}
321
322impl Mat4 {
323    /// The 4x4 identity matrix.
324    pub const IDENTITY: Self = Self {
325        cols: [Vec4::X, Vec4::Y, Vec4::Z, Vec4::W],
326    };
327
328    /// A 4x4 matrix with all elements set to 0.
329    pub const ZERO: Self = Self {
330        cols: [Vec4::ZERO; 4],
331    };
332
333    /// Creates a new matrix from four column vectors.
334    #[inline]
335    pub fn from_cols(c0: Vec4, c1: Vec4, c2: Vec4, c3: Vec4) -> Self {
336        Self {
337            cols: [c0, c1, c2, c3],
338        }
339    }
340
341    /// Returns a row of the matrix as a `Vec4`.
342    #[inline]
343    pub fn get_row(&self, index: usize) -> Vec4 {
344        Vec4 {
345            x: self.cols[0].get(index),
346            y: self.cols[1].get(index),
347            z: self.cols[2].get(index),
348            w: self.cols[3].get(index),
349        }
350    }
351
352    /// Returns the matrix as a 2D array of columns: `[[x, y, z, w]; 4]`.
353    #[inline]
354    pub const fn to_cols_array_2d(&self) -> [[f32; 4]; 4] {
355        [
356            [
357                self.cols[0].x,
358                self.cols[0].y,
359                self.cols[0].z,
360                self.cols[0].w,
361            ],
362            [
363                self.cols[1].x,
364                self.cols[1].y,
365                self.cols[1].z,
366                self.cols[1].w,
367            ],
368            [
369                self.cols[2].x,
370                self.cols[2].y,
371                self.cols[2].z,
372                self.cols[2].w,
373            ],
374            [
375                self.cols[3].x,
376                self.cols[3].y,
377                self.cols[3].z,
378                self.cols[3].w,
379            ],
380        ]
381    }
382
383    /// Creates a translation matrix.
384    ///
385    /// # Arguments
386    ///
387    /// * `v`: The translation vector to apply.
388    #[inline]
389    pub fn from_translation(v: Vec3) -> Self {
390        Self {
391            cols: [
392                Vec4::new(1.0, 0.0, 0.0, 0.0),
393                Vec4::new(0.0, 1.0, 0.0, 0.0),
394                Vec4::new(0.0, 0.0, 1.0, 0.0),
395                Vec4::new(v.x, v.y, v.z, 1.0),
396            ],
397        }
398    }
399
400    /// Creates a non-uniform scaling matrix.
401    #[inline]
402    pub fn from_scale(scale: Vec3) -> Self {
403        Self {
404            cols: [
405                Vec4::new(scale.x, 0.0, 0.0, 0.0),
406                Vec4::new(0.0, scale.y, 0.0, 0.0),
407                Vec4::new(0.0, 0.0, scale.z, 0.0),
408                Vec4::new(0.0, 0.0, 0.0, 1.0),
409            ],
410        }
411    }
412
413    /// Creates a matrix for a rotation around the X-axis.
414    ///
415    /// # Arguments
416    ///
417    /// * `angle`: The angle of rotation in radians.
418    #[inline]
419    pub fn from_rotation_x(angle: f32) -> Self {
420        let c = angle.cos();
421        let s = angle.sin();
422        Self {
423            cols: [
424                Vec4::new(1.0, 0.0, 0.0, 0.0),
425                Vec4::new(0.0, c, s, 0.0),
426                Vec4::new(0.0, -s, c, 0.0),
427                Vec4::new(0.0, 0.0, 0.0, 1.0),
428            ],
429        }
430    }
431
432    /// Creates a matrix for a right-handed rotation around the Y-axis.
433    ///
434    /// # Arguments
435    ///
436    /// * `angle`: The angle of rotation in radians.
437    #[inline]
438    pub fn from_rotation_y(angle: f32) -> Self {
439        let c = angle.cos();
440        let s = angle.sin();
441        Self {
442            cols: [
443                Vec4::new(c, 0.0, -s, 0.0),
444                Vec4::new(0.0, 1.0, 0.0, 0.0),
445                Vec4::new(s, 0.0, c, 0.0),
446                Vec4::new(0.0, 0.0, 0.0, 1.0),
447            ],
448        }
449    }
450
451    /// Creates a matrix for a rotation around the Z-axis.
452    ///
453    /// # Arguments
454    ///
455    /// * `angle`: The angle of rotation in radians.
456    #[inline]
457    pub fn from_rotation_z(angle: f32) -> Self {
458        let c = angle.cos();
459        let s = angle.sin();
460        Self {
461            cols: [
462                Vec4::new(c, s, 0.0, 0.0),
463                Vec4::new(-s, c, 0.0, 0.0),
464                Vec4::new(0.0, 0.0, 1.0, 0.0),
465                Vec4::new(0.0, 0.0, 0.0, 1.0),
466            ],
467        }
468    }
469
470    /// Creates a rotation matrix from a normalized axis and an angle.
471    ///
472    /// # Arguments
473    ///
474    /// * `axis`: The axis of rotation. Must be a unit vector.
475    /// * `angle`: The angle of rotation in radians.
476    #[inline]
477    pub fn from_axis_angle(axis: Vec3, angle: f32) -> Self {
478        let c = angle.cos();
479        let s = angle.sin();
480        let t = 1.0 - c;
481        let x = axis.x;
482        let y = axis.y;
483        let z = axis.z;
484
485        Self {
486            cols: [
487                Vec4::new(t * x * x + c, t * x * y - s * z, t * x * z + s * y, 0.0),
488                Vec4::new(t * y * x + s * z, t * y * y + c, t * y * z - s * x, 0.0),
489                Vec4::new(t * z * x - s * y, t * z * y + s * x, t * z * z + c, 0.0),
490                Vec4::new(0.0, 0.0, 0.0, 1.0),
491            ],
492        }
493    }
494
495    /// Creates a rotation matrix from a quaternion.
496    #[inline]
497    pub fn from_quat(q: Quaternion) -> Self {
498        let x = q.x;
499        let y = q.y;
500        let z = q.z;
501        let w = q.w;
502        let x2 = x + x;
503        let y2 = y + y;
504        let z2 = z + z;
505        let xx = x * x2;
506        let xy = x * y2;
507        let xz = x * z2;
508        let yy = y * y2;
509        let yz = y * z2;
510        let zz = z * z2;
511        let wx = w * x2;
512        let wy = w * y2;
513        let wz = w * z2;
514
515        Self::from_cols(
516            Vec4::new(1.0 - (yy + zz), xy + wz, xz - wy, 0.0),
517            Vec4::new(xy - wz, 1.0 - (xx + zz), yz + wx, 0.0),
518            Vec4::new(xz + wy, yz - wx, 1.0 - (xx + yy), 0.0),
519            Vec4::W,
520        )
521    }
522
523    /// Creates a right-handed perspective projection matrix with a [0, 1] depth range (ZO).
524    ///
525    /// # Arguments
526    ///
527    /// * `fov_y_radians`: Vertical field of view in radians.
528    /// * `aspect_ratio`: Width divided by height of the viewport.
529    /// * `z_near`: Distance to the near clipping plane (must be positive).
530    /// * `z_far`: Distance to the far clipping plane (must be positive and > `z_near`).
531    #[inline]
532    pub fn perspective_rh_zo(
533        fov_y_radians: f32,
534        aspect_ratio: f32,
535        z_near: f32,
536        z_far: f32,
537    ) -> Self {
538        assert!(z_near > 0.0 && z_far > z_near);
539        let tan_half_fovy = (fov_y_radians / 2.0).tan();
540        let f = 1.0 / tan_half_fovy;
541        let aa = f / aspect_ratio;
542        let bb = f;
543        let cc = z_far / (z_near - z_far);
544        let dd = (z_near * z_far) / (z_near - z_far);
545
546        Self::from_cols(
547            Vec4::new(aa, 0.0, 0.0, 0.0),
548            Vec4::new(0.0, bb, 0.0, 0.0),
549            Vec4::new(0.0, 0.0, cc, -1.0),
550            Vec4::new(0.0, 0.0, dd, 0.0),
551        )
552    }
553
554    /// Creates a right-handed orthographic projection matrix with a [0, 1] depth range (ZO).
555    #[inline]
556    pub fn orthographic_rh_zo(
557        left: f32,
558        right: f32,
559        bottom: f32,
560        top: f32,
561        z_near: f32,
562        z_far: f32,
563    ) -> Self {
564        let rml = right - left;
565        let rpl = right + left;
566        let tmb = top - bottom;
567        let tpb = top + bottom;
568        let fmn = z_far - z_near;
569        let aa = 2.0 / rml;
570        let bb = 2.0 / tmb;
571        let cc = -1.0 / fmn;
572        let dd = -rpl / rml;
573        let ee = -tpb / tmb;
574        let ff = -z_near / fmn;
575
576        Self::from_cols(
577            Vec4::new(aa, 0.0, 0.0, 0.0),
578            Vec4::new(0.0, bb, 0.0, 0.0),
579            Vec4::new(0.0, 0.0, cc, 0.0),
580            Vec4::new(dd, ee, ff, 1.0),
581        )
582    }
583
584    /// Creates a right-handed view matrix for a camera looking from `eye` towards `target`.
585    ///
586    /// # Arguments
587    ///
588    /// * `eye`: The position of the camera in world space.
589    /// * `target`: The point in world space that the camera is looking at.
590    /// * `up`: A vector indicating the "up" direction of the world (commonly `Vec3::Y`).
591    ///
592    /// # Returns
593    ///
594    /// Returns `Some(Mat4)` if a valid view matrix can be constructed, or `None` if
595    /// `eye` and `target` are too close, or if `up` is parallel to the view direction.
596    #[inline]
597    pub fn look_at_rh(eye: Vec3, target: Vec3, up: Vec3) -> Option<Self> {
598        let forward = target - eye;
599        if forward.length_squared() < crate::math::EPSILON * crate::math::EPSILON {
600            return None;
601        }
602        let f = forward.normalize();
603        let s = f.cross(up);
604        if s.length_squared() < crate::math::EPSILON * crate::math::EPSILON {
605            return None;
606        }
607        let s = s.normalize();
608        let u = s.cross(f);
609
610        Some(Self::from_cols(
611            Vec4::new(s.x, u.x, -f.x, 0.0),
612            Vec4::new(s.y, u.y, -f.y, 0.0),
613            Vec4::new(s.z, u.z, -f.z, 0.0),
614            Vec4::new(-eye.dot(s), -eye.dot(u), eye.dot(f), 1.0),
615        ))
616    }
617
618    /// Returns the transpose of the matrix, where rows and columns are swapped.
619    #[inline]
620    pub fn transpose(&self) -> Self {
621        Self::from_cols(
622            Vec4::new(
623                self.cols[0].x,
624                self.cols[1].x,
625                self.cols[2].x,
626                self.cols[3].x,
627            ),
628            Vec4::new(
629                self.cols[0].y,
630                self.cols[1].y,
631                self.cols[2].y,
632                self.cols[3].y,
633            ),
634            Vec4::new(
635                self.cols[0].z,
636                self.cols[1].z,
637                self.cols[2].z,
638                self.cols[3].z,
639            ),
640            Vec4::new(
641                self.cols[0].w,
642                self.cols[1].w,
643                self.cols[2].w,
644                self.cols[3].w,
645            ),
646        )
647    }
648
649    /// Computes the determinant of the matrix.
650    pub fn determinant(&self) -> f32 {
651        let c0 = self.cols[0];
652        let c1 = self.cols[1];
653        let c2 = self.cols[2];
654        let c3 = self.cols[3];
655
656        let m00 = c1.y * (c2.z * c3.w - c3.z * c2.w) - c2.y * (c1.z * c3.w - c3.z * c1.w)
657            + c3.y * (c1.z * c2.w - c2.z * c1.w);
658        let m01 = c0.y * (c2.z * c3.w - c3.z * c2.w) - c2.y * (c0.z * c3.w - c3.z * c0.w)
659            + c3.y * (c0.z * c2.w - c2.z * c0.w);
660        let m02 = c0.y * (c1.z * c3.w - c3.z * c1.w) - c1.y * (c0.z * c3.w - c3.z * c0.w)
661            + c3.y * (c0.z * c1.w - c1.z * c0.w);
662        let m03 = c0.y * (c1.z * c2.w - c2.z * c1.w) - c1.y * (c0.z * c2.w - c2.z * c0.w)
663            + c2.y * (c0.z * c1.w - c1.z * c0.w);
664
665        c0.x * m00 - c1.x * m01 + c2.x * m02 - c3.x * m03
666    }
667
668    /// Computes the inverse of the matrix.
669    /// Returns `None` if the matrix is not invertible.
670    pub fn inverse(&self) -> Option<Self> {
671        let c0 = self.cols[0];
672        let c1 = self.cols[1];
673        let c2 = self.cols[2];
674        let c3 = self.cols[3];
675
676        let a00 = c1.y * (c2.z * c3.w - c3.z * c2.w) - c2.y * (c1.z * c3.w - c3.z * c1.w)
677            + c3.y * (c1.z * c2.w - c2.z * c1.w);
678        let a01 = -(c1.x * (c2.z * c3.w - c3.z * c2.w) - c2.x * (c1.z * c3.w - c3.z * c1.w)
679            + c3.x * (c1.z * c2.w - c2.z * c1.w));
680        let a02 = c1.x * (c2.y * c3.w - c3.y * c2.w) - c2.x * (c1.y * c3.w - c3.y * c1.w)
681            + c3.x * (c1.y * c2.w - c2.y * c1.w);
682        let a03 = -(c1.x * (c2.y * c3.z - c3.y * c2.z) - c2.x * (c1.y * c3.z - c3.y * c1.z)
683            + c3.x * (c1.y * c2.z - c2.y * c1.z));
684
685        let a10 = -(c0.y * (c2.z * c3.w - c3.z * c2.w) - c2.y * (c0.z * c3.w - c3.z * c0.w)
686            + c3.y * (c0.z * c2.w - c2.z * c0.w));
687        let a11 = c0.x * (c2.z * c3.w - c3.z * c2.w) - c2.x * (c0.z * c3.w - c3.z * c0.w)
688            + c3.x * (c0.z * c2.w - c2.z * c0.w);
689        let a12 = -(c0.x * (c2.y * c3.w - c3.y * c2.w) - c2.x * (c0.y * c3.w - c3.y * c0.w)
690            + c3.x * (c0.y * c2.w - c2.y * c0.w));
691        let a13 = c0.x * (c2.y * c3.z - c3.y * c2.z) - c2.x * (c0.y * c3.z - c3.y * c0.z)
692            + c3.x * (c0.y * c2.z - c2.y * c0.z);
693
694        let a20 = c0.y * (c1.z * c3.w - c3.z * c1.w) - c1.y * (c0.z * c3.w - c3.z * c0.w)
695            + c3.y * (c0.z * c1.w - c1.z * c0.w);
696        let a21 = -(c0.x * (c1.z * c3.w - c3.z * c1.w) - c1.x * (c0.z * c3.w - c3.z * c0.w)
697            + c3.x * (c0.z * c1.w - c1.z * c0.w));
698        let a22 = c0.x * (c1.y * c3.w - c3.y * c1.w) - c1.x * (c0.y * c3.w - c3.y * c0.w)
699            + c3.x * (c0.y * c1.w - c1.y * c0.w);
700        let a23 = -(c0.x * (c1.y * c3.z - c3.y * c1.z) - c1.x * (c0.y * c3.z - c3.y * c0.z)
701            + c3.x * (c0.y * c1.z - c1.y * c0.z));
702
703        let a30 = -(c0.y * (c1.z * c2.w - c2.z * c1.w) - c1.y * (c0.z * c2.w - c2.z * c0.w)
704            + c2.y * (c0.z * c1.w - c1.z * c0.w));
705        let a31 = c0.x * (c1.z * c2.w - c2.z * c1.w) - c1.x * (c0.z * c2.w - c2.z * c0.w)
706            + c2.x * (c0.z * c1.w - c1.z * c0.w);
707        let a32 = -(c0.x * (c1.y * c2.w - c2.y * c1.w) - c1.x * (c0.y * c2.w - c2.y * c0.w)
708            + c2.x * (c0.y * c1.w - c1.y * c0.w));
709        let a33 = c0.x * (c1.y * c2.z - c2.y * c1.z) - c1.x * (c0.y * c2.z - c2.y * c0.z)
710            + c2.x * (c0.y * c1.z - c1.y * c0.z);
711
712        let det = c0.x * a00 + c1.x * a10 + c2.x * a20 + c3.x * a30;
713        if det.abs() < crate::math::EPSILON {
714            return None;
715        }
716        let inv_det = 1.0 / det;
717
718        Some(Self::from_cols(
719            Vec4::new(a00 * inv_det, a10 * inv_det, a20 * inv_det, a30 * inv_det),
720            Vec4::new(a01 * inv_det, a11 * inv_det, a21 * inv_det, a31 * inv_det),
721            Vec4::new(a02 * inv_det, a12 * inv_det, a22 * inv_det, a32 * inv_det),
722            Vec4::new(a03 * inv_det, a13 * inv_det, a23 * inv_det, a33 * inv_det),
723        ))
724    }
725
726    /// Computes the inverse of an affine transformation matrix more efficiently
727    /// and with better numerical stability than the general `inverse` method.
728    ///
729    /// An affine matrix is one composed of only translation, rotation, and scale.
730    ///
731    /// # Returns
732    ///
733    /// `None` if the matrix is not affine or is not invertible.
734    #[inline]
735    pub fn affine_inverse(&self) -> Option<Self> {
736        let c0 = self.cols[0].truncate();
737        let c1 = self.cols[1].truncate();
738        let c2 = self.cols[2].truncate();
739        let translation = self.cols[3].truncate();
740        let det3x3 = c0.x * (c1.y * c2.z - c2.y * c1.z) - c1.x * (c0.y * c2.z - c2.y * c0.z)
741            + c2.x * (c0.y * c1.z - c1.y * c0.z);
742
743        if det3x3.abs() < crate::math::EPSILON {
744            return None;
745        }
746
747        let inv_det3x3 = 1.0 / det3x3;
748        let inv00 = (c1.y * c2.z - c2.y * c1.z) * inv_det3x3;
749        let inv10 = -(c2.y * c0.z - c0.y * c2.z) * inv_det3x3;
750        let inv20 = (c0.y * c1.z - c1.y * c0.z) * inv_det3x3;
751        let inv01 = -(c2.x * c1.z - c1.x * c2.z) * inv_det3x3;
752        let inv11 = (c0.x * c2.z - c2.x * c0.z) * inv_det3x3;
753        let inv21 = -(c1.x * c0.z - c0.x * c1.z) * inv_det3x3;
754        let inv02 = (c1.x * c2.y - c2.x * c1.y) * inv_det3x3;
755        let inv12 = -(c2.x * c0.y - c0.x * c2.y) * inv_det3x3;
756        let inv22 = (c0.x * c1.y - c1.x * c0.y) * inv_det3x3;
757        let inv_tx = -(inv00 * translation.x + inv01 * translation.y + inv02 * translation.z);
758        let inv_ty = -(inv10 * translation.x + inv11 * translation.y + inv12 * translation.z);
759        let inv_tz = -(inv20 * translation.x + inv21 * translation.y + inv22 * translation.z);
760
761        Some(Self::from_cols(
762            Vec4::new(inv00, inv10, inv20, 0.0),
763            Vec4::new(inv01, inv11, inv21, 0.0),
764            Vec4::new(inv02, inv12, inv22, 0.0),
765            Vec4::new(inv_tx, inv_ty, inv_tz, 1.0),
766        ))
767    }
768}
769
770// --- Operators Overloading ---
771
772impl Default for Mat4 {
773    /// Returns the 4x4 identity matrix.
774    #[inline]
775    fn default() -> Self {
776        Self::IDENTITY
777    }
778}
779
780impl Mul<Mat4> for Mat4 {
781    type Output = Self;
782    /// Multiplies this matrix by another `Mat4`. Note that matrix multiplication is not commutative.
783    #[inline]
784    fn mul(self, rhs: Mat4) -> Self::Output {
785        let mut result_cols = [Vec4 {
786            x: 0.0,
787            y: 0.0,
788            z: 0.0,
789            w: 0.0,
790        }; 4];
791        for (c_idx, target_col_ref_mut) in result_cols.iter_mut().enumerate().take(4) {
792            let col_from_rhs = rhs.cols[c_idx];
793            *target_col_ref_mut = Vec4 {
794                x: self.get_row(0).dot(col_from_rhs),
795                y: self.get_row(1).dot(col_from_rhs),
796                z: self.get_row(2).dot(col_from_rhs),
797                w: self.get_row(3).dot(col_from_rhs),
798            };
799        }
800        Mat4 { cols: result_cols }
801    }
802}
803
804impl Mul<Vec4> for Mat4 {
805    type Output = Vec4;
806    /// Transforms a `Vec4` by this matrix.
807    #[inline]
808    fn mul(self, rhs: Vec4) -> Self::Output {
809        self.cols[0] * rhs.x + self.cols[1] * rhs.y + self.cols[2] * rhs.z + self.cols[3] * rhs.w
810    }
811}
812
813// --- Tests ---
814
815#[cfg(test)]
816mod tests {
817    use super::*;
818    use crate::math::{approx_eq, matrix::Mat4, quaternion::Quaternion, vector::Vec3, PI};
819
820    fn vec3_approx_eq(a: Vec3, b: Vec3) -> bool {
821        approx_eq(a.x, b.x) && approx_eq(a.y, b.y) && approx_eq(a.z, b.z)
822    }
823
824    fn mat3_approx_eq(a: Mat3, b: Mat3) -> bool {
825        vec3_approx_eq(a.cols[0], b.cols[0])
826            && vec3_approx_eq(a.cols[1], b.cols[1])
827            && vec3_approx_eq(a.cols[2], b.cols[2])
828    }
829
830    fn vec4_approx_eq(a: Vec4, b: Vec4) -> bool {
831        approx_eq(a.x, b.x) && approx_eq(a.y, b.y) && approx_eq(a.z, b.z) && approx_eq(a.w, b.w)
832    }
833
834    fn mat4_approx_eq(a: Mat4, b: Mat4) -> bool {
835        vec4_approx_eq(a.cols[0], b.cols[0])
836            && vec4_approx_eq(a.cols[1], b.cols[1])
837            && vec4_approx_eq(a.cols[2], b.cols[2])
838            && vec4_approx_eq(a.cols[3], b.cols[3])
839    }
840
841    // --- Tests for Mat3 ---
842
843    #[test]
844    fn test_mat3_identity_default() {
845        assert_eq!(Mat3::default(), Mat3::IDENTITY);
846
847        let m = Mat3::from_scale(Vec3::new(1.0, 2.0, 3.0));
848        assert!(mat3_approx_eq(m * Mat3::IDENTITY, m));
849        assert!(mat3_approx_eq(Mat3::IDENTITY * m, m));
850    }
851
852    #[test]
853    fn test_mat3_from_scale() {
854        let s = Vec3::new(2.0, -3.0, 0.5);
855        let m = Mat3::from_scale(s);
856        let v = Vec3::new(1.0, 1.0, 1.0);
857        assert!(vec3_approx_eq(m * v, s)); // Scaling (1,1,1) should yield the scale vector
858    }
859
860    #[test]
861    fn test_mat3_rotations() {
862        let angle = PI / 6.0; // 30 degrees
863        let mx = Mat3::from_rotation_x(angle);
864        let my = Mat3::from_rotation_y(angle);
865        let mz = Mat3::from_rotation_z(angle);
866
867        let p = Vec3::Y; // Point on Y axis
868        let expected_px = Vec3::new(0.0, angle.cos(), angle.sin());
869        assert!(vec3_approx_eq(mx * p, expected_px));
870
871        let p = Vec3::X; // Point on X axis
872        let expected_py = Vec3::new(angle.cos(), 0.0, -angle.sin()); // RH
873        assert!(vec3_approx_eq(my * p, expected_py));
874
875        let p = Vec3::X; // Point on X axis
876        let expected_pz = Vec3::new(angle.cos(), angle.sin(), 0.0);
877        assert!(vec3_approx_eq(mz * p, expected_pz));
878    }
879
880    #[test]
881    fn test_mat3_from_axis_angle() {
882        let axis = Vec3::new(1.0, 1.0, 1.0).normalize();
883        let angle = 1.2 * PI;
884        let m = Mat3::from_axis_angle(axis, angle);
885
886        // Rotation around (1,1,1) axis should permute basis vectors
887        let v = Vec3::X;
888        let v_rotated = m * v;
889
890        // Check if length is preserved
891        assert!(approx_eq(v_rotated.length(), v.length()));
892
893        // Specific check is hard without known values, but ensure it's not identity or zero
894        assert!(v_rotated.distance_squared(v) > EPSILON);
895    }
896
897    #[test]
898    fn test_mat3_from_quat() {
899        let axis = Vec3::new(1.0, -2.0, 3.0).normalize();
900        let angle = PI / 7.0;
901        let q = Quaternion::from_axis_angle(axis, angle);
902        let m_from_q = Mat3::from_quat(q);
903
904        let v = Vec3::new(0.5, 1.0, -0.2);
905        let v_rotated_q = q * v;
906        let v_rotated_m = m_from_q * v;
907
908        assert!(vec3_approx_eq(v_rotated_q, v_rotated_m));
909    }
910
911    #[test]
912    fn test_mat3_determinant() {
913        assert!(approx_eq(Mat3::IDENTITY.determinant(), 1.0));
914        assert!(approx_eq(Mat3::ZERO.determinant(), 0.0));
915
916        let m_scale = Mat3::from_scale(Vec3::new(2.0, 3.0, 4.0));
917        assert!(approx_eq(m_scale.determinant(), 24.0));
918
919        let m_rot = Mat3::from_rotation_y(PI / 5.0);
920        assert!(approx_eq(m_rot.determinant(), 1.0)); // Rotations preserve volume
921    }
922
923    #[test]
924    fn test_mat3_transpose() {
925        let m = Mat3::from_cols(
926            Vec3::new(1.0, 2.0, 3.0),
927            Vec3::new(4.0, 5.0, 6.0),
928            Vec3::new(7.0, 8.0, 9.0),
929        );
930        let mt = m.transpose();
931        let expected_mt = Mat3::from_cols(
932            Vec3::new(1.0, 4.0, 7.0),
933            Vec3::new(2.0, 5.0, 8.0),
934            Vec3::new(3.0, 6.0, 9.0),
935        );
936
937        assert!(mat3_approx_eq(mt, expected_mt));
938        assert!(mat3_approx_eq(m.transpose().transpose(), m)); // Double transpose
939    }
940
941    #[test]
942    fn test_mat3_inverse() {
943        let m = Mat3::from_rotation_z(PI / 3.0) * Mat3::from_scale(Vec3::new(1.0, 2.0, 0.5));
944        let inv_m = m.inverse().expect("Matrix should be invertible");
945        let identity = m * inv_m;
946        assert!(
947            mat3_approx_eq(identity, Mat3::IDENTITY),
948            "M * inv(M) should be Identity"
949        );
950
951        let singular = Mat3::from_scale(Vec3::new(1.0, 0.0, 1.0));
952        assert!(
953            singular.inverse().is_none(),
954            "Singular matrix inverse should be None"
955        );
956    }
957
958    #[test]
959    fn test_mat3_mul_vec3() {
960        let m = Mat3::from_rotation_z(PI / 2.0); // Rotate 90 deg around Z
961        let v = Vec3::X; // (1, 0, 0)
962        let expected_v = Vec3::Y; // (0, 1, 0)
963        assert!(vec3_approx_eq(m * v, expected_v));
964    }
965
966    #[test]
967    fn test_mat3_mul_mat3() {
968        let rot90z = Mat3::from_rotation_z(PI / 2.0);
969        let rot180z = rot90z * rot90z;
970        let expected_rot180z = Mat3::from_rotation_z(PI);
971        assert!(mat3_approx_eq(rot180z, expected_rot180z));
972    }
973
974    #[test]
975    fn test_mat3_conversions() {
976        let m4 = Mat4::from_translation(Vec3::new(10., 20., 30.)) * Mat4::from_rotation_x(PI / 4.0);
977        let m3 = Mat3::from_mat4(&m4);
978        let m4_again = m3.to_mat4();
979
980        // Check if rotation part was extracted correctly
981        let v = Vec3::Y;
982        let v_rot_m3 = m3 * v;
983        let v_rot_m4 = Mat4::from_rotation_x(PI / 4.0) * Vec4::from_vec3(v, 0.0); // Rotate as vector
984        assert!(vec3_approx_eq(v_rot_m3, v_rot_m4.truncate()));
985
986        // Check if embedding back into Mat4 worked (translation should be zero)
987        let origin = Vec4::new(0.0, 0.0, 0.0, 1.0);
988        let transformed_origin = m4_again * origin;
989        assert!(approx_eq(transformed_origin.x, 0.0));
990        assert!(approx_eq(transformed_origin.y, 0.0));
991        assert!(approx_eq(transformed_origin.z, 0.0));
992        assert!(approx_eq(transformed_origin.w, 1.0));
993    }
994
995    #[test]
996    fn test_mat3_index() {
997        let mut m = Mat3::from_cols(Vec3::X, Vec3::Y, Vec3::Z);
998        assert_eq!(m[0], Vec3::X);
999        assert_eq!(m[1], Vec3::Y);
1000        assert_eq!(m[2], Vec3::Z);
1001        m[0] = Vec3::ONE;
1002        assert_eq!(m.cols[0], Vec3::ONE);
1003    }
1004
1005    #[test]
1006    #[should_panic]
1007    fn test_mat3_index_out_of_bounds() {
1008        let m = Mat3::IDENTITY;
1009        let _ = m[3]; // Should panic
1010    }
1011
1012    // --- End of Mat3 Tests ---
1013
1014    // --- Tests for Mat4 ---
1015
1016    #[test]
1017    fn test_identity() {
1018        assert_eq!(Mat4::default(), Mat4::IDENTITY);
1019        let m = Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0));
1020        assert!(mat4_approx_eq(m * Mat4::IDENTITY, m));
1021        assert!(mat4_approx_eq(Mat4::IDENTITY * m, m));
1022    }
1023
1024    #[test]
1025    fn test_from_quat() {
1026        let axis = Vec3::new(1.0, 2.0, 3.0).normalize();
1027        let angle = PI / 5.0;
1028        let q = Quaternion::from_axis_angle(axis, angle);
1029        let m_from_q = Mat4::from_quat(q);
1030
1031        let v = Vec3::new(5.0, -1.0, 2.0);
1032
1033        let v_rotated_q = q * v; // Rotate using quaternion directly
1034                                 // Rotate using matrix: convert v to Vec4(point), multiply, convert back
1035        let v4 = Vec4::from_vec3(v, 1.0);
1036        let v_rotated_m4 = m_from_q * v4;
1037        let v_rotated_m = v_rotated_m4.truncate();
1038
1039        // Compare results
1040        assert!(approx_eq(v_rotated_q.x, v_rotated_m.x));
1041        assert!(approx_eq(v_rotated_q.y, v_rotated_m.y));
1042        assert!(approx_eq(v_rotated_q.z, v_rotated_m.z));
1043    }
1044
1045    #[test]
1046    fn test_translation() {
1047        let t = Vec3::new(1.0, 2.0, 3.0);
1048        let m = Mat4::from_translation(t);
1049        let p = Vec4::new(1.0, 1.0, 1.0, 1.0);
1050        let expected_p = Vec4::new(2.0, 3.0, 4.0, 1.0);
1051
1052        assert!(vec4_approx_eq(m * p, expected_p));
1053    }
1054
1055    #[test]
1056    fn test_scale() {
1057        let s = Vec3::new(2.0, 3.0, 4.0);
1058        let m = Mat4::from_scale(s);
1059        let p = Vec4::new(1.0, 1.0, 1.0, 1.0);
1060        let expected_p = Vec4::new(2.0, 3.0, 4.0, 1.0);
1061        assert!(vec4_approx_eq(m * p, expected_p));
1062    }
1063
1064    #[test]
1065    fn test_rotation_x() {
1066        let angle = PI / 2.0; // 90 degrees
1067        let m = Mat4::from_rotation_x(angle);
1068        let p = Vec4::new(0.0, 1.0, 0.0, 1.0); // Point on Y axis
1069        let expected_p = Vec4::new(0.0, 0.0, 1.0, 1.0); // Should rotate to Z axis
1070        assert!(vec4_approx_eq(m * p, expected_p));
1071    }
1072
1073    #[test]
1074    fn test_rotation_y() {
1075        let angle = PI / 2.0; // 90 degrees
1076        let m = Mat4::from_rotation_y(angle);
1077        let p = Vec4::new(1.0, 0.0, 0.0, 1.0); // Point on X axis
1078        let expected_p = Vec4::new(0.0, 0.0, -1.0, 1.0); // Should rotate to -Z axis
1079
1080        assert!(vec4_approx_eq(m * p, expected_p));
1081    }
1082
1083    #[test]
1084    fn test_rotation_z() {
1085        let angle = PI / 2.0; // 90 degrees
1086        let m = Mat4::from_rotation_z(angle);
1087        let p = Vec4::new(1.0, 0.0, 0.0, 1.0); // Point on X axis
1088        let expected_p = Vec4::new(0.0, 1.0, 0.0, 1.0); // Should rotate to Y axis
1089        assert!(vec4_approx_eq(m * p, expected_p));
1090    }
1091
1092    #[test]
1093    fn test_transpose() {
1094        let m = Mat4::from_cols(
1095            Vec4::new(1., 2., 3., 4.),
1096            Vec4::new(5., 6., 7., 8.),
1097            Vec4::new(9., 10., 11., 12.),
1098            Vec4::new(13., 14., 15., 16.),
1099        );
1100        let mt = m.transpose();
1101        let expected_mt = Mat4::from_cols(
1102            Vec4::new(1., 5., 9., 13.),
1103            Vec4::new(2., 6., 10., 14.),
1104            Vec4::new(3., 7., 11., 15.),
1105            Vec4::new(4., 8., 12., 16.),
1106        );
1107        assert_eq!(mt.cols[0], expected_mt.cols[0]); // Compare columns after transpose
1108        assert_eq!(mt.cols[1], expected_mt.cols[1]);
1109        assert_eq!(mt.cols[2], expected_mt.cols[2]);
1110        assert_eq!(mt.cols[3], expected_mt.cols[3]);
1111
1112        // Test double transpose
1113        assert!(mat4_approx_eq(m.transpose().transpose(), m));
1114    }
1115
1116    #[test]
1117    fn test_mul_mat4() {
1118        let t = Mat4::from_translation(Vec3::new(1.0, 0.0, 0.0));
1119        let r = Mat4::from_rotation_z(PI / 2.0);
1120
1121        // Order matters: Translate then Rotate
1122        let tr = r * t;
1123        let p = Vec4::new(1.0, 0.0, 0.0, 1.0); // Point at (1,0,0)
1124                                               // 1. Translate: p becomes (2, 0, 0, 1)
1125                                               // 2. Rotate Z 90: (2, 0, 0) becomes (0, 2, 0)
1126        let expected_tr = Vec4::new(0.0, 2.0, 0.0, 1.0);
1127        assert!(vec4_approx_eq(tr * p, expected_tr));
1128
1129        // Order matters: Rotate then Translate
1130        let rt = t * r;
1131        // 1. Rotate Z 90: p becomes (0, 1, 0, 1)
1132        // 2. Translate: (0, 1, 0) becomes (1, 1, 0)
1133        let expected_rt = Vec4::new(1.0, 1.0, 0.0, 1.0);
1134        assert!(vec4_approx_eq(rt * p, expected_rt));
1135    }
1136
1137    #[test]
1138    fn test_inverse() {
1139        let m = Mat4::from_translation(Vec3::new(1., 2., 3.))
1140            * Mat4::from_rotation_y(PI / 4.0)
1141            * Mat4::from_scale(Vec3::new(1., 2., 1.));
1142
1143        let inv_m = m.inverse().expect("Matrix should be invertible");
1144        let identity = m * inv_m;
1145
1146        // Check if M * M^-1 is close to identity
1147        assert!(
1148            mat4_approx_eq(identity, Mat4::IDENTITY),
1149            "M * inv(M) should be Identity"
1150        );
1151
1152        // Check singular matrix (e.g., scale with zero)
1153        let singular = Mat4::from_scale(Vec3::new(1.0, 0.0, 1.0));
1154        assert!(
1155            singular.inverse().is_none(),
1156            "Singular matrix inverse should be None"
1157        );
1158    }
1159
1160    #[test]
1161    fn test_affine_inverse() {
1162        let t = Mat4::from_translation(Vec3::new(1., 2., 3.));
1163        let r = Mat4::from_rotation_y(PI / 3.0);
1164        let s = Mat4::from_scale(Vec3::new(1., 2., 0.5));
1165        let m = t * r * s; // Combined affine transform
1166
1167        let inv_m = m.inverse().expect("Matrix should be invertible");
1168        let affine_inv_m = m
1169            .affine_inverse()
1170            .expect("Matrix should be affine invertible");
1171
1172        // Check if affine inverse matches general inverse for this case
1173        assert!(
1174            mat4_approx_eq(inv_m, affine_inv_m),
1175            "Affine inverse should match general inverse"
1176        );
1177
1178        // Check M * inv(M) == Identity using affine inverse
1179        let identity = m * affine_inv_m;
1180        assert!(
1181            mat4_approx_eq(identity, Mat4::IDENTITY),
1182            "M * affine_inv(M) should be Identity"
1183        );
1184
1185        // Test singular affine matrix
1186        let singular_s = Mat4::from_scale(Vec3::new(1.0, 0.0, 1.0));
1187        let singular_m = t * singular_s;
1188        assert!(
1189            singular_m.affine_inverse().is_none(),
1190            "Singular affine matrix inverse should be None"
1191        );
1192    }
1193
1194    #[test]
1195    fn test_perspective_rh_zo() {
1196        let fov = PI / 4.0; // 45 degrees
1197        let aspect = 16.0 / 9.0;
1198        let near = 0.1;
1199        let far = 100.0;
1200
1201        let m = Mat4::perspective_rh_zo(fov, aspect, near, far);
1202        assert!(approx_eq(m.cols[0].x, 1.0 / (aspect * (fov / 2.0).tan())));
1203        assert!(approx_eq(m.cols[1].y, 1.0 / ((fov / 2.0).tan())));
1204        assert!(approx_eq(m.cols[2].z, -far / (far - near)));
1205        assert!(approx_eq(m.cols[3].z, -(far * near) / (far - near)));
1206    }
1207
1208    #[test]
1209    fn test_orthographic_rh_zo() {
1210        let left = -1.0;
1211        let right = 1.0;
1212        let bottom = -1.0;
1213        let top = 1.0;
1214        let near = 0.1;
1215        let far = 100.0;
1216        let m = Mat4::orthographic_rh_zo(left, right, bottom, top, near, far);
1217
1218        // Check scale factors
1219        assert!(approx_eq(m.cols[0].x, 2.0 / (right - left)));
1220        assert!(approx_eq(m.cols[1].y, 2.0 / (top - bottom)));
1221        assert!(approx_eq(m.cols[2].z, -1.0 / (far - near)));
1222
1223        // Check translation factors
1224        assert!(approx_eq(m.cols[3].x, -(right + left) / (right - left)));
1225        assert!(approx_eq(m.cols[3].y, -(top + bottom) / (top - bottom)));
1226        assert!(approx_eq(m.cols[3].z, -near / (far - near))); // -near and not -(far + near)
1227    }
1228
1229    #[test]
1230    fn test_look_at_rh() {
1231        let eye = Vec3::new(0.0, 0.0, 5.0);
1232        let target = Vec3::new(0.0, 0.0, 0.0);
1233        let up = Vec3::new(0.0, 1.0, 0.0);
1234
1235        let m = Mat4::look_at_rh(eye, target, up).expect("look_at_rh should return Some(Mat4)");
1236
1237        // Forward direction (third column, third row): should be +1.0 for a right-handed system
1238        assert!(approx_eq(m.cols[2].z, 1.0));
1239
1240        // Translation part (fourth column, third row): should be -eye ยท forward = -5.0
1241        assert!(approx_eq(m.cols[3].z, -5.0));
1242    }
1243
1244    #[test]
1245    fn test_look_at_rh_invalid() {
1246        let eye = Vec3::new(0.0, 0.0, 5.0);
1247        let target = Vec3::new(0.0, 0.0, 5.0); // Same as eye
1248        let up = Vec3::new(0.0, 1.0, 0.0);
1249
1250        // This should panic or return None (depending on implementation)
1251        assert!(Mat4::look_at_rh(eye, target, up).is_none());
1252    }
1253
1254    // --- End of Tests For Mat4 ---
1255}