Skip to main content

mmd_anim_format/pmx/
mod.rs

1use std::collections::HashMap;
2
3use glam::{Mat4, Quat, Vec3A};
4use serde::{Deserialize, Serialize};
5
6use mmd_anim_runtime::{
7    AppendTransformInit, BoneIndex, BoneInit, BoneMorphOffset, GroupMorphOffset, IkAngleLimit,
8    IkLinkInit, IkSolverInit, ModelArena, MorphIndex, MorphInit, MorphOffsetSpan,
9};
10
11use crate::error::ImportError;
12
13const PMX_MAGIC: [u8; 4] = [0x50, 0x4D, 0x58, 0x20];
14
15const BONE_FLAG_TAIL_INDEX: u16 = 0x0001;
16const BONE_FLAG_IK: u16 = 0x0020;
17const BONE_FLAG_LOCAL_APPEND: u16 = 0x0080;
18const BONE_FLAG_APPEND_ROTATE: u16 = 0x0100;
19const BONE_FLAG_APPEND_TRANSLATE: u16 = 0x0200;
20const BONE_FLAG_FIXED_AXIS: u16 = 0x0400;
21const BONE_FLAG_LOCAL_AXIS: u16 = 0x0800;
22const BONE_FLAG_EXTERNAL_PARENT: u16 = 0x2000;
23
24fn decode_utf16le_lossy(bytes: &[u8]) -> String {
25    let end = bytes.len().saturating_sub(bytes.len() % 2);
26    let units = bytes[..end]
27        .chunks_exact(2)
28        .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
29        .take_while(|&code| code != 0);
30    std::char::decode_utf16(units)
31        .map(|item| item.unwrap_or(char::REPLACEMENT_CHARACTER))
32        .collect()
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum TextEncoding {
37    Utf16Le = 0,
38    Utf8 = 1,
39}
40
41#[derive(Debug, Clone)]
42pub struct PmxHeader {
43    pub version: f32,
44    pub encoding: TextEncoding,
45    pub extra_uv_count: u8,
46    pub vertex_index_size: u8,
47    pub texture_index_size: u8,
48    pub material_index_size: u8,
49    pub bone_index_size: u8,
50    pub morph_index_size: u8,
51    pub rigidbody_index_size: u8,
52}
53
54struct Reader<'a> {
55    data: &'a [u8],
56    pos: usize,
57}
58
59impl<'a> Reader<'a> {
60    fn new(data: &'a [u8]) -> Self {
61        Self { data, pos: 0 }
62    }
63
64    fn remaining(&self) -> usize {
65        self.data.len().saturating_sub(self.pos)
66    }
67
68    fn require(&self, n: usize) -> Result<(), ImportError> {
69        if self.remaining() >= n {
70            Ok(())
71        } else {
72            Err(ImportError::UnexpectedEof(
73                n.saturating_sub(self.remaining()),
74            ))
75        }
76    }
77
78    fn read_bytes(&mut self, n: usize) -> Result<&'a [u8], ImportError> {
79        self.require(n)?;
80        let slice = &self.data[self.pos..self.pos + n];
81        self.pos += n;
82        Ok(slice)
83    }
84
85    fn read_u8(&mut self) -> Result<u8, ImportError> {
86        Ok(self.read_bytes(1)?[0])
87    }
88
89    fn read_u16_le(&mut self) -> Result<u16, ImportError> {
90        let b = self.read_bytes(2)?;
91        Ok(u16::from_le_bytes([b[0], b[1]]))
92    }
93
94    fn read_i32_le(&mut self) -> Result<i32, ImportError> {
95        let b = self.read_bytes(4)?;
96        Ok(i32::from_le_bytes([b[0], b[1], b[2], b[3]]))
97    }
98
99    fn read_f32_le(&mut self) -> Result<f32, ImportError> {
100        let b = self.read_bytes(4)?;
101        Ok(f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
102    }
103
104    fn read_vec3(&mut self) -> Result<Vec3A, ImportError> {
105        Ok(Vec3A::new(
106            self.read_f32_le()?,
107            self.read_f32_le()?,
108            self.read_f32_le()?,
109        ))
110    }
111
112    fn read_vec2_array(&mut self) -> Result<[f32; 2], ImportError> {
113        Ok([self.read_f32_le()?, self.read_f32_le()?])
114    }
115
116    fn read_vec3_array(&mut self) -> Result<[f32; 3], ImportError> {
117        Ok([
118            self.read_f32_le()?,
119            self.read_f32_le()?,
120            self.read_f32_le()?,
121        ])
122    }
123
124    fn read_vec4_array(&mut self) -> Result<[f32; 4], ImportError> {
125        Ok([
126            self.read_f32_le()?,
127            self.read_f32_le()?,
128            self.read_f32_le()?,
129            self.read_f32_le()?,
130        ])
131    }
132
133    fn read_sized_index(&mut self, size: u8) -> Result<i32, ImportError> {
134        match size {
135            1 => Ok(self.read_u8()? as i8 as i32),
136            2 => Ok({
137                let b = self.read_bytes(2)?;
138                i16::from_le_bytes([b[0], b[1]]) as i32
139            }),
140            4 => self.read_i32_le(),
141            _ => Err(ImportError::InvalidIndexSize(size)),
142        }
143    }
144
145    fn read_index(&mut self, header: &PmxHeader) -> Result<i32, ImportError> {
146        self.read_sized_index(header.bone_index_size)
147    }
148
149    fn read_vertex_index(&mut self, size: u8) -> Result<u32, ImportError> {
150        match size {
151            1 => Ok(self.read_u8()? as u32),
152            2 => Ok(self.read_u16_le()? as u32),
153            4 => {
154                let raw = self.read_i32_le()?;
155                if raw < 0 {
156                    Err(ImportError::SectionOverflow)
157                } else {
158                    Ok(raw as u32)
159                }
160            }
161            _ => Err(ImportError::InvalidIndexSize(size)),
162        }
163    }
164
165    fn skip(&mut self, n: usize) -> Result<(), ImportError> {
166        self.require(n)?;
167        self.pos += n;
168        Ok(())
169    }
170
171    fn read_string(&mut self, encoding: TextEncoding) -> Result<String, ImportError> {
172        let len = self.read_i32_le()?;
173        if len <= 0 {
174            return Ok(String::new());
175        }
176        let bytes = self.read_bytes(len as usize)?;
177        match encoding {
178            TextEncoding::Utf8 => {
179                let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
180                Ok(String::from_utf8_lossy(&bytes[..end]).into_owned())
181            }
182            TextEncoding::Utf16Le => Ok(decode_utf16le_lossy(bytes)),
183        }
184    }
185
186    fn read_string_owned(
187        &mut self,
188        encoding: TextEncoding,
189    ) -> Result<(Vec<u8>, String), ImportError> {
190        let len = self.read_i32_le()?;
191        if len <= 0 {
192            return Ok((Vec::new(), String::new()));
193        }
194        let bytes = self.read_bytes(len as usize)?.to_vec();
195        let decoded = match encoding {
196            TextEncoding::Utf8 => {
197                let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
198                String::from_utf8_lossy(&bytes[..end]).into_owned()
199            }
200            TextEncoding::Utf16Le => decode_utf16le_lossy(&bytes),
201        };
202        Ok((bytes, decoded))
203    }
204
205    fn read_bone_index(&mut self, header: &PmxHeader) -> Result<Option<BoneIndex>, ImportError> {
206        let raw = self.read_sized_index(header.bone_index_size)?;
207        if raw < 0 {
208            Ok(None)
209        } else {
210            Ok(Some(BoneIndex(raw as u32)))
211        }
212    }
213}
214
215pub fn read_header(data: &[u8]) -> Result<(PmxHeader, usize), ImportError> {
216    let mut r = Reader::new(data);
217
218    let magic = r.read_bytes(4)?;
219    if magic != PMX_MAGIC {
220        return Err(ImportError::InvalidPmxMagic);
221    }
222
223    let version = r.read_f32_le()?;
224    if !(2.0..=2.2).contains(&version) {
225        return Err(ImportError::UnsupportedPmxVersion(version));
226    }
227
228    let data_count = r.read_u8()?;
229    if data_count < 8 {
230        return Err(ImportError::InvalidEncoding(data_count));
231    }
232
233    let encoding_byte = r.read_u8()?;
234    let encoding = match encoding_byte {
235        0 => TextEncoding::Utf16Le,
236        1 => TextEncoding::Utf8,
237        _ => return Err(ImportError::InvalidEncoding(encoding_byte)),
238    };
239
240    let extra_uv_count = r.read_u8()?;
241    let vertex_index_size = r.read_u8()?;
242    let texture_index_size = r.read_u8()?;
243    let material_index_size = r.read_u8()?;
244    let bone_index_size = r.read_u8()?;
245    let morph_index_size = r.read_u8()?;
246    let rigidbody_index_size = r.read_u8()?;
247    if data_count > 8 {
248        r.skip((data_count - 8) as usize)?;
249    }
250
251    let header = PmxHeader {
252        version,
253        encoding,
254        extra_uv_count,
255        vertex_index_size,
256        texture_index_size,
257        material_index_size,
258        bone_index_size,
259        morph_index_size,
260        rigidbody_index_size,
261    };
262
263    Ok((header, r.pos))
264}
265
266pub fn skip_model_info(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
267    let mut r = Reader { data, pos };
268    let _model_name = r.read_string(header.encoding)?;
269    let _english_name = r.read_string(header.encoding)?;
270    let _comment = r.read_string(header.encoding)?;
271    let _english_comment = r.read_string(header.encoding)?;
272    Ok(r.pos)
273}
274
275pub fn skip_vertices(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
276    let mut r = Reader { data, pos };
277    let count = r.read_i32_le()?;
278    if count < 0 {
279        return Err(ImportError::SectionOverflow);
280    }
281    let count = count as usize;
282
283    for _ in 0..count {
284        r.skip(12)?;
285        r.skip(12)?;
286        r.skip(8)?;
287        r.skip(header.extra_uv_count as usize * 16)?;
288        let vertex_deform_type = r.read_u8()?;
289
290        let bone_indices: usize = match vertex_deform_type {
291            0 => 1,
292            1 => 2,
293            2 | 4 => 4,
294            3 => 2,
295            _ => return Err(ImportError::SectionOverflow),
296        };
297        for _ in 0..bone_indices {
298            r.read_sized_index(header.bone_index_size)?;
299        }
300
301        let weight_count: usize = match vertex_deform_type {
302            0 => 0,
303            1 => 1,
304            2 | 4 => 4,
305            3 => 1,
306            _ => return Err(ImportError::SectionOverflow),
307        };
308        r.skip(weight_count * 4)?;
309
310        if vertex_deform_type == 3 {
311            r.skip(36)?;
312        }
313
314        r.skip(4)?;
315    }
316
317    Ok(r.pos)
318}
319
320pub fn skip_faces(data: &[u8], vertex_index_size: u8, pos: usize) -> Result<usize, ImportError> {
321    let mut r = Reader { data, pos };
322    let count = r.read_i32_le()?;
323    if count < 0 {
324        return Err(ImportError::SectionOverflow);
325    }
326    let count = count as usize;
327    r.skip(count * vertex_index_size as usize)?;
328    Ok(r.pos)
329}
330
331pub fn skip_textures(
332    data: &[u8],
333    encoding: TextEncoding,
334    pos: usize,
335) -> Result<usize, ImportError> {
336    let mut r = Reader { data, pos };
337    let count = r.read_i32_le()?;
338    if count < 0 {
339        return Err(ImportError::SectionOverflow);
340    }
341    for _ in 0..count {
342        let _path = r.read_string(encoding)?;
343    }
344    Ok(r.pos)
345}
346
347pub fn skip_materials(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
348    let mut r = Reader { data, pos };
349    let count = r.read_i32_le()?;
350    if count < 0 {
351        return Err(ImportError::SectionOverflow);
352    }
353    for _ in 0..count {
354        let _name = r.read_string(header.encoding)?;
355        let _english_name = r.read_string(header.encoding)?;
356        r.skip(16)?;
357        r.skip(16)?;
358        r.skip(12)?;
359        r.skip(1)?;
360        r.skip(16)?;
361        r.skip(4)?;
362        let _texture = r.read_sized_index(header.texture_index_size)?;
363        let _sphere_tex = r.read_sized_index(header.texture_index_size)?;
364        r.skip(1)?;
365        let _toon = r.read_u8()?;
366        if _toon == 0 {
367            r.read_sized_index(header.texture_index_size)?;
368        } else {
369            r.skip(1)?;
370        }
371        let _memo = r.read_string(header.encoding)?;
372        let _face_count = r.read_i32_le()?;
373    }
374    Ok(r.pos)
375}
376
377pub fn skip_morphs(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
378    let mut r = Reader { data, pos };
379    let count = r.read_i32_le()?;
380    if count < 0 {
381        return Err(ImportError::SectionOverflow);
382    }
383    for _ in 0..count {
384        let _name = r.read_string(header.encoding)?;
385        let _english_name = r.read_string(header.encoding)?;
386        r.skip(1)?;
387        let _morph_type = r.read_u8()?;
388        let _offset_count = r.read_i32_le()?;
389        match _morph_type {
390            0 => {
391                for _ in 0.._offset_count {
392                    r.read_sized_index(header.morph_index_size)?;
393                    r.skip(4)?;
394                }
395            }
396            1 => {
397                for _ in 0.._offset_count {
398                    r.read_sized_index(header.vertex_index_size)?;
399                    r.skip(12)?;
400                }
401            }
402            2 => {
403                for _ in 0.._offset_count {
404                    r.read_sized_index(header.bone_index_size)?;
405                    r.skip(12)?;
406                    r.skip(16)?;
407                }
408            }
409            3..=7 => {
410                for _ in 0.._offset_count {
411                    r.read_sized_index(header.vertex_index_size)?;
412                    r.skip(16)?;
413                }
414            }
415            8 => {
416                for _ in 0.._offset_count {
417                    r.read_sized_index(header.material_index_size)?;
418                    r.skip(1)?;
419                    r.skip(16)?;
420                    r.skip(12)?;
421                    r.skip(4)?;
422                    r.skip(12)?;
423                    r.skip(16)?;
424                    r.skip(4)?;
425                    r.skip(16)?;
426                    r.skip(16)?;
427                    r.skip(16)?;
428                }
429            }
430            9 => {
431                for _ in 0.._offset_count {
432                    r.read_sized_index(header.rigidbody_index_size)?;
433                    r.skip(1)?;
434                    r.skip(12)?;
435                    r.skip(12)?;
436                }
437            }
438            _ => return Err(ImportError::SectionOverflow),
439        }
440    }
441    Ok(r.pos)
442}
443
444#[derive(Debug, Clone)]
445pub struct PmxMorphNames {
446    pub name_bytes: Vec<Vec<u8>>,
447    pub names: Vec<String>,
448}
449
450pub fn read_morph_names(
451    data: &[u8],
452    header: &PmxHeader,
453    pos: usize,
454) -> Result<(PmxMorphNames, usize), ImportError> {
455    let mut r = Reader { data, pos };
456    let count = r.read_i32_le()?;
457    if count < 0 {
458        return Err(ImportError::SectionOverflow);
459    }
460    let count = count as usize;
461
462    let mut name_bytes = Vec::with_capacity(count);
463    let mut names = Vec::with_capacity(count);
464
465    for _ in 0..count {
466        let (raw, name) = r.read_string_owned(header.encoding)?;
467        let _english_name = r.read_string(header.encoding)?;
468        name_bytes.push(raw);
469        names.push(name);
470
471        r.skip(1)?;
472        let morph_type = r.read_u8()?;
473        let offset_count = r.read_i32_le()?;
474        if offset_count < 0 {
475            return Err(ImportError::SectionOverflow);
476        }
477        let offset_count = offset_count as usize;
478
479        let per_offset = match morph_type {
480            0 => header.morph_index_size as usize + 4,
481            1 => header.vertex_index_size as usize + 12,
482            2 => header.bone_index_size as usize + 12 + 16,
483            3..=7 => header.vertex_index_size as usize + 16,
484            8 => header.material_index_size as usize + 1 + 16 + 12 + 4 + 12 + 16 + 4 + 16 + 16 + 16,
485            9 => header.rigidbody_index_size as usize + 1 + 12 + 12,
486            _ => return Err(ImportError::SectionOverflow),
487        };
488        r.skip(
489            offset_count
490                .checked_mul(per_offset)
491                .ok_or(ImportError::SectionOverflow)?,
492        )?;
493    }
494
495    Ok((PmxMorphNames { name_bytes, names }, r.pos))
496}
497
498/// Read morph names and extract bone/group offset payloads.
499///
500/// Returns names (same layout as [`read_morph_names`]) plus a [`MorphInit`]
501/// suitable for [`ModelArena::new_with_morphs`]. Unsupported morph types
502/// (vertex, UV, material, flip) have their payload skipped.
503pub fn read_morph_offsets(
504    data: &[u8],
505    header: &PmxHeader,
506    pos: usize,
507) -> Result<(PmxMorphNames, MorphInit, usize), ImportError> {
508    let mut r = Reader { data, pos };
509    let count = r.read_i32_le()?;
510    if count < 0 {
511        return Err(ImportError::SectionOverflow);
512    }
513    let count = count as usize;
514
515    let mut name_bytes = Vec::with_capacity(count);
516    let mut names = Vec::with_capacity(count);
517
518    let mut bone_offsets_all: Vec<BoneMorphOffset> = Vec::new();
519    let mut bone_spans: Vec<MorphOffsetSpan> = Vec::with_capacity(count);
520    let mut group_offsets_all: Vec<GroupMorphOffset> = Vec::new();
521    let mut group_spans: Vec<MorphOffsetSpan> = Vec::with_capacity(count);
522
523    for _ in 0..count {
524        let (raw, name) = r.read_string_owned(header.encoding)?;
525        let _english_name = r.read_string(header.encoding)?;
526        name_bytes.push(raw);
527        names.push(name);
528
529        r.skip(1)?;
530        let morph_type = r.read_u8()?;
531        let offset_count = r.read_i32_le()?;
532        if offset_count < 0 {
533            return Err(ImportError::SectionOverflow);
534        }
535        let offset_count = offset_count as usize;
536
537        match morph_type {
538            0 => {
539                let start = group_offsets_all.len() as u32;
540                for _ in 0..offset_count {
541                    let child_raw = r.read_sized_index(header.morph_index_size)?;
542                    if child_raw < 0 {
543                        return Err(ImportError::SectionOverflow);
544                    }
545                    let ratio = r.read_f32_le()?;
546                    group_offsets_all.push(GroupMorphOffset {
547                        child_morph: MorphIndex(child_raw as u32),
548                        ratio,
549                    });
550                }
551                group_spans.push(MorphOffsetSpan {
552                    start,
553                    count: offset_count as u32,
554                });
555                bone_spans.push(MorphOffsetSpan::default());
556            }
557            2 => {
558                let start = bone_offsets_all.len() as u32;
559                for _ in 0..offset_count {
560                    let bone_raw = r.read_sized_index(header.bone_index_size)?;
561                    if bone_raw < 0 {
562                        return Err(ImportError::SectionOverflow);
563                    }
564                    let pos = r.read_vec3()?;
565                    let qx = r.read_f32_le()?;
566                    let qy = r.read_f32_le()?;
567                    let qz = r.read_f32_le()?;
568                    let qw = r.read_f32_le()?;
569                    bone_offsets_all.push(BoneMorphOffset {
570                        target_bone: BoneIndex(bone_raw as u32),
571                        position_offset: pos,
572                        rotation_offset: Quat::from_xyzw(qx, qy, qz, qw),
573                    });
574                }
575                bone_spans.push(MorphOffsetSpan {
576                    start,
577                    count: offset_count as u32,
578                });
579                group_spans.push(MorphOffsetSpan::default());
580            }
581            _ => {
582                let per_offset = match morph_type {
583                    1 => header.vertex_index_size as usize + 12,
584                    3..=7 => header.vertex_index_size as usize + 16,
585                    8 => {
586                        header.material_index_size as usize
587                            + 1
588                            + 16
589                            + 12
590                            + 4
591                            + 12
592                            + 16
593                            + 4
594                            + 16
595                            + 16
596                            + 16
597                    }
598                    9 => header.rigidbody_index_size as usize + 1 + 12 + 12,
599                    _ => return Err(ImportError::SectionOverflow),
600                };
601                r.skip(
602                    offset_count
603                        .checked_mul(per_offset)
604                        .ok_or(ImportError::SectionOverflow)?,
605                )?;
606                bone_spans.push(MorphOffsetSpan::default());
607                group_spans.push(MorphOffsetSpan::default());
608            }
609        }
610    }
611
612    Ok((
613        PmxMorphNames { name_bytes, names },
614        MorphInit {
615            morph_count: count as u32,
616            vertex_spans: vec![MorphOffsetSpan::default(); count],
617            bone_offsets: bone_offsets_all,
618            bone_spans,
619            group_offsets: group_offsets_all,
620            group_spans,
621            ..MorphInit::default()
622        },
623        r.pos,
624    ))
625}
626
627#[derive(Debug, Clone)]
628pub struct PmxBoneImport {
629    pub bones: Vec<BoneInit>,
630    pub ik_solvers: Vec<IkSolverInit>,
631    pub append_transforms: Vec<AppendTransformInit>,
632    pub bone_name_bytes: Vec<Vec<u8>>,
633    pub bone_names: Vec<String>,
634}
635
636pub fn read_bones(
637    data: &[u8],
638    header: &PmxHeader,
639    pos: usize,
640) -> Result<(PmxBoneImport, usize), ImportError> {
641    let mut r = Reader { data, pos };
642    let count = r.read_i32_le()?;
643    if count < 0 {
644        return Err(ImportError::SectionOverflow);
645    }
646    let count = count as usize;
647
648    let mut bones = Vec::with_capacity(count);
649    let mut ik_solvers = Vec::new();
650    let mut append_transforms = Vec::new();
651
652    let mut bone_name_bytes = Vec::with_capacity(count);
653    let mut bone_names = Vec::with_capacity(count);
654
655    let mut absolute_positions: Vec<Vec3A> = Vec::with_capacity(count);
656
657    for _bone_i in 0..count {
658        let (name_bytes, name) = r.read_string_owned(header.encoding)?;
659        let _english_name = r.read_string(header.encoding)?;
660        let position = r.read_vec3()?;
661        absolute_positions.push(position);
662        let parent = r.read_bone_index(header)?;
663        let transform_order = r.read_i32_le()?;
664        let flags = r.read_u16_le()?;
665
666        if flags & BONE_FLAG_TAIL_INDEX != 0 {
667            r.read_index(header)?;
668        } else {
669            r.read_vec3()?;
670        }
671
672        let has_append =
673            flags & BONE_FLAG_APPEND_ROTATE != 0 || flags & BONE_FLAG_APPEND_TRANSLATE != 0;
674
675        if has_append {
676            let append_parent = r.read_bone_index(header)?;
677            let append_ratio = r.read_f32_le()?;
678
679            if let Some(source) = append_parent {
680                let target_idx = bones.len();
681                append_transforms.push(
682                    AppendTransformInit::new(BoneIndex(target_idx as u32), source, append_ratio)
683                        .with_rotation_if(flags & BONE_FLAG_APPEND_ROTATE != 0)
684                        .with_translation_if(flags & BONE_FLAG_APPEND_TRANSLATE != 0)
685                        .with_local_if(flags & BONE_FLAG_LOCAL_APPEND != 0),
686                );
687            }
688        }
689
690        let fixed_axis = if flags & BONE_FLAG_FIXED_AXIS != 0 {
691            Some(r.read_vec3()?)
692        } else {
693            None
694        };
695
696        if flags & BONE_FLAG_LOCAL_AXIS != 0 {
697            r.read_vec3()?;
698            r.read_vec3()?;
699        }
700
701        if flags & BONE_FLAG_EXTERNAL_PARENT != 0 {
702            r.read_i32_le()?;
703        }
704
705        if flags & BONE_FLAG_IK != 0 {
706            let ik_target = r.read_bone_index(header)?;
707            let ik_loop_count = r.read_i32_le()?;
708            let ik_limit_angle = r.read_f32_le()?;
709            let link_count = r.read_i32_le()?;
710            if link_count < 0 {
711                return Err(ImportError::SectionOverflow);
712            }
713
714            let mut links = Vec::with_capacity(link_count as usize);
715            for _ in 0..link_count {
716                let link_bone = r.read_bone_index(header)?.unwrap_or(BoneIndex(0));
717                let has_limit = r.read_u8()?;
718                let angle_limit = if has_limit != 0 {
719                    let min = r.read_vec3()?;
720                    let max = r.read_vec3()?;
721                    Some(IkAngleLimit::new(min, max))
722                } else {
723                    None
724                };
725                links.push(match angle_limit {
726                    Some(lim) => IkLinkInit::new(link_bone).with_angle_limit(lim),
727                    None => IkLinkInit::new(link_bone),
728                });
729            }
730
731            let ik_bone = BoneIndex(bones.len() as u32);
732            if let Some(target) = ik_target {
733                ik_solvers.push(IkSolverInit {
734                    ik_bone,
735                    target_bone: target,
736                    links,
737                    iteration_count: ik_loop_count as u32,
738                    limit_angle: ik_limit_angle,
739                });
740            }
741        }
742
743        bone_name_bytes.push(name_bytes);
744        bone_names.push(name);
745
746        bones.push(BoneInit {
747            parent,
748            rest_position: position,
749            inverse_bind_matrix: Mat4::from_translation((-position).into()),
750            transform_order,
751            fixed_axis,
752        });
753    }
754
755    for bone in bones.iter_mut() {
756        if let Some(parent_idx) = bone.parent
757            && let Some(parent_abs) = absolute_positions.get(parent_idx.as_usize())
758        {
759            bone.rest_position -= parent_abs;
760        }
761    }
762
763    Ok((
764        PmxBoneImport {
765            bones,
766            ik_solvers,
767            append_transforms,
768            bone_name_bytes,
769            bone_names,
770        },
771        r.pos,
772    ))
773}
774
775pub fn import_pmx_model(data: &[u8]) -> Result<PmxBoneImport, ImportError> {
776    let (header, pos) = read_header(data)?;
777    let pos = skip_model_info(data, &header, pos)?;
778    let pos = skip_vertices(data, &header, pos)?;
779    let pos = skip_faces(data, header.vertex_index_size, pos)?;
780    let pos = skip_textures(data, header.encoding, pos)?;
781    let pos = skip_materials(data, &header, pos)?;
782    let (bones, _pos) = read_bones(data, &header, pos)?;
783    Ok(bones)
784}
785
786#[derive(Debug, Clone, Serialize, Deserialize)]
787#[serde(rename_all = "camelCase")]
788pub struct PmxParsedModel {
789    pub metadata: PmxParsedMetadata,
790    pub geometry: PmxParsedGeometry,
791    pub materials: Vec<PmxParsedMaterial>,
792    pub skeleton: PmxParsedSkeleton,
793    pub morphs: Vec<PmxParsedMorph>,
794    pub display_frames: Vec<PmxParsedDisplayFrame>,
795    pub rigid_bodies: Vec<PmxParsedRigidBody>,
796    pub joints: Vec<PmxParsedJoint>,
797    pub soft_bodies: Vec<PmxParsedSoftBody>,
798    pub diagnostics: Vec<PmxParserDiagnostic>,
799}
800
801#[derive(Debug, Clone, Serialize, Deserialize)]
802#[serde(rename_all = "camelCase")]
803pub struct PmxParsedMetadata {
804    #[serde(default = "default_pmx_format")]
805    pub format: String,
806    pub version: f32,
807    pub encoding: String,
808    pub name: String,
809    pub english_name: String,
810    pub comment: String,
811    pub english_comment: String,
812    pub counts: PmxParsedCounts,
813    pub index_sizes: PmxParsedIndexSizes,
814    pub additional_uv_count: u8,
815}
816
817#[derive(Debug, Clone, Serialize, Deserialize)]
818#[serde(rename_all = "camelCase")]
819pub struct PmxParsedCounts {
820    pub vertices: usize,
821    pub faces: usize,
822    pub materials: usize,
823    pub bones: usize,
824    pub morphs: usize,
825    pub display_frames: usize,
826    pub rigid_bodies: usize,
827    pub joints: usize,
828    pub soft_bodies: usize,
829}
830
831#[derive(Debug, Clone, Serialize, Deserialize)]
832pub struct PmxParsedIndexSizes {
833    pub vertex: u8,
834    pub texture: u8,
835    pub material: u8,
836    pub bone: u8,
837    pub morph: u8,
838    #[serde(rename = "rigidBody")]
839    pub rigid_body: u8,
840}
841
842#[derive(Debug, Clone, Serialize, Deserialize)]
843#[serde(rename_all = "camelCase")]
844pub struct PmxParsedGeometry {
845    pub positions: Vec<f32>,
846    pub normals: Vec<f32>,
847    pub uvs: Vec<f32>,
848    pub additional_uvs: Vec<Vec<f32>>,
849    pub indices: Vec<u32>,
850    pub skin_indices: Vec<u32>,
851    pub skin_weights: Vec<f32>,
852    pub edge_scale: Vec<f32>,
853    pub material_groups: Vec<PmxParsedMaterialGroup>,
854}
855
856#[derive(Debug, Clone, Serialize, Deserialize)]
857#[serde(rename_all = "camelCase")]
858pub struct PmxParsedMaterialGroup {
859    pub start: usize,
860    pub count: usize,
861    pub material_index: usize,
862}
863
864#[derive(Debug, Clone, Serialize, Deserialize)]
865#[serde(rename_all = "camelCase")]
866pub struct PmxParsedMaterial {
867    pub name: String,
868    pub english_name: String,
869    pub texture_path: String,
870    pub sphere_texture_path: String,
871    pub sphere_mode: String,
872    pub toon_texture_path: String,
873    pub shared_toon_index: Option<u8>,
874    pub diffuse: [f32; 4],
875    pub specular: [f32; 3],
876    pub specular_power: f32,
877    pub ambient: [f32; 3],
878    pub edge_color: [f32; 4],
879    pub edge_size: f32,
880    pub flags: PmxParsedMaterialFlags,
881    pub face_count: i32,
882}
883
884#[derive(Debug, Clone, Serialize, Deserialize)]
885#[serde(rename_all = "camelCase")]
886pub struct PmxParsedMaterialFlags {
887    pub double_sided: bool,
888    pub ground_shadow: bool,
889    pub self_shadow_map: bool,
890    pub self_shadow: bool,
891    pub edge: bool,
892    pub vertex_color: bool,
893    pub point_draw: bool,
894    pub line_draw: bool,
895}
896
897#[derive(Debug, Clone, Serialize, Deserialize)]
898pub struct PmxParsedSkeleton {
899    pub bones: Vec<PmxParsedBone>,
900}
901
902#[derive(Debug, Clone, Serialize, Deserialize)]
903#[serde(rename_all = "camelCase")]
904pub struct PmxParsedBone {
905    pub name: String,
906    pub english_name: String,
907    pub parent_index: i32,
908    pub layer: i32,
909    pub position: [f32; 3],
910    pub tail_index: i32,
911    pub tail_position: Option<[f32; 3]>,
912    pub flags: PmxParsedBoneFlags,
913    pub append_transform: Option<PmxParsedAppendTransform>,
914    pub fixed_axis: Option<[f32; 3]>,
915    pub local_axis: Option<PmxParsedLocalAxis>,
916    pub external_parent_key: Option<i32>,
917    pub ik: Option<PmxParsedIk>,
918}
919
920#[derive(Debug, Clone, Serialize, Deserialize)]
921#[serde(rename_all = "camelCase")]
922pub struct PmxParsedBoneFlags {
923    pub indexed_tail: bool,
924    pub rotatable: bool,
925    pub translatable: bool,
926    pub visible: bool,
927    pub enabled: bool,
928    pub ik: bool,
929    pub append_local: bool,
930    pub append_rotate: bool,
931    pub append_translate: bool,
932    pub fixed_axis: bool,
933    pub local_axis: bool,
934    pub transform_after_physics: bool,
935    pub external_parent_transform: bool,
936}
937
938#[derive(Debug, Clone, Serialize, Deserialize)]
939#[serde(rename_all = "camelCase")]
940pub struct PmxParsedAppendTransform {
941    pub parent_index: i32,
942    pub weight: f32,
943}
944
945#[derive(Debug, Clone, Serialize, Deserialize)]
946pub struct PmxParsedLocalAxis {
947    pub x: [f32; 3],
948    pub z: [f32; 3],
949}
950
951#[derive(Debug, Clone, Serialize, Deserialize)]
952#[serde(rename_all = "camelCase")]
953pub struct PmxParsedIk {
954    pub target_index: i32,
955    pub loop_count: i32,
956    pub limit_angle: f32,
957    pub links: Vec<PmxParsedIkLink>,
958}
959
960#[derive(Debug, Clone, Serialize, Deserialize)]
961#[serde(rename_all = "camelCase")]
962pub struct PmxParsedIkLink {
963    pub bone_index: i32,
964    pub limits: Option<PmxParsedIkLimit>,
965}
966
967#[derive(Debug, Clone, Serialize, Deserialize)]
968pub struct PmxParsedIkLimit {
969    pub lower: [f32; 3],
970    pub upper: [f32; 3],
971}
972
973#[derive(Debug, Clone, Serialize, Deserialize)]
974#[serde(rename_all = "camelCase")]
975pub struct PmxParsedMorph {
976    pub name: String,
977    pub english_name: String,
978    #[serde(rename = "type")]
979    pub kind: String,
980    pub vertex_offsets: Vec<PmxParsedVertexMorphOffset>,
981    pub group_offsets: Vec<PmxParsedGroupMorphOffset>,
982    pub bone_offsets: Vec<PmxParsedBoneMorphOffset>,
983    pub uv_offsets: Vec<PmxParsedUvMorphOffset>,
984    pub additional_uv_offsets: Vec<PmxParsedAdditionalUvMorphOffset>,
985    pub material_offsets: Vec<PmxParsedMaterialMorphOffset>,
986    pub flip_offsets: Vec<PmxParsedGroupMorphOffset>,
987    pub impulse_offsets: Vec<PmxParsedImpulseMorphOffset>,
988}
989
990#[derive(Debug, Clone, Serialize, Deserialize)]
991#[serde(rename_all = "camelCase")]
992pub struct PmxParsedVertexMorphOffset {
993    pub vertex_index: u32,
994    pub position: [f32; 3],
995}
996
997#[derive(Debug, Clone, Serialize, Deserialize)]
998#[serde(rename_all = "camelCase")]
999pub struct PmxParsedGroupMorphOffset {
1000    pub morph_index: i32,
1001    pub weight: f32,
1002}
1003
1004#[derive(Debug, Clone, Serialize, Deserialize)]
1005#[serde(rename_all = "camelCase")]
1006pub struct PmxParsedBoneMorphOffset {
1007    pub bone_index: i32,
1008    pub translation: [f32; 3],
1009    pub rotation: [f32; 4],
1010}
1011
1012#[derive(Debug, Clone, Serialize, Deserialize)]
1013#[serde(rename_all = "camelCase")]
1014pub struct PmxParsedUvMorphOffset {
1015    pub vertex_index: u32,
1016    pub uv: [f32; 4],
1017}
1018
1019#[derive(Debug, Clone, Serialize, Deserialize)]
1020#[serde(rename_all = "camelCase")]
1021pub struct PmxParsedAdditionalUvMorphOffset {
1022    pub vertex_index: u32,
1023    pub uv_index: u8,
1024    pub uv: [f32; 4],
1025}
1026
1027#[derive(Debug, Clone, Serialize, Deserialize)]
1028#[serde(rename_all = "camelCase")]
1029pub struct PmxParsedMaterialMorphOffset {
1030    pub material_index: i32,
1031    pub operation: String,
1032    pub diffuse: [f32; 4],
1033    pub specular: [f32; 3],
1034    pub specular_power: f32,
1035    pub ambient: [f32; 3],
1036    pub edge_color: [f32; 4],
1037    pub edge_size: f32,
1038    pub texture_factor: [f32; 4],
1039    pub sphere_texture_factor: [f32; 4],
1040    pub toon_texture_factor: [f32; 4],
1041}
1042
1043#[derive(Debug, Clone, Serialize, Deserialize)]
1044#[serde(rename_all = "camelCase")]
1045pub struct PmxParsedImpulseMorphOffset {
1046    pub rigid_body_index: i32,
1047    pub local: bool,
1048    pub velocity: [f32; 3],
1049    pub torque: [f32; 3],
1050}
1051
1052#[derive(Debug, Clone, Serialize, Deserialize)]
1053#[serde(rename_all = "camelCase")]
1054pub struct PmxParsedDisplayFrame {
1055    pub name: String,
1056    pub english_name: String,
1057    pub special: bool,
1058    pub frames: Vec<PmxParsedDisplayFrameElement>,
1059}
1060
1061#[derive(Debug, Clone, Serialize, Deserialize)]
1062pub struct PmxParsedDisplayFrameElement {
1063    #[serde(rename = "type")]
1064    pub kind: String,
1065    pub index: i32,
1066}
1067
1068#[derive(Debug, Clone, Serialize, Deserialize)]
1069#[serde(rename_all = "camelCase")]
1070pub struct PmxParsedRigidBody {
1071    pub name: String,
1072    pub english_name: String,
1073    pub bone_index: i32,
1074    pub group: u8,
1075    pub mask: u16,
1076    pub shape: String,
1077    pub size: [f32; 3],
1078    pub position: [f32; 3],
1079    pub rotation: [f32; 3],
1080    pub mass: f32,
1081    pub linear_damping: f32,
1082    pub angular_damping: f32,
1083    pub restitution: f32,
1084    pub friction: f32,
1085    pub mode: String,
1086}
1087
1088#[derive(Debug, Clone, Serialize, Deserialize)]
1089#[serde(rename_all = "camelCase")]
1090pub struct PmxParsedJoint {
1091    pub name: String,
1092    pub english_name: String,
1093    #[serde(rename = "type")]
1094    pub kind: String,
1095    pub rigid_body_index_a: i32,
1096    pub rigid_body_index_b: i32,
1097    pub position: [f32; 3],
1098    pub rotation: [f32; 3],
1099    pub translation_lower_limit: [f32; 3],
1100    pub translation_upper_limit: [f32; 3],
1101    pub rotation_lower_limit: [f32; 3],
1102    pub rotation_upper_limit: [f32; 3],
1103    pub spring_translation_factor: [f32; 3],
1104    pub spring_rotation_factor: [f32; 3],
1105}
1106
1107#[derive(Debug, Clone, Serialize, Deserialize)]
1108#[serde(rename_all = "camelCase")]
1109pub struct PmxParsedSoftBody {
1110    pub name: String,
1111    pub english_name: String,
1112    #[serde(rename = "type")]
1113    pub kind: String,
1114    pub material_index: i32,
1115    pub collision_group: u8,
1116    pub collision_mask: u16,
1117    pub flags: u8,
1118    pub bending_constraints_distance: i32,
1119    pub cluster_count: i32,
1120    pub total_mass: f32,
1121    pub collision_margin: f32,
1122}
1123
1124#[derive(Debug, Clone, Serialize, Deserialize)]
1125pub struct PmxParserDiagnostic {
1126    pub level: String,
1127    pub code: String,
1128    pub message: String,
1129}
1130
1131#[derive(Debug, Clone, Serialize, Deserialize)]
1132#[serde(rename_all = "camelCase")]
1133pub struct PmxPartsDescriptor {
1134    #[serde(default = "default_pmx_parts_version")]
1135    pub version: f32,
1136    #[serde(default = "default_pmx_parts_encoding")]
1137    pub encoding: String,
1138    #[serde(default = "default_pmx_parts_name")]
1139    pub name: String,
1140    #[serde(default)]
1141    pub english_name: String,
1142    #[serde(default)]
1143    pub comment: String,
1144    #[serde(default)]
1145    pub english_comment: String,
1146    #[serde(default)]
1147    pub material_name: String,
1148    #[serde(default)]
1149    pub english_material_name: String,
1150    #[serde(default)]
1151    pub materials: Vec<PmxPartsMaterialDescriptor>,
1152    #[serde(default)]
1153    pub bones: Vec<PmxPartsBoneDescriptor>,
1154    #[serde(default)]
1155    pub morphs: Vec<PmxPartsMorphDescriptor>,
1156    #[serde(default)]
1157    pub display_frames: Vec<PmxPartsDisplayFrameDescriptor>,
1158    #[serde(default)]
1159    pub rigid_bodies: Vec<PmxPartsRigidBodyDescriptor>,
1160    #[serde(default)]
1161    pub joints: Vec<PmxPartsJointDescriptor>,
1162    #[serde(default)]
1163    pub index_sizes: PmxPartsIndexSizes,
1164}
1165
1166#[derive(Debug, Clone, Serialize, Deserialize)]
1167#[serde(rename_all = "camelCase")]
1168pub struct PmxPartsMaterialDescriptor {
1169    #[serde(default = "default_pmx_parts_material_name")]
1170    pub name: String,
1171    #[serde(default)]
1172    pub english_name: String,
1173    #[serde(default)]
1174    pub texture_path: String,
1175    #[serde(default)]
1176    pub sphere_texture_path: String,
1177    #[serde(default)]
1178    pub sphere_mode: String,
1179    #[serde(default)]
1180    pub toon_texture_path: String,
1181    #[serde(default = "default_pmx_parts_shared_toon_index")]
1182    pub shared_toon_index: Option<u8>,
1183    #[serde(default = "default_pmx_parts_diffuse")]
1184    pub diffuse: [f32; 4],
1185    #[serde(default)]
1186    pub specular: [f32; 3],
1187    #[serde(default = "default_pmx_parts_specular_power")]
1188    pub specular_power: f32,
1189    #[serde(default = "default_pmx_parts_ambient")]
1190    pub ambient: [f32; 3],
1191    #[serde(default = "default_pmx_parts_edge_color")]
1192    pub edge_color: [f32; 4],
1193    #[serde(default = "default_pmx_parts_edge_size")]
1194    pub edge_size: f32,
1195    #[serde(default)]
1196    pub flags: PmxPartsMaterialFlags,
1197    #[serde(default)]
1198    pub face_count: i32,
1199}
1200
1201#[derive(Debug, Clone, Serialize, Deserialize)]
1202#[serde(rename_all = "camelCase")]
1203pub struct PmxPartsMaterialFlags {
1204    #[serde(default = "default_true")]
1205    pub double_sided: bool,
1206    #[serde(default = "default_true")]
1207    pub ground_shadow: bool,
1208    #[serde(default = "default_true")]
1209    pub self_shadow_map: bool,
1210    #[serde(default = "default_true")]
1211    pub self_shadow: bool,
1212    #[serde(default)]
1213    pub edge: bool,
1214    #[serde(default)]
1215    pub vertex_color: bool,
1216    #[serde(default)]
1217    pub point_draw: bool,
1218    #[serde(default)]
1219    pub line_draw: bool,
1220}
1221
1222impl Default for PmxPartsMaterialFlags {
1223    fn default() -> Self {
1224        Self {
1225            double_sided: true,
1226            ground_shadow: true,
1227            self_shadow_map: true,
1228            self_shadow: true,
1229            edge: false,
1230            vertex_color: false,
1231            point_draw: false,
1232            line_draw: false,
1233        }
1234    }
1235}
1236
1237#[derive(Debug, Clone, Serialize, Deserialize)]
1238#[serde(rename_all = "camelCase")]
1239pub struct PmxPartsBoneDescriptor {
1240    #[serde(default = "default_pmx_parts_root_bone_name")]
1241    pub name: String,
1242    #[serde(default)]
1243    pub english_name: String,
1244    #[serde(default = "default_negative_index")]
1245    pub parent_index: i32,
1246    #[serde(default)]
1247    pub layer: i32,
1248    #[serde(default)]
1249    pub position: [f32; 3],
1250    #[serde(default = "default_negative_index")]
1251    pub tail_index: i32,
1252    #[serde(default)]
1253    pub tail_position: Option<[f32; 3]>,
1254    #[serde(default)]
1255    pub rotatable: bool,
1256    #[serde(default)]
1257    pub translatable: bool,
1258    #[serde(default = "default_true")]
1259    pub visible: bool,
1260    #[serde(default = "default_true")]
1261    pub enabled: bool,
1262}
1263
1264#[derive(Debug, Clone, Serialize, Deserialize)]
1265#[serde(rename_all = "camelCase")]
1266pub struct PmxPartsMorphDescriptor {
1267    #[serde(default)]
1268    pub name: String,
1269    #[serde(default)]
1270    pub english_name: String,
1271    #[serde(default = "default_pmx_parts_morph_kind", alias = "type")]
1272    pub kind: String,
1273    #[serde(default)]
1274    pub vertex_offsets: Vec<PmxPartsVertexMorphOffset>,
1275    #[serde(default)]
1276    pub group_offsets: Vec<PmxPartsGroupMorphOffset>,
1277}
1278
1279#[derive(Debug, Clone, Serialize, Deserialize)]
1280#[serde(rename_all = "camelCase")]
1281pub struct PmxPartsVertexMorphOffset {
1282    pub vertex_index: u32,
1283    pub position: [f32; 3],
1284}
1285
1286#[derive(Debug, Clone, Serialize, Deserialize)]
1287#[serde(rename_all = "camelCase")]
1288pub struct PmxPartsGroupMorphOffset {
1289    pub morph_index: i32,
1290    pub weight: f32,
1291}
1292
1293#[derive(Debug, Clone, Serialize, Deserialize)]
1294#[serde(rename_all = "camelCase")]
1295pub struct PmxPartsDisplayFrameDescriptor {
1296    #[serde(default)]
1297    pub name: String,
1298    #[serde(default)]
1299    pub english_name: String,
1300    #[serde(default)]
1301    pub special: bool,
1302    #[serde(default)]
1303    pub frames: Vec<PmxPartsDisplayFrameItem>,
1304}
1305
1306#[derive(Debug, Clone, Serialize, Deserialize)]
1307#[serde(rename_all = "camelCase")]
1308pub struct PmxPartsDisplayFrameItem {
1309    #[serde(default = "default_pmx_parts_display_frame_kind")]
1310    pub kind: String,
1311    #[serde(default)]
1312    pub index: i32,
1313}
1314
1315#[derive(Debug, Clone, Serialize, Deserialize)]
1316#[serde(rename_all = "camelCase")]
1317pub struct PmxPartsRigidBodyDescriptor {
1318    #[serde(default)]
1319    pub name: String,
1320    #[serde(default)]
1321    pub english_name: String,
1322    #[serde(default = "default_negative_index")]
1323    pub bone_index: i32,
1324    #[serde(default)]
1325    pub group: u8,
1326    #[serde(default)]
1327    pub mask: u16,
1328    #[serde(default = "default_pmx_parts_rigid_body_shape")]
1329    pub shape: String,
1330    #[serde(default = "default_unit_vec3")]
1331    pub size: [f32; 3],
1332    #[serde(default)]
1333    pub position: [f32; 3],
1334    #[serde(default)]
1335    pub rotation: [f32; 3],
1336    #[serde(default = "default_pmx_parts_mass")]
1337    pub mass: f32,
1338    #[serde(default)]
1339    pub linear_damping: f32,
1340    #[serde(default)]
1341    pub angular_damping: f32,
1342    #[serde(default)]
1343    pub restitution: f32,
1344    #[serde(default)]
1345    pub friction: f32,
1346    #[serde(default = "default_pmx_parts_rigid_body_mode")]
1347    pub mode: String,
1348}
1349
1350#[derive(Debug, Clone, Serialize, Deserialize)]
1351#[serde(rename_all = "camelCase")]
1352pub struct PmxPartsJointDescriptor {
1353    #[serde(default)]
1354    pub name: String,
1355    #[serde(default)]
1356    pub english_name: String,
1357    #[serde(
1358        default = "default_pmx_parts_joint_kind",
1359        rename = "type",
1360        alias = "kind"
1361    )]
1362    pub kind: String,
1363    #[serde(default = "default_negative_index")]
1364    pub rigid_body_index_a: i32,
1365    #[serde(default = "default_negative_index")]
1366    pub rigid_body_index_b: i32,
1367    #[serde(default)]
1368    pub position: [f32; 3],
1369    #[serde(default)]
1370    pub rotation: [f32; 3],
1371    #[serde(default)]
1372    pub translation_lower_limit: [f32; 3],
1373    #[serde(default)]
1374    pub translation_upper_limit: [f32; 3],
1375    #[serde(default)]
1376    pub rotation_lower_limit: [f32; 3],
1377    #[serde(default)]
1378    pub rotation_upper_limit: [f32; 3],
1379    #[serde(default)]
1380    pub spring_translation_factor: [f32; 3],
1381    #[serde(default)]
1382    pub spring_rotation_factor: [f32; 3],
1383}
1384
1385#[derive(Debug, Clone, Serialize, Deserialize)]
1386#[serde(rename_all = "camelCase")]
1387pub struct PmxPartsIndexSizes {
1388    #[serde(default = "default_pmx_vertex_index_size")]
1389    pub vertex: u8,
1390    #[serde(default = "default_pmx_small_index_size")]
1391    pub texture: u8,
1392    #[serde(default = "default_pmx_small_index_size")]
1393    pub material: u8,
1394    #[serde(default = "default_pmx_small_index_size")]
1395    pub bone: u8,
1396    #[serde(default = "default_pmx_small_index_size")]
1397    pub morph: u8,
1398    #[serde(default = "default_pmx_small_index_size")]
1399    pub rigid_body: u8,
1400}
1401
1402impl Default for PmxPartsIndexSizes {
1403    fn default() -> Self {
1404        Self {
1405            vertex: default_pmx_vertex_index_size(),
1406            texture: default_pmx_small_index_size(),
1407            material: default_pmx_small_index_size(),
1408            bone: default_pmx_small_index_size(),
1409            morph: default_pmx_small_index_size(),
1410            rigid_body: default_pmx_small_index_size(),
1411        }
1412    }
1413}
1414
1415pub struct PmxPartsInput<'a> {
1416    pub descriptor: PmxPartsDescriptor,
1417    pub positions_xyz: &'a [f32],
1418    pub normals_xyz: &'a [f32],
1419    pub uvs_xy: &'a [f32],
1420    pub indices: &'a [u32],
1421    pub skin_indices: &'a [u32],
1422    pub skin_weights: &'a [f32],
1423    pub edge_scale: &'a [f32],
1424}
1425
1426fn default_pmx_format() -> String {
1427    "pmx".to_owned()
1428}
1429
1430fn default_pmx_parts_version() -> f32 {
1431    2.0
1432}
1433
1434fn default_pmx_parts_encoding() -> String {
1435    "utf-8".to_owned()
1436}
1437
1438fn default_pmx_parts_name() -> String {
1439    "model".to_owned()
1440}
1441
1442fn default_pmx_parts_root_bone_name() -> String {
1443    "root".to_owned()
1444}
1445
1446fn default_negative_index() -> i32 {
1447    -1
1448}
1449
1450fn default_pmx_parts_display_frame_kind() -> String {
1451    "bone".to_owned()
1452}
1453
1454fn default_pmx_parts_morph_kind() -> String {
1455    "vertex".to_owned()
1456}
1457
1458fn default_pmx_parts_rigid_body_shape() -> String {
1459    "sphere".to_owned()
1460}
1461
1462fn default_pmx_parts_rigid_body_mode() -> String {
1463    "static".to_owned()
1464}
1465
1466fn default_pmx_parts_joint_kind() -> String {
1467    "generic6dofSpring".to_owned()
1468}
1469
1470fn default_unit_vec3() -> [f32; 3] {
1471    [1.0, 1.0, 1.0]
1472}
1473
1474fn default_pmx_parts_mass() -> f32 {
1475    1.0
1476}
1477
1478fn default_pmx_parts_material_name() -> String {
1479    "material".to_owned()
1480}
1481
1482fn default_pmx_parts_shared_toon_index() -> Option<u8> {
1483    Some(0)
1484}
1485
1486fn default_pmx_parts_diffuse() -> [f32; 4] {
1487    [0.8, 0.8, 0.8, 1.0]
1488}
1489
1490fn default_pmx_parts_specular_power() -> f32 {
1491    1.0
1492}
1493
1494fn default_pmx_parts_ambient() -> [f32; 3] {
1495    [0.2, 0.2, 0.2]
1496}
1497
1498fn default_pmx_parts_edge_color() -> [f32; 4] {
1499    [0.0, 0.0, 0.0, 1.0]
1500}
1501
1502fn default_pmx_parts_edge_size() -> f32 {
1503    1.0
1504}
1505
1506fn default_true() -> bool {
1507    true
1508}
1509
1510fn default_pmx_vertex_index_size() -> u8 {
1511    4
1512}
1513
1514fn default_pmx_small_index_size() -> u8 {
1515    1
1516}
1517
1518pub fn build_pmx_model_from_parts(input: PmxPartsInput<'_>) -> Result<PmxParsedModel, String> {
1519    let vertex_count = validate_pmx_parts_geometry(
1520        input.positions_xyz,
1521        input.normals_xyz,
1522        input.uvs_xy,
1523        input.indices,
1524        input.skin_indices,
1525        input.skin_weights,
1526        input.edge_scale,
1527    )?;
1528    validate_pmx_parts_descriptor(
1529        &input.descriptor,
1530        vertex_count,
1531        input.indices,
1532        input.skin_indices,
1533    )?;
1534
1535    let skin_indices = if input.skin_indices.is_empty() {
1536        vec![0; vertex_count * 4]
1537    } else {
1538        input.skin_indices.to_vec()
1539    };
1540    let skin_weights = if input.skin_weights.is_empty() {
1541        default_pmx_skin_weights(vertex_count)
1542    } else {
1543        input.skin_weights.to_vec()
1544    };
1545    let edge_scale = if input.edge_scale.is_empty() {
1546        vec![1.0; vertex_count]
1547    } else {
1548        input.edge_scale.to_vec()
1549    };
1550    let face_count = input.indices.len() / 3;
1551    let (materials, material_groups) = build_pmx_parts_materials(&input.descriptor, face_count);
1552    let bones = build_pmx_parts_bones(&input.descriptor);
1553    let morphs = build_pmx_parts_morphs(&input.descriptor);
1554    let display_frames = build_pmx_parts_display_frames(&input.descriptor);
1555    let rigid_bodies = build_pmx_parts_rigid_bodies(&input.descriptor);
1556    let joints = build_pmx_parts_joints(&input.descriptor);
1557
1558    let model = PmxParsedModel {
1559        metadata: PmxParsedMetadata {
1560            format: "pmx".to_owned(),
1561            version: input.descriptor.version,
1562            encoding: input.descriptor.encoding,
1563            name: input.descriptor.name,
1564            english_name: input.descriptor.english_name,
1565            comment: input.descriptor.comment,
1566            english_comment: input.descriptor.english_comment,
1567            counts: PmxParsedCounts {
1568                vertices: vertex_count,
1569                faces: face_count,
1570                materials: materials.len(),
1571                bones: bones.len(),
1572                morphs: morphs.len(),
1573                display_frames: display_frames.len(),
1574                rigid_bodies: rigid_bodies.len(),
1575                joints: joints.len(),
1576                soft_bodies: 0,
1577            },
1578            index_sizes: PmxParsedIndexSizes {
1579                vertex: input.descriptor.index_sizes.vertex,
1580                texture: input.descriptor.index_sizes.texture,
1581                material: input.descriptor.index_sizes.material,
1582                bone: input.descriptor.index_sizes.bone,
1583                morph: input.descriptor.index_sizes.morph,
1584                rigid_body: input.descriptor.index_sizes.rigid_body,
1585            },
1586            additional_uv_count: 0,
1587        },
1588        geometry: PmxParsedGeometry {
1589            positions: input.positions_xyz.to_vec(),
1590            normals: input.normals_xyz.to_vec(),
1591            uvs: input.uvs_xy.to_vec(),
1592            additional_uvs: Vec::new(),
1593            indices: input.indices.to_vec(),
1594            skin_indices,
1595            skin_weights,
1596            edge_scale,
1597            material_groups,
1598        },
1599        materials,
1600        skeleton: PmxParsedSkeleton { bones },
1601        morphs,
1602        display_frames,
1603        rigid_bodies,
1604        joints,
1605        soft_bodies: Vec::new(),
1606        diagnostics: Vec::new(),
1607    };
1608    validate_pmx_export_model(&model)?;
1609    Ok(model)
1610}
1611
1612pub fn validate_pmx_export_model(model: &PmxParsedModel) -> Result<(), String> {
1613    let geometry = &model.geometry;
1614    if !geometry.positions.len().is_multiple_of(3) {
1615        return Err(format!(
1616            "PMX export positions length must be divisible by 3: {}",
1617            geometry.positions.len()
1618        ));
1619    }
1620    let vertex_count = geometry.positions.len() / 3;
1621    if geometry.normals.len() != vertex_count * 3 {
1622        return Err(format!(
1623            "PMX export normals length mismatch: expected {}, got {}",
1624            vertex_count * 3,
1625            geometry.normals.len()
1626        ));
1627    }
1628    if geometry.uvs.len() != vertex_count * 2 {
1629        return Err(format!(
1630            "PMX export uvs length mismatch: expected {}, got {}",
1631            vertex_count * 2,
1632            geometry.uvs.len()
1633        ));
1634    }
1635    if geometry.additional_uvs.len() != model.metadata.additional_uv_count as usize {
1636        return Err(format!(
1637            "PMX export additionalUvs set count mismatch: expected {}, got {}",
1638            model.metadata.additional_uv_count,
1639            geometry.additional_uvs.len()
1640        ));
1641    }
1642    for (index, values) in geometry.additional_uvs.iter().enumerate() {
1643        if values.len() != vertex_count * 4 {
1644            return Err(format!(
1645                "PMX export additionalUvs[{}] length mismatch: expected {}, got {}",
1646                index,
1647                vertex_count * 4,
1648                values.len()
1649            ));
1650        }
1651    }
1652    if !geometry.indices.len().is_multiple_of(3) {
1653        return Err(format!(
1654            "PMX export indices length must be divisible by 3: {}",
1655            geometry.indices.len()
1656        ));
1657    }
1658    let expected_skin_len = vertex_count * 4;
1659    if geometry.skin_indices.is_empty() != geometry.skin_weights.is_empty() {
1660        return Err(
1661            "PMX export skinIndices and skinWeights must be both empty or both provided".to_owned(),
1662        );
1663    }
1664    if !geometry.skin_indices.is_empty() && geometry.skin_indices.len() != expected_skin_len {
1665        return Err(format!(
1666            "PMX export skinIndices length mismatch: expected {}, got {}",
1667            expected_skin_len,
1668            geometry.skin_indices.len()
1669        ));
1670    }
1671    if !geometry.skin_weights.is_empty() && geometry.skin_weights.len() != expected_skin_len {
1672        return Err(format!(
1673            "PMX export skinWeights length mismatch: expected {}, got {}",
1674            expected_skin_len,
1675            geometry.skin_weights.len()
1676        ));
1677    }
1678    Ok(())
1679}
1680
1681fn validate_pmx_parts_geometry(
1682    positions_xyz: &[f32],
1683    normals_xyz: &[f32],
1684    uvs_xy: &[f32],
1685    indices: &[u32],
1686    skin_indices: &[u32],
1687    skin_weights: &[f32],
1688    edge_scale: &[f32],
1689) -> Result<usize, String> {
1690    if !positions_xyz.len().is_multiple_of(3) {
1691        return Err(format!(
1692            "positions_xyz must contain vertex_count * 3 values, got {}",
1693            positions_xyz.len()
1694        ));
1695    }
1696    let vertex_count = positions_xyz.len() / 3;
1697    if vertex_count == 0 {
1698        return Err("PMX parts export requires at least one vertex".to_owned());
1699    }
1700    if normals_xyz.len() != vertex_count * 3 {
1701        return Err(format!(
1702            "normals_xyz must contain vertex_count * 3 values, expected {}, got {}",
1703            vertex_count * 3,
1704            normals_xyz.len()
1705        ));
1706    }
1707    if uvs_xy.len() != vertex_count * 2 {
1708        return Err(format!(
1709            "uvs_xy must contain vertex_count * 2 values, expected {}, got {}",
1710            vertex_count * 2,
1711            uvs_xy.len()
1712        ));
1713    }
1714    if !indices.len().is_multiple_of(3) {
1715        return Err(format!(
1716            "indices must contain triangle indices and be divisible by 3, got {}",
1717            indices.len()
1718        ));
1719    }
1720    if let Some(&index) = indices
1721        .iter()
1722        .find(|&&index| index as usize >= vertex_count)
1723    {
1724        return Err(format!(
1725            "indices contains out-of-range vertex index {index} for vertex_count {vertex_count}"
1726        ));
1727    }
1728    if !skin_indices.is_empty() && skin_indices.len() != vertex_count * 4 {
1729        return Err(format!(
1730            "skin_indices must contain vertex_count * 4 values when provided, expected {}, got {}",
1731            vertex_count * 4,
1732            skin_indices.len()
1733        ));
1734    }
1735    if !skin_weights.is_empty() && skin_weights.len() != vertex_count * 4 {
1736        return Err(format!(
1737            "skin_weights must contain vertex_count * 4 values when provided, expected {}, got {}",
1738            vertex_count * 4,
1739            skin_weights.len()
1740        ));
1741    }
1742    if skin_indices.is_empty() != skin_weights.is_empty() {
1743        return Err(
1744            "skin_indices and skin_weights must either both be provided or both be empty"
1745                .to_owned(),
1746        );
1747    }
1748    if !edge_scale.is_empty() && edge_scale.len() != vertex_count {
1749        return Err(format!(
1750            "edge_scale must contain vertex_count values when provided, expected {}, got {}",
1751            vertex_count,
1752            edge_scale.len()
1753        ));
1754    }
1755    Ok(vertex_count)
1756}
1757
1758fn validate_pmx_parts_descriptor(
1759    descriptor: &PmxPartsDescriptor,
1760    vertex_count: usize,
1761    indices: &[u32],
1762    skin_indices: &[u32],
1763) -> Result<(), String> {
1764    if descriptor.version < 2.0 {
1765        return Err(format!(
1766            "PMX parts export supports PMX 2.0 or newer, got {}",
1767            descriptor.version
1768        ));
1769    }
1770    if descriptor.encoding != "utf-8" && descriptor.encoding != "utf-16-le" {
1771        return Err(format!(
1772            "PMX parts export encoding must be utf-8 or utf-16-le, got {}",
1773            descriptor.encoding
1774        ));
1775    }
1776    validate_pmx_index_size("vertex", descriptor.index_sizes.vertex)?;
1777    validate_pmx_index_size("texture", descriptor.index_sizes.texture)?;
1778    validate_pmx_index_size("material", descriptor.index_sizes.material)?;
1779    validate_pmx_index_size("bone", descriptor.index_sizes.bone)?;
1780    validate_pmx_index_size("morph", descriptor.index_sizes.morph)?;
1781    validate_pmx_index_size("rigidBody", descriptor.index_sizes.rigid_body)?;
1782    if indices.len() / 3 > i32::MAX as usize {
1783        return Err(format!(
1784            "face count {} exceeds PMX i32 material face count limit",
1785            indices.len() / 3
1786        ));
1787    }
1788    if !pmx_vertex_index_size_can_hold(descriptor.index_sizes.vertex, vertex_count) {
1789        return Err(format!(
1790            "vertex index size {} cannot hold vertex_count {}",
1791            descriptor.index_sizes.vertex, vertex_count
1792        ));
1793    }
1794    if !indices.is_empty()
1795        && !pmx_vertex_index_size_can_hold(
1796            descriptor.index_sizes.vertex,
1797            indices.iter().copied().max().unwrap_or(0) as usize + 1,
1798        )
1799    {
1800        return Err(format!(
1801            "vertex index size {} cannot hold max index {}",
1802            descriptor.index_sizes.vertex,
1803            indices.iter().copied().max().unwrap_or(0)
1804        ));
1805    }
1806    validate_pmx_parts_materials(descriptor, indices.len() / 3)?;
1807    validate_pmx_parts_bones(descriptor)?;
1808    validate_pmx_parts_morphs(descriptor, vertex_count)?;
1809    validate_pmx_parts_skin_indices(descriptor, skin_indices)?;
1810    validate_pmx_parts_display_frames(descriptor)?;
1811    validate_pmx_parts_physics(descriptor)?;
1812    Ok(())
1813}
1814
1815fn validate_pmx_parts_materials(
1816    descriptor: &PmxPartsDescriptor,
1817    face_count: usize,
1818) -> Result<(), String> {
1819    if descriptor.materials.is_empty() {
1820        return Ok(());
1821    }
1822    if !pmx_signed_index_size_can_hold_count(
1823        descriptor.index_sizes.material,
1824        descriptor.materials.len(),
1825    ) {
1826        return Err(format!(
1827            "material index size {} cannot hold material count {}",
1828            descriptor.index_sizes.material,
1829            descriptor.materials.len()
1830        ));
1831    }
1832    let mut total = 0usize;
1833    for (index, material) in descriptor.materials.iter().enumerate() {
1834        if material.face_count < 0 {
1835            return Err(format!(
1836                "materials[{index}].faceCount must be non-negative, got {}",
1837                material.face_count
1838            ));
1839        }
1840        total += material.face_count as usize;
1841    }
1842    if total != face_count {
1843        return Err(format!(
1844            "materials faceCount sum must match index face count, expected {}, got {}",
1845            face_count, total
1846        ));
1847    }
1848    Ok(())
1849}
1850
1851fn validate_pmx_parts_bones(descriptor: &PmxPartsDescriptor) -> Result<(), String> {
1852    let bone_count = descriptor.bones.len().max(1);
1853    if !pmx_signed_index_size_can_hold_count(descriptor.index_sizes.bone, bone_count) {
1854        return Err(format!(
1855            "bone index size {} cannot hold bone count {}",
1856            descriptor.index_sizes.bone, bone_count
1857        ));
1858    }
1859    for (index, bone) in descriptor.bones.iter().enumerate() {
1860        if bone.parent_index < -1
1861            || (bone.parent_index != -1 && bone.parent_index as usize >= bone_count)
1862        {
1863            return Err(format!(
1864                "bones[{index}].parentIndex must be -1 or a valid bone index, got {}",
1865                bone.parent_index
1866            ));
1867        }
1868        if bone.tail_index < -1 || (bone.tail_index != -1 && bone.tail_index as usize >= bone_count)
1869        {
1870            return Err(format!(
1871                "bones[{index}].tailIndex must be -1 or a valid bone index, got {}",
1872                bone.tail_index
1873            ));
1874        }
1875    }
1876    Ok(())
1877}
1878
1879fn validate_pmx_parts_skin_indices(
1880    descriptor: &PmxPartsDescriptor,
1881    skin_indices: &[u32],
1882) -> Result<(), String> {
1883    if skin_indices.is_empty() {
1884        return Ok(());
1885    }
1886    let bone_count = descriptor.bones.len().max(1);
1887    if let Some(&index) = skin_indices
1888        .iter()
1889        .find(|&&index| index as usize >= bone_count)
1890    {
1891        return Err(format!(
1892            "skin_indices contains out-of-range bone index {index} for bone count {bone_count}"
1893        ));
1894    }
1895    Ok(())
1896}
1897
1898fn validate_pmx_parts_morphs(
1899    descriptor: &PmxPartsDescriptor,
1900    vertex_count: usize,
1901) -> Result<(), String> {
1902    if !pmx_signed_index_size_can_hold_count(descriptor.index_sizes.morph, descriptor.morphs.len())
1903    {
1904        return Err(format!(
1905            "morph index size {} cannot hold morph count {}",
1906            descriptor.index_sizes.morph,
1907            descriptor.morphs.len()
1908        ));
1909    }
1910    for (morph_index, morph) in descriptor.morphs.iter().enumerate() {
1911        match morph.kind.as_str() {
1912            "vertex" => {
1913                for (offset_index, offset) in morph.vertex_offsets.iter().enumerate() {
1914                    if offset.vertex_index as usize >= vertex_count {
1915                        return Err(format!(
1916                            "morphs[{morph_index}].vertexOffsets[{offset_index}].vertexIndex out of range: {} for vertex_count {vertex_count}",
1917                            offset.vertex_index
1918                        ));
1919                    }
1920                }
1921            }
1922            "group" => {
1923                for (offset_index, offset) in morph.group_offsets.iter().enumerate() {
1924                    if offset.morph_index < 0
1925                        || offset.morph_index as usize >= descriptor.morphs.len()
1926                    {
1927                        return Err(format!(
1928                            "morphs[{morph_index}].groupOffsets[{offset_index}].morphIndex out of range: {}",
1929                            offset.morph_index
1930                        ));
1931                    }
1932                }
1933            }
1934            other => {
1935                return Err(format!(
1936                    "morphs[{morph_index}].kind must be vertex or group, got {other}"
1937                ));
1938            }
1939        }
1940    }
1941    Ok(())
1942}
1943
1944fn validate_pmx_parts_display_frames(descriptor: &PmxPartsDescriptor) -> Result<(), String> {
1945    let bone_count = descriptor.bones.len().max(1);
1946    let morph_count = descriptor.morphs.len();
1947    for (frame_index, frame) in descriptor.display_frames.iter().enumerate() {
1948        for (item_index, item) in frame.frames.iter().enumerate() {
1949            match item.kind.as_str() {
1950                "bone" => {
1951                    if item.index < 0 || item.index as usize >= bone_count {
1952                        return Err(format!(
1953                            "displayFrames[{frame_index}].frames[{item_index}].index must reference an existing bone, got {}",
1954                            item.index
1955                        ));
1956                    }
1957                }
1958                "morph" => {
1959                    if item.index < 0 || item.index as usize >= morph_count {
1960                        return Err(format!(
1961                            "displayFrames[{frame_index}].frames[{item_index}].index must reference an existing morph, got {}",
1962                            item.index
1963                        ));
1964                    }
1965                }
1966                other => {
1967                    return Err(format!(
1968                        "displayFrames[{frame_index}].frames[{item_index}].kind must be bone or morph, got {other}"
1969                    ));
1970                }
1971            }
1972        }
1973    }
1974    Ok(())
1975}
1976
1977fn validate_pmx_parts_physics(descriptor: &PmxPartsDescriptor) -> Result<(), String> {
1978    let bone_count = descriptor.bones.len().max(1);
1979    if !pmx_signed_index_size_can_hold_count(
1980        descriptor.index_sizes.rigid_body,
1981        descriptor.rigid_bodies.len(),
1982    ) {
1983        return Err(format!(
1984            "rigidBody index size {} cannot hold rigid body count {}",
1985            descriptor.index_sizes.rigid_body,
1986            descriptor.rigid_bodies.len()
1987        ));
1988    }
1989    for (index, body) in descriptor.rigid_bodies.iter().enumerate() {
1990        if body.bone_index < -1 || (body.bone_index != -1 && body.bone_index as usize >= bone_count)
1991        {
1992            return Err(format!(
1993                "rigidBodies[{index}].boneIndex must be -1 or a valid bone index, got {}",
1994                body.bone_index
1995            ));
1996        }
1997        if !matches!(body.shape.as_str(), "sphere" | "box" | "capsule") {
1998            return Err(format!(
1999                "rigidBodies[{index}].shape must be sphere, box, or capsule, got {}",
2000                body.shape
2001            ));
2002        }
2003        if !matches!(body.mode.as_str(), "static" | "dynamic" | "dynamicBone") {
2004            return Err(format!(
2005                "rigidBodies[{index}].mode must be static, dynamic, or dynamicBone, got {}",
2006                body.mode
2007            ));
2008        }
2009    }
2010    for (index, joint) in descriptor.joints.iter().enumerate() {
2011        validate_pmx_parts_rigid_body_ref(
2012            "rigidBodyIndexA",
2013            index,
2014            joint.rigid_body_index_a,
2015            descriptor.rigid_bodies.len(),
2016        )?;
2017        validate_pmx_parts_rigid_body_ref(
2018            "rigidBodyIndexB",
2019            index,
2020            joint.rigid_body_index_b,
2021            descriptor.rigid_bodies.len(),
2022        )?;
2023        if !matches!(
2024            joint.kind.as_str(),
2025            "generic6dofSpring" | "generic6dof" | "point2point" | "coneTwist" | "slider" | "hinge"
2026        ) {
2027            return Err(format!(
2028                "joints[{index}].kind has unsupported PMX joint type {}",
2029                joint.kind
2030            ));
2031        }
2032    }
2033    Ok(())
2034}
2035
2036fn validate_pmx_parts_rigid_body_ref(
2037    field: &str,
2038    joint_index: usize,
2039    value: i32,
2040    rigid_body_count: usize,
2041) -> Result<(), String> {
2042    if value < -1 || (value != -1 && value as usize >= rigid_body_count) {
2043        Err(format!(
2044            "joints[{joint_index}].{field} must be -1 or a valid rigid body index, got {value}"
2045        ))
2046    } else {
2047        Ok(())
2048    }
2049}
2050
2051fn validate_pmx_index_size(name: &str, value: u8) -> Result<(), String> {
2052    if matches!(value, 1 | 2 | 4) {
2053        Ok(())
2054    } else {
2055        Err(format!(
2056            "PMX {name} index size must be 1, 2, or 4, got {value}"
2057        ))
2058    }
2059}
2060
2061fn pmx_vertex_index_size_can_hold(size: u8, count: usize) -> bool {
2062    match size {
2063        1 => count <= u8::MAX as usize + 1,
2064        2 => count <= u16::MAX as usize + 1,
2065        4 => true,
2066        _ => false,
2067    }
2068}
2069
2070fn pmx_signed_index_size_can_hold_count(size: u8, count: usize) -> bool {
2071    match size {
2072        1 => count <= i8::MAX as usize + 1,
2073        2 => count <= i16::MAX as usize + 1,
2074        4 => true,
2075        _ => false,
2076    }
2077}
2078
2079fn default_pmx_skin_weights(vertex_count: usize) -> Vec<f32> {
2080    let mut weights = vec![0.0; vertex_count * 4];
2081    for vertex in 0..vertex_count {
2082        weights[vertex * 4] = 1.0;
2083    }
2084    weights
2085}
2086
2087fn build_pmx_parts_materials(
2088    descriptor: &PmxPartsDescriptor,
2089    face_count: usize,
2090) -> (Vec<PmxParsedMaterial>, Vec<PmxParsedMaterialGroup>) {
2091    if face_count == 0 {
2092        return (Vec::new(), Vec::new());
2093    }
2094    if descriptor.materials.is_empty() {
2095        return (
2096            vec![default_pmx_parts_material(
2097                &descriptor.material_name,
2098                &descriptor.english_material_name,
2099                face_count as i32,
2100            )],
2101            vec![PmxParsedMaterialGroup {
2102                start: 0,
2103                count: face_count * 3,
2104                material_index: 0,
2105            }],
2106        );
2107    }
2108
2109    let mut start = 0usize;
2110    let mut materials = Vec::with_capacity(descriptor.materials.len());
2111    let mut groups = Vec::with_capacity(descriptor.materials.len());
2112    for (index, material) in descriptor.materials.iter().enumerate() {
2113        let index_count = material.face_count as usize * 3;
2114        materials.push(pmx_parts_material_from_descriptor(material));
2115        groups.push(PmxParsedMaterialGroup {
2116            start,
2117            count: index_count,
2118            material_index: index,
2119        });
2120        start += index_count;
2121    }
2122    (materials, groups)
2123}
2124
2125fn pmx_parts_material_from_descriptor(material: &PmxPartsMaterialDescriptor) -> PmxParsedMaterial {
2126    PmxParsedMaterial {
2127        name: material.name.clone(),
2128        english_name: material.english_name.clone(),
2129        texture_path: material.texture_path.clone(),
2130        sphere_texture_path: material.sphere_texture_path.clone(),
2131        sphere_mode: if material.sphere_mode.is_empty() {
2132            "none".to_owned()
2133        } else {
2134            material.sphere_mode.clone()
2135        },
2136        toon_texture_path: material.toon_texture_path.clone(),
2137        shared_toon_index: material.shared_toon_index,
2138        diffuse: material.diffuse,
2139        specular: material.specular,
2140        specular_power: material.specular_power,
2141        ambient: material.ambient,
2142        edge_color: material.edge_color,
2143        edge_size: material.edge_size,
2144        flags: PmxParsedMaterialFlags {
2145            double_sided: material.flags.double_sided,
2146            ground_shadow: material.flags.ground_shadow,
2147            self_shadow_map: material.flags.self_shadow_map,
2148            self_shadow: material.flags.self_shadow,
2149            edge: material.flags.edge,
2150            vertex_color: material.flags.vertex_color,
2151            point_draw: material.flags.point_draw,
2152            line_draw: material.flags.line_draw,
2153        },
2154        face_count: material.face_count,
2155    }
2156}
2157
2158fn build_pmx_parts_bones(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedBone> {
2159    if descriptor.bones.is_empty() {
2160        return vec![default_pmx_parts_root_bone()];
2161    }
2162    descriptor
2163        .bones
2164        .iter()
2165        .map(pmx_parts_bone_from_descriptor)
2166        .collect()
2167}
2168
2169fn build_pmx_parts_display_frames(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedDisplayFrame> {
2170    descriptor
2171        .display_frames
2172        .iter()
2173        .map(|frame| PmxParsedDisplayFrame {
2174            name: frame.name.clone(),
2175            english_name: frame.english_name.clone(),
2176            special: frame.special,
2177            frames: frame
2178                .frames
2179                .iter()
2180                .map(|item| PmxParsedDisplayFrameElement {
2181                    kind: item.kind.clone(),
2182                    index: item.index,
2183                })
2184                .collect(),
2185        })
2186        .collect()
2187}
2188
2189fn build_pmx_parts_morphs(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedMorph> {
2190    descriptor
2191        .morphs
2192        .iter()
2193        .map(pmx_parts_morph_from_descriptor)
2194        .collect()
2195}
2196
2197fn build_pmx_parts_rigid_bodies(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedRigidBody> {
2198    descriptor
2199        .rigid_bodies
2200        .iter()
2201        .map(|body| PmxParsedRigidBody {
2202            name: body.name.clone(),
2203            english_name: body.english_name.clone(),
2204            bone_index: body.bone_index,
2205            group: body.group,
2206            mask: body.mask,
2207            shape: body.shape.clone(),
2208            size: body.size,
2209            position: body.position,
2210            rotation: body.rotation,
2211            mass: body.mass,
2212            linear_damping: body.linear_damping,
2213            angular_damping: body.angular_damping,
2214            restitution: body.restitution,
2215            friction: body.friction,
2216            mode: body.mode.clone(),
2217        })
2218        .collect()
2219}
2220
2221fn build_pmx_parts_joints(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedJoint> {
2222    descriptor
2223        .joints
2224        .iter()
2225        .map(|joint| PmxParsedJoint {
2226            name: joint.name.clone(),
2227            english_name: joint.english_name.clone(),
2228            kind: joint.kind.clone(),
2229            rigid_body_index_a: joint.rigid_body_index_a,
2230            rigid_body_index_b: joint.rigid_body_index_b,
2231            position: joint.position,
2232            rotation: joint.rotation,
2233            translation_lower_limit: joint.translation_lower_limit,
2234            translation_upper_limit: joint.translation_upper_limit,
2235            rotation_lower_limit: joint.rotation_lower_limit,
2236            rotation_upper_limit: joint.rotation_upper_limit,
2237            spring_translation_factor: joint.spring_translation_factor,
2238            spring_rotation_factor: joint.spring_rotation_factor,
2239        })
2240        .collect()
2241}
2242
2243fn pmx_parts_morph_from_descriptor(morph: &PmxPartsMorphDescriptor) -> PmxParsedMorph {
2244    let mut parsed = PmxParsedMorph {
2245        name: morph.name.clone(),
2246        english_name: morph.english_name.clone(),
2247        kind: morph.kind.clone(),
2248        vertex_offsets: Vec::new(),
2249        group_offsets: Vec::new(),
2250        bone_offsets: Vec::new(),
2251        uv_offsets: Vec::new(),
2252        additional_uv_offsets: Vec::new(),
2253        material_offsets: Vec::new(),
2254        flip_offsets: Vec::new(),
2255        impulse_offsets: Vec::new(),
2256    };
2257    match morph.kind.as_str() {
2258        "vertex" => {
2259            parsed.vertex_offsets = morph
2260                .vertex_offsets
2261                .iter()
2262                .map(|offset| PmxParsedVertexMorphOffset {
2263                    vertex_index: offset.vertex_index,
2264                    position: offset.position,
2265                })
2266                .collect();
2267        }
2268        "group" => {
2269            parsed.group_offsets = morph
2270                .group_offsets
2271                .iter()
2272                .map(|offset| PmxParsedGroupMorphOffset {
2273                    morph_index: offset.morph_index,
2274                    weight: offset.weight,
2275                })
2276                .collect();
2277        }
2278        _ => unreachable!("PmxPartsMorphDescriptor was validated before build"),
2279    }
2280    parsed
2281}
2282
2283fn pmx_parts_bone_from_descriptor(bone: &PmxPartsBoneDescriptor) -> PmxParsedBone {
2284    let tail_position = if bone.tail_index >= 0 {
2285        None
2286    } else {
2287        Some(bone.tail_position.unwrap_or([0.0, 1.0, 0.0]))
2288    };
2289    PmxParsedBone {
2290        name: bone.name.clone(),
2291        english_name: bone.english_name.clone(),
2292        parent_index: bone.parent_index,
2293        layer: bone.layer,
2294        position: bone.position,
2295        tail_index: bone.tail_index,
2296        tail_position,
2297        flags: PmxParsedBoneFlags {
2298            indexed_tail: bone.tail_index >= 0,
2299            rotatable: bone.rotatable,
2300            translatable: bone.translatable,
2301            visible: bone.visible,
2302            enabled: bone.enabled,
2303            ik: false,
2304            append_local: false,
2305            append_rotate: false,
2306            append_translate: false,
2307            fixed_axis: false,
2308            local_axis: false,
2309            external_parent_transform: false,
2310            transform_after_physics: false,
2311        },
2312        append_transform: None,
2313        fixed_axis: None,
2314        local_axis: None,
2315        external_parent_key: None,
2316        ik: None,
2317    }
2318}
2319
2320fn default_pmx_parts_material(
2321    name: &str,
2322    english_name: &str,
2323    face_count: i32,
2324) -> PmxParsedMaterial {
2325    PmxParsedMaterial {
2326        name: if name.is_empty() {
2327            "material".to_owned()
2328        } else {
2329            name.to_owned()
2330        },
2331        english_name: english_name.to_owned(),
2332        texture_path: String::new(),
2333        sphere_texture_path: String::new(),
2334        sphere_mode: "none".to_owned(),
2335        toon_texture_path: String::new(),
2336        shared_toon_index: Some(0),
2337        diffuse: [0.8, 0.8, 0.8, 1.0],
2338        specular: [0.0, 0.0, 0.0],
2339        specular_power: 1.0,
2340        ambient: [0.2, 0.2, 0.2],
2341        edge_color: [0.0, 0.0, 0.0, 1.0],
2342        edge_size: 1.0,
2343        flags: PmxParsedMaterialFlags {
2344            double_sided: true,
2345            ground_shadow: true,
2346            self_shadow_map: true,
2347            self_shadow: true,
2348            edge: false,
2349            vertex_color: false,
2350            point_draw: false,
2351            line_draw: false,
2352        },
2353        face_count,
2354    }
2355}
2356
2357fn default_pmx_parts_root_bone() -> PmxParsedBone {
2358    PmxParsedBone {
2359        name: "root".to_owned(),
2360        english_name: "root".to_owned(),
2361        parent_index: -1,
2362        layer: 0,
2363        position: [0.0, 0.0, 0.0],
2364        tail_index: -1,
2365        tail_position: Some([0.0, 1.0, 0.0]),
2366        flags: PmxParsedBoneFlags {
2367            indexed_tail: false,
2368            rotatable: true,
2369            translatable: true,
2370            visible: true,
2371            enabled: true,
2372            ik: false,
2373            append_local: false,
2374            append_rotate: false,
2375            append_translate: false,
2376            fixed_axis: false,
2377            local_axis: false,
2378            transform_after_physics: false,
2379            external_parent_transform: false,
2380        },
2381        append_transform: None,
2382        fixed_axis: None,
2383        local_axis: None,
2384        external_parent_key: None,
2385        ik: None,
2386    }
2387}
2388
2389pub fn parse_pmx_model(data: &[u8]) -> Result<PmxParsedModel, ImportError> {
2390    let (header, pos) = read_header(data)?;
2391    let mut r = Reader { data, pos };
2392    let name = r.read_string(header.encoding)?;
2393    let english_name = r.read_string(header.encoding)?;
2394    let comment = r.read_string(header.encoding)?;
2395    let english_comment = r.read_string(header.encoding)?;
2396
2397    let vertex_count = read_section_count(&mut r)?;
2398    let geometry = read_parsed_geometry(&mut r, &header, vertex_count)?;
2399    let index_count = geometry.indices.len();
2400    let textures = read_parsed_textures(&mut r, header.encoding)?;
2401    let materials = read_parsed_materials(&mut r, &header, &textures)?;
2402    let material_groups = build_material_groups(&materials);
2403    let bone_count = read_section_count(&mut r)?;
2404    let skeleton = PmxParsedSkeleton {
2405        bones: read_parsed_bones(&mut r, &header, bone_count)?,
2406    };
2407    let morphs = read_parsed_morphs(&mut r, &header)?;
2408    let display_frames = read_parsed_display_frames(&mut r, &header)?;
2409    let rigid_bodies = if r.remaining() >= 4 {
2410        read_parsed_rigid_bodies(&mut r, &header)?
2411    } else {
2412        Vec::new()
2413    };
2414    let joints = if r.remaining() >= 4 {
2415        read_parsed_joints(&mut r, &header)?
2416    } else {
2417        Vec::new()
2418    };
2419    let soft_bodies = if header.version >= 2.05 && r.remaining() >= 4 {
2420        read_parsed_soft_bodies(&mut r, &header)?
2421    } else {
2422        Vec::new()
2423    };
2424
2425    let mut geometry = geometry;
2426    geometry.material_groups = material_groups;
2427    let mut diagnostics = Vec::new();
2428    if !soft_bodies.is_empty() {
2429        diagnostics.push(PmxParserDiagnostic {
2430            level: "warning".to_owned(),
2431            code: "PMX_SOFT_BODY_UNSUPPORTED".to_owned(),
2432            message: format!(
2433                "{} PMX soft bodies are parsed only as header records by mmd-anim.",
2434                soft_bodies.len()
2435            ),
2436        });
2437    }
2438    if r.remaining() > 0 {
2439        diagnostics.push(PmxParserDiagnostic {
2440            level: "warning".to_owned(),
2441            code: "PMX_TRAILING_DATA_UNPARSED".to_owned(),
2442            message: format!("{} trailing PMX bytes were left unparsed.", r.remaining()),
2443        });
2444    }
2445
2446    Ok(PmxParsedModel {
2447        metadata: PmxParsedMetadata {
2448            format: "pmx".to_owned(),
2449            version: header.version,
2450            encoding: match header.encoding {
2451                TextEncoding::Utf8 => "utf-8".to_owned(),
2452                TextEncoding::Utf16Le => "utf-16-le".to_owned(),
2453            },
2454            name,
2455            english_name,
2456            comment,
2457            english_comment,
2458            counts: PmxParsedCounts {
2459                vertices: vertex_count,
2460                faces: index_count / 3,
2461                materials: materials.len(),
2462                bones: skeleton.bones.len(),
2463                morphs: morphs.len(),
2464                display_frames: display_frames.len(),
2465                rigid_bodies: rigid_bodies.len(),
2466                joints: joints.len(),
2467                soft_bodies: soft_bodies.len(),
2468            },
2469            index_sizes: PmxParsedIndexSizes {
2470                vertex: header.vertex_index_size,
2471                texture: header.texture_index_size,
2472                material: header.material_index_size,
2473                bone: header.bone_index_size,
2474                morph: header.morph_index_size,
2475                rigid_body: header.rigidbody_index_size,
2476            },
2477            additional_uv_count: header.extra_uv_count,
2478        },
2479        geometry,
2480        materials,
2481        skeleton,
2482        morphs,
2483        display_frames,
2484        rigid_bodies,
2485        joints,
2486        soft_bodies,
2487        diagnostics,
2488    })
2489}
2490
2491pub fn export_pmx_model(model: &PmxParsedModel) -> Vec<u8> {
2492    let encoding = pmx_text_encoding(&model.metadata);
2493    let mut out = Vec::new();
2494    out.extend_from_slice(&PMX_MAGIC);
2495    write_f32(&mut out, model.metadata.version);
2496    out.push(8);
2497    out.push(encoding as u8);
2498    out.push(model.metadata.additional_uv_count);
2499    out.push(model.metadata.index_sizes.vertex);
2500    out.push(model.metadata.index_sizes.texture);
2501    out.push(model.metadata.index_sizes.material);
2502    out.push(model.metadata.index_sizes.bone);
2503    out.push(model.metadata.index_sizes.morph);
2504    out.push(model.metadata.index_sizes.rigid_body);
2505
2506    write_pmx_string(&mut out, &model.metadata.name, encoding);
2507    write_pmx_string(&mut out, &model.metadata.english_name, encoding);
2508    write_pmx_string(&mut out, &model.metadata.comment, encoding);
2509    write_pmx_string(&mut out, &model.metadata.english_comment, encoding);
2510
2511    let vertex_count = model.geometry.positions.len() / 3;
2512    write_i32(&mut out, vertex_count as i32);
2513    for index in 0..vertex_count {
2514        write_f32_slice(
2515            &mut out,
2516            &model.geometry.positions[index * 3..index * 3 + 3],
2517        );
2518        write_f32_slice(&mut out, &model.geometry.normals[index * 3..index * 3 + 3]);
2519        write_f32_slice(&mut out, &model.geometry.uvs[index * 2..index * 2 + 2]);
2520        for uv in &model.geometry.additional_uvs {
2521            write_f32_slice(&mut out, &uv[index * 4..index * 4 + 4]);
2522        }
2523        out.push(2); // BDEF4 preserves the parsed skin index/weight arrays.
2524        for slot in 0..4 {
2525            write_sized_index(
2526                &mut out,
2527                model
2528                    .geometry
2529                    .skin_indices
2530                    .get(index * 4 + slot)
2531                    .copied()
2532                    .unwrap_or(0) as i32,
2533                model.metadata.index_sizes.bone,
2534            );
2535        }
2536        for slot in 0..4 {
2537            write_f32(
2538                &mut out,
2539                model
2540                    .geometry
2541                    .skin_weights
2542                    .get(index * 4 + slot)
2543                    .copied()
2544                    .unwrap_or(if slot == 0 { 1.0 } else { 0.0 }),
2545            );
2546        }
2547        write_f32(
2548            &mut out,
2549            model.geometry.edge_scale.get(index).copied().unwrap_or(1.0),
2550        );
2551    }
2552
2553    write_i32(&mut out, model.geometry.indices.len() as i32);
2554    for &index in &model.geometry.indices {
2555        write_vertex_index(&mut out, index, model.metadata.index_sizes.vertex);
2556    }
2557
2558    let textures = collect_pmx_textures(&model.materials);
2559    write_i32(&mut out, textures.len() as i32);
2560    for texture in &textures {
2561        write_pmx_string(&mut out, texture, encoding);
2562    }
2563
2564    write_i32(&mut out, model.materials.len() as i32);
2565    for material in &model.materials {
2566        write_pmx_string(&mut out, &material.name, encoding);
2567        write_pmx_string(&mut out, &material.english_name, encoding);
2568        write_f32_slice(&mut out, &material.diffuse);
2569        write_f32_slice(&mut out, &material.specular);
2570        write_f32(&mut out, material.specular_power);
2571        write_f32_slice(&mut out, &material.ambient);
2572        out.push(material_flag_bits(&material.flags));
2573        write_f32_slice(&mut out, &material.edge_color);
2574        write_f32(&mut out, material.edge_size);
2575        write_sized_index(
2576            &mut out,
2577            texture_index(&textures, &material.texture_path),
2578            model.metadata.index_sizes.texture,
2579        );
2580        write_sized_index(
2581            &mut out,
2582            texture_index(&textures, &material.sphere_texture_path),
2583            model.metadata.index_sizes.texture,
2584        );
2585        out.push(match material.sphere_mode.as_str() {
2586            "multiply" => 1,
2587            "add" => 2,
2588            "subTexture" => 3,
2589            _ => 0,
2590        });
2591        if let Some(shared) = material.shared_toon_index {
2592            out.push(1);
2593            out.push(shared);
2594        } else {
2595            out.push(0);
2596            write_sized_index(
2597                &mut out,
2598                texture_index(&textures, &material.toon_texture_path),
2599                model.metadata.index_sizes.texture,
2600            );
2601        }
2602        write_pmx_string(&mut out, "", encoding);
2603        write_i32(&mut out, material.face_count.saturating_mul(3));
2604    }
2605
2606    write_i32(&mut out, model.skeleton.bones.len() as i32);
2607    for bone in &model.skeleton.bones {
2608        write_pmx_string(&mut out, &bone.name, encoding);
2609        write_pmx_string(&mut out, &bone.english_name, encoding);
2610        write_f32_slice(&mut out, &bone.position);
2611        write_sized_index(&mut out, bone.parent_index, model.metadata.index_sizes.bone);
2612        write_i32(&mut out, bone.layer);
2613        let bits = bone_flag_bits(&bone.flags);
2614        write_u16(&mut out, bits);
2615        if bone.flags.indexed_tail {
2616            write_sized_index(&mut out, bone.tail_index, model.metadata.index_sizes.bone);
2617        } else {
2618            write_f32_slice(&mut out, &bone.tail_position.unwrap_or([0.0; 3]));
2619        }
2620        if bone.flags.append_rotate || bone.flags.append_translate {
2621            let append = bone.append_transform.as_ref();
2622            write_sized_index(
2623                &mut out,
2624                append.map(|value| value.parent_index).unwrap_or(-1),
2625                model.metadata.index_sizes.bone,
2626            );
2627            write_f32(&mut out, append.map(|value| value.weight).unwrap_or(0.0));
2628        }
2629        if bone.flags.fixed_axis {
2630            write_f32_slice(&mut out, &bone.fixed_axis.unwrap_or([0.0; 3]));
2631        }
2632        if bone.flags.local_axis {
2633            if let Some(axis) = &bone.local_axis {
2634                write_f32_slice(&mut out, &axis.x);
2635                write_f32_slice(&mut out, &axis.z);
2636            } else {
2637                write_f32_slice(&mut out, &[1.0, 0.0, 0.0]);
2638                write_f32_slice(&mut out, &[0.0, 0.0, 1.0]);
2639            }
2640        }
2641        if bone.flags.external_parent_transform {
2642            write_i32(&mut out, bone.external_parent_key.unwrap_or(0));
2643        }
2644        if bone.flags.ik {
2645            if let Some(ik) = &bone.ik {
2646                write_sized_index(&mut out, ik.target_index, model.metadata.index_sizes.bone);
2647                write_i32(&mut out, ik.loop_count);
2648                write_f32(&mut out, ik.limit_angle);
2649                write_i32(&mut out, ik.links.len() as i32);
2650                for link in &ik.links {
2651                    write_sized_index(&mut out, link.bone_index, model.metadata.index_sizes.bone);
2652                    out.push(u8::from(link.limits.is_some()));
2653                    if let Some(limits) = &link.limits {
2654                        write_f32_slice(&mut out, &limits.lower);
2655                        write_f32_slice(&mut out, &limits.upper);
2656                    }
2657                }
2658            } else {
2659                write_sized_index(&mut out, -1, model.metadata.index_sizes.bone);
2660                write_i32(&mut out, 0);
2661                write_f32(&mut out, 0.0);
2662                write_i32(&mut out, 0);
2663            }
2664        }
2665    }
2666
2667    write_i32(&mut out, model.morphs.len() as i32);
2668    for morph in &model.morphs {
2669        write_pmx_string(&mut out, &morph.name, encoding);
2670        write_pmx_string(&mut out, &morph.english_name, encoding);
2671        out.push(0);
2672        let morph_type = morph_type_byte(morph);
2673        out.push(morph_type);
2674        write_i32(&mut out, morph_offset_count(morph, morph_type) as i32);
2675        match morph_type {
2676            0 => write_group_morph_offsets(&mut out, &morph.group_offsets, model),
2677            1 => {
2678                for offset in &morph.vertex_offsets {
2679                    write_vertex_index(
2680                        &mut out,
2681                        offset.vertex_index,
2682                        model.metadata.index_sizes.vertex,
2683                    );
2684                    write_f32_slice(&mut out, &offset.position);
2685                }
2686            }
2687            2 => {
2688                for offset in &morph.bone_offsets {
2689                    write_sized_index(&mut out, offset.bone_index, model.metadata.index_sizes.bone);
2690                    write_f32_slice(&mut out, &offset.translation);
2691                    write_f32_slice(&mut out, &offset.rotation);
2692                }
2693            }
2694            3 => write_uv_morph_offsets(&mut out, &morph.uv_offsets, model),
2695            4..=7 => {
2696                for offset in &morph.additional_uv_offsets {
2697                    write_vertex_index(
2698                        &mut out,
2699                        offset.vertex_index,
2700                        model.metadata.index_sizes.vertex,
2701                    );
2702                    write_f32_slice(&mut out, &offset.uv);
2703                }
2704            }
2705            8 => {
2706                for offset in &morph.material_offsets {
2707                    write_sized_index(
2708                        &mut out,
2709                        offset.material_index,
2710                        model.metadata.index_sizes.material,
2711                    );
2712                    out.push(if offset.operation == "multiply" { 0 } else { 1 });
2713                    write_f32_slice(&mut out, &offset.diffuse);
2714                    write_f32_slice(&mut out, &offset.specular);
2715                    write_f32(&mut out, offset.specular_power);
2716                    write_f32_slice(&mut out, &offset.ambient);
2717                    write_f32_slice(&mut out, &offset.edge_color);
2718                    write_f32(&mut out, offset.edge_size);
2719                    write_f32_slice(&mut out, &offset.texture_factor);
2720                    write_f32_slice(&mut out, &offset.sphere_texture_factor);
2721                    write_f32_slice(&mut out, &offset.toon_texture_factor);
2722                }
2723            }
2724            9 => write_group_morph_offsets(&mut out, &morph.flip_offsets, model),
2725            10 => {
2726                for offset in &morph.impulse_offsets {
2727                    write_sized_index(
2728                        &mut out,
2729                        offset.rigid_body_index,
2730                        model.metadata.index_sizes.rigid_body,
2731                    );
2732                    out.push(u8::from(offset.local));
2733                    write_f32_slice(&mut out, &offset.velocity);
2734                    write_f32_slice(&mut out, &offset.torque);
2735                }
2736            }
2737            _ => {}
2738        }
2739    }
2740
2741    write_i32(&mut out, model.display_frames.len() as i32);
2742    for frame in &model.display_frames {
2743        write_pmx_string(&mut out, &frame.name, encoding);
2744        write_pmx_string(&mut out, &frame.english_name, encoding);
2745        out.push(u8::from(frame.special));
2746        write_i32(&mut out, frame.frames.len() as i32);
2747        for item in &frame.frames {
2748            if item.kind == "morph" {
2749                out.push(1);
2750                write_sized_index(&mut out, item.index, model.metadata.index_sizes.morph);
2751            } else {
2752                out.push(0);
2753                write_sized_index(&mut out, item.index, model.metadata.index_sizes.bone);
2754            }
2755        }
2756    }
2757
2758    write_i32(&mut out, model.rigid_bodies.len() as i32);
2759    for body in &model.rigid_bodies {
2760        write_pmx_string(&mut out, &body.name, encoding);
2761        write_pmx_string(&mut out, &body.english_name, encoding);
2762        write_sized_index(&mut out, body.bone_index, model.metadata.index_sizes.bone);
2763        out.push(body.group);
2764        write_u16(&mut out, body.mask);
2765        out.push(match body.shape.as_str() {
2766            "box" => 1,
2767            "capsule" => 2,
2768            _ => 0,
2769        });
2770        write_f32_slice(&mut out, &body.size);
2771        write_f32_slice(&mut out, &body.position);
2772        write_f32_slice(&mut out, &body.rotation);
2773        write_f32(&mut out, body.mass);
2774        write_f32(&mut out, body.linear_damping);
2775        write_f32(&mut out, body.angular_damping);
2776        write_f32(&mut out, body.restitution);
2777        write_f32(&mut out, body.friction);
2778        out.push(match body.mode.as_str() {
2779            "dynamic" => 1,
2780            "dynamicBone" => 2,
2781            _ => 0,
2782        });
2783    }
2784
2785    write_i32(&mut out, model.joints.len() as i32);
2786    for joint in &model.joints {
2787        write_pmx_string(&mut out, &joint.name, encoding);
2788        write_pmx_string(&mut out, &joint.english_name, encoding);
2789        out.push(match joint.kind.as_str() {
2790            "generic6dof" => 1,
2791            "point2point" => 2,
2792            "coneTwist" => 3,
2793            "slider" => 4,
2794            "hinge" => 5,
2795            _ => 0,
2796        });
2797        write_sized_index(
2798            &mut out,
2799            joint.rigid_body_index_a,
2800            model.metadata.index_sizes.rigid_body,
2801        );
2802        write_sized_index(
2803            &mut out,
2804            joint.rigid_body_index_b,
2805            model.metadata.index_sizes.rigid_body,
2806        );
2807        write_f32_slice(&mut out, &joint.position);
2808        write_f32_slice(&mut out, &joint.rotation);
2809        write_f32_slice(&mut out, &joint.translation_lower_limit);
2810        write_f32_slice(&mut out, &joint.translation_upper_limit);
2811        write_f32_slice(&mut out, &joint.rotation_lower_limit);
2812        write_f32_slice(&mut out, &joint.rotation_upper_limit);
2813        write_f32_slice(&mut out, &joint.spring_translation_factor);
2814        write_f32_slice(&mut out, &joint.spring_rotation_factor);
2815    }
2816
2817    if model.metadata.version >= 2.05 {
2818        write_i32(&mut out, model.soft_bodies.len() as i32);
2819        for soft_body in &model.soft_bodies {
2820            write_pmx_string(&mut out, &soft_body.name, encoding);
2821            write_pmx_string(&mut out, &soft_body.english_name, encoding);
2822            out.push(if soft_body.kind == "rope" { 1 } else { 0 });
2823            write_sized_index(
2824                &mut out,
2825                soft_body.material_index,
2826                model.metadata.index_sizes.material,
2827            );
2828            out.push(soft_body.collision_group);
2829            write_u16(&mut out, soft_body.collision_mask);
2830            out.push(soft_body.flags);
2831            write_i32(&mut out, soft_body.bending_constraints_distance);
2832            write_i32(&mut out, soft_body.cluster_count);
2833            write_f32(&mut out, soft_body.total_mass);
2834            write_f32(&mut out, soft_body.collision_margin);
2835            out.extend_from_slice(&[0u8; 4 + 12 * 4 + 6 * 4 + 4 * 4 + 3 * 4]);
2836            write_i32(&mut out, 0);
2837            write_i32(&mut out, 0);
2838        }
2839    }
2840
2841    out
2842}
2843
2844fn read_section_count(r: &mut Reader<'_>) -> Result<usize, ImportError> {
2845    let count = r.read_i32_le()?;
2846    if count < 0 {
2847        return Err(ImportError::SectionOverflow);
2848    }
2849    Ok(count as usize)
2850}
2851
2852fn read_parsed_geometry(
2853    r: &mut Reader<'_>,
2854    header: &PmxHeader,
2855    vertex_count: usize,
2856) -> Result<PmxParsedGeometry, ImportError> {
2857    let mut positions = Vec::with_capacity(vertex_count * 3);
2858    let mut normals = Vec::with_capacity(vertex_count * 3);
2859    let mut uvs = Vec::with_capacity(vertex_count * 2);
2860    let mut additional_uvs =
2861        vec![Vec::with_capacity(vertex_count * 4); header.extra_uv_count as usize];
2862    let mut skin_indices = Vec::with_capacity(vertex_count * 4);
2863    let mut skin_weights = Vec::with_capacity(vertex_count * 4);
2864    let mut edge_scale = Vec::with_capacity(vertex_count);
2865
2866    for _ in 0..vertex_count {
2867        positions.extend_from_slice(&r.read_vec3_array()?);
2868        normals.extend_from_slice(&r.read_vec3_array()?);
2869        uvs.extend_from_slice(&r.read_vec2_array()?);
2870        for uv in additional_uvs.iter_mut() {
2871            uv.extend_from_slice(&r.read_vec4_array()?);
2872        }
2873        let weight_type = r.read_u8()?;
2874        let mut indices = [0u32; 4];
2875        let mut weights = [0.0f32; 4];
2876        match weight_type {
2877            0 => {
2878                indices[0] =
2879                    normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
2880                weights[0] = 1.0;
2881            }
2882            1 | 3 => {
2883                indices[0] =
2884                    normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
2885                indices[1] =
2886                    normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
2887                let w = r.read_f32_le()?;
2888                weights[0] = w;
2889                weights[1] = 1.0 - w;
2890                if weight_type == 3 {
2891                    r.skip(36)?;
2892                }
2893            }
2894            2 | 4 => {
2895                for index in &mut indices {
2896                    *index =
2897                        normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
2898                }
2899                for weight in &mut weights {
2900                    *weight = r.read_f32_le()?;
2901                }
2902            }
2903            _ => return Err(ImportError::SectionOverflow),
2904        }
2905        skin_indices.extend_from_slice(&indices);
2906        skin_weights.extend_from_slice(&weights);
2907        edge_scale.push(r.read_f32_le()?);
2908    }
2909
2910    let index_count = read_section_count(r)?;
2911    let mut indices = Vec::with_capacity(index_count);
2912    for _ in 0..index_count {
2913        indices.push(r.read_vertex_index(header.vertex_index_size)?);
2914    }
2915
2916    Ok(PmxParsedGeometry {
2917        positions,
2918        normals,
2919        uvs,
2920        additional_uvs,
2921        indices,
2922        skin_indices,
2923        skin_weights,
2924        edge_scale,
2925        material_groups: Vec::new(),
2926    })
2927}
2928
2929fn normalize_nonnegative_index(index: i32) -> u32 {
2930    if index < 0 { 0 } else { index as u32 }
2931}
2932
2933fn read_parsed_textures(
2934    r: &mut Reader<'_>,
2935    encoding: TextEncoding,
2936) -> Result<Vec<String>, ImportError> {
2937    let count = read_section_count(r)?;
2938    let mut textures = Vec::with_capacity(count);
2939    for _ in 0..count {
2940        textures.push(r.read_string(encoding)?);
2941    }
2942    Ok(textures)
2943}
2944
2945fn read_parsed_materials(
2946    r: &mut Reader<'_>,
2947    header: &PmxHeader,
2948    textures: &[String],
2949) -> Result<Vec<PmxParsedMaterial>, ImportError> {
2950    let count = read_section_count(r)?;
2951    let mut materials = Vec::with_capacity(count);
2952    for _ in 0..count {
2953        let name = r.read_string(header.encoding)?;
2954        let english_name = r.read_string(header.encoding)?;
2955        let diffuse = r.read_vec4_array()?;
2956        let specular = r.read_vec3_array()?;
2957        let specular_power = r.read_f32_le()?;
2958        let ambient = r.read_vec3_array()?;
2959        let flag_bits = r.read_u8()?;
2960        let edge_color = r.read_vec4_array()?;
2961        let edge_size = r.read_f32_le()?;
2962        let texture_index = r.read_sized_index(header.texture_index_size)?;
2963        let sphere_texture_index = r.read_sized_index(header.texture_index_size)?;
2964        let sphere_mode_raw = r.read_u8()?;
2965        let toon_flag = r.read_u8()?;
2966        let (toon_texture_path, shared_toon_index) = if toon_flag == 0 {
2967            let toon_texture_index = r.read_sized_index(header.texture_index_size)?;
2968            (texture_path(textures, toon_texture_index), None)
2969        } else {
2970            (String::new(), Some(r.read_u8()?))
2971        };
2972        let _memo = r.read_string(header.encoding)?;
2973        let face_count = r.read_i32_le()? / 3;
2974        materials.push(PmxParsedMaterial {
2975            name,
2976            english_name,
2977            texture_path: texture_path(textures, texture_index),
2978            sphere_texture_path: texture_path(textures, sphere_texture_index),
2979            sphere_mode: match sphere_mode_raw {
2980                1 => "multiply",
2981                2 => "add",
2982                3 => "subTexture",
2983                _ => "none",
2984            }
2985            .to_owned(),
2986            toon_texture_path,
2987            shared_toon_index,
2988            diffuse,
2989            specular,
2990            specular_power,
2991            ambient,
2992            edge_color,
2993            edge_size,
2994            flags: PmxParsedMaterialFlags {
2995                double_sided: flag_bits & 0x01 != 0,
2996                ground_shadow: flag_bits & 0x02 != 0,
2997                self_shadow_map: flag_bits & 0x04 != 0,
2998                self_shadow: flag_bits & 0x08 != 0,
2999                edge: flag_bits & 0x10 != 0,
3000                vertex_color: flag_bits & 0x20 != 0,
3001                point_draw: flag_bits & 0x40 != 0,
3002                line_draw: flag_bits & 0x80 != 0,
3003            },
3004            face_count,
3005        });
3006    }
3007    Ok(materials)
3008}
3009
3010fn texture_path(textures: &[String], index: i32) -> String {
3011    if index < 0 {
3012        String::new()
3013    } else {
3014        textures.get(index as usize).cloned().unwrap_or_default()
3015    }
3016}
3017
3018fn build_material_groups(materials: &[PmxParsedMaterial]) -> Vec<PmxParsedMaterialGroup> {
3019    let mut start = 0usize;
3020    materials
3021        .iter()
3022        .enumerate()
3023        .map(|(material_index, material)| {
3024            let count = (material.face_count.max(0) as usize) * 3;
3025            let group = PmxParsedMaterialGroup {
3026                start,
3027                count,
3028                material_index,
3029            };
3030            start += count;
3031            group
3032        })
3033        .collect()
3034}
3035
3036fn pmx_text_encoding(metadata: &PmxParsedMetadata) -> TextEncoding {
3037    match metadata.encoding.as_str() {
3038        "utf-16-le" => TextEncoding::Utf16Le,
3039        _ => TextEncoding::Utf8,
3040    }
3041}
3042
3043fn write_pmx_string(out: &mut Vec<u8>, text: &str, encoding: TextEncoding) {
3044    match encoding {
3045        TextEncoding::Utf8 => {
3046            write_i32(out, text.len() as i32);
3047            out.extend_from_slice(text.as_bytes());
3048        }
3049        TextEncoding::Utf16Le => {
3050            let bytes = text
3051                .encode_utf16()
3052                .flat_map(u16::to_le_bytes)
3053                .collect::<Vec<_>>();
3054            write_i32(out, bytes.len() as i32);
3055            out.extend_from_slice(&bytes);
3056        }
3057    }
3058}
3059
3060fn write_f32_slice(out: &mut Vec<u8>, values: &[f32]) {
3061    for &value in values {
3062        write_f32(out, value);
3063    }
3064}
3065
3066fn write_f32(out: &mut Vec<u8>, value: f32) {
3067    out.extend_from_slice(&value.to_le_bytes());
3068}
3069
3070fn write_i32(out: &mut Vec<u8>, value: i32) {
3071    out.extend_from_slice(&value.to_le_bytes());
3072}
3073
3074fn write_u16(out: &mut Vec<u8>, value: u16) {
3075    out.extend_from_slice(&value.to_le_bytes());
3076}
3077
3078fn write_sized_index(out: &mut Vec<u8>, value: i32, size: u8) {
3079    match size {
3080        1 => out.push(value as i8 as u8),
3081        2 => out.extend_from_slice(&(value as i16).to_le_bytes()),
3082        4 => out.extend_from_slice(&value.to_le_bytes()),
3083        _ => {}
3084    }
3085}
3086
3087fn write_vertex_index(out: &mut Vec<u8>, value: u32, size: u8) {
3088    match size {
3089        1 => out.push(value as u8),
3090        2 => out.extend_from_slice(&(value as u16).to_le_bytes()),
3091        4 => out.extend_from_slice(&(value as i32).to_le_bytes()),
3092        _ => {}
3093    }
3094}
3095
3096fn material_flag_bits(flags: &PmxParsedMaterialFlags) -> u8 {
3097    u8::from(flags.double_sided)
3098        | (u8::from(flags.ground_shadow) << 1)
3099        | (u8::from(flags.self_shadow_map) << 2)
3100        | (u8::from(flags.self_shadow) << 3)
3101        | (u8::from(flags.edge) << 4)
3102        | (u8::from(flags.vertex_color) << 5)
3103        | (u8::from(flags.point_draw) << 6)
3104        | (u8::from(flags.line_draw) << 7)
3105}
3106
3107fn bone_flag_bits(flags: &PmxParsedBoneFlags) -> u16 {
3108    u16::from(flags.indexed_tail)
3109        | (u16::from(flags.rotatable) << 1)
3110        | (u16::from(flags.translatable) << 2)
3111        | (u16::from(flags.visible) << 3)
3112        | (u16::from(flags.enabled) << 4)
3113        | if flags.ik { BONE_FLAG_IK } else { 0 }
3114        | if flags.append_local {
3115            BONE_FLAG_LOCAL_APPEND
3116        } else {
3117            0
3118        }
3119        | if flags.append_rotate {
3120            BONE_FLAG_APPEND_ROTATE
3121        } else {
3122            0
3123        }
3124        | if flags.append_translate {
3125            BONE_FLAG_APPEND_TRANSLATE
3126        } else {
3127            0
3128        }
3129        | if flags.fixed_axis {
3130            BONE_FLAG_FIXED_AXIS
3131        } else {
3132            0
3133        }
3134        | if flags.local_axis {
3135            BONE_FLAG_LOCAL_AXIS
3136        } else {
3137            0
3138        }
3139        | (u16::from(flags.transform_after_physics) << 12)
3140        | if flags.external_parent_transform {
3141            BONE_FLAG_EXTERNAL_PARENT
3142        } else {
3143            0
3144        }
3145}
3146
3147fn collect_pmx_textures(materials: &[PmxParsedMaterial]) -> Vec<String> {
3148    let mut textures = Vec::new();
3149    for texture in materials.iter().flat_map(|material| {
3150        [
3151            material.texture_path.as_str(),
3152            material.sphere_texture_path.as_str(),
3153            material.toon_texture_path.as_str(),
3154        ]
3155    }) {
3156        if !texture.is_empty() && !textures.iter().any(|existing| existing == texture) {
3157            textures.push(texture.to_owned());
3158        }
3159    }
3160    textures
3161}
3162
3163fn texture_index(textures: &[String], texture: &str) -> i32 {
3164    if texture.is_empty() {
3165        -1
3166    } else {
3167        textures
3168            .iter()
3169            .position(|candidate| candidate == texture)
3170            .map(|index| index as i32)
3171            .unwrap_or(-1)
3172    }
3173}
3174
3175fn morph_type_byte(morph: &PmxParsedMorph) -> u8 {
3176    match morph.kind.as_str() {
3177        "group" => 0,
3178        "vertex" => 1,
3179        "bone" => 2,
3180        "uv" => 3,
3181        "additionalUv" => morph
3182            .additional_uv_offsets
3183            .first()
3184            .map(|offset| offset.uv_index.saturating_add(4).min(7))
3185            .unwrap_or(4),
3186        "material" => 8,
3187        "flip" => 9,
3188        "impulse" => 10,
3189        _ => 0,
3190    }
3191}
3192
3193fn morph_offset_count(morph: &PmxParsedMorph, morph_type: u8) -> usize {
3194    match morph_type {
3195        0 => morph.group_offsets.len(),
3196        1 => morph.vertex_offsets.len(),
3197        2 => morph.bone_offsets.len(),
3198        3 => morph.uv_offsets.len(),
3199        4..=7 => morph.additional_uv_offsets.len(),
3200        8 => morph.material_offsets.len(),
3201        9 => morph.flip_offsets.len(),
3202        10 => morph.impulse_offsets.len(),
3203        _ => 0,
3204    }
3205}
3206
3207fn write_group_morph_offsets(
3208    out: &mut Vec<u8>,
3209    offsets: &[PmxParsedGroupMorphOffset],
3210    model: &PmxParsedModel,
3211) {
3212    for offset in offsets {
3213        write_sized_index(out, offset.morph_index, model.metadata.index_sizes.morph);
3214        write_f32(out, offset.weight);
3215    }
3216}
3217
3218fn write_uv_morph_offsets(
3219    out: &mut Vec<u8>,
3220    offsets: &[PmxParsedUvMorphOffset],
3221    model: &PmxParsedModel,
3222) {
3223    for offset in offsets {
3224        write_vertex_index(out, offset.vertex_index, model.metadata.index_sizes.vertex);
3225        write_f32_slice(out, &offset.uv);
3226    }
3227}
3228
3229fn read_parsed_bones(
3230    r: &mut Reader<'_>,
3231    header: &PmxHeader,
3232    count: usize,
3233) -> Result<Vec<PmxParsedBone>, ImportError> {
3234    let mut bones = Vec::with_capacity(count);
3235    for _ in 0..count {
3236        let name = r.read_string(header.encoding)?;
3237        let english_name = r.read_string(header.encoding)?;
3238        let position = r.read_vec3_array()?;
3239        let parent_index = r.read_sized_index(header.bone_index_size)?;
3240        let layer = r.read_i32_le()?;
3241        let bits = r.read_u16_le()?;
3242        let flags = parsed_bone_flags(bits);
3243        let (tail_index, tail_position) = if flags.indexed_tail {
3244            (r.read_sized_index(header.bone_index_size)?, None)
3245        } else {
3246            (-1, Some(r.read_vec3_array()?))
3247        };
3248        let append_transform = if flags.append_rotate || flags.append_translate {
3249            Some(PmxParsedAppendTransform {
3250                parent_index: r.read_sized_index(header.bone_index_size)?,
3251                weight: r.read_f32_le()?,
3252            })
3253        } else {
3254            None
3255        };
3256        let fixed_axis = if flags.fixed_axis {
3257            Some(r.read_vec3_array()?)
3258        } else {
3259            None
3260        };
3261        let local_axis = if flags.local_axis {
3262            Some(PmxParsedLocalAxis {
3263                x: r.read_vec3_array()?,
3264                z: r.read_vec3_array()?,
3265            })
3266        } else {
3267            None
3268        };
3269        let external_parent_key = if flags.external_parent_transform {
3270            Some(r.read_i32_le()?)
3271        } else {
3272            None
3273        };
3274        let ik = if flags.ik {
3275            let target_index = r.read_sized_index(header.bone_index_size)?;
3276            let loop_count = r.read_i32_le()?;
3277            let limit_angle = r.read_f32_le()?;
3278            let link_count = read_section_count(r)?;
3279            let mut links = Vec::with_capacity(link_count);
3280            for _ in 0..link_count {
3281                let bone_index = r.read_sized_index(header.bone_index_size)?;
3282                let has_limit = r.read_u8()? != 0;
3283                links.push(PmxParsedIkLink {
3284                    bone_index,
3285                    limits: if has_limit {
3286                        Some(PmxParsedIkLimit {
3287                            lower: r.read_vec3_array()?,
3288                            upper: r.read_vec3_array()?,
3289                        })
3290                    } else {
3291                        None
3292                    },
3293                });
3294            }
3295            Some(PmxParsedIk {
3296                target_index,
3297                loop_count,
3298                limit_angle,
3299                links,
3300            })
3301        } else {
3302            None
3303        };
3304        bones.push(PmxParsedBone {
3305            name,
3306            english_name,
3307            parent_index,
3308            layer,
3309            position,
3310            tail_index,
3311            tail_position,
3312            flags,
3313            append_transform,
3314            fixed_axis,
3315            local_axis,
3316            external_parent_key,
3317            ik,
3318        });
3319    }
3320    Ok(bones)
3321}
3322
3323fn parsed_bone_flags(bits: u16) -> PmxParsedBoneFlags {
3324    PmxParsedBoneFlags {
3325        indexed_tail: bits & BONE_FLAG_TAIL_INDEX != 0,
3326        rotatable: bits & 0x0002 != 0,
3327        translatable: bits & 0x0004 != 0,
3328        visible: bits & 0x0008 != 0,
3329        enabled: bits & 0x0010 != 0,
3330        ik: bits & BONE_FLAG_IK != 0,
3331        append_local: bits & BONE_FLAG_LOCAL_APPEND != 0,
3332        append_rotate: bits & BONE_FLAG_APPEND_ROTATE != 0,
3333        append_translate: bits & BONE_FLAG_APPEND_TRANSLATE != 0,
3334        fixed_axis: bits & BONE_FLAG_FIXED_AXIS != 0,
3335        local_axis: bits & BONE_FLAG_LOCAL_AXIS != 0,
3336        transform_after_physics: bits & 0x1000 != 0,
3337        external_parent_transform: bits & BONE_FLAG_EXTERNAL_PARENT != 0,
3338    }
3339}
3340
3341fn read_parsed_morphs(
3342    r: &mut Reader<'_>,
3343    header: &PmxHeader,
3344) -> Result<Vec<PmxParsedMorph>, ImportError> {
3345    let count = read_section_count(r)?;
3346    let mut morphs = Vec::with_capacity(count);
3347    for _ in 0..count {
3348        let name = r.read_string(header.encoding)?;
3349        let english_name = r.read_string(header.encoding)?;
3350        let _panel = r.read_u8()?;
3351        let morph_type = r.read_u8()?;
3352        let offset_count = read_section_count(r)?;
3353        let mut morph = PmxParsedMorph {
3354            name,
3355            english_name,
3356            kind: match morph_type {
3357                0 => "group",
3358                1 => "vertex",
3359                2 => "bone",
3360                3 => "uv",
3361                4..=7 => "additionalUv",
3362                8 => "material",
3363                9 => "flip",
3364                10 => "impulse",
3365                _ => "unknown",
3366            }
3367            .to_owned(),
3368            vertex_offsets: Vec::new(),
3369            group_offsets: Vec::new(),
3370            bone_offsets: Vec::new(),
3371            uv_offsets: Vec::new(),
3372            additional_uv_offsets: Vec::new(),
3373            material_offsets: Vec::new(),
3374            flip_offsets: Vec::new(),
3375            impulse_offsets: Vec::new(),
3376        };
3377        for _ in 0..offset_count {
3378            match morph_type {
3379                0 => morph.group_offsets.push(PmxParsedGroupMorphOffset {
3380                    morph_index: r.read_sized_index(header.morph_index_size)?,
3381                    weight: r.read_f32_le()?,
3382                }),
3383                1 => morph.vertex_offsets.push(PmxParsedVertexMorphOffset {
3384                    vertex_index: r.read_vertex_index(header.vertex_index_size)?,
3385                    position: r.read_vec3_array()?,
3386                }),
3387                2 => morph.bone_offsets.push(PmxParsedBoneMorphOffset {
3388                    bone_index: r.read_sized_index(header.bone_index_size)?,
3389                    translation: r.read_vec3_array()?,
3390                    rotation: r.read_vec4_array()?,
3391                }),
3392                3 => morph.uv_offsets.push(PmxParsedUvMorphOffset {
3393                    vertex_index: r.read_vertex_index(header.vertex_index_size)?,
3394                    uv: r.read_vec4_array()?,
3395                }),
3396                4..=7 => morph
3397                    .additional_uv_offsets
3398                    .push(PmxParsedAdditionalUvMorphOffset {
3399                        vertex_index: r.read_vertex_index(header.vertex_index_size)?,
3400                        uv_index: morph_type - 4,
3401                        uv: r.read_vec4_array()?,
3402                    }),
3403                8 => morph.material_offsets.push(PmxParsedMaterialMorphOffset {
3404                    material_index: r.read_sized_index(header.material_index_size)?,
3405                    operation: if r.read_u8()? == 0 {
3406                        "multiply".to_owned()
3407                    } else {
3408                        "add".to_owned()
3409                    },
3410                    diffuse: r.read_vec4_array()?,
3411                    specular: r.read_vec3_array()?,
3412                    specular_power: r.read_f32_le()?,
3413                    ambient: r.read_vec3_array()?,
3414                    edge_color: r.read_vec4_array()?,
3415                    edge_size: r.read_f32_le()?,
3416                    texture_factor: r.read_vec4_array()?,
3417                    sphere_texture_factor: r.read_vec4_array()?,
3418                    toon_texture_factor: r.read_vec4_array()?,
3419                }),
3420                9 => morph.flip_offsets.push(PmxParsedGroupMorphOffset {
3421                    morph_index: r.read_sized_index(header.morph_index_size)?,
3422                    weight: r.read_f32_le()?,
3423                }),
3424                10 => morph.impulse_offsets.push(PmxParsedImpulseMorphOffset {
3425                    rigid_body_index: r.read_sized_index(header.rigidbody_index_size)?,
3426                    local: r.read_u8()? != 0,
3427                    velocity: r.read_vec3_array()?,
3428                    torque: r.read_vec3_array()?,
3429                }),
3430                _ => return Err(ImportError::SectionOverflow),
3431            }
3432        }
3433        morphs.push(morph);
3434    }
3435    Ok(morphs)
3436}
3437
3438fn read_parsed_display_frames(
3439    r: &mut Reader<'_>,
3440    header: &PmxHeader,
3441) -> Result<Vec<PmxParsedDisplayFrame>, ImportError> {
3442    let count = read_section_count(r)?;
3443    let mut frames = Vec::with_capacity(count);
3444    for _ in 0..count {
3445        let name = r.read_string(header.encoding)?;
3446        let english_name = r.read_string(header.encoding)?;
3447        let special = r.read_u8()? == 1;
3448        let item_count = read_section_count(r)?;
3449        let mut items = Vec::with_capacity(item_count);
3450        for _ in 0..item_count {
3451            let kind = r.read_u8()?;
3452            let index = match kind {
3453                0 => r.read_sized_index(header.bone_index_size)?,
3454                1 => r.read_sized_index(header.morph_index_size)?,
3455                _ => return Err(ImportError::SectionOverflow),
3456            };
3457            items.push(PmxParsedDisplayFrameElement {
3458                kind: if kind == 0 {
3459                    "bone".to_owned()
3460                } else {
3461                    "morph".to_owned()
3462                },
3463                index,
3464            });
3465        }
3466        frames.push(PmxParsedDisplayFrame {
3467            name,
3468            english_name,
3469            special,
3470            frames: items,
3471        });
3472    }
3473    Ok(frames)
3474}
3475
3476fn read_parsed_rigid_bodies(
3477    r: &mut Reader<'_>,
3478    header: &PmxHeader,
3479) -> Result<Vec<PmxParsedRigidBody>, ImportError> {
3480    let count = read_section_count(r)?;
3481    let mut bodies = Vec::with_capacity(count);
3482    for _ in 0..count {
3483        bodies.push(PmxParsedRigidBody {
3484            name: r.read_string(header.encoding)?,
3485            english_name: r.read_string(header.encoding)?,
3486            bone_index: r.read_sized_index(header.bone_index_size)?,
3487            group: r.read_u8()?,
3488            mask: r.read_u16_le()?,
3489            shape: match r.read_u8()? {
3490                0 => "sphere",
3491                1 => "box",
3492                2 => "capsule",
3493                _ => "unknown",
3494            }
3495            .to_owned(),
3496            size: r.read_vec3_array()?,
3497            position: r.read_vec3_array()?,
3498            rotation: r.read_vec3_array()?,
3499            mass: r.read_f32_le()?,
3500            linear_damping: r.read_f32_le()?,
3501            angular_damping: r.read_f32_le()?,
3502            restitution: r.read_f32_le()?,
3503            friction: r.read_f32_le()?,
3504            mode: match r.read_u8()? {
3505                0 => "static",
3506                1 => "dynamic",
3507                2 => "dynamicBone",
3508                _ => "unknown",
3509            }
3510            .to_owned(),
3511        });
3512    }
3513    Ok(bodies)
3514}
3515
3516fn read_parsed_joints(
3517    r: &mut Reader<'_>,
3518    header: &PmxHeader,
3519) -> Result<Vec<PmxParsedJoint>, ImportError> {
3520    let count = read_section_count(r)?;
3521    let mut joints = Vec::with_capacity(count);
3522    for _ in 0..count {
3523        joints.push(PmxParsedJoint {
3524            name: r.read_string(header.encoding)?,
3525            english_name: r.read_string(header.encoding)?,
3526            kind: match r.read_u8()? {
3527                0 => "generic6dofSpring",
3528                1 => "generic6dof",
3529                2 => "point2point",
3530                3 => "coneTwist",
3531                4 => "slider",
3532                5 => "hinge",
3533                _ => "unknown",
3534            }
3535            .to_owned(),
3536            rigid_body_index_a: r.read_sized_index(header.rigidbody_index_size)?,
3537            rigid_body_index_b: r.read_sized_index(header.rigidbody_index_size)?,
3538            position: r.read_vec3_array()?,
3539            rotation: r.read_vec3_array()?,
3540            translation_lower_limit: r.read_vec3_array()?,
3541            translation_upper_limit: r.read_vec3_array()?,
3542            rotation_lower_limit: r.read_vec3_array()?,
3543            rotation_upper_limit: r.read_vec3_array()?,
3544            spring_translation_factor: r.read_vec3_array()?,
3545            spring_rotation_factor: r.read_vec3_array()?,
3546        });
3547    }
3548    Ok(joints)
3549}
3550
3551fn read_parsed_soft_bodies(
3552    r: &mut Reader<'_>,
3553    header: &PmxHeader,
3554) -> Result<Vec<PmxParsedSoftBody>, ImportError> {
3555    let count = read_section_count(r)?;
3556    let mut soft_bodies = Vec::with_capacity(count);
3557    for _ in 0..count {
3558        let name = r.read_string(header.encoding)?;
3559        let english_name = r.read_string(header.encoding)?;
3560        let kind = match r.read_u8()? {
3561            0 => "triMesh",
3562            1 => "rope",
3563            _ => "unknown",
3564        }
3565        .to_owned();
3566        let material_index = r.read_sized_index(header.material_index_size)?;
3567        let collision_group = r.read_u8()?;
3568        let collision_mask = r.read_u16_le()?;
3569        let flags = r.read_u8()?;
3570        let bending_constraints_distance = r.read_i32_le()?;
3571        let cluster_count = r.read_i32_le()?;
3572        let total_mass = r.read_f32_le()?;
3573        let collision_margin = r.read_f32_le()?;
3574        r.skip(4 + 12 * 4 + 6 * 4 + 4 * 4 + 3 * 4)?;
3575        let anchor_count = read_section_count(r)?;
3576        for _ in 0..anchor_count {
3577            r.read_sized_index(header.rigidbody_index_size)?;
3578            r.read_vertex_index(header.vertex_index_size)?;
3579            r.skip(1)?;
3580        }
3581        let pin_count = read_section_count(r)?;
3582        for _ in 0..pin_count {
3583            r.read_vertex_index(header.vertex_index_size)?;
3584        }
3585        soft_bodies.push(PmxParsedSoftBody {
3586            name,
3587            english_name,
3588            kind,
3589            material_index,
3590            collision_group,
3591            collision_mask,
3592            flags,
3593            bending_constraints_distance,
3594            cluster_count,
3595            total_mass,
3596            collision_margin,
3597        });
3598    }
3599    Ok(soft_bodies)
3600}
3601
3602#[derive(Debug)]
3603pub struct PmxRuntimeImport {
3604    pub model: ModelArena,
3605    pub bone_names: Vec<String>,
3606    pub bone_name_to_index: HashMap<Vec<u8>, BoneIndex>,
3607    pub morph_name_to_index: HashMap<Vec<u8>, MorphIndex>,
3608    pub ik_solver_bone_name_to_index: HashMap<Vec<u8>, usize>,
3609}
3610
3611pub fn import_pmx_runtime(data: &[u8]) -> Result<PmxRuntimeImport, ImportError> {
3612    let (header, pos) = read_header(data)?;
3613    let pos = skip_model_info(data, &header, pos)?;
3614    let pos = skip_vertices(data, &header, pos)?;
3615    let pos = skip_faces(data, header.vertex_index_size, pos)?;
3616    let pos = skip_textures(data, header.encoding, pos)?;
3617    let pos = skip_materials(data, &header, pos)?;
3618    let (bone_import, pos) = read_bones(data, &header, pos)?;
3619    let (morph_names, morph_init, _pos) = read_morph_offsets(data, &header, pos)?;
3620
3621    let mut bone_name_to_index = HashMap::with_capacity(bone_import.bone_name_bytes.len() * 2);
3622    for (i, bytes) in bone_import.bone_name_bytes.iter().enumerate() {
3623        let index = BoneIndex(i as u32);
3624        bone_name_to_index.insert(bytes.clone(), index);
3625        let decoded = bone_import.bone_names[i].as_bytes();
3626        if decoded != bytes.as_slice() && !decoded.is_empty() {
3627            bone_name_to_index.insert(decoded.to_vec(), index);
3628        }
3629    }
3630
3631    let mut morph_name_to_index = HashMap::with_capacity(morph_names.name_bytes.len() * 2);
3632    for (i, bytes) in morph_names.name_bytes.iter().enumerate() {
3633        let index = MorphIndex(i as u32);
3634        morph_name_to_index.insert(bytes.clone(), index);
3635        let decoded = morph_names.names[i].as_bytes();
3636        if decoded != bytes.as_slice() && !decoded.is_empty() {
3637            morph_name_to_index.insert(decoded.to_vec(), index);
3638        }
3639    }
3640
3641    let mut ik_solver_bone_name_to_index = HashMap::with_capacity(bone_import.ik_solvers.len() * 2);
3642    for (solver_idx, solver) in bone_import.ik_solvers.iter().enumerate() {
3643        let bone_idx = solver.ik_bone.as_usize();
3644        if bone_idx < bone_import.bone_name_bytes.len() {
3645            let bytes = &bone_import.bone_name_bytes[bone_idx];
3646            ik_solver_bone_name_to_index.insert(bytes.clone(), solver_idx);
3647            let decoded = bone_import.bone_names[bone_idx].as_bytes();
3648            if decoded != bytes.as_slice() && !decoded.is_empty() {
3649                ik_solver_bone_name_to_index.insert(decoded.to_vec(), solver_idx);
3650            }
3651        }
3652    }
3653
3654    let model = ModelArena::new_with_morphs(
3655        bone_import.bones,
3656        bone_import.ik_solvers,
3657        bone_import.append_transforms,
3658        morph_init,
3659    )
3660    .map_err(ImportError::ModelBuildFailed)?;
3661
3662    Ok(PmxRuntimeImport {
3663        model,
3664        bone_names: bone_import.bone_names,
3665        bone_name_to_index,
3666        morph_name_to_index,
3667        ik_solver_bone_name_to_index,
3668    })
3669}
3670
3671trait AppendTransformInitExt {
3672    fn with_rotation_if(self, on: bool) -> Self;
3673    fn with_translation_if(self, on: bool) -> Self;
3674    fn with_local_if(self, on: bool) -> Self;
3675}
3676
3677impl AppendTransformInitExt for AppendTransformInit {
3678    fn with_rotation_if(mut self, on: bool) -> Self {
3679        if on {
3680            self.affect_rotation = true;
3681        }
3682        self
3683    }
3684
3685    fn with_translation_if(mut self, on: bool) -> Self {
3686        if on {
3687            self.affect_translation = true;
3688        }
3689        self
3690    }
3691
3692    fn with_local_if(mut self, on: bool) -> Self {
3693        if on {
3694            self.local = true;
3695        }
3696        self
3697    }
3698}
3699
3700#[cfg(test)]
3701mod tests {
3702    use super::*;
3703    use glam::Vec3;
3704    use mmd_anim_runtime::RuntimeInstance;
3705    use std::sync::Arc;
3706
3707    fn build_small_pmx_header_bytes(bone_index_size: u8, encoding: TextEncoding) -> Vec<u8> {
3708        let mut buf = Vec::new();
3709        buf.extend_from_slice(b"PMX ");
3710        buf.extend_from_slice(&2.0f32.to_le_bytes());
3711        buf.push(8);
3712        buf.push(encoding as u8);
3713        buf.push(0);
3714        buf.push(4);
3715        buf.push(1);
3716        buf.push(1);
3717        buf.push(bone_index_size);
3718        buf.push(1);
3719        buf.push(1);
3720        buf
3721    }
3722
3723    fn build_empty_model_info(_encoding: TextEncoding) -> Vec<u8> {
3724        let mut buf = Vec::new();
3725        for _ in 0..4 {
3726            buf.extend_from_slice(&0i32.to_le_bytes());
3727        }
3728        buf
3729    }
3730
3731    fn build_empty_vertex_section() -> Vec<u8> {
3732        vec![0u8, 0, 0, 0]
3733    }
3734
3735    fn build_empty_face_section() -> Vec<u8> {
3736        vec![0u8, 0, 0, 0]
3737    }
3738
3739    fn build_empty_texture_section() -> Vec<u8> {
3740        vec![0u8, 0, 0, 0]
3741    }
3742
3743    fn build_empty_material_section() -> Vec<u8> {
3744        vec![0u8, 0, 0, 0]
3745    }
3746
3747    fn build_bone_name_bytes(name: &str) -> Vec<u8> {
3748        let mut buf = Vec::new();
3749        let name_bytes = name.as_bytes();
3750        buf.extend_from_slice(&(name_bytes.len() as i32).to_le_bytes());
3751        buf.extend_from_slice(name_bytes);
3752        buf
3753    }
3754
3755    fn build_bone_section_header(count: u32) -> Vec<u8> {
3756        let mut buf = Vec::new();
3757        buf.extend_from_slice(&count.to_le_bytes());
3758        buf
3759    }
3760
3761    fn build_morph_name_bytes(name: &str) -> Vec<u8> {
3762        let mut buf = Vec::new();
3763        let name_bytes = name.as_bytes();
3764        buf.extend_from_slice(&(name_bytes.len() as i32).to_le_bytes());
3765        buf.extend_from_slice(name_bytes);
3766        buf
3767    }
3768
3769    fn empty_pmx_flags() -> PmxParsedMaterialFlags {
3770        PmxParsedMaterialFlags {
3771            double_sided: false,
3772            ground_shadow: false,
3773            self_shadow_map: false,
3774            self_shadow: false,
3775            edge: false,
3776            vertex_color: false,
3777            point_draw: false,
3778            line_draw: false,
3779        }
3780    }
3781
3782    fn basic_bone_flags(indexed_tail: bool) -> PmxParsedBoneFlags {
3783        PmxParsedBoneFlags {
3784            indexed_tail,
3785            rotatable: true,
3786            translatable: false,
3787            visible: true,
3788            enabled: true,
3789            ik: false,
3790            append_local: false,
3791            append_rotate: false,
3792            append_translate: false,
3793            fixed_axis: false,
3794            local_axis: false,
3795            transform_after_physics: false,
3796            external_parent_transform: false,
3797        }
3798    }
3799
3800    fn parsed_pmx_fixture() -> PmxParsedModel {
3801        PmxParsedModel {
3802            metadata: PmxParsedMetadata {
3803                format: "pmx".to_owned(),
3804                version: 2.0,
3805                encoding: "utf-8".to_owned(),
3806                name: "model".to_owned(),
3807                english_name: "model-en".to_owned(),
3808                comment: "comment".to_owned(),
3809                english_comment: "comment-en".to_owned(),
3810                counts: PmxParsedCounts {
3811                    vertices: 1,
3812                    faces: 1,
3813                    materials: 1,
3814                    bones: 1,
3815                    morphs: 1,
3816                    display_frames: 1,
3817                    rigid_bodies: 1,
3818                    joints: 1,
3819                    soft_bodies: 0,
3820                },
3821                index_sizes: PmxParsedIndexSizes {
3822                    vertex: 4,
3823                    texture: 1,
3824                    material: 1,
3825                    bone: 2,
3826                    morph: 1,
3827                    rigid_body: 1,
3828                },
3829                additional_uv_count: 1,
3830            },
3831            geometry: PmxParsedGeometry {
3832                positions: vec![1.0, 2.0, 3.0],
3833                normals: vec![0.0, 1.0, 0.0],
3834                uvs: vec![0.25, 0.75],
3835                additional_uvs: vec![vec![1.0, 2.0, 3.0, 4.0]],
3836                indices: vec![0, 0, 0],
3837                skin_indices: vec![0, 0, 0, 0],
3838                skin_weights: vec![1.0, 0.0, 0.0, 0.0],
3839                edge_scale: vec![1.25],
3840                material_groups: vec![PmxParsedMaterialGroup {
3841                    start: 0,
3842                    count: 3,
3843                    material_index: 0,
3844                }],
3845            },
3846            materials: vec![PmxParsedMaterial {
3847                name: "mat".to_owned(),
3848                english_name: "mat-en".to_owned(),
3849                texture_path: "tex.png".to_owned(),
3850                sphere_texture_path: "sphere.spa".to_owned(),
3851                sphere_mode: "add".to_owned(),
3852                toon_texture_path: "toon.bmp".to_owned(),
3853                shared_toon_index: None,
3854                diffuse: [0.1, 0.2, 0.3, 0.4],
3855                specular: [0.5, 0.6, 0.7],
3856                specular_power: 8.0,
3857                ambient: [0.8, 0.9, 1.0],
3858                edge_color: [0.0, 0.1, 0.2, 0.3],
3859                edge_size: 1.5,
3860                flags: PmxParsedMaterialFlags {
3861                    double_sided: true,
3862                    edge: true,
3863                    ..empty_pmx_flags()
3864                },
3865                face_count: 1,
3866            }],
3867            skeleton: PmxParsedSkeleton {
3868                bones: vec![PmxParsedBone {
3869                    name: "Root".to_owned(),
3870                    english_name: "RootEn".to_owned(),
3871                    parent_index: -1,
3872                    layer: 0,
3873                    position: [0.0, 1.0, 2.0],
3874                    tail_index: -1,
3875                    tail_position: Some([0.0, 2.0, 0.0]),
3876                    flags: basic_bone_flags(false),
3877                    append_transform: None,
3878                    fixed_axis: None,
3879                    local_axis: None,
3880                    external_parent_key: None,
3881                    ik: None,
3882                }],
3883            },
3884            morphs: vec![PmxParsedMorph {
3885                name: "Smile".to_owned(),
3886                english_name: "SmileEn".to_owned(),
3887                kind: "vertex".to_owned(),
3888                vertex_offsets: vec![PmxParsedVertexMorphOffset {
3889                    vertex_index: 0,
3890                    position: [0.1, 0.2, 0.3],
3891                }],
3892                group_offsets: Vec::new(),
3893                bone_offsets: Vec::new(),
3894                uv_offsets: Vec::new(),
3895                additional_uv_offsets: Vec::new(),
3896                material_offsets: Vec::new(),
3897                flip_offsets: Vec::new(),
3898                impulse_offsets: Vec::new(),
3899            }],
3900            display_frames: vec![PmxParsedDisplayFrame {
3901                name: "RootFrame".to_owned(),
3902                english_name: "RootFrameEn".to_owned(),
3903                special: true,
3904                frames: vec![
3905                    PmxParsedDisplayFrameElement {
3906                        kind: "bone".to_owned(),
3907                        index: 0,
3908                    },
3909                    PmxParsedDisplayFrameElement {
3910                        kind: "morph".to_owned(),
3911                        index: 0,
3912                    },
3913                ],
3914            }],
3915            rigid_bodies: vec![PmxParsedRigidBody {
3916                name: "body".to_owned(),
3917                english_name: "body-en".to_owned(),
3918                bone_index: 0,
3919                group: 1,
3920                mask: 2,
3921                shape: "box".to_owned(),
3922                size: [1.0, 2.0, 3.0],
3923                position: [4.0, 5.0, 6.0],
3924                rotation: [0.1, 0.2, 0.3],
3925                mass: 10.0,
3926                linear_damping: 0.4,
3927                angular_damping: 0.5,
3928                restitution: 0.6,
3929                friction: 0.7,
3930                mode: "dynamicBone".to_owned(),
3931            }],
3932            joints: vec![PmxParsedJoint {
3933                name: "joint".to_owned(),
3934                english_name: "joint-en".to_owned(),
3935                kind: "generic6dofSpring".to_owned(),
3936                rigid_body_index_a: 0,
3937                rigid_body_index_b: -1,
3938                position: [1.0, 1.1, 1.2],
3939                rotation: [2.0, 2.1, 2.2],
3940                translation_lower_limit: [-1.0, -1.1, -1.2],
3941                translation_upper_limit: [1.0, 1.1, 1.2],
3942                rotation_lower_limit: [-0.1, -0.2, -0.3],
3943                rotation_upper_limit: [0.1, 0.2, 0.3],
3944                spring_translation_factor: [3.0, 3.1, 3.2],
3945                spring_rotation_factor: [4.0, 4.1, 4.2],
3946            }],
3947            soft_bodies: Vec::new(),
3948            diagnostics: Vec::new(),
3949        }
3950    }
3951
3952    fn assert_pmx_roundtrip_eq(left: &PmxParsedModel, right: &PmxParsedModel) {
3953        assert_eq!(
3954            serde_json::to_value(left).unwrap(),
3955            serde_json::to_value(right).unwrap()
3956        );
3957    }
3958
3959    fn json_keys(value: &serde_json::Value) -> Vec<String> {
3960        let mut keys = value
3961            .as_object()
3962            .unwrap()
3963            .keys()
3964            .map(ToOwned::to_owned)
3965            .collect::<Vec<_>>();
3966        keys.sort();
3967        keys
3968    }
3969
3970    #[test]
3971    fn pmx_model_json_top_level_schema_is_stable() {
3972        let parsed = parsed_pmx_fixture();
3973        let keys = json_keys(&serde_json::to_value(&parsed).unwrap());
3974
3975        assert_eq!(
3976            keys,
3977            vec![
3978                "diagnostics",
3979                "displayFrames",
3980                "geometry",
3981                "joints",
3982                "materials",
3983                "metadata",
3984                "morphs",
3985                "rigidBodies",
3986                "skeleton",
3987                "softBodies",
3988            ]
3989        );
3990    }
3991
3992    #[test]
3993    fn parses_pmx_header() {
3994        let header_bytes = build_small_pmx_header_bytes(4, TextEncoding::Utf8);
3995        let (header, pos) = read_header(&header_bytes).unwrap();
3996
3997        assert_eq!(header.version, 2.0);
3998        assert_eq!(header.encoding, TextEncoding::Utf8);
3999        assert_eq!(header.bone_index_size, 4);
4000        assert_eq!(header.vertex_index_size, 4);
4001        assert!(pos > 0);
4002    }
4003
4004    #[test]
4005    fn pmx_bone_flag_values_match_reference_layout() {
4006        assert_eq!(BONE_FLAG_TAIL_INDEX, 0x0001);
4007        assert_eq!(BONE_FLAG_IK, 0x0020);
4008        assert_eq!(BONE_FLAG_LOCAL_APPEND, 0x0080);
4009        assert_eq!(BONE_FLAG_APPEND_ROTATE, 0x0100);
4010        assert_eq!(BONE_FLAG_APPEND_TRANSLATE, 0x0200);
4011        assert_eq!(BONE_FLAG_FIXED_AXIS, 0x0400);
4012        assert_eq!(BONE_FLAG_LOCAL_AXIS, 0x0800);
4013        assert_eq!(BONE_FLAG_EXTERNAL_PARENT, 0x2000);
4014    }
4015
4016    #[test]
4017    fn rejects_bad_magic() {
4018        let mut header_bytes = build_small_pmx_header_bytes(4, TextEncoding::Utf8);
4019        header_bytes[0] = 0xFF;
4020        assert_eq!(
4021            read_header(&header_bytes).unwrap_err(),
4022            ImportError::InvalidPmxMagic
4023        );
4024    }
4025
4026    #[test]
4027    fn reads_single_bone_no_parent() {
4028        let mut buf = Vec::new();
4029        buf.extend_from_slice(&build_small_pmx_header_bytes(4, TextEncoding::Utf8));
4030        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4031        buf.extend_from_slice(&build_empty_vertex_section());
4032        buf.extend_from_slice(&build_empty_face_section());
4033        buf.extend_from_slice(&build_empty_texture_section());
4034        buf.extend_from_slice(&build_empty_material_section());
4035        buf.extend_from_slice(&build_bone_section_header(1));
4036
4037        buf.extend_from_slice(&build_bone_name_bytes("Root"));
4038        buf.extend_from_slice(&build_bone_name_bytes(""));
4039        buf.extend_from_slice(&0.0f32.to_le_bytes());
4040        buf.extend_from_slice(&1.0f32.to_le_bytes());
4041        buf.extend_from_slice(&2.0f32.to_le_bytes());
4042        buf.extend_from_slice(&(-1i32).to_le_bytes());
4043        buf.extend_from_slice(&0i32.to_le_bytes());
4044        buf.extend_from_slice(&0u16.to_le_bytes());
4045        buf.extend_from_slice(&0.0f32.to_le_bytes());
4046        buf.extend_from_slice(&0.0f32.to_le_bytes());
4047        buf.extend_from_slice(&0.0f32.to_le_bytes());
4048
4049        let result = import_pmx_model(&buf).unwrap();
4050        assert_eq!(result.bones.len(), 1);
4051        assert_eq!(result.bone_names.len(), 1);
4052        assert_eq!(result.bone_names[0], "Root");
4053        assert_eq!(&result.bone_name_bytes[0][..], b"Root");
4054        assert_eq!(result.bones[0].parent, None);
4055        assert!((result.bones[0].rest_position.x - 0.0).abs() < 0.001);
4056        assert!((result.bones[0].rest_position.y - 1.0).abs() < 0.001);
4057        assert!((result.bones[0].rest_position.z - 2.0).abs() < 0.001);
4058        assert!(result.ik_solvers.is_empty());
4059        assert!(result.append_transforms.is_empty());
4060    }
4061
4062    #[test]
4063    fn reads_bone_with_parent() {
4064        let mut buf = Vec::new();
4065        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4066        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4067        buf.extend_from_slice(&build_empty_vertex_section());
4068        buf.extend_from_slice(&build_empty_face_section());
4069        buf.extend_from_slice(&build_empty_texture_section());
4070        buf.extend_from_slice(&build_empty_material_section());
4071        buf.extend_from_slice(&build_bone_section_header(2));
4072
4073        buf.extend_from_slice(&build_bone_name_bytes("Root"));
4074        buf.extend_from_slice(&build_bone_name_bytes(""));
4075        buf.extend_from_slice(&0.0f32.to_le_bytes());
4076        buf.extend_from_slice(&0.0f32.to_le_bytes());
4077        buf.extend_from_slice(&0.0f32.to_le_bytes());
4078        buf.extend_from_slice(&(-1i16).to_le_bytes());
4079        buf.extend_from_slice(&0i32.to_le_bytes());
4080        buf.extend_from_slice(&0u16.to_le_bytes());
4081        buf.extend_from_slice(&0.0f32.to_le_bytes());
4082        buf.extend_from_slice(&0.0f32.to_le_bytes());
4083        buf.extend_from_slice(&0.0f32.to_le_bytes());
4084
4085        buf.extend_from_slice(&build_bone_name_bytes("Child"));
4086        buf.extend_from_slice(&build_bone_name_bytes(""));
4087        buf.extend_from_slice(&1.0f32.to_le_bytes());
4088        buf.extend_from_slice(&0.0f32.to_le_bytes());
4089        buf.extend_from_slice(&0.0f32.to_le_bytes());
4090        buf.extend_from_slice(&0i16.to_le_bytes());
4091        buf.extend_from_slice(&0i32.to_le_bytes());
4092        buf.extend_from_slice(&0u16.to_le_bytes());
4093        buf.extend_from_slice(&0.0f32.to_le_bytes());
4094        buf.extend_from_slice(&0.0f32.to_le_bytes());
4095        buf.extend_from_slice(&0.0f32.to_le_bytes());
4096
4097        let result = import_pmx_model(&buf).unwrap();
4098        assert_eq!(result.bones.len(), 2);
4099        assert_eq!(result.bone_names[0], "Root");
4100        assert_eq!(result.bone_names[1], "Child");
4101        assert_eq!(result.bones[0].parent, None);
4102        assert_eq!(result.bones[1].parent, Some(BoneIndex(0)));
4103    }
4104
4105    #[test]
4106    fn reads_bone_with_append_transform() {
4107        let mut buf = Vec::new();
4108        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4109        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4110        buf.extend_from_slice(&build_empty_vertex_section());
4111        buf.extend_from_slice(&build_empty_face_section());
4112        buf.extend_from_slice(&build_empty_texture_section());
4113        buf.extend_from_slice(&build_empty_material_section());
4114        buf.extend_from_slice(&build_bone_section_header(2));
4115
4116        buf.extend_from_slice(&build_bone_name_bytes("Root"));
4117        buf.extend_from_slice(&build_bone_name_bytes(""));
4118        buf.extend_from_slice(&0.0f32.to_le_bytes());
4119        buf.extend_from_slice(&0.0f32.to_le_bytes());
4120        buf.extend_from_slice(&0.0f32.to_le_bytes());
4121        buf.extend_from_slice(&(-1i16).to_le_bytes());
4122        buf.extend_from_slice(&0i32.to_le_bytes());
4123        buf.extend_from_slice(&0u16.to_le_bytes());
4124        buf.extend_from_slice(&0.0f32.to_le_bytes());
4125        buf.extend_from_slice(&0.0f32.to_le_bytes());
4126        buf.extend_from_slice(&0.0f32.to_le_bytes());
4127
4128        buf.extend_from_slice(&build_bone_name_bytes("AppendBone"));
4129        buf.extend_from_slice(&build_bone_name_bytes(""));
4130        buf.extend_from_slice(&1.0f32.to_le_bytes());
4131        buf.extend_from_slice(&0.0f32.to_le_bytes());
4132        buf.extend_from_slice(&0.0f32.to_le_bytes());
4133        buf.extend_from_slice(&0i16.to_le_bytes());
4134        buf.extend_from_slice(&0i32.to_le_bytes());
4135        buf.extend_from_slice(
4136            &(BONE_FLAG_LOCAL_APPEND | BONE_FLAG_APPEND_ROTATE | BONE_FLAG_APPEND_TRANSLATE)
4137                .to_le_bytes(),
4138        );
4139        buf.extend_from_slice(&0.0f32.to_le_bytes());
4140        buf.extend_from_slice(&0.0f32.to_le_bytes());
4141        buf.extend_from_slice(&0.0f32.to_le_bytes());
4142        buf.extend_from_slice(&0i16.to_le_bytes());
4143        buf.extend_from_slice(&0.5f32.to_le_bytes());
4144
4145        let result = import_pmx_model(&buf).unwrap();
4146        assert_eq!(result.bones.len(), 2);
4147        assert_eq!(result.append_transforms.len(), 1);
4148        assert_eq!(result.append_transforms[0].target_bone, BoneIndex(1));
4149        assert_eq!(result.append_transforms[0].source_bone, BoneIndex(0));
4150        assert!((result.append_transforms[0].ratio - 0.5).abs() < 0.001);
4151        assert!(result.append_transforms[0].affect_rotation);
4152        assert!(result.append_transforms[0].affect_translation);
4153        assert!(result.append_transforms[0].local);
4154    }
4155
4156    #[test]
4157    fn reads_ik_bone_with_links_and_angle_limits() {
4158        let mut buf = Vec::new();
4159        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4160        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4161        buf.extend_from_slice(&build_empty_vertex_section());
4162        buf.extend_from_slice(&build_empty_face_section());
4163        buf.extend_from_slice(&build_empty_texture_section());
4164        buf.extend_from_slice(&build_empty_material_section());
4165        buf.extend_from_slice(&build_bone_section_header(3));
4166
4167        buf.extend_from_slice(&build_bone_name_bytes("Root"));
4168        buf.extend_from_slice(&build_bone_name_bytes(""));
4169        buf.extend_from_slice(&0.0f32.to_le_bytes());
4170        buf.extend_from_slice(&0.0f32.to_le_bytes());
4171        buf.extend_from_slice(&0.0f32.to_le_bytes());
4172        buf.extend_from_slice(&(-1i16).to_le_bytes());
4173        buf.extend_from_slice(&0i32.to_le_bytes());
4174        buf.extend_from_slice(&0u16.to_le_bytes());
4175        buf.extend_from_slice(&0.0f32.to_le_bytes());
4176        buf.extend_from_slice(&0.0f32.to_le_bytes());
4177        buf.extend_from_slice(&0.0f32.to_le_bytes());
4178
4179        buf.extend_from_slice(&build_bone_name_bytes("IKLink"));
4180        buf.extend_from_slice(&build_bone_name_bytes(""));
4181        buf.extend_from_slice(&2.0f32.to_le_bytes());
4182        buf.extend_from_slice(&0.0f32.to_le_bytes());
4183        buf.extend_from_slice(&0.0f32.to_le_bytes());
4184        buf.extend_from_slice(&0i16.to_le_bytes());
4185        buf.extend_from_slice(&0i32.to_le_bytes());
4186        buf.extend_from_slice(&0u16.to_le_bytes());
4187        buf.extend_from_slice(&0.0f32.to_le_bytes());
4188        buf.extend_from_slice(&0.0f32.to_le_bytes());
4189        buf.extend_from_slice(&0.0f32.to_le_bytes());
4190
4191        buf.extend_from_slice(&build_bone_name_bytes("IKBone"));
4192        buf.extend_from_slice(&build_bone_name_bytes(""));
4193        buf.extend_from_slice(&4.0f32.to_le_bytes());
4194        buf.extend_from_slice(&0.0f32.to_le_bytes());
4195        buf.extend_from_slice(&0.0f32.to_le_bytes());
4196        buf.extend_from_slice(&0i16.to_le_bytes());
4197        buf.extend_from_slice(&0i32.to_le_bytes());
4198        buf.extend_from_slice(&(BONE_FLAG_TAIL_INDEX | BONE_FLAG_IK).to_le_bytes());
4199        buf.extend_from_slice(&1i16.to_le_bytes());
4200        buf.extend_from_slice(&1i16.to_le_bytes());
4201        buf.extend_from_slice(&10i32.to_le_bytes());
4202        buf.extend_from_slice(&0.5f32.to_le_bytes());
4203        buf.extend_from_slice(&1i32.to_le_bytes());
4204        buf.extend_from_slice(&1i16.to_le_bytes());
4205        buf.push(1u8);
4206        buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4207        buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4208        buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4209        buf.extend_from_slice(&1.0f32.to_le_bytes());
4210        buf.extend_from_slice(&1.0f32.to_le_bytes());
4211        buf.extend_from_slice(&1.0f32.to_le_bytes());
4212
4213        let result = import_pmx_model(&buf).unwrap();
4214        assert_eq!(result.bones.len(), 3);
4215        assert_eq!(result.ik_solvers.len(), 1);
4216        assert_eq!(result.ik_solvers[0].ik_bone, BoneIndex(2));
4217        assert_eq!(result.ik_solvers[0].target_bone, BoneIndex(1));
4218        assert_eq!(result.ik_solvers[0].links.len(), 1);
4219        assert_eq!(result.ik_solvers[0].links[0].bone, BoneIndex(1));
4220        assert!(result.ik_solvers[0].links[0].angle_limit.is_some());
4221        assert_eq!(result.ik_solvers[0].iteration_count, 10);
4222        assert!((result.ik_solvers[0].limit_angle - 0.5).abs() < 0.001);
4223    }
4224
4225    #[test]
4226    fn handles_utf16le_encoding() {
4227        let header_bytes = build_small_pmx_header_bytes(4, TextEncoding::Utf16Le);
4228        let (header, _) = read_header(&header_bytes).unwrap();
4229        assert_eq!(header.encoding, TextEncoding::Utf16Le);
4230    }
4231
4232    #[test]
4233    fn decodes_utf16le_surrogate_pairs() {
4234        let bytes = [0x3D, 0xD8, 0x00, 0xDE, 0x00, 0x00];
4235        assert_eq!(decode_utf16le_lossy(&bytes), "\u{1F600}");
4236    }
4237
4238    #[test]
4239    fn reports_eof_on_truncated_data() {
4240        let buf = vec![0x50, 0x4D, 0x58]; // incomplete magic
4241        assert!(matches!(
4242            read_header(&buf).unwrap_err(),
4243            ImportError::UnexpectedEof(_)
4244        ));
4245    }
4246
4247    #[test]
4248    fn bone_names_retained_and_mapped_to_indices() {
4249        let mut buf = Vec::new();
4250        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4251        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4252        buf.extend_from_slice(&build_empty_vertex_section());
4253        buf.extend_from_slice(&build_empty_face_section());
4254        buf.extend_from_slice(&build_empty_texture_section());
4255        buf.extend_from_slice(&build_empty_material_section());
4256        buf.extend_from_slice(&build_bone_section_header(2));
4257
4258        buf.extend_from_slice(&build_bone_name_bytes("Hips"));
4259        buf.extend_from_slice(&build_bone_name_bytes(""));
4260        buf.extend_from_slice(&0.0f32.to_le_bytes());
4261        buf.extend_from_slice(&0.0f32.to_le_bytes());
4262        buf.extend_from_slice(&0.0f32.to_le_bytes());
4263        buf.extend_from_slice(&(-1i16).to_le_bytes());
4264        buf.extend_from_slice(&0i32.to_le_bytes());
4265        buf.extend_from_slice(&0u16.to_le_bytes());
4266        buf.extend_from_slice(&0.0f32.to_le_bytes());
4267        buf.extend_from_slice(&0.0f32.to_le_bytes());
4268        buf.extend_from_slice(&0.0f32.to_le_bytes());
4269
4270        buf.extend_from_slice(&build_bone_name_bytes("Spine"));
4271        buf.extend_from_slice(&build_bone_name_bytes(""));
4272        buf.extend_from_slice(&1.0f32.to_le_bytes());
4273        buf.extend_from_slice(&0.0f32.to_le_bytes());
4274        buf.extend_from_slice(&0.0f32.to_le_bytes());
4275        buf.extend_from_slice(&0i16.to_le_bytes());
4276        buf.extend_from_slice(&0i32.to_le_bytes());
4277        buf.extend_from_slice(&0u16.to_le_bytes());
4278        buf.extend_from_slice(&0.0f32.to_le_bytes());
4279        buf.extend_from_slice(&0.0f32.to_le_bytes());
4280        buf.extend_from_slice(&0.0f32.to_le_bytes());
4281
4282        let result = import_pmx_model(&buf).unwrap();
4283        assert_eq!(result.bone_name_bytes.len(), 2);
4284        assert_eq!(&result.bone_name_bytes[0][..], b"Hips");
4285        assert_eq!(&result.bone_name_bytes[1][..], b"Spine");
4286        assert_eq!(result.bone_names[0], "Hips");
4287        assert_eq!(result.bone_names[1], "Spine");
4288
4289        let bone_map: HashMap<Vec<u8>, BoneIndex> = result
4290            .bone_name_bytes
4291            .iter()
4292            .enumerate()
4293            .map(|(i, b)| (b.clone(), BoneIndex(i as u32)))
4294            .collect();
4295
4296        assert_eq!(bone_map.get(b"Hips".as_slice()), Some(&BoneIndex(0)));
4297        assert_eq!(bone_map.get(b"Spine".as_slice()), Some(&BoneIndex(1)));
4298        assert_eq!(bone_map.get(b"Unknown".as_slice()), None);
4299    }
4300
4301    #[test]
4302    fn ik_solver_name_mapped_to_solver_index() {
4303        let mut buf = Vec::new();
4304        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4305        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4306        buf.extend_from_slice(&build_empty_vertex_section());
4307        buf.extend_from_slice(&build_empty_face_section());
4308        buf.extend_from_slice(&build_empty_texture_section());
4309        buf.extend_from_slice(&build_empty_material_section());
4310        buf.extend_from_slice(&build_bone_section_header(3));
4311
4312        buf.extend_from_slice(&build_bone_name_bytes("Root"));
4313        buf.extend_from_slice(&build_bone_name_bytes(""));
4314        buf.extend_from_slice(&0.0f32.to_le_bytes());
4315        buf.extend_from_slice(&0.0f32.to_le_bytes());
4316        buf.extend_from_slice(&0.0f32.to_le_bytes());
4317        buf.extend_from_slice(&(-1i16).to_le_bytes());
4318        buf.extend_from_slice(&0i32.to_le_bytes());
4319        buf.extend_from_slice(&0u16.to_le_bytes());
4320        buf.extend_from_slice(&0.0f32.to_le_bytes());
4321        buf.extend_from_slice(&0.0f32.to_le_bytes());
4322        buf.extend_from_slice(&0.0f32.to_le_bytes());
4323
4324        buf.extend_from_slice(&build_bone_name_bytes("IKLink"));
4325        buf.extend_from_slice(&build_bone_name_bytes(""));
4326        buf.extend_from_slice(&2.0f32.to_le_bytes());
4327        buf.extend_from_slice(&0.0f32.to_le_bytes());
4328        buf.extend_from_slice(&0.0f32.to_le_bytes());
4329        buf.extend_from_slice(&0i16.to_le_bytes());
4330        buf.extend_from_slice(&0i32.to_le_bytes());
4331        buf.extend_from_slice(&0u16.to_le_bytes());
4332        buf.extend_from_slice(&0.0f32.to_le_bytes());
4333        buf.extend_from_slice(&0.0f32.to_le_bytes());
4334        buf.extend_from_slice(&0.0f32.to_le_bytes());
4335
4336        buf.extend_from_slice(&build_bone_name_bytes("LeftLegIK"));
4337        buf.extend_from_slice(&build_bone_name_bytes(""));
4338        buf.extend_from_slice(&4.0f32.to_le_bytes());
4339        buf.extend_from_slice(&0.0f32.to_le_bytes());
4340        buf.extend_from_slice(&0.0f32.to_le_bytes());
4341        buf.extend_from_slice(&0i16.to_le_bytes());
4342        buf.extend_from_slice(&0i32.to_le_bytes());
4343        buf.extend_from_slice(&(BONE_FLAG_TAIL_INDEX | BONE_FLAG_IK).to_le_bytes());
4344        buf.extend_from_slice(&1i16.to_le_bytes());
4345        buf.extend_from_slice(&1i16.to_le_bytes());
4346        buf.extend_from_slice(&10i32.to_le_bytes());
4347        buf.extend_from_slice(&0.5f32.to_le_bytes());
4348        buf.extend_from_slice(&1i32.to_le_bytes());
4349        buf.extend_from_slice(&1i16.to_le_bytes());
4350        buf.push(1u8);
4351        buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4352        buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4353        buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4354        buf.extend_from_slice(&1.0f32.to_le_bytes());
4355        buf.extend_from_slice(&1.0f32.to_le_bytes());
4356        buf.extend_from_slice(&1.0f32.to_le_bytes());
4357
4358        let bone_import = import_pmx_model(&buf).unwrap();
4359        assert_eq!(bone_import.ik_solvers.len(), 1);
4360        assert_eq!(bone_import.ik_solvers[0].ik_bone, BoneIndex(2));
4361        assert_eq!(
4362            bone_import.bone_names[bone_import.ik_solvers[0].ik_bone.as_usize()],
4363            "LeftLegIK"
4364        );
4365
4366        let ik_name_map: HashMap<Vec<u8>, usize> = bone_import
4367            .ik_solvers
4368            .iter()
4369            .enumerate()
4370            .filter_map(|(solver_idx, solver)| {
4371                let bone_idx = solver.ik_bone.as_usize();
4372                if bone_idx < bone_import.bone_name_bytes.len() {
4373                    Some((bone_import.bone_name_bytes[bone_idx].clone(), solver_idx))
4374                } else {
4375                    None
4376                }
4377            })
4378            .collect();
4379
4380        assert_eq!(ik_name_map.get(b"LeftLegIK".as_slice()), Some(&0));
4381    }
4382
4383    #[test]
4384    fn reads_morph_names_without_payloads() {
4385        let (header, pos) =
4386            read_header(&build_small_pmx_header_bytes(4, TextEncoding::Utf8)).unwrap();
4387
4388        let mut buf = vec![0u8; pos];
4389        buf.extend_from_slice(&1i32.to_le_bytes());
4390        buf.extend_from_slice(&build_morph_name_bytes("Smile"));
4391        buf.extend_from_slice(&build_morph_name_bytes(""));
4392        buf.push(4u8);
4393        buf.push(0u8);
4394        buf.extend_from_slice(&0i32.to_le_bytes());
4395
4396        let (morphs, new_pos) = read_morph_names(&buf[..], &header, pos).unwrap();
4397        assert_eq!(morphs.name_bytes.len(), 1);
4398        assert_eq!(&morphs.name_bytes[0][..], b"Smile");
4399        assert_eq!(morphs.names[0], "Smile");
4400        assert!(new_pos > pos);
4401    }
4402
4403    #[test]
4404    fn reads_morph_type_zero_with_vertex_group_offsets() {
4405        let (header, pos) =
4406            read_header(&build_small_pmx_header_bytes(4, TextEncoding::Utf8)).unwrap();
4407        assert_eq!(header.vertex_index_size, 4);
4408        assert_eq!(header.morph_index_size, 1);
4409
4410        let mut buf = vec![0u8; pos];
4411        buf.extend_from_slice(&1i32.to_le_bytes());
4412        buf.extend_from_slice(&build_morph_name_bytes("Blink"));
4413        buf.extend_from_slice(&build_morph_name_bytes(""));
4414        buf.push(0u8);
4415        buf.push(0u8);
4416        buf.extend_from_slice(&2i32.to_le_bytes());
4417        buf.push(0u8);
4418        buf.extend_from_slice(&[0u8; 4]);
4419        buf.push(1u8);
4420        buf.extend_from_slice(&[0u8; 4]);
4421
4422        let (morphs, new_pos) = read_morph_names(&buf[..], &header, pos).unwrap();
4423        assert_eq!(morphs.name_bytes.len(), 1);
4424        assert_eq!(&morphs.name_bytes[0][..], b"Blink");
4425        assert_eq!(morphs.names[0], "Blink");
4426        assert_eq!(new_pos, buf.len());
4427    }
4428
4429    #[test]
4430    fn runtime_import_builds_model_arena_with_name_lookup() {
4431        let mut buf = Vec::new();
4432        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4433        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4434        buf.extend_from_slice(&build_empty_vertex_section());
4435        buf.extend_from_slice(&build_empty_face_section());
4436        buf.extend_from_slice(&build_empty_texture_section());
4437        buf.extend_from_slice(&build_empty_material_section());
4438        buf.extend_from_slice(&build_bone_section_header(2));
4439
4440        buf.extend_from_slice(&build_bone_name_bytes("Hips"));
4441        buf.extend_from_slice(&build_bone_name_bytes(""));
4442        buf.extend_from_slice(&0.0f32.to_le_bytes());
4443        buf.extend_from_slice(&0.0f32.to_le_bytes());
4444        buf.extend_from_slice(&0.0f32.to_le_bytes());
4445        buf.extend_from_slice(&(-1i16).to_le_bytes());
4446        buf.extend_from_slice(&0i32.to_le_bytes());
4447        buf.extend_from_slice(&0u16.to_le_bytes());
4448        buf.extend_from_slice(&0.0f32.to_le_bytes());
4449        buf.extend_from_slice(&0.0f32.to_le_bytes());
4450        buf.extend_from_slice(&0.0f32.to_le_bytes());
4451
4452        buf.extend_from_slice(&build_bone_name_bytes("Spine"));
4453        buf.extend_from_slice(&build_bone_name_bytes(""));
4454        buf.extend_from_slice(&0.0f32.to_le_bytes());
4455        buf.extend_from_slice(&1.0f32.to_le_bytes());
4456        buf.extend_from_slice(&0.0f32.to_le_bytes());
4457        buf.extend_from_slice(&0i16.to_le_bytes());
4458        buf.extend_from_slice(&0i32.to_le_bytes());
4459        buf.extend_from_slice(&0u16.to_le_bytes());
4460        buf.extend_from_slice(&0.0f32.to_le_bytes());
4461        buf.extend_from_slice(&0.0f32.to_le_bytes());
4462        buf.extend_from_slice(&0.0f32.to_le_bytes());
4463        buf.extend_from_slice(&0i32.to_le_bytes());
4464
4465        let rt = import_pmx_runtime(&buf).unwrap();
4466        assert_eq!(rt.model.bone_count(), 2);
4467        assert_eq!(rt.model.parent_index(BoneIndex(1)), Some(BoneIndex(0)));
4468        assert_eq!(
4469            rt.bone_name_to_index.get(b"Hips".as_slice()),
4470            Some(&BoneIndex(0))
4471        );
4472        assert_eq!(
4473            rt.bone_name_to_index.get(b"Spine".as_slice()),
4474            Some(&BoneIndex(1))
4475        );
4476        assert!(rt.morph_name_to_index.is_empty());
4477        assert!(rt.ik_solver_bone_name_to_index.is_empty());
4478    }
4479
4480    #[test]
4481    fn parse_pmx_model_exposes_three_loader_shaped_sections() {
4482        let mut buf = Vec::new();
4483        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4484        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4485        buf.extend_from_slice(&build_empty_vertex_section());
4486        buf.extend_from_slice(&build_empty_face_section());
4487        buf.extend_from_slice(&build_empty_texture_section());
4488        buf.extend_from_slice(&build_empty_material_section());
4489        buf.extend_from_slice(&build_bone_section_header(2));
4490
4491        buf.extend_from_slice(&build_bone_name_bytes("Root"));
4492        buf.extend_from_slice(&build_bone_name_bytes(""));
4493        buf.extend_from_slice(&0.0f32.to_le_bytes());
4494        buf.extend_from_slice(&0.0f32.to_le_bytes());
4495        buf.extend_from_slice(&0.0f32.to_le_bytes());
4496        buf.extend_from_slice(&(-1i16).to_le_bytes());
4497        buf.extend_from_slice(&0i32.to_le_bytes());
4498        buf.extend_from_slice(&0u16.to_le_bytes());
4499        buf.extend_from_slice(&0.0f32.to_le_bytes());
4500        buf.extend_from_slice(&0.0f32.to_le_bytes());
4501        buf.extend_from_slice(&0.0f32.to_le_bytes());
4502
4503        buf.extend_from_slice(&build_bone_name_bytes("Child"));
4504        buf.extend_from_slice(&build_bone_name_bytes(""));
4505        buf.extend_from_slice(&0.0f32.to_le_bytes());
4506        buf.extend_from_slice(&1.0f32.to_le_bytes());
4507        buf.extend_from_slice(&0.0f32.to_le_bytes());
4508        buf.extend_from_slice(&0i16.to_le_bytes());
4509        buf.extend_from_slice(&0i32.to_le_bytes());
4510        buf.extend_from_slice(&0u16.to_le_bytes());
4511        buf.extend_from_slice(&0.0f32.to_le_bytes());
4512        buf.extend_from_slice(&0.0f32.to_le_bytes());
4513        buf.extend_from_slice(&0.0f32.to_le_bytes());
4514
4515        buf.extend_from_slice(&0i32.to_le_bytes()); // morphs
4516        buf.extend_from_slice(&1i32.to_le_bytes()); // display frames
4517        buf.extend_from_slice(&build_bone_name_bytes("RootFrame"));
4518        buf.extend_from_slice(&build_bone_name_bytes(""));
4519        buf.push(1);
4520        buf.extend_from_slice(&1i32.to_le_bytes());
4521        buf.push(0);
4522        buf.extend_from_slice(&1i16.to_le_bytes());
4523        buf.extend_from_slice(&0i32.to_le_bytes()); // rigid bodies
4524        buf.extend_from_slice(&0i32.to_le_bytes()); // joints
4525
4526        let parsed = parse_pmx_model(&buf).unwrap();
4527        assert_eq!(parsed.metadata.format, "pmx");
4528        assert_eq!(parsed.metadata.counts.bones, 2);
4529        assert_eq!(parsed.skeleton.bones[0].name, "Root");
4530        assert_eq!(parsed.skeleton.bones[1].parent_index, 0);
4531        assert_eq!(parsed.display_frames.len(), 1);
4532        assert_eq!(parsed.display_frames[0].frames[0].kind, "bone");
4533        assert_eq!(parsed.display_frames[0].frames[0].index, 1);
4534        assert!(parsed.diagnostics.is_empty());
4535    }
4536
4537    #[test]
4538    fn exports_pmx_semantic_roundtrip() {
4539        let parsed = parsed_pmx_fixture();
4540        let exported = export_pmx_model(&parsed);
4541        let reparsed = parse_pmx_model(&exported).unwrap();
4542
4543        assert_pmx_roundtrip_eq(&parsed, &reparsed);
4544    }
4545
4546    #[test]
4547    fn exports_pmx_json_dto_semantic_roundtrip() {
4548        let parsed = parsed_pmx_fixture();
4549        let json = serde_json::to_string(&parsed).unwrap();
4550        let from_json: PmxParsedModel = serde_json::from_str(&json).unwrap();
4551        let exported = export_pmx_model(&from_json);
4552        let reparsed = parse_pmx_model(&exported).unwrap();
4553
4554        assert_pmx_roundtrip_eq(&parsed, &reparsed);
4555    }
4556
4557    #[test]
4558    fn builds_pmx_parts_with_material_descriptors() {
4559        let descriptor: PmxPartsDescriptor = serde_json::from_value(serde_json::json!({
4560            "name": "material-parts",
4561            "materials": [
4562                {
4563                    "name": "red",
4564                    "englishName": "red-en",
4565                    "diffuse": [1.0, 0.0, 0.0, 1.0],
4566                    "ambient": [0.5, 0.0, 0.0],
4567                    "flags": { "edge": true },
4568                    "faceCount": 1
4569                },
4570                {
4571                    "name": "blue",
4572                    "diffuse": [0.0, 0.0, 1.0, 1.0],
4573                    "faceCount": 1
4574                }
4575            ],
4576            "bones": [
4577                { "name": "root", "englishName": "root-en", "tailIndex": 1 },
4578                { "name": "child", "parentIndex": 0, "position": [0.0, 1.0, 0.0], "rotatable": true }
4579            ],
4580            "morphs": [
4581                {
4582                    "name": "raise",
4583                    "englishName": "raise-en",
4584                    "kind": "vertex",
4585                    "vertexOffsets": [{ "vertexIndex": 0, "position": [0.0, 0.1, 0.0] }]
4586                },
4587                {
4588                    "name": "combo",
4589                    "kind": "group",
4590                    "groupOffsets": [{ "morphIndex": 0, "weight": 0.5 }]
4591                }
4592            ],
4593            "displayFrames": [
4594                {
4595                    "name": "Root",
4596                    "englishName": "Root-en",
4597                    "special": true,
4598                    "frames": [
4599                        { "kind": "bone", "index": 0 },
4600                        { "kind": "bone", "index": 1 },
4601                        { "kind": "morph", "index": 0 }
4602                    ]
4603                }
4604            ],
4605            "rigidBodies": [
4606                {
4607                    "name": "body",
4608                    "englishName": "body-en",
4609                    "boneIndex": 1,
4610                    "group": 2,
4611                    "mask": 3,
4612                    "shape": "box",
4613                    "size": [1.0, 2.0, 3.0],
4614                    "position": [0.0, 1.0, 0.0],
4615                    "rotation": [0.1, 0.2, 0.3],
4616                    "mass": 2.0,
4617                    "linearDamping": 0.4,
4618                    "angularDamping": 0.5,
4619                    "restitution": 0.6,
4620                    "friction": 0.7,
4621                    "mode": "dynamicBone"
4622                }
4623            ],
4624            "joints": [
4625                {
4626                    "name": "joint",
4627                    "type": "generic6dofSpring",
4628                    "rigidBodyIndexA": 0,
4629                    "rigidBodyIndexB": -1,
4630                    "position": [0.0, 1.0, 0.0],
4631                    "translationLowerLimit": [-1.0, -1.0, -1.0],
4632                    "translationUpperLimit": [1.0, 1.0, 1.0],
4633                    "springTranslationFactor": [0.1, 0.2, 0.3]
4634                }
4635            ],
4636            "indexSizes": {
4637                "vertex": 1,
4638                "material": 1,
4639                "texture": 1,
4640                "bone": 1,
4641                "morph": 1,
4642                "rigidBody": 1
4643            }
4644        }))
4645        .unwrap();
4646        let model = build_pmx_model_from_parts(PmxPartsInput {
4647            descriptor,
4648            positions_xyz: &[
4649                0.0, 0.0, 0.0, //
4650                1.0, 0.0, 0.0, //
4651                1.0, 1.0, 0.0, //
4652                0.0, 1.0, 0.0,
4653            ],
4654            normals_xyz: &[
4655                0.0, 0.0, 1.0, //
4656                0.0, 0.0, 1.0, //
4657                0.0, 0.0, 1.0, //
4658                0.0, 0.0, 1.0,
4659            ],
4660            uvs_xy: &[0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0],
4661            indices: &[0, 1, 2, 0, 2, 3],
4662            skin_indices: &[0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
4663            skin_weights: &[
4664                0.75, 0.25, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
4665            ],
4666            edge_scale: &[],
4667        })
4668        .unwrap();
4669        let exported = export_pmx_model(&model);
4670        let reparsed = parse_pmx_model(&exported).unwrap();
4671
4672        assert_eq!(reparsed.metadata.name, "material-parts");
4673        assert_eq!(reparsed.metadata.counts.materials, 2);
4674        assert_eq!(reparsed.materials[0].name, "red");
4675        assert_eq!(reparsed.materials[0].english_name, "red-en");
4676        assert_eq!(reparsed.materials[0].face_count, 1);
4677        assert!(reparsed.materials[0].flags.edge);
4678        assert_eq!(reparsed.materials[1].name, "blue");
4679        assert_eq!(reparsed.materials[1].face_count, 1);
4680        assert_eq!(reparsed.geometry.material_groups.len(), 2);
4681        assert_eq!(reparsed.geometry.material_groups[0].start, 0);
4682        assert_eq!(reparsed.geometry.material_groups[0].count, 3);
4683        assert_eq!(reparsed.geometry.material_groups[1].start, 3);
4684        assert_eq!(reparsed.geometry.material_groups[1].count, 3);
4685        assert_eq!(reparsed.metadata.counts.bones, 2);
4686        assert_eq!(reparsed.skeleton.bones[0].name, "root");
4687        assert_eq!(reparsed.skeleton.bones[0].english_name, "root-en");
4688        assert_eq!(reparsed.skeleton.bones[0].tail_index, 1);
4689        assert!(reparsed.skeleton.bones[0].flags.indexed_tail);
4690        assert_eq!(reparsed.skeleton.bones[1].name, "child");
4691        assert_eq!(reparsed.skeleton.bones[1].parent_index, 0);
4692        assert!(reparsed.skeleton.bones[1].flags.rotatable);
4693        assert_eq!(reparsed.geometry.skin_indices[1], 1);
4694        assert_eq!(reparsed.geometry.skin_weights[1], 0.25);
4695        assert_eq!(reparsed.metadata.counts.morphs, 2);
4696        assert_eq!(reparsed.morphs[0].name, "raise");
4697        assert_eq!(reparsed.morphs[0].english_name, "raise-en");
4698        assert_eq!(reparsed.morphs[0].kind, "vertex");
4699        assert_eq!(reparsed.morphs[0].vertex_offsets[0].vertex_index, 0);
4700        assert_eq!(
4701            reparsed.morphs[0].vertex_offsets[0].position,
4702            [0.0, 0.1, 0.0]
4703        );
4704        assert_eq!(reparsed.morphs[1].name, "combo");
4705        assert_eq!(reparsed.morphs[1].kind, "group");
4706        assert_eq!(reparsed.morphs[1].group_offsets[0].morph_index, 0);
4707        assert_eq!(reparsed.morphs[1].group_offsets[0].weight, 0.5);
4708        assert_eq!(reparsed.display_frames.len(), 1);
4709        assert_eq!(reparsed.display_frames[0].name, "Root");
4710        assert_eq!(reparsed.display_frames[0].english_name, "Root-en");
4711        assert!(reparsed.display_frames[0].special);
4712        assert_eq!(reparsed.display_frames[0].frames.len(), 3);
4713        assert_eq!(reparsed.display_frames[0].frames[1].kind, "bone");
4714        assert_eq!(reparsed.display_frames[0].frames[1].index, 1);
4715        assert_eq!(reparsed.display_frames[0].frames[2].kind, "morph");
4716        assert_eq!(reparsed.display_frames[0].frames[2].index, 0);
4717        assert_eq!(reparsed.rigid_bodies.len(), 1);
4718        assert_eq!(reparsed.rigid_bodies[0].name, "body");
4719        assert_eq!(reparsed.rigid_bodies[0].english_name, "body-en");
4720        assert_eq!(reparsed.rigid_bodies[0].bone_index, 1);
4721        assert_eq!(reparsed.rigid_bodies[0].group, 2);
4722        assert_eq!(reparsed.rigid_bodies[0].mask, 3);
4723        assert_eq!(reparsed.rigid_bodies[0].shape, "box");
4724        assert_eq!(reparsed.rigid_bodies[0].mode, "dynamicBone");
4725        assert_eq!(reparsed.joints.len(), 1);
4726        assert_eq!(reparsed.joints[0].name, "joint");
4727        assert_eq!(reparsed.joints[0].kind, "generic6dofSpring");
4728        assert_eq!(reparsed.joints[0].rigid_body_index_a, 0);
4729        assert_eq!(reparsed.joints[0].rigid_body_index_b, -1);
4730    }
4731
4732    #[test]
4733    fn rejects_pmx_parts_material_face_count_mismatch() {
4734        let descriptor: PmxPartsDescriptor = serde_json::from_value(serde_json::json!({
4735            "materials": [{ "name": "bad", "faceCount": 2 }]
4736        }))
4737        .unwrap();
4738        let error = build_pmx_model_from_parts(PmxPartsInput {
4739            descriptor,
4740            positions_xyz: &[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
4741            normals_xyz: &[0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0],
4742            uvs_xy: &[0.0, 0.0, 1.0, 0.0, 0.0, 1.0],
4743            indices: &[0, 1, 2],
4744            skin_indices: &[],
4745            skin_weights: &[],
4746            edge_scale: &[],
4747        })
4748        .unwrap_err();
4749
4750        assert!(error.contains("materials faceCount sum"));
4751    }
4752
4753    #[test]
4754    fn rejects_pmx_export_model_partial_skin_arrays() {
4755        let mut parsed = parsed_pmx_fixture();
4756        parsed.geometry.skin_weights.clear();
4757
4758        let error = validate_pmx_export_model(&parsed).unwrap_err();
4759
4760        assert!(error.contains("skinIndices and skinWeights"));
4761    }
4762
4763    // Synthetic PMX 2.2 gate — no real PMX 2.2 fixture exists in the scan set yet.
4764    // Verifies that version=2.2 survives export_pmx_model -> parse_pmx_model unchanged.
4765    #[test]
4766    fn exports_pmx_22_synthetic_roundtrip() {
4767        let mut parsed = parsed_pmx_fixture();
4768        parsed.metadata.version = 2.2;
4769
4770        let exported = export_pmx_model(&parsed);
4771        let reparsed = parse_pmx_model(&exported).unwrap();
4772
4773        assert_eq!(
4774            reparsed.metadata.version, 2.2,
4775            "PMX 2.2 version must survive the export-parse cycle"
4776        );
4777        assert_pmx_roundtrip_eq(&parsed, &reparsed);
4778    }
4779
4780    #[test]
4781    fn exports_pmx_soft_body_header_fields_with_diagnostic_for_pmx_21_and_22() {
4782        for version in [2.1, 2.2] {
4783            let mut parsed = parsed_pmx_fixture();
4784            parsed.metadata.version = version;
4785            parsed.soft_bodies = vec![PmxParsedSoftBody {
4786                name: "soft".to_owned(),
4787                english_name: "soft-en".to_owned(),
4788                kind: "rope".to_owned(),
4789                material_index: 0,
4790                collision_group: 1,
4791                collision_mask: 2,
4792                flags: 3,
4793                bending_constraints_distance: 4,
4794                cluster_count: 5,
4795                total_mass: 6.0,
4796                collision_margin: 0.7,
4797            }];
4798            let exported = export_pmx_model(&parsed);
4799            let reparsed = parse_pmx_model(&exported).unwrap();
4800
4801            assert_eq!(reparsed.metadata.version, version);
4802            assert_eq!(reparsed.metadata.counts.soft_bodies, 1);
4803            assert_eq!(reparsed.soft_bodies.len(), 1);
4804            assert_eq!(reparsed.soft_bodies[0].name, "soft");
4805            assert_eq!(reparsed.soft_bodies[0].english_name, "soft-en");
4806            assert_eq!(reparsed.soft_bodies[0].kind, "rope");
4807            assert_eq!(reparsed.soft_bodies[0].material_index, 0);
4808            assert_eq!(reparsed.soft_bodies[0].collision_group, 1);
4809            assert_eq!(reparsed.soft_bodies[0].collision_mask, 2);
4810            assert_eq!(reparsed.soft_bodies[0].flags, 3);
4811            assert_eq!(reparsed.soft_bodies[0].bending_constraints_distance, 4);
4812            assert_eq!(reparsed.soft_bodies[0].cluster_count, 5);
4813            assert_eq!(reparsed.soft_bodies[0].total_mass, 6.0);
4814            assert_eq!(reparsed.soft_bodies[0].collision_margin, 0.7);
4815            assert_eq!(reparsed.diagnostics.len(), 1);
4816            assert_eq!(reparsed.diagnostics[0].code, "PMX_SOFT_BODY_UNSUPPORTED");
4817        }
4818    }
4819
4820    #[test]
4821    fn exports_parsed_pmx_skeleton_display_roundtrip() {
4822        let mut buf = Vec::new();
4823        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4824        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4825        buf.extend_from_slice(&build_empty_vertex_section());
4826        buf.extend_from_slice(&build_empty_face_section());
4827        buf.extend_from_slice(&build_empty_texture_section());
4828        buf.extend_from_slice(&build_empty_material_section());
4829        buf.extend_from_slice(&build_bone_section_header(2));
4830
4831        buf.extend_from_slice(&build_bone_name_bytes("Root"));
4832        buf.extend_from_slice(&build_bone_name_bytes(""));
4833        buf.extend_from_slice(&0.0f32.to_le_bytes());
4834        buf.extend_from_slice(&0.0f32.to_le_bytes());
4835        buf.extend_from_slice(&0.0f32.to_le_bytes());
4836        buf.extend_from_slice(&(-1i16).to_le_bytes());
4837        buf.extend_from_slice(&0i32.to_le_bytes());
4838        buf.extend_from_slice(&0u16.to_le_bytes());
4839        buf.extend_from_slice(&0.0f32.to_le_bytes());
4840        buf.extend_from_slice(&0.0f32.to_le_bytes());
4841        buf.extend_from_slice(&0.0f32.to_le_bytes());
4842
4843        buf.extend_from_slice(&build_bone_name_bytes("Child"));
4844        buf.extend_from_slice(&build_bone_name_bytes(""));
4845        buf.extend_from_slice(&0.0f32.to_le_bytes());
4846        buf.extend_from_slice(&1.0f32.to_le_bytes());
4847        buf.extend_from_slice(&0.0f32.to_le_bytes());
4848        buf.extend_from_slice(&0i16.to_le_bytes());
4849        buf.extend_from_slice(&0i32.to_le_bytes());
4850        buf.extend_from_slice(&0u16.to_le_bytes());
4851        buf.extend_from_slice(&0.0f32.to_le_bytes());
4852        buf.extend_from_slice(&0.0f32.to_le_bytes());
4853        buf.extend_from_slice(&0.0f32.to_le_bytes());
4854
4855        buf.extend_from_slice(&0i32.to_le_bytes()); // morphs
4856        buf.extend_from_slice(&1i32.to_le_bytes()); // display frames
4857        buf.extend_from_slice(&build_bone_name_bytes("RootFrame"));
4858        buf.extend_from_slice(&build_bone_name_bytes(""));
4859        buf.push(1);
4860        buf.extend_from_slice(&1i32.to_le_bytes());
4861        buf.push(0);
4862        buf.extend_from_slice(&1i16.to_le_bytes());
4863        buf.extend_from_slice(&0i32.to_le_bytes()); // rigid bodies
4864        buf.extend_from_slice(&0i32.to_le_bytes()); // joints
4865
4866        let parsed = parse_pmx_model(&buf).unwrap();
4867        let exported = export_pmx_model(&parsed);
4868        let reparsed = parse_pmx_model(&exported).unwrap();
4869
4870        assert_pmx_roundtrip_eq(&parsed, &reparsed);
4871    }
4872
4873    #[test]
4874    fn runtime_import_ik_solver_name_lookup() {
4875        let mut buf = Vec::new();
4876        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4877        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4878        buf.extend_from_slice(&build_empty_vertex_section());
4879        buf.extend_from_slice(&build_empty_face_section());
4880        buf.extend_from_slice(&build_empty_texture_section());
4881        buf.extend_from_slice(&build_empty_material_section());
4882        buf.extend_from_slice(&build_bone_section_header(3));
4883
4884        buf.extend_from_slice(&build_bone_name_bytes("Root"));
4885        buf.extend_from_slice(&build_bone_name_bytes(""));
4886        buf.extend_from_slice(&0.0f32.to_le_bytes());
4887        buf.extend_from_slice(&0.0f32.to_le_bytes());
4888        buf.extend_from_slice(&0.0f32.to_le_bytes());
4889        buf.extend_from_slice(&(-1i16).to_le_bytes());
4890        buf.extend_from_slice(&0i32.to_le_bytes());
4891        buf.extend_from_slice(&0u16.to_le_bytes());
4892        buf.extend_from_slice(&0.0f32.to_le_bytes());
4893        buf.extend_from_slice(&0.0f32.to_le_bytes());
4894        buf.extend_from_slice(&0.0f32.to_le_bytes());
4895
4896        buf.extend_from_slice(&build_bone_name_bytes("IKLink"));
4897        buf.extend_from_slice(&build_bone_name_bytes(""));
4898        buf.extend_from_slice(&2.0f32.to_le_bytes());
4899        buf.extend_from_slice(&0.0f32.to_le_bytes());
4900        buf.extend_from_slice(&0.0f32.to_le_bytes());
4901        buf.extend_from_slice(&0i16.to_le_bytes());
4902        buf.extend_from_slice(&0i32.to_le_bytes());
4903        buf.extend_from_slice(&0u16.to_le_bytes());
4904        buf.extend_from_slice(&0.0f32.to_le_bytes());
4905        buf.extend_from_slice(&0.0f32.to_le_bytes());
4906        buf.extend_from_slice(&0.0f32.to_le_bytes());
4907
4908        buf.extend_from_slice(&build_bone_name_bytes("LeftLegIK"));
4909        buf.extend_from_slice(&build_bone_name_bytes(""));
4910        buf.extend_from_slice(&4.0f32.to_le_bytes());
4911        buf.extend_from_slice(&0.0f32.to_le_bytes());
4912        buf.extend_from_slice(&0.0f32.to_le_bytes());
4913        buf.extend_from_slice(&0i16.to_le_bytes());
4914        buf.extend_from_slice(&0i32.to_le_bytes());
4915        buf.extend_from_slice(&(BONE_FLAG_TAIL_INDEX | BONE_FLAG_IK).to_le_bytes());
4916        buf.extend_from_slice(&1i16.to_le_bytes());
4917        buf.extend_from_slice(&1i16.to_le_bytes());
4918        buf.extend_from_slice(&10i32.to_le_bytes());
4919        buf.extend_from_slice(&0.5f32.to_le_bytes());
4920        buf.extend_from_slice(&1i32.to_le_bytes());
4921        buf.extend_from_slice(&1i16.to_le_bytes());
4922        buf.push(1u8);
4923        buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4924        buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4925        buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4926        buf.extend_from_slice(&1.0f32.to_le_bytes());
4927        buf.extend_from_slice(&1.0f32.to_le_bytes());
4928        buf.extend_from_slice(&1.0f32.to_le_bytes());
4929        buf.extend_from_slice(&0i32.to_le_bytes());
4930
4931        let rt = import_pmx_runtime(&buf).unwrap();
4932        assert_eq!(rt.model.bone_count(), 3);
4933        assert_eq!(rt.model.ik_count(), 1);
4934        assert_eq!(
4935            rt.ik_solver_bone_name_to_index.get(b"LeftLegIK".as_slice()),
4936            Some(&0)
4937        );
4938        assert_eq!(
4939            rt.bone_name_to_index.get(b"LeftLegIK".as_slice()),
4940            Some(&BoneIndex(2))
4941        );
4942        assert_eq!(
4943            rt.ik_solver_bone_name_to_index.get(b"NoSuchIK".as_slice()),
4944            None
4945        );
4946    }
4947
4948    #[test]
4949    fn converts_absolute_pmx_positions_to_local_rest_positions() {
4950        let mut buf = Vec::new();
4951        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4952        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4953        buf.extend_from_slice(&build_empty_vertex_section());
4954        buf.extend_from_slice(&build_empty_face_section());
4955        buf.extend_from_slice(&build_empty_texture_section());
4956        buf.extend_from_slice(&build_empty_material_section());
4957        buf.extend_from_slice(&build_bone_section_header(2));
4958
4959        buf.extend_from_slice(&build_bone_name_bytes("Parent"));
4960        buf.extend_from_slice(&build_bone_name_bytes(""));
4961        buf.extend_from_slice(&1.0f32.to_le_bytes());
4962        buf.extend_from_slice(&2.0f32.to_le_bytes());
4963        buf.extend_from_slice(&0.0f32.to_le_bytes());
4964        buf.extend_from_slice(&(-1i16).to_le_bytes());
4965        buf.extend_from_slice(&0i32.to_le_bytes());
4966        buf.extend_from_slice(&0u16.to_le_bytes());
4967        buf.extend_from_slice(&0.0f32.to_le_bytes());
4968        buf.extend_from_slice(&0.0f32.to_le_bytes());
4969        buf.extend_from_slice(&0.0f32.to_le_bytes());
4970
4971        buf.extend_from_slice(&build_bone_name_bytes("Child"));
4972        buf.extend_from_slice(&build_bone_name_bytes(""));
4973        buf.extend_from_slice(&1.0f32.to_le_bytes());
4974        buf.extend_from_slice(&3.0f32.to_le_bytes());
4975        buf.extend_from_slice(&0.0f32.to_le_bytes());
4976        buf.extend_from_slice(&0i16.to_le_bytes());
4977        buf.extend_from_slice(&0i32.to_le_bytes());
4978        buf.extend_from_slice(&0u16.to_le_bytes());
4979        buf.extend_from_slice(&0.0f32.to_le_bytes());
4980        buf.extend_from_slice(&0.0f32.to_le_bytes());
4981        buf.extend_from_slice(&0.0f32.to_le_bytes());
4982
4983        let result = import_pmx_model(&buf).unwrap();
4984        assert_eq!(result.bones.len(), 2);
4985        assert_eq!(result.bones[0].parent, None);
4986        assert!((result.bones[0].rest_position.x - 1.0).abs() < 0.001);
4987        assert!((result.bones[0].rest_position.y - 2.0).abs() < 0.001);
4988        assert!((result.bones[0].rest_position.z - 0.0).abs() < 0.001);
4989        assert_eq!(result.bones[1].parent, Some(BoneIndex(0)));
4990        assert!((result.bones[1].rest_position.x - 0.0).abs() < 0.001);
4991        assert!((result.bones[1].rest_position.y - 1.0).abs() < 0.001);
4992        assert!((result.bones[1].rest_position.z - 0.0).abs() < 0.001);
4993    }
4994
4995    #[test]
4996    fn root_inverse_bind_cancels_rest_world() {
4997        let mut buf = Vec::new();
4998        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4999        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5000        buf.extend_from_slice(&build_empty_vertex_section());
5001        buf.extend_from_slice(&build_empty_face_section());
5002        buf.extend_from_slice(&build_empty_texture_section());
5003        buf.extend_from_slice(&build_empty_material_section());
5004        buf.extend_from_slice(&build_bone_section_header(1));
5005
5006        buf.extend_from_slice(&build_bone_name_bytes("Root"));
5007        buf.extend_from_slice(&build_bone_name_bytes(""));
5008        buf.extend_from_slice(&1.0f32.to_le_bytes());
5009        buf.extend_from_slice(&2.0f32.to_le_bytes());
5010        buf.extend_from_slice(&3.0f32.to_le_bytes());
5011        buf.extend_from_slice(&(-1i16).to_le_bytes());
5012        buf.extend_from_slice(&0i32.to_le_bytes());
5013        buf.extend_from_slice(&0u16.to_le_bytes());
5014        buf.extend_from_slice(&0.0f32.to_le_bytes());
5015        buf.extend_from_slice(&0.0f32.to_le_bytes());
5016        buf.extend_from_slice(&0.0f32.to_le_bytes());
5017
5018        let result = import_pmx_model(&buf).unwrap();
5019        let ibm = result.bones[0].inverse_bind_matrix;
5020        let rest_world = Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0));
5021        assert_eq!(rest_world * ibm, Mat4::IDENTITY);
5022    }
5023
5024    #[test]
5025    fn reads_pmx_fixed_axis_bone_descriptor() {
5026        let mut buf = Vec::new();
5027        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5028        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5029        buf.extend_from_slice(&build_empty_vertex_section());
5030        buf.extend_from_slice(&build_empty_face_section());
5031        buf.extend_from_slice(&build_empty_texture_section());
5032        buf.extend_from_slice(&build_empty_material_section());
5033        buf.extend_from_slice(&build_bone_section_header(1));
5034
5035        buf.extend_from_slice(&build_bone_name_bytes("Fixed"));
5036        buf.extend_from_slice(&build_bone_name_bytes(""));
5037        buf.extend_from_slice(&0.0f32.to_le_bytes());
5038        buf.extend_from_slice(&0.0f32.to_le_bytes());
5039        buf.extend_from_slice(&0.0f32.to_le_bytes());
5040        buf.extend_from_slice(&(-1i16).to_le_bytes());
5041        buf.extend_from_slice(&0i32.to_le_bytes());
5042        buf.extend_from_slice(&BONE_FLAG_FIXED_AXIS.to_le_bytes());
5043        buf.extend_from_slice(&0.0f32.to_le_bytes());
5044        buf.extend_from_slice(&0.0f32.to_le_bytes());
5045        buf.extend_from_slice(&0.0f32.to_le_bytes());
5046        buf.extend_from_slice(&0.0f32.to_le_bytes());
5047        buf.extend_from_slice(&1.0f32.to_le_bytes());
5048        buf.extend_from_slice(&0.0f32.to_le_bytes());
5049
5050        let result = import_pmx_model(&buf).unwrap();
5051        assert_eq!(result.bones[0].fixed_axis, Some(Vec3A::Y));
5052    }
5053
5054    #[test]
5055    fn child_inverse_bind_uses_absolute_not_local_position() {
5056        let mut buf = Vec::new();
5057        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5058        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5059        buf.extend_from_slice(&build_empty_vertex_section());
5060        buf.extend_from_slice(&build_empty_face_section());
5061        buf.extend_from_slice(&build_empty_texture_section());
5062        buf.extend_from_slice(&build_empty_material_section());
5063        buf.extend_from_slice(&build_bone_section_header(2));
5064
5065        buf.extend_from_slice(&build_bone_name_bytes("Root"));
5066        buf.extend_from_slice(&build_bone_name_bytes(""));
5067        buf.extend_from_slice(&1.0f32.to_le_bytes());
5068        buf.extend_from_slice(&0.0f32.to_le_bytes());
5069        buf.extend_from_slice(&0.0f32.to_le_bytes());
5070        buf.extend_from_slice(&(-1i16).to_le_bytes());
5071        buf.extend_from_slice(&0i32.to_le_bytes());
5072        buf.extend_from_slice(&0u16.to_le_bytes());
5073        buf.extend_from_slice(&0.0f32.to_le_bytes());
5074        buf.extend_from_slice(&0.0f32.to_le_bytes());
5075        buf.extend_from_slice(&0.0f32.to_le_bytes());
5076
5077        buf.extend_from_slice(&build_bone_name_bytes("Child"));
5078        buf.extend_from_slice(&build_bone_name_bytes(""));
5079        buf.extend_from_slice(&5.0f32.to_le_bytes());
5080        buf.extend_from_slice(&0.0f32.to_le_bytes());
5081        buf.extend_from_slice(&0.0f32.to_le_bytes());
5082        buf.extend_from_slice(&0i16.to_le_bytes());
5083        buf.extend_from_slice(&0i32.to_le_bytes());
5084        buf.extend_from_slice(&0u16.to_le_bytes());
5085        buf.extend_from_slice(&0.0f32.to_le_bytes());
5086        buf.extend_from_slice(&0.0f32.to_le_bytes());
5087        buf.extend_from_slice(&0.0f32.to_le_bytes());
5088
5089        let result = import_pmx_model(&buf).unwrap();
5090        assert_eq!(result.bones[1].parent, Some(BoneIndex(0)));
5091        assert_eq!(result.bones[1].rest_position, Vec3A::new(4.0, 0.0, 0.0));
5092        let ibm = result.bones[1].inverse_bind_matrix;
5093        let rest_world = Mat4::from_translation(Vec3::new(5.0, 0.0, 0.0));
5094        assert_eq!(rest_world * ibm, Mat4::IDENTITY);
5095    }
5096
5097    #[test]
5098    fn runtime_rest_pose_skinning_matrices_are_identity() {
5099        let mut buf = Vec::new();
5100        buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5101        buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5102        buf.extend_from_slice(&build_empty_vertex_section());
5103        buf.extend_from_slice(&build_empty_face_section());
5104        buf.extend_from_slice(&build_empty_texture_section());
5105        buf.extend_from_slice(&build_empty_material_section());
5106        buf.extend_from_slice(&build_bone_section_header(2));
5107
5108        buf.extend_from_slice(&build_bone_name_bytes("Parent"));
5109        buf.extend_from_slice(&build_bone_name_bytes(""));
5110        buf.extend_from_slice(&1.0f32.to_le_bytes());
5111        buf.extend_from_slice(&2.0f32.to_le_bytes());
5112        buf.extend_from_slice(&0.0f32.to_le_bytes());
5113        buf.extend_from_slice(&(-1i16).to_le_bytes());
5114        buf.extend_from_slice(&0i32.to_le_bytes());
5115        buf.extend_from_slice(&0u16.to_le_bytes());
5116        buf.extend_from_slice(&0.0f32.to_le_bytes());
5117        buf.extend_from_slice(&0.0f32.to_le_bytes());
5118        buf.extend_from_slice(&0.0f32.to_le_bytes());
5119
5120        buf.extend_from_slice(&build_bone_name_bytes("Child"));
5121        buf.extend_from_slice(&build_bone_name_bytes(""));
5122        buf.extend_from_slice(&1.0f32.to_le_bytes());
5123        buf.extend_from_slice(&3.0f32.to_le_bytes());
5124        buf.extend_from_slice(&0.0f32.to_le_bytes());
5125        buf.extend_from_slice(&0i16.to_le_bytes());
5126        buf.extend_from_slice(&0i32.to_le_bytes());
5127        buf.extend_from_slice(&0u16.to_le_bytes());
5128        buf.extend_from_slice(&0.0f32.to_le_bytes());
5129        buf.extend_from_slice(&0.0f32.to_le_bytes());
5130        buf.extend_from_slice(&0.0f32.to_le_bytes());
5131
5132        buf.extend_from_slice(&0i32.to_le_bytes());
5133
5134        let rt = import_pmx_runtime(&buf).unwrap();
5135        let model = Arc::new(rt.model);
5136        let mut instance = RuntimeInstance::new(model);
5137        instance.evaluate_current_pose();
5138        let matrices = instance.skinning_matrices();
5139        assert_eq!(matrices.len(), 2);
5140        assert_eq!(matrices[0], Mat4::IDENTITY);
5141        assert_eq!(matrices[1], Mat4::IDENTITY);
5142    }
5143}