1use std::ops::Mul;
6
7use crate::MathError;
8use crate::vec::Point3;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct Mat3(pub [[f64; 3]; 3]);
18
19impl Mat3 {
20 #[must_use]
22 pub const fn identity() -> Self {
23 Self([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
24 }
25
26 #[must_use]
28 pub const fn transpose(self) -> Self {
29 let m = &self.0;
30 Self([
31 [m[0][0], m[1][0], m[2][0]],
32 [m[0][1], m[1][1], m[2][1]],
33 [m[0][2], m[1][2], m[2][2]],
34 ])
35 }
36
37 #[must_use]
39 pub fn determinant(self) -> f64 {
40 let m = &self.0;
41 m[0][0].mul_add(
42 m[1][1].mul_add(m[2][2], -(m[1][2] * m[2][1])),
43 m[0][1].mul_add(
44 m[1][2].mul_add(m[2][0], -(m[1][0] * m[2][2])),
45 m[0][2] * m[1][0].mul_add(m[2][1], -(m[1][1] * m[2][0])),
46 ),
47 )
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58pub struct Mat4(pub [[f64; 4]; 4]);
59
60impl Mat4 {
61 #[must_use]
63 pub const fn identity() -> Self {
64 Self([
65 [1.0, 0.0, 0.0, 0.0],
66 [0.0, 1.0, 0.0, 0.0],
67 [0.0, 0.0, 1.0, 0.0],
68 [0.0, 0.0, 0.0, 1.0],
69 ])
70 }
71
72 #[must_use]
74 pub const fn translation(tx: f64, ty: f64, tz: f64) -> Self {
75 Self([
76 [1.0, 0.0, 0.0, tx],
77 [0.0, 1.0, 0.0, ty],
78 [0.0, 0.0, 1.0, tz],
79 [0.0, 0.0, 0.0, 1.0],
80 ])
81 }
82
83 #[must_use]
85 pub const fn scale(sx: f64, sy: f64, sz: f64) -> Self {
86 Self([
87 [sx, 0.0, 0.0, 0.0],
88 [0.0, sy, 0.0, 0.0],
89 [0.0, 0.0, sz, 0.0],
90 [0.0, 0.0, 0.0, 1.0],
91 ])
92 }
93
94 #[must_use]
96 pub fn rotation_x(angle: f64) -> Self {
97 let (s, c) = angle.sin_cos();
98 Self([
99 [1.0, 0.0, 0.0, 0.0],
100 [0.0, c, -s, 0.0],
101 [0.0, s, c, 0.0],
102 [0.0, 0.0, 0.0, 1.0],
103 ])
104 }
105
106 #[must_use]
108 pub fn rotation_y(angle: f64) -> Self {
109 let (s, c) = angle.sin_cos();
110 Self([
111 [c, 0.0, s, 0.0],
112 [0.0, 1.0, 0.0, 0.0],
113 [-s, 0.0, c, 0.0],
114 [0.0, 0.0, 0.0, 1.0],
115 ])
116 }
117
118 #[must_use]
120 pub fn rotation_z(angle: f64) -> Self {
121 let (s, c) = angle.sin_cos();
122 Self([
123 [c, -s, 0.0, 0.0],
124 [s, c, 0.0, 0.0],
125 [0.0, 0.0, 1.0, 0.0],
126 [0.0, 0.0, 0.0, 1.0],
127 ])
128 }
129
130 #[must_use]
132 pub fn mul_point(self, p: Point3) -> Point3 {
133 let m = &self.0;
134 Point3::new(
135 m[0][0].mul_add(
136 p.x(),
137 m[0][1].mul_add(p.y(), m[0][2].mul_add(p.z(), m[0][3])),
138 ),
139 m[1][0].mul_add(
140 p.x(),
141 m[1][1].mul_add(p.y(), m[1][2].mul_add(p.z(), m[1][3])),
142 ),
143 m[2][0].mul_add(
144 p.x(),
145 m[2][1].mul_add(p.y(), m[2][2].mul_add(p.z(), m[2][3])),
146 ),
147 )
148 }
149
150 #[must_use]
152 pub const fn transpose(self) -> Self {
153 let m = &self.0;
154 Self([
155 [m[0][0], m[1][0], m[2][0], m[3][0]],
156 [m[0][1], m[1][1], m[2][1], m[3][1]],
157 [m[0][2], m[1][2], m[2][2], m[3][2]],
158 [m[0][3], m[1][3], m[2][3], m[3][3]],
159 ])
160 }
161
162 #[must_use]
164 #[allow(clippy::similar_names)]
165 pub fn determinant(self) -> f64 {
166 let m = &self.0;
167
168 let s0 = m[0][0].mul_add(m[1][1], -(m[1][0] * m[0][1]));
169 let s1 = m[0][0].mul_add(m[1][2], -(m[1][0] * m[0][2]));
170 let s2 = m[0][0].mul_add(m[1][3], -(m[1][0] * m[0][3]));
171 let s3 = m[0][1].mul_add(m[1][2], -(m[1][1] * m[0][2]));
172 let s4 = m[0][1].mul_add(m[1][3], -(m[1][1] * m[0][3]));
173 let s5 = m[0][2].mul_add(m[1][3], -(m[1][2] * m[0][3]));
174
175 let c5 = m[2][2].mul_add(m[3][3], -(m[3][2] * m[2][3]));
176 let c4 = m[2][1].mul_add(m[3][3], -(m[3][1] * m[2][3]));
177 let c3 = m[2][1].mul_add(m[3][2], -(m[3][1] * m[2][2]));
178 let c2 = m[2][0].mul_add(m[3][3], -(m[3][0] * m[2][3]));
179 let c1 = m[2][0].mul_add(m[3][2], -(m[3][0] * m[2][2]));
180 let c0 = m[2][0].mul_add(m[3][1], -(m[3][0] * m[2][1]));
181
182 s0.mul_add(
183 c5,
184 (-s1).mul_add(
185 c4,
186 s2.mul_add(c3, s3.mul_add(c2, (-s4).mul_add(c1, s5 * c0))),
187 ),
188 )
189 }
190
191 #[allow(clippy::similar_names)]
197 pub fn inverse(self) -> Result<Self, MathError> {
198 let m = &self.0;
199
200 let s0 = m[0][0].mul_add(m[1][1], -(m[1][0] * m[0][1]));
202 let s1 = m[0][0].mul_add(m[1][2], -(m[1][0] * m[0][2]));
203 let s2 = m[0][0].mul_add(m[1][3], -(m[1][0] * m[0][3]));
204 let s3 = m[0][1].mul_add(m[1][2], -(m[1][1] * m[0][2]));
205 let s4 = m[0][1].mul_add(m[1][3], -(m[1][1] * m[0][3]));
206 let s5 = m[0][2].mul_add(m[1][3], -(m[1][2] * m[0][3]));
207
208 let c5 = m[2][2].mul_add(m[3][3], -(m[3][2] * m[2][3]));
209 let c4 = m[2][1].mul_add(m[3][3], -(m[3][1] * m[2][3]));
210 let c3 = m[2][1].mul_add(m[3][2], -(m[3][1] * m[2][2]));
211 let c2 = m[2][0].mul_add(m[3][3], -(m[3][0] * m[2][3]));
212 let c1 = m[2][0].mul_add(m[3][2], -(m[3][0] * m[2][2]));
213 let c0 = m[2][0].mul_add(m[3][1], -(m[3][0] * m[2][1]));
214
215 let det = s0.mul_add(
216 c5,
217 (-s1).mul_add(
218 c4,
219 s2.mul_add(c3, s3.mul_add(c2, (-s4).mul_add(c1, s5 * c0))),
220 ),
221 );
222
223 let max_minor = s0
229 .abs()
230 .max(s1.abs())
231 .max(s2.abs())
232 .max(s3.abs())
233 .max(s4.abs())
234 .max(s5.abs())
235 .max(c0.abs())
236 .max(c1.abs())
237 .max(c2.abs())
238 .max(c3.abs())
239 .max(c4.abs())
240 .max(c5.abs());
241 if max_minor == 0.0 {
242 return Err(MathError::SingularMatrix);
243 }
244 if det.abs() < f64::EPSILON * max_minor * max_minor {
245 return Err(MathError::SingularMatrix);
246 }
247
248 let inv_det = 1.0 / det;
249
250 Ok(Self([
251 [
252 m[1][1].mul_add(c5, m[1][3].mul_add(c3, -(m[1][2] * c4))) * inv_det,
253 (-m[0][1]).mul_add(c5, m[0][2].mul_add(c4, -(m[0][3] * c3))) * inv_det,
254 m[3][1].mul_add(s5, m[3][3].mul_add(s3, -(m[3][2] * s4))) * inv_det,
255 (-m[2][1]).mul_add(s5, m[2][2].mul_add(s4, -(m[2][3] * s3))) * inv_det,
256 ],
257 [
258 (-m[1][0]).mul_add(c5, m[1][2].mul_add(c2, -(m[1][3] * c1))) * inv_det,
259 m[0][0].mul_add(c5, m[0][3].mul_add(c1, -(m[0][2] * c2))) * inv_det,
260 (-m[3][0]).mul_add(s5, m[3][2].mul_add(s2, -(m[3][3] * s1))) * inv_det,
261 m[2][0].mul_add(s5, m[2][3].mul_add(s1, -(m[2][2] * s2))) * inv_det,
262 ],
263 [
264 m[1][0].mul_add(c4, m[1][3].mul_add(c0, -(m[1][1] * c2))) * inv_det,
265 (-m[0][0]).mul_add(c4, m[0][1].mul_add(c2, -(m[0][3] * c0))) * inv_det,
266 m[3][0].mul_add(s4, m[3][3].mul_add(s0, -(m[3][1] * s2))) * inv_det,
267 (-m[2][0]).mul_add(s4, m[2][1].mul_add(s2, -(m[2][3] * s0))) * inv_det,
268 ],
269 [
270 (-m[1][0]).mul_add(c3, m[1][1].mul_add(c1, -(m[1][2] * c0))) * inv_det,
271 m[0][0].mul_add(c3, m[0][2].mul_add(c0, -(m[0][1] * c1))) * inv_det,
272 (-m[3][0]).mul_add(s3, m[3][1].mul_add(s1, -(m[3][2] * s0))) * inv_det,
273 m[2][0].mul_add(s3, m[2][2].mul_add(s0, -(m[2][1] * s1))) * inv_det,
274 ],
275 ]))
276 }
277}
278
279impl Mul for Mat4 {
280 type Output = Self;
281
282 fn mul(self, rhs: Self) -> Self {
283 let a = &self.0;
284 let b = &rhs.0;
285 let mut out = [[0.0_f64; 4]; 4];
286 for i in 0..4 {
287 for j in 0..4 {
288 out[i][j] = a[i][0].mul_add(
289 b[0][j],
290 a[i][1].mul_add(b[1][j], a[i][2].mul_add(b[2][j], a[i][3] * b[3][j])),
291 );
292 }
293 }
294 Self(out)
295 }
296}
297
298#[cfg(test)]
299#[allow(clippy::expect_used)]
300mod tests {
301 use super::*;
302
303 fn approx_eq_mat4(a: &Mat4, b: &Mat4, tol: f64) -> bool {
304 for i in 0..4 {
305 for j in 0..4 {
306 if (a.0[i][j] - b.0[i][j]).abs() > tol {
307 return false;
308 }
309 }
310 }
311 true
312 }
313
314 #[test]
315 fn identity_inverse() {
316 let inv = Mat4::identity().inverse().expect("invertible");
317 assert!(approx_eq_mat4(&inv, &Mat4::identity(), 1e-14));
318 }
319
320 #[test]
321 fn translation_inverse() {
322 let m = Mat4::translation(1.0, 2.0, 3.0);
323 let inv = m.inverse().expect("invertible");
324 let product = m * inv;
325 assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-12));
326 }
327
328 #[test]
329 fn rotation_inverse() {
330 let m = Mat4::rotation_x(0.7) * Mat4::rotation_y(1.2) * Mat4::rotation_z(0.3);
331 let inv = m.inverse().expect("invertible");
332 let product = m * inv;
333 assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-12));
334 }
335
336 #[test]
337 fn scale_inverse() {
338 let m = Mat4::scale(2.0, 3.0, 4.0);
339 let inv = m.inverse().expect("invertible");
340 let product = m * inv;
341 assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-12));
342 }
343
344 #[test]
345 fn singular_matrix() {
346 let m = Mat4([[1.0, 0.0, 0.0, 0.0]; 4]);
347 assert!(m.inverse().is_err());
348 }
349
350 #[test]
351 fn combined_transform_inverse() {
352 let m = Mat4::translation(5.0, -3.0, 2.0)
353 * Mat4::rotation_z(std::f64::consts::FRAC_PI_4)
354 * Mat4::scale(2.0, 0.5, 1.0);
355 let inv = m.inverse().expect("invertible");
356 let product = m * inv;
357 assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-10));
358 }
359
360 use proptest::prelude::*;
361
362 proptest! {
363 #[test]
364 fn prop_inverse_roundtrip(
365 tx in -10.0f64..10.0,
366 ty in -10.0f64..10.0,
367 tz in -10.0f64..10.0,
368 angle in 0.0f64..std::f64::consts::TAU,
369 ) {
370 let m = Mat4::translation(tx, ty, tz) * Mat4::rotation_z(angle);
371 let inv = m.inverse().expect("invertible");
372 let product = m * inv;
373 prop_assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-10));
374 }
375
376 #[test]
379 fn prop_inverse_scaled(
380 tx in -10.0f64..10.0,
381 ty in -10.0f64..10.0,
382 tz in -10.0f64..10.0,
383 angle in 0.0f64..std::f64::consts::TAU,
384 scale_exp in prop::sample::select(&[-8_i32, -6, -4, -2, 2, 4, 6][..]),
385 ) {
386 let scale = 10.0_f64.powi(scale_exp);
387 let m = Mat4::translation(tx * scale, ty * scale, tz * scale)
388 * Mat4::rotation_z(angle)
389 * Mat4::scale(scale, scale, scale);
390 let inv = m.inverse().expect("invertible");
391 let product = m * inv;
392 prop_assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-6));
395 }
396 }
397}