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}