retrofire_core/math/
mat.rs

1#![allow(clippy::needless_range_loop)]
2
3//! Matrices and linear and affine transforms.
4//!
5//! TODO Docs
6
7use core::{
8    array,
9    fmt::{self, Debug, Formatter},
10    marker::PhantomData as Pd,
11    ops::Range,
12};
13
14use crate::render::{NdcToScreen, ViewToProj};
15
16use super::{
17    approx::ApproxEq,
18    float::f32,
19    point::{Point2, Point2u, Point3, pt2},
20    space::{Linear, Proj3, Real},
21    vec::{ProjVec3, Vec2, Vec3, Vector, vec2, vec3},
22};
23
24/// A linear transform from one space (or basis) to another.
25///
26/// This is a tag trait with no functionality in itself. It is used to
27/// statically ensure that only compatible maps can be composed, and that
28/// only compatible vectors can be transformed.
29pub trait LinearMap {
30    /// The source space, or domain, of `Self`.
31    type Source;
32    /// The destination space, or range, of `Self`.
33    type Dest;
34}
35
36/// Composition of two `LinearMap`s, `Self` ∘ `Inner`.
37///
38/// If `Self` maps from `B` to `C`, and `Inner` maps from `A` to `B`,
39/// `Self::Result` maps from `A` to `C`.
40pub trait Compose<Inner: LinearMap>: LinearMap<Source = Inner::Dest> {
41    /// The result of composing `Self` with `Inner`.
42    type Result: LinearMap<Source = Inner::Source, Dest = Self::Dest>;
43}
44
45/// Trait for applying a transform to a type.
46pub trait Apply<T> {
47    /// The transform codomain type.
48    type Output;
49
50    /// Applies this transform to a value.
51    #[must_use]
52    fn apply(&self, t: &T) -> Self::Output;
53}
54
55/// A change of basis in real vector space of dimension `DIM`.
56#[derive(Copy, Clone, Default, Eq, PartialEq)]
57pub struct RealToReal<const DIM: usize, SrcBasis = (), DstBasis = ()>(
58    Pd<(SrcBasis, DstBasis)>,
59);
60
61/// Mapping from real to projective space.
62#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
63pub struct RealToProj<SrcBasis>(Pd<SrcBasis>);
64
65/// A generic matrix type.
66#[repr(transparent)]
67#[derive(Copy, Eq, PartialEq)]
68pub struct Matrix<Repr, Map>(pub Repr, Pd<Map>);
69
70/// Type alias for a 2x2 float matrix.
71pub type Mat2x2<Map = ()> = Matrix<[[f32; 2]; 2], Map>;
72/// Type alias for a 3x3 float matrix.
73pub type Mat3x3<Map = ()> = Matrix<[[f32; 3]; 3], Map>;
74/// Type alias for a 4x4 float matrix.
75pub type Mat4x4<Map = ()> = Matrix<[[f32; 4]; 4], Map>;
76
77//
78// Inherent impls
79//
80
81/// Slight syntactic sugar for creating [`Matrix`] instances.
82///
83/// # Examples
84/// ```
85/// use retrofire_core::{mat, math::Mat3x3};
86///
87/// let m: Mat3x3 = mat![
88///     0.0, 2.0, 0.0;
89///     1.0, 0.0, 0.0;
90///     0.0, 0.0, 3.0;
91/// ];
92/// assert_eq!(m.0, [
93///     [0.0, 2.0, 0.0],
94///     [1.0, 0.0, 0.0],
95///     [0.0, 0.0, 3.0]
96/// ]);
97/// ```
98#[macro_export]
99macro_rules! mat {
100    ( $( $( $elem:expr ),+ );+ $(;)? ) => {
101        $crate::math::mat::Matrix::new([
102            $([$($elem),+]),+
103        ])
104    };
105}
106
107impl<Repr, Map> Matrix<Repr, Map> {
108    /// Returns a matrix with the given elements.
109    #[inline]
110    pub const fn new(els: Repr) -> Self {
111        Self(els, Pd)
112    }
113
114    /// Returns a matrix equal to `self` but with mapping `M`.
115    ///
116    /// This method can be used to coerce a matrix to a different
117    /// mapping in case it is needed to make types match.
118    #[inline]
119    pub fn to<M>(&self) -> Matrix<Repr, M>
120    where
121        Repr: Clone,
122    {
123        Matrix(self.0.clone(), Pd)
124    }
125}
126
127impl<Sc, const N: usize, const M: usize, Map> Matrix<[[Sc; N]; M], Map>
128where
129    Sc: Copy,
130    Map: LinearMap,
131{
132    /// Returns the row vector of `self` with index `i`.
133    ///
134    /// The returned vector is in space `Map::Source`.
135    ///
136    /// # Panics
137    /// If `i >= M`.
138    #[inline]
139    pub fn row_vec(&self, i: usize) -> Vector<[Sc; N], Map::Source> {
140        Vector::new(self.0[i])
141    }
142    /// Returns the column vector of `self` with index `i`.
143    ///
144    /// The returned vector is in space `Map::Dest`.
145    ///
146    /// # Panics
147    /// If `i >= N`.
148    #[inline]
149    pub fn col_vec(&self, i: usize) -> Vector<[Sc; M], Map::Dest> {
150        Vector::new(self.0.map(|row| row[i]))
151    }
152}
153impl<Sc: Copy, const N: usize, const DIM: usize, S, D>
154    Matrix<[[Sc; N]; N], RealToReal<DIM, S, D>>
155{
156    /// Returns `self` with its rows and columns swapped.
157    pub fn transpose(self) -> Matrix<[[Sc; N]; N], RealToReal<DIM, D, S>> {
158        const { assert!(N >= DIM, "map dimension >= matrix dimension") }
159        array::from_fn(|j| array::from_fn(|i| self.0[i][j])).into()
160    }
161}
162
163impl<const N: usize, Map> Matrix<[[f32; N]; N], Map> {
164    /// Returns the `N`×`N` identity matrix.
165    ///
166    /// An identity matrix is a square matrix with ones on the main diagonal
167    /// and zeroes everywhere else:
168    /// ```text
169    ///         ⎛ 1  0  ⋯  0 ⎞
170    ///  I  =   ⎜ 0  1       ⎟
171    ///         ⎜ ⋮     ⋱  0 ⎟
172    ///         ⎝ 0     0  1 ⎠
173    /// ```
174    /// It is the neutral element of matrix multiplication:
175    /// **A · I** = **I · A** = **A**, as well as matrix-vector
176    /// multiplication: **I·v** = **v**.
177
178    pub const fn identity() -> Self {
179        let mut els = [[0.0; N]; N];
180        let mut i = 0;
181        while i < N {
182            els[i][i] = 1.0;
183            i += 1;
184        }
185        Self::new(els)
186    }
187
188    /// Returns whether every element of `self` is finite
189    /// (ie. neither `Inf`, `-Inf`, or `NaN`).
190    fn is_finite(&self) -> bool {
191        self.0.iter().flatten().all(|e| e.is_finite())
192    }
193}
194
195impl Mat4x4 {
196    /// Constructs a matrix from a linear basis.
197    ///
198    /// The basis does not have to be orthonormal.
199    pub const fn from_linear<S, D>(
200        i: Vec3<D>,
201        j: Vec3<D>,
202        k: Vec3<D>,
203    ) -> Mat4x4<RealToReal<3, S, D>> {
204        Self::from_affine(i, j, k, Point3::origin())
205    }
206
207    /// Constructs a matrix from an affine basis, or frame.
208    ///
209    /// The basis does not have to be orthonormal.
210    pub const fn from_affine<S, D>(
211        i: Vec3<D>,
212        j: Vec3<D>,
213        k: Vec3<D>,
214        o: Point3<D>,
215    ) -> Mat4x4<RealToReal<3, S, D>> {
216        let (o, i, j, k) = (o.0, i.0, j.0, k.0);
217        mat![
218            i[0], j[0], k[0], o[0];
219            i[1], j[1], k[1], o[1];
220            i[2], j[2], k[2], o[2];
221            0.0, 0.0, 0.0, 1.0
222        ]
223    }
224}
225
226impl<Sc, const N: usize, Map> Matrix<[[Sc; N]; N], Map>
227where
228    Sc: Linear<Scalar = Sc> + Copy,
229    Map: LinearMap,
230{
231    /// Returns the composite transform of `self` and `other`.
232    ///
233    /// Computes the matrix product of `self` and `other` such that applying
234    /// the resulting transformation is equivalent to first applying `other`
235    /// and then `self`. More succinctly,
236    /// ```text
237    /// (𝗠 ∘ 𝗡) 𝘃 = 𝗠(𝗡 𝘃)
238    /// ```
239    /// for some matrices 𝗠 and 𝗡 and a vector 𝘃.
240    #[must_use]
241    pub fn compose<Inner: LinearMap>(
242        &self,
243        other: &Matrix<[[Sc; N]; N], Inner>,
244    ) -> Matrix<[[Sc; N]; N], <Map as Compose<Inner>>::Result>
245    where
246        Map: Compose<Inner>,
247    {
248        let cols: [_; N] = array::from_fn(|i| other.col_vec(i));
249        array::from_fn(|j| {
250            let row = self.row_vec(j);
251            array::from_fn(|i| row.dot(&cols[i]))
252        })
253        .into()
254    }
255    /// Returns the composite transform of `other` and `self`.
256    ///
257    /// Computes the matrix product of `self` and `other` such that applying
258    /// the resulting matrix is equivalent to first applying `self` and then
259    /// `other`. The call `self.then(other)` is thus equivalent to
260    /// `other.compose(self)`.
261    #[must_use]
262    pub fn then<Outer: Compose<Map>>(
263        &self,
264        other: &Matrix<[[Sc; N]; N], Outer>,
265    ) -> Matrix<[[Sc; N]; N], <Outer as Compose<Map>>::Result> {
266        other.compose(self)
267    }
268}
269
270impl<Src, Dest> Mat2x2<RealToReal<2, Src, Dest>> {
271    /// Returns the determinant of `self`.
272    ///
273    /// # Examples
274    /// ```
275    /// use retrofire_core::math::{Mat2x2, mat::RealToReal};
276    ///
277    /// let double: Mat2x2<RealToReal<2>> = [[2.0, 0.0], [0.0, 2.0]].into();
278    /// assert_eq!(double.determinant(), 4.0);
279    ///
280    /// let singular: Mat2x2<RealToReal<2>> = [[1.0, 0.0], [2.0, 0.0]].into();
281    /// assert_eq!(singular.determinant(), 0.0);
282    /// ```
283    pub const fn determinant(&self) -> f32 {
284        let [[a, b], [c, d]] = self.0;
285        a * d - b * c
286    }
287
288    /// Returns the [inverse][Self::inverse] of `self`, or `None` if `self`
289    /// is not invertible.
290    ///
291    /// A matrix is invertible if and only if its [determinant][Self::determinant]
292    /// is nonzero. A non-invertible matrix is also called singular.
293    ///
294    /// # Examples
295    /// ```
296    /// use retrofire_core::math::{Mat2x2, mat::RealToReal};
297    ///
298    /// let rotate_90: Mat2x2<RealToReal<2>> = [[0.0, -1.0], [1.0, 0.0]].into();
299    /// let rotate_neg_90 = rotate_90.checked_inverse();
300    ///
301    /// assert_eq!(rotate_neg_90, Some([[0.0, 1.0], [-1.0, 0.0]].into()));
302    ///
303    /// let singular: Mat2x2<RealToReal<2>> = [[1.0, 0.0], [2.0, 0.0]].into();
304    /// assert_eq!(singular.checked_inverse(), None);
305    /// ```
306    #[must_use]
307    pub const fn checked_inverse(
308        &self,
309    ) -> Option<Mat2x2<RealToReal<2, Dest, Src>>> {
310        let det = self.determinant();
311        // No approx_eq in const :/
312        if det.abs() < 1e-6 {
313            return None;
314        }
315        let r_det = 1.0 / det;
316        let [[a, b], [c, d]] = self.0;
317        Some(mat![
318            r_det * d, r_det * -b;
319            r_det * -c, r_det * a
320        ])
321    }
322
323    /// Returns the inverse of `self`, if it exists.
324    ///
325    /// A matrix is invertible if and only if its [determinant][Self::determinant]
326    /// is nonzero. A non-invertible matrix is also called singular.
327    ///
328    /// # Panics
329    /// If `self` has no inverse.
330    ///
331    /// # Examples
332    /// ```
333    /// use retrofire_core::math::{Mat2x2, mat::RealToReal, vec2};
334    ///
335    /// let rotate_90: Mat2x2<RealToReal<2>> = [[0.0, -1.0], [1.0, 0.0]].into();
336    /// let rotate_neg_90 = rotate_90.inverse();
337    ///
338    /// assert_eq!(rotate_neg_90.0, [[0.0, 1.0], [-1.0, 0.0]]);
339    /// assert_eq!(rotate_90.then(&rotate_neg_90), Mat2x2::identity())
340    /// ```
341    /// ```should_panic
342    /// # use retrofire_core::math::{Mat2x2, mat::RealToReal};
343    ///
344    /// // This matrix has no inverse
345    /// let singular: Mat2x2<RealToReal<2>> = [[1.0, 0.0], [2.0, 0.0]].into();
346    ///
347    /// // This will panic
348    /// let _ = singular.inverse();
349    /// ```
350    #[must_use]
351    pub const fn inverse(&self) -> Mat2x2<RealToReal<2, Dest, Src>> {
352        self.checked_inverse()
353            .expect("matrix cannot be singular or near-singular")
354    }
355}
356
357impl<Src, Dest> Mat3x3<RealToReal<2, Src, Dest>> {
358    /// Returns the determinant of `self`.
359    pub const fn determinant(&self) -> f32 {
360        let [a, b, c] = self.0[0];
361
362        // assert!(g == 0.0 && h == 0.0 && i == 1.0);
363        // TODO If affine (as should be), reduces to:
364        // a * e - b * d
365
366        a * self.cofactor(0, 0)
367            + b * self.cofactor(0, 1)
368            + c * self.cofactor(0, 2)
369    }
370
371    /// Returns the cofactor of the element at the given row and column.
372    ///
373    /// Cofactors are used to compute the inverse of a matrix. A cofactor is
374    /// calculated as follows:
375    ///
376    /// 1. Remove the given row and column from `self` to get a 2x2 submatrix;
377    /// 2. Compute its determinant;
378    /// 3. If exactly one of `row` and `col` is even, multiply by -1.
379    ///
380    /// # Examples
381    /// ```
382    /// use retrofire_core::{mat, math::Mat3x3, math::mat::RealToReal};
383    ///
384    /// let mat: Mat3x3<RealToReal<2>> = mat![
385    ///     1.0, 2.0, 3.0;
386    ///     4.0, 5.0, 6.0;
387    ///     7.0, 8.0, 9.0
388    /// ];
389    /// // Remove row 0 and col 1, giving [[4.0, 6.0], [7.0, 9.0]].
390    /// // The determinant of this submatrix is 4.0 * 7.0 - 6.0 * 9.0.
391    /// // Multiply by -1 because row is even and col is odd.
392    /// assert_eq!(mat.cofactor(0, 1), 6.0 * 7.0 - 4.0 * 9.0);
393    /// ```
394    #[inline]
395    pub const fn cofactor(&self, row: usize, col: usize) -> f32 {
396        // This automatically takes care of the negation
397        let r1 = (row + 1) % 3;
398        let r2 = (row + 2) % 3;
399        let c1 = (col + 1) % 3;
400        let c2 = (col + 2) % 3;
401        self.0[r1][c1] * self.0[r2][c2] - self.0[r1][c2] * self.0[r2][c1]
402    }
403
404    /// Returns the inverse of `self`, or `None` if `self` is singular.
405    #[must_use]
406    pub fn checked_inverse(&self) -> Option<Mat3x3<RealToReal<2, Dest, Src>>> {
407        let det = self.determinant();
408        if det.abs() < 1e-6 {
409            return None;
410        }
411
412        // Compute cofactors
413        let c_a = self.cofactor(0, 0); // = e
414        let c_b = self.cofactor(0, 1); // = d
415        let c_c = self.cofactor(0, 2); // = 0
416        let c_d = self.cofactor(1, 0); // = b
417        let c_e = self.cofactor(1, 1); // = a
418        let c_f = self.cofactor(1, 2); // = 0
419        let c_g = self.cofactor(2, 0); // = b * f - c * e
420        let c_h = self.cofactor(2, 1); // = a * f - c * d
421        let c_i = self.cofactor(2, 2); // = a * e - b * d
422
423        let r_det = 1.0 / det;
424        // Inverse is transpose of cofactor matrix, divided by determinant
425        let abc = r_det * vec3(c_a, c_d, c_g);
426        let def = r_det * vec3(c_b, c_e, c_h);
427        let ghi = r_det * vec3(c_c, c_f, c_i);
428
429        Some(Mat3x3::from_rows(abc, def, ghi))
430    }
431
432    pub fn inverse(&self) -> Mat3x3<RealToReal<2, Dest, Src>> {
433        self.checked_inverse()
434            .expect("matrix cannot be singular or near-singular")
435    }
436
437    const fn from_rows(i: Vec3<Src>, j: Vec3<Src>, k: Vec3<Src>) -> Self {
438        Self::new([i.0, j.0, k.0])
439    }
440}
441
442impl<Src, Dst> Mat4x4<RealToReal<3, Src, Dst>> {
443    /// Returns the determinant of `self`.
444    ///
445    /// Given a matrix M,
446    /// ```text
447    ///         ⎛ a  b  c  d ⎞
448    ///  M  =   ⎜ e  f  g  h ⎟
449    ///         ⎜ i  j  k  l ⎟
450    ///         ⎝ m  n  o  p ⎠
451    /// ```
452    /// its determinant can be computed by recursively computing the determinants
453    /// of sub-matrices on rows 1..4 and multiplying them by the elements on row 0:
454    /// ```text
455    ///              ⎜ f g h ⎜       ⎜ e g h ⎜
456    /// det(M) = a · ⎜ j k l ⎜ - b · ⎜ i k l ⎜  + - ···
457    ///              ⎜ n o p ⎜       ⎜ m o p ⎜
458    /// ```
459    pub fn determinant(&self) -> f32 {
460        let [[a, b, c, d], r, s, t] = self.0;
461        let det2 = |m, n| s[m] * t[n] - s[n] * t[m];
462        let det3 =
463            |j, k, l| r[j] * det2(k, l) - r[k] * det2(j, l) + r[l] * det2(j, k);
464
465        a * det3(1, 2, 3) - b * det3(0, 2, 3) + c * det3(0, 1, 3)
466            - d * det3(0, 1, 2)
467    }
468
469    /// Returns the inverse matrix of `self`.
470    ///
471    /// The inverse 𝝡<sup>-1</sup> of matrix 𝝡 is a matrix that, when
472    /// composed with 𝝡, results in the [identity](Self::identity) matrix:
473    ///
474    /// 𝝡 ∘ 𝝡<sup>-1</sup> = 𝝡<sup>-1</sup> ∘ 𝝡 = 𝐈
475    ///
476    /// In other words, it applies the transform of 𝝡 in reverse.
477    /// Given vectors 𝘃 and 𝘂,
478    ///
479    /// 𝝡𝘃 = 𝘂 ⇔ 𝝡<sup>-1</sup> 𝘂 = 𝘃.
480    ///
481    /// Only matrices with a nonzero determinant have a defined inverse.
482    /// A matrix without an inverse is said to be singular.
483    ///
484    /// Note: This method uses naive Gauss–Jordan elimination and may
485    /// suffer from imprecision or numerical instability in certain cases.
486    ///
487    /// # Panics
488    /// If debug assertions are enabled, panics if `self` is singular or near-singular.
489    /// If not enabled, the return value is unspecified and may contain non-finite
490    /// values (infinities and NaNs).
491    #[must_use]
492    pub fn inverse(&self) -> Mat4x4<RealToReal<3, Dst, Src>> {
493        use super::float::f32;
494        if cfg!(debug_assertions) {
495            let det = self.determinant();
496            assert!(
497                !det.approx_eq(&0.0),
498                "a singular, near-singular, or non-finite matrix does not \
499                 have a well-defined inverse (determinant = {det})"
500            );
501        }
502
503        // Elementary row operation subtracting one row,
504        // multiplied by a scalar, from another
505        fn sub_row(m: &mut Mat4x4, from: usize, to: usize, mul: f32) {
506            m.0[to] = (m.row_vec(to) - m.row_vec(from) * mul).0;
507        }
508
509        // Elementary row operation multiplying one row with a scalar
510        fn mul_row(m: &mut Mat4x4, row: usize, mul: f32) {
511            m.0[row] = (m.row_vec(row) * mul).0;
512        }
513
514        // Elementary row operation swapping two rows
515        fn swap_rows(m: &mut Mat4x4, r: usize, s: usize) {
516            m.0.swap(r, s);
517        }
518
519        // This algorithm attempts to reduce `this` to the identity matrix
520        // by simultaneously applying elementary row operations to it and
521        // another matrix `inv` which starts as the identity matrix. Once
522        // `this` is reduced, the value of `inv` has become the inverse of
523        // `this` and thus of `self`.
524
525        let inv = &mut Mat4x4::identity();
526        let this = &mut self.to();
527
528        // Apply row operations to reduce the matrix to an upper echelon form
529        for idx in 0..4 {
530            let pivot = (idx..4)
531                .max_by(|&r1, &r2| {
532                    let v1 = this.0[r1][idx].abs();
533                    let v2 = this.0[r2][idx].abs();
534                    v1.total_cmp(&v2)
535                })
536                .unwrap();
537
538            if this.0[pivot][idx] != 0.0 {
539                swap_rows(this, idx, pivot);
540                swap_rows(inv, idx, pivot);
541
542                let div = 1.0 / this.0[idx][idx];
543                for r in (idx + 1)..4 {
544                    let x = this.0[r][idx] * div;
545                    sub_row(this, idx, r, x);
546                    sub_row(inv, idx, r, x);
547                }
548            }
549        }
550        // now in upper echelon form, back-substitute variables
551        for &idx in &[3, 2, 1] {
552            let diag = this.0[idx][idx];
553            for r in 0..idx {
554                let x = this.0[r][idx] / diag;
555
556                sub_row(this, idx, r, x);
557                sub_row(inv, idx, r, x);
558            }
559        }
560        // normalize
561        for r in 0..4 {
562            let x = 1.0 / this.0[r][r];
563            mul_row(this, r, x);
564            mul_row(inv, r, x);
565        }
566        debug_assert!(inv.is_finite());
567        inv.to()
568    }
569}
570
571//
572// Local trait impls
573//
574
575impl<const DIM: usize, S, D> LinearMap for RealToReal<DIM, S, D> {
576    type Source = Real<DIM, S>;
577    type Dest = Real<DIM, D>;
578}
579
580impl<const DIM: usize, S, I, D> Compose<RealToReal<DIM, S, I>>
581    for RealToReal<DIM, I, D>
582{
583    type Result = RealToReal<DIM, S, D>;
584}
585
586impl<S> LinearMap for RealToProj<S> {
587    type Source = Real<3, S>;
588    type Dest = Proj3;
589}
590
591impl<S, I> Compose<RealToReal<3, S, I>> for RealToProj<I> {
592    type Result = RealToProj<S>;
593}
594
595/// Dummy `LinearMap` to help with generic code.
596impl LinearMap for () {
597    type Source = ();
598    type Dest = ();
599}
600
601impl<Repr, E, M> ApproxEq<Self, E> for Matrix<Repr, M>
602where
603    Repr: ApproxEq<Repr, E>,
604{
605    fn approx_eq_eps(&self, other: &Self, rel_eps: &E) -> bool {
606        self.0.approx_eq_eps(&other.0, rel_eps)
607    }
608
609    fn relative_epsilon() -> E {
610        Repr::relative_epsilon()
611    }
612}
613
614// Apply trait impls
615
616impl<Src, Dest> Apply<Vec2<Src>> for Mat2x2<RealToReal<2, Src, Dest>> {
617    type Output = Vec2<Dest>;
618
619    /// Maps a real 2-vector from basis `Src` to basis `Dst`.
620    ///
621    /// Computes the matrix–vector multiplication **MV** where **v** is
622    /// interpreted as a column vector:
623    ///
624    /// ```text
625    ///  Mv  =  ⎛ M00 M01 ⎞ ⎛ v0 ⎞  =  ⎛ v0' ⎞
626    ///         ⎝ M10 M11 ⎠ ⎝ v1 ⎠     ⎝ v1' ⎠
627    /// ```
628    fn apply(&self, v: &Vec2<Src>) -> Vec2<Dest> {
629        vec2(self.row_vec(0).dot(v), self.row_vec(1).dot(v))
630    }
631}
632
633impl<Src, Dest> Apply<Point2<Src>> for Mat2x2<RealToReal<2, Src, Dest>> {
634    type Output = Point2<Dest>;
635
636    /// Maps a real 2-vector from basis `Src` to basis `Dst`.
637    ///
638    /// Computes the matrix–point multiplication **M***p* where *p* is
639    /// interpreted as a column vector:
640    ///
641    /// ```text
642    ///  Mp  =  ⎛ M00 M01 ⎞ ⎛ v0 ⎞  =  ⎛ v0' ⎞
643    ///         ⎝ M10 M11 ⎠ ⎝ v1 ⎠     ⎝ v1' ⎠
644    /// ```
645    fn apply(&self, pt: &Point2<Src>) -> Point2<Dest> {
646        self.apply(&pt.to_vec()).to_pt()
647    }
648}
649
650impl<Src, Dest> Apply<Vec2<Src>> for Mat3x3<RealToReal<2, Src, Dest>> {
651    type Output = Vec2<Dest>;
652
653    /// Maps a real 2-vector from basis `Src` to basis `Dst`.
654    ///
655    /// Computes the matrix–vector multiplication 𝝡𝘃 where 𝘃 is interpreted as
656    /// a column vector with an implicit 𝘃<sub>2</sub> component with value 0:
657    ///
658    /// ```text
659    ///         ⎛ M00 ·  ·  ⎞ ⎛ v0 ⎞     ⎛ v0' ⎞
660    ///  Mv  =  ⎜  ·  ·  ·  ⎟ ⎜ v1 ⎟  =  ⎜ v1' ⎟
661    ///         ⎝  ·  · M22 ⎠ ⎝  0 ⎠     ⎝  0  ⎠
662    /// ```
663    fn apply(&self, v: &Vec2<Src>) -> Vec2<Dest> {
664        // TODO can't use vec3, as space has to be Real<2> to match row_vec
665        let v = Vector::new([v.x(), v.y(), 0.0]);
666        vec2(self.row_vec(0).dot(&v), self.row_vec(1).dot(&v))
667    }
668}
669
670impl<Src, Dest> Apply<Point2<Src>> for Mat3x3<RealToReal<2, Src, Dest>> {
671    type Output = Point2<Dest>;
672
673    /// Maps a real 2-point from basis `Src` to basis `Dst`.
674    ///
675    /// Computes the affine matrix–point multiplication 𝝡*p* where *p* is interpreted
676    /// as a column vector with an implicit *p*<sub>2</sub> component with value 1:
677    ///
678    /// ```text
679    ///         ⎛ M00 ·  ·  ⎞ ⎛ p0 ⎞     ⎛ p0' ⎞
680    ///  Mp  =  ⎜  ·  ·  ·  ⎟ ⎜ p1 ⎟  =  ⎜ p1' ⎟
681    ///         ⎝  ·  · M22 ⎠ ⎝  1 ⎠     ⎝  1  ⎠
682    /// ```
683    fn apply(&self, p: &Point2<Src>) -> Point2<Dest> {
684        let v = Vector::new([p.x(), p.y(), 1.0]);
685        pt2(self.row_vec(0).dot(&v), self.row_vec(1).dot(&v))
686    }
687}
688
689impl<Src, Dest> Apply<Vec3<Src>> for Mat3x3<RealToReal<3, Src, Dest>> {
690    type Output = Vec3<Dest>;
691
692    /// Maps a real 3-vector from basis `Src` to basis `Dst`.
693    ///
694    /// Computes the matrix–vector multiplication **Mv** where **v** is
695    /// interpreted as a column vector:
696    ///
697    /// ```text
698    ///         ⎛ M00 ·  ·  ⎞ ⎛ v0 ⎞     ⎛ v0' ⎞
699    ///  Mv  =  ⎜  ·  ·  ·  ⎟ ⎜ v1 ⎟  =  ⎜ v1' ⎟
700    ///         ⎝  ·  · M22 ⎠ ⎝ v2 ⎠     ⎝ v2' ⎠
701    /// ```
702    fn apply(&self, v: &Vec3<Src>) -> Vec3<Dest> {
703        vec3(
704            self.row_vec(0).dot(v),
705            self.row_vec(1).dot(v),
706            self.row_vec(2).dot(v),
707        )
708    }
709}
710
711impl<Src, Dest> Apply<Point3<Src>> for Mat3x3<RealToReal<3, Src, Dest>> {
712    type Output = Point3<Dest>;
713
714    /// Maps a real 3-point from basis `Src` to basis `Dst`.
715    ///
716    /// Computes the linear matrix–point multiplication **M***p* where *p* is
717    /// interpreted as a column vector:
718    ///
719    /// ```text
720    ///         ⎛ M00 ·  ·  ⎞ ⎛ p0 ⎞     ⎛ p0' ⎞
721    ///  Mp  =  ⎜  ·  ·  ·  ⎟ ⎜ p1 ⎟  =  ⎜ p1' ⎟
722    ///         ⎝  ·  · M22 ⎠ ⎝ p2 ⎠     ⎝ p2' ⎠
723    /// ```
724    fn apply(&self, p: &Point3<Src>) -> Point3<Dest> {
725        self.apply(&p.to_vec()).to_pt()
726    }
727}
728
729impl<Src, Dst> Apply<Vec3<Src>> for Mat4x4<RealToReal<3, Src, Dst>> {
730    type Output = Vec3<Dst>;
731
732    /// Maps a real 3-vector from basis `Src` to basis `Dst`.
733    ///
734    /// Computes the matrix–vector multiplication **Mv** where **v** is interpreted
735    /// as a column vector with an implicit **v**<sub>3</sub> component with value 0:
736    ///
737    /// ```text
738    ///         ⎛ M00 ·  ·  ·  ⎞ ⎛ v0 ⎞     ⎛ v0' ⎞
739    ///  Mv  =  ⎜  ·  ·  ·  ·  ⎟ ⎜ v1 ⎟  =  ⎜ v1' ⎟
740    ///         ⎜  ·  ·  ·  ·  ⎟ ⎜ v2 ⎟     ⎜ v2' ⎟
741    ///         ⎝  ·  ·  · M33 ⎠ ⎝  0 ⎠     ⎝  0  ⎠
742    /// ```
743    fn apply(&self, v: &Vec3<Src>) -> Vec3<Dst> {
744        let v = [v.x(), v.y(), v.z(), 0.0].into();
745        array::from_fn(|i| self.row_vec(i).dot(&v)).into()
746    }
747}
748
749impl<Src, Dst> Apply<Point3<Src>> for Mat4x4<RealToReal<3, Src, Dst>> {
750    type Output = Point3<Dst>;
751
752    /// Maps a real 3-point from basis `Src` to basis `Dst`.
753    ///
754    /// Computes the affine matrix–point multiplication 𝝡*p* where *p* is interpreted
755    /// as a column vector with an implicit *p*<sub>3</sub> component with value 1:
756    ///
757    /// ```text
758    ///         ⎛ M00 ·  ·  ·  ⎞ ⎛ p0 ⎞     ⎛ p0' ⎞
759    ///  Mp  =  ⎜  ·  ·  ·  ·  ⎟ ⎜ p1 ⎟  =  ⎜ p1' ⎟
760    ///         ⎜  ·  ·  ·  ·  ⎟ ⎜ p2 ⎟     ⎜ p2' ⎟
761    ///         ⎝  ·  ·  · M33 ⎠ ⎝  1 ⎠     ⎝  1  ⎠
762    /// ```
763    fn apply(&self, p: &Point3<Src>) -> Point3<Dst> {
764        let p = [p.x(), p.y(), p.z(), 1.0].into();
765        array::from_fn(|i| self.row_vec(i).dot(&p)).into()
766    }
767}
768
769impl<Src> Apply<Point3<Src>> for Mat4x4<RealToProj<Src>> {
770    type Output = ProjVec3;
771
772    /// Maps the real 3-point *p* from basis B to the projective 3-space.
773    ///
774    /// Computes the matrix–point multiplication **M***p* where *p* is interpreted
775    /// as a column vector with an implicit *p*<sub>3</sub> component with value 1:
776    ///
777    /// ```text
778    ///         ⎛ M00  ·  · ⎞ ⎛ p0 ⎞     ⎛ p0' ⎞
779    ///  Mp  =  ⎜    ·      ⎟ ⎜ p1 ⎟  =  ⎜ p1' ⎟
780    ///         ⎜      ·    ⎟ ⎜ p2 ⎟     ⎜ p2' ⎟
781    ///         ⎝ ·  ·  M33 ⎠ ⎝  1 ⎠     ⎝ p3' ⎠
782    /// ```
783    fn apply(&self, p: &Point3<Src>) -> ProjVec3 {
784        let v = Vector::new([p.x(), p.y(), p.z(), 1.0]);
785        array::from_fn(|i| self.row_vec(i).dot(&v)).into()
786    }
787}
788
789//
790// Foreign trait impls
791//
792
793impl<R: Clone, M> Clone for Matrix<R, M> {
794    fn clone(&self) -> Self {
795        self.to()
796    }
797}
798
799impl<const N: usize, Map> Default for Matrix<[[f32; N]; N], Map> {
800    /// Returns the `N`×`N` identity matrix.
801    fn default() -> Self {
802        Self::identity()
803    }
804}
805
806impl<S: Debug, Map: Debug + Default, const N: usize, const M: usize> Debug
807    for Matrix<[[S; N]; M], Map>
808{
809    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
810        writeln!(f, "Matrix<{:?}>[", Map::default())?;
811        for i in 0..M {
812            writeln!(f, "    {:4?}", self.0[i])?;
813        }
814        write!(f, "]")
815    }
816}
817
818impl<const DIM: usize, F, T> Debug for RealToReal<DIM, F, T>
819where
820    F: Debug + Default,
821    T: Debug + Default,
822{
823    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
824        write!(f, "{:?}→{:?}", F::default(), T::default())
825    }
826}
827
828impl<Repr, M> From<Repr> for Matrix<Repr, M> {
829    fn from(repr: Repr) -> Self {
830        Self(repr, Pd)
831    }
832}
833
834//
835// Free functions
836//
837
838/// Returns a matrix applying a scaling by `s`.
839///
840/// Tip: use [`splat`][super::vec::splat] to scale uniformly:
841/// ```
842/// use retrofire_core::math::{scale, splat};
843/// let m = scale(splat(2.0));
844/// assert_eq!(m.0[0][0], 2.0);
845/// assert_eq!(m.0[1][1], 2.0);
846/// assert_eq!(m.0[2][2], 2.0);
847/// ```
848pub const fn scale(s: Vec3) -> Mat4x4<RealToReal<3>> {
849    scale3(s.0[0], s.0[1], s.0[2])
850}
851
852pub const fn scale3(x: f32, y: f32, z: f32) -> Mat4x4<RealToReal<3>> {
853    mat![
854         x,  0.0, 0.0, 0.0;
855        0.0,  y,  0.0, 0.0;
856        0.0, 0.0,  z,  0.0;
857        0.0, 0.0, 0.0, 1.0;
858    ]
859}
860
861/// Returns a matrix applying a translation by `t`.
862pub const fn translate(t: Vec3) -> Mat4x4<RealToReal<3>> {
863    translate3(t.0[0], t.0[1], t.0[2])
864}
865
866pub const fn translate3(x: f32, y: f32, z: f32) -> Mat4x4<RealToReal<3>> {
867    mat![
868        1.0, 0.0, 0.0,  x ;
869        0.0, 1.0, 0.0,  y ;
870        0.0, 0.0, 1.0,  z ;
871        0.0, 0.0, 0.0, 1.0;
872    ]
873}
874
875#[cfg(feature = "fp")]
876use super::Angle;
877
878/// Returns a matrix applying a rotation such that the original y-axis
879/// is now parallel with `new_y` and the new z axis is orthogonal to
880/// both `x` and `new_y`.
881///
882/// Returns an orthogonal basis. If `new_y` and `x` are unit vectors,
883/// the basis is orthonormal.
884///
885/// # Panics
886/// If `x` is approximately parallel to `new_y` and the basis would be
887/// degenerate.
888#[cfg(feature = "fp")]
889pub fn orient_y(new_y: Vec3, x: Vec3) -> Mat4x4<RealToReal<3>> {
890    orient(new_y, x.cross(&new_y).normalize())
891}
892/// Returns a matrix applying a rotation such that the original z axis
893/// is now parallel with `new_z` and the new y-axis is orthogonal to
894/// both `new_z` and `x`.
895///
896/// Returns an orthogonal basis. If `new_z` and `x` are unit vectors,
897/// the basis is orthonormal.
898///
899/// # Panics
900/// If `x` is approximately parallel to `new_z` and the basis would be
901/// degenerate.
902#[cfg(feature = "fp")]
903pub fn orient_z(new_z: Vec3, x: Vec3) -> Mat4x4<RealToReal<3>> {
904    orient(new_z.cross(&x).normalize(), new_z)
905}
906
907/// Constructs a change-of-basis matrix given y and z basis vectors.
908///
909/// The third basis vector is the cross product of `new_y` and `new_z`.
910/// If the inputs are orthogonal, the resulting basis is orthogonal.
911/// If the inputs are also unit vectors, the basis is orthonormal.
912///
913/// # Panics
914/// If `new_y` is approximately parallel to `new_z` and the basis would
915/// be degenerate.
916#[cfg(feature = "fp")]
917fn orient(new_y: Vec3, new_z: Vec3) -> Mat4x4<RealToReal<3>> {
918    let new_x = new_y.cross(&new_z);
919    assert!(
920        !new_x.len_sqr().approx_eq(&0.0),
921        "{new_y:?} × {new_z:?} non-finite or too close to zero vector"
922    );
923    Mat4x4::from_linear(new_x, new_y, new_z)
924}
925
926// TODO constify rotate_* functions once we have const trig functions
927
928/// Returns a matrix applying a 3D rotation about the x-axis.
929#[cfg(feature = "fp")]
930pub fn rotate_x(a: Angle) -> Mat4x4<RealToReal<3>> {
931    let (sin, cos) = a.sin_cos();
932    mat![
933        1.0,  0.0, 0.0, 0.0;
934        0.0,  cos, sin, 0.0;
935        0.0, -sin, cos, 0.0;
936        0.0,  0.0, 0.0, 1.0;
937    ]
938}
939/// Returns a matrix applying a 3D rotation about the y-axis.
940#[cfg(feature = "fp")]
941pub fn rotate_y(a: Angle) -> Mat4x4<RealToReal<3>> {
942    let (sin, cos) = a.sin_cos();
943    mat![
944        cos, 0.0, -sin, 0.0;
945        0.0, 1.0,  0.0, 0.0;
946        sin, 0.0,  cos, 0.0;
947        0.0, 0.0,  0.0, 1.0;
948    ]
949}
950/// Returns a matrix applying a 3D rotation about the z axis.
951#[cfg(feature = "fp")]
952pub fn rotate_z(a: Angle) -> Mat4x4<RealToReal<3>> {
953    let (sin, cos) = a.sin_cos();
954    mat![
955         cos, sin, 0.0, 0.0;
956        -sin, cos, 0.0, 0.0;
957         0.0, 0.0, 1.0, 0.0;
958         0.0, 0.0, 0.0, 1.0;
959    ]
960}
961
962/// Returns a matrix applying a 2D rotation by an angle.
963#[cfg(feature = "fp")]
964pub fn rotate2(a: Angle) -> Mat3x3<RealToReal<2>> {
965    let (sin, cos) = a.sin_cos();
966    mat![
967         cos, sin, 0.0;
968        -sin, cos, 0.0;
969         0.0, 0.0, 1.0;
970    ]
971}
972
973/// Returns a matrix applying a 3D rotation about an arbitrary axis.
974#[cfg(feature = "fp")]
975pub fn rotate(axis: Vec3, a: Angle) -> Mat4x4<RealToReal<3>> {
976    use crate::math::approx::ApproxEq;
977
978    // 1. Change of basis such that `axis` is mapped to the z-axis,
979    // 2. Rotation about the z-axis
980    // 3. Change of basis back to the original
981    let mut other = Vec3::X;
982    if axis.cross(&other).len_sqr().approx_eq(&0.0) {
983        // Avoid degeneracy
984        other = Vec3::Y;
985    }
986
987    let z_to_axis = orient_z(axis.normalize(), other);
988    // Inverse of orthogonal matrix is its transpose
989    let axis_to_z = z_to_axis.transpose();
990    axis_to_z.then(&rotate_z(a)).then(&z_to_axis)
991}
992
993/// Creates a perspective projection matrix.
994///
995/// # Parameters
996/// * `focal_ratio`: Focal length/aperture ratio. Larger values mean
997///   a smaller angle of view, with 1.0 corresponding to a horizontal
998///   field of view of 90 degrees.
999/// * `aspect_ratio`: Viewport width/height ratio. Larger values mean
1000///   a wider field of view.
1001/// * `near_far`: Depth range between the near and far clipping planes.
1002///   Objects outside this range are clipped or culled.
1003///
1004/// # Panics
1005/// * If any parameter value is nonpositive.
1006/// * If `near_far` is an empty range.
1007pub fn perspective(
1008    focal_ratio: f32,
1009    aspect_ratio: f32,
1010    near_far: Range<f32>,
1011) -> Mat4x4<ViewToProj> {
1012    let (near, far) = (near_far.start, near_far.end);
1013
1014    assert!(focal_ratio > 0.0, "focal ratio must be positive");
1015    assert!(aspect_ratio > 0.0, "aspect ratio must be positive");
1016    assert!(near > 0.0, "near must be positive");
1017    assert!(far > near, "far must be greater than near");
1018
1019    let e00 = focal_ratio;
1020    let e11 = e00 * aspect_ratio;
1021    let e22 = (far + near) / (far - near);
1022    let e23 = 2.0 * far * near / (near - far);
1023    mat![
1024        e00, 0.0, 0.0, 0.0;
1025        0.0, e11, 0.0, 0.0;
1026        0.0, 0.0, e22, e23;
1027        0.0, 0.0, 1.0, 0.0;
1028    ]
1029}
1030
1031/// Creates an orthographic projection matrix.
1032///
1033/// # Parameters
1034/// * `lbn`: The left-bottom-near corner of the projection box.
1035/// * `rtf`: The right-bottom-far corner of the projection box.
1036pub fn orthographic(lbn: Point3, rtf: Point3) -> Mat4x4<ViewToProj> {
1037    let half_d = (rtf - lbn) / 2.0;
1038    let [cx, cy, cz] = (lbn + half_d).0;
1039    let [idx, idy, idz] = half_d.map(f32::recip).0;
1040    mat![
1041        idx, 0.0, 0.0, -cx * idx;
1042        0.0, idy, 0.0, -cy * idy;
1043        0.0, 0.0, idz, -cz * idz;
1044        0.0, 0.0, 0.0, 1.0;
1045    ]
1046}
1047
1048/// Creates a viewport transform matrix with the given pixel space bounds.
1049///
1050/// A viewport matrix is used to transform points from the NDC space to
1051/// screen space for rasterization. NDC coordinates (-1, -1, z) are mapped
1052/// to `bounds.start` and NDC coordinates (1, 1, z) to `bounds.end`.
1053pub fn viewport(bounds: Range<Point2u>) -> Mat4x4<NdcToScreen> {
1054    let s = bounds.start.map(|c| c as f32);
1055    let e = bounds.end.map(|c| c as f32);
1056    let half_d = (e - s) / 2.0;
1057    let [dx, dy] = half_d.0;
1058    let [cx, cy] = (s + half_d).0;
1059    mat![
1060         dx, 0.0, 0.0,  cx;
1061        0.0,  dy, 0.0,  cy;
1062        0.0, 0.0, 1.0, 0.0;
1063        0.0, 0.0, 0.0, 1.0;
1064    ]
1065}
1066
1067#[cfg(test)]
1068mod tests {
1069    use crate::assert_approx_eq;
1070    use crate::math::pt3;
1071
1072    #[cfg(feature = "fp")]
1073    use crate::math::degs;
1074
1075    use super::*;
1076
1077    #[derive(Debug, Default, Eq, PartialEq)]
1078    struct Basis1;
1079    #[derive(Debug, Default, Eq, PartialEq)]
1080    struct Basis2;
1081
1082    type Map<const N: usize = 3> = RealToReal<N, Basis1, Basis2>;
1083    type InvMap<const N: usize = 3> = RealToReal<N, Basis2, Basis1>;
1084
1085    const X: Vec3 = Vec3::X;
1086    const Y: Vec3 = Vec3::Y;
1087    const Z: Vec3 = Vec3::Z;
1088    const O: Vec3 = Vec3::new([0.0; 3]);
1089
1090    mod mat2x2 {
1091        use super::*;
1092
1093        #[test]
1094        fn determinant_of_identity_is_one() {
1095            let id = Mat2x2::<RealToReal<2>>::identity();
1096            assert_eq!(id.determinant(), 1.0);
1097        }
1098        #[test]
1099        fn determinant_of_reflection_is_negative_one() {
1100            let refl: Mat2x2<Map<2>> = [[0.0, 1.0], [1.0, 0.0]].into();
1101            assert_eq!(refl.determinant(), -1.0);
1102        }
1103
1104        #[test]
1105        fn inverse_of_identity_is_identity() {
1106            let id = Mat2x2::<RealToReal<2>>::identity();
1107            assert_eq!(id.inverse(), id);
1108        }
1109        #[test]
1110        fn inverse_of_inverse_is_original() {
1111            let m: Mat2x2<Map<2>> = [[0.5, 1.5], [1.0, -0.5]].into();
1112            let m_inv: Mat2x2<InvMap<2>> = m.inverse();
1113            assert_approx_eq!(m_inv.inverse(), m);
1114        }
1115        #[test]
1116        fn composition_of_inverse_is_identity() {
1117            let m: Mat2x2<Map<2>> = [[0.5, 1.5], [1.0, -0.5]].into();
1118            let m_inv: Mat2x2<InvMap<2>> = m.inverse();
1119            assert_approx_eq!(m.compose(&m_inv), Mat2x2::identity());
1120            assert_approx_eq!(m.then(&m_inv), Mat2x2::identity());
1121        }
1122    }
1123
1124    mod mat3x3 {
1125        use super::*;
1126
1127        const MAT: Mat3x3<Map> = mat![
1128             0.0,  1.0,  2.0;
1129            10.0, 11.0, 12.0;
1130            20.0, 21.0, 22.0;
1131        ];
1132
1133        #[test]
1134        fn row_col_vecs() {
1135            assert_eq!(MAT.row_vec(2), vec3::<_, Basis1>(20.0, 21.0, 22.0));
1136            assert_eq!(MAT.col_vec(2), vec3::<_, Basis2>(2.0, 12.0, 22.0));
1137        }
1138
1139        #[test]
1140        fn composition() {
1141            let tr: Mat3x3<Map<2>> = mat![
1142                1.0,  0.0,  2.0;
1143                0.0,  1.0, -3.0;
1144                0.0,  0.0,  1.0;
1145            ];
1146            let sc: Mat3x3<InvMap<2>> = mat![
1147                -1.0, 0.0, 0.0;
1148                 0.0, 2.0, 0.0;
1149                 0.0, 0.0, 1.0;
1150            ];
1151
1152            let tr_sc = tr.then(&sc);
1153            let sc_tr = sc.then(&tr);
1154
1155            assert_eq!(tr_sc, sc.compose(&tr));
1156            assert_eq!(sc_tr, tr.compose(&sc));
1157
1158            assert_eq!(tr_sc.apply(&vec2(1.0, 2.0)), vec2(-1.0, 4.0));
1159            assert_eq!(sc_tr.apply(&vec2(1.0, 2.0)), vec2(-1.0, 4.0));
1160
1161            assert_eq!(tr_sc.apply(&pt2(1.0, 2.0)), pt2(-3.0, -2.0));
1162            assert_eq!(sc_tr.apply(&pt2(1.0, 2.0)), pt2(1.0, 1.0));
1163        }
1164
1165        #[test]
1166        fn scaling() {
1167            let m: Mat3x3<Map<2>> = mat![
1168                2.0,  0.0,  0.0;
1169                0.0, -3.0,  0.0;
1170                0.0,  0.0,  1.0;
1171            ];
1172            assert_eq!(m.apply(&vec2(1.0, 2.0)), vec2(2.0, -6.0));
1173            assert_eq!(m.apply(&pt2(2.0, -1.0)), pt2(4.0, 3.0));
1174        }
1175
1176        #[test]
1177        fn translation() {
1178            let m: Mat3x3<Map<2>> = mat![
1179                1.0,  0.0,  2.0;
1180                0.0,  1.0, -3.0;
1181                0.0,  0.0,  1.0;
1182            ];
1183            assert_eq!(m.apply(&vec2(1.0, 2.0)), vec2(1.0, 2.0));
1184            assert_eq!(m.apply(&pt2(2.0, -1.0)), pt2(4.0, -4.0));
1185        }
1186
1187        #[test]
1188        fn inverse_of_identity_is_identity() {
1189            let i = Mat3x3::<RealToReal<_>>::identity();
1190            assert_eq!(i.inverse(), i);
1191        }
1192        #[test]
1193        fn inverse_of_scale_is_reciprocal_scale() {
1194            let scale: Mat3x3<Map<2>> = mat![
1195                2.0, 0.0,  0.0;
1196                0.0, -3.0,  0.0;
1197                0.0,  0.0,  4.0;
1198            ];
1199            assert_eq!(
1200                scale.inverse(),
1201                mat![
1202                    1.0/2.0, 0.0,  0.0;
1203                    0.0, -1.0/3.0, 0.0;
1204                    0.0,  0.0,  1.0/4.0
1205                ]
1206            );
1207        }
1208        #[test]
1209        fn matrix_composed_with_inverse_is_identity() {
1210            let mat: Mat3x3<Map<2>> = mat![
1211                1.0, -2.0,  2.0;
1212                3.0,  4.0, -3.0;
1213                0.0,  0.0,  1.0;
1214            ];
1215            let composed = mat.compose(&mat.inverse());
1216            assert_approx_eq!(composed, Mat3x3::identity());
1217        }
1218
1219        #[test]
1220        fn singular_matrix_has_no_inverse() {
1221            let singular: Mat3x3<Map<2>> = mat![
1222                1.0,  2.0,  0.0;
1223                0.0,  0.0,  0.0;
1224                0.0,  0.0,  1.0;
1225            ];
1226
1227            assert_approx_eq!(singular.checked_inverse(), None);
1228        }
1229
1230        #[test]
1231        fn matrix_debug() {
1232            assert_eq!(
1233                alloc::format!("{MAT:?}"),
1234                r#"Matrix<Basis1→Basis2>[
1235    [ 0.0,  1.0,  2.0]
1236    [10.0, 11.0, 12.0]
1237    [20.0, 21.0, 22.0]
1238]"#
1239            );
1240        }
1241    }
1242
1243    mod mat4x4 {
1244        use super::*;
1245
1246        const MAT: Mat4x4<Map> = mat![
1247             0.0,  1.0,  2.0,  3.0;
1248            10.0, 11.0, 12.0, 13.0;
1249            20.0, 21.0, 22.0, 23.0;
1250            30.0, 31.0, 32.0, 33.0;
1251        ];
1252
1253        #[test]
1254        fn row_col_vecs() {
1255            assert_eq!(MAT.row_vec(1), [10.0, 11.0, 12.0, 13.0].into());
1256            assert_eq!(MAT.col_vec(3), [3.0, 13.0, 23.0, 33.0].into());
1257        }
1258
1259        #[test]
1260        fn composition() {
1261            let t = translate3(1.0, 2.0, 3.0).to::<Map>();
1262            let s = scale3(3.0, 2.0, 1.0).to::<InvMap>();
1263
1264            let ts = t.then(&s);
1265            let st = s.then(&t);
1266
1267            assert_eq!(ts, s.compose(&t));
1268            assert_eq!(st, t.compose(&s));
1269
1270            let o = <Point3>::origin();
1271            assert_eq!(ts.apply(&o.to()), pt3::<_, Basis1>(3.0, 4.0, 3.0));
1272            assert_eq!(st.apply(&o.to()), pt3::<_, Basis2>(1.0, 2.0, 3.0));
1273        }
1274
1275        #[test]
1276        fn scaling() {
1277            let m = scale3(1.0, -2.0, 3.0);
1278
1279            let v = vec3(0.0, 4.0, -3.0);
1280            assert_eq!(m.apply(&v), vec3(0.0, -8.0, -9.0));
1281
1282            let p = pt3(4.0, 0.0, -3.0);
1283            assert_eq!(m.apply(&p), pt3(4.0, 0.0, -9.0));
1284        }
1285
1286        #[test]
1287        fn translation() {
1288            let m = translate3(1.0, 2.0, 3.0);
1289
1290            let v = vec3(0.0, 5.0, -3.0);
1291            assert_eq!(m.apply(&v), vec3(0.0, 5.0, -3.0));
1292
1293            let p = pt3(3.0, 5.0, 0.0);
1294            assert_eq!(m.apply(&p), pt3(4.0, 7.0, 3.0));
1295        }
1296
1297        #[cfg(feature = "fp")]
1298        #[test]
1299        fn rotation_x() {
1300            let m = rotate_x(degs(90.0));
1301
1302            assert_eq!(m.apply(&O), O);
1303
1304            assert_approx_eq!(m.apply(&Z), Y);
1305            assert_approx_eq!(
1306                m.apply(&pt3(0.0, -2.0, 0.0)),
1307                pt3(0.0, 0.0, 2.0)
1308            );
1309        }
1310
1311        #[cfg(feature = "fp")]
1312        #[test]
1313        fn rotation_y() {
1314            let m = rotate_y(degs(90.0));
1315
1316            assert_eq!(m.apply(&O), O);
1317
1318            assert_approx_eq!(m.apply(&X), Z);
1319            assert_approx_eq!(
1320                m.apply(&pt3(0.0, 0.0, -2.0)),
1321                pt3(2.0, 0.0, 0.0)
1322            );
1323        }
1324
1325        #[cfg(feature = "fp")]
1326        #[test]
1327        fn rotation_z() {
1328            let m = rotate_z(degs(90.0));
1329
1330            assert_eq!(m.apply(&O), O);
1331
1332            assert_approx_eq!(m.apply(&Y), X);
1333            assert_approx_eq!(
1334                m.apply(&(pt3(-2.0, 0.0, 0.0))),
1335                pt3(0.0, 2.0, 0.0)
1336            );
1337        }
1338
1339        #[cfg(feature = "fp")]
1340        #[test]
1341        fn rotation_arbitrary() {
1342            let m = rotate(vec3(1.0, 1.0, 0.0).normalize(), degs(180.0));
1343
1344            assert_approx_eq!(m.apply(&X), Y);
1345            assert_approx_eq!(m.apply(&Y), X);
1346            assert_approx_eq!(m.apply(&Z), -Z);
1347        }
1348
1349        #[cfg(feature = "fp")]
1350        #[test]
1351        fn rotation_arbitrary_x() {
1352            let a = rotate(X, degs(128.0));
1353            let b = rotate_x(degs(128.0));
1354            assert_eq!(a, b);
1355        }
1356        #[cfg(feature = "fp")]
1357        #[test]
1358        fn rotation_arbitrary_y() {
1359            let a = rotate(Y, degs(128.0));
1360            let b = rotate_y(degs(128.0));
1361            assert_eq!(a, b);
1362        }
1363        #[cfg(feature = "fp")]
1364        #[test]
1365        fn rotation_arbitrary_z() {
1366            let a = rotate(Z, degs(128.0));
1367            let b = rotate_z(degs(128.0));
1368            assert_eq!(a, b);
1369        }
1370
1371        #[test]
1372        fn from_basis() {
1373            let m = Mat4x4::from_linear(Y, 2.0 * Z, -3.0 * X);
1374            assert_eq!(m.apply(&X), Y);
1375            assert_eq!(m.apply(&Y), 2.0 * Z);
1376            assert_eq!(m.apply(&Z), -3.0 * X);
1377        }
1378
1379        #[cfg(feature = "fp")]
1380        #[test]
1381        fn orientation_no_op() {
1382            let m = orient_y(Y, X);
1383
1384            assert_eq!(m.apply(&X), X);
1385            assert_eq!(m.apply(&X.to_pt()), X.to_pt());
1386
1387            assert_eq!(m.apply(&Y), Y);
1388            assert_eq!(m.apply(&Y.to_pt()), Y.to_pt());
1389
1390            assert_eq!(m.apply(&Z), Z);
1391            assert_eq!(m.apply(&Z.to_pt()), Z.to_pt());
1392        }
1393
1394        #[cfg(feature = "fp")]
1395        #[test]
1396        fn orientation_y_to_z() {
1397            let m = orient_y(Z, X);
1398
1399            assert_eq!(m.apply(&X), X);
1400            assert_eq!(m.apply(&X.to_pt()), X.to_pt());
1401
1402            assert_eq!(m.apply(&Y), Z);
1403            assert_eq!(m.apply(&Y.to_pt()), Z.to_pt());
1404
1405            assert_eq!(m.apply(&Z), -Y);
1406            assert_eq!(m.apply(&Z.to_pt()), (-Y).to_pt());
1407        }
1408
1409        #[cfg(feature = "fp")]
1410        #[test]
1411        fn orientation_z_to_y() {
1412            let m = orient_z(Y, X);
1413
1414            assert_eq!(m.apply(&X), X);
1415            assert_eq!(m.apply(&X.to_pt()), X.to_pt());
1416
1417            assert_eq!(m.apply(&Y), -Z);
1418            assert_eq!(m.apply(&Y.to_pt()), (-Z).to_pt());
1419
1420            assert_eq!(m.apply(&Z), Y);
1421            assert_eq!(m.apply(&Z.to_pt()), Y.to_pt());
1422        }
1423
1424        #[test]
1425        fn matrix_debug() {
1426            assert_eq!(
1427                alloc::format!("{MAT:?}"),
1428                r#"Matrix<Basis1→Basis2>[
1429    [ 0.0,  1.0,  2.0,  3.0]
1430    [10.0, 11.0, 12.0, 13.0]
1431    [20.0, 21.0, 22.0, 23.0]
1432    [30.0, 31.0, 32.0, 33.0]
1433]"#
1434            );
1435        }
1436    }
1437
1438    #[test]
1439    fn transposition() {
1440        let m = Matrix::<_, Map>::new([
1441            [0.0, 1.0, 2.0], //
1442            [10.0, 11.0, 12.0],
1443            [20.0, 21.0, 22.0],
1444        ]);
1445        assert_eq!(
1446            m.transpose(),
1447            Matrix::<_, InvMap>::new([
1448                [0.0, 10.0, 20.0], //
1449                [1.0, 11.0, 21.0],
1450                [2.0, 12.0, 22.0],
1451            ])
1452        );
1453    }
1454
1455    #[test]
1456    fn determinant_of_identity_is_one() {
1457        let id: Mat4x4<Map> = Mat4x4::identity();
1458        assert_eq!(id.determinant(), 1.0);
1459    }
1460
1461    #[test]
1462    fn determinant_of_scaling_is_product_of_diagonal() {
1463        let scale: Mat4x4<_> = scale3(2.0, 3.0, 4.0);
1464        assert_eq!(scale.determinant(), 24.0);
1465    }
1466
1467    #[cfg(feature = "fp")]
1468    #[test]
1469    fn determinant_of_rotation_is_one() {
1470        let rot = rotate_x(degs(73.0)).then(&rotate_y(degs(-106.0)));
1471        assert_approx_eq!(rot.determinant(), 1.0);
1472    }
1473
1474    #[cfg(feature = "fp")]
1475    #[test]
1476    fn matrix_composed_with_inverse_is_identity() {
1477        let m = translate3(1.0e3, -2.0e2, 0.0)
1478            .then(&scale3(0.5, 100.0, 42.0))
1479            .to::<Map>();
1480
1481        let m_inv: Mat4x4<InvMap> = m.inverse();
1482
1483        assert_eq!(
1484            m.compose(&m_inv),
1485            Mat4x4::<RealToReal<3, Basis2, Basis2>>::identity()
1486        );
1487        assert_eq!(
1488            m_inv.compose(&m),
1489            Mat4x4::<RealToReal<3, Basis1, Basis1>>::identity()
1490        );
1491    }
1492
1493    #[test]
1494    fn inverse_reverts_transform() {
1495        let m: Mat4x4<Map> = scale3(1.0, 2.0, 0.5)
1496            .then(&translate3(-2.0, 3.0, 0.0))
1497            .to();
1498        let m_inv: Mat4x4<InvMap> = m.inverse();
1499
1500        let v1: Vec3<Basis1> = vec3(1.0, -2.0, 3.0);
1501        let v2: Vec3<Basis2> = vec3(2.0, 0.0, -2.0);
1502
1503        assert_eq!(m_inv.apply(&m.apply(&v1)), v1);
1504        assert_eq!(m.apply(&m_inv.apply(&v2)), v2);
1505    }
1506
1507    #[test]
1508    fn orthographic_box_maps_to_unit_cube() {
1509        let lbn = pt3(-20.0, 0.0, 0.01);
1510        let rtf = pt3(100.0, 50.0, 100.0);
1511
1512        let m = orthographic(lbn, rtf);
1513
1514        assert_approx_eq!(m.apply(&lbn.to()), [-1.0, -1.0, -1.0, 1.0].into());
1515        assert_approx_eq!(m.apply(&rtf.to()), [1.0, 1.0, 1.0, 1.0].into());
1516    }
1517
1518    #[test]
1519    fn perspective_frustum_maps_to_unit_cube() {
1520        let left_bot_near = pt3(-0.125, -0.0625, 0.1);
1521        let right_top_far = pt3(125.0, 62.5, 100.0);
1522
1523        let m = perspective(0.8, 2.0, 0.1..100.0);
1524
1525        let lbn = m.apply(&left_bot_near);
1526        assert_approx_eq!(lbn / lbn.w(), [-1.0, -1.0, -1.0, 1.0].into());
1527
1528        let rtf = m.apply(&right_top_far);
1529        assert_approx_eq!(rtf / rtf.w(), [1.0, 1.0, 1.0, 1.0].into());
1530    }
1531
1532    #[test]
1533    fn viewport_maps_ndc_to_screen() {
1534        let m = viewport(pt2(20, 10)..pt2(620, 470));
1535
1536        assert_eq!(m.apply(&pt3(-1.0, -1.0, 0.2)), pt3(20.0, 10.0, 0.2));
1537        assert_eq!(m.apply(&pt3(1.0, 1.0, 0.6)), pt3(620.0, 470.0, 0.6));
1538    }
1539}