1use super::{
2 FaceData, FaceNormal, FaceVertex, FrameDataEntry, FrameNormalData, FrameType, FrameVertexData,
3 Model3DFile, Model3DHeader, ModelVersion, TextureData, VertexCoord, VertexNormal,
4};
5use log::trace;
6use nom::{
7 IResult, Parser,
8 bytes::complete::take,
9 number::complete::{le_f32, le_i16, le_i32, le_u8, le_u16, le_u32},
10};
11
12pub fn parse_3d_header(input: &[u8]) -> IResult<&[u8], Model3DHeader> {
14 let (
15 input,
16 (
17 version,
18 num_vertices,
19 num_faces,
20 radius,
21 num_frames,
22 offset_frame_data,
23 total_face_vertices,
24 offset_section4,
25 section4_count,
26 unused_24,
27 offset_normal_indices,
28 offset_vertex_normals,
29 offset_vertex_coords,
30 offset_face_normals,
31 total_face_vertices_dup,
32 offset_face_data,
33 ),
34 ) = (
35 take(4u8),
36 le_u32,
37 le_u32,
38 le_u32,
39 le_u32,
40 le_u32,
41 le_u32,
42 le_u32,
43 le_u32,
44 le_u32,
45 le_u32,
46 le_u32,
47 le_u32,
48 le_u32,
49 le_u32,
50 le_u32,
51 )
52 .parse(input)?;
53
54 let mut version_bytes = [0_u8; 4];
55 version_bytes.copy_from_slice(version);
56
57 let mut header = Model3DHeader {
58 version: version_bytes,
59 num_vertices,
60 num_faces,
61 radius,
62 num_frames,
63 offset_frame_data,
64 total_face_vertices,
65 offset_section4,
66 section4_count,
67 _unused_24: unused_24,
68 offset_normal_indices,
69 offset_vertex_normals,
70 offset_vertex_coords,
71 offset_face_normals,
72 total_face_vertices_dup,
73 offset_face_data,
74 };
75
76 let version_str = String::from_utf8_lossy(&header.version);
77 if version_str.starts_with("v2.6") || version_str.starts_with("v2.7") {
78 let tmp = header.total_face_vertices;
79 header.total_face_vertices = 0;
80 header.offset_normal_indices = tmp;
81 std::mem::swap(
82 &mut header.offset_vertex_normals,
83 &mut header.offset_normal_indices,
84 );
85 }
86
87 Ok((input, header))
88}
89
90fn parse_face_texture_header<'a>(
91 input: &'a [u8],
92 version: &ModelVersion,
93) -> IResult<&'a [u8], (u32, u8)> {
94 if matches!(version, ModelVersion::V40 | ModelVersion::V50) {
95 let (input, tex_hi) = le_u8(input)?;
96 let (input, raw) = le_u32(input)?;
97 trace!("v4.0/5.0 texture fields: tex_hi=0x{tex_hi:02x}, raw=0x{raw:08x}");
98 Ok((input, (raw, tex_hi)))
99 } else {
100 let (input, u1) = le_u8(input)?;
101 let (input, texture_data) = le_u16(input)?;
102 let raw = u32::from(texture_data);
103 trace!(
104 "v2.6/2.7 texture fields: u1=0x{u1:02x}, texture_data=0x{texture_data:04x}, raw=0x{raw:08x}"
105 );
106 Ok((input, (raw, u1)))
107 }
108}
109
110fn parse_face_vertices<'a>(
111 input: &'a [u8],
112 vertex_count: u8,
113 version: &ModelVersion,
114) -> IResult<&'a [u8], Vec<FaceVertex>> {
115 let mut face_vertices = Vec::with_capacity(usize::from(vertex_count));
116 let mut acc_u = 0i32;
117 let mut acc_v = 0i32;
118 let mut input = input;
119
120 for i in 0..vertex_count {
121 trace!(
122 "Parsing face vertex {}, remaining bytes: {:02x?}",
123 i,
124 &input[..input.len().min(12)]
125 );
126
127 let (input2, vertex_index_raw) = le_u32(input)?;
128 let (input2, u) = le_i16(input2)?;
129 let (input2, v) = le_i16(input2)?;
130
131 let vertex_index = if matches!(
132 version,
133 ModelVersion::V26 | ModelVersion::V27 | ModelVersion::Unknown
134 ) {
135 vertex_index_raw / 12
136 } else {
137 vertex_index_raw
138 };
139
140 trace!(
141 "Raw vertex_index: {vertex_index_raw} (0x{vertex_index_raw:08x}), adjusted: {vertex_index}, u: {u}, v: {v}"
142 );
143
144 acc_u += i32::from(u);
145 acc_v += i32::from(v);
146
147 face_vertices.push(FaceVertex {
148 vertex_index,
149 u: acc_u,
150 v: acc_v,
151 });
152
153 input = input2;
154 }
155
156 Ok((input, face_vertices))
157}
158
159pub fn parse_face_data<'a>(input: &'a [u8], version: &ModelVersion) -> IResult<&'a [u8], FaceData> {
161 trace!(
162 "Starting face data parsing, first 16 bytes: {:02x?}",
163 &input[..input.len().min(16)]
164 );
165
166 let (input, vertex_count) = le_u8(input)?;
167 trace!("Read vertex_count: {vertex_count}");
168
169 let (input, (texture_data_raw, tex_hi_val)) = parse_face_texture_header(input, version)?;
170 let (input, _unused_04) = le_u32(input)?;
171
172 let texture_data = parse_texture_data_value(texture_data_raw, version);
173 trace!("Parsed texture_data: {texture_data:?}");
174
175 let (input, face_vertices) = parse_face_vertices(input, vertex_count, version)?;
176
177 Ok((
178 input,
179 FaceData {
180 vertex_count,
181 tex_hi: tex_hi_val,
182 texture_data,
183 face_vertices,
184 },
185 ))
186}
187
188#[must_use]
190pub const fn parse_texture_data_value(raw: u32, version: &ModelVersion) -> TextureData {
191 #[allow(clippy::cast_possible_truncation)]
192 match version {
194 ModelVersion::V26 | ModelVersion::V27 | ModelVersion::Unknown => {
195 let texture_id = (raw >> 7) as u16;
196 if texture_id < 2 {
197 let color_index = raw as u8;
198 TextureData::SolidColor(color_index)
199 } else {
200 let image_id = (raw & 0x7F) as u8;
201 TextureData::Texture {
202 texture_id,
203 image_id,
204 }
205 }
206 }
207 ModelVersion::V40 | ModelVersion::V50 => {
208 if (raw >> 20) == 0x0FFF {
209 let color_index = (raw >> 8) as u8;
210 TextureData::SolidColor(color_index)
211 } else {
212 let tmp = (raw >> 8).wrapping_sub(4_000_000);
213 let one = (tmp / 250) % 40;
214 let ten = ((tmp - (one * 250)) / 1000) % 100;
215 let hundred = (tmp - (one * 250) - (ten * 1000)) / 4000;
216 let texture_id = (one + ten + hundred) as u16;
217
218 let one = (raw & 0xFF) % 10;
219 let ten = ((raw & 0xFF) / 40) * 10;
220 let image_id = (one + ten) as u8;
221
222 TextureData::Texture {
223 texture_id,
224 image_id,
225 }
226 }
227 }
228 }
229}
230
231pub fn parse_vertex_coord(input: &[u8]) -> IResult<&[u8], VertexCoord> {
233 let (input, [x, y, z]) = parse_fixed_vec3(input)?;
234 Ok((input, VertexCoord { x, y, z }))
235}
236
237pub fn parse_face_normal(input: &[u8]) -> IResult<&[u8], FaceNormal> {
239 let (input, [x, y, z]) = parse_fixed_vec3(input)?;
240 Ok((input, FaceNormal { x, y, z }))
241}
242
243fn parse_fixed_vec3(input: &[u8]) -> IResult<&[u8], [f32; 3]> {
244 let (input, (x_raw, y_raw, z_raw)) = (le_i32, le_i32, le_i32).parse(input)?;
245 let scale = 1.0 / 256.0;
246 #[allow(clippy::cast_precision_loss)] Ok((
248 input,
249 [
250 x_raw as f32 * scale,
251 y_raw as f32 * scale,
252 z_raw as f32 * scale,
253 ],
254 ))
255}
256
257pub fn parse_vertex_normal(input: &[u8]) -> IResult<&[u8], VertexNormal> {
259 let (input, (x, y, z)) = (le_f32, le_f32, le_f32).parse(input)?;
260 Ok((input, VertexNormal { x, y, z }))
261}
262
263fn parse_frame_data_section(input: &[u8], header: &Model3DHeader) -> Vec<FrameDataEntry> {
264 let mut entries = Vec::new();
265 let Ok(mut off) = usize::try_from(header.offset_frame_data) else {
266 return entries;
267 };
268
269 for _ in 0..header.num_frames {
270 if off + 16 > input.len() {
271 break;
272 }
273 let vertex_offset =
274 u32::from_le_bytes([input[off], input[off + 1], input[off + 2], input[off + 3]]);
275 let normal_offset = u32::from_le_bytes([
276 input[off + 4],
277 input[off + 5],
278 input[off + 6],
279 input[off + 7],
280 ]);
281 let reserved = u32::from_le_bytes([
282 input[off + 8],
283 input[off + 9],
284 input[off + 10],
285 input[off + 11],
286 ]);
287 let raw_type = u32::from_le_bytes([
288 input[off + 12],
289 input[off + 13],
290 input[off + 14],
291 input[off + 15],
292 ]);
293
294 let frame_type = match raw_type {
295 0 => FrameType::Static3D,
296 2 => FrameType::AnimatedI16,
297 18 => FrameType::AnimatedI16Unscaled,
298 4 => FrameType::AnimatedI32,
299 8 => FrameType::Static3DC,
300 other => FrameType::Unknown(other),
301 };
302
303 entries.push(FrameDataEntry {
304 vertex_offset,
305 normal_offset,
306 reserved,
307 frame_type,
308 });
309 off += 16;
310 }
311
312 trace!(
313 "Parsed {} frame data entries (num_frames={})",
314 entries.len(),
315 header.num_frames
316 );
317 entries
318}
319
320fn parse_section4_data_section(input: &[u8], header: &Model3DHeader) -> Vec<u8> {
321 if header.offset_section4 == 0 || header.section4_count == 0 {
322 return Vec::new();
323 }
324 let Ok(start) = usize::try_from(header.offset_section4) else {
325 return Vec::new();
326 };
327 if start >= input.len() {
328 return Vec::new();
329 }
330 input[start..].to_vec()
331}
332
333fn parse_face_data_section(
334 input: &[u8],
335 header: &Model3DHeader,
336 version: &ModelVersion,
337) -> Vec<FaceData> {
338 let mut face_data = Vec::new();
339 let input_len_u32 = u32::try_from(input.len()).unwrap_or(u32::MAX);
340
341 if header.offset_face_data < input_len_u32 {
342 let Ok(offset_face_data) = usize::try_from(header.offset_face_data) else {
343 return face_data;
344 };
345 let mut face_data_input = &input[offset_face_data..];
346
347 for face_index in 0..header.num_faces {
348 if face_data_input.len() < 20 {
349 trace!(
350 "Face {}: Not enough data ({} bytes remaining)",
351 face_index,
352 face_data_input.len()
353 );
354 break;
355 }
356
357 match parse_face_data(face_data_input, version) {
358 Ok((remaining, face)) => {
359 trace!(
360 "Face {}: vertex_count={}, tex_hi={}, texture_data={:?}",
361 face_index, face.vertex_count, face.tex_hi, face.texture_data,
362 );
363 face_data.push(face);
364 face_data_input = remaining;
365 }
366 Err(e) => {
367 trace!("Face {face_index}: Failed to parse face data: {e:?}");
368 break;
369 }
370 }
371 }
372 }
373
374 trace!("Successfully parsed {} faces", face_data.len());
375 face_data
376}
377
378fn parse_vertex_coords_section(
379 input: &[u8],
380 header: &Model3DHeader,
381 adjusted_offset_vertex_coords: u32,
382) -> Vec<VertexCoord> {
383 let mut vertex_coords = Vec::new();
384 let input_len_u32 = u32::try_from(input.len()).unwrap_or(u32::MAX);
385
386 if adjusted_offset_vertex_coords < input_len_u32 {
387 let Ok(offset_vertex_coords) = usize::try_from(adjusted_offset_vertex_coords) else {
388 return vertex_coords;
389 };
390 let mut vertex_input = &input[offset_vertex_coords..];
391
392 for vertex_index in 0..header.num_vertices {
393 match parse_vertex_coord(vertex_input) {
394 Ok((remaining, vertex)) => {
395 if vertex_index < 5 {
396 trace!(
397 "Vertex {}: ({:.2}, {:.2}, {:.2})",
398 vertex_index, vertex.x, vertex.y, vertex.z
399 );
400 }
401 vertex_coords.push(vertex);
402 vertex_input = remaining;
403 }
404 Err(e) => {
405 trace!("Vertex {vertex_index}: Failed to parse vertex: {e:?}");
406 break;
407 }
408 }
409 }
410 }
411
412 trace!("Successfully parsed {} vertices", vertex_coords.len());
413 vertex_coords
414}
415
416fn parse_face_normals_section(input: &[u8], header: &Model3DHeader) -> Vec<FaceNormal> {
417 let mut face_normals = Vec::new();
418 let input_len_u32 = u32::try_from(input.len()).unwrap_or(u32::MAX);
419
420 if header.offset_face_normals < input_len_u32 {
421 let Ok(offset_face_normals) = usize::try_from(header.offset_face_normals) else {
422 return face_normals;
423 };
424 let mut normal_input = &input[offset_face_normals..];
425
426 for normal_index in 0..header.num_faces {
427 match parse_face_normal(normal_input) {
428 Ok((remaining, normal)) => {
429 if normal_index < 5 {
430 trace!(
431 "Normal {}: ({:.2}, {:.2}, {:.2})",
432 normal_index, normal.x, normal.y, normal.z
433 );
434 }
435 face_normals.push(normal);
436 normal_input = remaining;
437 }
438 Err(e) => {
439 trace!("Normal {normal_index}: Failed to parse normal: {e:?}");
440 break;
441 }
442 }
443 }
444 }
445
446 trace!("Successfully parsed {} face normals", face_normals.len());
447 face_normals
448}
449
450fn parse_normal_indices_section(input: &[u8], header: &Model3DHeader) -> Vec<u32> {
451 let mut indices = Vec::new();
452 let input_len_u32 = u32::try_from(input.len()).unwrap_or(u32::MAX);
453
454 if header.offset_normal_indices == 0 || header.offset_normal_indices >= input_len_u32 {
455 return indices;
456 }
457
458 let Ok(offset_normal_indices) = usize::try_from(header.offset_normal_indices) else {
459 return indices;
460 };
461 let mut idx_input = &input[offset_normal_indices..];
462
463 for i in 0..header.total_face_vertices {
464 match le_u32::<_, nom::error::Error<_>>(idx_input) {
465 Ok((remaining, offset)) => {
466 if i < 5 {
467 trace!("Normal index {i}: {offset}");
468 }
469 indices.push(offset);
470 idx_input = remaining;
471 }
472 Err(e) => {
473 trace!("Normal index {i}: Failed to parse: {e:?}");
474 break;
475 }
476 }
477 }
478
479 trace!("Successfully parsed {} normal indices", indices.len());
480 indices
481}
482
483fn parse_vertex_normals_section(input: &[u8], count: usize, offset: u32) -> Vec<VertexNormal> {
484 let mut normals = Vec::new();
485 let input_len_u32 = u32::try_from(input.len()).unwrap_or(u32::MAX);
486
487 if offset == 0 || offset >= input_len_u32 {
488 return normals;
489 }
490
491 let Ok(normals_offset) = usize::try_from(offset) else {
492 return normals;
493 };
494 let mut norm_input = &input[normals_offset..];
495
496 for i in 0..count.min(norm_input.len() / 12) {
497 match parse_vertex_normal(norm_input) {
498 Ok((remaining, normal)) => {
499 if i < 5 {
500 trace!(
501 "Vertex normal {}: ({:.4}, {:.4}, {:.4})",
502 i, normal.x, normal.y, normal.z
503 );
504 }
505 normals.push(normal);
506 norm_input = remaining;
507 }
508 Err(e) => {
509 trace!("Vertex normal {i}: Failed to parse: {e:?}");
510 break;
511 }
512 }
513 }
514
515 trace!("Successfully parsed {} vertex normals", normals.len());
516 normals
517}
518
519fn vertex_normals_offset(header: &Model3DHeader) -> u32 {
525 if header.is_v27_or_earlier() {
526 0
527 } else {
528 header.offset_vertex_normals
529 }
530}
531
532fn convert_normal_indices(raw_indices: Vec<u32>, adjusted_offset_normals: u32) -> Vec<u32> {
536 if adjusted_offset_normals > 0 {
537 raw_indices
538 .into_iter()
539 .map(|offset| {
540 if offset >= adjusted_offset_normals {
541 (offset - adjusted_offset_normals) / 12
542 } else {
543 u32::MAX
544 }
545 })
546 .collect()
547 } else {
548 raw_indices
549 }
550}
551
552fn parse_i32_frame_vertices(input: &[u8], offset: usize, num_verts: usize) -> Vec<VertexCoord> {
553 const SCALE: f32 = 1.0 / 256.0;
554 let mut coords = Vec::with_capacity(num_verts);
555 let mut pos = offset;
556 for _ in 0..num_verts {
557 if pos + 12 > input.len() {
558 break;
559 }
560 let x = i32::from_le_bytes([input[pos], input[pos + 1], input[pos + 2], input[pos + 3]])
561 as f32
562 * SCALE;
563 let y = i32::from_le_bytes([
564 input[pos + 4],
565 input[pos + 5],
566 input[pos + 6],
567 input[pos + 7],
568 ]) as f32
569 * SCALE;
570 let z = i32::from_le_bytes([
571 input[pos + 8],
572 input[pos + 9],
573 input[pos + 10],
574 input[pos + 11],
575 ]) as f32
576 * SCALE;
577 coords.push(VertexCoord { x, y, z });
578 pos += 12;
579 }
580 coords
581}
582
583fn parse_i16_frame_vertices(input: &[u8], offset: usize, num_verts: usize) -> Vec<VertexCoord> {
584 let mut coords = Vec::with_capacity(num_verts);
585 let mut pos = offset;
586 for _ in 0..num_verts {
587 if pos + 6 > input.len() {
588 break;
589 }
590 let x = i16::from_le_bytes([input[pos], input[pos + 1]]) as f32;
591 let y = i16::from_le_bytes([input[pos + 2], input[pos + 3]]) as f32;
592 let z = i16::from_le_bytes([input[pos + 4], input[pos + 5]]) as f32;
593 coords.push(VertexCoord { x, y, z });
594 pos += 6;
595 }
596 coords
597}
598
599fn parse_packed_frame_normals(input: &[u8], offset: usize, num_faces: usize) -> Vec<FaceNormal> {
600 let mut face_normals = Vec::with_capacity(num_faces);
601 let mut pos = offset;
602 for _ in 0..num_faces {
603 if pos + 4 > input.len() {
604 break;
605 }
606 let packed =
607 u32::from_le_bytes([input[pos], input[pos + 1], input[pos + 2], input[pos + 3]]);
608 let mut x = (packed & 0x3FF) as i32;
609 if x >= 512 {
610 x -= 1024;
611 }
612 let mut y = ((packed >> 10) & 0x3FF) as i32;
613 if y >= 512 {
614 y -= 1024;
615 }
616 let mut z = ((packed >> 20) & 0x3FF) as i32;
617 if z >= 512 {
618 z -= 1024;
619 }
620 face_normals.push(FaceNormal {
621 x: x as f32 / 256.0,
622 y: y as f32 / 256.0,
623 z: z as f32 / 256.0,
624 });
625 pos += 4;
626 }
627 face_normals
628}
629
630fn parse_all_frame_data(
632 input: &[u8],
633 header: &Model3DHeader,
634 frame_data: &[FrameDataEntry],
635 frames_use_i32: bool,
636 uncompressed_frames: bool,
637) -> (Vec<FrameVertexData>, Vec<FrameNormalData>) {
638 let num_verts = header.num_vertices as usize;
639 let num_faces = header.num_faces as usize;
640 let mut fvd: Vec<FrameVertexData> = Vec::with_capacity(frame_data.len());
641 let mut fnd: Vec<FrameNormalData> = Vec::with_capacity(frame_data.len());
642
643 for (frame_index, entry) in frame_data.iter().enumerate() {
644 let coords = if frames_use_i32 || frame_index == 0 {
645 parse_i32_frame_vertices(input, entry.vertex_offset as usize, num_verts)
646 } else {
647 let scale = if frame_data[0].frame_type == FrameType::AnimatedI16Unscaled {
648 1.0
649 } else {
650 1.0 / 256.0
651 };
652 parse_i16_frame_vertices(input, entry.vertex_offset as usize, num_verts)
653 .into_iter()
654 .zip(&fvd[0].coords)
655 .map(|(delta, base)| VertexCoord {
656 x: (f64::from(base.x) - f64::from(delta.x) * scale) as f32,
657 y: (f64::from(base.y) - f64::from(delta.y) * scale) as f32,
658 z: (f64::from(base.z) - f64::from(delta.z) * scale) as f32,
659 })
660 .collect()
661 };
662
663 let face_normals = if uncompressed_frames && entry.normal_offset > 0 {
664 input
665 .get(entry.normal_offset as usize..)
666 .map_or_else(Vec::new, |mut bytes| {
667 let mut normals = Vec::with_capacity(num_faces);
668 for _ in 0..num_faces {
669 let Ok((remaining, normal)) = parse_face_normal(bytes) else {
670 break;
671 };
672 normals.push(normal);
673 bytes = remaining;
674 }
675 normals
676 })
677 } else if frame_index > 0 && entry.normal_offset > 0 {
678 parse_packed_frame_normals(input, entry.normal_offset as usize, num_faces)
679 } else {
680 Vec::new()
681 };
682
683 fvd.push(FrameVertexData { coords });
684 fnd.push(FrameNormalData { face_normals });
685 }
686
687 (fvd, fnd)
688}
689
690fn parse_software_frames(
691 input: &[u8],
692 header: &Model3DHeader,
693) -> Option<(
694 Vec<FrameDataEntry>,
695 Vec<FrameVertexData>,
696 Vec<FrameNormalData>,
697)> {
698 let metadata = usize::try_from(header.offset_frame_data).ok()?;
699 let bytes = input.get(metadata..metadata.checked_add(24)?)?;
700 let table = usize::try_from(u32::from_le_bytes(bytes[..4].try_into().ok()?)).ok()?;
701 let flags = u16::from_le_bytes(bytes[12..14].try_into().ok()?);
702 let compressed = flags & 1 != 0;
703 let reference = usize::from(u16::from_le_bytes(bytes[14..16].try_into().ok()?));
704 let shift = bytes[23] & 31;
705 let count = usize::try_from(header.num_frames).ok()?;
706 let stride = if compressed { 12 } else { 16 };
707 let table_bytes = input.get(table..table.checked_add(count.checked_mul(stride)?)?)?;
708 if compressed && reference >= count {
709 return None;
710 }
711 let mut entries = Vec::with_capacity(count);
712 for record in table_bytes.chunks_exact(stride) {
713 entries.push(FrameDataEntry {
714 vertex_offset: u32::from_le_bytes(record[..4].try_into().ok()?),
715 normal_offset: u32::from_le_bytes(record[4..8].try_into().ok()?),
716 reserved: u32::from_le_bytes(record[8..12].try_into().ok()?),
717 frame_type: if compressed {
718 FrameType::SoftwareI16 {
719 base_frame: reference as u16,
720 shift,
721 }
722 } else {
723 FrameType::AnimatedI32
724 },
725 });
726 }
727 let vertices = header.num_vertices as usize;
728 let faces = header.num_faces as usize;
729 let base: Vec<[i32; 3]> = if compressed {
730 let offset = entries[reference].vertex_offset as usize;
731 input
732 .get(offset..offset.checked_add(vertices.checked_mul(12)?)?)?
733 .as_chunks::<12>()
734 .0
735 .iter()
736 .map(|bytes| {
737 std::array::from_fn(|axis| {
738 i32::from_le_bytes(bytes[axis * 4..axis * 4 + 4].try_into().unwrap())
739 })
740 })
741 .collect()
742 } else {
743 Vec::new()
744 };
745 let mut positions = Vec::with_capacity(count);
746 let mut normals = Vec::with_capacity(count);
747 for (frame, entry) in entries.iter().enumerate() {
748 let coords = if !compressed || frame == reference {
749 let offset = entry.vertex_offset as usize;
750 input.get(offset..offset.checked_add(vertices.checked_mul(12)?)?)?;
751 parse_i32_frame_vertices(input, entry.vertex_offset as usize, vertices)
752 } else {
753 let offset = entry.vertex_offset as usize;
754 input.get(offset..offset.checked_add(vertices.checked_mul(6)?)?)?;
755 input[offset..offset + vertices * 6]
756 .as_chunks::<6>()
757 .0
758 .iter()
759 .zip(&base)
760 .map(|(bytes, base)| {
761 let values: [f32; 3] = std::array::from_fn(|axis| {
762 let delta = i32::from(i16::from_le_bytes(
763 bytes[axis * 2..axis * 2 + 2].try_into().unwrap(),
764 ));
765 base[axis].wrapping_add(delta.wrapping_shl(u32::from(shift))) as f32 / 256.0
766 });
767 VertexCoord {
768 x: values[0],
769 y: values[1],
770 z: values[2],
771 }
772 })
773 .collect()
774 };
775 if coords.len() != vertices {
776 return None;
777 }
778 let offset = entry.normal_offset as usize;
779 let face_normals = if compressed {
780 let bytes = input.get(offset..offset.checked_add(faces.checked_mul(4)?)?)?;
781 bytes
782 .as_chunks::<4>()
783 .0
784 .iter()
785 .map(|bytes| {
786 let packed = i32::from_le_bytes(*bytes);
787 FaceNormal {
788 x: ((packed << 2) >> 22) as f32 / 256.0,
789 y: ((packed << 12) >> 22) as f32 / 256.0,
790 z: ((packed << 22) >> 22) as f32 / 256.0,
791 }
792 })
793 .collect()
794 } else {
795 let mut bytes = input.get(offset..offset.checked_add(faces.checked_mul(12)?)?)?;
796 let mut result = Vec::with_capacity(faces);
797 for _ in 0..faces {
798 let (remaining, normal) = parse_face_normal(bytes).ok()?;
799 result.push(normal);
800 bytes = remaining;
801 }
802 result
803 };
804 positions.push(FrameVertexData { coords });
805 normals.push(FrameNormalData { face_normals });
806 }
807 Some((entries, positions, normals))
808}
809
810pub fn parse_3d_file(input: &[u8]) -> IResult<&[u8], Model3DFile> {
812 trace!(
813 "First 128 bytes of file: {:02x?}",
814 &input[..128.min(input.len())]
815 );
816 let (_input_after_header, header) = parse_3d_header(input)?;
817 let version = header.parse_version();
818
819 trace!(
820 "Parsing 3D file - Version: {:?}, Vertices: {}, Faces: {}",
821 version, header.num_vertices, header.num_faces
822 );
823
824 if header.is_v27_or_earlier() && header.num_frames > 0 {
825 let (frame_data, frame_vertex_data, frame_normal_data) =
826 parse_software_frames(input, &header).ok_or_else(|| {
827 nom::Err::Failure(nom::error::Error::new(
828 &input[..0],
829 nom::error::ErrorKind::Verify,
830 ))
831 })?;
832 let face_data = parse_face_data_section(input, &header, &version);
833 if face_data.len() != header.num_faces as usize {
834 return Err(nom::Err::Failure(nom::error::Error::new(
835 input,
836 nom::error::ErrorKind::Verify,
837 )));
838 }
839 let vertex_coords = frame_vertex_data[0].coords.clone();
840 let face_normals = frame_normal_data[0].face_normals.clone();
841 return Ok((
842 &[],
843 Model3DFile {
844 header,
845 version,
846 frame_data,
847 face_data,
848 vertex_coords,
849 face_normals,
850 normal_indices: Vec::new(),
851 vertex_normals: Vec::new(),
852 frame_vertex_data,
853 frame_normal_data,
854 },
855 ));
856 }
857 let frame_data = parse_frame_data_section(input, &header);
858 let _ = parse_section4_data_section(input, &header);
859 let face_data = parse_face_data_section(input, &header, &version);
860
861 let adjusted_offset_normals = vertex_normals_offset(&header);
862 let adjusted_offset_vertex_coords = header.offset_vertex_coords;
863
864 let vertex_coords = parse_vertex_coords_section(input, &header, adjusted_offset_vertex_coords);
865 let face_normals = parse_face_normals_section(input, &header);
866 let raw_normal_indices = parse_normal_indices_section(input, &header);
867 let normal_indices = convert_normal_indices(raw_normal_indices, adjusted_offset_normals);
868 let normal_count = if normal_indices.is_empty() {
869 usize::try_from(header.num_vertices).unwrap_or(usize::MAX)
870 } else {
871 normal_indices
872 .iter()
873 .filter(|&&index| index != u32::MAX)
874 .filter_map(|&index| usize::try_from(index).ok()?.checked_add(1))
875 .max()
876 .unwrap_or(0)
877 };
878 let vertex_normals = parse_vertex_normals_section(input, normal_count, adjusted_offset_normals);
879
880 let uncompressed_frames = matches!(
882 header.parse_version(),
883 ModelVersion::V40 | ModelVersion::V50
884 ) && frame_data
885 .first()
886 .is_some_and(|frame| frame.frame_type == FrameType::Static3D);
887 let frames_use_i32 = uncompressed_frames
888 || !frame_data.is_empty() && matches!(frame_data[0].frame_type, FrameType::AnimatedI32);
889
890 let (frame_vertex_data, frame_normal_data) = parse_all_frame_data(
891 input,
892 &header,
893 &frame_data,
894 frames_use_i32,
895 uncompressed_frames,
896 );
897
898 let remaining = &[];
899
900 Ok((
901 remaining,
902 Model3DFile {
903 header,
904 version,
905 frame_data,
906 face_data,
907 vertex_coords,
908 face_normals,
909 normal_indices,
910 vertex_normals,
911 frame_vertex_data,
912 frame_normal_data,
913 },
914 ))
915}
916
917#[cfg(test)]
918mod tests {
919 use super::*;
920
921 #[test]
922 fn indexed_normals_can_outnumber_positions() {
923 let mut bytes = vec![0u8; 112];
924 bytes[..4].copy_from_slice(b"v5.0");
925 let words = [1u32, 0, 0, 0, 0, 3, 0, 0, 0, 64, 76, 100, 0, 3, 0];
926 for (index, word) in words.into_iter().enumerate() {
927 bytes[4 + index * 4..8 + index * 4].copy_from_slice(&word.to_le_bytes());
928 }
929 for (index, pointer) in [88u32, 76, 88].into_iter().enumerate() {
930 bytes[64 + index * 4..68 + index * 4].copy_from_slice(&pointer.to_le_bytes());
931 }
932 for (index, value) in [0.0f32, 1.0, 0.0, 1.0, 0.0, 0.0].into_iter().enumerate() {
933 bytes[76 + index * 4..80 + index * 4].copy_from_slice(&value.to_le_bytes());
934 }
935 let model = super::super::parse_3d_file(&bytes).unwrap();
936 assert_eq!(model.vertex_coords.len(), 1);
937 assert_eq!(model.normal_indices, [1, 0, 1]);
938 assert_eq!(model.vertex_normals.len(), 2);
939 assert_eq!(model.vertex_normals[1].x, 1.0);
940 assert_eq!(model.vertex_normals[1].y, 0.0);
941 assert_eq!(model.vertex_normals[1].z, 0.0);
942 }
943
944 #[test]
945 fn v40_later_frames_keep_full_fixed_point_positions_and_normals() {
946 let mut bytes = vec![0u8; 154];
947 bytes[..4].copy_from_slice(b"v4.0");
948 let words = [1u32, 1, 1, 2, 74, 0, 0, 0, 0, 0, 0, 106, 118, 0, 64];
949 for (index, word) in words.into_iter().enumerate() {
950 bytes[4 + index * 4..8 + index * 4].copy_from_slice(&word.to_le_bytes());
951 }
952 for (index, (vertex, normal)) in [(106u32, 118u32), (130, 142)].into_iter().enumerate() {
953 let offset = 74 + index * 16;
954 bytes[offset..offset + 4].copy_from_slice(&vertex.to_le_bytes());
955 bytes[offset + 4..offset + 8].copy_from_slice(&normal.to_le_bytes());
956 }
957 for (offset, values) in [
958 (106, [256i32, 512, -768]),
959 (118, [0, 0, 256]),
960 (130, [1024, -640, 128]),
961 (142, [0, -256, 0]),
962 ] {
963 for (axis, value) in values.into_iter().enumerate() {
964 bytes[offset + axis * 4..offset + axis * 4 + 4]
965 .copy_from_slice(&value.to_le_bytes());
966 }
967 }
968 let model = super::super::parse_3d_file(&bytes).unwrap();
969 let metadata = super::super::export_model_metadata(
970 &model,
971 &[super::super::subobjects::Subobject {
972 center: [5120, -10240, 15360],
973 radius: 5120,
974 extents: Some([1.0, 2.0, 3.0]),
975 faces: vec![0],
976 }],
977 );
978 assert_eq!(
979 metadata["scene_subobjects"][0]["center"],
980 serde_json::json!([-1.0, 2.0, 3.0])
981 );
982 assert_eq!(metadata["scene_subobjects"][0]["radius"], 1.0);
983
984 assert_eq!(
985 metadata["model"]["frame_vertex_data"][1]["coords"][0]["y"],
986 -2.5
987 );
988 assert_eq!(
989 metadata["model"]["frame_normal_data"][1]["face_normals"][0]["y"],
990 -1.0
991 );
992 assert_eq!(metadata["uv_units"], "sixteenths_of_a_texel");
993 let vertex = model.frame_vertex_data[1].coords[0];
994 assert_eq!([vertex.x, vertex.y, vertex.z], [4.0, -2.5, 0.5]);
995 let posed = model.at_frame(1).unwrap();
996 let point = posed.vertex_coords[0];
997 assert_eq!([point.x, point.y, point.z], [4.0, -2.5, 0.5]);
998 assert!(posed.frame_data.is_empty());
999 assert!(posed.at_frame(1).is_err());
1000 let normal = &model.frame_normal_data[1].face_normals[0];
1001 assert_eq!([normal.x, normal.y, normal.z], [0.0, -1.0, 0.0]);
1002 }
1003
1004 #[test]
1005 fn v40_animated_models_keep_packed_later_normals() {
1006 for encoding in [2u32, 18, 4] {
1007 let normal_offset = if encoding != 4 { 136usize } else { 142 };
1008 let mut bytes = vec![0u8; normal_offset + 4];
1009 bytes[..4].copy_from_slice(b"v4.0");
1010 let words = [1u32, 1, 1, 2, 74, 0, 0, 0, 0, 0, 0, 106, 118, 0, 64];
1011 for (index, word) in words.into_iter().enumerate() {
1012 bytes[4 + index * 4..8 + index * 4].copy_from_slice(&word.to_le_bytes());
1013 }
1014 for (index, (vertex, normal)) in [(106u32, 118u32), (130, normal_offset as u32)]
1015 .into_iter()
1016 .enumerate()
1017 {
1018 let offset = 74 + index * 16;
1019 bytes[offset..offset + 4].copy_from_slice(&vertex.to_le_bytes());
1020 bytes[offset + 4..offset + 8].copy_from_slice(&normal.to_le_bytes());
1021 }
1022 bytes[86..90].copy_from_slice(&encoding.to_le_bytes());
1023 if encoding != 4 {
1024 for (axis, value) in [-7i16, 9, 2].into_iter().enumerate() {
1025 bytes[130 + axis * 2..132 + axis * 2].copy_from_slice(&value.to_le_bytes());
1026 }
1027 } else {
1028 for (axis, value) in [-1792i32, 2304, 512].into_iter().enumerate() {
1029 bytes[130 + axis * 4..134 + axis * 4].copy_from_slice(&value.to_le_bytes());
1030 }
1031 }
1032 bytes[normal_offset..normal_offset + 4].copy_from_slice(&(768u32 << 10).to_le_bytes());
1033 let model = super::super::parse_3d_file(&bytes).unwrap();
1034 let vertex = model.frame_vertex_data[1].coords[0];
1035 let expected = match encoding {
1036 2 => [7.0 / 256.0, -9.0 / 256.0, -2.0 / 256.0],
1037 18 => [7.0, -9.0, -2.0],
1038 _ => [-7.0, 9.0, 2.0],
1039 };
1040 assert_eq!([vertex.x, vertex.y, vertex.z], expected);
1041 let posed = model.at_frame(1).unwrap();
1042 let point = posed.vertex_coords[0];
1043 assert_eq!([point.x, point.y, point.z], expected);
1044 assert!(model.at_frame(2).is_err());
1045 let mut incomplete = model.clone();
1046 incomplete.frame_vertex_data[1].coords.clear();
1047 assert!(incomplete.at_frame(1).is_err());
1048 let normals = &model.frame_normal_data[1].face_normals;
1049 assert_eq!(normals.len(), 1);
1050 assert_eq!([normals[0].x, normals[0].y, normals[0].z], [0.0, -1.0, 0.0]);
1051 }
1052 }
1053
1054 #[test]
1055 fn compressed_frames_subtract_from_base_not_previous_frame() {
1056 let mut bytes = vec![0u8; 136];
1057 bytes[..4].copy_from_slice(b"v4.0");
1058 let words = [1u32, 0, 0, 3, 64, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0];
1059 for (index, word) in words.into_iter().enumerate() {
1060 bytes[4 + index * 4..8 + index * 4].copy_from_slice(&word.to_le_bytes());
1061 }
1062 for (index, offset) in [112u32, 124, 130].into_iter().enumerate() {
1063 bytes[64 + index * 16..68 + index * 16].copy_from_slice(&offset.to_le_bytes());
1064 }
1065 bytes[76..80].copy_from_slice(&2u32.to_le_bytes());
1066 for (axis, value) in [2560i32, -5120, 7680].into_iter().enumerate() {
1067 bytes[112 + axis * 4..116 + axis * 4].copy_from_slice(&value.to_le_bytes());
1068 }
1069 for offset in [124, 130] {
1070 for (axis, delta) in [256i16, -512, 128].into_iter().enumerate() {
1071 bytes[offset + axis * 2..offset + axis * 2 + 2]
1072 .copy_from_slice(&delta.to_le_bytes());
1073 }
1074 }
1075 let model = super::super::parse_3d_file(&bytes).unwrap();
1076 for frame in [1, 2] {
1077 let v = model.frame_vertex_data[frame].coords[0];
1078 assert_eq!([v.x, v.y, v.z], [9.0, -18.0, 29.5]);
1079 }
1080 }
1081
1082 fn make_v27_face_bytes(u1: u8, texture_data: u16, vertex_count: u8) -> Vec<u8> {
1083 let mut buf = Vec::new();
1084 buf.push(vertex_count);
1085 buf.push(u1);
1086 buf.extend_from_slice(&texture_data.to_le_bytes());
1087 buf.extend_from_slice(&0u32.to_le_bytes());
1088 for _ in 0..vertex_count {
1089 buf.extend_from_slice(&0u32.to_le_bytes());
1090 buf.extend_from_slice(&0i16.to_le_bytes());
1091 buf.extend_from_slice(&0i16.to_le_bytes());
1092 }
1093 buf
1094 }
1095
1096 #[test]
1097 fn v27_texture_decode_uses_u16_not_combined_with_u1() {
1098 let data = make_v27_face_bytes(0xFF, 0x0180, 3);
1099 let (_, face) = parse_face_data(&data, &ModelVersion::V27).unwrap();
1100 match face.texture_data {
1101 TextureData::Texture {
1102 texture_id,
1103 image_id,
1104 } => {
1105 assert_eq!(texture_id, 3, "texture_id should be 0x0180 >> 7 = 3");
1106 assert_eq!(image_id, 0, "image_id should be 0x0180 & 0x7F = 0");
1107 }
1108 TextureData::SolidColor(_) => panic!("expected Texture, got SolidColor"),
1109 }
1110 }
1111
1112 #[test]
1113 fn v27_u1_byte_does_not_affect_texture_id() {
1114 let data_a = make_v27_face_bytes(0x00, 0x0400, 3);
1115 let data_b = make_v27_face_bytes(0xFF, 0x0400, 3);
1116 let (_, face_a) = parse_face_data(&data_a, &ModelVersion::V27).unwrap();
1117 let (_, face_b) = parse_face_data(&data_b, &ModelVersion::V27).unwrap();
1118 match (&face_a.texture_data, &face_b.texture_data) {
1119 (
1120 TextureData::Texture {
1121 texture_id: tid_a,
1122 image_id: iid_a,
1123 },
1124 TextureData::Texture {
1125 texture_id: tid_b,
1126 image_id: iid_b,
1127 },
1128 ) => {
1129 assert_eq!(tid_a, tid_b, "u1 must not contaminate texture_id");
1130 assert_eq!(iid_a, iid_b, "u1 must not contaminate image_id");
1131 }
1132 _ => panic!("expected Texture variants"),
1133 }
1134 }
1135
1136 #[test]
1137 fn v27_solid_color_when_texture_id_below_2() {
1138 let data = make_v27_face_bytes(0x00, 0x0042, 3);
1139 let (_, face) = parse_face_data(&data, &ModelVersion::V27).unwrap();
1140 match face.texture_data {
1141 TextureData::SolidColor(idx) => {
1142 assert_eq!(idx, 0x42, "color_index should be low byte of texture_data");
1143 }
1144 TextureData::Texture { .. } => panic!("expected SolidColor for texture_id < 2"),
1145 }
1146 }
1147}