1#![cfg_attr(target_arch = "mips", feature(asm_experimental_arch))]
3#![no_std]
41#![deny(unsafe_op_in_unsafe_fn)]
42#![warn(missing_docs)]
43
44pub mod hma1;
45pub mod hmd8;
46
47use psx_gte::math::{Vec3I16, Vec3I32};
48
49const MESH_VERSION_U8_INDICES: u16 = 1;
50const MESH_U8_INDEX_STRIDE: usize = 3;
51const MESH_U16_INDEX_STRIDE: usize = 6;
52
53pub mod world_topology {
55 pub use psxed_format::world::topology::{
56 horizontal_triangle_at_local, split_triangle, split_triangles, triangle_contains_corner,
57 wall_shape_for_dropped_corner, wall_shape_triangle, wall_shape_triangle_corners,
58 SplitTriangles, TriangleCorners, HORIZONTAL_NE_SW_TRIANGLES, HORIZONTAL_NW_SE_TRIANGLES,
59 SPLIT_ONE_THREE_TRIANGLES, SPLIT_ZERO_TWO_TRIANGLES, WHOLE_QUAD_TRIANGLE_INDEX,
60 };
61}
62
63pub const WORLD_SPLIT_NORTH_WEST_SOUTH_EAST: u8 = psxed_format::world::split::NORTH_WEST_SOUTH_EAST;
65
66pub const WORLD_SPLIT_NORTH_EAST_SOUTH_WEST: u8 = psxed_format::world::split::NORTH_EAST_SOUTH_WEST;
68
69pub const WORLD_WALL_SHAPE_QUAD: u16 = psxed_format::world::wall_shape::QUAD;
71
72pub const WORLD_WALL_SHAPE_DROP_BOTTOM_LEFT: u16 =
74 psxed_format::world::wall_shape::DROP_BOTTOM_LEFT;
75
76pub const WORLD_WALL_SHAPE_DROP_BOTTOM_RIGHT: u16 =
78 psxed_format::world::wall_shape::DROP_BOTTOM_RIGHT;
79
80pub const WORLD_WALL_SHAPE_DROP_TOP_RIGHT: u16 = psxed_format::world::wall_shape::DROP_TOP_RIGHT;
82
83pub const WORLD_WALL_SHAPE_DROP_TOP_LEFT: u16 = psxed_format::world::wall_shape::DROP_TOP_LEFT;
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum ParseError {
89 Truncated,
91 WrongMagic,
93 UnsupportedVersion(u16),
95 InvalidPayloadLen {
97 declared: u32,
99 actual: usize,
101 },
102 TableOverflow,
104 InvalidWorldLayout,
106 InvalidModelLayout,
108 InvalidAnimationLayout,
110 InvalidAudioLayout,
112}
113
114#[derive(Copy, Clone, Debug)]
120pub struct Mesh<'a> {
121 verts: &'a [u8],
122 indices: &'a [u8],
123 face_colors: Option<&'a [u8]>,
124 vertex_normals: Option<&'a [u8]>,
125 index_stride: u8,
126 vert_count: u16,
127 face_count: u16,
128 flags: u16,
129}
130
131impl<'a> Mesh<'a> {
132 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, ParseError> {
135 if bytes.len() < psxed_format::AssetHeader::SIZE {
137 return Err(ParseError::Truncated);
138 }
139 let magic: [u8; 4] = [bytes[0], bytes[1], bytes[2], bytes[3]];
140 if magic != psxed_format::mesh::MAGIC {
141 return Err(ParseError::WrongMagic);
142 }
143 let version = u16::from_le_bytes([bytes[4], bytes[5]]);
144 let index_stride = match version {
145 MESH_VERSION_U8_INDICES => MESH_U8_INDEX_STRIDE,
146 psxed_format::mesh::VERSION => MESH_U16_INDEX_STRIDE,
147 _ => return Err(ParseError::UnsupportedVersion(version)),
148 };
149 let flags = u16::from_le_bytes([bytes[6], bytes[7]]);
150 let payload_len = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
151 let payload_start = psxed_format::AssetHeader::SIZE;
152 let actual_payload = bytes.len().saturating_sub(payload_start);
153 if (payload_len as usize) != actual_payload {
154 return Err(ParseError::InvalidPayloadLen {
155 declared: payload_len,
156 actual: actual_payload,
157 });
158 }
159
160 if actual_payload < psxed_format::mesh::MeshHeader::SIZE {
162 return Err(ParseError::Truncated);
163 }
164 let mh = &bytes[payload_start..];
165 let vert_count = u16::from_le_bytes([mh[0], mh[1]]);
166 let face_count = u16::from_le_bytes([mh[2], mh[3]]);
167 let mut off = payload_start + psxed_format::mesh::MeshHeader::SIZE;
171 let vert_bytes = (vert_count as usize) * 6;
172 if off + vert_bytes > bytes.len() {
173 return Err(ParseError::TableOverflow);
174 }
175 let verts = &bytes[off..off + vert_bytes];
176 off += vert_bytes;
177
178 let index_bytes = (face_count as usize) * index_stride;
179 if off + index_bytes > bytes.len() {
180 return Err(ParseError::TableOverflow);
181 }
182 let indices = &bytes[off..off + index_bytes];
183 off += index_bytes;
184
185 let face_colors = if flags & psxed_format::mesh::flags::HAS_FACE_COLORS != 0 {
186 let color_bytes = (face_count as usize) * 3;
187 if off + color_bytes > bytes.len() {
188 return Err(ParseError::TableOverflow);
189 }
190 let slice = &bytes[off..off + color_bytes];
191 off += color_bytes;
192 Some(slice)
193 } else {
194 None
195 };
196
197 let vertex_normals = if flags & psxed_format::mesh::flags::HAS_NORMALS != 0 {
198 let normal_bytes = (vert_count as usize) * 6;
199 if off + normal_bytes > bytes.len() {
200 return Err(ParseError::TableOverflow);
201 }
202 let slice = &bytes[off..off + normal_bytes];
203 Some(slice)
204 } else {
205 None
206 };
207
208 Ok(Self {
209 verts,
210 indices,
211 face_colors,
212 vertex_normals,
213 index_stride: index_stride as u8,
214 vert_count,
215 face_count,
216 flags,
217 })
218 }
219
220 #[inline]
222 pub fn vertex_count(&self) -> u16 {
223 self.vert_count
224 }
225
226 #[deprecated(note = "renamed to `vertex_count`")]
228 #[inline(always)]
229 pub fn vert_count(&self) -> u16 {
230 self.vertex_count()
231 }
232
233 #[inline]
235 pub fn face_count(&self) -> u16 {
236 self.face_count
237 }
238
239 #[inline]
241 pub fn flags(&self) -> u16 {
242 self.flags
243 }
244
245 #[inline]
247 pub fn has_face_colors(&self) -> bool {
248 self.flags & psxed_format::mesh::flags::HAS_FACE_COLORS != 0
249 }
250
251 #[inline]
254 pub fn has_normals(&self) -> bool {
255 self.flags & psxed_format::mesh::flags::HAS_NORMALS != 0
256 }
257
258 #[inline]
263 pub fn vertex(&self, i: u16) -> Vec3I16 {
264 let idx = i as usize;
265 if idx >= self.vert_count as usize {
266 return Vec3I16::ZERO;
267 }
268 let base = idx * 6;
269 let x = i16::from_le_bytes([self.verts[base], self.verts[base + 1]]);
270 let y = i16::from_le_bytes([self.verts[base + 2], self.verts[base + 3]]);
271 let z = i16::from_le_bytes([self.verts[base + 4], self.verts[base + 5]]);
272 Vec3I16::new(x, y, z)
273 }
274
275 #[inline]
278 pub fn face(&self, i: u16) -> (u16, u16, u16) {
279 let idx = i as usize;
280 if idx >= self.face_count as usize {
281 return (0, 0, 0);
282 }
283 let base = idx * self.index_stride as usize;
284 if self.index_stride as usize == MESH_U16_INDEX_STRIDE {
285 (
286 u16::from_le_bytes([self.indices[base], self.indices[base + 1]]),
287 u16::from_le_bytes([self.indices[base + 2], self.indices[base + 3]]),
288 u16::from_le_bytes([self.indices[base + 4], self.indices[base + 5]]),
289 )
290 } else {
291 (
292 self.indices[base] as u16,
293 self.indices[base + 1] as u16,
294 self.indices[base + 2] as u16,
295 )
296 }
297 }
298
299 #[inline]
302 pub fn face_color(&self, i: u16) -> Option<(u8, u8, u8)> {
303 let colors = self.face_colors?;
304 let idx = i as usize;
305 if idx >= self.face_count as usize {
306 return None;
307 }
308 let base = idx * 3;
309 Some((colors[base], colors[base + 1], colors[base + 2]))
310 }
311
312 #[inline]
318 pub fn vertex_normal(&self, i: u16) -> Option<Vec3I16> {
319 let normals = self.vertex_normals?;
320 let idx = i as usize;
321 if idx >= self.vert_count as usize {
322 return None;
323 }
324 let base = idx * 6;
325 let x = i16::from_le_bytes([normals[base], normals[base + 1]]);
326 let y = i16::from_le_bytes([normals[base + 2], normals[base + 3]]);
327 let z = i16::from_le_bytes([normals[base + 4], normals[base + 5]]);
328 Some(Vec3I16::new(x, y, z))
329 }
330}
331
332#[derive(Copy, Clone, Debug)]
340pub struct Model<'a> {
341 joints: &'a [u8],
342 materials: &'a [u8],
343 parts: &'a [u8],
344 vertices: &'a [u8],
345 faces: &'a [u8],
346 face_palette_banks: Option<&'a [u8]>,
347 joint_count: u16,
348 material_count: u16,
349 part_count: u16,
350 vertex_count: u16,
351 face_count: u16,
352 texture_width: u16,
353 texture_height: u16,
354 local_to_world_q12: u16,
355 flags: u16,
356}
357
358impl<'a> Model<'a> {
359 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, ParseError> {
361 use psxed_format::model::{
362 JointRecord, MaterialRecord, ModelHeader, PartRecord, FACE_RECORD_SIZE, LEGACY_VERSION,
363 MAGIC, VERSION, VERTEX_RECORD_SIZE,
364 };
365
366 if bytes.len() < psxed_format::AssetHeader::SIZE {
367 return Err(ParseError::Truncated);
368 }
369 let magic = [bytes[0], bytes[1], bytes[2], bytes[3]];
370 if magic != MAGIC {
371 return Err(ParseError::WrongMagic);
372 }
373 let version = read_u16(bytes, 4);
374 if version != LEGACY_VERSION && version != VERSION {
375 return Err(ParseError::UnsupportedVersion(version));
376 }
377 let flags = read_u16(bytes, 6);
378 let payload_len = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
379 let payload_start = psxed_format::AssetHeader::SIZE;
380 let actual_payload = bytes.len().saturating_sub(payload_start);
381 if payload_len as usize != actual_payload {
382 return Err(ParseError::InvalidPayloadLen {
383 declared: payload_len,
384 actual: actual_payload,
385 });
386 }
387 if actual_payload < ModelHeader::SIZE {
388 return Err(ParseError::Truncated);
389 }
390
391 let mh = &bytes[payload_start..payload_start + ModelHeader::SIZE];
392 let joint_count = read_u16(mh, 0);
393 let part_count = read_u16(mh, 2);
394 let vertex_count = read_u16(mh, 4);
395 let face_count = read_u16(mh, 6);
396 let material_count = read_u16(mh, 8);
397 let texture_width = read_u16(mh, 10);
398 let texture_height = read_u16(mh, 12);
399 let local_to_world_q12 = read_u16(mh, 14);
400
401 let mut off = payload_start + ModelHeader::SIZE;
402
403 let joint_bytes = checked_table_bytes(joint_count, JointRecord::SIZE)?;
404 let joints = take_table(bytes, &mut off, joint_bytes)?;
405
406 let material_bytes = checked_table_bytes(material_count, MaterialRecord::SIZE)?;
407 let materials = take_table(bytes, &mut off, material_bytes)?;
408
409 let part_bytes = checked_table_bytes(part_count, PartRecord::SIZE)?;
410 let parts = take_table(bytes, &mut off, part_bytes)?;
411
412 let vertex_bytes = checked_table_bytes(vertex_count, VERTEX_RECORD_SIZE)?;
413 let vertices = take_table(bytes, &mut off, vertex_bytes)?;
414
415 let face_bytes = checked_table_bytes(face_count, FACE_RECORD_SIZE)?;
416 let faces = take_table(bytes, &mut off, face_bytes)?;
417
418 let has_face_palette_banks = flags & psxed_format::model::flags::FACE_PALETTE_BANKS != 0;
419 if version == LEGACY_VERSION && has_face_palette_banks {
420 return Err(ParseError::InvalidModelLayout);
421 }
422 let face_palette_banks = if has_face_palette_banks {
423 let bytes_needed = psxed_format::model::face_palette_bank_bytes(face_count);
424 Some(take_table(bytes, &mut off, bytes_needed)?)
425 } else {
426 None
427 };
428
429 if off != bytes.len() {
430 return Err(ParseError::InvalidModelLayout);
431 }
432 validate_model_parts(parts, joint_count, material_count, vertex_count, face_count)?;
433 validate_model_faces(faces, vertex_count)?;
434
435 Ok(Self {
436 joints,
437 materials,
438 parts,
439 vertices,
440 faces,
441 face_palette_banks,
442 joint_count,
443 material_count,
444 part_count,
445 vertex_count,
446 face_count,
447 texture_width,
448 texture_height,
449 local_to_world_q12,
450 flags,
451 })
452 }
453
454 #[inline]
456 pub fn flags(&self) -> u16 {
457 self.flags
458 }
459
460 #[inline]
463 pub fn is_double_sided(&self) -> bool {
464 self.flags & psxed_format::model::flags::DOUBLE_SIDED != 0
465 }
466
467 #[inline]
469 pub fn joint_count(&self) -> u16 {
470 self.joint_count
471 }
472
473 #[inline]
475 pub fn material_count(&self) -> u16 {
476 self.material_count
477 }
478
479 #[inline]
481 pub fn part_count(&self) -> u16 {
482 self.part_count
483 }
484
485 #[inline]
487 pub fn vertex_count(&self) -> u16 {
488 self.vertex_count
489 }
490
491 #[inline]
493 pub fn face_count(&self) -> u16 {
494 self.face_count
495 }
496
497 #[inline]
499 pub fn texture_width(&self) -> u16 {
500 self.texture_width
501 }
502
503 #[inline]
505 pub fn texture_height(&self) -> u16 {
506 self.texture_height
507 }
508
509 pub fn bind_pose_floor_lift(&self) -> i32 {
519 let mut lowest = i32::MAX;
520 let mut i = 0u16;
521 while i < self.vertex_count() {
522 if let Some(vertex) = self.vertex(i) {
523 lowest = lowest.min(vertex.position.y as i32);
524 }
525 i = i.wrapping_add(1);
526 if i == 0 {
527 break;
528 }
529 }
530 if lowest == i32::MAX || lowest >= 0 {
531 return 0;
532 }
533 -lowest
534 }
535
536 #[inline]
541 pub fn local_to_world_q12(&self) -> u16 {
542 if self.local_to_world_q12 == 0 {
543 psxed_format::model::DEFAULT_LOCAL_TO_WORLD_Q12
544 } else {
545 self.local_to_world_q12
546 }
547 }
548
549 #[inline]
551 pub fn joint(&self, index: u16) -> Option<ModelJoint> {
552 if index >= self.joint_count {
553 return None;
554 }
555 let base = index as usize * psxed_format::model::JointRecord::SIZE;
556 let bytes = self
557 .joints
558 .get(base..base + psxed_format::model::JointRecord::SIZE)?;
559 Some(ModelJoint {
560 parent: read_u16(bytes, 0),
561 })
562 }
563
564 #[inline]
566 pub fn material(&self, index: u16) -> Option<ModelMaterial> {
567 if index >= self.material_count {
568 return None;
569 }
570 let base = index as usize * psxed_format::model::MaterialRecord::SIZE;
571 let bytes = self
572 .materials
573 .get(base..base + psxed_format::model::MaterialRecord::SIZE)?;
574 Some(ModelMaterial {
575 texture_index: read_u16(bytes, 0),
576 flags: read_u16(bytes, 2),
577 base_color: [bytes[4], bytes[5], bytes[6], bytes[7]],
578 })
579 }
580
581 #[inline]
583 pub fn part(&self, index: u16) -> Option<ModelPart> {
584 if index >= self.part_count {
585 return None;
586 }
587 let base = index as usize * psxed_format::model::PartRecord::SIZE;
588 let bytes = self
589 .parts
590 .get(base..base + psxed_format::model::PartRecord::SIZE)?;
591 Some(ModelPart {
592 joint_index: read_u16(bytes, 0),
593 first_vertex: read_u16(bytes, 2),
594 vertex_count: read_u16(bytes, 4),
595 first_face: read_u16(bytes, 6),
596 face_count: read_u16(bytes, 8),
597 material_index: read_u16(bytes, 10),
598 })
599 }
600
601 #[inline]
603 pub fn vertex(&self, index: u16) -> Option<ModelVertex> {
604 if index >= self.vertex_count {
605 return None;
606 }
607 let base = index as usize * psxed_format::model::VERTEX_RECORD_SIZE;
608 let bytes = self
609 .vertices
610 .get(base..base + psxed_format::model::VERTEX_RECORD_SIZE)?;
611 Some(ModelVertex {
612 position: Vec3I16::new(read_i16(bytes, 0), read_i16(bytes, 2), read_i16(bytes, 4)),
613 joint1: bytes[6],
614 blend: bytes[7],
615 })
616 }
617
618 #[inline]
620 pub fn face(&self, index: u16) -> Option<ModelFace> {
621 if index >= self.face_count {
622 return None;
623 }
624 let base = index as usize * psxed_format::model::FACE_RECORD_SIZE;
625 let bytes = self
626 .faces
627 .get(base..base + psxed_format::model::FACE_RECORD_SIZE)?;
628 Some(ModelFace {
629 corners: [
630 ModelFaceCorner {
631 vertex_index: read_u16(bytes, 0),
632 uv: (bytes[2], bytes[3]),
633 },
634 ModelFaceCorner {
635 vertex_index: read_u16(bytes, 4),
636 uv: (bytes[6], bytes[7]),
637 },
638 ModelFaceCorner {
639 vertex_index: read_u16(bytes, 8),
640 uv: (bytes[10], bytes[11]),
641 },
642 ],
643 })
644 }
645
646 #[inline]
651 pub fn face_palette_bank(&self, index: u16) -> Option<u8> {
652 if index >= self.face_count {
653 return None;
654 }
655 let Some(packed) = self.face_palette_banks else {
656 return Some(0);
657 };
658 let byte = *packed.get(index as usize / 4)?;
659 Some((byte >> ((index as usize & 3) * 2)) & 3)
660 }
661
662 pub fn palette_bank_count(&self) -> u8 {
664 let mut highest = 0u8;
665 let mut index = 0u16;
666 while index < self.face_count {
667 highest = highest.max(self.face_palette_bank(index).unwrap_or(0));
668 index += 1;
669 }
670 highest + 1
671 }
672}
673
674#[derive(Copy, Clone, Debug, PartialEq, Eq)]
676pub struct ModelJoint {
677 parent: u16,
678}
679
680impl ModelJoint {
681 #[inline]
683 pub fn parent(&self) -> Option<u16> {
684 if self.parent == psxed_format::model::NO_JOINT {
685 None
686 } else {
687 Some(self.parent)
688 }
689 }
690}
691
692#[derive(Copy, Clone, Debug, PartialEq, Eq)]
694pub struct ModelMaterial {
695 texture_index: u16,
696 flags: u16,
697 base_color: [u8; 4],
698}
699
700impl ModelMaterial {
701 #[inline]
703 pub fn texture_index(&self) -> u16 {
704 self.texture_index
705 }
706
707 #[inline]
709 pub fn flags(&self) -> u16 {
710 self.flags
711 }
712
713 #[inline]
715 pub fn base_color(&self) -> [u8; 4] {
716 self.base_color
717 }
718}
719
720#[derive(Copy, Clone, Debug, PartialEq, Eq)]
722pub struct ModelPart {
723 joint_index: u16,
724 first_vertex: u16,
725 vertex_count: u16,
726 first_face: u16,
727 face_count: u16,
728 material_index: u16,
729}
730
731impl ModelPart {
732 pub const ZERO: Self = Self {
734 joint_index: 0,
735 first_vertex: 0,
736 vertex_count: 0,
737 first_face: 0,
738 face_count: 0,
739 material_index: 0,
740 };
741
742 #[inline]
744 pub fn joint_index(&self) -> u16 {
745 self.joint_index
746 }
747
748 #[inline]
750 pub fn first_vertex(&self) -> u16 {
751 self.first_vertex
752 }
753
754 #[inline]
756 pub fn vertex_count(&self) -> u16 {
757 self.vertex_count
758 }
759
760 #[inline]
762 pub fn first_face(&self) -> u16 {
763 self.first_face
764 }
765
766 #[inline]
768 pub fn face_count(&self) -> u16 {
769 self.face_count
770 }
771
772 #[inline]
774 pub fn material_index(&self) -> u16 {
775 self.material_index
776 }
777}
778
779pub const NO_JOINT8: u8 = psxed_format::model::NO_JOINT8;
783
784#[repr(C, align(4))]
788#[derive(Copy, Clone, Debug, PartialEq, Eq)]
789pub struct ModelVertex {
790 pub position: Vec3I16,
794 pub joint1: u8,
797 pub blend: u8,
800}
801
802const _: () = assert!(core::mem::size_of::<ModelVertex>() == 8);
804
805impl ModelVertex {
806 pub const ZERO: Self = Self {
808 position: Vec3I16::ZERO,
809 joint1: NO_JOINT8,
810 blend: 0,
811 };
812
813 #[inline]
815 pub fn is_blend(&self) -> bool {
816 self.blend != 0 && self.joint1 != NO_JOINT8
817 }
818}
819
820#[derive(Copy, Clone, Debug, PartialEq, Eq)]
822pub struct ModelFaceCorner {
823 pub vertex_index: u16,
825 pub uv: (u8, u8),
827}
828
829#[derive(Copy, Clone, Debug, PartialEq, Eq)]
831pub struct ModelFace {
832 pub corners: [ModelFaceCorner; 3],
834}
835
836#[derive(Copy, Clone, Debug)]
839pub struct Animation<'a> {
840 pose_indices: &'a [u8],
841 poses: &'a [u8],
842 joint_count: u16,
843 frame_count: u16,
844 sample_rate_hz: u16,
845 pose_record_size: usize,
846 translation_shift: u8,
847}
848
849impl<'a> Animation<'a> {
850 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, ParseError> {
852 use psxed_format::animation::{
853 AnimationHeader, MAGIC, POSE_RECORD_SIZE, POSE_RECORD_SIZE_V1, POSE_RECORD_SIZE_V3,
854 POSE_RECORD_SIZE_V4, VERSION, VERSION_V1, VERSION_V3, VERSION_V4, VERSION_V5,
855 };
856
857 if bytes.len() < psxed_format::AssetHeader::SIZE {
858 return Err(ParseError::Truncated);
859 }
860 let magic = [bytes[0], bytes[1], bytes[2], bytes[3]];
861 if magic != MAGIC {
862 return Err(ParseError::WrongMagic);
863 }
864 let version = read_u16(bytes, 4);
865 let pose_record_size = match version {
866 VERSION => POSE_RECORD_SIZE,
867 VERSION_V1 => POSE_RECORD_SIZE_V1,
868 VERSION_V3 => POSE_RECORD_SIZE_V3,
869 VERSION_V4 | VERSION_V5 => POSE_RECORD_SIZE_V4,
870 _ => {
871 return Err(ParseError::UnsupportedVersion(version));
872 }
873 };
874 let payload_len = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
875 let payload_start = psxed_format::AssetHeader::SIZE;
876 let actual_payload = bytes.len().saturating_sub(payload_start);
877 if payload_len as usize != actual_payload {
878 return Err(ParseError::InvalidPayloadLen {
879 declared: payload_len,
880 actual: actual_payload,
881 });
882 }
883 if actual_payload < AnimationHeader::SIZE {
884 return Err(ParseError::Truncated);
885 }
886
887 let ah = &bytes[payload_start..payload_start + AnimationHeader::SIZE];
888 let joint_count = read_u16(ah, 0);
889 let frame_count = read_u16(ah, 2);
890 let sample_rate_hz = read_u16(ah, 4);
891 let translation_shift = if version == VERSION
892 || version == VERSION_V3
893 || version == VERSION_V4
894 || version == VERSION_V5
895 {
896 let shift = read_u16(ah, 6);
897 if shift > 15 {
898 return Err(ParseError::InvalidAnimationLayout);
899 }
900 shift as u8
901 } else {
902 0
903 };
904 if joint_count == 0 || frame_count == 0 || sample_rate_hz == 0 {
905 return Err(ParseError::InvalidAnimationLayout);
906 }
907
908 let mut off = payload_start + AnimationHeader::SIZE;
909 let pose_count = (joint_count as usize)
910 .checked_mul(frame_count as usize)
911 .ok_or(ParseError::TableOverflow)?;
912 let (pose_indices, poses) = if version == VERSION_V5 {
913 let index_bytes = pose_count.checked_mul(2).ok_or(ParseError::TableOverflow)?;
914 let indices = take_table(bytes, &mut off, index_bytes)?;
915 off = (off + 3) & !3;
916 let poses = bytes.get(off..).ok_or(ParseError::Truncated)?;
917 if poses.is_empty() || poses.len() % POSE_RECORD_SIZE_V4 != 0 {
918 return Err(ParseError::InvalidAnimationLayout);
919 }
920 let records = poses.len() / POSE_RECORD_SIZE_V4;
921 for index in indices.chunks_exact(2) {
922 if read_u16(index, 0) as usize >= records {
923 return Err(ParseError::InvalidAnimationLayout);
924 }
925 }
926 (indices, poses)
927 } else {
928 let pose_bytes = pose_count
929 .checked_mul(pose_record_size)
930 .ok_or(ParseError::TableOverflow)?;
931 let poses = take_table(bytes, &mut off, pose_bytes)?;
932 if off != bytes.len() {
933 return Err(ParseError::InvalidAnimationLayout);
934 }
935 (&[][..], poses)
936 };
937
938 Ok(Self {
939 pose_indices,
940 poses,
941 joint_count,
942 frame_count,
943 sample_rate_hz,
944 pose_record_size,
945 translation_shift,
946 })
947 }
948
949 #[inline]
951 pub fn joint_count(&self) -> u16 {
952 self.joint_count
953 }
954
955 #[inline]
957 pub fn frame_count(&self) -> u16 {
958 self.frame_count
959 }
960
961 #[inline]
963 pub fn sample_rate_hz(&self) -> u16 {
964 self.sample_rate_hz
965 }
966
967 #[inline]
969 pub fn pose_record_size(&self) -> usize {
970 self.pose_record_size
971 }
972
973 #[inline]
975 pub fn translation_shift(&self) -> u8 {
976 self.translation_shift
977 }
978
979 #[inline]
985 pub fn phase_step_q12(&self, playback_hz: u16) -> u32 {
986 ((self.sample_rate_hz as u32) << 12) / playback_hz.max(1) as u32
987 }
988
989 #[inline]
995 pub fn phase_at_tick_q12(&self, playback_tick: u32, playback_hz: u16) -> u32 {
996 playback_tick.wrapping_mul(self.phase_step_q12(playback_hz))
997 }
998
999 #[inline]
1004 pub fn phase_at_tick_scaled_q12(
1005 &self,
1006 playback_tick: u32,
1007 playback_hz: u16,
1008 speed_q8: u16,
1009 ) -> u32 {
1010 let step = self.phase_step_q12(playback_hz);
1015 let speed = u32::from(speed_q8);
1016 let scaled_step = (step >> 8)
1017 .wrapping_mul(speed)
1018 .wrapping_add(((step & 0xff) * speed) >> 8);
1019 playback_tick.wrapping_mul(scaled_step)
1020 }
1021
1022 pub fn pose(&self, frame_index: u16, joint_index: u16) -> Option<JointPose> {
1024 if frame_index >= self.frame_count || joint_index >= self.joint_count {
1025 return None;
1026 }
1027 let base = frame_index as usize * self.joint_count as usize * self.pose_record_size;
1028 Some(unsafe { self.pose_at_frame_offset_unchecked(base, joint_index) })
1031 }
1032
1033 #[inline]
1034 fn record_offset(&self, logical_offset: usize) -> usize {
1035 if self.pose_indices.is_empty() {
1036 logical_offset
1037 } else {
1038 usize::from(read_u16(self.pose_indices, logical_offset >> 3)) << 4
1041 }
1042 }
1043
1044 #[inline]
1053 unsafe fn pose_at_frame_offset_unchecked(
1054 &self,
1055 frame_offset: usize,
1056 joint_index: u16,
1057 ) -> JointPose {
1058 let base = self.record_offset(frame_offset + joint_index as usize * self.pose_record_size);
1059 if self.poses.as_ptr() as usize & 1 != 0 {
1060 return unsafe { self.pose_at_byte_offset_unaligned(base) };
1063 }
1064 if self.pose_record_size == psxed_format::animation::POSE_RECORD_SIZE_V4 {
1065 return unsafe { self.pose_v4_unchecked(base) };
1069 }
1070 if self.pose_record_size == psxed_format::animation::POSE_RECORD_SIZE_V3 {
1071 return unsafe { self.pose_v3_unchecked(base) };
1075 }
1076 let matrix = unsafe { read_pose_matrix_aligned_unchecked(self.poses, base) };
1083 let off = 18;
1084 let translation = if self.pose_record_size == psxed_format::animation::POSE_RECORD_SIZE {
1085 Vec3I32::new(
1089 decode_packed_translation(
1090 unsafe { read_i16_aligned_unchecked(self.poses, base + off) },
1092 self.translation_shift,
1093 ),
1094 decode_packed_translation(
1095 unsafe { read_i16_aligned_unchecked(self.poses, base + off + 2) },
1097 self.translation_shift,
1098 ),
1099 decode_packed_translation(
1100 unsafe { read_i16_aligned_unchecked(self.poses, base + off + 4) },
1102 self.translation_shift,
1103 ),
1104 )
1105 } else {
1106 Vec3I32::new(
1109 unsafe { read_i32_unchecked(self.poses, base + off) },
1111 unsafe { read_i32_unchecked(self.poses, base + off + 4) },
1113 unsafe { read_i32_unchecked(self.poses, base + off + 8) },
1115 )
1116 };
1117 JointPose {
1118 matrix,
1119 translation,
1120 }
1121 }
1122
1123 unsafe fn pose_v3_unchecked(&self, base: usize) -> JointPose {
1134 let record = unsafe { self.poses.as_ptr().add(base) };
1137 let (matrix, packed_translation) = if record as usize & 3 == 0 {
1138 unsafe { read_pose_v3_word_aligned_unchecked(self.poses, base) }
1141 } else {
1142 let matrix = unsafe { read_pose_matrix_q11_unchecked(self.poses, base) };
1144 let off = psxed_format::animation::POSE_ROTATION_BLOCK_SIZE_V3;
1145 let translation = Vec3I16::new(
1146 unsafe { read_i16_unchecked(self.poses, base + off) },
1148 unsafe { read_i16_unchecked(self.poses, base + off + 2) },
1150 unsafe { read_i16_unchecked(self.poses, base + off + 4) },
1152 );
1153 (matrix, translation)
1154 };
1155 let translation = Vec3I32::new(
1156 decode_packed_translation(packed_translation.x, self.translation_shift),
1157 decode_packed_translation(packed_translation.y, self.translation_shift),
1158 decode_packed_translation(packed_translation.z, self.translation_shift),
1159 );
1160 JointPose {
1161 matrix,
1162 translation,
1163 }
1164 }
1165
1166 unsafe fn pose_v4_unchecked(&self, base: usize) -> JointPose {
1172 let record = unsafe { self.poses.as_ptr().add(base) };
1175 let (matrix, packed_translation) = if record as usize & 3 == 0 {
1176 unsafe { read_pose_v4_word_aligned_unchecked(self.poses, base) }
1179 } else {
1180 let matrix = unsafe { read_pose_matrix_q11_cross_unchecked(self.poses, base) };
1182 let off = psxed_format::animation::POSE_ROTATION_BLOCK_SIZE_V4;
1183 let translation = Vec3I16::new(
1184 unsafe { read_i16_unchecked(self.poses, base + off) },
1186 unsafe { read_i16_unchecked(self.poses, base + off + 2) },
1188 unsafe { read_i16_unchecked(self.poses, base + off + 4) },
1190 );
1191 (matrix, translation)
1192 };
1193 JointPose {
1194 matrix,
1195 translation: Vec3I32::new(
1196 decode_packed_translation(packed_translation.x, self.translation_shift),
1197 decode_packed_translation(packed_translation.y, self.translation_shift),
1198 decode_packed_translation(packed_translation.z, self.translation_shift),
1199 ),
1200 }
1201 }
1202
1203 #[inline(never)]
1210 unsafe fn pose_at_byte_offset_unaligned(&self, base: usize) -> JointPose {
1211 if self.pose_record_size == psxed_format::animation::POSE_RECORD_SIZE_V4 {
1212 return unsafe { self.pose_v4_unchecked(base) };
1214 }
1215 if self.pose_record_size == psxed_format::animation::POSE_RECORD_SIZE_V3 {
1216 return unsafe { self.pose_v3_unchecked(base) };
1218 }
1219 let matrix = unsafe { read_pose_matrix_unchecked(self.poses, base) };
1223 let off = 18;
1224 let translation = if self.pose_record_size == psxed_format::animation::POSE_RECORD_SIZE {
1225 Vec3I32::new(
1226 decode_packed_translation(
1227 unsafe { read_i16_unchecked(self.poses, base + off) },
1229 self.translation_shift,
1230 ),
1231 decode_packed_translation(
1232 unsafe { read_i16_unchecked(self.poses, base + off + 2) },
1234 self.translation_shift,
1235 ),
1236 decode_packed_translation(
1237 unsafe { read_i16_unchecked(self.poses, base + off + 4) },
1239 self.translation_shift,
1240 ),
1241 )
1242 } else {
1243 Vec3I32::new(
1244 unsafe { read_i32_unchecked(self.poses, base + off) },
1246 unsafe { read_i32_unchecked(self.poses, base + off + 4) },
1248 unsafe { read_i32_unchecked(self.poses, base + off + 8) },
1250 )
1251 };
1252 JointPose {
1253 matrix,
1254 translation,
1255 }
1256 }
1257
1258 #[inline]
1259 fn packed_pose_at_frame_offset(
1260 &self,
1261 frame_offset: usize,
1262 joint_index: u16,
1263 ) -> Option<GteJointPose> {
1264 let v3 = self.pose_record_size == psxed_format::animation::POSE_RECORD_SIZE_V3;
1265 let v4 = self.pose_record_size == psxed_format::animation::POSE_RECORD_SIZE_V4;
1266 if joint_index >= self.joint_count
1267 || !(v3 || v4 || self.pose_record_size == psxed_format::animation::POSE_RECORD_SIZE)
1268 {
1269 return None;
1270 }
1271 let base = frame_offset.checked_add(joint_index as usize * self.pose_record_size)?;
1272 let base = self.record_offset(base);
1273 let bytes = self.poses.get(base..base + self.pose_record_size)?;
1274 if v4 {
1275 let (matrix, translation) = if bytes.as_ptr() as usize & 3 == 0 {
1276 unsafe { read_pose_v4_word_aligned_unchecked(bytes, 0) }
1279 } else {
1280 let matrix = unsafe { read_pose_matrix_q11_cross_unchecked(bytes, 0) };
1282 let off = psxed_format::animation::POSE_ROTATION_BLOCK_SIZE_V4;
1283 let translation = Vec3I16::new(
1284 read_i16(bytes, off),
1285 read_i16(bytes, off + 2),
1286 read_i16(bytes, off + 4),
1287 );
1288 (matrix, translation)
1289 };
1290 return Some(GteJointPose {
1291 matrix,
1292 translation,
1293 translation_shift: self.translation_shift,
1294 });
1295 }
1296 if v3 {
1297 let (matrix, translation) = if bytes.as_ptr() as usize & 3 == 0 {
1301 unsafe { read_pose_v3_word_aligned_unchecked(bytes, 0) }
1303 } else {
1304 let matrix = unsafe { read_pose_matrix_q11_unchecked(bytes, 0) };
1306 let off = psxed_format::animation::POSE_ROTATION_BLOCK_SIZE_V3;
1307 let translation = Vec3I16::new(
1308 read_i16(bytes, off),
1309 read_i16(bytes, off + 2),
1310 read_i16(bytes, off + 4),
1311 );
1312 (matrix, translation)
1313 };
1314 return Some(GteJointPose {
1315 matrix,
1316 translation,
1317 translation_shift: self.translation_shift,
1318 });
1319 }
1320 Some(GteJointPose {
1321 matrix: read_pose_matrix(bytes),
1322 translation: Vec3I16::new(
1323 read_i16(bytes, 18),
1324 read_i16(bytes, 20),
1325 read_i16(bytes, 22),
1326 ),
1327 translation_shift: self.translation_shift,
1328 })
1329 }
1330
1331 pub fn pose_looped_q12(&self, frame_q12: u32, joint_index: u16) -> Option<JointPose> {
1340 self.looped_pose_sample_q12(frame_q12)?.pose(joint_index)
1341 }
1342
1343 pub fn looped_pose_sample_q12(&self, frame_q12: u32) -> Option<AnimationPoseSample<'a>> {
1350 if self.frame_count == 0 {
1351 return None;
1352 }
1353
1354 let cycle_frames = self.frame_count.saturating_sub(1).max(1);
1355 let base_frame = ((frame_q12 >> 12) % cycle_frames as u32) as u16;
1356 let next_frame = if cycle_frames <= 1 || base_frame + 1 >= cycle_frames {
1357 0
1358 } else {
1359 base_frame + 1
1360 };
1361 Some(AnimationPoseSample {
1362 animation: *self,
1363 base_frame,
1364 next_frame,
1365 base_frame_offset: base_frame as usize
1366 * self.joint_count as usize
1367 * self.pose_record_size,
1368 next_frame_offset: next_frame as usize
1369 * self.joint_count as usize
1370 * self.pose_record_size,
1371 alpha_q12: (frame_q12 & 0x0fff) as u16,
1372 })
1373 }
1374}
1375
1376#[derive(Copy, Clone, Debug)]
1378pub struct AnimationPoseSample<'a> {
1379 animation: Animation<'a>,
1380 base_frame: u16,
1381 next_frame: u16,
1382 base_frame_offset: usize,
1383 next_frame_offset: usize,
1384 alpha_q12: u16,
1385}
1386
1387impl AnimationPoseSample<'_> {
1388 #[inline]
1390 pub fn pose(&self, joint_index: u16) -> Option<JointPose> {
1391 if joint_index >= self.animation.joint_count {
1392 return None;
1393 }
1394 if self.alpha_q12 == 0 || self.base_frame == self.next_frame {
1395 return Some(unsafe {
1398 self.animation
1399 .pose_at_frame_offset_unchecked(self.base_frame_offset, joint_index)
1400 });
1401 }
1402
1403 if self.animation.pose_record_size == psxed_format::animation::POSE_RECORD_SIZE_V4 {
1415 let pool = self.animation.poses.as_ptr();
1416 if pool as usize & 3 == 0 {
1417 let joint_offset =
1418 joint_index as usize * psxed_format::animation::POSE_RECORD_SIZE_V4;
1419 return Some(unsafe {
1424 lerp_v4_pair_word_aligned(
1425 pool.add(
1426 self.animation
1427 .record_offset(self.base_frame_offset + joint_offset),
1428 ),
1429 pool.add(
1430 self.animation
1431 .record_offset(self.next_frame_offset + joint_offset),
1432 ),
1433 self.animation.translation_shift,
1434 self.alpha_q12,
1435 )
1436 });
1437 }
1438 }
1439
1440 let a = unsafe {
1443 self.animation
1444 .pose_at_frame_offset_unchecked(self.base_frame_offset, joint_index)
1445 };
1446 let b = unsafe {
1449 self.animation
1450 .pose_at_frame_offset_unchecked(self.next_frame_offset, joint_index)
1451 };
1452 Some(lerp_pose_q12(a, b, self.alpha_q12))
1453 }
1454
1455 #[inline]
1460 pub fn gte_pose(&self, joint_index: u16) -> Option<GteJointPose> {
1461 if self.alpha_q12 == 0 || self.base_frame == self.next_frame {
1462 return self
1463 .animation
1464 .packed_pose_at_frame_offset(self.base_frame_offset, joint_index);
1465 }
1466
1467 let a = self
1468 .animation
1469 .packed_pose_at_frame_offset(self.base_frame_offset, joint_index)?;
1470 let b = self
1471 .animation
1472 .packed_pose_at_frame_offset(self.next_frame_offset, joint_index)?;
1473 Some(lerp_gte_pose_q12(a, b, self.alpha_q12))
1474 }
1475}
1476
1477#[derive(Copy, Clone, Debug)]
1490pub struct ModelPoseBlend<'a> {
1491 pub sample: AnimationPoseSample<'a>,
1493 pub alpha_q12: u16,
1495}
1496
1497impl ModelPoseBlend<'_> {
1498 #[inline]
1503 pub fn blend_toward(&self, primary: JointPose, joint_index: u16) -> JointPose {
1504 if self.alpha_q12 >= 1 << 12 {
1505 return primary;
1506 }
1507 match self.sample.pose(joint_index) {
1508 Some(from) => lerp_pose_q12(from, primary, self.alpha_q12),
1509 None => primary,
1510 }
1511 }
1512}
1513
1514#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1516pub struct JointPose {
1517 pub matrix: [[i16; 3]; 3],
1519 pub translation: Vec3I32,
1521}
1522
1523#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1525pub struct GteJointPose {
1526 pub matrix: [[i16; 3]; 3],
1528 pub translation: Vec3I16,
1530 pub translation_shift: u8,
1532}
1533
1534#[derive(Copy, Clone, Debug)]
1536pub struct Audio<'a> {
1537 adpcm: &'a [u8],
1538 flags: u16,
1539 sample_rate_hz: u32,
1540 sample_count: u32,
1541 adpcm_block_count: u32,
1542 loop_start_block: u32,
1543}
1544
1545impl<'a> Audio<'a> {
1546 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, ParseError> {
1548 use psxed_format::audio::{self, AudioHeader};
1549
1550 if bytes.len() < psxed_format::AssetHeader::SIZE {
1551 return Err(ParseError::Truncated);
1552 }
1553 let magic = [bytes[0], bytes[1], bytes[2], bytes[3]];
1554 if magic != audio::MAGIC {
1555 return Err(ParseError::WrongMagic);
1556 }
1557 let version = read_u16(bytes, 4);
1558 if version != audio::VERSION {
1559 return Err(ParseError::UnsupportedVersion(version));
1560 }
1561 let flags = read_u16(bytes, 6);
1562 let payload_len = read_u32(bytes, 8);
1563 let payload_start = psxed_format::AssetHeader::SIZE;
1564 let actual_payload = bytes.len().saturating_sub(payload_start);
1565 if payload_len as usize != actual_payload {
1566 return Err(ParseError::InvalidPayloadLen {
1567 declared: payload_len,
1568 actual: actual_payload,
1569 });
1570 }
1571 if actual_payload < AudioHeader::SIZE {
1572 return Err(ParseError::Truncated);
1573 }
1574
1575 let ah = &bytes[payload_start..payload_start + AudioHeader::SIZE];
1576 let codec = ah[0];
1577 let channel_count = ah[1];
1578 let sample_rate_hz = read_u32(ah, 4);
1579 let sample_count = read_u32(ah, 8);
1580 let adpcm_block_count = read_u32(ah, 12);
1581 let loop_start_block = read_u32(ah, 16);
1582 let decoded_capacity = adpcm_block_count
1583 .checked_mul(28)
1584 .ok_or(ParseError::TableOverflow)?;
1585 let min_samples_for_blocks = adpcm_block_count.saturating_sub(1) * 28 + 1;
1586 if codec != audio::CODEC_SPU_ADPCM
1587 || channel_count != 1
1588 || flags & audio::flags::MONO == 0
1589 || flags & audio::flags::ONE_SHOT == 0
1590 || sample_rate_hz == 0
1591 || sample_count == 0
1592 || adpcm_block_count == 0
1593 || sample_count > decoded_capacity
1594 || sample_count < min_samples_for_blocks
1595 || loop_start_block != AudioHeader::NO_LOOP
1596 {
1597 return Err(ParseError::InvalidAudioLayout);
1598 }
1599
1600 let adpcm_bytes = (adpcm_block_count as usize)
1601 .checked_mul(16)
1602 .ok_or(ParseError::TableOverflow)?;
1603 let adpcm_start = payload_start + AudioHeader::SIZE;
1604 let adpcm_end = adpcm_start
1605 .checked_add(adpcm_bytes)
1606 .ok_or(ParseError::TableOverflow)?;
1607 if adpcm_end != bytes.len() {
1608 return Err(ParseError::InvalidAudioLayout);
1609 }
1610
1611 Ok(Self {
1612 adpcm: &bytes[adpcm_start..adpcm_end],
1613 flags,
1614 sample_rate_hz,
1615 sample_count,
1616 adpcm_block_count,
1617 loop_start_block,
1618 })
1619 }
1620
1621 #[inline]
1623 pub fn sample_rate_hz(&self) -> u32 {
1624 self.sample_rate_hz
1625 }
1626
1627 #[inline]
1629 pub fn sample_count(&self) -> u32 {
1630 self.sample_count
1631 }
1632
1633 #[inline]
1635 pub fn adpcm_block_count(&self) -> u32 {
1636 self.adpcm_block_count
1637 }
1638
1639 #[inline]
1641 pub fn loop_start_block(&self) -> Option<u32> {
1642 if self.loop_start_block == psxed_format::audio::AudioHeader::NO_LOOP {
1643 None
1644 } else {
1645 Some(self.loop_start_block)
1646 }
1647 }
1648
1649 #[inline]
1651 pub fn adpcm_bytes(&self) -> &'a [u8] {
1652 self.adpcm
1653 }
1654
1655 #[inline]
1657 pub fn is_one_shot(&self) -> bool {
1658 self.flags & psxed_format::audio::flags::ONE_SHOT != 0
1659 }
1660}
1661
1662#[inline]
1663fn lerp_pose_q12(a: JointPose, b: JointPose, alpha_q12: u16) -> JointPose {
1664 let mut matrix = [[0i16; 3]; 3];
1665 let mut col = 0;
1666 while col < 3 {
1667 let mut row = 0;
1668 while row < 3 {
1669 matrix[col][row] = lerp_i16_q12(a.matrix[col][row], b.matrix[col][row], alpha_q12);
1670 row += 1;
1671 }
1672 col += 1;
1673 }
1674
1675 JointPose {
1676 matrix,
1677 translation: Vec3I32::new(
1678 lerp_i32_q12(a.translation.x, b.translation.x, alpha_q12),
1679 lerp_i32_q12(a.translation.y, b.translation.y, alpha_q12),
1680 lerp_i32_q12(a.translation.z, b.translation.z, alpha_q12),
1681 ),
1682 }
1683}
1684
1685#[inline]
1686fn lerp_gte_pose_q12(a: GteJointPose, b: GteJointPose, alpha_q12: u16) -> GteJointPose {
1687 let mut matrix = [[0i16; 3]; 3];
1688 let mut col = 0;
1689 while col < 3 {
1690 let mut row = 0;
1691 while row < 3 {
1692 matrix[col][row] = lerp_i16_q12(a.matrix[col][row], b.matrix[col][row], alpha_q12);
1693 row += 1;
1694 }
1695 col += 1;
1696 }
1697
1698 GteJointPose {
1699 matrix,
1700 translation: Vec3I16::new(
1701 lerp_i16_q12(a.translation.x, b.translation.x, alpha_q12),
1702 lerp_i16_q12(a.translation.y, b.translation.y, alpha_q12),
1703 lerp_i16_q12(a.translation.z, b.translation.z, alpha_q12),
1704 ),
1705 translation_shift: a.translation_shift,
1706 }
1707}
1708
1709#[inline(always)]
1717fn decode_q11_element(raw: u16) -> i16 {
1718 decode_q11_element_wide(raw) as i16
1719}
1720
1721#[inline(always)]
1728fn decode_q11_element_wide(raw: u16) -> i32 {
1729 #[cfg(target_arch = "mips")]
1730 {
1731 let decoded: u32;
1732 unsafe {
1739 core::arch::asm!(
1740 ".set push",
1741 ".set noat",
1742 "xori {scratch}, {decoded}, 0x07ff",
1743 "sltiu {scratch}, {scratch}, 1",
1744 "sll {decoded}, {decoded}, 20",
1745 "sra {decoded}, {decoded}, 19",
1746 "sll {scratch}, {scratch}, 1",
1747 "addu {decoded}, {decoded}, {scratch}",
1748 ".set pop",
1749 decoded = inlateout(reg) raw as u32 => decoded,
1750 scratch = lateout(reg) _,
1751 options(nomem, nostack, preserves_flags),
1752 );
1753 }
1754 decoded as i32
1755 }
1756
1757 #[cfg(not(target_arch = "mips"))]
1758 {
1759 let signed = ((raw << 4) as i16) >> 4;
1760 signed
1761 .wrapping_shl(1)
1762 .wrapping_add((((raw & 0x0FFF) == 0x07FF) as i16) << 1) as i32
1763 }
1764}
1765
1766#[inline]
1775unsafe fn read_pose_matrix_q11_unchecked(bytes: &[u8], offset: usize) -> [[i16; 3]; 3] {
1776 let mut flat = [0i16; 9];
1777 let mut pair = 0;
1778 while pair < 4 {
1779 let o = offset + pair * 3;
1780 let packed = unsafe {
1784 (bytes.as_ptr().add(o).read() as u32)
1785 | ((bytes.as_ptr().add(o + 1).read() as u32) << 8)
1786 | ((bytes.as_ptr().add(o + 2).read() as u32) << 16)
1787 };
1788 flat[pair * 2] = decode_q11_element((packed & 0x0FFF) as u16);
1789 flat[pair * 2 + 1] = decode_q11_element(((packed >> 12) & 0x0FFF) as u16);
1790 pair += 1;
1791 }
1792 let last = unsafe {
1795 (bytes.as_ptr().add(offset + 12).read() as u16)
1796 | ((bytes.as_ptr().add(offset + 13).read() as u16) << 8)
1797 };
1798 flat[8] = decode_q11_element(last & 0x0FFF);
1799 [
1800 [flat[0], flat[1], flat[2]],
1801 [flat[3], flat[4], flat[5]],
1802 [flat[6], flat[7], flat[8]],
1803 ]
1804}
1805
1806const V4_CROSS_LIMIT: i32 = 2 * 4096 * 4096;
1814
1815#[inline(always)]
1821fn cross_round_q12(value: i32) -> i32 {
1822 debug_assert!((-V4_CROSS_LIMIT..=V4_CROSS_LIMIT).contains(&value));
1823 if value >= 0 {
1824 (value + (1 << 11)) >> 12
1825 } else {
1826 -((-value + (1 << 11)) >> 12)
1827 }
1828}
1829
1830#[inline(always)]
1848fn v4_third_basis_q12(first: [i32; 3], second: [i32; 3], correction_byte: u8) -> [i32; 3] {
1849 let ([f0, f1, f2], [s0, s1, s2]) = (first, second);
1850 let x = cross_round_q12(f1 * s2 - f2 * s1);
1851 let y = cross_round_q12(f2 * s0 - f0 * s2);
1852 let z = cross_round_q12(f0 * s1 - f1 * s0);
1853 let correction = ((((correction_byte >> 2) << 2) as i8) >> 2) as i32;
1854 let (x, y, z) = match correction_byte & 0x03 {
1857 0 => (x + correction, y, z),
1858 1 => (x, y + correction, z),
1859 2 => (x, y, z + correction),
1860 _ => (x, y, z),
1861 };
1862 [
1863 x.clamp(-4096, 4096),
1864 y.clamp(-4096, 4096),
1865 z.clamp(-4096, 4096),
1866 ]
1867}
1868
1869#[inline]
1875unsafe fn read_pose_matrix_q11_cross_unchecked(bytes: &[u8], offset: usize) -> [[i16; 3]; 3] {
1876 let mut flat = [0i32; 6];
1877 let mut pair = 0usize;
1878 while pair < 3 {
1879 let o = offset + pair * 3;
1880 let packed = unsafe {
1883 (bytes.as_ptr().add(o).read() as u32)
1884 | ((bytes.as_ptr().add(o + 1).read() as u32) << 8)
1885 | ((bytes.as_ptr().add(o + 2).read() as u32) << 16)
1886 };
1887 flat[pair * 2] = decode_q11_element_wide((packed & 0x0fff) as u16);
1888 flat[pair * 2 + 1] = decode_q11_element_wide(((packed >> 12) & 0x0fff) as u16);
1889 pair += 1;
1890 }
1891 let first = [flat[0], flat[1], flat[2]];
1892 let second = [flat[3], flat[4], flat[5]];
1893 let correction = unsafe { bytes.as_ptr().add(offset + 9).read() };
1895 let third = v4_third_basis_q12(first, second, correction);
1896 [
1897 narrow_q12_row(first),
1898 narrow_q12_row(second),
1899 narrow_q12_row(third),
1900 ]
1901}
1902
1903#[inline]
1908unsafe fn read_pose_v4_word_aligned_unchecked(
1909 bytes: &[u8],
1910 offset: usize,
1911) -> ([[i16; 3]; 3], Vec3I16) {
1912 unsafe { read_pose_v4_word_aligned_ptr(bytes.as_ptr().add(offset)) }
1915}
1916
1917#[inline(always)]
1924unsafe fn read_pose_v4_word_aligned_ptr(record: *const u8) -> ([[i16; 3]; 3], Vec3I16) {
1925 let (rows, translation) = unsafe { read_pose_v4_rows_word_aligned(record) };
1927 (
1928 [
1929 narrow_q12_row(rows[0]),
1930 narrow_q12_row(rows[1]),
1931 narrow_q12_row(rows[2]),
1932 ],
1933 translation,
1934 )
1935}
1936
1937#[inline(always)]
1940fn narrow_q12_row(row: [i32; 3]) -> [i16; 3] {
1941 [row[0] as i16, row[1] as i16, row[2] as i16]
1942}
1943
1944#[inline(always)]
1956unsafe fn read_pose_v4_rows_word_aligned(record: *const u8) -> ([[i32; 3]; 3], Vec3I16) {
1957 debug_assert_eq!(record as usize & 3, 0);
1958 let words = record.cast::<u32>();
1959 let w0 = u32::from_le(unsafe { words.add(0).read() });
1963 let w1 = u32::from_le(unsafe { words.add(1).read() });
1965 let w2 = u32::from_le(unsafe { words.add(2).read() });
1967 let w3 = u32::from_le(unsafe { words.add(3).read() });
1969
1970 let p0 = w0 & 0x00ff_ffff;
1971 let p1 = (w0 >> 24) | ((w1 & 0x0000_ffff) << 8);
1972 let p2 = (w1 >> 16) | ((w2 & 0x0000_00ff) << 16);
1973 let first = [
1974 decode_q11_element_wide((p0 & 0x0fff) as u16),
1975 decode_q11_element_wide(((p0 >> 12) & 0x0fff) as u16),
1976 decode_q11_element_wide((p1 & 0x0fff) as u16),
1977 ];
1978 let second = [
1979 decode_q11_element_wide(((p1 >> 12) & 0x0fff) as u16),
1980 decode_q11_element_wide((p2 & 0x0fff) as u16),
1981 decode_q11_element_wide(((p2 >> 12) & 0x0fff) as u16),
1982 ];
1983 let third = v4_third_basis_q12(first, second, ((w2 >> 8) & 0xff) as u8);
1984 let translation = Vec3I16::new(
1985 (w2 >> 16) as u16 as i16,
1986 w3 as u16 as i16,
1987 (w3 >> 16) as u16 as i16,
1988 );
1989 ([first, second, third], translation)
1990}
1991
1992#[inline]
2004unsafe fn lerp_v4_pair_word_aligned(
2005 a_record: *const u8,
2006 b_record: *const u8,
2007 translation_shift: u8,
2008 alpha_q12: u16,
2009) -> JointPose {
2010 let (a_rows, a_packed) = unsafe { read_pose_v4_rows_word_aligned(a_record) };
2013 let (b_rows, b_packed) = unsafe { read_pose_v4_rows_word_aligned(b_record) };
2015
2016 let mut matrix = [[0i16; 3]; 3];
2017 let mut col = 0;
2018 while col < 3 {
2019 let mut row = 0;
2020 while row < 3 {
2021 matrix[col][row] = lerp_q12_wide_to_i16(a_rows[col][row], b_rows[col][row], alpha_q12);
2022 row += 1;
2023 }
2024 col += 1;
2025 }
2026
2027 JointPose {
2028 matrix,
2029 translation: Vec3I32::new(
2030 lerp_packed_translation_q12(a_packed.x, b_packed.x, translation_shift, alpha_q12),
2031 lerp_packed_translation_q12(a_packed.y, b_packed.y, translation_shift, alpha_q12),
2032 lerp_packed_translation_q12(a_packed.z, b_packed.z, translation_shift, alpha_q12),
2033 ),
2034 }
2035}
2036
2037#[inline(always)]
2052fn lerp_packed_translation_q12(a: i16, b: i16, shift: u8, alpha_q12: u16) -> i32 {
2053 debug_assert!(shift <= 15);
2054 debug_assert!(alpha_q12 < 4096);
2055 let base = (a as i32) << shift;
2056 let delta = ((b as i32) << shift) - base;
2057 let alpha = alpha_q12 as i32;
2060 let whole = delta >> 12;
2061 let frac = delta - (whole << 12);
2062 base + whole * alpha + ((frac * alpha) >> 12)
2063}
2064
2065#[inline]
2077unsafe fn read_pose_v3_word_aligned_unchecked(
2078 bytes: &[u8],
2079 offset: usize,
2080) -> ([[i16; 3]; 3], Vec3I16) {
2081 let record = unsafe { bytes.as_ptr().add(offset) };
2084 debug_assert_eq!(record as usize & 3, 0);
2085 let words = record.cast::<u32>();
2086 let w0 = u32::from_le(unsafe { words.add(0).read() });
2090 let w1 = u32::from_le(unsafe { words.add(1).read() });
2092 let w2 = u32::from_le(unsafe { words.add(2).read() });
2094 let w3 = u32::from_le(unsafe { words.add(3).read() });
2096 let w4 = u32::from_le(unsafe { words.add(4).read() });
2098
2099 let p0 = w0 & 0x00ff_ffff;
2100 let p1 = (w0 >> 24) | ((w1 & 0x0000_ffff) << 8);
2101 let p2 = (w1 >> 16) | ((w2 & 0x0000_00ff) << 16);
2102 let p3 = (w2 >> 8) & 0x00ff_ffff;
2103 let matrix = [
2104 [
2105 decode_q11_element((p0 & 0x0fff) as u16),
2106 decode_q11_element(((p0 >> 12) & 0x0fff) as u16),
2107 decode_q11_element((p1 & 0x0fff) as u16),
2108 ],
2109 [
2110 decode_q11_element(((p1 >> 12) & 0x0fff) as u16),
2111 decode_q11_element((p2 & 0x0fff) as u16),
2112 decode_q11_element(((p2 >> 12) & 0x0fff) as u16),
2113 ],
2114 [
2115 decode_q11_element((p3 & 0x0fff) as u16),
2116 decode_q11_element(((p3 >> 12) & 0x0fff) as u16),
2117 decode_q11_element((w3 & 0x0fff) as u16),
2118 ],
2119 ];
2120 let translation = Vec3I16::new(
2121 (w3 >> 16) as u16 as i16,
2122 w4 as u16 as i16,
2123 (w4 >> 16) as u16 as i16,
2124 );
2125 (matrix, translation)
2126}
2127
2128fn read_pose_matrix(bytes: &[u8]) -> [[i16; 3]; 3] {
2129 [
2130 [read_i16(bytes, 0), read_i16(bytes, 2), read_i16(bytes, 4)],
2131 [read_i16(bytes, 6), read_i16(bytes, 8), read_i16(bytes, 10)],
2132 [
2133 read_i16(bytes, 12),
2134 read_i16(bytes, 14),
2135 read_i16(bytes, 16),
2136 ],
2137 ]
2138}
2139
2140#[inline]
2145unsafe fn read_pose_matrix_unchecked(bytes: &[u8], offset: usize) -> [[i16; 3]; 3] {
2146 [
2147 [
2148 unsafe { read_i16_unchecked(bytes, offset) },
2150 unsafe { read_i16_unchecked(bytes, offset + 2) },
2152 unsafe { read_i16_unchecked(bytes, offset + 4) },
2154 ],
2155 [
2156 unsafe { read_i16_unchecked(bytes, offset + 6) },
2158 unsafe { read_i16_unchecked(bytes, offset + 8) },
2160 unsafe { read_i16_unchecked(bytes, offset + 10) },
2162 ],
2163 [
2164 unsafe { read_i16_unchecked(bytes, offset + 12) },
2166 unsafe { read_i16_unchecked(bytes, offset + 14) },
2168 unsafe { read_i16_unchecked(bytes, offset + 16) },
2170 ],
2171 ]
2172}
2173
2174#[inline]
2180unsafe fn read_pose_matrix_aligned_unchecked(bytes: &[u8], offset: usize) -> [[i16; 3]; 3] {
2181 [
2182 [
2183 unsafe { read_i16_aligned_unchecked(bytes, offset) },
2185 unsafe { read_i16_aligned_unchecked(bytes, offset + 2) },
2187 unsafe { read_i16_aligned_unchecked(bytes, offset + 4) },
2189 ],
2190 [
2191 unsafe { read_i16_aligned_unchecked(bytes, offset + 6) },
2193 unsafe { read_i16_aligned_unchecked(bytes, offset + 8) },
2195 unsafe { read_i16_aligned_unchecked(bytes, offset + 10) },
2197 ],
2198 [
2199 unsafe { read_i16_aligned_unchecked(bytes, offset + 12) },
2201 unsafe { read_i16_aligned_unchecked(bytes, offset + 14) },
2203 unsafe { read_i16_aligned_unchecked(bytes, offset + 16) },
2205 ],
2206 ]
2207}
2208
2209#[inline]
2210fn decode_packed_translation(value: i16, shift: u8) -> i32 {
2211 debug_assert!(shift <= 15);
2212 (value as i32) * (1i32 << shift)
2213}
2214
2215#[inline(always)]
2221fn lerp_q12_wide_to_i16(a: i32, b: i32, alpha_q12: u16) -> i16 {
2222 debug_assert!(alpha_q12 < 4096);
2223 (a + (((b - a) * alpha_q12 as i32) >> 12)) as i16
2224}
2225
2226#[inline]
2227fn lerp_i16_q12(a: i16, b: i16, alpha_q12: u16) -> i16 {
2228 debug_assert!(alpha_q12 < 4096);
2229 let value = a as i32 + (((b as i32 - a as i32) * alpha_q12 as i32) >> 12);
2230 value as i16
2231}
2232
2233#[inline]
2234fn lerp_i32_q12(a: i32, b: i32, alpha_q12: u16) -> i32 {
2235 let delta = b.saturating_sub(a);
2236 a.saturating_add(scale_i32_q12(delta, alpha_q12 as i32))
2237}
2238
2239#[inline]
2240fn scale_i32_q12(value: i32, scale_q12: i32) -> i32 {
2241 let whole = value >> 12;
2242 let frac = value - (whole << 12);
2243 whole.saturating_mul(scale_q12) + ((frac * scale_q12) >> 12)
2244}
2245
2246#[derive(Copy, Clone, Debug)]
2255pub struct Texture<'a> {
2256 pixel_data: &'a [u8],
2259 clut_data: &'a [u8],
2261 width_px: u16,
2262 height_px: u16,
2263 depth: psxed_format::texture::Depth,
2264 clut_entries: u16,
2265 flags: u16,
2266}
2267
2268impl<'a> Texture<'a> {
2269 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, ParseError> {
2273 use psxed_format::texture::{Depth, TextureHeader, MAGIC, VERSION};
2274
2275 if bytes.len() < psxed_format::AssetHeader::SIZE {
2277 return Err(ParseError::Truncated);
2278 }
2279 let magic = [bytes[0], bytes[1], bytes[2], bytes[3]];
2280 if magic != MAGIC {
2281 return Err(ParseError::WrongMagic);
2282 }
2283 let version = u16::from_le_bytes([bytes[4], bytes[5]]);
2284 if version != VERSION {
2285 return Err(ParseError::UnsupportedVersion(version));
2286 }
2287 let flags = u16::from_le_bytes([bytes[6], bytes[7]]);
2288 let payload_len = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
2289 let payload_start = psxed_format::AssetHeader::SIZE;
2290 let actual_payload = bytes.len().saturating_sub(payload_start);
2291 if (payload_len as usize) != actual_payload {
2292 return Err(ParseError::InvalidPayloadLen {
2293 declared: payload_len,
2294 actual: actual_payload,
2295 });
2296 }
2297
2298 if actual_payload < TextureHeader::SIZE {
2300 return Err(ParseError::Truncated);
2301 }
2302 let th = &bytes[payload_start..];
2303 let depth = Depth::from_byte(th[0]).ok_or(ParseError::TableOverflow)?;
2304 let width_px = u16::from_le_bytes([th[2], th[3]]);
2306 let height_px = u16::from_le_bytes([th[4], th[5]]);
2307 let clut_entries = u16::from_le_bytes([th[6], th[7]]);
2308 let pixel_bytes = u32::from_le_bytes([th[8], th[9], th[10], th[11]]) as usize;
2309 let clut_bytes = u32::from_le_bytes([th[12], th[13], th[14], th[15]]) as usize;
2310
2311 let mut off = payload_start + TextureHeader::SIZE;
2315 let pixel_data = take_table(bytes, &mut off, pixel_bytes)?;
2316 let clut_data = take_table(bytes, &mut off, clut_bytes)?;
2317
2318 Ok(Self {
2319 pixel_data,
2320 clut_data,
2321 width_px,
2322 height_px,
2323 depth,
2324 clut_entries,
2325 flags,
2326 })
2327 }
2328
2329 #[inline]
2331 pub fn width(&self) -> u16 {
2332 self.width_px
2333 }
2334
2335 #[inline]
2337 pub fn height(&self) -> u16 {
2338 self.height_px
2339 }
2340
2341 #[inline]
2343 pub fn depth(&self) -> psxed_format::texture::Depth {
2344 self.depth
2345 }
2346
2347 #[inline]
2351 pub fn clut_entries(&self) -> u16 {
2352 self.clut_entries
2353 }
2354
2355 #[inline]
2357 pub fn flags(&self) -> u16 {
2358 self.flags
2359 }
2360
2361 #[inline]
2363 pub fn is_index_zero_transparent(&self) -> bool {
2364 self.flags & psxed_format::texture::flags::INDEX_ZERO_TRANSPARENT != 0
2365 }
2366
2367 #[inline]
2371 pub fn pixel_bytes(&self) -> &'a [u8] {
2372 self.pixel_data
2373 }
2374
2375 #[inline]
2377 pub fn clut_bytes(&self) -> &'a [u8] {
2378 self.clut_data
2379 }
2380
2381 #[inline]
2386 pub fn halfwords_per_row(&self) -> u16 {
2387 psxed_format::texture::TextureHeader::halfwords_per_row(self.depth, self.width_px)
2388 }
2389}
2390
2391#[derive(Copy, Clone, Debug)]
2397pub struct World<'a> {
2398 sectors: &'a [u8],
2399 walls: &'a [u8],
2400 horizontal_overrides: &'a [u8],
2401 surface_lights: &'a [u8],
2402 sector_record_size: usize,
2403 wall_record_size: usize,
2404 horizontal_override_record_size: usize,
2405 width: u16,
2406 depth: u16,
2407 sector_size: i32,
2408 material_count: u16,
2409 wall_count: u16,
2410 horizontal_override_count: u16,
2411 surface_light_count: u16,
2412 ambient_color: [u8; 3],
2413 flags: u8,
2414 static_vertex_lighting: bool,
2415}
2416
2417#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2419pub struct WorldQuadUvs {
2420 corners: [(u8, u8); 4],
2421}
2422
2423impl WorldQuadUvs {
2424 pub const fn new(corners: [(u8, u8); 4]) -> Self {
2426 Self { corners }
2427 }
2428
2429 pub const fn corners(self) -> [(u8, u8); 4] {
2431 self.corners
2432 }
2433}
2434
2435#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2437pub struct WorldSurfaceLight {
2438 vertex_rgb: [[u8; 3]; 4],
2439}
2440
2441impl WorldSurfaceLight {
2442 pub const fn white() -> Self {
2444 Self {
2445 vertex_rgb: [[255, 255, 255]; 4],
2446 }
2447 }
2448
2449 pub const fn new(vertex_rgb: [[u8; 3]; 4]) -> Self {
2451 Self { vertex_rgb }
2452 }
2453
2454 pub const fn vertex_rgb(self) -> [[u8; 3]; 4] {
2456 self.vertex_rgb
2457 }
2458}
2459
2460const WORLD_V1_SECTOR_RECORD_SIZE: usize = 44;
2461const WORLD_V1_WALL_RECORD_SIZE: usize = 24;
2462const WORLD_V2_SECTOR_RECORD_SIZE: usize = 60;
2463const WORLD_V2_WALL_RECORD_SIZE: usize = 32;
2464const WORLD_V3_HEADER_SIZE: usize = 20;
2465const WORLD_V3_SECTOR_RECORD_SIZE: usize = 60;
2466const WORLD_V3_WALL_RECORD_SIZE: usize = 32;
2467const WORLD_V4_HORIZONTAL_OVERRIDE_RECORD_SIZE: usize = 24;
2468
2469impl<'a> World<'a> {
2470 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, ParseError> {
2472 use psxed_format::world::{
2473 WorldHeader, MAGIC, VERSION, VERSION_V1, VERSION_V2, VERSION_V3, VERSION_V4,
2474 };
2475
2476 if bytes.len() < psxed_format::AssetHeader::SIZE {
2477 return Err(ParseError::Truncated);
2478 }
2479 let magic = [bytes[0], bytes[1], bytes[2], bytes[3]];
2480 if magic != MAGIC {
2481 return Err(ParseError::WrongMagic);
2482 }
2483 let version = u16::from_le_bytes([bytes[4], bytes[5]]);
2484 if version != VERSION
2485 && version != VERSION_V3
2486 && version != VERSION_V4
2487 && version != VERSION_V2
2488 && version != VERSION_V1
2489 {
2490 return Err(ParseError::UnsupportedVersion(version));
2491 }
2492 let (world_header_size, sector_record_size, wall_record_size) = match version {
2493 VERSION_V1 => (
2494 WORLD_V3_HEADER_SIZE,
2495 WORLD_V1_SECTOR_RECORD_SIZE,
2496 WORLD_V1_WALL_RECORD_SIZE,
2497 ),
2498 VERSION_V2 => (
2499 WORLD_V3_HEADER_SIZE,
2500 WORLD_V2_SECTOR_RECORD_SIZE,
2501 WORLD_V2_WALL_RECORD_SIZE,
2502 ),
2503 VERSION_V3 => (
2504 WORLD_V3_HEADER_SIZE,
2505 WORLD_V3_SECTOR_RECORD_SIZE,
2506 WORLD_V3_WALL_RECORD_SIZE,
2507 ),
2508 VERSION_V4 => (
2509 WorldHeader::SIZE,
2510 psxed_format::world::SectorRecord::SIZE,
2511 psxed_format::world::WallRecord::SIZE,
2512 ),
2513 _ => (
2514 WorldHeader::SIZE,
2515 psxed_format::world::SectorRecord::SIZE,
2516 psxed_format::world::WallRecord::SIZE,
2517 ),
2518 };
2519 let payload_len = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
2520 let payload_start = psxed_format::AssetHeader::SIZE;
2521 let actual_payload = bytes.len().saturating_sub(payload_start);
2522 if payload_len as usize != actual_payload {
2523 return Err(ParseError::InvalidPayloadLen {
2524 declared: payload_len,
2525 actual: actual_payload,
2526 });
2527 }
2528 if actual_payload < world_header_size {
2529 return Err(ParseError::Truncated);
2530 }
2531
2532 let wh = &bytes[payload_start..payload_start + world_header_size];
2533 let width = read_u16(wh, 0);
2534 let depth = read_u16(wh, 2);
2535 let sector_size = read_i32(wh, 4);
2536 let sector_count = read_u16(wh, 8);
2537 let material_count = read_u16(wh, 10);
2538 let wall_count = read_u16(wh, 12);
2539 let ambient_color = [wh[14], wh[15], wh[16]];
2540 let flags = wh[17];
2541 let surface_light_count =
2542 if version == VERSION || version == VERSION_V4 || version == VERSION_V3 {
2543 read_u16(wh, 18)
2544 } else {
2545 0
2546 };
2547 let horizontal_override_count = if version == VERSION || version == VERSION_V4 {
2548 read_u16(wh, 20)
2549 } else {
2550 0
2551 };
2552 let static_vertex_lighting =
2553 (version == VERSION || version == VERSION_V4 || version == VERSION_V3)
2554 && flags & psxed_format::world::world_flags::STATIC_VERTEX_LIGHTING != 0;
2555
2556 let expected_sectors = (width as usize)
2557 .checked_mul(depth as usize)
2558 .ok_or(ParseError::InvalidWorldLayout)?;
2559 if sector_count as usize != expected_sectors {
2560 return Err(ParseError::InvalidWorldLayout);
2561 }
2562 if static_vertex_lighting {
2563 let expected_surface_lights = sector_count
2564 .checked_mul(2)
2565 .and_then(|count| count.checked_add(wall_count))
2566 .ok_or(ParseError::InvalidWorldLayout)?;
2567 if surface_light_count != expected_surface_lights {
2568 return Err(ParseError::InvalidWorldLayout);
2569 }
2570 } else if surface_light_count != 0 {
2571 return Err(ParseError::InvalidWorldLayout);
2572 }
2573
2574 let mut off = payload_start + world_header_size;
2575 let sector_bytes = (sector_count as usize)
2576 .checked_mul(sector_record_size)
2577 .ok_or(ParseError::TableOverflow)?;
2578 if off + sector_bytes > bytes.len() {
2579 return Err(ParseError::TableOverflow);
2580 }
2581 let sectors = &bytes[off..off + sector_bytes];
2582 off += sector_bytes;
2583
2584 let wall_bytes = (wall_count as usize)
2585 .checked_mul(wall_record_size)
2586 .ok_or(ParseError::TableOverflow)?;
2587 if off + wall_bytes > bytes.len() {
2588 return Err(ParseError::TableOverflow);
2589 }
2590 let walls = &bytes[off..off + wall_bytes];
2591 off += wall_bytes;
2592 let horizontal_override_record_size = if version == VERSION_V4 {
2593 WORLD_V4_HORIZONTAL_OVERRIDE_RECORD_SIZE
2594 } else {
2595 psxed_format::world::HorizontalOverrideRecord::SIZE
2596 };
2597 let horizontal_override_bytes = (horizontal_override_count as usize)
2598 .checked_mul(horizontal_override_record_size)
2599 .ok_or(ParseError::TableOverflow)?;
2600 if off + horizontal_override_bytes > bytes.len() {
2601 return Err(ParseError::TableOverflow);
2602 }
2603 let horizontal_overrides = &bytes[off..off + horizontal_override_bytes];
2604 off += horizontal_override_bytes;
2605 let surface_light_bytes = (surface_light_count as usize)
2606 .checked_mul(psxed_format::world::SurfaceLightRecord::SIZE)
2607 .ok_or(ParseError::TableOverflow)?;
2608 if off + surface_light_bytes > bytes.len() {
2609 return Err(ParseError::TableOverflow);
2610 }
2611 let surface_lights = &bytes[off..off + surface_light_bytes];
2612 off += surface_light_bytes;
2613 if off != bytes.len() {
2614 return Err(ParseError::InvalidWorldLayout);
2615 }
2616
2617 validate_sector_wall_ranges(sectors, sector_record_size, wall_count)?;
2618 validate_horizontal_overrides(
2619 horizontal_overrides,
2620 horizontal_override_count,
2621 sector_count,
2622 horizontal_override_record_size,
2623 )?;
2624
2625 Ok(Self {
2626 sectors,
2627 walls,
2628 horizontal_overrides,
2629 surface_lights,
2630 sector_record_size,
2631 wall_record_size,
2632 horizontal_override_record_size,
2633 width,
2634 depth,
2635 sector_size,
2636 material_count,
2637 wall_count,
2638 horizontal_override_count,
2639 surface_light_count,
2640 ambient_color,
2641 flags,
2642 static_vertex_lighting,
2643 })
2644 }
2645
2646 #[inline]
2648 pub fn width(&self) -> u16 {
2649 self.width
2650 }
2651
2652 #[inline]
2654 pub fn depth(&self) -> u16 {
2655 self.depth
2656 }
2657
2658 #[inline]
2660 pub fn sector_size(&self) -> i32 {
2661 self.sector_size
2662 }
2663
2664 #[inline]
2666 pub fn material_count(&self) -> u16 {
2667 self.material_count
2668 }
2669
2670 #[inline]
2672 pub fn wall_count(&self) -> u16 {
2673 self.wall_count
2674 }
2675
2676 #[inline]
2678 pub fn horizontal_override_count(&self) -> u16 {
2679 self.horizontal_override_count
2680 }
2681
2682 #[inline]
2684 pub fn surface_light_count(&self) -> u16 {
2685 self.surface_light_count
2686 }
2687
2688 #[inline]
2690 pub fn ambient_color(&self) -> [u8; 3] {
2691 self.ambient_color
2692 }
2693
2694 #[inline]
2696 pub fn is_fog_enabled(&self) -> bool {
2697 self.flags & psxed_format::world::world_flags::FOG_ENABLED != 0
2698 }
2699
2700 #[inline]
2702 pub fn has_static_vertex_lighting(&self) -> bool {
2703 self.static_vertex_lighting
2704 }
2705
2706 pub fn sector(&self, x: u16, z: u16) -> Option<WorldSector> {
2708 if x >= self.width || z >= self.depth {
2709 return None;
2710 }
2711 let index = x as usize * self.depth as usize + z as usize;
2712 let sector = self.sector_record(index)?;
2713 if sector.has_geometry() {
2714 Some(sector)
2715 } else {
2716 None
2717 }
2718 }
2719
2720 pub fn sector_without_horizontal_overrides(&self, x: u16, z: u16) -> Option<WorldSector> {
2724 if x >= self.width || z >= self.depth {
2725 return None;
2726 }
2727 let index = x as usize * self.depth as usize + z as usize;
2728 let sector = self.sector_record_without_horizontal_overrides(index)?;
2729 if sector.has_geometry() {
2730 Some(sector)
2731 } else {
2732 None
2733 }
2734 }
2735
2736 pub fn sector_collision_probe(&self, x: u16, z: u16) -> Option<WorldSectorCollisionProbe> {
2740 if x >= self.width || z >= self.depth {
2741 return None;
2742 }
2743 let index = x as usize * self.depth as usize + z as usize;
2744 let sector = self.sector_collision_probe_record(index)?;
2745 if sector.has_geometry() {
2746 Some(sector)
2747 } else {
2748 None
2749 }
2750 }
2751
2752 pub fn sector_floor_collision(
2757 &self,
2758 x: u16,
2759 z: u16,
2760 local_x: i32,
2761 local_z: i32,
2762 sector_size: i32,
2763 ) -> Option<WorldSectorFloorCollision> {
2764 if x >= self.width || z >= self.depth || sector_size <= 0 {
2765 return None;
2766 }
2767 let index = x as usize * self.depth as usize + z as usize;
2768 let size = self.sector_record_size;
2769 let base = index.checked_mul(size)?;
2770 let end = base.checked_add(size)?;
2771 let bytes = self.sectors.get(base..end)?;
2772 let flags = bytes[0];
2773 if flags & psxed_format::world::sector_flags::HAS_FLOOR == 0 {
2774 return None;
2775 }
2776
2777 let split = bytes[1];
2778 let triangle =
2779 world_topology::horizontal_triangle_at_local(split, local_x, local_z, sector_size);
2780 let default_flags = default_horizontal_flags(
2781 true,
2782 flags & psxed_format::world::sector_flags::FLOOR_WALKABLE != 0,
2783 );
2784 let sector_index = u16::try_from(index).ok()?;
2785 let (triangle_flags, override_heights) = self
2786 .horizontal_collision_override(
2787 sector_index,
2788 psxed_format::world::horizontal_surface::FLOOR,
2789 triangle,
2790 )
2791 .unwrap_or((default_flags, None));
2792 if !horizontal_triangle_present(triangle_flags, triangle) {
2793 return None;
2794 }
2795
2796 let floor_heights = read_i32x4(bytes, 12);
2797 let triangle_heights = override_heights
2798 .unwrap_or_else(|| horizontal_triangle_heights(floor_heights, split, triangle));
2799 Some(WorldSectorFloorCollision {
2800 split,
2801 triangle: triangle as u8,
2802 walkable: horizontal_triangle_walkable(triangle_flags, triangle),
2803 floor_heights,
2804 triangle_heights,
2805 })
2806 }
2807
2808 pub fn sector_record(&self, index: usize) -> Option<WorldSector> {
2810 let size = self.sector_record_size;
2811 let base = index.checked_mul(size)?;
2812 let end = base.checked_add(size)?;
2813 let bytes = self.sectors.get(base..end)?;
2814 let sector_index = if index <= u16::MAX as usize {
2815 index as u16
2816 } else {
2817 return None;
2818 };
2819 let floor_override =
2820 self.horizontal_override(sector_index, psxed_format::world::horizontal_surface::FLOOR);
2821 let ceiling_override = self.horizontal_override(
2822 sector_index,
2823 psxed_format::world::horizontal_surface::CEILING,
2824 );
2825 Some(WorldSector::decode(bytes, floor_override, ceiling_override))
2826 }
2827
2828 fn sector_record_without_horizontal_overrides(&self, index: usize) -> Option<WorldSector> {
2829 let size = self.sector_record_size;
2830 let base = index.checked_mul(size)?;
2831 let end = base.checked_add(size)?;
2832 let bytes = self.sectors.get(base..end)?;
2833 if index > u16::MAX as usize {
2834 return None;
2835 }
2836 Some(WorldSector::decode(bytes, None, None))
2837 }
2838
2839 fn sector_collision_probe_record(&self, index: usize) -> Option<WorldSectorCollisionProbe> {
2840 let size = self.sector_record_size;
2841 let base = index.checked_mul(size)?;
2842 let end = base.checked_add(size)?;
2843 let bytes = self.sectors.get(base..end)?;
2844 if index > u16::MAX as usize {
2845 return None;
2846 }
2847 Some(WorldSectorCollisionProbe {
2848 flags: bytes[0],
2849 first_wall: read_u16(bytes, 8),
2850 wall_count: read_u16(bytes, 10),
2851 })
2852 }
2853
2854 pub fn wall(&self, index: u16) -> Option<WorldWall> {
2856 if index >= self.wall_count {
2857 return None;
2858 }
2859 let size = self.wall_record_size;
2860 let base = index as usize * size;
2861 let end = base.checked_add(size)?;
2862 let bytes = self.walls.get(base..end)?;
2863 Some(WorldWall::decode(bytes))
2864 }
2865
2866 pub fn sector_wall(&self, sector: WorldSector, local_index: u16) -> Option<WorldWall> {
2868 if local_index >= sector.wall_count {
2869 return None;
2870 }
2871 self.wall(sector.first_wall.checked_add(local_index)?)
2872 }
2873
2874 pub fn surface_light(&self, index: u16) -> Option<WorldSurfaceLight> {
2876 if !self.static_vertex_lighting || index >= self.surface_light_count {
2877 return None;
2878 }
2879 let size = psxed_format::world::SurfaceLightRecord::SIZE;
2880 let base = index as usize * size;
2881 let end = base.checked_add(size)?;
2882 let bytes = self.surface_lights.get(base..end)?;
2883 Some(read_world_surface_light(bytes, 0))
2884 }
2885
2886 fn horizontal_override(
2887 &self,
2888 sector_index: u16,
2889 surface: u8,
2890 ) -> Option<WorldHorizontalOverride> {
2891 let size = self.horizontal_override_record_size;
2892 let mut index = 0usize;
2893 while index < self.horizontal_override_count as usize {
2894 let base = index.checked_mul(size)?;
2895 let bytes = self.horizontal_overrides.get(base..base + size)?;
2896 if read_u16(bytes, 0) == sector_index && bytes[2] == surface {
2897 return Some(read_world_horizontal_override(bytes));
2898 }
2899 index += 1;
2900 }
2901 None
2902 }
2903
2904 fn horizontal_collision_override(
2905 &self,
2906 sector_index: u16,
2907 surface: u8,
2908 triangle: usize,
2909 ) -> Option<(u8, Option<[i32; 3]>)> {
2910 let size = self.horizontal_override_record_size;
2911 let mut index = 0usize;
2912 while index < self.horizontal_override_count as usize {
2913 let base = index.checked_mul(size)?;
2914 let bytes = self.horizontal_overrides.get(base..base + size)?;
2915 if read_u16(bytes, 0) == sector_index && bytes[2] == surface {
2916 let heights = (bytes.len() >= psxed_format::world::HorizontalOverrideRecord::SIZE)
2917 .then(|| {
2918 let offset = 24 + triangle.min(1) * 12;
2919 [
2920 read_i32(bytes, offset),
2921 read_i32(bytes, offset + 4),
2922 read_i32(bytes, offset + 8),
2923 ]
2924 });
2925 return Some((bytes[3], heights));
2926 }
2927 index += 1;
2928 }
2929 None
2930 }
2931}
2932
2933#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2934struct WorldHorizontalOverride {
2935 flags: u8,
2936 materials: [u16; 2],
2937 uvs: [WorldQuadUvs; 2],
2938 heights: Option<[[i32; 3]; 2]>,
2939}
2940
2941#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2943pub struct WorldSectorCollisionProbe {
2944 flags: u8,
2945 first_wall: u16,
2946 wall_count: u16,
2947}
2948
2949#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2951pub struct WorldSectorFloorCollision {
2952 split: u8,
2953 triangle: u8,
2954 walkable: bool,
2955 floor_heights: [i32; 4],
2956 triangle_heights: [i32; 3],
2957}
2958
2959impl WorldSectorFloorCollision {
2960 #[inline]
2962 pub fn split(self) -> u8 {
2963 self.split
2964 }
2965
2966 #[inline]
2968 pub fn triangle(self) -> usize {
2969 self.triangle as usize
2970 }
2971
2972 #[inline]
2974 pub fn is_walkable(self) -> bool {
2975 self.walkable
2976 }
2977
2978 #[deprecated(note = "renamed to `is_walkable`")]
2980 #[inline(always)]
2981 pub fn walkable(self) -> bool {
2982 self.is_walkable()
2983 }
2984
2985 #[inline]
2987 pub fn floor_heights(self) -> [i32; 4] {
2988 self.floor_heights
2989 }
2990
2991 #[inline]
2993 pub fn triangle_heights(self) -> [i32; 3] {
2994 self.triangle_heights
2995 }
2996}
2997
2998impl WorldSectorCollisionProbe {
2999 #[inline]
3001 pub fn has_geometry(self) -> bool {
3002 self.has_floor() || self.has_ceiling() || self.wall_count != 0
3003 }
3004
3005 #[inline]
3007 pub fn has_floor(self) -> bool {
3008 self.flags & psxed_format::world::sector_flags::HAS_FLOOR != 0
3009 }
3010
3011 #[inline]
3013 pub fn has_ceiling(self) -> bool {
3014 self.flags & psxed_format::world::sector_flags::HAS_CEILING != 0
3015 }
3016
3017 #[inline]
3019 pub fn first_wall(self) -> u16 {
3020 self.first_wall
3021 }
3022
3023 #[inline]
3025 pub fn wall_count(self) -> u16 {
3026 self.wall_count
3027 }
3028}
3029
3030#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3032pub struct WorldSector {
3033 flags: u8,
3034 floor_split: u8,
3035 ceiling_split: u8,
3036 floor_material: u16,
3037 ceiling_material: u16,
3038 floor_triangle_flags: u8,
3039 ceiling_triangle_flags: u8,
3040 floor_triangle_materials: [u16; 2],
3041 ceiling_triangle_materials: [u16; 2],
3042 floor_triangle_heights: [[i32; 3]; 2],
3043 ceiling_triangle_heights: [[i32; 3]; 2],
3044 first_wall: u16,
3045 wall_count: u16,
3046 floor_heights: [i32; 4],
3047 ceiling_heights: [i32; 4],
3048 floor_uvs: WorldQuadUvs,
3049 ceiling_uvs: WorldQuadUvs,
3050 floor_triangle_uvs: [WorldQuadUvs; 2],
3051 ceiling_triangle_uvs: [WorldQuadUvs; 2],
3052}
3053
3054impl WorldSector {
3055 fn decode(
3056 bytes: &[u8],
3057 floor_override: Option<WorldHorizontalOverride>,
3058 ceiling_override: Option<WorldHorizontalOverride>,
3059 ) -> Self {
3060 let has_v2_uvs = bytes.len() >= WORLD_V2_SECTOR_RECORD_SIZE;
3061 let flags = bytes[0];
3062 let floor_material = read_u16(bytes, 4);
3063 let ceiling_material = read_u16(bytes, 6);
3064 let floor_heights = read_i32x4(bytes, 12);
3065 let ceiling_heights = read_i32x4(bytes, 28);
3066 let floor_uvs = if has_v2_uvs {
3067 read_world_uvs(bytes, 44)
3068 } else {
3069 WorldQuadUvs::new(psxed_format::world::FLOOR_UVS)
3070 };
3071 let ceiling_uvs = if has_v2_uvs {
3072 read_world_uvs(bytes, 52)
3073 } else {
3074 WorldQuadUvs::new(psxed_format::world::FLOOR_UVS)
3075 };
3076 let floor_default_flags = default_horizontal_flags(
3077 flags & psxed_format::world::sector_flags::HAS_FLOOR != 0,
3078 flags & psxed_format::world::sector_flags::FLOOR_WALKABLE != 0,
3079 );
3080 let ceiling_default_flags = default_horizontal_flags(
3081 flags & psxed_format::world::sector_flags::HAS_CEILING != 0,
3082 flags & psxed_format::world::sector_flags::CEILING_WALKABLE != 0,
3083 );
3084 let floor_triangle_flags =
3085 floor_override.map_or(floor_default_flags, |override_data| override_data.flags);
3086 let ceiling_triangle_flags =
3087 ceiling_override.map_or(ceiling_default_flags, |override_data| override_data.flags);
3088 let floor_triangle_materials = floor_override
3089 .map_or([floor_material, floor_material], |override_data| {
3090 override_data.materials
3091 });
3092 let ceiling_triangle_materials = ceiling_override
3093 .map_or([ceiling_material, ceiling_material], |override_data| {
3094 override_data.materials
3095 });
3096 let floor_triangle_uvs =
3097 floor_override.map_or([floor_uvs, floor_uvs], |override_data| override_data.uvs);
3098 let ceiling_triangle_uvs = ceiling_override
3099 .map_or([ceiling_uvs, ceiling_uvs], |override_data| {
3100 override_data.uvs
3101 });
3102 let floor_triangle_heights = floor_override
3103 .and_then(|override_data| override_data.heights)
3104 .unwrap_or_else(|| {
3105 [
3106 horizontal_triangle_heights(floor_heights, bytes[1], 0),
3107 horizontal_triangle_heights(floor_heights, bytes[1], 1),
3108 ]
3109 });
3110 let ceiling_triangle_heights = ceiling_override
3111 .and_then(|override_data| override_data.heights)
3112 .unwrap_or_else(|| {
3113 [
3114 horizontal_triangle_heights(ceiling_heights, bytes[2], 0),
3115 horizontal_triangle_heights(ceiling_heights, bytes[2], 1),
3116 ]
3117 });
3118 Self {
3119 flags,
3120 floor_split: bytes[1],
3121 ceiling_split: bytes[2],
3122 floor_material,
3123 ceiling_material,
3124 floor_triangle_flags,
3125 ceiling_triangle_flags,
3126 floor_triangle_materials,
3127 ceiling_triangle_materials,
3128 floor_triangle_heights,
3129 ceiling_triangle_heights,
3130 first_wall: read_u16(bytes, 8),
3131 wall_count: read_u16(bytes, 10),
3132 floor_heights,
3133 ceiling_heights,
3134 floor_uvs,
3135 ceiling_uvs,
3136 floor_triangle_uvs,
3137 ceiling_triangle_uvs,
3138 }
3139 }
3140
3141 #[inline]
3143 pub fn has_geometry(&self) -> bool {
3144 self.has_floor() || self.has_ceiling() || self.wall_count != 0
3145 }
3146
3147 #[inline]
3149 pub fn has_floor(&self) -> bool {
3150 self.flags & psxed_format::world::sector_flags::HAS_FLOOR != 0
3151 && (self.has_floor_triangle(0) || self.has_floor_triangle(1))
3152 }
3153
3154 #[inline]
3156 pub fn has_ceiling(&self) -> bool {
3157 self.flags & psxed_format::world::sector_flags::HAS_CEILING != 0
3158 && (self.has_ceiling_triangle(0) || self.has_ceiling_triangle(1))
3159 }
3160
3161 #[inline]
3163 pub fn is_floor_walkable(&self) -> bool {
3164 self.is_floor_triangle_walkable(0) || self.is_floor_triangle_walkable(1)
3165 }
3166
3167 #[deprecated(note = "renamed to `is_floor_walkable`")]
3169 #[inline(always)]
3170 pub fn floor_walkable(&self) -> bool {
3171 self.is_floor_walkable()
3172 }
3173
3174 #[inline]
3176 pub fn floor_split(&self) -> u8 {
3177 self.floor_split
3178 }
3179
3180 #[inline]
3182 pub fn floor_material(&self) -> Option<u16> {
3183 self.floor_triangle_material(0)
3184 .or_else(|| self.floor_triangle_material(1))
3185 .or_else(|| material_or_none(self.floor_material))
3186 }
3187
3188 #[inline]
3190 pub fn floor_heights(&self) -> [i32; 4] {
3191 self.floor_heights
3192 }
3193
3194 #[inline]
3196 pub fn floor_uvs(&self) -> WorldQuadUvs {
3197 self.floor_uvs
3198 }
3199
3200 #[inline]
3202 pub fn has_floor_triangle(&self, index: usize) -> bool {
3203 horizontal_triangle_present(self.floor_triangle_flags, index)
3204 }
3205
3206 #[deprecated(note = "renamed to `has_floor_triangle`")]
3208 #[inline(always)]
3209 pub fn floor_triangle_present(&self, index: usize) -> bool {
3210 self.has_floor_triangle(index)
3211 }
3212
3213 #[inline]
3215 pub fn floor_triangle_material(&self, index: usize) -> Option<u16> {
3216 if self.has_floor_triangle(index) {
3217 material_or_none(self.floor_triangle_materials[index.min(1)])
3218 } else {
3219 None
3220 }
3221 }
3222
3223 #[inline]
3225 pub fn floor_triangle_uvs(&self, index: usize) -> WorldQuadUvs {
3226 self.floor_triangle_uvs[index.min(1)]
3227 }
3228
3229 #[inline]
3231 pub fn floor_triangle_heights(&self, index: usize) -> [i32; 3] {
3232 self.floor_triangle_heights[index.min(1)]
3233 }
3234
3235 #[inline]
3237 pub fn is_floor_triangle_walkable(&self, index: usize) -> bool {
3238 self.has_floor_triangle(index)
3239 && horizontal_triangle_walkable(self.floor_triangle_flags, index)
3240 }
3241
3242 #[deprecated(note = "renamed to `is_floor_triangle_walkable`")]
3244 #[inline(always)]
3245 pub fn floor_triangle_walkable(&self, index: usize) -> bool {
3246 self.is_floor_triangle_walkable(index)
3247 }
3248
3249 #[inline]
3251 pub fn ceiling_split(&self) -> u8 {
3252 self.ceiling_split
3253 }
3254
3255 #[inline]
3257 pub fn ceiling_material(&self) -> Option<u16> {
3258 self.ceiling_triangle_material(0)
3259 .or_else(|| self.ceiling_triangle_material(1))
3260 .or_else(|| material_or_none(self.ceiling_material))
3261 }
3262
3263 #[inline]
3265 pub fn ceiling_heights(&self) -> [i32; 4] {
3266 self.ceiling_heights
3267 }
3268
3269 #[inline]
3271 pub fn ceiling_uvs(&self) -> WorldQuadUvs {
3272 self.ceiling_uvs
3273 }
3274
3275 #[inline]
3277 pub fn has_ceiling_triangle(&self, index: usize) -> bool {
3278 horizontal_triangle_present(self.ceiling_triangle_flags, index)
3279 }
3280
3281 #[deprecated(note = "renamed to `has_ceiling_triangle`")]
3283 #[inline(always)]
3284 pub fn ceiling_triangle_present(&self, index: usize) -> bool {
3285 self.has_ceiling_triangle(index)
3286 }
3287
3288 #[inline]
3290 pub fn ceiling_triangle_material(&self, index: usize) -> Option<u16> {
3291 if self.has_ceiling_triangle(index) {
3292 material_or_none(self.ceiling_triangle_materials[index.min(1)])
3293 } else {
3294 None
3295 }
3296 }
3297
3298 #[inline]
3300 pub fn ceiling_triangle_uvs(&self, index: usize) -> WorldQuadUvs {
3301 self.ceiling_triangle_uvs[index.min(1)]
3302 }
3303
3304 #[inline]
3306 pub fn ceiling_triangle_heights(&self, index: usize) -> [i32; 3] {
3307 self.ceiling_triangle_heights[index.min(1)]
3308 }
3309
3310 #[inline]
3312 pub fn is_ceiling_triangle_walkable(&self, index: usize) -> bool {
3313 self.has_ceiling_triangle(index)
3314 && horizontal_triangle_walkable(self.ceiling_triangle_flags, index)
3315 }
3316
3317 #[inline]
3319 pub fn first_wall(&self) -> u16 {
3320 self.first_wall
3321 }
3322
3323 #[inline]
3325 pub fn wall_count(&self) -> u16 {
3326 self.wall_count
3327 }
3328}
3329
3330#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3332pub struct WorldWall {
3333 direction: u8,
3334 flags: u8,
3335 material: u16,
3336 shape: u16,
3337 heights: [i32; 4],
3338 uvs: WorldQuadUvs,
3339}
3340
3341impl WorldWall {
3342 fn decode(bytes: &[u8]) -> Self {
3343 let has_v2_uvs = bytes.len() >= WORLD_V2_WALL_RECORD_SIZE;
3344 Self {
3345 direction: bytes[0],
3346 flags: bytes[1],
3347 material: read_u16(bytes, 4),
3348 shape: if has_v2_uvs {
3349 read_u16(bytes, 6)
3350 } else {
3351 psxed_format::world::wall_shape::QUAD
3352 },
3353 heights: read_i32x4(bytes, 8),
3354 uvs: if has_v2_uvs {
3355 read_world_uvs(bytes, 24)
3356 } else {
3357 WorldQuadUvs::new(psxed_format::world::WALL_UVS)
3358 },
3359 }
3360 }
3361
3362 #[inline]
3364 pub fn direction(&self) -> u8 {
3365 self.direction
3366 }
3367
3368 #[inline]
3370 pub fn is_solid(&self) -> bool {
3371 self.flags & psxed_format::world::wall_flags::SOLID != 0
3372 }
3373
3374 #[inline]
3376 pub fn material(&self) -> u16 {
3377 self.material
3378 }
3379
3380 #[inline]
3382 pub fn shape(&self) -> u16 {
3383 self.shape
3384 }
3385
3386 #[inline]
3388 pub fn heights(&self) -> [i32; 4] {
3389 self.heights
3390 }
3391
3392 #[inline]
3394 pub fn uvs(&self) -> WorldQuadUvs {
3395 self.uvs
3396 }
3397}
3398
3399#[inline]
3400fn material_or_none(material: u16) -> Option<u16> {
3401 if material == psxed_format::world::NO_MATERIAL {
3402 None
3403 } else {
3404 Some(material)
3405 }
3406}
3407
3408const fn default_horizontal_flags(present: bool, walkable: bool) -> u8 {
3409 if !present {
3410 0
3411 } else {
3412 let mut flags = psxed_format::world::horizontal_flags::TRI_A_PRESENT
3413 | psxed_format::world::horizontal_flags::TRI_B_PRESENT;
3414 if walkable {
3415 flags |= psxed_format::world::horizontal_flags::TRI_A_WALKABLE
3416 | psxed_format::world::horizontal_flags::TRI_B_WALKABLE;
3417 }
3418 flags
3419 }
3420}
3421
3422const fn horizontal_triangle_present(flags: u8, index: usize) -> bool {
3423 let bit = if index == 0 {
3424 psxed_format::world::horizontal_flags::TRI_A_PRESENT
3425 } else {
3426 psxed_format::world::horizontal_flags::TRI_B_PRESENT
3427 };
3428 flags & bit != 0
3429}
3430
3431const fn horizontal_triangle_walkable(flags: u8, index: usize) -> bool {
3432 let bit = if index == 0 {
3433 psxed_format::world::horizontal_flags::TRI_A_WALKABLE
3434 } else {
3435 psxed_format::world::horizontal_flags::TRI_B_WALKABLE
3436 };
3437 flags & bit != 0
3438}
3439
3440fn horizontal_triangle_heights(heights: [i32; 4], split: u8, index: usize) -> [i32; 3] {
3441 let corners = psxed_format::world::topology::split_triangle(split, index);
3442 [
3443 heights[corners[0]],
3444 heights[corners[1]],
3445 heights[corners[2]],
3446 ]
3447}
3448
3449fn checked_table_bytes(count: u16, stride: usize) -> Result<usize, ParseError> {
3450 (count as usize)
3451 .checked_mul(stride)
3452 .ok_or(ParseError::TableOverflow)
3453}
3454
3455fn take_table<'a>(bytes: &'a [u8], offset: &mut usize, len: usize) -> Result<&'a [u8], ParseError> {
3456 let end = offset.checked_add(len).ok_or(ParseError::TableOverflow)?;
3457 if end > bytes.len() {
3458 return Err(ParseError::TableOverflow);
3459 }
3460 let table = &bytes[*offset..end];
3461 *offset = end;
3462 Ok(table)
3463}
3464
3465fn validate_model_parts(
3466 parts: &[u8],
3467 joint_count: u16,
3468 material_count: u16,
3469 vertex_count: u16,
3470 face_count: u16,
3471) -> Result<(), ParseError> {
3472 let size = psxed_format::model::PartRecord::SIZE;
3473 let count = parts.len() / size;
3474 for index in 0..count {
3475 let base = index * size;
3476 let joint_index = read_u16(parts, base);
3477 let first_vertex = read_u16(parts, base + 2);
3478 let part_vertex_count = read_u16(parts, base + 4);
3479 let first_face = read_u16(parts, base + 6);
3480 let part_face_count = read_u16(parts, base + 8);
3481 let material_index = read_u16(parts, base + 10);
3482
3483 if joint_index != psxed_format::model::NO_JOINT && joint_index >= joint_count {
3484 return Err(ParseError::InvalidModelLayout);
3485 }
3486 if material_index >= material_count {
3487 return Err(ParseError::InvalidModelLayout);
3488 }
3489 let Some(vertex_end) = first_vertex.checked_add(part_vertex_count) else {
3490 return Err(ParseError::InvalidModelLayout);
3491 };
3492 if vertex_end > vertex_count {
3493 return Err(ParseError::InvalidModelLayout);
3494 }
3495 let Some(face_end) = first_face.checked_add(part_face_count) else {
3496 return Err(ParseError::InvalidModelLayout);
3497 };
3498 if face_end > face_count {
3499 return Err(ParseError::InvalidModelLayout);
3500 }
3501 }
3502 Ok(())
3503}
3504
3505fn validate_model_faces(faces: &[u8], vertex_count: u16) -> Result<(), ParseError> {
3506 let size = psxed_format::model::FACE_RECORD_SIZE;
3507 let count = faces.len() / size;
3508 for index in 0..count {
3509 let base = index * size;
3510 let a = read_u16(faces, base);
3511 let b = read_u16(faces, base + 4);
3512 let c = read_u16(faces, base + 8);
3513 if a >= vertex_count || b >= vertex_count || c >= vertex_count {
3514 return Err(ParseError::InvalidModelLayout);
3515 }
3516 }
3517 Ok(())
3518}
3519
3520#[inline]
3521fn read_u16(bytes: &[u8], offset: usize) -> u16 {
3522 u16::from_le_bytes([bytes[offset], bytes[offset + 1]])
3523}
3524
3525#[inline]
3526fn read_u32(bytes: &[u8], offset: usize) -> u32 {
3527 u32::from_le_bytes([
3528 bytes[offset],
3529 bytes[offset + 1],
3530 bytes[offset + 2],
3531 bytes[offset + 3],
3532 ])
3533}
3534
3535#[inline]
3536fn read_i16(bytes: &[u8], offset: usize) -> i16 {
3537 i16::from_le_bytes([bytes[offset], bytes[offset + 1]])
3538}
3539
3540#[inline]
3545unsafe fn read_i16_unchecked(bytes: &[u8], offset: usize) -> i16 {
3546 let lo = unsafe { *bytes.get_unchecked(offset) };
3549 let hi = unsafe { *bytes.get_unchecked(offset + 1) };
3551 i16::from_le_bytes([lo, hi])
3552}
3553
3554#[inline]
3560unsafe fn read_i16_aligned_unchecked(bytes: &[u8], offset: usize) -> i16 {
3561 debug_assert_eq!((unsafe { bytes.as_ptr().add(offset) } as usize) & 1, 0);
3564 i16::from_le(unsafe { bytes.as_ptr().add(offset).cast::<i16>().read() })
3567}
3568
3569#[inline]
3570fn read_i32(bytes: &[u8], offset: usize) -> i32 {
3571 i32::from_le_bytes([
3572 bytes[offset],
3573 bytes[offset + 1],
3574 bytes[offset + 2],
3575 bytes[offset + 3],
3576 ])
3577}
3578
3579#[inline]
3584unsafe fn read_i32_unchecked(bytes: &[u8], offset: usize) -> i32 {
3585 i32::from_le_bytes([
3586 unsafe { *bytes.get_unchecked(offset) },
3588 unsafe { *bytes.get_unchecked(offset + 1) },
3590 unsafe { *bytes.get_unchecked(offset + 2) },
3592 unsafe { *bytes.get_unchecked(offset + 3) },
3594 ])
3595}
3596
3597#[inline]
3598fn read_i32x4(bytes: &[u8], offset: usize) -> [i32; 4] {
3599 [
3600 read_i32(bytes, offset),
3601 read_i32(bytes, offset + 4),
3602 read_i32(bytes, offset + 8),
3603 read_i32(bytes, offset + 12),
3604 ]
3605}
3606
3607#[inline]
3608fn read_world_uvs(bytes: &[u8], offset: usize) -> WorldQuadUvs {
3609 WorldQuadUvs::new([
3610 (bytes[offset], bytes[offset + 1]),
3611 (bytes[offset + 2], bytes[offset + 3]),
3612 (bytes[offset + 4], bytes[offset + 5]),
3613 (bytes[offset + 6], bytes[offset + 7]),
3614 ])
3615}
3616
3617#[inline]
3618fn read_world_horizontal_override(bytes: &[u8]) -> WorldHorizontalOverride {
3619 WorldHorizontalOverride {
3620 flags: bytes[3],
3621 materials: [read_u16(bytes, 4), read_u16(bytes, 6)],
3622 uvs: [read_world_uvs(bytes, 8), read_world_uvs(bytes, 16)],
3623 heights: (bytes.len() >= psxed_format::world::HorizontalOverrideRecord::SIZE).then(|| {
3624 [
3625 [
3626 read_i32(bytes, 24),
3627 read_i32(bytes, 28),
3628 read_i32(bytes, 32),
3629 ],
3630 [
3631 read_i32(bytes, 36),
3632 read_i32(bytes, 40),
3633 read_i32(bytes, 44),
3634 ],
3635 ]
3636 }),
3637 }
3638}
3639
3640#[inline]
3641fn read_world_surface_light(bytes: &[u8], offset: usize) -> WorldSurfaceLight {
3642 WorldSurfaceLight::new([
3643 [bytes[offset], bytes[offset + 1], bytes[offset + 2]],
3644 [bytes[offset + 3], bytes[offset + 4], bytes[offset + 5]],
3645 [bytes[offset + 6], bytes[offset + 7], bytes[offset + 8]],
3646 [bytes[offset + 9], bytes[offset + 10], bytes[offset + 11]],
3647 ])
3648}
3649
3650fn validate_horizontal_overrides(
3651 overrides: &[u8],
3652 override_count: u16,
3653 sector_count: u16,
3654 record_size: usize,
3655) -> Result<(), ParseError> {
3656 let size = record_size;
3657 if overrides.len() != override_count as usize * size {
3658 return Err(ParseError::InvalidWorldLayout);
3659 }
3660 for index in 0..override_count as usize {
3661 let base = index * size;
3662 let sector_index = read_u16(overrides, base);
3663 let surface = overrides[base + 2];
3664 if sector_index >= sector_count {
3665 return Err(ParseError::InvalidWorldLayout);
3666 }
3667 if surface != psxed_format::world::horizontal_surface::FLOOR
3668 && surface != psxed_format::world::horizontal_surface::CEILING
3669 {
3670 return Err(ParseError::InvalidWorldLayout);
3671 }
3672 }
3673 Ok(())
3674}
3675
3676fn validate_sector_wall_ranges(
3677 sectors: &[u8],
3678 sector_record_size: usize,
3679 wall_count: u16,
3680) -> Result<(), ParseError> {
3681 let size = sector_record_size;
3682 let count = sectors.len() / size;
3683 for index in 0..count {
3684 let base = index * size;
3685 let first_wall = read_u16(sectors, base + 8);
3686 let sector_wall_count = read_u16(sectors, base + 10);
3687 let Some(end) = first_wall.checked_add(sector_wall_count) else {
3688 return Err(ParseError::InvalidWorldLayout);
3689 };
3690 if end > wall_count {
3691 return Err(ParseError::InvalidWorldLayout);
3692 }
3693 }
3694 Ok(())
3695}
3696
3697#[cfg(test)]
3698extern crate std;
3699
3700#[cfg(test)]
3701mod tests;