Skip to main content

rgpre/import/model3d/
parser.rs

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
12/// Parses the fixed-size model header from a 3D/3DC byte slice.
13pub 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
159/// Parses one face record, including per-vertex index and accumulated UV deltas.
160pub 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/// Decodes a raw texture field into `TextureData` for a given model version.
189#[must_use]
190pub const fn parse_texture_data_value(raw: u32, version: &ModelVersion) -> TextureData {
191    #[allow(clippy::cast_possible_truncation)]
192    // Bitfield extraction is range-limited by masks/shifts.
193    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
231/// Parses one vertex coordinate triplet and applies fixed-point scaling.
232pub 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
237/// Parses one face normal vector and applies fixed-point scaling.
238pub 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)] // Source format stores fixed-point i32 vectors.
247    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
257/// Parses one vertex normal vector.
258pub 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
519/// Computes the file offset where per-vertex normal data begins.
520///
521/// v4.0/v5.0 store per-vertex normals at `offset_vertex_normals`.
522/// v2.6/v2.7 have no per-vertex normal section; returning 0 causes
523/// `parse_vertex_normals_section` to skip parsing (its guard: `offset == 0`).
524fn 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
532// Convert normal_indices from file offsets to vertex_normals array indices.
533// Each raw entry is a byte offset into the vertex-normal section; dividing
534// by 12 (size of one f32×3 normal) yields the array index.
535fn 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
630/// Parses all animation frame vertex and normal data for every frame entry.
631fn 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
810/// Parses a full 3D/3DC model and returns decoded geometry sections.
811pub 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    // v4.0/v5.0 type-0 models keep complete i32 positions and normals.
881    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}