Skip to main content

ferroui_base/numerics/
matrix3x2.rs

1//! A 3x2 matrix (a two-dimensional affine transform).
2
3use std::fmt;
4use std::ops::{Add, Mul, Neg, Sub};
5
6use super::single::{ieee_remainder, InvariantF32};
7use super::Vector2;
8
9/// Rotations within this distance (0.1% of a degree) of a multiple of a
10/// quarter turn use exact sine and cosine values.
11const ROTATION_EPSILON: f32 = 0.001 * std::f32::consts::PI / 180.0;
12
13/// A 3x2 matrix (a two-dimensional affine transform).
14#[derive(Clone, Copy, Debug, PartialEq, Default)]
15pub struct Matrix3x2 {
16    /// The first element of the first row.
17    pub m11: f32,
18    /// The second element of the first row.
19    pub m12: f32,
20    /// The first element of the second row.
21    pub m21: f32,
22    /// The second element of the second row.
23    pub m22: f32,
24    /// The first element of the third row.
25    pub m31: f32,
26    /// The second element of the third row.
27    pub m32: f32,
28}
29
30impl Matrix3x2 {
31    /// The multiplicative identity matrix.
32    pub const IDENTITY: Matrix3x2 = Matrix3x2::new(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
33
34    /// Creates a 3x2 matrix from the specified components.
35    #[inline]
36    pub const fn new(m11: f32, m12: f32, m21: f32, m22: f32, m31: f32, m32: f32) -> Self {
37        Self {
38            m11,
39            m12,
40            m21,
41            m22,
42            m31,
43            m32,
44        }
45    }
46
47    /// Gets a value that indicates whether the current matrix is the identity matrix.
48    #[inline]
49    pub fn is_identity(&self) -> bool {
50        *self == Matrix3x2::IDENTITY
51    }
52
53    /// Gets the translation component of this matrix.
54    #[inline]
55    pub fn translation(&self) -> Vector2 {
56        Vector2::new(self.m31, self.m32)
57    }
58
59    /// Sets the translation component of this matrix.
60    #[inline]
61    pub fn set_translation(&mut self, value: Vector2) {
62        self.m31 = value.x;
63        self.m32 = value.y;
64    }
65
66    /// Creates a rotation matrix using the given rotation in radians.
67    pub fn create_rotation(radians: f32) -> Matrix3x2 {
68        let (s, c) = rotation_sin_cos(radians);
69
70        // [  c  s ]
71        // [ -s  c ]
72        // [  0  0 ]
73        Matrix3x2::new(c, s, -s, c, 0.0, 0.0)
74    }
75
76    /// Creates a rotation matrix using the specified rotation in radians and a center point.
77    pub fn create_rotation_at(radians: f32, center_point: Vector2) -> Matrix3x2 {
78        let (s, c) = rotation_sin_cos(radians);
79
80        let x = center_point.x * (1.0 - c) + center_point.y * s;
81        let y = center_point.y * (1.0 - c) - center_point.x * s;
82
83        // [  c  s ]
84        // [ -s  c ]
85        // [  x  y ]
86        Matrix3x2::new(c, s, -s, c, x, y)
87    }
88
89    /// Creates a scaling matrix from the specified vector scale.
90    #[inline]
91    pub fn create_scale(scales: Vector2) -> Matrix3x2 {
92        Matrix3x2::create_scale_xy(scales.x, scales.y)
93    }
94
95    /// Creates a scaling matrix from the specified X and Y components.
96    #[inline]
97    pub fn create_scale_xy(x_scale: f32, y_scale: f32) -> Matrix3x2 {
98        Matrix3x2::new(x_scale, 0.0, 0.0, y_scale, 0.0, 0.0)
99    }
100
101    /// Creates a scaling matrix that scales uniformly with the given scale.
102    #[inline]
103    pub fn create_scale_uniform(scale: f32) -> Matrix3x2 {
104        Matrix3x2::create_scale_xy(scale, scale)
105    }
106
107    /// Creates a scaling matrix from the specified vector scale with an offset
108    /// from the specified center point.
109    #[inline]
110    pub fn create_scale_at(scales: Vector2, center_point: Vector2) -> Matrix3x2 {
111        let tx = center_point.x * (1.0 - scales.x);
112        let ty = center_point.y * (1.0 - scales.y);
113        Matrix3x2::new(scales.x, 0.0, 0.0, scales.y, tx, ty)
114    }
115
116    /// Creates a skew matrix from the specified angles in radians.
117    pub fn create_skew(radians_x: f32, radians_y: f32) -> Matrix3x2 {
118        let x_tan = radians_x.tan();
119        let y_tan = radians_y.tan();
120
121        Matrix3x2::new(1.0, y_tan, x_tan, 1.0, 0.0, 0.0)
122    }
123
124    /// Creates a skew matrix from the specified angles in radians and a center point.
125    pub fn create_skew_at(radians_x: f32, radians_y: f32, center_point: Vector2) -> Matrix3x2 {
126        let x_tan = radians_x.tan();
127        let y_tan = radians_y.tan();
128
129        let tx = -center_point.y * x_tan;
130        let ty = -center_point.x * y_tan;
131
132        Matrix3x2::new(1.0, y_tan, x_tan, 1.0, tx, ty)
133    }
134
135    /// Creates a translation matrix from the specified 2-dimensional vector.
136    #[inline]
137    pub fn create_translation(position: Vector2) -> Matrix3x2 {
138        Matrix3x2::create_translation_xy(position.x, position.y)
139    }
140
141    /// Creates a translation matrix from the specified X and Y components.
142    #[inline]
143    pub fn create_translation_xy(x_position: f32, y_position: f32) -> Matrix3x2 {
144        Matrix3x2::new(1.0, 0.0, 0.0, 1.0, x_position, y_position)
145    }
146
147    /// Calculates the determinant for this matrix.
148    #[inline]
149    pub fn get_determinant(&self) -> f32 {
150        // There isn't actually any such thing as a determinant for a non-square matrix,
151        // but this 3x2 type is really just an optimization of a 3x3 where we happen to
152        // know the rightmost column is always (0, 0, 1). So we expand to 3x3 format:
153        //
154        //  [ M11, M12, 0 ]
155        //  [ M21, M22, 0 ]
156        //  [ M31, M32, 1 ]
157        //
158        // Sum the diagonal products:
159        //  (M11 * M22 * 1) + (M12 * 0 * M31) + (0 * M21 * M32)
160        //
161        // Subtract the opposite diagonal products:
162        //  (M31 * M22 * 0) + (M32 * 0 * M11) + (1 * M21 * M12)
163        //
164        // Collapse out the constants and oh look, this is just a 2x2 determinant!
165        (self.m11 * self.m22) - (self.m21 * self.m12)
166    }
167
168    /// Tries to invert the specified matrix. Returns `None` when the matrix is
169    /// singular; see [`Matrix3x2::invert_or_nan`] for the value the reference
170    /// runtime stores in that case.
171    pub fn invert(matrix: Matrix3x2) -> Option<Matrix3x2> {
172        let det = (matrix.m11 * matrix.m22) - (matrix.m21 * matrix.m12);
173
174        // The reference runtime compares against the smallest positive
175        // (subnormal) single.
176        if det.abs() < f32::from_bits(1) {
177            return None;
178        }
179
180        let inv_det = 1.0 / det;
181
182        Some(Matrix3x2::new(
183            matrix.m22 * inv_det,
184            -matrix.m12 * inv_det,
185            -matrix.m21 * inv_det,
186            matrix.m11 * inv_det,
187            (matrix.m21 * matrix.m32 - matrix.m31 * matrix.m22) * inv_det,
188            (matrix.m31 * matrix.m12 - matrix.m11 * matrix.m32) * inv_det,
189        ))
190    }
191
192    /// Inverts the specified matrix; a singular matrix gives a matrix whose
193    /// components are all NaN (the `out` value of the reference runtime).
194    pub fn invert_or_nan(matrix: Matrix3x2) -> Matrix3x2 {
195        Matrix3x2::invert(matrix).unwrap_or(Matrix3x2::new(
196            f32::NAN,
197            f32::NAN,
198            f32::NAN,
199            f32::NAN,
200            f32::NAN,
201            f32::NAN,
202        ))
203    }
204
205    /// Performs a linear interpolation from one matrix to a second matrix
206    /// based on a value that specifies the weighting of the second matrix.
207    pub fn lerp(matrix1: Matrix3x2, matrix2: Matrix3x2, amount: f32) -> Matrix3x2 {
208        Matrix3x2::new(
209            matrix1.m11 + (matrix2.m11 - matrix1.m11) * amount,
210            matrix1.m12 + (matrix2.m12 - matrix1.m12) * amount,
211            matrix1.m21 + (matrix2.m21 - matrix1.m21) * amount,
212            matrix1.m22 + (matrix2.m22 - matrix1.m22) * amount,
213            matrix1.m31 + (matrix2.m31 - matrix1.m31) * amount,
214            matrix1.m32 + (matrix2.m32 - matrix1.m32) * amount,
215        )
216    }
217}
218
219/// The sine and cosine of a 2D rotation, exact near multiples of a quarter turn.
220#[allow(clippy::manual_range_contains)]
221fn rotation_sin_cos(radians: f32) -> (f32, f32) {
222    use std::f32::consts::PI;
223
224    let radians = ieee_remainder(radians, PI * 2.0);
225
226    if radians > -ROTATION_EPSILON && radians < ROTATION_EPSILON {
227        // Exact case for zero rotation.
228        (0.0, 1.0)
229    } else if radians > PI / 2.0 - ROTATION_EPSILON && radians < PI / 2.0 + ROTATION_EPSILON {
230        // Exact case for 90 degree rotation.
231        (1.0, 0.0)
232    } else if radians < -PI + ROTATION_EPSILON || radians > PI - ROTATION_EPSILON {
233        // Exact case for 180 degree rotation.
234        (0.0, -1.0)
235    } else if radians > -PI / 2.0 - ROTATION_EPSILON && radians < -PI / 2.0 + ROTATION_EPSILON {
236        // Exact case for 270 degree rotation.
237        (-1.0, 0.0)
238    } else {
239        // Arbitrary rotation.
240        (radians.sin(), radians.cos())
241    }
242}
243
244impl Add for Matrix3x2 {
245    type Output = Matrix3x2;
246    #[inline]
247    fn add(self, value2: Matrix3x2) -> Matrix3x2 {
248        Matrix3x2::new(
249            self.m11 + value2.m11,
250            self.m12 + value2.m12,
251            self.m21 + value2.m21,
252            self.m22 + value2.m22,
253            self.m31 + value2.m31,
254            self.m32 + value2.m32,
255        )
256    }
257}
258
259impl Sub for Matrix3x2 {
260    type Output = Matrix3x2;
261    #[inline]
262    fn sub(self, value2: Matrix3x2) -> Matrix3x2 {
263        Matrix3x2::new(
264            self.m11 - value2.m11,
265            self.m12 - value2.m12,
266            self.m21 - value2.m21,
267            self.m22 - value2.m22,
268            self.m31 - value2.m31,
269            self.m32 - value2.m32,
270        )
271    }
272}
273
274impl Neg for Matrix3x2 {
275    type Output = Matrix3x2;
276    #[inline]
277    fn neg(self) -> Matrix3x2 {
278        Matrix3x2::new(-self.m11, -self.m12, -self.m21, -self.m22, -self.m31, -self.m32)
279    }
280}
281
282impl Mul for Matrix3x2 {
283    type Output = Matrix3x2;
284    #[inline]
285    fn mul(self, value2: Matrix3x2) -> Matrix3x2 {
286        let value1 = self;
287        Matrix3x2::new(
288            value1.m11 * value2.m11 + value1.m12 * value2.m21,
289            value1.m11 * value2.m12 + value1.m12 * value2.m22,
290            value1.m21 * value2.m11 + value1.m22 * value2.m21,
291            value1.m21 * value2.m12 + value1.m22 * value2.m22,
292            value1.m31 * value2.m11 + value1.m32 * value2.m21 + value2.m31,
293            value1.m31 * value2.m12 + value1.m32 * value2.m22 + value2.m32,
294        )
295    }
296}
297
298/// Multiplies every component by a scalar.
299impl Mul<f32> for Matrix3x2 {
300    type Output = Matrix3x2;
301    #[inline]
302    fn mul(self, value2: f32) -> Matrix3x2 {
303        Matrix3x2::new(
304            self.m11 * value2,
305            self.m12 * value2,
306            self.m21 * value2,
307            self.m22 * value2,
308            self.m31 * value2,
309            self.m32 * value2,
310        )
311    }
312}
313
314impl fmt::Display for Matrix3x2 {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        write!(
317            f,
318            "{{ {{M11:{} M12:{}}} {{M21:{} M22:{}}} {{M31:{} M32:{}}} }}",
319            InvariantF32(self.m11),
320            InvariantF32(self.m12),
321            InvariantF32(self.m21),
322            InvariantF32(self.m22),
323            InvariantF32(self.m31),
324            InvariantF32(self.m32)
325        )
326    }
327}