Skip to main content

goud_engine/ecs/components/transform2d/
mat3x3.rs

1//! 3x3 matrix type for 2D transformations.
2
3use crate::core::math::Vec2;
4
5/// A 3x3 transformation matrix for 2D transforms.
6///
7/// Stored in column-major order for OpenGL compatibility.
8/// The bottom row is always [0, 0, 1].
9///
10/// Layout:
11/// ```text
12/// | m[0] m[3] m[6] |   | cos*sx  -sin*sy  tx |
13/// | m[1] m[4] m[7] | = | sin*sx   cos*sy  ty |
14/// | m[2] m[5] m[8] |   |   0        0      1 |
15/// ```
16#[repr(C)]
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct Mat3x3 {
19    /// Matrix elements in column-major order.
20    pub m: [f32; 9],
21}
22
23impl Mat3x3 {
24    /// Identity matrix.
25    pub const IDENTITY: Mat3x3 = Mat3x3 {
26        m: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
27    };
28
29    /// Creates a new matrix from column-major elements.
30    #[inline]
31    pub const fn new(m: [f32; 9]) -> Self {
32        Self { m }
33    }
34
35    /// Creates a matrix from individual components.
36    ///
37    /// Arguments are in row-major order for readability:
38    /// ```text
39    /// | m00 m01 m02 |
40    /// | m10 m11 m12 |
41    /// | m20 m21 m22 |
42    /// ```
43    #[inline]
44    pub const fn from_rows(
45        m00: f32,
46        m01: f32,
47        m02: f32,
48        m10: f32,
49        m11: f32,
50        m12: f32,
51        m20: f32,
52        m21: f32,
53        m22: f32,
54    ) -> Self {
55        // Convert to column-major storage
56        Self {
57            m: [m00, m10, m20, m01, m11, m21, m02, m12, m22],
58        }
59    }
60
61    /// Creates a translation matrix.
62    #[inline]
63    pub fn translation(tx: f32, ty: f32) -> Self {
64        Self {
65            m: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, tx, ty, 1.0],
66        }
67    }
68
69    /// Creates a rotation matrix from an angle in radians.
70    #[inline]
71    pub fn rotation(angle: f32) -> Self {
72        let (sin, cos) = angle.sin_cos();
73        Self {
74            m: [cos, sin, 0.0, -sin, cos, 0.0, 0.0, 0.0, 1.0],
75        }
76    }
77
78    /// Creates a scale matrix.
79    #[inline]
80    pub fn scale(sx: f32, sy: f32) -> Self {
81        Self {
82            m: [sx, 0.0, 0.0, 0.0, sy, 0.0, 0.0, 0.0, 1.0],
83        }
84    }
85
86    /// Returns the translation component.
87    #[inline]
88    pub fn get_translation(&self) -> Vec2 {
89        Vec2::new(self.m[6], self.m[7])
90    }
91
92    /// Multiplies two matrices.
93    #[inline]
94    pub fn multiply(&self, other: &Self) -> Self {
95        let a = &self.m;
96        let b = &other.m;
97
98        Self {
99            m: [
100                a[0] * b[0] + a[3] * b[1] + a[6] * b[2],
101                a[1] * b[0] + a[4] * b[1] + a[7] * b[2],
102                a[2] * b[0] + a[5] * b[1] + a[8] * b[2],
103                a[0] * b[3] + a[3] * b[4] + a[6] * b[5],
104                a[1] * b[3] + a[4] * b[4] + a[7] * b[5],
105                a[2] * b[3] + a[5] * b[4] + a[8] * b[5],
106                a[0] * b[6] + a[3] * b[7] + a[6] * b[8],
107                a[1] * b[6] + a[4] * b[7] + a[7] * b[8],
108                a[2] * b[6] + a[5] * b[7] + a[8] * b[8],
109            ],
110        }
111    }
112
113    /// Transforms a point by this matrix.
114    #[inline]
115    pub fn transform_point(&self, point: Vec2) -> Vec2 {
116        Vec2::new(
117            self.m[0] * point.x + self.m[3] * point.y + self.m[6],
118            self.m[1] * point.x + self.m[4] * point.y + self.m[7],
119        )
120    }
121
122    /// Transforms a direction by this matrix (ignores translation).
123    #[inline]
124    pub fn transform_direction(&self, direction: Vec2) -> Vec2 {
125        Vec2::new(
126            self.m[0] * direction.x + self.m[3] * direction.y,
127            self.m[1] * direction.x + self.m[4] * direction.y,
128        )
129    }
130
131    /// Computes the determinant of the matrix.
132    #[inline]
133    pub fn determinant(&self) -> f32 {
134        let m = &self.m;
135        m[0] * (m[4] * m[8] - m[7] * m[5]) - m[3] * (m[1] * m[8] - m[7] * m[2])
136            + m[6] * (m[1] * m[5] - m[4] * m[2])
137    }
138
139    /// Computes the inverse of the matrix.
140    ///
141    /// Returns None if the matrix is not invertible (determinant is zero).
142    pub fn inverse(&self) -> Option<Self> {
143        let det = self.determinant();
144        if det.abs() < f32::EPSILON {
145            return None;
146        }
147
148        let m = &self.m;
149        let inv_det = 1.0 / det;
150
151        Some(Self {
152            m: [
153                (m[4] * m[8] - m[7] * m[5]) * inv_det,
154                (m[7] * m[2] - m[1] * m[8]) * inv_det,
155                (m[1] * m[5] - m[4] * m[2]) * inv_det,
156                (m[6] * m[5] - m[3] * m[8]) * inv_det,
157                (m[0] * m[8] - m[6] * m[2]) * inv_det,
158                (m[3] * m[2] - m[0] * m[5]) * inv_det,
159                (m[3] * m[7] - m[6] * m[4]) * inv_det,
160                (m[6] * m[1] - m[0] * m[7]) * inv_det,
161                (m[0] * m[4] - m[3] * m[1]) * inv_det,
162            ],
163        })
164    }
165
166    /// Converts to a 4x4 matrix for 3D rendering.
167    ///
168    /// The result is a 4x4 matrix with the 2D transform in the XY plane:
169    /// ```text
170    /// | m[0] m[3]  0  m[6] |
171    /// | m[1] m[4]  0  m[7] |
172    /// |  0    0    1   0   |
173    /// |  0    0    0   1   |
174    /// ```
175    #[inline]
176    pub fn to_mat4(&self) -> [f32; 16] {
177        [
178            self.m[0], self.m[1], 0.0, 0.0, // column 0
179            self.m[3], self.m[4], 0.0, 0.0, // column 1
180            0.0, 0.0, 1.0, 0.0, // column 2
181            self.m[6], self.m[7], 0.0, 1.0, // column 3
182        ]
183    }
184}
185
186impl Default for Mat3x3 {
187    #[inline]
188    fn default() -> Self {
189        Self::IDENTITY
190    }
191}
192
193impl std::ops::Mul for Mat3x3 {
194    type Output = Self;
195
196    #[inline]
197    fn mul(self, other: Self) -> Self {
198        self.multiply(&other)
199    }
200}