Skip to main content

omelet/matrices/
mat2.rs

1use crate::matrices::Mat3;
2use crate::utils::epsilon_eq_default;
3use crate::vec::{Vec2, Vec3};
4use core::f32;
5use std::{
6    cmp::PartialEq,
7    fmt,
8    ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign},
9};
10
11/// A 2x2 column-major matrix used for 2D linear transformations such as
12/// rotation, scaling, and shearing.
13///
14/// The matrix is composed of two `Vec2` column vectors:
15///
16/// ```text
17/// [col0.x, col1.x]
18/// [col0.y, col1.y]
19/// ```
20///
21/// # Example
22/// ```rust
23/// use omelet::vec::Vec2;
24/// use omelet::matrices::Mat2;
25///
26/// let col0 = Vec2::new(1.0, 0.0);
27/// let col1 = Vec2::new(0.0, 1.0);
28/// let identity = Mat2::new(col0, col1);
29/// ```
30#[derive(Debug, Clone, Copy)]
31pub struct Mat2 {
32    /// The first column of the matrix
33    pub col0: Vec2,
34    /// The second column of the matrix
35    pub col1: Vec2,
36}
37
38// ============= Types ==============
39pub type Mat2Tuple2D = ((f32, f32), (f32, f32));
40pub type Mat2Tuple = (f32, f32, f32, f32);
41
42impl Mat2 {
43    // ============= Construction and Conversion =============
44    /// Constructs a new 2x2 matrix from two column vectors
45    ///
46    /// The matrix is structured in column-major order:
47    /// ```text
48    /// [col0.x, col1.x]
49    /// [col0.y, col1.y]
50    /// ```
51    pub fn new(col0: Vec2, col1: Vec2) -> Mat2 {
52        Mat2 { col0, col1 }
53    }
54
55    /// Constructs a matrix from row vectors
56    ///
57    /// # Example
58    /// ```rust
59    /// use omelet::vec::Vec2;
60    /// use omelet::matrices::Mat2;
61    ///
62    /// let m = Mat2::from_rows(Vec2::new(1.0, 2.0), Vec2::new(3.0, 4.0));
63    /// assert_eq!(m, Mat2::new(Vec2::new(1.0, 3.0), Vec2::new(2.0, 4.0)));
64    /// ```
65    pub fn from_rows(r0: Vec2, r1: Vec2) -> Mat2 {
66        Mat2 {
67            col0: Vec2::new(r0.x, r1.x),
68            col1: Vec2::new(r0.y, r1.y),
69        }
70    }
71
72    /// Constructs a matrix representing a counter-clockwise rotation
73    ///
74    /// # Parameters
75    /// - `radians`: Rotation angle in radians
76    ///
77    /// # Example
78    /// ```rust
79    /// use omelet::matrices::Mat2;
80    /// let rot90 = Mat2::from_angle(std::f32::consts::FRAC_PI_2);
81    /// ```
82    pub fn from_angle(radians: f32) -> Mat2 {
83        let (s, c) = radians.sin_cos();
84        Mat2::new(Vec2::new(c, s), Vec2::new(-s, c))
85    }
86
87    /// Constructs a scale matrix from a scale vector
88    ///
89    /// # Example
90    /// ```rust
91    /// use omelet::matrices::Mat2;
92    /// use omelet::vec::Vec2;
93    /// let scale = Mat2::from_scale(Vec2::new(2.0, 3.0));
94    /// assert_eq!(scale, Mat2::new(Vec2::new(2.0, 0.0), Vec2::new(0.0, 3.0)));
95    /// ```
96    pub fn from_scale(scale: Vec2) -> Mat2 {
97        Mat2::new(Vec2::new(scale.x, 0.0), Vec2::new(0.0, scale.y))
98    }
99
100    // ============= Constants ==============
101    pub const ZERO: Self = Self {
102        col0: Vec2::ZERO,
103        col1: Vec2::ZERO,
104    };
105
106    pub const IDENTITY: Self = Self {
107        col0: Vec2 { x: 1.0, y: 0.0 },
108        col1: Vec2 { x: 0.0, y: 1.0 },
109    };
110
111    pub const NAN: Self = Self {
112        col0: Vec2::NAN,
113        col1: Vec2::NAN,
114    };
115
116    pub const INFINITY: Self = Self {
117        col0: Vec2::INFINITY,
118        col1: Vec2::INFINITY,
119    };
120
121    /// Returns the transpose of the matrix
122    ///
123    /// Swaps rows and columns:
124    /// ```text
125    /// [a, b]  =>  [a, c]
126    /// [c, d]  =>  [b, d]
127    /// ```
128    pub fn transpose(&self) -> Mat2 {
129        Mat2::new(
130            Vec2::new(self.col0.x, self.col1.x),
131            Vec2::new(self.col0.y, self.col1.y),
132        )
133    }
134
135    /// Returns the determinant of the matrix
136    ///
137    /// Used for checking invertibility or computing area under transformations
138    ///
139    /// ```text
140    /// det = a * d- b * c
141    /// ```
142    pub fn determinant(&self) -> f32 {
143        self.col0.x * self.col1.y - self.col0.y * self.col1.x
144    }
145
146    /// Returns the inverse of the matrix, if it exists
147    ///
148    /// Returns `None` if the matrix is singular (non-invertible)
149    ///
150    /// # Panics
151    /// Will not panic. Returns `None` instead of panicking on singular matrices
152    pub fn inverse(&self) -> Option<Mat2> {
153        let det = self.determinant();
154        if epsilon_eq_default(det.abs(), 0.0) {
155            return None;
156        }
157
158        let inv_det = 1.0 / det;
159
160        let a = self.col0.x;
161        let b = self.col0.y;
162        let c = self.col1.x;
163        let d = self.col1.y;
164
165        Some(Mat2::new(
166            Vec2::new(d, -b) * inv_det,
167            Vec2::new(-c, a) * inv_det,
168        ))
169    }
170
171    /// Returns `true` if the matrix is invertible (non-zero determinant)
172    pub fn is_invertible(&self) -> bool {
173        self.determinant().abs() > 1e-6
174    }
175
176    /// Returns a row major 2 dimensional array `[[f32; 2]; 2]`
177    ///
178    /// Equivalent to `[[col0.x, col1.x], [col0.y, col1.y]]`
179    pub fn to_array_2d_row_major(&self) -> [[f32; 2]; 2] {
180        [[self.col0.x, self.col1.x], [self.col0.y, self.col1.y]]
181    }
182
183    /// Returns a row major flat array `[f32; 4]`
184    ///
185    /// Equivalent to `[col0.x, col0.y, col1.x, col1.y]`
186    pub fn to_array_row_major(&self) -> [f32; 4] {
187        [self.col0.x, self.col0.y, self.col1.x, self.col1.y]
188    }
189
190    /// Returns a column major 2 dimensional array `[[f32; 2]; 2]`
191    ///
192    /// Equivalent to `[[col0.x, col0.y], [col1.x, col1.y]]`
193    pub fn to_array_2d_col_major(&self) -> [[f32; 2]; 2] {
194        [[self.col0.x, self.col0.y], [self.col1.x, self.col1.y]]
195    }
196
197    /// Returns a column major flat array `[f32; 4]`
198    ///
199    /// Equivalent to `[col0.x, col0.y, col1.x, col1.y]`
200    pub fn to_array_col_major(&self) -> [f32; 4] {
201        [self.col0.x, self.col0.y, self.col1.x, self.col1.y]
202    }
203
204    /// Returns a row major 2 dimensional tuple `((f32, f32), (f32, f32))`
205    ///
206    /// Equivalent to `((col0.x, col1.x), (col0.y, col1.y))`
207    pub fn to_tuple_2d_row_major(&self) -> Mat2Tuple2D {
208        ((self.col0.x, self.col1.x), (self.col0.y, self.col1.y))
209    }
210
211    /// Returns a row major tuple `(f32, f32, f32, f32)`
212    ///
213    /// Equivalent to `(col0.x, col0.y, col1.x, col1.y)`
214    pub fn to_tuple_row_major(&self) -> Mat2Tuple {
215        (self.col0.x, self.col0.y, self.col1.x, self.col1.y)
216    }
217
218    /// Returns a column major 2 dimensional tuple `((f32, f32), (f32, f32))`
219    ///
220    /// Equivalent to `((col0.x, col0.y), (col1.x, col1.y))`
221    pub fn to_tuple_2d_col_major(&self) -> Mat2Tuple2D {
222        ((self.col0.x, self.col0.y), (self.col1.x, self.col1.y))
223    }
224
225    /// Returns a column major tuple `(f32, f32, f32, f32)`
226    ///
227    /// Equivalent to `(col0.x, col0.y, col1.x, col1.y)`
228    pub fn to_tuple_col_major(&self) -> Mat2Tuple {
229        (self.col0.x, self.col0.y, self.col1.x, self.col1.y)
230    }
231
232    /// Returns a `Mat2` from a 2 dimensional array
233    ///
234    /// # Paramneters
235    /// - `arr`: 2 dimensional array `[[f32; 2]; 2]`
236    pub fn from_2d_array(arr: [[f32; 2]; 2]) -> Mat2 {
237        Mat2::new(
238            Vec2::new(arr[0][0], arr[0][1]),
239            Vec2::new(arr[1][0], arr[1][1]),
240        )
241    }
242
243    /// Returns a `Mat2` from a flat array
244    ///
245    /// # Parameters
246    /// - `arr`: Flat array `[f32; 4]`
247    ///
248    /// Equivalent to `Mat2{Vec2{arr[0], arr[1]}, Vec2[arr[2], arr[3]]}}`
249    pub fn from_array(arr: [f32; 4]) -> Mat2 {
250        Mat2::new(Vec2::new(arr[0], arr[1]), Vec2::new(arr[2], arr[3]))
251    }
252
253    /// Returns a `Mat2` from a 2 dimensional tuple
254    ///
255    /// # Parameters
256    /// - `t`: Tuple to use `((f32, f32), (f32, f32))`
257    pub fn from_2d_tuple(t: Mat2Tuple2D) -> Mat2 {
258        Mat2::new(Vec2::new(t.0 .0, t.0 .1), Vec2::new(t.1 .0, t.1 .1))
259    }
260
261    /// Returns a `Mat2` from a tuple
262    ///
263    /// # Parameters
264    /// - `t`: Tuple `(f32, f32, f32, f32)`
265    ///
266    /// Equivalent to `Mat2{Vec2{t.0, t.1}, Vec2{t.2, t.3}}`
267    pub fn from_tuple(t: Mat2Tuple) -> Mat2 {
268        Mat2::new(Vec2::new(t.0, t.1), Vec2::new(t.2, t.3))
269    }
270
271    /// Returns the specified column vector.
272    ///
273    /// # Panics
274    /// Panics if `col_idx` is not 0 or 1.
275    pub fn col(&self, index: usize) -> Vec2 {
276        match index {
277            0 => self.col0,
278            1 => self.col1,
279            _ => panic!("Mat2 column index out of bounds: {}", index),
280        }
281    }
282
283    pub fn row(&self, index: usize) -> Vec2 {
284        match index {
285            0 => Vec2::new(self.col0.x, self.col1.x),
286            1 => Vec2::new(self.col0.y, self.col1.y),
287            _ => panic!("Mat2 row index out of bounds: {}", index),
288        }
289    }
290
291    /// Returns a 3x3 column-major matrix.
292    ///
293    /// # Returns
294    /// A `Mat3`:
295    /// ```text
296    /// [a, c, 0]
297    /// [b, d, 0]
298    /// [0, 0, 1]
299    /// ```
300    pub fn to_mat3(&self) -> Mat3 {
301        Mat3::new(
302            Vec3::new(self.col0.x, self.col0.y, 0.0),
303            Vec3::new(self.col1.x, self.col1.y, 0.0),
304            Vec3::new(0.0, 0.0, 1.0),
305        )
306    }
307
308    // ============= Math Utilities =============
309
310    /// Returns a matrix with the absolute value of each component
311    ///
312    /// Useful for bounding box operations or eliminating directional signs
313    pub fn abs(self) -> Mat2 {
314        Mat2 {
315            col0: self.col0.abs(),
316            col1: self.col1.abs(),
317        }
318    }
319
320    /// Returns the sign (-1.0, 0.0, 1.0) of each matrix component
321    ///
322    /// Useful when analyzing directionality of matrix effects
323    pub fn signum(self) -> Mat2 {
324        Mat2 {
325            col0: self.col0.signum(),
326            col1: self.col1.signum(),
327        }
328    }
329
330    /// Linearly interpolates between `self` and matrix `b` by amount `t`
331    ///
332    /// Equivalent to `self * (1.0 - t) + b * t`
333    ///
334    /// # Parameters
335    /// - `b`: The target matrix
336    /// - `t`: Interpolation factor
337    ///
338    /// # Example
339    /// ```rust
340    /// use omelet::matrices::Mat2;
341    /// use omelet::vec::Vec2;
342    ///
343    /// let a = Mat2::IDENTITY;
344    /// let b = Mat2::from_scale(Vec2::new(2.0, 2.0));
345    /// let halfway = a.lerp(b, 0.5);
346    /// ```
347    pub fn lerp(&self, b: Mat2, t: f32) -> Mat2 {
348        Mat2::new(self.col0.lerp(b.col0, t), self.col1.lerp(b.col1, t))
349    }
350
351    /// Linearly interpolates between matrix `a` and matrix `b` by amount `t`
352    ///
353    /// # Parameters
354    /// - `a`: Starting matrix
355    /// - `b`: Target matrix
356    /// - `t`: Interpolation factor
357    ///
358    /// Equivalent to `a * (1.0 - t) + b * t`
359    pub fn lerp_between(a: Mat2, b: Mat2, t: f32) -> Mat2 {
360        Mat2::new(a.col0.lerp(b.col0, t), a.col1.lerp(b.col1, t))
361    }
362
363    /// Checks if all components in `self` are approximately equal to
364    /// all components in `other` with a default epsilon
365    ///
366    /// # Parameters
367    /// - `other`: The other matrix in the comparison
368    pub fn approx_eq(&self, other: Mat2) -> bool {
369        self.col0.approx_eq(other.col0) && self.col1.approx_eq(other.col1)
370    }
371
372    /// Checks if all components in `self` are approximately equal to
373    /// all components in `other` with a custom epsilon
374    ///
375    /// # Parameters
376    /// - `other`: The other matrix in the comparison
377    /// - `epsilon`: Epsilon to use in the check
378    pub fn approx_eq_eps(&self, other: Mat2, epsilon: f32) -> bool {
379        self.col0.approx_eq_eps(other.col0, epsilon) && self.col1.approx_eq_eps(other.col1, epsilon)
380    }
381
382    /// Returns `true` if any of the components are NaN
383    pub fn is_nan(&self) -> bool {
384        self.col0.is_nan() || self.col1.is_nan()
385    }
386
387    /// Returns `true` if all of the components are finite
388    pub fn is_finite(&self) -> bool {
389        self.col0.is_finite() && self.col1.is_finite()
390    }
391
392    /// Computes the adjugate (classical adjoint) of the matrix.
393    ///
394    /// For matrix A =
395    /// ```text
396    /// [a, b]
397    /// [c, d]
398    /// ```
399    /// the adjugate is:
400    /// ```text
401    /// [ d, -b]
402    /// [-c,  a]
403    /// ```
404    pub fn adjugate(self) -> Mat2 {
405        Mat2::new(
406            Vec2::new(self.col1.y, -self.col0.y),
407            Vec2::new(-self.col1.x, self.col0.x),
408        )
409    }
410
411    // ============= Transform Utilities =============
412    /// Extracts the scale component from the matrix by computing
413    /// the length (magnitude) of each column vector
414    ///
415    /// This assumes that the matrix encodes a rotation+scale transform
416    pub fn extract_scale(&self) -> Vec2 {
417        Vec2::new(self.col0.length(), self.col1.length())
418    }
419
420    /// Returns the diagonal scale components without considering rotation
421    ///
422    /// This assumes the matrix is purely scaling or has scale aligned with axes
423    /// It's a "raw" lookup: returns (col0.x, col1.y)
424    pub fn extract_scale_raw(&self) -> Vec2 {
425        Vec2::new(self.col0.x, self.col1.y)
426    }
427
428    /// Extracts the rotation angle in radians from the matrix
429    ///
430    /// Assumes the matrix is a pure rotation or a rotation+scale transform
431    /// Uses the direction of the first column vector (X-axis) to determine angle
432    pub fn extract_rotation(&self) -> f32 {
433        self.col0.y.atan2(self.col0.x)
434    }
435
436    /// Returns an orthonormalized version of the matrix
437    ///
438    /// This ensures the columns are orthogonal unit vectors.
439    /// This is useful if the matrix has accumulated numerical
440    /// error or is approximately orthonormal
441    ///
442    /// - First column is normalized as the X-axis.
443    /// - Second column is computed as the perpendicular vector (rotated 90 degrees CCW)
444    pub fn orthonormalize(&self) -> Mat2 {
445        let x = self.col0.normalize();
446        let y = Vec2::new(-x.y, x.x);
447        Mat2::new(x, y)
448    }
449
450    /// Checks if the matrix is orthogonal within a tolerance.
451    ///
452    /// Orthogonal matrices satisfy `M^T * M = I`.
453    ///
454    /// # Parameters
455    /// - `epsilon`: Maximum allowed deviation from orthogonality.
456    ///
457    /// # Returns
458    /// `true` if orthogonal within tolerance.
459    pub fn is_orthogonal(&self, epsilon: f32) -> bool {
460        (self.transpose() * *self).approx_eq_eps(Mat2::IDENTITY, epsilon)
461    }
462
463    /// Returns the trace of the matrix.
464    ///
465    /// The trace is the sum of the diagonal elements (`a + d`).
466    ///
467    /// # Returns
468    /// Scalar trace value.
469    pub fn trace(self) -> f32 {
470        self.col0.x + self.col1.y
471    }
472
473    // ============= Decomposition =============
474    pub fn decompose(&self) -> (Vec2, f32) {
475        (self.extract_scale(), self.extract_rotation())
476    }
477
478    // ============= Triangle Checks =============
479    /// Checks if the matrix is lower-triangular within a tolerance.
480    ///
481    /// This means elements above the main diagonal are approx zero.
482    ///
483    /// # Parameters
484    /// - `epsilon`: Maximum allowed magnitude of upper-triangular elements.
485    ///
486    /// # Returns
487    /// `true` if matrix is lower-triangular within tolerance.
488    pub fn is_lower_triangular(&self, epsilon: f32) -> bool {
489        self.col0.y.abs() <= epsilon
490    }
491
492    /// Checks if the matrix is upper-triangular within a tolerance.
493    ///
494    /// This means elements below the main diagonal are approx zero.
495    ///
496    /// # Parameters
497    /// - `epsilon`: Maximum allowed magnitude of lower-triangular elements.
498    ///
499    /// # Returns
500    /// `true` if matrix is upper-triangular within tolerance.
501    pub fn is_upper_triangular(&self, epsilon: f32) -> bool {
502        self.col1.x.abs() <= epsilon
503    }
504}
505
506// ============= Operator Overloads =============
507
508/// Adds two matrices together component-wise.
509impl Add for Mat2 {
510    type Output = Self;
511    #[inline]
512    fn add(self, rhs: Self) -> Self::Output {
513        Self::new(self.col0 + rhs.col0, self.col1 + rhs.col1)
514    }
515}
516
517/// Subtracts `rhs` from `self` component-wise.
518impl Sub for Mat2 {
519    type Output = Self;
520    #[inline]
521    fn sub(self, rhs: Self) -> Self::Output {
522        Self::new(self.col0 - rhs.col0, self.col1 - rhs.col1)
523    }
524}
525
526/// Multiplies two matrices using standard matrix multiplication.
527impl Mul for Mat2 {
528    type Output = Self;
529    #[inline]
530    fn mul(self, rhs: Self) -> Self::Output {
531        Self::new(self * rhs.col0, self * rhs.col1)
532    }
533}
534
535/// Multiplies the matrix by a `Vec2` (matrix-vector multiplication).
536impl Mul<Vec2> for Mat2 {
537    type Output = Vec2;
538    #[inline]
539    fn mul(self, rhs: Vec2) -> Self::Output {
540        self.col0 * rhs.x + self.col1 * rhs.y
541    }
542}
543
544/// Multiplies each component of the matrix by a scalar.
545impl Mul<f32> for Mat2 {
546    type Output = Self;
547    #[inline]
548    fn mul(self, rhs: f32) -> Self::Output {
549        Self::new(self.col0 * rhs, self.col1 * rhs)
550    }
551}
552
553/// Multiplies a scalar by each component of the matrix.
554impl Mul<Mat2> for f32 {
555    type Output = Mat2;
556    #[inline]
557    fn mul(self, rhs: Mat2) -> Self::Output {
558        rhs * self
559    }
560}
561
562/// Divides each component of the matrix by a scalar.
563impl Div<f32> for Mat2 {
564    type Output = Self;
565    #[inline]
566    fn div(self, rhs: f32) -> Self::Output {
567        Self::new(self.col0 / rhs, self.col1 / rhs)
568    }
569}
570
571/// Negates each component of the matrix.
572impl Neg for Mat2 {
573    type Output = Self;
574    #[inline]
575    fn neg(self) -> Self::Output {
576        Self::new(-self.col0, -self.col1)
577    }
578}
579
580// ============= Assignment Operator Overloads =============
581
582impl AddAssign for Mat2 {
583    #[inline]
584    fn add_assign(&mut self, rhs: Self) {
585        self.col0 += rhs.col0;
586        self.col1 += rhs.col1;
587    }
588}
589
590impl SubAssign for Mat2 {
591    #[inline]
592    fn sub_assign(&mut self, rhs: Self) {
593        self.col0 -= rhs.col0;
594        self.col1 -= rhs.col1;
595    }
596}
597
598impl MulAssign for Mat2 {
599    #[inline]
600    fn mul_assign(&mut self, rhs: Self) {
601        *self = *self * rhs;
602    }
603}
604
605impl MulAssign<f32> for Mat2 {
606    #[inline]
607    fn mul_assign(&mut self, rhs: f32) {
608        self.col0 *= rhs;
609        self.col1 *= rhs;
610    }
611}
612
613impl DivAssign<f32> for Mat2 {
614    #[inline]
615    fn div_assign(&mut self, rhs: f32) {
616        self.col0 /= rhs;
617        self.col1 /= rhs;
618    }
619}
620
621// ============= Trait Implementations =============
622
623impl Default for Mat2 {
624    /// Returns the identity matrix.
625    #[inline]
626    fn default() -> Self {
627        Self::IDENTITY // Assumes Mat2::IDENTITY constant exists
628    }
629}
630
631/// Checks whether two matrices are exactly equal.
632impl PartialEq for Mat2 {
633    #[inline]
634    fn eq(&self, other: &Self) -> bool {
635        self.col0 == other.col0 && self.col1 == other.col1
636    }
637}
638
639/// Enables `m[column]` access. Panics if `col_index` is out of bounds.
640impl Index<usize> for Mat2 {
641    type Output = Vec2;
642    #[inline]
643    fn index(&self, col_index: usize) -> &Self::Output {
644        match col_index {
645            0 => &self.col0,
646            1 => &self.col1,
647            _ => panic!("Mat2 column index out of bounds: {}", col_index),
648        }
649    }
650}
651
652/// Enables mutable `m[column]` access. Panics if `col_index` is out of bounds.
653impl IndexMut<usize> for Mat2 {
654    #[inline]
655    fn index_mut(&mut self, col_index: usize) -> &mut Self::Output {
656        match col_index {
657            0 => &mut self.col0,
658            1 => &mut self.col1,
659            _ => panic!("Mat2 column index out of bounds: {}", col_index),
660        }
661    }
662}
663
664/// Implements the `Display` trait for pretty-printing.
665impl fmt::Display for Mat2 {
666    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
667        write!(
668            f,
669            "[{:.3}, {:.3}]\n[{:.3}, {:.3}]",
670            self.col0.x, self.col1.x, self.col0.y, self.col1.y
671        )
672    }
673}
674
675// ============= Approx Crate Implementations =============
676
677/// Implements absolute difference equality comparison for `Mat2`.
678impl approx::AbsDiffEq for Mat2 {
679    type Epsilon = f32;
680
681    #[inline]
682    fn default_epsilon() -> f32 {
683        f32::EPSILON
684    }
685
686    #[inline]
687    fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
688        self.col0.abs_diff_eq(&other.col0, epsilon) && self.col1.abs_diff_eq(&other.col1, epsilon)
689    }
690}
691
692/// Implements relative equality comparison for `Mat2`.
693impl approx::RelativeEq for Mat2 {
694    #[inline]
695    fn default_max_relative() -> f32 {
696        f32::EPSILON
697    }
698
699    #[inline]
700    fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
701        self.col0.relative_eq(&other.col0, epsilon, max_relative)
702            && self.col1.relative_eq(&other.col1, epsilon, max_relative)
703    }
704}