1use crate::math::{acos, atan2, 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) type Quat = [f32; 4];
143
144pub(crate) fn rotation_mat3(rotation_deg: [f32; 3]) -> Mat3 {
148 let [pitch, yaw, roll] = rotation_deg;
149 let (sp, cp) = sin_cos(pitch.to_radians());
150 let (syw, cyw) = sin_cos(yaw.to_radians());
151 let (sr, cr) = sin_cos(roll.to_radians());
152 [
153 [cyw * cr + syw * sp * sr, cp * sr, -syw * cr + cyw * sp * sr],
154 [-cyw * sr + syw * sp * cr, cp * cr, syw * sr + cyw * sp * cr],
155 [syw * cp, -sp, cyw * cp],
156 ]
157}
158
159pub fn compose(r: Mat3, scale: [f32; 3], t: [f32; 3]) -> Mat4 {
162 let [sx, sy, sz] = scale;
163 [
164 [r[0][0] * sx, r[0][1] * sx, r[0][2] * sx, 0.0],
165 [r[1][0] * sy, r[1][1] * sy, r[1][2] * sy, 0.0],
166 [r[2][0] * sz, r[2][1] * sz, r[2][2] * sz, 0.0],
167 [t[0], t[1], t[2], 1.0],
168 ]
169}
170
171pub fn trs_matrix(position: [f32; 3], rotation_deg: [f32; 3], scale: [f32; 3]) -> Mat4 {
175 compose(rotation_mat3(rotation_deg), scale, position)
176}
177
178pub(crate) fn quat_from_mat3(m: Mat3) -> Quat {
181 let (m00, m11, m22) = (m[0][0], m[1][1], m[2][2]);
182 let trace = m00 + m11 + m22;
183 if trace > 0.0 {
184 let s = sqrt(trace + 1.0) * 2.0;
185 [
186 (m[1][2] - m[2][1]) / s,
187 (m[2][0] - m[0][2]) / s,
188 (m[0][1] - m[1][0]) / s,
189 0.25 * s,
190 ]
191 } else if m00 > m11 && m00 > m22 {
192 let s = sqrt(1.0 + m00 - m11 - m22) * 2.0;
193 [
194 0.25 * s,
195 (m[1][0] + m[0][1]) / s,
196 (m[2][0] + m[0][2]) / s,
197 (m[1][2] - m[2][1]) / s,
198 ]
199 } else if m11 > m22 {
200 let s = sqrt(1.0 + m11 - m00 - m22) * 2.0;
201 [
202 (m[1][0] + m[0][1]) / s,
203 0.25 * s,
204 (m[2][1] + m[1][2]) / s,
205 (m[2][0] - m[0][2]) / s,
206 ]
207 } else {
208 let s = sqrt(1.0 + m22 - m00 - m11) * 2.0;
209 [
210 (m[2][0] + m[0][2]) / s,
211 (m[2][1] + m[1][2]) / s,
212 0.25 * s,
213 (m[0][1] - m[1][0]) / s,
214 ]
215 }
216}
217
218pub(crate) fn quat_to_mat3(q: Quat) -> Mat3 {
220 let [x, y, z, w] = q;
221 [
222 [
223 1.0 - 2.0 * (y * y + z * z),
224 2.0 * (x * y + w * z),
225 2.0 * (x * z - w * y),
226 ],
227 [
228 2.0 * (x * y - w * z),
229 1.0 - 2.0 * (x * x + z * z),
230 2.0 * (y * z + w * x),
231 ],
232 [
233 2.0 * (x * z + w * y),
234 2.0 * (y * z - w * x),
235 1.0 - 2.0 * (x * x + y * y),
236 ],
237 ]
238}
239
240pub(crate) fn quat_normalize(q: Quat) -> Quat {
243 let len = sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
244 if len < 1e-12 {
245 return [0.0, 0.0, 0.0, 1.0];
246 }
247 [q[0] / len, q[1] / len, q[2] / len, q[3] / len]
248}
249
250pub(crate) fn quat_slerp(a: Quat, mut b: Quat, f: f32) -> Quat {
256 let f = f.clamp(0.0, 1.0);
257 let mut dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
258 if dot < 0.0 {
259 b = [-b[0], -b[1], -b[2], -b[3]];
260 dot = -dot;
261 }
262 if dot > 0.9995 {
263 return quat_normalize([
264 a[0] + (b[0] - a[0]) * f,
265 a[1] + (b[1] - a[1]) * f,
266 a[2] + (b[2] - a[2]) * f,
267 a[3] + (b[3] - a[3]) * f,
268 ]);
269 }
270 let theta_0 = acos(dot.clamp(-1.0, 1.0));
271 let sin_0 = sin(theta_0);
272 let s_a = sin((1.0 - f) * theta_0) / sin_0;
273 let s_b = sin(f * theta_0) / sin_0;
274 [
275 a[0] * s_a + b[0] * s_b,
276 a[1] * s_a + b[1] * s_b,
277 a[2] * s_a + b[2] * s_b,
278 a[3] * s_a + b[3] * s_b,
279 ]
280}
281
282pub fn decompose(m: Mat4) -> ([f32; 3], Quat, [f32; 3]) {
288 let t = [m[3][0], m[3][1], m[3][2]];
289 let col_len = |c: usize| sqrt(m[c][0] * m[c][0] + m[c][1] * m[c][1] + m[c][2] * m[c][2]);
290 let scale = [col_len(0), col_len(1), col_len(2)];
291 let norm = |c: usize| {
292 let s = scale[c];
293 if s < 1e-12 {
294 [0.0, 0.0, 0.0]
295 } else {
296 [m[c][0] / s, m[c][1] / s, m[c][2] / s]
297 }
298 };
299 let r: Mat3 = [norm(0), norm(1), norm(2)];
300 (t, quat_normalize(quat_from_mat3(r)), scale)
301}
302
303pub fn blend_matrices(a: Mat4, b: Mat4, f: f32) -> Mat4 {
308 let f = f.clamp(0.0, 1.0);
309 let (ta, qa, sa) = decompose(a);
310 let (tb, qb, sb) = decompose(b);
311 let mix = |x: [f32; 3], y: [f32; 3]| {
312 [
313 x[0] + (y[0] - x[0]) * f,
314 x[1] + (y[1] - x[1]) * f,
315 x[2] + (y[2] - x[2]) * f,
316 ]
317 };
318 compose(
319 quat_to_mat3(quat_slerp(qa, qb, f)),
320 mix(sa, sb),
321 mix(ta, tb),
322 )
323}
324
325pub fn euler_yxz_from_quat(q: Quat) -> [f32; 3] {
333 let m = quat_to_mat3(quat_normalize(q));
334 let sp = (-m[2][1]).clamp(-1.0, 1.0);
335 let cp = sqrt(m[2][0] * m[2][0] + m[2][2] * m[2][2]);
339 let pitch = atan2(sp, cp);
340 let (yaw, roll) = if cp > 1e-4 {
341 (atan2(m[2][0], m[2][2]), atan2(m[0][1], m[1][1]))
342 } else {
343 (atan2(sp * m[1][0], m[0][0]), 0.0)
344 };
345 [pitch.to_degrees(), yaw.to_degrees(), roll.to_degrees()]
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 fn approx(a: f32, b: f32) -> bool {
353 (a - b).abs() < 1e-4
354 }
355
356 #[test]
357 fn affine_inverse_round_trips() {
358 let m = trs_matrix([3.0, -2.0, 5.0], [0.0, 30.0, 0.0], [2.0, 2.0, 2.0]);
359 let id = mat4_mul(m, mat4_affine_inverse(m));
360 for col in 0..4 {
361 for row in 0..4 {
362 assert!(approx(id[col][row], IDENTITY[col][row]));
363 }
364 }
365 }
366
367 #[test]
368 fn affine_inverse_of_a_degenerate_matrix_falls_back_to_identity() {
369 let m = trs_matrix([1.0, 2.0, 3.0], [10.0, 20.0, 30.0], [1.0, 0.0, 1.0]);
372 assert_eq!(mat4_affine_inverse(m), IDENTITY);
373 }
374
375 #[test]
376 fn general_inverse_round_trips_a_projection_and_a_view() {
377 let proj = crate::gfx::projection::perspective_rh(75.0f32.to_radians(), 1.6, 0.1, 500.0);
380 let view: Mat4 = [
381 [0.92388, 0.0, -0.38268, 0.0],
382 [0.0, 1.0, 0.0, 0.0],
383 [0.38268, 0.0, 0.92388, 0.0],
384 [-1.5, -0.7, 4.0, 1.0],
385 ];
386 for m in [proj, view, mat4_mul(proj, view)] {
387 for id in [mat4_mul(m, mat4_inverse(m)), mat4_mul(mat4_inverse(m), m)] {
388 for col in 0..4 {
389 for row in 0..4 {
390 assert!(
391 (id[col][row] - IDENTITY[col][row]).abs() < 1e-3,
392 "[{col}][{row}]: {}",
393 id[col][row]
394 );
395 }
396 }
397 }
398 }
399 }
400
401 #[test]
405 fn general_inverse_falls_back_on_a_singular_or_non_finite_matrix() {
406 let mut singular = IDENTITY;
409 singular[1] = [0.0; 4];
410 assert_eq!(mat4_inverse(singular), IDENTITY);
411 let mut nan = IDENTITY;
414 nan[0][0] = f32::NAN;
415 assert_eq!(mat4_inverse(nan), IDENTITY);
416 let mut inf = IDENTITY;
417 inf[2][2] = f32::INFINITY;
418 assert_eq!(mat4_inverse(inf), IDENTITY);
419 }
420
421 #[test]
422 fn quat_mat3_round_trips() {
423 for e in [
427 [0.0, 0.0, 0.0],
428 [30.0, 50.0, 20.0],
429 [-80.0, 140.0, -25.0],
430 [90.0, 0.0, 0.0],
431 [0.0, 180.0, 0.0],
432 ] {
433 let r = rotation_mat3(e);
434 let r2 = quat_to_mat3(quat_from_mat3(r));
435 for c in 0..3 {
436 for row in 0..3 {
437 assert!(
438 approx(r[c][row], r2[c][row]),
439 "e={:?} [{}][{}]: {} vs {}",
440 e,
441 c,
442 row,
443 r[c][row],
444 r2[c][row]
445 );
446 }
447 }
448 }
449 }
450
451 #[test]
452 fn quat_normalize_falls_back_for_a_zero_quaternion() {
453 assert_eq!(quat_normalize([0.0; 4]), [0.0, 0.0, 0.0, 1.0]);
454 let n = quat_normalize([0.0, 0.0, 0.0, 4.0]);
455 assert!(approx(n[3], 1.0));
456 }
457
458 #[test]
459 fn slerp_midpoint_splits_the_arc_equally() {
460 let qa = quat_from_mat3(rotation_mat3([10.0, 20.0, 30.0]));
464 let qb = quat_from_mat3(rotation_mat3([70.0, -40.0, 80.0]));
465 let qm = quat_slerp(qa, qb, 0.5);
466 let angle = |x: Quat, y: Quat| {
467 let d = (x[0] * y[0] + x[1] * y[1] + x[2] * y[2] + x[3] * y[3])
468 .abs()
469 .min(1.0);
470 2.0 * acos(d)
471 };
472 assert!(
473 approx(angle(qa, qm), angle(qm, qb)),
474 "arcs {} vs {}",
475 angle(qa, qm),
476 angle(qm, qb)
477 );
478 }
479
480 #[test]
481 fn decompose_round_trips_a_composed_matrix() {
482 let m = trs_matrix([3.0, -2.0, 5.0], [25.0, -60.0, 40.0], [1.5, 0.5, 2.0]);
485 let (t, q, s) = decompose(m);
486 let rebuilt = compose(quat_to_mat3(q), s, t);
487 for c in 0..4 {
488 for row in 0..4 {
489 assert!(
490 approx(rebuilt[c][row], m[c][row]),
491 "[{}][{}]: {} vs {}",
492 c,
493 row,
494 rebuilt[c][row],
495 m[c][row]
496 );
497 }
498 }
499 }
500
501 #[test]
502 fn blend_matrices_endpoints_are_exact_and_the_middle_slerps() {
503 let a = trs_matrix([1.0, 2.0, 3.0], [10.0, 20.0, 30.0], [1.0, 1.5, 2.0]);
504 let b = trs_matrix([-4.0, 0.0, 5.0], [70.0, -40.0, 15.0], [2.0, 1.0, 0.5]);
505 for (f, want) in [(0.0, a), (1.0, b)] {
506 let got = blend_matrices(a, b, f);
507 for c in 0..4 {
508 for row in 0..4 {
509 assert!(approx(got[c][row], want[c][row]), "f={f} [{c}][{row}]");
510 }
511 }
512 }
513 let mid = blend_matrices(a, b, 0.5);
516 let (_, _, scale) = decompose(mid);
517 assert!(approx(scale[0], 1.5) && approx(scale[1], 1.25) && approx(scale[2], 1.25));
518 assert!(approx(mid[3][0], -1.5) && approx(mid[3][1], 1.0) && approx(mid[3][2], 4.0));
519 assert_eq!(blend_matrices(a, b, -1.0), blend_matrices(a, b, 0.0));
521 assert_eq!(blend_matrices(a, b, 2.0), blend_matrices(a, b, 1.0));
522 }
523
524 #[test]
525 fn euler_from_quat_round_trips_through_the_rotation_matrix() {
526 for e in [
530 [0.0, 0.0, 0.0],
531 [25.0, -60.0, 40.0],
532 [-80.0, 140.0, -25.0],
533 [10.0, 200.0, -170.0],
534 ] {
535 let r = rotation_mat3(e);
536 let q = quat_from_mat3(r);
537 let e2 = euler_yxz_from_quat(q);
538 let r2 = rotation_mat3(e2);
539 for c in 0..3 {
540 for row in 0..3 {
541 assert!(
542 approx(r[c][row], r2[c][row]),
543 "e={:?} [{}][{}]: {} vs {}",
544 e,
545 c,
546 row,
547 r[c][row],
548 r2[c][row]
549 );
550 }
551 }
552 }
553 }
554
555 #[test]
556 fn euler_from_quat_handles_gimbal_lock() {
557 for e in [[90.0, 35.0, 0.0], [-90.0, -110.0, 0.0]] {
560 let r = rotation_mat3(e);
561 let e2 = euler_yxz_from_quat(quat_from_mat3(r));
562 assert!(e2.iter().all(|v| v.is_finite()), "non-finite for {:?}", e);
563 let r2 = rotation_mat3(e2);
564 for c in 0..3 {
565 for row in 0..3 {
566 assert!(approx(r[c][row], r2[c][row]), "e={:?} [{}][{}]", e, c, row);
567 }
568 }
569 }
570 }
571}