gfx_maths/
mat4.rs

1use auto_ops::impl_op_ex;
2
3use crate::{Quaternion, Vec3, Vec4};
4
5/// A struct representing a 4x4 matrix.
6///
7/// It's values are stored in column-major order by default,
8/// as expected by OpenGL and accepted by all other APIs.
9/// To change this, use feature `mat-row-major`
10#[derive(Debug, Clone, Copy, PartialEq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[repr(C)]
13pub struct Mat4 {
14    pub values: [f32; 4 * 4],
15}
16
17impl Default for Mat4 {
18    /// Creates the identity matrix.
19    fn default() -> Self {
20        Self::identity()
21    }
22}
23
24#[cfg(not(feature = "mat-row-major"))]
25const fn cr(c: usize, r: usize) -> usize {
26    r + c * 4
27}
28#[cfg(feature = "mat-row-major")]
29const fn cr(c: usize, r: usize) -> usize {
30    r * 4 + c
31}
32
33impl Mat4 {
34    /// Creates the identity matrix.
35    pub const fn identity() -> Self {
36        Self {
37            values: [
38                1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
39            ],
40        }
41    }
42
43    /// Creates a 3D translation matrix.
44    pub const fn translate(t: Vec3) -> Self {
45        let mut res = Self::identity();
46
47        res.values[cr(3, 0)] = t.x;
48        res.values[cr(3, 1)] = t.y;
49        res.values[cr(3, 2)] = t.z;
50
51        res
52    }
53
54    /// Creates a 3D rotation matrix.
55    pub fn rotate(r: Quaternion) -> Self {
56        let mut res = Self::identity();
57
58        let right = r.right();
59        let up = r.up();
60        let fwd = r.forward();
61
62        res.values[cr(0, 0)] = right.x;
63        res.values[cr(0, 1)] = right.y;
64        res.values[cr(0, 2)] = right.z;
65
66        res.values[cr(1, 0)] = up.x;
67        res.values[cr(1, 1)] = up.y;
68        res.values[cr(1, 2)] = up.z;
69
70        res.values[cr(2, 0)] = fwd.x;
71        res.values[cr(2, 1)] = fwd.y;
72        res.values[cr(2, 2)] = fwd.z;
73
74        res
75    }
76
77    /// Creates a 3D scale matrix.
78    pub const fn scale(s: Vec3) -> Self {
79        let mut res = Self::identity();
80
81        res.values[cr(0, 0)] = s.x;
82        res.values[cr(1, 1)] = s.y;
83        res.values[cr(2, 2)] = s.z;
84
85        res
86    }
87
88    /// Creates a 3D local-to-world/object-to-world matrix.
89    ///
90    /// When multiplying this matrix by a vector, it will be
91    /// - scaled by `s`
92    /// - rotated by `r`
93    /// - translated by `t`
94    ///
95    /// in this order.
96    pub fn local_to_world(t: Vec3, r: Quaternion, s: Vec3) -> Self {
97        Self::translate(t) * Self::rotate(r) * Self::scale(s)
98    }
99
100    /// Creates a 3D world-to-local/world-to-object matrix.
101    ///
102    /// This matrix does the opposite of [`local_to_world()`](Self::local_to_world())
103    pub fn world_to_local(t: Vec3, r: Quaternion, s: Vec3) -> Self {
104        Self::scale(1.0 / s) * Self::rotate(-r) * Self::translate(-t)
105    }
106
107    /// Creates an orthographic projection matrix
108    /// with z mapped to \[0; 1\], as expected by Vulkan.
109    pub fn orthographic_vulkan(
110        left: f32,
111        right: f32,
112        bottom: f32,
113        top: f32,
114        near: f32,
115        far: f32,
116    ) -> Self {
117        let mut res = Self::identity();
118
119        let a = 2.0 / (right - left);
120        let b = (-left - right) / (right - left);
121        let c = 2.0 / (top - bottom);
122        let d = (-bottom - top) / (top - bottom);
123        let e = 1.0 / (far - near);
124        let f = -near / (far - near);
125
126        res.values[cr(0, 0)] = a;
127        res.values[cr(3, 0)] = b;
128
129        res.values[cr(1, 1)] = c;
130        res.values[cr(3, 1)] = d;
131
132        res.values[cr(2, 2)] = e;
133        res.values[cr(3, 2)] = f;
134
135        res
136    }
137
138    /// Creates an orthographic projection matrix
139    /// with z mapped to \[-1; 1\], as expected by OpenGL.
140    pub fn orthographic_opengl(
141        left: f32,
142        right: f32,
143        bottom: f32,
144        top: f32,
145        near: f32,
146        far: f32,
147    ) -> Self {
148        let mut res = Self::identity();
149
150        let a = 2.0 / (right - left);
151        let b = (-left - right) / (right - left);
152        let c = 2.0 / (top - bottom);
153        let d = (-bottom - top) / (top - bottom);
154        let e = 2.0 / (far - near);
155        let f = (-near - far) / (far - near);
156
157        res.values[cr(0, 0)] = a;
158        res.values[cr(3, 0)] = b;
159
160        res.values[cr(1, 1)] = c;
161        res.values[cr(3, 1)] = d;
162
163        res.values[cr(2, 2)] = e;
164        res.values[cr(3, 2)] = f;
165
166        res
167    }
168
169    /// Creates an inverse orthographics projection matrix
170    /// with z mapped to \[0; 1\], as expected by Vulkan.
171    ///
172    /// The world position of a uv/depth pair can be reconstructed with the same code as shown
173    /// in [`inverse_perspective_vulkan()`](Self::inverse_perspective_vulkan())
174    pub fn inverse_orthographic_vulkan(
175        left: f32,
176        right: f32,
177        bottom: f32,
178        top: f32,
179        near: f32,
180        far: f32,
181    ) -> Self {
182        let mut res = Self::identity();
183
184        let a = 2.0 * (right - left);
185        let b = (-left - right) / (right - left);
186        let c = 2.0 * (top - bottom);
187        let d = (-bottom - top) / (top - bottom);
188        let e = 1.0 / (far - near);
189        let f = -near / (far - near);
190
191        res.values[cr(0, 0)] = 1.0 / a;
192        res.values[cr(3, 0)] = -b / a;
193
194        res.values[cr(1, 1)] = 1.0 / c;
195        res.values[cr(3, 1)] = -d / c;
196
197        res.values[cr(2, 2)] = 1.0 / e;
198        res.values[cr(3, 2)] = -f / e;
199
200        res
201    }
202
203    /// Creates an inverse orthographics projection matrix
204    /// with z mapped to \[-1; 1\], as expected by OpenGL.
205    ///
206    /// The world position of a uv/depth pair can be reconstructed with the same code as shown
207    /// in [`inverse_perspective_opengl()`](Self::inverse_perspective_opengl())
208    pub fn inverse_orthographic_opengl(
209        left: f32,
210        right: f32,
211        bottom: f32,
212        top: f32,
213        near: f32,
214        far: f32,
215    ) -> Self {
216        let mut res = Self::identity();
217
218        let a = 2.0 * (right - left);
219        let b = (-left - right) / (right - left);
220        let c = 2.0 * (top - bottom);
221        let d = (-bottom - top) / (top - bottom);
222        let e = 2.0 * (far - near);
223        let f = (-near - far) / (far - near);
224
225        res.values[cr(0, 0)] = 1.0 / a;
226        res.values[cr(3, 0)] = -b / a;
227
228        res.values[cr(1, 1)] = 1.0 / c;
229        res.values[cr(3, 1)] = -d / c;
230
231        res.values[cr(2, 2)] = 1.0 / e;
232        res.values[cr(3, 2)] = -f / e;
233
234        res
235    }
236
237    /// Creates a perspective projection matrix
238    /// with z mapped to \[0; 1\], as expected by Vulkan.
239    pub fn perspective_vulkan(fov_rad: f32, near: f32, far: f32, aspect: f32) -> Self {
240        let mut res = Self::identity();
241        let thfov = (fov_rad * 0.5).tan();
242
243        res.values[cr(0, 0)] = 1.0 / (thfov * aspect);
244        res.values[cr(1, 1)] = 1.0 / thfov;
245
246        res.values[cr(2, 2)] = far / (far - near);
247        res.values[cr(3, 2)] = (-far * near) / (far - near);
248
249        res.values[cr(2, 3)] = 1.0;
250        res.values[cr(3, 3)] = 0.0;
251
252        res
253    }
254
255    /// Creates a perspective projection matrix
256    /// with z mapped to \[-1; 1\], as expected by OpenGL.
257    pub fn perspective_opengl(fov_rad: f32, near: f32, far: f32, aspect: f32) -> Self {
258        let mut res = Self::identity();
259        let thfov = (fov_rad * 0.5).tan();
260
261        res.values[cr(0, 0)] = 1.0 / (thfov * aspect);
262        res.values[cr(1, 1)] = 1.0 / thfov;
263
264        res.values[cr(2, 2)] = (far + near) / (far - near);
265        res.values[cr(3, 2)] = (-2.0 * far * near) / (far - near);
266
267        res.values[cr(2, 3)] = 1.0;
268        res.values[cr(3, 3)] = 0.0;
269
270        res
271    }
272
273    /// Creates an inverse perspective matrix
274    /// with z mapped to \[0; 1\], as expected by Vulkan.
275    ///
276    /// This matrix can be used to reconstruct the world position from a uv/depth pair in a shader.
277    /// This pseudo code can be used:
278    /// ```GLSL
279    /// vec4 clipPos = vec4(uv.xy, depth, 1.0);
280    /// vec4 worldPos = invProjection * clipPos;
281    /// worldPos /= worldPos.w;
282    /// ```
283    pub fn inverse_perspective_vulkan(fov_rad: f32, near: f32, far: f32, aspect: f32) -> Self {
284        let mut res = Self::identity();
285
286        let thfov = (fov_rad * 0.5).tan();
287        let c = far / (far - near);
288        let d = (-far * near) / (far - near);
289
290        res.values[cr(0, 0)] = thfov * aspect;
291        res.values[cr(1, 1)] = thfov;
292
293        res.values[cr(3, 2)] = 1.0;
294
295        res.values[cr(2, 2)] = 0.0;
296        res.values[cr(2, 3)] = 1.0 / d;
297        res.values[cr(3, 3)] = -c / d;
298
299        res
300    }
301
302    /// Creates an inverse perspective matrix
303    /// with z mapped to \[-1; 1\], as expected by OpenGL.
304    ///
305    /// This matrix can be used to reconstruct the world position from a uv/depth pair in a shader.
306    /// This pseudo code can be used:
307    /// ```GLSL
308    /// vec4 clipPos = vec4(uv.xy, depth * 2.0 - 1.0, 1.0);
309    /// vec4 worldPos = invProjection * clipPos;
310    /// worldPos /= worldPos.w;
311    /// ```
312    pub fn inverse_perspective_opengl(fov_rad: f32, near: f32, far: f32, aspect: f32) -> Self {
313        let mut res = Self::identity();
314
315        let thfov = (fov_rad * 0.5).tan();
316        let c = (far + near) / (far - near);
317        let d = (-2.0 * far * near) / (far - near);
318
319        res.values[cr(0, 0)] = thfov * aspect;
320        res.values[cr(1, 1)] = thfov;
321
322        res.values[cr(3, 2)] = 1.0;
323
324        res.values[cr(2, 2)] = 0.0;
325        res.values[cr(2, 3)] = 1.0 / d;
326        res.values[cr(3, 3)] = -c / d;
327
328        res
329    }
330
331    /// Returns a value indexed by `column` and `row`
332    pub const fn get(&self, column: usize, row: usize) -> f32 {
333        self.values[cr(column, row)]
334    }
335
336    /// Sets the value indexed by `column` and `row` to `val`
337    pub fn set(&mut self, column: usize, row: usize, val: f32) {
338        self.values[cr(column, row)] = val;
339    }
340
341    /// Returns a transposed copy of `self`.
342    #[must_use]
343    pub fn transposed(&self) -> Mat4 {
344        let mut res = Mat4::identity();
345
346        for c in 0..4 {
347            for r in 0..4 {
348                res.values[cr(r, c)] = self.values[cr(c, r)];
349            }
350        }
351
352        res
353    }
354
355    /// Returns the underlying values as a slice
356    pub fn as_slice(&self) -> &[f32] {
357        &self.values
358    }
359
360    /// Returns the underlying values as a mutable slice
361    pub fn as_mut_slice(&mut self) -> &mut [f32] {
362        &mut self.values
363    }
364
365    /// Returns the underlying values as a pointer with no size information
366    pub fn as_ptr(&self) -> *const f32 {
367        self.values.as_ptr()
368    }
369
370    /// Returns the underlying values as a mutable pointer with no size information
371    pub fn as_mut_ptr(&mut self) -> *mut f32 {
372        self.values.as_mut_ptr()
373    }
374}
375
376impl_op_ex!(*|a: &Mat4, b: &Mat4| -> Mat4 {
377    let mut res = Mat4::identity();
378
379    for r in 0..4 {
380        for c in 0..4 {
381            res.values[cr(c, r)] = a.values[cr(0, r)] * b.values[cr(c, 0)]
382                + a.values[cr(1, r)] * b.values[cr(c, 1)]
383                + a.values[cr(2, r)] * b.values[cr(c, 2)]
384                + a.values[cr(3, r)] * b.values[cr(c, 3)];
385        }
386    }
387
388    res
389});
390
391impl_op_ex!(*|a: &Mat4, b: &Vec4| -> Vec4 {
392    Vec4 {
393        x: a.values[cr(0, 0)] * b.x
394            + a.values[cr(1, 0)] * b.y
395            + a.values[cr(2, 0)] * b.z
396            + a.values[cr(3, 0)] * b.w,
397        y: a.values[cr(0, 1)] * b.x
398            + a.values[cr(1, 1)] * b.y
399            + a.values[cr(2, 1)] * b.z
400            + a.values[cr(3, 1)] * b.w,
401        z: a.values[cr(0, 2)] * b.x
402            + a.values[cr(1, 2)] * b.y
403            + a.values[cr(2, 2)] * b.z
404            + a.values[cr(3, 2)] * b.w,
405        w: a.values[cr(0, 3)] * b.x
406            + a.values[cr(1, 3)] * b.y
407            + a.values[cr(2, 3)] * b.z
408            + a.values[cr(3, 3)] * b.w,
409    }
410});
411
412impl_op_ex!(*|a: &Mat4, b: &Vec3| -> Vec3 {
413    Vec3 {
414        x: a.values[cr(0, 0)] * b.x + a.values[cr(1, 0)] * b.y + a.values[cr(2, 0)] * b.z,
415        y: a.values[cr(0, 1)] * b.x + a.values[cr(1, 1)] * b.y + a.values[cr(2, 1)] * b.z,
416        z: a.values[cr(0, 2)] * b.x + a.values[cr(1, 2)] * b.y + a.values[cr(2, 2)] * b.z,
417    }
418});
419
420impl From<[f32; 16]> for Mat4 {
421    fn from(d: [f32; 16]) -> Self {
422        Self { values: d }
423    }
424}
425
426impl From<[[f32; 4]; 4]> for Mat4 {
427    fn from(d: [[f32; 4]; 4]) -> Self {
428        Self {
429            values: [
430                d[0][0], d[0][1], d[0][2], d[0][3], d[1][0], d[1][1], d[1][2], d[1][3], d[2][0],
431                d[2][1], d[2][2], d[2][3], d[3][0], d[3][1], d[3][2], d[3][3],
432            ],
433        }
434    }
435}
436
437impl std::ops::Index<(usize, usize)> for Mat4 {
438    type Output = f32;
439
440    fn index(&self, (c, r): (usize, usize)) -> &Self::Output {
441        &self.values[cr(c, r)]
442    }
443}
444
445impl std::ops::IndexMut<(usize, usize)> for Mat4 {
446    fn index_mut(&mut self, (c, r): (usize, usize)) -> &mut Self::Output {
447        &mut self.values[cr(c, r)]
448    }
449}