1use crate::math::{acos, sin, sin_cos, sqrt};
8
9pub type Mat4 = [[f32; 4]; 4];
12
13pub const IDENTITY: Mat4 = [
15 [1.0, 0.0, 0.0, 0.0],
16 [0.0, 1.0, 0.0, 0.0],
17 [0.0, 0.0, 1.0, 0.0],
18 [0.0, 0.0, 0.0, 1.0],
19];
20
21pub fn mat4_mul(a: Mat4, b: Mat4) -> Mat4 {
23 let mut out = [[0.0f32; 4]; 4];
24 for col in 0..4 {
25 for row in 0..4 {
26 for k in 0..4 {
27 out[col][row] += a[k][row] * b[col][k];
28 }
29 }
30 }
31 out
32}
33
34pub fn mat4_affine_inverse(m: Mat4) -> Mat4 {
39 let a = m;
41 let det = a[0][0] * (a[1][1] * a[2][2] - a[2][1] * a[1][2])
42 - a[1][0] * (a[0][1] * a[2][2] - a[2][1] * a[0][2])
43 + a[2][0] * (a[0][1] * a[1][2] - a[1][1] * a[0][2]);
44 if det.abs() < 1e-12 {
45 return IDENTITY;
46 }
47 let inv_det = 1.0 / det;
48 let mut inv = [[0.0f32; 4]; 4];
50 inv[0][0] = (a[1][1] * a[2][2] - a[2][1] * a[1][2]) * inv_det;
51 inv[1][0] = -(a[1][0] * a[2][2] - a[2][0] * a[1][2]) * inv_det;
52 inv[2][0] = (a[1][0] * a[2][1] - a[2][0] * a[1][1]) * inv_det;
53 inv[0][1] = -(a[0][1] * a[2][2] - a[2][1] * a[0][2]) * inv_det;
54 inv[1][1] = (a[0][0] * a[2][2] - a[2][0] * a[0][2]) * inv_det;
55 inv[2][1] = -(a[0][0] * a[2][1] - a[2][0] * a[0][1]) * inv_det;
56 inv[0][2] = (a[0][1] * a[1][2] - a[1][1] * a[0][2]) * inv_det;
57 inv[1][2] = -(a[0][0] * a[1][2] - a[1][0] * a[0][2]) * inv_det;
58 inv[2][2] = (a[0][0] * a[1][1] - a[1][0] * a[0][1]) * inv_det;
59 let t = [m[3][0], m[3][1], m[3][2]];
61 inv[3][0] = -(inv[0][0] * t[0] + inv[1][0] * t[1] + inv[2][0] * t[2]);
62 inv[3][1] = -(inv[0][1] * t[0] + inv[1][1] * t[1] + inv[2][1] * t[2]);
63 inv[3][2] = -(inv[0][2] * t[0] + inv[1][2] * t[1] + inv[2][2] * t[2]);
64 inv[3][3] = 1.0;
65 inv
66}
67
68pub fn mat4_inverse(m: Mat4) -> Mat4 {
75 let a00 = m[0][0];
78 let a01 = m[1][0];
79 let a02 = m[2][0];
80 let a03 = m[3][0];
81 let a10 = m[0][1];
82 let a11 = m[1][1];
83 let a12 = m[2][1];
84 let a13 = m[3][1];
85 let a20 = m[0][2];
86 let a21 = m[1][2];
87 let a22 = m[2][2];
88 let a23 = m[3][2];
89 let a30 = m[0][3];
90 let a31 = m[1][3];
91 let a32 = m[2][3];
92 let a33 = m[3][3];
93
94 let b00 = a00 * a11 - a01 * a10;
95 let b01 = a00 * a12 - a02 * a10;
96 let b02 = a00 * a13 - a03 * a10;
97 let b03 = a01 * a12 - a02 * a11;
98 let b04 = a01 * a13 - a03 * a11;
99 let b05 = a02 * a13 - a03 * a12;
100 let b06 = a20 * a31 - a21 * a30;
101 let b07 = a20 * a32 - a22 * a30;
102 let b08 = a20 * a33 - a23 * a30;
103 let b09 = a21 * a32 - a22 * a31;
104 let b10 = a21 * a33 - a23 * a31;
105 let b11 = a22 * a33 - a23 * a32;
106
107 let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
108 if det.abs() < 1e-20 || !det.is_finite() {
109 return IDENTITY;
110 }
111 let inv_det = 1.0 / det;
112
113 let i00 = (a11 * b11 - a12 * b10 + a13 * b09) * inv_det;
114 let i01 = (-a01 * b11 + a02 * b10 - a03 * b09) * inv_det;
115 let i02 = (a31 * b05 - a32 * b04 + a33 * b03) * inv_det;
116 let i03 = (-a21 * b05 + a22 * b04 - a23 * b03) * inv_det;
117 let i10 = (-a10 * b11 + a12 * b08 - a13 * b07) * inv_det;
118 let i11 = (a00 * b11 - a02 * b08 + a03 * b07) * inv_det;
119 let i12 = (-a30 * b05 + a32 * b02 - a33 * b01) * inv_det;
120 let i13 = (a20 * b05 - a22 * b02 + a23 * b01) * inv_det;
121 let i20 = (a10 * b10 - a11 * b08 + a13 * b06) * inv_det;
122 let i21 = (-a00 * b10 + a01 * b08 - a03 * b06) * inv_det;
123 let i22 = (a30 * b04 - a31 * b02 + a33 * b00) * inv_det;
124 let i23 = (-a20 * b04 + a21 * b02 - a23 * b00) * inv_det;
125 let i30 = (-a10 * b09 + a11 * b07 - a12 * b06) * inv_det;
126 let i31 = (a00 * b09 - a01 * b07 + a02 * b06) * inv_det;
127 let i32 = (-a30 * b03 + a31 * b01 - a32 * b00) * inv_det;
128 let i33 = (a20 * b03 - a21 * b01 + a22 * b00) * inv_det;
129
130 [
131 [i00, i10, i20, i30],
132 [i01, i11, i21, i31],
133 [i02, i12, i22, i32],
134 [i03, i13, i23, i33],
135 ]
136}
137
138pub type Mat3 = [[f32; 3]; 3];
140
141pub(crate) use crate::math::Quat;
142
143pub(crate) fn rotation_mat3(rotation_deg: [f32; 3]) -> Mat3 {
147 let [pitch, yaw, roll] = rotation_deg;
148 let (sp, cp) = sin_cos(pitch.to_radians());
149 let (syw, cyw) = sin_cos(yaw.to_radians());
150 let (sr, cr) = sin_cos(roll.to_radians());
151 [
152 [cyw * cr + syw * sp * sr, cp * sr, -syw * cr + cyw * sp * sr],
153 [-cyw * sr + syw * sp * cr, cp * cr, syw * sr + cyw * sp * cr],
154 [syw * cp, -sp, cyw * cp],
155 ]
156}
157
158pub fn compose(r: Mat3, scale: [f32; 3], t: [f32; 3]) -> Mat4 {
161 let [sx, sy, sz] = scale;
162 [
163 [r[0][0] * sx, r[0][1] * sx, r[0][2] * sx, 0.0],
164 [r[1][0] * sy, r[1][1] * sy, r[1][2] * sy, 0.0],
165 [r[2][0] * sz, r[2][1] * sz, r[2][2] * sz, 0.0],
166 [t[0], t[1], t[2], 1.0],
167 ]
168}
169
170pub fn trs_matrix(position: [f32; 3], rotation_deg: [f32; 3], scale: [f32; 3]) -> Mat4 {
174 compose(rotation_mat3(rotation_deg), scale, position)
175}
176
177pub(crate) fn quat_from_mat3(m: Mat3) -> Quat {
180 let (m00, m11, m22) = (m[0][0], m[1][1], m[2][2]);
181 let trace = m00 + m11 + m22;
182 if trace > 0.0 {
183 let s = sqrt(trace + 1.0) * 2.0;
184 [
185 (m[1][2] - m[2][1]) / s,
186 (m[2][0] - m[0][2]) / s,
187 (m[0][1] - m[1][0]) / s,
188 0.25 * s,
189 ]
190 } else if m00 > m11 && m00 > m22 {
191 let s = sqrt(1.0 + m00 - m11 - m22) * 2.0;
192 [
193 0.25 * s,
194 (m[1][0] + m[0][1]) / s,
195 (m[2][0] + m[0][2]) / s,
196 (m[1][2] - m[2][1]) / s,
197 ]
198 } else if m11 > m22 {
199 let s = sqrt(1.0 + m11 - m00 - m22) * 2.0;
200 [
201 (m[1][0] + m[0][1]) / s,
202 0.25 * s,
203 (m[2][1] + m[1][2]) / s,
204 (m[2][0] - m[0][2]) / s,
205 ]
206 } else {
207 let s = sqrt(1.0 + m22 - m00 - m11) * 2.0;
208 [
209 (m[2][0] + m[0][2]) / s,
210 (m[2][1] + m[1][2]) / s,
211 0.25 * s,
212 (m[0][1] - m[1][0]) / s,
213 ]
214 }
215}
216
217pub(crate) fn quat_to_mat3(q: Quat) -> Mat3 {
219 let [x, y, z, w] = q;
220 [
221 [
222 1.0 - 2.0 * (y * y + z * z),
223 2.0 * (x * y + w * z),
224 2.0 * (x * z - w * y),
225 ],
226 [
227 2.0 * (x * y - w * z),
228 1.0 - 2.0 * (x * x + z * z),
229 2.0 * (y * z + w * x),
230 ],
231 [
232 2.0 * (x * z + w * y),
233 2.0 * (y * z - w * x),
234 1.0 - 2.0 * (x * x + y * y),
235 ],
236 ]
237}
238
239pub(crate) fn quat_slerp(a: Quat, mut b: Quat, f: f32) -> Quat {
248 let f = f.clamp(0.0, 1.0);
249 let mut dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
250 if dot < 0.0 {
251 b = [-b[0], -b[1], -b[2], -b[3]];
252 dot = -dot;
253 }
254 if dot > 0.9995 {
255 return crate::math::quat_normalize([
256 a[0] + (b[0] - a[0]) * f,
257 a[1] + (b[1] - a[1]) * f,
258 a[2] + (b[2] - a[2]) * f,
259 a[3] + (b[3] - a[3]) * f,
260 ]);
261 }
262 let theta_0 = acos(dot.clamp(-1.0, 1.0));
263 let sin_0 = sin(theta_0);
264 let s_a = sin((1.0 - f) * theta_0) / sin_0;
265 let s_b = sin(f * theta_0) / sin_0;
266 [
267 a[0] * s_a + b[0] * s_b,
268 a[1] * s_a + b[1] * s_b,
269 a[2] * s_a + b[2] * s_b,
270 a[3] * s_a + b[3] * s_b,
271 ]
272}
273
274pub fn decompose(m: Mat4) -> ([f32; 3], Quat, [f32; 3]) {
280 let t = [m[3][0], m[3][1], m[3][2]];
281 let col_len = |c: usize| sqrt(m[c][0] * m[c][0] + m[c][1] * m[c][1] + m[c][2] * m[c][2]);
282 let scale = [col_len(0), col_len(1), col_len(2)];
283 let norm = |c: usize| {
284 let s = scale[c];
285 if s < 1e-12 {
286 [0.0, 0.0, 0.0]
287 } else {
288 [m[c][0] / s, m[c][1] / s, m[c][2] / s]
289 }
290 };
291 let r: Mat3 = [norm(0), norm(1), norm(2)];
292 (t, crate::math::quat_normalize(quat_from_mat3(r)), scale)
293}
294
295pub fn blend_matrices(a: Mat4, b: Mat4, f: f32) -> Mat4 {
300 let f = f.clamp(0.0, 1.0);
301 let (ta, qa, sa) = decompose(a);
302 let (tb, qb, sb) = decompose(b);
303 let mix = |x: [f32; 3], y: [f32; 3]| {
304 [
305 x[0] + (y[0] - x[0]) * f,
306 x[1] + (y[1] - x[1]) * f,
307 x[2] + (y[2] - x[2]) * f,
308 ]
309 };
310 compose(
311 quat_to_mat3(quat_slerp(qa, qb, f)),
312 mix(sa, sb),
313 mix(ta, tb),
314 )
315}
316
317pub fn euler_yxz_from_quat(q: Quat) -> [f32; 3] {
325 crate::math::euler_yxz_deg_from_quat(q)
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 fn approx(a: f32, b: f32) -> bool {
333 (a - b).abs() < 1e-4
334 }
335
336 #[test]
337 fn affine_inverse_round_trips() {
338 let m = trs_matrix([3.0, -2.0, 5.0], [0.0, 30.0, 0.0], [2.0, 2.0, 2.0]);
339 let id = mat4_mul(m, mat4_affine_inverse(m));
340 for col in 0..4 {
341 for row in 0..4 {
342 assert!(approx(id[col][row], IDENTITY[col][row]));
343 }
344 }
345 }
346
347 #[test]
348 fn affine_inverse_of_a_degenerate_matrix_falls_back_to_identity() {
349 let m = trs_matrix([1.0, 2.0, 3.0], [10.0, 20.0, 30.0], [1.0, 0.0, 1.0]);
352 assert_eq!(mat4_affine_inverse(m), IDENTITY);
353 }
354
355 #[test]
356 fn general_inverse_round_trips_a_projection_and_a_view() {
357 let proj = crate::gfx::projection::perspective_rh(75.0f32.to_radians(), 1.6, 0.1, 500.0);
360 let view: Mat4 = [
361 [0.92388, 0.0, -0.38268, 0.0],
362 [0.0, 1.0, 0.0, 0.0],
363 [0.38268, 0.0, 0.92388, 0.0],
364 [-1.5, -0.7, 4.0, 1.0],
365 ];
366 for m in [proj, view, mat4_mul(proj, view)] {
367 for id in [mat4_mul(m, mat4_inverse(m)), mat4_mul(mat4_inverse(m), m)] {
368 for col in 0..4 {
369 for row in 0..4 {
370 assert!(
371 (id[col][row] - IDENTITY[col][row]).abs() < 1e-3,
372 "[{col}][{row}]: {}",
373 id[col][row]
374 );
375 }
376 }
377 }
378 }
379 }
380
381 #[test]
385 fn general_inverse_falls_back_on_a_singular_or_non_finite_matrix() {
386 let mut singular = IDENTITY;
389 singular[1] = [0.0; 4];
390 assert_eq!(mat4_inverse(singular), IDENTITY);
391 let mut nan = IDENTITY;
394 nan[0][0] = f32::NAN;
395 assert_eq!(mat4_inverse(nan), IDENTITY);
396 let mut inf = IDENTITY;
397 inf[2][2] = f32::INFINITY;
398 assert_eq!(mat4_inverse(inf), IDENTITY);
399 }
400
401 #[test]
402 fn quat_mat3_round_trips() {
403 for e in [
407 [0.0, 0.0, 0.0],
408 [30.0, 50.0, 20.0],
409 [-80.0, 140.0, -25.0],
410 [90.0, 0.0, 0.0],
411 [0.0, 180.0, 0.0],
412 ] {
413 let r = rotation_mat3(e);
414 let r2 = quat_to_mat3(quat_from_mat3(r));
415 for c in 0..3 {
416 for row in 0..3 {
417 assert!(
418 approx(r[c][row], r2[c][row]),
419 "e={:?} [{}][{}]: {} vs {}",
420 e,
421 c,
422 row,
423 r[c][row],
424 r2[c][row]
425 );
426 }
427 }
428 }
429 }
430
431 #[test]
432 fn quat_normalize_falls_back_for_a_zero_quaternion() {
433 assert_eq!(crate::math::quat_normalize([0.0; 4]), [0.0, 0.0, 0.0, 1.0]);
434 let n = crate::math::quat_normalize([0.0, 0.0, 0.0, 4.0]);
435 assert!(approx(n[3], 1.0));
436 }
437
438 #[test]
439 fn slerp_midpoint_splits_the_arc_equally() {
440 let qa = quat_from_mat3(rotation_mat3([10.0, 20.0, 30.0]));
444 let qb = quat_from_mat3(rotation_mat3([70.0, -40.0, 80.0]));
445 let qm = quat_slerp(qa, qb, 0.5);
446 let angle = |x: Quat, y: Quat| {
447 let d = (x[0] * y[0] + x[1] * y[1] + x[2] * y[2] + x[3] * y[3])
448 .abs()
449 .min(1.0);
450 2.0 * acos(d)
451 };
452 assert!(
453 approx(angle(qa, qm), angle(qm, qb)),
454 "arcs {} vs {}",
455 angle(qa, qm),
456 angle(qm, qb)
457 );
458 }
459
460 #[test]
461 fn decompose_round_trips_a_composed_matrix() {
462 let m = trs_matrix([3.0, -2.0, 5.0], [25.0, -60.0, 40.0], [1.5, 0.5, 2.0]);
465 let (t, q, s) = decompose(m);
466 let rebuilt = compose(quat_to_mat3(q), s, t);
467 for c in 0..4 {
468 for row in 0..4 {
469 assert!(
470 approx(rebuilt[c][row], m[c][row]),
471 "[{}][{}]: {} vs {}",
472 c,
473 row,
474 rebuilt[c][row],
475 m[c][row]
476 );
477 }
478 }
479 }
480
481 #[test]
482 fn blend_matrices_endpoints_are_exact_and_the_middle_slerps() {
483 let a = trs_matrix([1.0, 2.0, 3.0], [10.0, 20.0, 30.0], [1.0, 1.5, 2.0]);
484 let b = trs_matrix([-4.0, 0.0, 5.0], [70.0, -40.0, 15.0], [2.0, 1.0, 0.5]);
485 for (f, want) in [(0.0, a), (1.0, b)] {
486 let got = blend_matrices(a, b, f);
487 for c in 0..4 {
488 for row in 0..4 {
489 assert!(approx(got[c][row], want[c][row]), "f={f} [{c}][{row}]");
490 }
491 }
492 }
493 let mid = blend_matrices(a, b, 0.5);
496 let (_, _, scale) = decompose(mid);
497 assert!(approx(scale[0], 1.5) && approx(scale[1], 1.25) && approx(scale[2], 1.25));
498 assert!(approx(mid[3][0], -1.5) && approx(mid[3][1], 1.0) && approx(mid[3][2], 4.0));
499 assert_eq!(blend_matrices(a, b, -1.0), blend_matrices(a, b, 0.0));
501 assert_eq!(blend_matrices(a, b, 2.0), blend_matrices(a, b, 1.0));
502 }
503
504 #[test]
505 fn euler_from_quat_round_trips_through_the_rotation_matrix() {
506 for e in [
510 [0.0, 0.0, 0.0],
511 [25.0, -60.0, 40.0],
512 [-80.0, 140.0, -25.0],
513 [10.0, 200.0, -170.0],
514 ] {
515 let r = rotation_mat3(e);
516 let q = quat_from_mat3(r);
517 let e2 = euler_yxz_from_quat(q);
518 let r2 = rotation_mat3(e2);
519 for c in 0..3 {
520 for row in 0..3 {
521 assert!(
522 approx(r[c][row], r2[c][row]),
523 "e={:?} [{}][{}]: {} vs {}",
524 e,
525 c,
526 row,
527 r[c][row],
528 r2[c][row]
529 );
530 }
531 }
532 }
533 }
534
535 #[test]
536 fn euler_from_quat_handles_gimbal_lock() {
537 for e in [[90.0, 35.0, 0.0], [-90.0, -110.0, 0.0]] {
540 let r = rotation_mat3(e);
541 let e2 = euler_yxz_from_quat(quat_from_mat3(r));
542 assert!(e2.iter().all(|v| v.is_finite()), "non-finite for {:?}", e);
543 let r2 = rotation_mat3(e2);
544 for c in 0..3 {
545 for row in 0..3 {
546 assert!(approx(r[c][row], r2[c][row]), "e={:?} [{}][{}]", e, c, row);
547 }
548 }
549 }
550 }
551
552 #[test]
556 fn decomposing_a_collapsed_axis_yields_a_zero_scale() {
557 let mut m = IDENTITY;
558 m[1] = [0.0, 0.0, 0.0, 0.0];
559 m[3] = [1.0, 2.0, 3.0, 1.0];
560
561 let (translation, rotation, scale) = decompose(m);
562 assert_eq!(translation, [1.0, 2.0, 3.0]);
563 assert_eq!(scale[1], 0.0, "the collapsed axis has no length");
564 assert_eq!((scale[0], scale[2]), (1.0, 1.0), "the others are intact");
565 assert!(
566 rotation.iter().all(|v| v.is_finite()),
567 "{rotation:?} is not a usable rotation"
568 );
569 }
570}