1#![allow(missing_docs, clippy::needless_range_loop)]
25
26use core::ptr;
27
28pub const RATE_COUNT: usize = 7;
29const CLIP_HEADER: usize = 4 + 2 * RATE_COUNT * 2;
30
31#[derive(Clone, Copy)]
32#[repr(C)]
33pub struct Affine {
34 pub r: [[i16; 3]; 3],
35 pub _pad: i16,
36 pub t: [i32; 3],
37}
38
39impl Affine {
40 pub const ZERO: Affine = Affine {
41 r: [[0; 3]; 3],
42 _pad: 0,
43 t: [0; 3],
44 };
45}
46
47#[inline(always)]
52unsafe fn u8_at(d: *const u8, o: usize) -> u32 {
53 unsafe { *d.add(o) as u32 }
55}
56#[inline(always)]
57unsafe fn i8_at(d: *const u8, o: usize) -> i32 {
58 unsafe { *d.add(o) as i8 as i32 }
60}
61#[inline(always)]
62unsafe fn u16_at(d: *const u8, o: usize) -> u32 {
63 unsafe { *d.add(o) as u32 | (*d.add(o + 1) as u32) << 8 }
66}
67#[inline(always)]
68unsafe fn i16_at(d: *const u8, o: usize) -> i32 {
69 unsafe { (*d.add(o) as u32 | (*d.add(o + 1) as u32) << 8) as i16 as i32 }
71}
72#[inline(always)]
73unsafe fn u32_unaligned(d: *const u8, o: usize) -> u32 {
74 unsafe { ptr::read_unaligned(d.add(o).cast::<u32>()) }
76}
77
78#[inline(always)]
79unsafe fn read8(d: *const u8, o: usize) -> [i32; 4] {
80 unsafe {
82 [
83 i8_at(d, o) << 5,
84 i8_at(d, o + 1) << 5,
85 i8_at(d, o + 2) << 5,
86 i8_at(d, o + 3) << 5,
87 ]
88 }
89}
90#[inline(always)]
91unsafe fn read12(d: *const u8, o: usize) -> [i32; 4] {
92 unsafe {
94 let packed = u16_at(d, o) | (u16_at(d, o + 2) << 16);
95 let w2 = u16_at(d, o + 4);
96 let s = |v: u32| (((v << 20) as i32) >> 20) << 1;
97 [
98 s(packed),
99 s(packed >> 12),
100 s((packed >> 24) | (w2 << 8)),
101 s(w2 >> 4),
102 ]
103 }
104}
105
106#[inline(never)]
109unsafe fn seg(d: *const u8, off: usize, r: usize, pos_q8: u32) -> (usize, i32, usize) {
110 unsafe {
113 let s = u16_at(d, off + 4 + r * 2) as usize;
114 let factor = u16_at(d, off + 4 + RATE_COUNT * 2 + r * 2);
115 let sp = (pos_q8 * factor) >> 15;
116 let i = (sp >> 8) as usize;
117 if i >= s {
118 (s.saturating_sub(1), 256, s + 1)
119 } else {
120 (i, (sp & 255) as i32, s + 1)
121 }
122 }
123}
124
125#[inline(always)]
126fn lerp4(a: [i32; 4], b: [i32; 4], f: i32) -> [i32; 4] {
127 [
128 a[0] + (((b[0] - a[0]) * f) >> 8),
129 a[1] + (((b[1] - a[1]) * f) >> 8),
130 a[2] + (((b[2] - a[2]) * f) >> 8),
131 a[3] + (((b[3] - a[3]) * f) >> 8),
132 ]
133}
134
135#[inline(always)]
136fn quat_to_mat(q: [i32; 4]) -> [[i16; 3]; 3] {
137 let [mut x, mut y, mut z, mut w] = q;
138 let n = (x * x + y * y + z * z + w * w + 2048) >> 12;
139 let e = (4096 - n).clamp(-4096, 4096);
143 let r = 4096 + (e >> 1) + ((3 * e * e) >> 15);
144 x = (x * r + 2048) >> 12;
145 y = (y * r + 2048) >> 12;
146 z = (z * r + 2048) >> 12;
147 w = (w * r + 2048) >> 12;
148 let m2 = |a: i32, b: i32| (a * b + 1024) >> 11;
149 let (xx, yy, zz) = (m2(x, x), m2(y, y), m2(z, z));
150 let (xy, xz, yz) = (m2(x, y), m2(x, z), m2(y, z));
151 let (wx, wy, wz) = (m2(w, x), m2(w, y), m2(w, z));
152 [
153 [
154 (4096 - (yy + zz)) as i16,
155 (xy - wz) as i16,
156 (xz + wy) as i16,
157 ],
158 [
159 (xy + wz) as i16,
160 (4096 - (xx + zz)) as i16,
161 (yz - wx) as i16,
162 ],
163 [
164 (xz - wy) as i16,
165 (yz + wx) as i16,
166 (4096 - (xx + yy)) as i16,
167 ],
168 ]
169}
170
171#[cfg(target_arch = "mips")]
173#[inline(always)]
174fn mvmva_raw(xy: u32, z: u32) -> [i32; 3] {
175 unsafe {
179 let m1: u32;
180 let m2: u32;
181 let m3: u32;
182 core::arch::asm!(
183 ".word {w0}", ".word {w1}", ".word 0", ".word 0",
187 ".word {w2}", ".word {w3}", ".word {w4}", ".word {w5}", ".word 0",
192 w0 = const psx_hw::gte::mtc2(8, 0),
193 w1 = const psx_hw::gte::mtc2(9, 1),
194 w2 = const psx_hw::gte::mvmva(psx_hw::gte::Matrix::Rotation, psx_hw::gte::Vector::V0, psx_hw::gte::Translation::None),
195 w3 = const psx_hw::gte::mfc2(8, 25),
196 w4 = const psx_hw::gte::mfc2(9, 26),
197 w5 = const psx_hw::gte::mfc2(10, 27),
198 inlateout("$8") xy => m1,
199 inlateout("$9") z => m2,
200 lateout("$10") m3,
201 options(nostack, nomem, preserves_flags),
202 );
203 [m1 as i32, m2 as i32, m3 as i32]
204 }
205}
206
207#[inline(always)]
208fn pack(a: i32, b: i32) -> u32 {
209 (a as u32 & 0xffff) | ((b as u32) << 16)
210}
211
212#[inline(always)]
215fn compose_loaded(p: &Affine, r: &[[i16; 3]; 3], t: [i32; 3], out: &mut Affine) {
216 let rd = |v: i32| (v + 2048) >> 12;
217 let cols = |xy: u32, z: u32| -> [i32; 3] {
218 #[cfg(target_arch = "mips")]
219 {
220 mvmva_raw(xy, z)
221 }
222 #[cfg(not(target_arch = "mips"))]
223 {
224 let v = [
225 (xy & 0xffff) as i16 as i32,
226 (xy >> 16) as i16 as i32,
227 z as i16 as i32,
228 ];
229 let mut o = [0i32; 3];
230 for i in 0..3 {
231 o[i] = p.r[i][0] as i32 * v[0] + p.r[i][1] as i32 * v[1] + p.r[i][2] as i32 * v[2];
232 }
233 o
234 }
235 };
236 let c0 = cols(pack(r[0][0] as i32, r[1][0] as i32), r[2][0] as i32 as u32);
237 let c1 = cols(pack(r[0][1] as i32, r[1][1] as i32), r[2][1] as i32 as u32);
238 let c2 = cols(pack(r[0][2] as i32, r[1][2] as i32), r[2][2] as i32 as u32);
239 let ct = cols(pack(t[0], t[1]), t[2] as u32);
240 for i in 0..3 {
241 out.r[i][0] = rd(c0[i]) as i16;
242 out.r[i][1] = rd(c1[i]) as i16;
243 out.r[i][2] = rd(c2[i]) as i16;
244 out.t[i] = rd(ct[i]) + p.t[i];
245 }
246}
247
248#[inline(always)]
249fn load_rot(_r: &[[i16; 3]; 3]) {
250 #[cfg(target_arch = "mips")]
251 psx_gte::scene::load_rotation(&psx_gte::math::Mat3I16 { m: *_r });
252}
253
254#[derive(Clone, Copy)]
259pub struct Jaw {
260 pub bone: usize,
262 pub post: bool,
263 pub q: [i32; 4],
266}
267
268impl Jaw {
269 pub const NONE: Jaw = Jaw {
270 bone: usize::MAX,
271 post: false,
272 q: [0, 0, 0, 4096],
273 };
274
275 pub fn open(bone: usize, post: bool, full: [i16; 4], amount: u8) -> Jaw {
279 if amount == 0 {
280 return Jaw::NONE;
281 }
282 let a = amount.min(64) as i32;
283 let id = [0, 0, 0, 4096];
284 let mut q = [0i32; 4];
285 for k in 0..4 {
286 q[k] = id[k] + (((full[k] as i32 - id[k]) * a) >> 6);
287 }
288 Jaw { bone, post, q }
289 }
290}
291
292#[inline(always)]
294pub fn quat_mul(a: [i32; 4], b: [i32; 4]) -> [i32; 4] {
295 let [ax, ay, az, aw] = a;
296 let [bx, by, bz, bw] = b;
297 let r = |v: i32| (v + 2048) >> 12;
298 [
299 r(aw * bx + ax * bw + ay * bz - az * by),
300 r(aw * by - ax * bz + ay * bw + az * bx),
301 r(aw * bz + ax * by - ay * bx + az * bw),
302 r(aw * bw - ax * bx - ay * by - az * bz),
303 ]
304}
305
306#[derive(Clone, Copy)]
313pub struct Model {
314 d: *const u8,
315 n_bones: usize,
316 n_clips: usize,
317 bind_off: usize,
318 clips_off: usize,
319}
320
321fn layout(d: &[u8]) -> Option<(usize, usize, usize, usize)> {
324 let n_bones = u16::from_le_bytes([*d.first()?, *d.get(1)?]) as usize;
325 let n_clips = u16::from_le_bytes([*d.get(2)?, *d.get(3)?]) as usize;
326 let bind_off = (4 + n_bones + 1) & !1;
327 let clips_off = (bind_off + n_bones * 6 + 3) & !3;
328 (clips_off + n_clips * 4 <= d.len()).then_some((n_bones, n_clips, bind_off, clips_off))
329}
330
331fn track_end(keys: usize, segments: usize, key_bytes: usize, read: usize) -> usize {
335 keys.saturating_add(segments.max(1).saturating_mul(key_bytes))
336 .saturating_add(read)
337}
338
339fn tracks_fit(d: &[u8], n_bones: usize, n_clips: usize, clips_off: usize) -> bool {
342 let len = d.len();
343 let u16_at = |o: usize| u16::from_le_bytes([d[o], d[o + 1]]) as usize;
344 for clip in 0..n_clips {
345 let o = clips_off + clip * 4;
346 let off = u32::from_le_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]]) as usize;
347 let modes = off.saturating_add(CLIP_HEADER);
348 if modes.saturating_add(n_bones) > len {
349 return false;
350 }
351 let segments = |rate: usize| u16_at(off + 4 + rate * 2);
352 let mut p = (modes + n_bones + 1) & !1;
353 for b in 0..n_bones {
354 let mode = d[modes + b] as usize;
355 let (rc, pc, hi) = (mode & 7, (mode >> 3) & 7, mode & 0x40 != 0);
356 let end = if rc == 7 {
358 p = p.saturating_add(if hi { 6 } else { 4 });
359 p
360 } else if !hi {
361 if p.saturating_add(6) > len {
362 return false;
363 }
364 let kb = d[p + 4] as usize;
365 if kb > 4 {
368 return false;
369 }
370 let ks = p + 6;
371 p = ks.saturating_add((segments(rc) + 1).saturating_mul(kb));
372 track_end(ks, segments(rc), kb, 4)
373 } else {
374 let keys = p;
375 p = p.saturating_add((segments(rc) + 1).saturating_mul(6));
376 track_end(keys, segments(rc), 6, 6)
377 };
378 if end > len {
379 return false;
380 }
381 let end = if pc == 7 {
383 0
384 } else if pc == 6 {
385 p = p.saturating_add(6);
386 p
387 } else {
388 if p.saturating_add(7) > len {
389 return false;
390 }
391 let kb = d[p + 6] as usize;
392 let ks = p + 8;
393 p = ks.saturating_add((segments(pc) + 1).saturating_mul(kb));
394 if kb == 6 {
395 track_end(ks, segments(pc), 6, 6)
396 } else {
397 track_end(ks, segments(pc), kb, 4)
398 }
399 };
400 if end > len {
401 return false;
402 }
403 }
404 }
405 true
406}
407
408pub(crate) fn validated_layout(d: &[u8]) -> Option<(usize, usize, usize, usize)> {
411 let (n_bones, n_clips, bind_off, clips_off) = layout(d)?;
412 if n_clips == 0 {
413 return None;
414 }
415 for b in 0..n_bones {
416 let parent = d[4 + b] as usize;
417 if parent != 0xff && parent >= b {
418 return None;
419 }
420 }
421 if !tracks_fit(d, n_bones, n_clips, clips_off) {
422 return None;
423 }
424 Some((n_bones, n_clips, bind_off, clips_off))
425}
426
427impl Model {
428 pub fn new(d: &'static [u8]) -> Option<Model> {
433 let (n_bones, n_clips, bind_off, clips_off) = validated_layout(d)?;
434 Some(Model {
435 d: d.as_ptr(),
436 n_bones,
437 n_clips,
438 bind_off,
439 clips_off,
440 })
441 }
442
443 #[inline]
448 pub(crate) unsafe fn new_unchecked(d: &'static [u8]) -> Model {
449 let (n_bones, n_clips, bind_off, clips_off) = layout(d).unwrap_or((0, 1, 0, 0));
451 Model {
452 d: d.as_ptr(),
453 n_bones,
454 n_clips,
455 bind_off,
456 clips_off,
457 }
458 }
459
460 #[inline]
462 pub fn bone_count(&self) -> usize {
463 self.n_bones
464 }
465
466 #[inline]
468 pub fn clip_count(&self) -> usize {
469 self.n_clips
470 }
471
472 #[inline(always)]
474 fn clip_off(&self, clip: usize) -> usize {
475 let clip = clip.min(self.n_clips - 1);
476 unsafe { ptr::read_unaligned(self.d.add(self.clips_off + clip * 4).cast::<u32>()) as usize }
479 }
480
481 pub fn clip_intervals(&self, clip: usize) -> u32 {
485 unsafe { u16_at(self.d, self.clip_off(clip)).max(1) }
487 }
488
489 #[inline(always)]
493 pub fn decode(&self, clip: usize, pos_q8: u32, out: &mut [Affine]) {
494 self.decode_with(clip, pos_q8, &Jaw::NONE, out)
495 }
496
497 #[inline(never)]
500 pub fn decode_with(&self, clip: usize, pos_q8: u32, jaw: &Jaw, out: &mut [Affine]) {
501 let off = self.clip_off(clip);
502 unsafe {
508 let d = self.d;
509 let nb = self.n_bones;
510 if out.len() < nb {
511 return;
512 }
513 let modes = off + CLIP_HEADER;
514 let mut p = (modes + nb + 1) & !1;
515 let mut loaded = usize::MAX;
516 for b in 0..nb {
517 let mode = u8_at(d, modes + b);
518 let rc = (mode & 7) as usize;
519 let pc = ((mode >> 3) & 7) as usize;
520 let hi = mode & 0x40 != 0;
521 let q = if rc == 7 {
522 if hi {
523 let q = read12(d, p);
524 p += 6;
525 q
526 } else {
527 let q = read8(d, p);
528 p += 4;
529 q
530 }
531 } else if !hi {
532 let (i, f, nk) = seg(d, off, rc, pos_q8);
533 let kb = u8_at(d, p + 4) as usize;
534 debug_assert!(kb <= 4, "a validated rotation track has 0..=4 key bytes");
537 let w = (kb as u32) << 1;
538 let mask = (1u32 << w) - 1;
539 let (b0, b1, b2, b3) = (
540 i8_at(d, p),
541 i8_at(d, p + 1),
542 i8_at(d, p + 2),
543 i8_at(d, p + 3),
544 );
545 let ks = p + 6;
546 let wa = u32_unaligned(d, ks + i * kb);
547 let wb = u32_unaligned(d, ks + (i + 1) * kb);
548 let ex = |word: u32| -> [i32; 4] {
549 [
550 (b0 + (word & mask) as i32) << 5,
551 (b1 + ((word >> w) & mask) as i32) << 5,
552 (b2 + ((word >> (2 * w)) & mask) as i32) << 5,
553 (b3 + ((word >> (3 * w)) & mask) as i32) << 5,
554 ]
555 };
556 p = ks + nk * kb;
557 lerp4(ex(wa), ex(wb), f)
558 } else {
559 let (i, f, nk) = seg(d, off, rc, pos_q8);
560 let q = lerp4(read12(d, p + i * 6), read12(d, p + i * 6 + 6), f);
561 p += nk * 6;
562 q
563 };
564 let t = if pc == 7 {
565 let o = self.bind_off + b * 6;
566 [i16_at(d, o), i16_at(d, o + 2), i16_at(d, o + 4)]
567 } else if pc == 6 {
568 let v = [i16_at(d, p), i16_at(d, p + 2), i16_at(d, p + 4)];
569 p += 6;
570 v
571 } else {
572 let base = [i16_at(d, p), i16_at(d, p + 2), i16_at(d, p + 4)];
573 let kb = u8_at(d, p + 6) as usize;
574 let ks = p + 8;
575 let (i, f, nk) = seg(d, off, pc, pos_q8);
576 let v = if kb == 6 {
577 let a = [
578 i16_at(d, ks + i * 6) & 0xffff,
579 i16_at(d, ks + i * 6 + 2) & 0xffff,
580 i16_at(d, ks + i * 6 + 4) & 0xffff,
581 ];
582 let c = [
583 i16_at(d, ks + i * 6 + 6) & 0xffff,
584 i16_at(d, ks + i * 6 + 8) & 0xffff,
585 i16_at(d, ks + i * 6 + 10) & 0xffff,
586 ];
587 [
588 base[0] + a[0] + (((c[0] - a[0]) * f) >> 8),
589 base[1] + a[1] + (((c[1] - a[1]) * f) >> 8),
590 base[2] + a[2] + (((c[2] - a[2]) * f) >> 8),
591 ]
592 } else {
593 let w: u32 = if kb == 2 {
594 5
595 } else if kb == 3 {
596 8
597 } else {
598 10
599 };
600 let mask = (1u32 << w) - 1;
601 let wa = u32_unaligned(d, ks + i * kb);
602 let wb = u32_unaligned(d, ks + (i + 1) * kb);
603 let ex = |word: u32| {
604 [
605 (word & mask) as i32,
606 ((word >> w) & mask) as i32,
607 ((word >> (2 * w)) & mask) as i32,
608 ]
609 };
610 let (a, c) = (ex(wa), ex(wb));
611 [
612 base[0] + a[0] + (((c[0] - a[0]) * f) >> 8),
613 base[1] + a[1] + (((c[1] - a[1]) * f) >> 8),
614 base[2] + a[2] + (((c[2] - a[2]) * f) >> 8),
615 ]
616 };
617 p = ks + nk * kb;
618 v
619 };
620 let q = if b == jaw.bone {
621 if jaw.post {
622 quat_mul(q, jaw.q)
623 } else {
624 quat_mul(jaw.q, q)
625 }
626 } else {
627 q
628 };
629 let r = quat_to_mat(q);
630 let parent = u8_at(d, 4 + b) as usize;
631 if parent == 0xff {
634 *out.get_unchecked_mut(b) = Affine { r, _pad: 0, t };
635 } else {
636 let pa = *out.get_unchecked(parent);
637 if loaded != parent {
638 load_rot(&pa.r);
639 loaded = parent;
640 }
641 compose_loaded(&pa, &r, t, out.get_unchecked_mut(b));
642 }
643 }
644 }
645 }
646}
647
648#[cfg(test)]
649mod tests {
650 use super::*;
651 use core::f64::consts::FRAC_1_SQRT_2;
652
653 fn reference(q: [f64; 4]) -> [[f64; 3]; 3] {
656 let n = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt();
657 let [x, y, z, w] = [q[0] / n, q[1] / n, q[2] / n, q[3] / n];
658 [
659 [
660 1.0 - 2.0 * (y * y + z * z),
661 2.0 * (x * y - w * z),
662 2.0 * (x * z + w * y),
663 ],
664 [
665 2.0 * (x * y + w * z),
666 1.0 - 2.0 * (x * x + z * z),
667 2.0 * (y * z - w * x),
668 ],
669 [
670 2.0 * (x * z - w * y),
671 2.0 * (y * z + w * x),
672 1.0 - 2.0 * (x * x + y * y),
673 ],
674 ]
675 }
676
677 #[test]
678 fn quat_to_mat_matches_the_float_formula_for_near_unit_input() {
679 let axes: [[f64; 4]; 5] = [
682 [0.0, 0.0, 0.0, 1.0],
683 [0.5, 0.5, 0.5, 0.5],
684 [0.0, 0.0, FRAC_1_SQRT_2, FRAC_1_SQRT_2],
685 [0.3, -0.5, 0.2, 0.78],
686 [-0.6, 0.1, 0.0, 0.79],
687 ];
688 for axis in axes {
689 for scale in [0.95, 1.0, 1.05] {
690 let q = axis.map(|c| (c * 4096.0 * scale) as i32);
691 let got = quat_to_mat(q);
692 let want = reference(axis);
693 for r in 0..3 {
694 for c in 0..3 {
695 let expected = (want[r][c] * 4096.0).round() as i32;
696 let diff = (got[r][c] as i32 - expected).abs();
697 assert!(diff <= 24, "{axis:?} x{scale} [{r}][{c}] off by {diff}");
698 }
699 }
700 }
701 }
702 }
703
704 #[test]
705 fn quat_to_mat_survives_the_largest_components_a_key_can_hold() {
706 let extremes = [-4096, 0, 4096, 12224];
711 for x in extremes {
712 for y in extremes {
713 for z in extremes {
714 for w in extremes {
715 let m = quat_to_mat([x, y, z, w]);
716 let _ = m;
718 }
719 }
720 }
721 }
722 }
723}