1#![allow(missing_docs)]
30
31use core::ptr;
32
33use psx_gte::math::{Mat3I16, Vec3I16};
34
35#[inline(always)]
44fn fits(d: &[u8], o: usize, n: usize) -> bool {
45 o <= d.len() && n <= d.len() - o
46}
47
48fn move_within(d: &mut [u8], src: usize, dst: usize, len: usize) -> Option<()> {
51 if !fits(d, src, len) || !fits(d, dst, len) {
52 return None;
53 }
54 d.copy_within(src..src + len, dst);
55 Some(())
56}
57
58#[inline(always)]
61unsafe fn rd_u32(d: &[u8], o: usize) -> u32 {
62 unsafe {
64 let p = d.as_ptr().add(o);
65 u32::from_le_bytes([p.read(), p.add(1).read(), p.add(2).read(), p.add(3).read()])
66 }
67}
68#[inline(always)]
71unsafe fn rd_u16(d: &[u8], o: usize) -> u16 {
72 unsafe {
74 let p = d.as_ptr().add(o);
75 u16::from_le_bytes([p.read(), p.add(1).read()])
76 }
77}
78#[inline(always)]
81unsafe fn rd_i16(d: &[u8], o: usize) -> i16 {
82 unsafe { rd_u16(d, o) as i16 }
84}
85
86#[inline(always)]
87fn decode_q11(raw: u16) -> i16 {
88 #[cfg(target_arch = "mips")]
93 let decoded = unsafe {
97 let decoded: u32;
98 core::arch::asm!(
99 ".set push",
100 ".set noat",
101 "xori {scratch}, {decoded}, 0x07ff",
102 "sltiu {scratch}, {scratch}, 1",
103 "sll {decoded}, {decoded}, 20",
104 "sra {decoded}, {decoded}, 19",
105 "sll {scratch}, {scratch}, 1",
106 "addu {decoded}, {decoded}, {scratch}",
107 ".set pop",
108 decoded = inlateout(reg) raw as u32 => decoded,
109 scratch = lateout(reg) _,
110 options(nomem, nostack, preserves_flags),
111 );
112 decoded as i16
113 };
114 #[cfg(not(target_arch = "mips"))]
115 let decoded = {
116 let signed = ((raw << 4) as i16) >> 4;
117 signed
118 .wrapping_shl(1)
119 .wrapping_add(((raw == 0x07ff) as i16) << 1)
120 };
121 decoded
122}
123
124#[inline(always)]
127unsafe fn rd_q11_pair(d: &[u8], o: usize) -> (i16, i16) {
128 unsafe {
131 let packed = u32::from_le(d.as_ptr().add(o).cast::<u32>().read_unaligned());
135 (
136 decode_q11((packed & 0x0fff) as u16),
137 decode_q11(((packed >> 12) & 0x0fff) as u16),
138 )
139 }
140}
141#[inline(always)]
142fn unpack_normal_555(packed: u16) -> [i8; 3] {
143 let component = |shift: u32| -> i8 {
144 let bits = ((packed >> shift) & 0x1f) as i16;
145 let signed = if bits & 0x10 != 0 { bits - 32 } else { bits };
146 (signed * 8) as i8
147 };
148 [component(0), component(5), component(10)]
149}
150
151#[derive(Clone, Copy)]
167pub struct Model {
168 data: &'static [u8],
169 #[deprecated(note = "use `vertex_count()`")]
170 pub n_verts: usize,
171 #[deprecated(note = "use `triangle_count()`")]
172 pub n_tris: usize,
173 n_frames: usize,
174 n_clips: usize,
175 n_bones: usize,
176 n_ranges: usize,
177 n_hitboxes: usize,
178 clips_off: usize,
179 frame_times_off: usize,
180 ranges_off: usize,
181 vertices_off: usize,
182 vertices_z_off: usize,
183 poses_off: usize,
184 tri_off: usize,
185 tri_sz: usize,
186 normal_encoding: u8,
187 local_to_world_q12: u16,
188 mouth_xforms_off: usize,
189 hitboxes_off: usize,
190 has_body_masks: bool,
191 has_mouth: bool,
192 has_frame_times: bool,
193 hma_off: usize,
194 hma_len: usize,
195 #[allow(dead_code)] aligned_vertices: bool,
197 vertex_soa: bool,
198}
199
200#[derive(Clone, Copy)]
205pub struct GteVertexWords {
206 pub xy: u32,
207 pub z: u32,
208}
209
210impl GteVertexWords {
211 #[inline(always)]
213 pub const fn position(self) -> Vec3I16 {
214 Vec3I16::new(self.xy as i16, (self.xy >> 16) as i16, self.z as i16)
215 }
216
217 #[inline(always)]
218 const fn from_soa(xy: u32, z: i16) -> Self {
219 GteVertexWords {
220 xy,
221 z: z as i32 as u32,
222 }
223 }
224}
225
226#[derive(Clone, Copy)]
235pub struct VertexWords<'a> {
236 xy: &'a [u32],
237 z: &'a [i16],
238}
239
240impl<'a> VertexWords<'a> {
241 pub const EMPTY: VertexWords<'static> = VertexWords { xy: &[], z: &[] };
243
244 #[inline(always)]
246 pub const fn len(&self) -> usize {
247 self.xy.len()
248 }
249
250 #[inline(always)]
252 pub const fn is_empty(&self) -> bool {
253 self.xy.is_empty()
254 }
255
256 #[inline]
258 pub fn get(&self, index: usize) -> Option<GteVertexWords> {
259 Some(GteVertexWords::from_soa(
260 *self.xy.get(index)?,
261 *self.z.get(index)?,
262 ))
263 }
264
265 #[inline]
267 pub fn slice(&self, range: core::ops::Range<usize>) -> Option<VertexWords<'a>> {
268 Some(VertexWords {
269 xy: self.xy.get(range.clone())?,
270 z: self.z.get(range)?,
271 })
272 }
273
274 #[inline]
276 pub fn iter(&self) -> impl Iterator<Item = GteVertexWords> + 'a {
277 self.xy
278 .iter()
279 .zip(self.z)
280 .map(|(&xy, &z)| GteVertexWords::from_soa(xy, z))
281 }
282
283 #[inline]
286 pub fn triples(
287 &self,
288 ) -> (
289 impl Iterator<Item = [GteVertexWords; 3]> + 'a,
290 VertexWords<'a>,
291 ) {
292 let (xy, xy_rest) = self.xy.as_chunks::<3>();
293 let (z, z_rest) = self.z.as_chunks::<3>();
294 let groups = xy.iter().zip(z).map(|(xy, z)| {
295 [
296 GteVertexWords::from_soa(xy[0], z[0]),
297 GteVertexWords::from_soa(xy[1], z[1]),
298 GteVertexWords::from_soa(xy[2], z[2]),
299 ]
300 });
301 (
302 groups,
303 VertexWords {
304 xy: xy_rest,
305 z: z_rest,
306 },
307 )
308 }
309}
310
311#[derive(Clone, Copy)]
314pub struct ModelFrame<'a> {
315 data: &'a [u8],
316 poses_off: usize,
317 n_bones: usize,
318 frame_idx: usize,
319 mouth_xforms_off: usize,
320 has_mouth: bool,
321}
322
323#[derive(Clone, Copy)]
325pub struct InterpolatedModelFrame<'a> {
326 a: ModelFrame<'a>,
327 b: ModelFrame<'a>,
328 frac16: i32,
329}
330
331#[derive(Clone, Copy)]
337pub struct BoneRange {
338 pub first: usize,
339 pub count: usize,
340 pub bone: usize,
341 pub body_mask: u8,
342 pub mouth: bool,
343}
344
345#[derive(Clone, Copy)]
347pub struct BoneTransform {
348 pub rotation: Mat3I16,
349 pub translation: Vec3I16,
350}
351
352#[derive(Clone, Copy)]
354pub struct StudioHitbox {
355 pub bone: usize,
356 pub bbmin: Vec3I16,
357 pub bbmax: Vec3I16,
358}
359
360const TRI_SZ: usize = 16;
361const TRI_SZ_FULL_NORMALS: usize = 20;
362const HMD7_HEADER_BYTES: usize = 36;
363const HMD7_RANGE_BYTES: usize = 8;
364const HMD8_AFFINE_BYTES: usize = 20;
365const HMD7_HITBOX_BYTES: usize = 14;
366const CLIP_FRAME_COUNT_MASK: u16 = 0x00ff;
367const CLIP_FIRST_FRAME_MASK: u16 = 0x7fff;
368const CLIP_DURATION_EXT_BIT: u16 = 0x8000;
369const DEFAULT_CLIP_HOLD_TICKS: u16 = 40;
370const LOCAL_TO_WORLD_IDENTITY_Q12: u16 = 4096;
371
372#[inline(always)]
373const fn valid_local_to_world_q12(raw: u16) -> bool {
374 matches!(raw, 0 | 256 | 512 | 1024 | 2048 | 4096)
375}
376
377const HMD_FLAG_BODY_MASKS: u16 = 1 << 0;
378const HMD_FLAG_MOUTH: u16 = 1 << 1;
379const HMD_FLAG_PACKED_NORMALS: u16 = 1 << 2;
380const HMD_FLAG_I8_NORMALS: u16 = 1 << 3;
381const HMD_FLAG_HITBOXES: u16 = 1 << 4;
382const HMD_FLAG_FRAME_TIMES: u16 = 1 << 5;
383const HMD_FLAG_ALIGNED_MODEL_DATA: u16 = 1 << 6;
384const HMD_FLAG_VERTEX_SOA: u16 = 1 << 7;
385const HMD_FLAG_HMA1: u16 = 1 << 8;
392const HMA1_SECTION_HEADER: usize = 4 + 2 + 2 + 8;
393const HMD7_RANGE_MOUTH: u8 = 1 << 0;
394const NORMAL_NONE: u8 = 0;
395const NORMAL_I8X3: u8 = 1;
396const NORMAL_PACKED_555: u8 = 2;
397const MOUTH_XFORM_BYTES: usize = 24;
398const IDENTITY_TRANSFORM: BoneTransform = BoneTransform {
399 rotation: Mat3I16::IDENTITY,
400 translation: Vec3I16::ZERO,
401};
402pub const DEFAULT_MAX_VERTICES: usize = RENDER_FACE_INDEX_MASK as usize + 1;
409
410#[derive(Clone, Copy)]
412pub struct Triangle {
413 pub idx: [u16; 3],
414 pub tex: usize,
415 pub uv: [(u8, u8); 3],
416 pub normal: [i8; 3],
417 pub body_mask: u8,
418}
419
420#[derive(Clone, Copy)]
425#[repr(C)]
426pub struct RenderFacePayload {
427 pub uv_words: [u16; 3],
428}
429
430impl RenderFacePayload {
431 pub const ZERO: Self = Self { uv_words: [0; 3] };
432}
433
434pub const RENDER_FACE_INDEX_MASK: u32 = 0x3ff;
441
442#[inline(always)]
443fn uv_word(uv: (u8, u8)) -> u16 {
444 (uv.0 as u16) | ((uv.1 as u16) << 8)
445}
446
447#[inline(always)]
448unsafe fn write_render_face(
449 indices: *mut u32,
450 payloads: *mut RenderFacePayload,
451 t: usize,
452 tri: &Triangle,
453) {
454 unsafe {
456 let packed = (tri.idx[0] as u32 & RENDER_FACE_INDEX_MASK)
457 | ((tri.idx[1] as u32 & RENDER_FACE_INDEX_MASK) << 10)
458 | ((tri.idx[2] as u32 & RENDER_FACE_INDEX_MASK) << 20);
459 ptr::write(indices.add(t), packed);
460 ptr::write(
461 payloads.add(t),
462 RenderFacePayload {
463 uv_words: [uv_word(tri.uv[0]), uv_word(tri.uv[1]), uv_word(tri.uv[2])],
464 },
465 );
466 }
467}
468
469impl Model {
470 #[allow(deprecated)] pub const EMPTY: Model = Model {
473 data: &[],
474 n_verts: 0,
475 n_tris: 0,
476 n_frames: 0,
477 n_clips: 0,
478 clips_off: 0,
479 frame_times_off: 0,
480 ranges_off: 0,
481 vertices_off: 0,
482 vertices_z_off: 0,
483 poses_off: 0,
484 n_bones: 0,
485 n_ranges: 0,
486 tri_off: 0,
487 tri_sz: TRI_SZ,
488 normal_encoding: NORMAL_NONE,
489 local_to_world_q12: 4096,
490 mouth_xforms_off: 0,
491 hitboxes_off: 0,
492 n_hitboxes: 0,
493 has_body_masks: false,
494 has_mouth: false,
495 has_frame_times: false,
496 hma_off: 0,
497 hma_len: 0,
498 aligned_vertices: false,
499 vertex_soa: false,
500 };
501
502 pub fn from_bytes(data: &'static [u8]) -> Model {
504 Self::from_bytes_with_vertex_cap(data, DEFAULT_MAX_VERTICES)
505 }
506
507 #[allow(deprecated)] #[inline]
510 pub const fn vertex_count(&self) -> usize {
511 self.n_verts
512 }
513
514 #[allow(deprecated)] #[inline]
517 pub const fn triangle_count(&self) -> usize {
518 self.n_tris
519 }
520
521 #[allow(deprecated)] #[inline]
524 pub const fn frame_count(&self) -> usize {
525 self.n_frames
526 }
527
528 #[allow(deprecated)] #[inline]
531 pub const fn clip_count(&self) -> usize {
532 self.n_clips
533 }
534
535 #[allow(deprecated)] #[inline]
538 pub const fn bone_count(&self) -> usize {
539 self.n_bones
540 }
541
542 #[allow(deprecated)] #[inline]
545 pub const fn bone_range_count(&self) -> usize {
546 self.n_ranges
547 }
548
549 #[allow(deprecated)] #[inline]
552 pub const fn hitbox_count(&self) -> usize {
553 self.n_hitboxes
554 }
555
556 pub fn from_bytes_with_vertex_cap(data: &'static [u8], max_verts: usize) -> Model {
559 let (model, keeps_blob) = Self::layout(data, max_verts);
560 if keeps_blob {
561 Model { data, ..model }
562 } else {
563 model
564 }
565 }
566
567 #[allow(deprecated)] fn layout(data: &[u8], max_verts: usize) -> (Model, bool) {
575 if data.len() < HMD7_HEADER_BYTES || data.get(0..4) != Some(b"HMD8") {
576 return (Self::EMPTY, false);
577 }
578 let (
581 n_verts,
582 n_tris,
583 packed_texture_hitbox_counts,
584 n_frames,
585 n_clips,
586 model_data_len,
587 raw_local_to_world_q12,
588 hmd_flags,
589 n_bones,
590 n_ranges,
591 ) = unsafe {
592 (
593 rd_u32(data, 4) as usize,
594 rd_u32(data, 8) as usize,
595 rd_u32(data, 12),
596 (rd_u32(data, 16) as usize).max(1),
597 (rd_u32(data, 20) as usize).max(1),
598 rd_u32(data, 24) as usize,
599 rd_u16(data, 28),
600 rd_u16(data, 30),
601 rd_u16(data, 32) as usize,
602 rd_u16(data, 34) as usize,
603 )
604 };
605 if !valid_local_to_world_q12(raw_local_to_world_q12) {
606 return (Self::EMPTY, false);
607 }
608 let local_to_world_q12 = if raw_local_to_world_q12 == 0 {
609 LOCAL_TO_WORLD_IDENTITY_Q12
610 } else {
611 raw_local_to_world_q12
612 };
613 let clips_off = HMD7_HEADER_BYTES;
614 let requested_frame_times = hmd_flags & HMD_FLAG_FRAME_TIMES != 0;
615 let frame_times_off = clips_off.saturating_add(n_clips.saturating_mul(4));
616 let unaligned_ranges_off =
617 frame_times_off.saturating_add(if requested_frame_times { n_frames } else { 0 });
618 let requested_vertex_soa = hmd_flags & HMD_FLAG_VERTEX_SOA != 0;
619 let ranges_off = if requested_vertex_soa {
620 unaligned_ranges_off.saturating_add(3) & !3
621 } else if hmd_flags & HMD_FLAG_ALIGNED_MODEL_DATA != 0 {
622 unaligned_ranges_off.saturating_add(1) & !1
623 } else {
624 unaligned_ranges_off
625 };
626 let vertices_off = ranges_off.saturating_add(n_ranges.saturating_mul(HMD7_RANGE_BYTES));
627 let vertices_z_off = if requested_vertex_soa {
628 vertices_off.saturating_add(n_verts.saturating_mul(4))
629 } else {
630 vertices_off
631 };
632 let poses_off = vertices_off.saturating_add(n_verts.saturating_mul(6));
633 let poses_len = n_frames
634 .saturating_mul(n_bones)
635 .saturating_mul(HMD8_AFFINE_BYTES);
636 let requested_mouth = hmd_flags & HMD_FLAG_MOUTH != 0;
637 let mouth_xforms_off = poses_off.saturating_add(poses_len);
638 let mouth_xforms_len = if requested_mouth {
639 n_frames.saturating_mul(MOUTH_XFORM_BYTES)
640 } else {
641 0
642 };
643 let requested_hitboxes = hmd_flags & HMD_FLAG_HITBOXES != 0;
644 let n_hitboxes = if requested_hitboxes {
645 (packed_texture_hitbox_counts >> 16) as usize
646 } else {
647 0
648 };
649 let hitboxes_off = mouth_xforms_off.saturating_add(mouth_xforms_len);
650 let hitboxes_len = n_hitboxes.saturating_mul(HMD7_HITBOX_BYTES);
651 let hma_off = hitboxes_off.saturating_add(hitboxes_len);
652 let hma_len = if hmd_flags & HMD_FLAG_HMA1 != 0 && fits(data, hma_off, 4) {
653 4usize.saturating_add(unsafe { rd_u32(data, hma_off) } as usize)
655 } else {
656 0
657 };
658 let tri_off = ranges_off.saturating_add(model_data_len);
659 let full_normals = hmd_flags & HMD_FLAG_I8_NORMALS != 0;
660 let tri_sz = if full_normals {
661 TRI_SZ_FULL_NORMALS
662 } else {
663 TRI_SZ
664 };
665 let requested_body_masks = hmd_flags & HMD_FLAG_BODY_MASKS != 0;
666 let requested_packed_normals = hmd_flags & HMD_FLAG_PACKED_NORMALS != 0;
667 let metadata_ok = (!requested_packed_normals || (!requested_body_masks && !full_normals))
668 && hma_off.saturating_add(hma_len) == tri_off;
669
670 let tri_end = tri_off.saturating_add(n_tris.saturating_mul(tri_sz));
677 let mut valid = metadata_ok
678 && n_bones > 0
679 && n_ranges > 0
680 && n_verts <= max_verts
681 && (!requested_vertex_soa || (data.as_ptr() as usize + vertices_off) & 3 == 0)
682 && poses_off <= tri_off
683 && tri_end <= data.len();
684 let mut range = 0usize;
685 while valid && range < n_ranges {
686 let o = ranges_off + range * HMD7_RANGE_BYTES;
687 valid = fits(data, o, HMD7_RANGE_BYTES) && {
690 let (first, count, bone) = unsafe {
692 (
693 rd_u16(data, o) as usize,
694 rd_u16(data, o + 2) as usize,
695 rd_u16(data, o + 4) as usize,
696 )
697 };
698 count > 0 && first.saturating_add(count) <= n_verts && bone < n_bones
699 };
700 range += 1;
701 }
702 let mut hitbox = 0usize;
703 while valid && hitbox < n_hitboxes {
704 let o = hitboxes_off + hitbox * HMD7_HITBOX_BYTES;
705 valid = fits(data, o, HMD7_HITBOX_BYTES)
706 && (unsafe { rd_u16(data, o) } as usize) < n_bones;
708 hitbox += 1;
709 }
710 if valid && hma_len != 0 {
711 valid = hma1_section_valid(data, hma_off, hma_len, n_bones, n_clips);
712 }
713 if !valid {
714 return (
717 Model {
718 data: &[],
719 n_verts: 0,
720 n_tris: 0,
721 n_frames: 1,
722 n_clips: 1,
723 clips_off: 0,
724 frame_times_off: 0,
725 ranges_off: 0,
726 vertices_off: 0,
727 vertices_z_off: 0,
728 poses_off: 0,
729 n_bones: 0,
730 n_ranges: 0,
731 tri_off: 0,
732 tri_sz,
733 normal_encoding: NORMAL_NONE,
734 local_to_world_q12: LOCAL_TO_WORLD_IDENTITY_Q12,
735 mouth_xforms_off: 0,
736 hitboxes_off: 0,
737 n_hitboxes: 0,
738 has_body_masks: false,
739 has_mouth: false,
740 has_frame_times: false,
741 hma_off: 0,
742 hma_len: 0,
743 aligned_vertices: false,
744 vertex_soa: false,
745 },
746 true,
747 );
748 }
749
750 (
751 Model {
752 data: &[],
753 n_verts,
754 n_tris,
755 n_frames,
756 n_clips,
757 clips_off,
758 frame_times_off,
759 ranges_off,
760 vertices_off,
761 vertices_z_off,
762 poses_off,
763 n_bones,
764 n_ranges,
765 tri_off,
766 tri_sz,
767 normal_encoding: if full_normals {
768 NORMAL_I8X3
769 } else if requested_packed_normals {
770 NORMAL_PACKED_555
771 } else {
772 NORMAL_NONE
773 },
774 local_to_world_q12,
775 mouth_xforms_off,
776 hitboxes_off,
777 n_hitboxes,
778 has_body_masks: requested_body_masks,
779 has_mouth: requested_mouth,
780 has_frame_times: requested_frame_times,
781 hma_off,
782 hma_len,
783 aligned_vertices: (data.as_ptr() as usize + vertices_off) & 1 == 0,
784 vertex_soa: requested_vertex_soa,
785 },
786 true,
787 )
788 }
789
790 #[doc(alias = "hma1")]
793 #[inline]
794 pub fn tracks(&self) -> Option<Tracks> {
795 if self.hma_len == 0 {
796 return None;
797 }
798 let data: &'static [u8] = self.data;
799 let off = self.hma_off;
800 let (map_off, blob_off, end) = hma1_section_layout(data, off, self.hma_len)?;
801 let n_used = u16::from_le_bytes([data[off + 4], data[off + 5]]) as usize;
803 let blob = data.get(blob_off..end)?;
804 let jaw_bone = data[off + 6];
805 let le_i16 = |o: usize| i16::from_le_bytes([data[o], data[o + 1]]);
806 Some(Tracks {
807 model: unsafe { crate::hma1::Model::new_unchecked(blob) },
810 map: data.get(map_off..map_off + n_used)?,
811 jaw: if jaw_bone == 0xff {
812 None
813 } else {
814 Some(Jaw {
815 bone: jaw_bone as usize,
816 post: data[off + 7] & 1 != 0,
817 open: [
818 le_i16(off + 8),
819 le_i16(off + 10),
820 le_i16(off + 12),
821 le_i16(off + 14),
822 ],
823 })
824 },
825 })
826 }
827
828 #[inline]
832 pub fn has_tracks(&self) -> bool {
833 self.hma_len != 0
834 }
835
836 #[inline]
842 pub fn pose<'a>(
843 &'a self,
844 frame: usize,
845 frame2: usize,
846 frac16: u32,
847 mouth: u8,
848 scratch: &'a mut [crate::hma1::Affine],
849 ) -> Pose<'a> {
850 let Some(tracks) = self.tracks() else {
851 return Pose::Palette(self.frame(frame).interpolate(self.frame(frame2), frac16));
852 };
853 if tracks.model.bone_count() > scratch.len() {
854 return Pose::Palette(self.frame(0).interpolate(self.frame(0), 0));
855 }
856 let jaw = match tracks.jaw {
857 Some(j) => crate::hma1::Jaw::open(j.bone, j.post, j.open, mouth),
858 None => crate::hma1::Jaw::NONE,
859 };
860 let clip = frame.min(self.clip_count().saturating_sub(1));
861 tracks.model.decode_with(clip, frac16, &jaw, scratch);
862 Pose::Tracks {
863 map: tracks.map,
864 bones: scratch,
865 }
866 }
867
868 #[inline]
870 pub fn clip_end_pose(&self, clip: usize) -> (usize, usize, u32) {
871 let clip = clip.min(self.clip_count().saturating_sub(1));
872 if let Some(tracks) = self.tracks() {
873 return (clip, clip, tracks.model.clip_intervals(clip) * 256);
874 }
875 let frame = self.clip_frame(clip, self.clip_len(clip) - 1);
876 (frame, frame, 0)
877 }
878
879 #[inline]
880 pub fn local_to_world_q12(&self) -> u16 {
881 self.local_to_world_q12
882 }
883
884 #[inline]
887 fn clip_rec(&self, clip: usize) -> Option<(u16, u16)> {
888 if self.clips_off == 0 {
889 return None;
890 }
891 let o = self.clips_off + clip.min(self.clip_count().saturating_sub(1)) * 4;
892 if !fits(self.data, o, 4) {
893 return None;
894 }
895 Some(unsafe { (rd_u16(self.data, o), rd_u16(self.data, o + 2)) })
897 }
898
899 #[inline]
900 pub fn clip_len(&self, clip: usize) -> usize {
901 let Some((_, count)) = self.clip_rec(clip) else {
902 return self.frame_count().max(1);
903 };
904 ((count & CLIP_FRAME_COUNT_MASK) as usize).max(1)
905 }
906
907 #[inline]
912 pub fn clip_hold_ticks(&self, clip: usize) -> u16 {
913 let Some((packed_first, count)) = self.clip_rec(clip) else {
914 return DEFAULT_CLIP_HOLD_TICKS;
915 };
916 let quanta = (count >> 8)
917 | if packed_first & CLIP_DURATION_EXT_BIT != 0 {
918 0x0100
919 } else {
920 0
921 };
922 if quanta == 0 {
923 DEFAULT_CLIP_HOLD_TICKS
924 } else {
925 quanta.saturating_mul(2)
926 }
927 }
928
929 #[inline]
930 pub fn clip_frame(&self, clip: usize, local_frame: usize) -> usize {
931 let Some((first, count)) = self.clip_rec(clip) else {
932 return local_frame % self.frame_count().max(1);
933 };
934 let first = (first & CLIP_FIRST_FRAME_MASK) as usize;
935 let count = ((count & CLIP_FRAME_COUNT_MASK) as usize).max(1);
936 (first + (local_frame % count)).min(self.frame_count().saturating_sub(1))
937 }
938
939 #[inline]
944 fn timed_clip_phase(
945 &self,
946 clip: usize,
947 duration: usize,
948 elapsed: usize,
949 looping: bool,
950 ) -> (usize, usize, u32) {
951 let clip = clip.min(self.clip_count().saturating_sub(1));
952 if let Some(tracks) = self.tracks() {
953 let span = tracks.model.clip_intervals(clip) as usize * 256;
956 let duration = duration.max(1);
957 let elapsed = if looping {
958 elapsed % duration
959 } else {
960 elapsed.min(duration)
961 };
962 let pos = elapsed.saturating_mul(span) / duration;
963 return (clip, clip, pos as u32);
964 }
965 let len = self.clip_len(clip);
966 if len <= 1 {
967 let frame = self.clip_frame(clip, 0);
968 return (frame, frame, 0);
969 }
970 if !self.has_frame_times {
971 let duration = duration.max(1);
972 let span16 = if looping { len * 16 } else { (len - 1) * 16 };
973 let phase16 = if looping {
974 (elapsed % duration).saturating_mul(span16) / duration
975 } else {
976 elapsed.min(duration).saturating_mul(span16) / duration
977 };
978 let local = (phase16 / 16).min(len - 1);
979 let next = if looping {
980 (local + 1) % len
981 } else {
982 (local + 1).min(len - 1)
983 };
984 return (
985 self.clip_frame(clip, local),
986 self.clip_frame(clip, next),
987 (phase16 & 15) as u32,
988 );
989 }
990
991 let duration = duration.max(1);
992 let target = if looping {
993 ((elapsed % duration).saturating_mul(256) / duration).min(255)
994 } else {
995 (elapsed.min(duration).saturating_mul(255) / duration).min(255)
996 };
997 let first = self.clip_frame(clip, 0);
998 let last_frame = self.frame_count().saturating_sub(1);
1002 let time = |local: usize| {
1003 self.data[self.frame_times_off + (first + local).min(last_frame)] as usize
1004 };
1005 let mut left = 0usize;
1006 while left + 1 < len && time(left + 1) <= target {
1007 left += 1;
1008 }
1009 let (right, left_time, right_time, target_time) = if looping && left + 1 == len {
1010 (0, time(left), time(0) + 256, target)
1011 } else {
1012 let right = (left + 1).min(len - 1);
1013 (right, time(left), time(right), target)
1014 };
1015 let denominator = right_time.saturating_sub(left_time);
1016 let frac16 = if denominator == 0 {
1017 0
1018 } else {
1019 target_time
1020 .saturating_sub(left_time)
1021 .saturating_mul(16)
1022 .checked_div(denominator)
1023 .unwrap_or(0)
1024 .min(15) as u32
1025 };
1026 (
1027 self.clip_frame(clip, left),
1028 self.clip_frame(clip, right),
1029 frac16,
1030 )
1031 }
1032
1033 #[inline]
1034 pub fn looped_clip_phase(
1035 &self,
1036 clip: usize,
1037 duration: usize,
1038 elapsed: usize,
1039 ) -> (usize, usize, u32) {
1040 self.timed_clip_phase(clip, duration, elapsed, true)
1041 }
1042
1043 #[inline]
1044 pub fn one_shot_clip_phase(
1045 &self,
1046 clip: usize,
1047 duration: usize,
1048 elapsed: usize,
1049 ) -> (usize, usize, u32) {
1050 self.timed_clip_phase(clip, duration, elapsed, false)
1051 }
1052
1053 #[inline]
1054 pub fn frame(&self, frame: usize) -> ModelFrame<'_> {
1055 let f = if frame < self.frame_count() { frame } else { 0 };
1056 ModelFrame {
1057 data: self.data,
1058 poses_off: self.poses_off,
1059 n_bones: self.bone_count(),
1060 frame_idx: f,
1061 mouth_xforms_off: self.mouth_xforms_off,
1062 has_mouth: self.has_mouth,
1063 }
1064 }
1065
1066 #[inline]
1069 pub fn bone_range(&self, index: usize) -> BoneRange {
1070 let index = index.min(self.bone_range_count().saturating_sub(1));
1071 let o = self.ranges_off + index * HMD7_RANGE_BYTES;
1072 if !fits(self.data, o, HMD7_RANGE_BYTES) {
1073 return BoneRange {
1074 first: 0,
1075 count: 0,
1076 bone: 0,
1077 body_mask: 0,
1078 mouth: false,
1079 };
1080 }
1081 let (first, count, bone) = unsafe {
1083 (
1084 rd_u16(self.data, o) as usize,
1085 rd_u16(self.data, o + 2) as usize,
1086 rd_u16(self.data, o + 4) as usize,
1087 )
1088 };
1089 BoneRange {
1090 first,
1091 count,
1092 bone,
1093 body_mask: self.data[o + 6],
1094 mouth: self.data[o + 7] & HMD7_RANGE_MOUTH != 0,
1095 }
1096 }
1097
1098 #[inline]
1101 pub fn hitbox(&self, index: usize) -> StudioHitbox {
1102 let index = index.min(self.hitbox_count().saturating_sub(1));
1103 let o = self.hitboxes_off + index * HMD7_HITBOX_BYTES;
1104 if self.hitbox_count() == 0 || !fits(self.data, o, HMD7_HITBOX_BYTES) {
1105 return StudioHitbox {
1106 bone: 0,
1107 bbmin: Vec3I16::ZERO,
1108 bbmax: Vec3I16::ZERO,
1109 };
1110 }
1111 unsafe {
1113 StudioHitbox {
1114 bone: rd_u16(self.data, o) as usize,
1115 bbmin: Vec3I16::new(
1116 rd_i16(self.data, o + 2),
1117 rd_i16(self.data, o + 4),
1118 rd_i16(self.data, o + 6),
1119 ),
1120 bbmax: Vec3I16::new(
1121 rd_i16(self.data, o + 8),
1122 rd_i16(self.data, o + 10),
1123 rd_i16(self.data, o + 12),
1124 ),
1125 }
1126 }
1127 }
1128
1129 #[inline(always)]
1133 fn vert_in_blob(&self, index: usize) -> bool {
1134 if self.vertex_soa {
1135 fits(self.data, self.vertices_off + index * 4, 4)
1136 && fits(self.data, self.vertices_z_off + index * 2, 2)
1137 } else {
1138 fits(self.data, self.vertices_off + index * 6, 6)
1139 }
1140 }
1141
1142 #[inline]
1146 pub fn vertex(&self, index: usize) -> Vec3I16 {
1147 if !self.vert_in_blob(index) {
1148 return Vec3I16::ZERO;
1149 }
1150 unsafe { self.vertex_unchecked(index) }
1152 }
1153
1154 #[inline]
1161 pub unsafe fn vertex_unchecked(&self, index: usize) -> Vec3I16 {
1162 if self.vertex_soa {
1163 let xy = self.vertices_off + index * 4;
1164 let z = self.vertices_z_off + index * 2;
1165 return unsafe {
1168 Vec3I16::new(
1169 rd_i16(self.data, xy),
1170 rd_i16(self.data, xy + 2),
1171 rd_i16(self.data, z),
1172 )
1173 };
1174 }
1175 #[allow(unused_variables)] let o = self.vertices_off + index * 6;
1177 #[cfg(target_arch = "mips")]
1178 if self.aligned_vertices {
1179 unsafe {
1187 let p = self.data.as_ptr().add(o).cast::<i16>();
1188 return Vec3I16::new(p.read(), p.add(1).read(), p.add(2).read());
1189 }
1190 }
1191 unsafe {
1194 Vec3I16::new(
1195 rd_i16(self.data, o),
1196 rd_i16(self.data, o + 2),
1197 rd_i16(self.data, o + 4),
1198 )
1199 }
1200 }
1201
1202 #[inline]
1206 pub fn vertex_gte_words(&self, index: usize) -> GteVertexWords {
1207 if !self.vert_in_blob(index) {
1208 return GteVertexWords { xy: 0, z: 0 };
1209 }
1210 unsafe { self.vertex_gte_words_unchecked(index) }
1212 }
1213
1214 #[inline]
1224 pub fn vertex_words(&self) -> VertexWords<'static> {
1225 if !self.vertex_soa {
1226 return VertexWords::EMPTY;
1227 }
1228 let count = self.vertices_z_off.saturating_sub(self.vertices_off) / 4;
1231 let base = self.data.as_ptr() as usize;
1232 let in_blob = fits(self.data, self.vertices_off, count * 4)
1233 && fits(self.data, self.vertices_z_off, count * 2);
1234 let aligned = base
1235 .wrapping_add(self.vertices_off)
1236 .is_multiple_of(core::mem::align_of::<u32>())
1237 && base
1238 .wrapping_add(self.vertices_z_off)
1239 .is_multiple_of(core::mem::align_of::<i16>());
1240 debug_assert!(in_blob, "HMD8 SoA vertex stream outside the blob");
1243 debug_assert!(aligned, "HMD8 SoA vertex stream misaligned");
1244 if !(in_blob && aligned) {
1245 return VertexWords::EMPTY;
1246 }
1247 let start = self.data.as_ptr();
1248 unsafe {
1260 VertexWords {
1261 xy: core::slice::from_raw_parts(start.add(self.vertices_off).cast::<u32>(), count),
1262 z: core::slice::from_raw_parts(start.add(self.vertices_z_off).cast::<i16>(), count),
1263 }
1264 }
1265 }
1266
1267 #[inline]
1272 pub unsafe fn vertex_gte_words_unchecked(&self, index: usize) -> GteVertexWords {
1273 if self.vertex_soa {
1274 let xy = self.vertices_off + index * 4;
1275 let z = self.vertices_z_off + index * 2;
1276 #[cfg(target_arch = "mips")]
1277 unsafe {
1283 return GteVertexWords {
1284 xy: core::ptr::read_volatile(self.data.as_ptr().add(xy).cast::<u32>()),
1285 z: core::ptr::read_volatile(self.data.as_ptr().add(z).cast::<i16>()) as i32
1286 as u32,
1287 };
1288 }
1289 #[cfg(not(target_arch = "mips"))]
1290 return unsafe {
1292 GteVertexWords {
1293 xy: rd_u32(self.data, xy),
1294 z: rd_i16(self.data, z) as i32 as u32,
1295 }
1296 };
1297 }
1298 #[allow(unused_variables)] let o = self.vertices_off + index * 6;
1300 #[cfg(target_arch = "mips")]
1301 unsafe {
1305 let p = self.data.as_ptr().add(o);
1306 let xy = p.cast::<u32>().read_unaligned();
1307 let z = if self.aligned_vertices {
1308 p.add(4).cast::<i16>().read()
1309 } else {
1310 rd_i16(self.data, o + 4)
1311 };
1312 GteVertexWords {
1313 xy,
1314 z: z as i32 as u32,
1315 }
1316 }
1317 #[cfg(not(target_arch = "mips"))]
1318 {
1319 let v = unsafe { self.vertex_unchecked(index) };
1321 GteVertexWords {
1322 xy: ((v.y as u16 as u32) << 16) | v.x as u16 as u32,
1323 z: v.z as i32 as u32,
1324 }
1325 }
1326 }
1327
1328 #[inline]
1329 pub fn frame_section_len(&self) -> usize {
1334 self.tri_off
1335 }
1336
1337 pub fn compact_visible_bodies(
1356 blob: &mut [u8],
1357 visible_bodies: u8,
1358 remap: &mut [u16],
1359 ) -> Option<usize> {
1360 let (layout, _) = Self::layout(blob, DEFAULT_MAX_VERTICES);
1361 let old_n = layout.vertex_count();
1362 if !layout.has_body_masks
1363 || old_n == 0
1364 || old_n > remap.len()
1365 || layout.tri_off > blob.len()
1366 {
1367 return None;
1368 }
1369 let remap = &mut remap[..old_n];
1370 remap.fill(u16::MAX);
1371 let mut new_n = 0usize;
1372 let mut new_ranges = 0usize;
1373 for range in 0..layout.bone_range_count() {
1374 let o = layout.ranges_off + range * HMD7_RANGE_BYTES;
1375 let record = blob.get(o..o + HMD7_RANGE_BYTES)?;
1376 let first = u16::from_le_bytes([record[0], record[1]]) as usize;
1377 let count = u16::from_le_bytes([record[2], record[3]]) as usize;
1378 if record[6] & visible_bodies == 0 {
1379 continue;
1380 }
1381 let dst = layout.ranges_off + new_ranges * HMD7_RANGE_BYTES;
1384 blob.copy_within(o..o + HMD7_RANGE_BYTES, dst);
1385 blob[dst..dst + 2].copy_from_slice(&(new_n as u16).to_le_bytes());
1386 for slot in remap.get_mut(first..first + count)? {
1387 *slot = new_n as u16;
1388 new_n += 1;
1389 }
1390 new_ranges += 1;
1391 }
1392 if new_n == 0 || new_ranges == 0 {
1393 return None;
1394 }
1395 let dst = layout.compact_vertices(blob, remap, new_ranges, new_n)?;
1396 layout.compact_tail(blob, dst, new_ranges, new_n)
1397 }
1398
1399 fn compact_vertices(
1402 &self,
1403 blob: &mut [u8],
1404 remap: &[u16],
1405 new_ranges: usize,
1406 new_n: usize,
1407 ) -> Option<usize> {
1408 let kept = || remap.iter().enumerate().filter(|(_, &new)| new != u16::MAX);
1409 let new_vertices_off = self.ranges_off + new_ranges * HMD7_RANGE_BYTES;
1410 if !self.vertex_soa {
1411 let mut dst = new_vertices_off;
1412 for (old, _) in kept() {
1413 move_within(blob, self.vertices_off + old * 6, dst, 6)?;
1414 dst += 6;
1415 }
1416 return Some(dst);
1417 }
1418 let new_vertices_z_off = new_vertices_off + new_n * 4;
1422 for (new, (old, _)) in kept().enumerate() {
1423 move_within(
1424 blob,
1425 self.vertices_off + old * 4,
1426 new_vertices_off + new * 4,
1427 4,
1428 )?;
1429 }
1430 for (new, (old, _)) in kept().enumerate() {
1431 move_within(
1432 blob,
1433 self.vertices_z_off + old * 2,
1434 new_vertices_z_off + new * 2,
1435 2,
1436 )?;
1437 }
1438 Some(new_vertices_z_off + new_n * 2)
1439 }
1440
1441 fn compact_tail(
1445 &self,
1446 blob: &mut [u8],
1447 mut dst: usize,
1448 new_ranges: usize,
1449 new_n: usize,
1450 ) -> Option<usize> {
1451 let pose_len = self
1452 .frame_count()
1453 .checked_mul(self.bone_count())?
1454 .checked_mul(HMD8_AFFINE_BYTES)?;
1455 let mouth_len = if self.has_mouth {
1456 self.frame_count().checked_mul(MOUTH_XFORM_BYTES)?
1457 } else {
1458 0
1459 };
1460 let hitbox_len = self.hitbox_count().checked_mul(HMD7_HITBOX_BYTES)?;
1461 for (src, len) in [
1462 (self.poses_off, pose_len),
1463 (self.mouth_xforms_off, mouth_len),
1464 (self.hitboxes_off, hitbox_len),
1465 (self.hma_off, self.hma_len),
1467 ] {
1468 if len == 0 {
1469 continue;
1470 }
1471 if src.checked_add(len)? > self.tri_off || dst.checked_add(len)? > blob.len() {
1472 return None;
1473 }
1474 move_within(blob, src, dst, len)?;
1475 dst += len;
1476 }
1477 let model_data_len = u32::try_from(dst.checked_sub(self.ranges_off)?).ok()?;
1478 let new_n = u32::try_from(new_n).ok()?;
1479 blob[4..8].copy_from_slice(&new_n.to_le_bytes());
1480 blob[24..28].copy_from_slice(&model_data_len.to_le_bytes());
1481 blob[34..36].copy_from_slice(&(new_ranges as u16).to_le_bytes());
1482 Some(dst)
1483 }
1484
1485 #[inline(always)]
1489 fn tri_in_blob(&self, t: usize) -> bool {
1490 fits(self.data, self.tri_off + t * self.tri_sz, self.tri_sz)
1493 }
1494
1495 #[inline(always)]
1500 pub fn triangle(&self, t: usize) -> Triangle {
1501 if !self.tri_in_blob(t) {
1502 return Triangle {
1503 idx: [0; 3],
1504 tex: 0,
1505 uv: [(0, 0); 3],
1506 normal: [0; 3],
1507 body_mask: 0,
1508 };
1509 }
1510 unsafe { self.triangle_unchecked(t) }
1512 }
1513
1514 #[inline(always)]
1521 pub unsafe fn triangle_unchecked(&self, t: usize) -> Triangle {
1522 let o = self.tri_off + t * self.tri_sz;
1523 let d = self.data;
1524 unsafe {
1533 let p = d.as_ptr().add(o);
1534 Triangle {
1535 idx: [rd_u16(d, o), rd_u16(d, o + 2), rd_u16(d, o + 4)],
1536 tex: rd_u16(d, o + 6) as usize,
1537 uv: [
1538 (p.add(8).read(), p.add(9).read()),
1539 (p.add(10).read(), p.add(11).read()),
1540 (p.add(12).read(), p.add(13).read()),
1541 ],
1542 normal: match self.normal_encoding {
1543 NORMAL_I8X3 => [
1544 p.add(14).read() as i8,
1545 p.add(15).read() as i8,
1546 p.add(16).read() as i8,
1547 ],
1548 NORMAL_PACKED_555 => unpack_normal_555(rd_u16(d, o + 14)),
1549 _ => [0; 3],
1550 },
1551 body_mask: if self.has_body_masks {
1552 p.add(if self.normal_encoding == NORMAL_I8X3 {
1553 17
1554 } else {
1555 14
1556 })
1557 .read()
1558 } else {
1559 0xff
1560 },
1561 }
1562 }
1563 }
1564
1565 #[inline(always)]
1569 pub fn triangle_uv_words(&self, t: usize) -> [u16; 3] {
1570 if !self.tri_in_blob(t) {
1571 return [0; 3];
1572 }
1573 unsafe { self.triangle_uv_words_unchecked(t) }
1575 }
1576
1577 #[inline(always)]
1582 pub unsafe fn triangle_uv_words_unchecked(&self, t: usize) -> [u16; 3] {
1583 let o = self.tri_off + t * self.tri_sz + 8;
1584 unsafe {
1587 [
1588 rd_u16(self.data, o),
1589 rd_u16(self.data, o + 2),
1590 rd_u16(self.data, o + 4),
1591 ]
1592 }
1593 }
1594
1595 #[inline]
1596 pub fn has_body_masks(&self) -> bool {
1597 self.has_body_masks
1598 }
1599
1600 #[inline]
1603 pub fn triangle_normal(&self, t: usize) -> [i8; 3] {
1604 if !self.tri_in_blob(t) {
1605 return [0; 3];
1606 }
1607 unsafe { self.triangle_normal_unchecked(t) }
1609 }
1610
1611 #[inline]
1616 pub unsafe fn triangle_normal_unchecked(&self, t: usize) -> [i8; 3] {
1617 let o = self.tri_off + t * self.tri_sz + 14;
1618 let p = self.data.as_ptr();
1619 unsafe {
1622 match self.normal_encoding {
1623 NORMAL_I8X3 => [
1624 p.add(o).read() as i8,
1625 p.add(o + 1).read() as i8,
1626 p.add(o + 2).read() as i8,
1627 ],
1628 NORMAL_PACKED_555 => unpack_normal_555(rd_u16(self.data, o)),
1629 _ => [0; 3],
1630 }
1631 }
1632 }
1633
1634 pub fn render_face_count(&self, visible_bodies: u8) -> usize {
1636 let mut count = 0usize;
1637 let mut t = 0usize;
1638 while t < self.triangle_count() {
1639 if self.triangle(t).body_mask & visible_bodies != 0 {
1640 count += 1;
1641 }
1642 t += 1;
1643 }
1644 count
1645 }
1646
1647 pub fn render_texture_mask(&self, visible_bodies: u8) -> u32 {
1648 let mut mask = 0u32;
1649 let mut t = 0usize;
1650 while t < self.triangle_count() {
1651 let tri = self.triangle(t);
1652 if tri.body_mask & visible_bodies != 0 && tri.tex < 32 {
1653 mask |= 1u32 << tri.tex;
1654 }
1655 t += 1;
1656 }
1657 mask
1658 }
1659
1660 pub fn render_face_run_count(&self, visible_bodies: u8) -> usize {
1661 let mut runs = 0usize;
1662 let mut last_tex = usize::MAX;
1663 let mut last_mask = 0u8;
1664 let mut t = 0usize;
1665 while t < self.triangle_count() {
1666 let tri = self.triangle(t);
1667 if tri.body_mask & visible_bodies == 0 {
1668 t += 1;
1669 continue;
1670 }
1671 if tri.tex != last_tex || tri.body_mask != last_mask {
1672 runs += 1;
1673 last_tex = tri.tex;
1674 last_mask = tri.body_mask;
1675 }
1676 t += 1;
1677 }
1678 runs
1679 }
1680
1681 pub unsafe fn fill_render_faces_split_raw(
1692 &self,
1693 indices: *mut u32,
1694 payloads: *mut RenderFacePayload,
1695 runs: *mut u32,
1696 out_len: usize,
1697 visible_bodies: u8,
1698 ) -> (usize, usize) {
1699 debug_assert!(
1700 self.vertex_count() <= RENDER_FACE_INDEX_MASK as usize + 1,
1701 "render faces pack 10-bit vertex indices; this model has more vertices"
1702 );
1703 unsafe {
1705 if self.triangle_count() == 0 || out_len == 0 {
1706 return (0, 0);
1707 }
1708 let mut source_t = 0usize;
1709 let mut out_t = 0usize;
1710 let mut run_count = 0usize;
1711 let mut run_tex = usize::MAX;
1712 let mut run_mask = 0u8;
1713 while source_t < self.triangle_count() && out_t < out_len {
1714 let a = self.triangle(source_t);
1715 source_t += 1;
1716 if a.body_mask & visible_bodies == 0 {
1717 continue;
1718 }
1719 if a.tex != run_tex || a.body_mask != run_mask {
1720 if run_count != 0 {
1721 ptr::write(
1722 runs.add(run_count - 1),
1723 (out_t as u32)
1724 | ((run_tex.min(u8::MAX as usize) as u32) << 16)
1725 | ((run_mask as u32) << 24),
1726 );
1727 }
1728 run_tex = a.tex;
1729 run_mask = a.body_mask;
1730 run_count += 1;
1731 }
1732 write_render_face(indices, payloads, out_t, &a);
1733 out_t += 1;
1734 }
1735 if run_count == 0 {
1736 return (0, 0);
1737 }
1738 ptr::write(
1739 runs.add(run_count - 1),
1740 (out_t as u32)
1741 | ((run_tex.min(u8::MAX as usize) as u32) << 16)
1742 | ((run_mask as u32) << 24),
1743 );
1744 (out_t, run_count)
1745 }
1746 }
1747
1748 #[deprecated(note = "renamed to `vertex`")]
1750 #[inline(always)]
1751 pub fn vert(&self, index: usize) -> Vec3I16 {
1752 self.vertex(index)
1753 }
1754
1755 #[deprecated(note = "renamed to `triangle_uv_words`")]
1757 #[inline(always)]
1758 pub fn tri_uv_words(&self, t: usize) -> [u16; 3] {
1759 self.triangle_uv_words(t)
1760 }
1761}
1762
1763#[derive(Clone, Copy)]
1765pub struct Tracks {
1766 pub model: crate::hma1::Model,
1767 pub map: &'static [u8],
1769 pub jaw: Option<Jaw>,
1770}
1771
1772#[derive(Clone, Copy)]
1774pub struct Jaw {
1775 pub bone: usize,
1776 pub post: bool,
1777 pub open: [i16; 4],
1778}
1779
1780#[derive(Clone, Copy)]
1782pub enum Pose<'a> {
1783 Palette(InterpolatedModelFrame<'a>),
1784 Tracks {
1785 map: &'a [u8],
1786 bones: &'a [crate::hma1::Affine],
1787 },
1788}
1789
1790impl Pose<'_> {
1791 #[inline]
1795 pub fn bone(&self, bone: usize, mouth_range: bool, mouth: u8) -> BoneTransform {
1796 match self {
1797 Pose::Palette(frame) => frame.bone(bone, mouth_range, mouth),
1798 Pose::Tracks { map, bones } => {
1799 let a = &bones[map[bone] as usize];
1800 let t = |v: i32| ((v + 2) >> 2).clamp(i16::MIN as i32, i16::MAX as i32) as i16;
1803 BoneTransform {
1804 rotation: Mat3I16 { m: a.r },
1805 translation: Vec3I16::new(t(a.t[0]), t(a.t[1]), t(a.t[2])),
1806 }
1807 }
1808 }
1809 }
1810}
1811
1812#[inline(always)]
1815fn hma1_section_layout(data: &[u8], off: usize, len: usize) -> Option<(usize, usize, usize)> {
1816 let n_used = u16::from_le_bytes([*data.get(off + 4)?, *data.get(off + 5)?]) as usize;
1817 let map_off = off + HMA1_SECTION_HEADER;
1818 Some((map_off, (map_off + n_used + 1) & !1, off + len))
1819}
1820
1821fn hma1_section_valid(data: &[u8], off: usize, len: usize, n_bones: usize, n_clips: usize) -> bool {
1826 if len < HMA1_SECTION_HEADER + 4 {
1827 return false;
1828 }
1829 let Some((map_off, blob_off, end)) = hma1_section_layout(data, off, len) else {
1830 return false;
1831 };
1832 if map_off + n_bones > end || blob_off > end {
1833 return false;
1834 }
1835 let (Some(map), Some(blob), Some(&jaw)) = (
1836 data.get(map_off..map_off + n_bones),
1837 data.get(blob_off..end),
1838 data.get(off + 6),
1839 ) else {
1840 return false;
1841 };
1842 if u16::from_le_bytes([data[off + 4], data[off + 5]]) as usize != n_bones {
1843 return false;
1844 }
1845 let Some((hma_bones, hma_clips, _, _)) = crate::hma1::validated_layout(blob) else {
1846 return false;
1847 };
1848 hma_bones != 0
1849 && hma_clips >= n_clips.max(1)
1850 && map.iter().all(|&bone| (bone as usize) < hma_bones)
1851 && (jaw == 0xff || (jaw as usize) < hma_bones)
1852}
1853
1854impl<'a> ModelFrame<'a> {
1855 #[inline]
1856 pub fn interpolate(self, b: Self, frac16: u32) -> InterpolatedModelFrame<'a> {
1857 InterpolatedModelFrame {
1858 a: self,
1859 b,
1860 frac16: frac16 as i32,
1861 }
1862 }
1863 #[inline]
1866 fn bone(&self, bone: usize) -> BoneTransform {
1867 let bone = bone.min(self.n_bones.saturating_sub(1));
1868 let o = self.poses_off + (self.frame_idx * self.n_bones + bone) * HMD8_AFFINE_BYTES;
1869 if !fits(self.data, o, HMD8_AFFINE_BYTES) {
1870 return IDENTITY_TRANSFORM;
1871 }
1872 unsafe {
1875 let p0 = rd_q11_pair(self.data, o);
1876 let p1 = rd_q11_pair(self.data, o + 3);
1877 let p2 = rd_q11_pair(self.data, o + 6);
1878 let p3 = rd_q11_pair(self.data, o + 9);
1879 let last = decode_q11(rd_u16(self.data, o + 12) & 0x0fff);
1880 BoneTransform {
1881 rotation: Mat3I16 {
1882 m: [[p0.0, p0.1, p1.0], [p1.1, p2.0, p2.1], [p3.0, p3.1, last]],
1883 },
1884 translation: Vec3I16::new(
1885 rd_i16(self.data, o + 14),
1886 rd_i16(self.data, o + 16),
1887 rd_i16(self.data, o + 18),
1888 ),
1889 }
1890 }
1891 }
1892
1893 #[inline]
1896 fn mouth_xform(&self) -> BoneTransform {
1897 let o = self.mouth_xforms_off + self.frame_idx * MOUTH_XFORM_BYTES;
1898 if !fits(self.data, o, MOUTH_XFORM_BYTES) {
1899 return IDENTITY_TRANSFORM;
1900 }
1901 let d = self.data;
1902 unsafe {
1904 BoneTransform {
1905 rotation: Mat3I16 {
1906 m: [
1907 [rd_i16(d, o), rd_i16(d, o + 2), rd_i16(d, o + 4)],
1908 [rd_i16(d, o + 6), rd_i16(d, o + 8), rd_i16(d, o + 10)],
1909 [rd_i16(d, o + 12), rd_i16(d, o + 14), rd_i16(d, o + 16)],
1910 ],
1911 },
1912 translation: Vec3I16::new(rd_i16(d, o + 18), rd_i16(d, o + 20), rd_i16(d, o + 22)),
1913 }
1914 }
1915 }
1916
1917 fn bone_with_mouth(&self, bone: usize, mouth_range: bool, mouth: u8) -> BoneTransform {
1918 let base = self.bone(bone);
1919 if mouth == 0 || !mouth_range || !self.has_mouth {
1920 return base;
1921 }
1922 let amount = mouth.min(64) as i32;
1923 let open = self.mouth_xform();
1924 let mut relative = BoneTransform {
1925 rotation: Mat3I16::IDENTITY,
1926 translation: Vec3I16::ZERO,
1927 };
1928 for row in 0..3 {
1929 for column in 0..3 {
1930 let identity = if row == column { 4096 } else { 0 };
1931 relative.rotation.m[row][column] =
1932 (identity + (((open.rotation.m[row][column] as i32 - identity) * amount) >> 6))
1933 .clamp(i16::MIN as i32, i16::MAX as i32) as i16;
1934 }
1935 }
1936 relative.translation = Vec3I16::new(
1937 ((open.translation.x as i32 * amount) >> 6) as i16,
1938 ((open.translation.y as i32 * amount) >> 6) as i16,
1939 ((open.translation.z as i32 * amount) >> 6) as i16,
1940 );
1941 compose_affine(relative, base)
1942 }
1943}
1944
1945impl InterpolatedModelFrame<'_> {
1946 #[inline(always)]
1947 fn generic_component(a: i16, b: i16, frac16: i32) -> i16 {
1948 a.wrapping_add((((b as i32 - a as i32) * frac16) >> 4) as i16)
1949 }
1950
1951 #[inline(never)]
1952 pub fn bone(&self, bone: usize, mouth_range: bool, mouth: u8) -> BoneTransform {
1953 let a = self.a.bone_with_mouth(bone, mouth_range, mouth);
1954 if self.frac16 == 0 || self.a.frame_idx == self.b.frame_idx {
1955 return a;
1956 }
1957 let b = self.b.bone_with_mouth(bone, mouth_range, mouth);
1958 let mut rotation = Mat3I16::ZERO;
1959 for row in 0..3 {
1960 for column in 0..3 {
1961 rotation.m[row][column] = Self::generic_component(
1962 a.rotation.m[row][column],
1963 b.rotation.m[row][column],
1964 self.frac16,
1965 );
1966 }
1967 }
1968 BoneTransform {
1969 rotation,
1970 translation: Vec3I16::new(
1971 Self::generic_component(a.translation.x, b.translation.x, self.frac16),
1972 Self::generic_component(a.translation.y, b.translation.y, self.frac16),
1973 Self::generic_component(a.translation.z, b.translation.z, self.frac16),
1974 ),
1975 }
1976 }
1977}
1978
1979#[inline(never)]
1980fn compose_affine(parent: BoneTransform, child: BoneTransform) -> BoneTransform {
1981 let rotation = parent.rotation.mul(&child.rotation);
1982 let transformed = parent.rotation.transform(child.translation);
1983 BoneTransform {
1984 rotation,
1985 translation: Vec3I16::new(
1986 transformed[0]
1987 .saturating_add(parent.translation.x as i32)
1988 .clamp(i16::MIN as i32, i16::MAX as i32) as i16,
1989 transformed[1]
1990 .saturating_add(parent.translation.y as i32)
1991 .clamp(i16::MIN as i32, i16::MAX as i32) as i16,
1992 transformed[2]
1993 .saturating_add(parent.translation.z as i32)
1994 .clamp(i16::MIN as i32, i16::MAX as i32) as i16,
1995 ),
1996 }
1997}
1998
1999#[cfg(test)]
2000mod tests {
2001 use super::*;
2002
2003 #[repr(C, align(4))]
2006 struct Blob([u8; 1024]);
2007
2008 fn body_masked(ranges: &[(u16, u8)], soa: bool, hitboxes: usize) -> (Blob, usize) {
2012 const FRAMES: usize = 2;
2013 const BONES: usize = 3;
2014 let n_verts: usize = ranges.iter().map(|&(count, _)| count as usize).sum();
2015 let ranges_off = 40;
2016 let vertices_off = ranges_off + ranges.len() * HMD7_RANGE_BYTES;
2017 let poses_off = vertices_off + n_verts * 6;
2018 let mouth_off = poses_off + FRAMES * BONES * HMD8_AFFINE_BYTES;
2019 let hitboxes_off = mouth_off + FRAMES * MOUTH_XFORM_BYTES;
2020 let end = hitboxes_off + hitboxes * HMD7_HITBOX_BYTES;
2021 let mut blob = Blob([0; 1024]);
2022 let b = &mut blob.0;
2023 for (i, byte) in b[vertices_off..hitboxes_off].iter_mut().enumerate() {
2024 *byte = (i * 7 + 3) as u8;
2025 }
2026 let mut flags = HMD_FLAG_BODY_MASKS | HMD_FLAG_MOUTH;
2027 if soa {
2028 flags |= HMD_FLAG_VERTEX_SOA;
2029 }
2030 if hitboxes != 0 {
2031 flags |= HMD_FLAG_HITBOXES;
2032 }
2033 b[0..4].copy_from_slice(b"HMD8");
2034 b[4..8].copy_from_slice(&(n_verts as u32).to_le_bytes());
2035 b[12..16].copy_from_slice(&((hitboxes as u32) << 16).to_le_bytes());
2036 b[16..20].copy_from_slice(&(FRAMES as u32).to_le_bytes());
2037 b[20..24].copy_from_slice(&1u32.to_le_bytes());
2038 b[24..28].copy_from_slice(&((end - ranges_off) as u32).to_le_bytes());
2039 b[30..32].copy_from_slice(&flags.to_le_bytes());
2040 b[32..34].copy_from_slice(&(BONES as u16).to_le_bytes());
2041 b[34..36].copy_from_slice(&(ranges.len() as u16).to_le_bytes());
2042 let mut first = 0u16;
2043 for (i, &(count, mask)) in ranges.iter().enumerate() {
2044 let o = ranges_off + i * HMD7_RANGE_BYTES;
2045 b[o..o + 2].copy_from_slice(&first.to_le_bytes());
2046 b[o + 2..o + 4].copy_from_slice(&count.to_le_bytes());
2047 b[o + 4..o + 6].copy_from_slice(&((i % BONES) as u16).to_le_bytes());
2048 b[o + 6] = mask;
2049 first += count;
2050 }
2051 for h in 0..hitboxes {
2052 let o = hitboxes_off + h * HMD7_HITBOX_BYTES;
2053 b[o..o + 2].copy_from_slice(&((h % BONES) as u16).to_le_bytes());
2054 }
2055 (blob, end)
2056 }
2057
2058 fn reference_decode_q11(raw: u16) -> i16 {
2059 let code = if raw & 0x0800 != 0 {
2060 (raw | 0xf000) as i16
2061 } else {
2062 raw as i16
2063 };
2064 if code == 2047 {
2065 4096
2066 } else {
2067 code << 1
2068 }
2069 }
2070
2071 #[test]
2072 fn q11_decode_matches_every_packed_code() {
2073 for raw in 0..=0x0fff {
2074 assert_eq!(decode_q11(raw), reference_decode_q11(raw), "{raw:#05x}");
2075 }
2076 }
2077
2078 #[test]
2079 fn unaligned_pair_read_ignores_the_following_byte() {
2080 let a = 0x07ffu32;
2081 let b = 0x0800u32;
2082 let packed = a | (b << 12) | (0xa5 << 24);
2083 let bytes = packed.to_le_bytes();
2084 let pair = unsafe { rd_q11_pair(&bytes, 0) };
2086 assert_eq!(
2087 pair,
2088 (
2089 reference_decode_q11(a as u16),
2090 reference_decode_q11(b as u16)
2091 )
2092 );
2093 }
2094
2095 #[test]
2096 fn model_scale_header_accepts_only_exact_power_of_two_q12_scales() {
2097 for valid in [0, 256, 512, 1024, 2048, 4096] {
2098 assert!(valid_local_to_world_q12(valid));
2099 }
2100 for invalid in [1, 255, 257, 511, 513, 1000, 4095, 4097, u16::MAX] {
2101 assert!(!valid_local_to_world_q12(invalid));
2102 }
2103 }
2104
2105 #[test]
2109 fn compaction_keeps_the_visible_bodies() {
2110 let ranges: &[(u16, u8)] = &[(3, 1), (2, 2), (4, 1), (1, 3)];
2111 for soa in [false, true] {
2112 let (source, len) = body_masked(ranges, soa, 2);
2113 let leaked: &'static Blob = std::boxed::Box::leak(std::boxed::Box::new(Blob(source.0)));
2114 let before = Model::from_bytes(&leaked.0[..len]);
2115 let mut blob = Blob(source.0);
2116 let mut remap = [0u16; 16];
2117 let kept = Model::compact_visible_bodies(&mut blob.0[..len], 2, &mut remap)
2118 .expect("body 2 is visible");
2119 let mut expected = [u16::MAX; 10];
2121 expected[3] = 0;
2122 expected[4] = 1;
2123 expected[9] = 2;
2124 assert_eq!(&remap[..10], &expected);
2125 let compact: &'static Blob = std::boxed::Box::leak(std::boxed::Box::new(Blob(blob.0)));
2126 let after = Model::from_bytes(&compact.0[..kept]);
2127 assert_eq!(after.vertex_count(), 3);
2128 assert_eq!(after.bone_range_count(), 2);
2129 assert_eq!(after.hitbox_count(), 2);
2130 assert!(after.has_body_masks());
2131 for (old, &new) in expected.iter().enumerate() {
2132 if new != u16::MAX {
2133 assert_eq!(after.vertex(new as usize), before.vertex(old), "soa {soa}");
2134 }
2135 }
2136 assert_eq!(after.bone_range(1).first, 2);
2137 assert_eq!(after.bone_range(1).body_mask, 3);
2138 let pose_bytes =
2139 2 * 3 * HMD8_AFFINE_BYTES + 2 * MOUTH_XFORM_BYTES + 2 * HMD7_HITBOX_BYTES;
2140 assert_eq!(
2141 &compact.0[kept - pose_bytes..kept],
2142 &source.0[len - pose_bytes..len],
2143 "palette, mouth and hitboxes move intact"
2144 );
2145 }
2146 let (source, len) = body_masked(ranges, false, 0);
2148 let mut blob = Blob(source.0);
2149 assert_eq!(
2150 Model::compact_visible_bodies(&mut blob.0[..len], 4, &mut [0; 16]),
2151 None
2152 );
2153 assert_eq!(
2154 Model::compact_visible_bodies(&mut blob.0[..len], 2, &mut [0; 9]),
2155 None
2156 );
2157 }
2158}