Skip to main content

mmd_anim_format/
pmd.rs

1use std::collections::HashMap;
2
3use encoding_rs::SHIFT_JIS;
4use glam::Vec3A;
5use mmd_anim_runtime::{
6    BoneIndex, BoneInit, IkLinkInit, IkSolverInit, ModelArena, MorphIndex, MorphInit,
7    MorphOffsetSpan, VertexMorphOffset,
8};
9use serde::{Deserialize, Serialize};
10
11use crate::error::ImportError;
12use crate::normalize::normalize_vmd_name;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct PmdParsedModel {
17    pub metadata: PmdParsedMetadata,
18    pub geometry: PmdParsedGeometry,
19    pub materials: Vec<PmdParsedMaterial>,
20    pub toon_textures: Vec<String>,
21    pub toon_texture_bytes: Vec<Vec<u8>>,
22    pub skeleton: PmdParsedSkeleton,
23    pub morphs: Vec<PmdParsedMorph>,
24    pub display_frames: Vec<PmdParsedDisplayFrame>,
25    pub rigid_bodies: Vec<PmdParsedRigidBody>,
26    pub joints: Vec<PmdParsedJoint>,
27    pub diagnostics: Vec<PmdParserDiagnostic>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct PmdParsedMetadata {
33    pub format: String,
34    pub version: f32,
35    pub encoding: String,
36    pub name: String,
37    pub name_bytes: Vec<u8>,
38    pub english_name: String,
39    pub english_name_bytes: Vec<u8>,
40    pub comment: String,
41    pub comment_bytes: Vec<u8>,
42    pub english_comment: String,
43    pub english_comment_bytes: Vec<u8>,
44    pub counts: PmdParsedCounts,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct PmdParsedCounts {
50    pub vertices: usize,
51    pub faces: usize,
52    pub materials: usize,
53    pub bones: usize,
54    pub ik: usize,
55    pub morphs: usize,
56    pub display_frames: usize,
57    pub rigid_bodies: usize,
58    pub joints: usize,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct PmdParsedGeometry {
64    pub vertices: Vec<PmdParsedVertex>,
65    pub indices: Vec<u16>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct PmdParsedVertex {
71    pub position: [f32; 3],
72    pub normal: [f32; 3],
73    pub uv: [f32; 2],
74    pub bone_indices: [i32; 2],
75    pub bone_weight: u8,
76    pub edge_enabled: bool,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct PmdParsedMaterial {
82    pub diffuse: [f32; 4],
83    pub specular_power: f32,
84    pub specular: [f32; 3],
85    pub ambient: [f32; 3],
86    pub toon_index: u8,
87    pub edge_enabled: bool,
88    pub face_count: u32,
89    pub texture_name: String,
90    pub texture_name_bytes: Vec<u8>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct PmdParsedSkeleton {
95    pub bones: Vec<PmdParsedBone>,
96    pub ik: Vec<PmdParsedIk>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct PmdParsedBone {
102    pub name: String,
103    pub name_bytes: Vec<u8>,
104    pub english_name: String,
105    pub english_name_bytes: Vec<u8>,
106    pub parent_index: i32,
107    pub tail_index: i32,
108    pub bone_type: u8,
109    pub ik_index: i32,
110    pub position: [f32; 3],
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(rename_all = "camelCase")]
115pub struct PmdParsedIk {
116    pub bone_index: u16,
117    pub target_bone_index: u16,
118    pub loop_count: u16,
119    pub limit_angle: f32,
120    pub links: Vec<u16>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct PmdParsedMorph {
126    pub name: String,
127    pub name_bytes: Vec<u8>,
128    pub english_name: String,
129    pub english_name_bytes: Vec<u8>,
130    #[serde(rename = "type")]
131    pub kind: String,
132    pub vertex_count: usize,
133    pub vertex_offsets: Vec<PmdParsedMorphVertexOffset>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct PmdParsedMorphVertexOffset {
139    pub vertex_index: u32,
140    pub position: [f32; 3],
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct PmdParsedDisplayFrame {
146    pub name: String,
147    pub name_bytes: Vec<u8>,
148    pub english_name: String,
149    pub english_name_bytes: Vec<u8>,
150    pub frames: Vec<PmdParsedDisplayFrameElement>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct PmdParsedDisplayFrameElement {
155    #[serde(rename = "type")]
156    pub kind: String,
157    pub index: u16,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct PmdParsedRigidBody {
163    pub name: String,
164    pub name_bytes: Vec<u8>,
165    pub bone_index: i32,
166    pub group: u8,
167    pub mask: u16,
168    pub shape: String,
169    pub size: [f32; 3],
170    pub position: [f32; 3],
171    pub rotation: [f32; 3],
172    pub mass: f32,
173    pub linear_damping: f32,
174    pub angular_damping: f32,
175    pub restitution: f32,
176    pub friction: f32,
177    pub mode: String,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct PmdParsedJoint {
183    pub name: String,
184    pub name_bytes: Vec<u8>,
185    pub rigid_body_index_a: u32,
186    pub rigid_body_index_b: u32,
187    pub position: [f32; 3],
188    pub rotation: [f32; 3],
189    pub translation_min: [f32; 3],
190    pub translation_max: [f32; 3],
191    pub rotation_min: [f32; 3],
192    pub rotation_max: [f32; 3],
193    pub spring_translation: [f32; 3],
194    pub spring_rotation: [f32; 3],
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct PmdParserDiagnostic {
199    pub level: String,
200    pub code: String,
201    pub message: String,
202}
203
204#[derive(Debug)]
205pub struct PmdRuntimeImport {
206    pub model: ModelArena,
207    pub bone_names: Vec<String>,
208    pub bone_name_to_index: HashMap<Vec<u8>, BoneIndex>,
209    pub morph_name_to_index: HashMap<Vec<u8>, MorphIndex>,
210    pub ik_solver_bone_name_to_index: HashMap<Vec<u8>, usize>,
211    pub diagnostics: Vec<PmdParserDiagnostic>,
212}
213
214struct Reader<'a> {
215    data: &'a [u8],
216    pos: usize,
217}
218
219impl<'a> Reader<'a> {
220    fn new(data: &'a [u8]) -> Self {
221        Self { data, pos: 0 }
222    }
223
224    fn remaining(&self) -> usize {
225        self.data.len().saturating_sub(self.pos)
226    }
227
228    fn peek_u32_at(&self, pos: usize) -> Option<u32> {
229        let bytes = self.data.get(pos..pos + 4)?;
230        Some(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
231    }
232
233    fn read(&mut self, n: usize) -> Result<&'a [u8], ImportError> {
234        if self.remaining() < n {
235            return Err(ImportError::UnexpectedEof(n - self.remaining()));
236        }
237        let out = &self.data[self.pos..self.pos + n];
238        self.pos += n;
239        Ok(out)
240    }
241
242    fn skip(&mut self, n: usize) -> Result<(), ImportError> {
243        self.read(n).map(|_| ())
244    }
245
246    fn u8(&mut self) -> Result<u8, ImportError> {
247        Ok(self.read(1)?[0])
248    }
249
250    fn u16(&mut self) -> Result<u16, ImportError> {
251        let b = self.read(2)?;
252        Ok(u16::from_le_bytes([b[0], b[1]]))
253    }
254
255    fn u32(&mut self) -> Result<u32, ImportError> {
256        let b = self.read(4)?;
257        Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
258    }
259
260    fn f32(&mut self) -> Result<f32, ImportError> {
261        let b = self.read(4)?;
262        Ok(f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
263    }
264
265    fn vec3(&mut self) -> Result<[f32; 3], ImportError> {
266        Ok([self.f32()?, self.f32()?, self.f32()?])
267    }
268
269    fn fixed_text_raw(&mut self, n: usize) -> Result<(String, Vec<u8>), ImportError> {
270        let bytes = self.read(n)?;
271        Ok((decode_sjis_fixed(bytes), bytes.to_vec()))
272    }
273}
274
275pub fn parse_pmd_model(data: &[u8]) -> Result<PmdParsedModel, ImportError> {
276    let mut r = Reader::new(data);
277    if r.read(3)? != b"Pmd" {
278        return Err(ImportError::InvalidMagic { format: "PMD" });
279    }
280    let version = r.f32()?;
281    let (name, name_bytes) = r.fixed_text_raw(20)?;
282    let (comment, comment_bytes) = r.fixed_text_raw(256)?;
283
284    let vertex_count = r.u32()? as usize;
285    let mut vertices = Vec::with_capacity(vertex_count);
286    for _ in 0..vertex_count {
287        vertices.push(PmdParsedVertex {
288            position: r.vec3()?,
289            normal: r.vec3()?,
290            uv: [r.f32()?, r.f32()?],
291            bone_indices: [normalize_index(r.u16()?), normalize_index(r.u16()?)],
292            bone_weight: r.u8()?,
293            edge_enabled: r.u8()? == 0,
294        });
295    }
296    let index_count = r.u32()? as usize;
297    let mut indices = Vec::with_capacity(index_count);
298    for _ in 0..index_count {
299        indices.push(r.u16()?);
300    }
301
302    let material_count = r.u32()? as usize;
303    let mut materials = Vec::with_capacity(material_count);
304    for _ in 0..material_count {
305        let diffuse = [r.f32()?, r.f32()?, r.f32()?, r.f32()?];
306        let specular_power = r.f32()?;
307        let specular = [r.f32()?, r.f32()?, r.f32()?];
308        let ambient = [r.f32()?, r.f32()?, r.f32()?];
309        let toon_index = r.u8()?;
310        let edge_enabled = r.u8()? != 0;
311        let face_count = r.u32()? / 3;
312        let (texture_name, texture_name_bytes) = r.fixed_text_raw(20)?;
313        materials.push(PmdParsedMaterial {
314            diffuse,
315            specular_power,
316            specular,
317            ambient,
318            toon_index,
319            edge_enabled,
320            face_count,
321            texture_name,
322            texture_name_bytes,
323        });
324    }
325
326    let bone_count = r.u16()? as usize;
327    let mut bones = Vec::with_capacity(bone_count);
328    for _ in 0..bone_count {
329        let (name, name_bytes) = r.fixed_text_raw(20)?;
330        bones.push(PmdParsedBone {
331            name,
332            name_bytes,
333            english_name: String::new(),
334            english_name_bytes: Vec::new(),
335            parent_index: normalize_index(r.u16()?),
336            tail_index: normalize_index(r.u16()?),
337            bone_type: r.u8()?,
338            ik_index: normalize_index(r.u16()?),
339            position: r.vec3()?,
340        });
341    }
342
343    let ik_count = r.u16()? as usize;
344    let mut ik = Vec::with_capacity(ik_count);
345    for _ in 0..ik_count {
346        let bone_index = r.u16()?;
347        let target_bone_index = r.u16()?;
348        let link_count = r.u8()? as usize;
349        let loop_count = r.u16()?;
350        let limit_angle = r.f32()?;
351        let mut links = Vec::with_capacity(link_count);
352        for _ in 0..link_count {
353            links.push(r.u16()?);
354        }
355        ik.push(PmdParsedIk {
356            bone_index,
357            target_bone_index,
358            loop_count,
359            limit_angle,
360            links,
361        });
362    }
363
364    let morph_count = r.u16()? as usize;
365    let mut morphs = Vec::with_capacity(morph_count);
366    for _ in 0..morph_count {
367        let (name, name_bytes) = r.fixed_text_raw(20)?;
368        let vertex_count = r.u32()? as usize;
369        let morph_type = r.u8()?;
370        let mut vertex_offsets = Vec::with_capacity(vertex_count);
371        for _ in 0..vertex_count {
372            vertex_offsets.push(PmdParsedMorphVertexOffset {
373                vertex_index: r.u32()?,
374                position: r.vec3()?,
375            });
376        }
377        morphs.push(PmdParsedMorph {
378            name,
379            name_bytes,
380            english_name: String::new(),
381            english_name_bytes: Vec::new(),
382            kind: if morph_type == 0 { "base" } else { "vertex" }.to_owned(),
383            vertex_count,
384            vertex_offsets,
385        });
386    }
387
388    let mut display_frames = Vec::new();
389    let mut bone_display_name_count = 0usize;
390    let mut bone_display_frame_start = 0usize;
391    if r.remaining() >= 1 {
392        let morph_display_count = r.u8()? as usize;
393        for _ in 0..morph_display_count {
394            let index = r.u16()?;
395            display_frames.push(PmdParsedDisplayFrame {
396                name: morphs
397                    .get(index as usize)
398                    .map(|m| m.name.clone())
399                    .unwrap_or_default(),
400                name_bytes: morphs
401                    .get(index as usize)
402                    .map(|m| m.name_bytes.clone())
403                    .unwrap_or_default(),
404                english_name: String::new(),
405                english_name_bytes: Vec::new(),
406                frames: vec![PmdParsedDisplayFrameElement {
407                    kind: "morph".to_owned(),
408                    index,
409                }],
410            });
411        }
412    }
413    if r.remaining() >= 1 {
414        bone_display_name_count = r.u8()? as usize;
415        bone_display_frame_start = display_frames.len();
416        for _ in 0..bone_display_name_count {
417            let (name, name_bytes) = r.fixed_text_raw(50)?;
418            display_frames.push(PmdParsedDisplayFrame {
419                name,
420                name_bytes,
421                english_name: String::new(),
422                english_name_bytes: Vec::new(),
423                frames: Vec::new(),
424            });
425        }
426        if r.remaining() >= 4 {
427            let bone_display_count = r.u32()? as usize;
428            for _ in 0..bone_display_count {
429                let bone_index = r.u16()?;
430                let frame_index = r.u8()? as usize;
431                if let Some(frame) =
432                    display_frames.get_mut(bone_display_frame_start + frame_index.saturating_sub(1))
433                {
434                    frame.frames.push(PmdParsedDisplayFrameElement {
435                        kind: "bone".to_owned(),
436                        index: bone_index,
437                    });
438                }
439            }
440        }
441    }
442
443    let mut english_name = String::new();
444    let mut english_comment = String::new();
445    let mut english_name_bytes = Vec::new();
446    let mut english_comment_bytes = Vec::new();
447    if r.remaining() >= 1 && r.u8()? != 0 {
448        (english_name, english_name_bytes) = r.fixed_text_raw(20)?;
449        (english_comment, english_comment_bytes) = r.fixed_text_raw(256)?;
450        for bone in &mut bones {
451            (bone.english_name, bone.english_name_bytes) = r.fixed_text_raw(20)?;
452        }
453        for morph in morphs.iter_mut().skip(1) {
454            (morph.english_name, morph.english_name_bytes) = r.fixed_text_raw(20)?;
455        }
456        for index in 0..bone_display_name_count {
457            if let Some(frame) = display_frames.get_mut(bone_display_frame_start + index) {
458                (frame.english_name, frame.english_name_bytes) = r.fixed_text_raw(50)?;
459            } else {
460                r.skip(50)?;
461            }
462        }
463    }
464
465    let mut toon_textures = Vec::new();
466    let mut toon_texture_bytes = Vec::new();
467    if r.remaining() >= 1000 && !is_plausible_pmd_physics_tail(&r) {
468        toon_textures.reserve(10);
469        toon_texture_bytes.reserve(10);
470        for _ in 0..10 {
471            let (toon_texture, bytes) = r.fixed_text_raw(100)?;
472            toon_textures.push(toon_texture);
473            toon_texture_bytes.push(bytes);
474        }
475    }
476
477    let rigid_bodies = if r.remaining() >= 4 {
478        read_rigid_bodies(&mut r)?
479    } else {
480        Vec::new()
481    };
482    let joints = if r.remaining() >= 4 {
483        read_joints(&mut r)?
484    } else {
485        Vec::new()
486    };
487
488    Ok(PmdParsedModel {
489        metadata: PmdParsedMetadata {
490            format: "pmd".to_owned(),
491            version,
492            encoding: "shift-jis".to_owned(),
493            name,
494            name_bytes,
495            english_name,
496            english_name_bytes,
497            comment,
498            comment_bytes,
499            english_comment,
500            english_comment_bytes,
501            counts: PmdParsedCounts {
502                vertices: vertex_count,
503                faces: index_count / 3,
504                materials: materials.len(),
505                bones: bones.len(),
506                ik: ik.len(),
507                morphs: morphs.len(),
508                display_frames: display_frames.len(),
509                rigid_bodies: rigid_bodies.len(),
510                joints: joints.len(),
511            },
512        },
513        geometry: PmdParsedGeometry { vertices, indices },
514        materials,
515        toon_textures,
516        toon_texture_bytes,
517        skeleton: PmdParsedSkeleton { bones, ik },
518        morphs,
519        display_frames,
520        rigid_bodies,
521        joints,
522        diagnostics: Vec::new(),
523    })
524}
525
526pub fn import_pmd_runtime(data: &[u8]) -> Result<PmdRuntimeImport, ImportError> {
527    let parsed = parse_pmd_model(data)?;
528    build_pmd_runtime_from_parsed(parsed)
529}
530
531pub fn export_pmd_model(model: &PmdParsedModel) -> Vec<u8> {
532    let mut out = Vec::new();
533    out.extend_from_slice(b"Pmd");
534    write_f32(&mut out, model.metadata.version);
535    write_fixed_text(
536        &mut out,
537        &model.metadata.name,
538        &model.metadata.name_bytes,
539        20,
540    );
541    write_fixed_text(
542        &mut out,
543        &model.metadata.comment,
544        &model.metadata.comment_bytes,
545        256,
546    );
547
548    write_u32(&mut out, model.geometry.vertices.len() as u32);
549    for vertex in &model.geometry.vertices {
550        write_vec3(&mut out, vertex.position);
551        write_vec3(&mut out, vertex.normal);
552        write_f32(&mut out, vertex.uv[0]);
553        write_f32(&mut out, vertex.uv[1]);
554        write_index_u16(&mut out, vertex.bone_indices[0]);
555        write_index_u16(&mut out, vertex.bone_indices[1]);
556        out.push(vertex.bone_weight);
557        out.push(if vertex.edge_enabled { 0 } else { 1 });
558    }
559
560    write_u32(&mut out, model.geometry.indices.len() as u32);
561    for &index in &model.geometry.indices {
562        write_u16(&mut out, index);
563    }
564
565    write_u32(&mut out, model.materials.len() as u32);
566    for material in &model.materials {
567        for value in material.diffuse {
568            write_f32(&mut out, value);
569        }
570        write_f32(&mut out, material.specular_power);
571        write_vec3(&mut out, material.specular);
572        write_vec3(&mut out, material.ambient);
573        out.push(material.toon_index);
574        out.push(u8::from(material.edge_enabled));
575        write_u32(&mut out, material.face_count.saturating_mul(3));
576        write_fixed_text(
577            &mut out,
578            &material.texture_name,
579            &material.texture_name_bytes,
580            20,
581        );
582    }
583
584    write_u16(&mut out, model.skeleton.bones.len() as u16);
585    for bone in &model.skeleton.bones {
586        write_fixed_text(&mut out, &bone.name, &bone.name_bytes, 20);
587        write_index_u16(&mut out, bone.parent_index);
588        write_index_u16(&mut out, bone.tail_index);
589        out.push(bone.bone_type);
590        write_index_u16(&mut out, bone.ik_index);
591        write_vec3(&mut out, bone.position);
592    }
593
594    write_u16(&mut out, model.skeleton.ik.len() as u16);
595    for ik in &model.skeleton.ik {
596        write_u16(&mut out, ik.bone_index);
597        write_u16(&mut out, ik.target_bone_index);
598        out.push(ik.links.len() as u8);
599        write_u16(&mut out, ik.loop_count);
600        write_f32(&mut out, ik.limit_angle);
601        for &link in &ik.links {
602            write_u16(&mut out, link);
603        }
604    }
605
606    write_u16(&mut out, model.morphs.len() as u16);
607    for morph in &model.morphs {
608        write_fixed_text(&mut out, &morph.name, &morph.name_bytes, 20);
609        write_u32(&mut out, morph.vertex_offsets.len() as u32);
610        out.push(match morph.kind.as_str() {
611            "base" => 0,
612            _ => 1,
613        });
614        for offset in &morph.vertex_offsets {
615            write_u32(&mut out, offset.vertex_index);
616            write_vec3(&mut out, offset.position);
617        }
618    }
619
620    let morph_display_indices: Vec<u16> = model
621        .display_frames
622        .iter()
623        .filter_map(|frame| {
624            frame
625                .frames
626                .first()
627                .filter(|entry| entry.kind == "morph")
628                .map(|entry| entry.index)
629        })
630        .collect();
631    let bone_display_frames: Vec<&PmdParsedDisplayFrame> = model
632        .display_frames
633        .iter()
634        .filter(|frame| {
635            frame
636                .frames
637                .first()
638                .is_none_or(|entry| entry.kind != "morph")
639        })
640        .collect();
641    let has_english = should_export_pmd_english(model, &bone_display_frames);
642    let has_toon = should_export_pmd_toon(model);
643    let has_physics = !model.rigid_bodies.is_empty() || !model.joints.is_empty();
644    let has_display = !morph_display_indices.is_empty() || !bone_display_frames.is_empty();
645
646    if has_display || has_english || has_toon || has_physics {
647        out.push(morph_display_indices.len() as u8);
648        for index in morph_display_indices {
649            write_u16(&mut out, index);
650        }
651
652        out.push(bone_display_frames.len() as u8);
653        for frame in &bone_display_frames {
654            write_fixed_text(&mut out, &frame.name, &frame.name_bytes, 50);
655        }
656
657        let bone_display_count: usize = bone_display_frames
658            .iter()
659            .map(|frame| {
660                frame
661                    .frames
662                    .iter()
663                    .filter(|entry| entry.kind == "bone")
664                    .count()
665            })
666            .sum();
667        write_u32(&mut out, bone_display_count as u32);
668        for (frame_index, frame) in bone_display_frames.iter().enumerate() {
669            for entry in frame.frames.iter().filter(|entry| entry.kind == "bone") {
670                write_u16(&mut out, entry.index);
671                out.push((frame_index + 1) as u8);
672            }
673        }
674
675        out.push(u8::from(has_english));
676        if has_english {
677            write_fixed_text(
678                &mut out,
679                &model.metadata.english_name,
680                &model.metadata.english_name_bytes,
681                20,
682            );
683            write_fixed_text(
684                &mut out,
685                &model.metadata.english_comment,
686                &model.metadata.english_comment_bytes,
687                256,
688            );
689            for bone in &model.skeleton.bones {
690                write_fixed_text(&mut out, &bone.english_name, &bone.english_name_bytes, 20);
691            }
692            for morph in model.morphs.iter().skip(1) {
693                write_fixed_text(&mut out, &morph.english_name, &morph.english_name_bytes, 20);
694            }
695            for frame in &bone_display_frames {
696                write_fixed_text(&mut out, &frame.english_name, &frame.english_name_bytes, 50);
697            }
698        }
699    }
700
701    if has_toon {
702        for index in 0..10 {
703            let raw = model
704                .toon_texture_bytes
705                .get(index)
706                .map(Vec::as_slice)
707                .unwrap_or(&[]);
708            let text = model
709                .toon_textures
710                .get(index)
711                .map(String::as_str)
712                .unwrap_or("");
713            write_fixed_text(&mut out, text, raw, 100);
714        }
715    }
716
717    if has_physics {
718        write_u32(&mut out, model.rigid_bodies.len() as u32);
719        for body in &model.rigid_bodies {
720            write_fixed_text(&mut out, &body.name, &body.name_bytes, 20);
721            write_index_u16(&mut out, body.bone_index);
722            out.push(body.group);
723            write_u16(&mut out, body.mask);
724            out.push(match body.shape.as_str() {
725                "sphere" => 0,
726                "box" => 1,
727                "capsule" => 2,
728                _ => 0,
729            });
730            write_vec3(&mut out, body.size);
731            write_vec3(&mut out, body.position);
732            write_vec3(&mut out, body.rotation);
733            write_f32(&mut out, body.mass);
734            write_f32(&mut out, body.linear_damping);
735            write_f32(&mut out, body.angular_damping);
736            write_f32(&mut out, body.restitution);
737            write_f32(&mut out, body.friction);
738            out.push(match body.mode.as_str() {
739                "static" => 0,
740                "dynamic" => 1,
741                "dynamicBone" => 2,
742                _ => 0,
743            });
744        }
745
746        write_u32(&mut out, model.joints.len() as u32);
747        for joint in &model.joints {
748            write_fixed_text(&mut out, &joint.name, &joint.name_bytes, 20);
749            write_u32(&mut out, joint.rigid_body_index_a);
750            write_u32(&mut out, joint.rigid_body_index_b);
751            write_vec3(&mut out, joint.position);
752            write_vec3(&mut out, joint.rotation);
753            write_vec3(&mut out, joint.translation_min);
754            write_vec3(&mut out, joint.translation_max);
755            write_vec3(&mut out, joint.rotation_min);
756            write_vec3(&mut out, joint.rotation_max);
757            write_vec3(&mut out, joint.spring_translation);
758            write_vec3(&mut out, joint.spring_rotation);
759        }
760    }
761
762    out
763}
764
765fn build_pmd_runtime_from_parsed(parsed: PmdParsedModel) -> Result<PmdRuntimeImport, ImportError> {
766    let bones = parsed
767        .skeleton
768        .bones
769        .iter()
770        .map(|bone| {
771            BoneInit::new(
772                if bone.parent_index < 0 {
773                    None
774                } else {
775                    Some(BoneIndex(bone.parent_index as u32))
776                },
777                Vec3A::new(bone.position[0], bone.position[1], bone.position[2]),
778            )
779        })
780        .collect::<Vec<_>>();
781
782    let ik_solvers = parsed
783        .skeleton
784        .ik
785        .iter()
786        .map(|ik| IkSolverInit {
787            ik_bone: BoneIndex(u32::from(ik.bone_index)),
788            target_bone: BoneIndex(u32::from(ik.target_bone_index)),
789            links: ik
790                .links
791                .iter()
792                .map(|&link| IkLinkInit::new(BoneIndex(u32::from(link))))
793                .collect(),
794            iteration_count: u32::from(ik.loop_count),
795            limit_angle: ik.limit_angle,
796        })
797        .collect::<Vec<_>>();
798
799    let (vertex_offsets, vertex_spans, vertex_diagnostics) =
800        build_pmd_vertex_morph_offsets(&parsed.morphs, parsed.metadata.counts.vertices);
801    let morph_count = parsed.morphs.len();
802    let morph = MorphInit {
803        morph_count: morph_count as u32,
804        vertex_offsets,
805        vertex_spans,
806        bone_spans: vec![Default::default(); morph_count],
807        group_spans: vec![Default::default(); morph_count],
808        ..MorphInit::default()
809    };
810    let model = ModelArena::new_with_morphs(bones, ik_solvers, Vec::new(), morph)
811        .map_err(ImportError::ModelBuildFailed)?;
812
813    let mut bone_name_to_index = HashMap::with_capacity(parsed.skeleton.bones.len() * 2);
814    for (index, bone) in parsed.skeleton.bones.iter().enumerate() {
815        insert_sjis_name_keys(&mut bone_name_to_index, &bone.name, BoneIndex(index as u32));
816    }
817
818    let mut morph_name_to_index = HashMap::with_capacity(parsed.morphs.len() * 2);
819    for (index, morph) in parsed.morphs.iter().enumerate() {
820        insert_sjis_name_keys(
821            &mut morph_name_to_index,
822            &morph.name,
823            MorphIndex(index as u32),
824        );
825    }
826
827    let mut ik_solver_bone_name_to_index = HashMap::with_capacity(parsed.skeleton.ik.len() * 2);
828    for (solver_index, ik) in parsed.skeleton.ik.iter().enumerate() {
829        if let Some(bone) = parsed.skeleton.bones.get(ik.bone_index as usize) {
830            insert_sjis_name_keys(&mut ik_solver_bone_name_to_index, &bone.name, solver_index);
831        }
832    }
833
834    let mut diagnostics = parsed.diagnostics;
835    diagnostics.extend(vertex_diagnostics);
836    diagnostics.push(PmdParserDiagnostic {
837        level: "warning".to_owned(),
838        code: "PMD_RUNTIME_PARTIAL".to_owned(),
839        message: "PMD runtime import builds bones, IK solvers, morph slots, and vertex morph offsets; material/physics runtime parity is not implemented yet.".to_owned(),
840    });
841
842    Ok(PmdRuntimeImport {
843        model,
844        bone_names: parsed
845            .skeleton
846            .bones
847            .iter()
848            .map(|bone| bone.name.clone())
849            .collect(),
850        bone_name_to_index,
851        morph_name_to_index,
852        ik_solver_bone_name_to_index,
853        diagnostics,
854    })
855}
856
857fn build_pmd_vertex_morph_offsets(
858    morphs: &[PmdParsedMorph],
859    vertex_count: usize,
860) -> (
861    Vec<VertexMorphOffset>,
862    Vec<MorphOffsetSpan>,
863    Vec<PmdParserDiagnostic>,
864) {
865    let mut offsets = Vec::new();
866    let mut spans = vec![MorphOffsetSpan::default(); morphs.len()];
867    let mut diagnostics = Vec::new();
868    let base_vertices = morphs.first().filter(|morph| morph.kind == "base");
869
870    for (morph_index, morph) in morphs.iter().enumerate() {
871        if morph.kind != "vertex" {
872            continue;
873        }
874        let start = offsets.len() as u32;
875        for vertex in &morph.vertex_offsets {
876            let Some(base) = base_vertices.and_then(|base| {
877                base.vertex_offsets
878                    .get(vertex.vertex_index as usize)
879                    .map(|base| base.vertex_index)
880            }) else {
881                diagnostics.push(PmdParserDiagnostic {
882                    level: "warning".to_owned(),
883                    code: "PMD_VERTEX_MORPH_BASE_INDEX_MISSING".to_owned(),
884                    message: format!(
885                        "PMD morph {:?} references missing base morph vertex {}",
886                        morph.name, vertex.vertex_index
887                    ),
888                });
889                continue;
890            };
891            if base as usize >= vertex_count {
892                diagnostics.push(PmdParserDiagnostic {
893                    level: "warning".to_owned(),
894                    code: "PMD_VERTEX_MORPH_VERTEX_INDEX_OUT_OF_RANGE".to_owned(),
895                    message: format!(
896                        "PMD morph {:?} resolved vertex index {} outside vertex count {}",
897                        morph.name, base, vertex_count
898                    ),
899                });
900                continue;
901            }
902            offsets.push(VertexMorphOffset {
903                vertex_index: base,
904                position_offset: Vec3A::new(
905                    vertex.position[0],
906                    vertex.position[1],
907                    vertex.position[2],
908                ),
909            });
910        }
911        spans[morph_index] = MorphOffsetSpan {
912            start,
913            count: offsets.len() as u32 - start,
914        };
915    }
916
917    (offsets, spans, diagnostics)
918}
919
920fn insert_sjis_name_keys<T: Copy>(map: &mut HashMap<Vec<u8>, T>, name: &str, value: T) {
921    let (encoded, _, _) = SHIFT_JIS.encode(name);
922    let normalized = normalize_vmd_name(encoded.as_ref());
923    if !normalized.is_empty() {
924        map.insert(normalized, value);
925    }
926    if !name.is_empty() {
927        map.insert(name.as_bytes().to_vec(), value);
928    }
929}
930
931fn read_rigid_bodies(r: &mut Reader<'_>) -> Result<Vec<PmdParsedRigidBody>, ImportError> {
932    let count = r.u32()? as usize;
933    let mut bodies = Vec::with_capacity(count);
934    for _ in 0..count {
935        let (name, name_bytes) = r.fixed_text_raw(20)?;
936        bodies.push(PmdParsedRigidBody {
937            name,
938            name_bytes,
939            bone_index: normalize_index(r.u16()?),
940            group: r.u8()?,
941            mask: r.u16()?,
942            shape: match r.u8()? {
943                0 => "sphere",
944                1 => "box",
945                2 => "capsule",
946                _ => "unknown",
947            }
948            .to_owned(),
949            size: r.vec3()?,
950            position: r.vec3()?,
951            rotation: r.vec3()?,
952            mass: r.f32()?,
953            linear_damping: r.f32()?,
954            angular_damping: r.f32()?,
955            restitution: r.f32()?,
956            friction: r.f32()?,
957            mode: match r.u8()? {
958                0 => "static",
959                1 => "dynamic",
960                2 => "dynamicBone",
961                _ => "unknown",
962            }
963            .to_owned(),
964        });
965    }
966    Ok(bodies)
967}
968
969fn read_joints(r: &mut Reader<'_>) -> Result<Vec<PmdParsedJoint>, ImportError> {
970    let count = r.u32()? as usize;
971    let mut joints = Vec::with_capacity(count);
972    for _ in 0..count {
973        let (name, name_bytes) = r.fixed_text_raw(20)?;
974        joints.push(PmdParsedJoint {
975            name,
976            name_bytes,
977            rigid_body_index_a: r.u32()?,
978            rigid_body_index_b: r.u32()?,
979            position: r.vec3()?,
980            rotation: r.vec3()?,
981            translation_min: r.vec3()?,
982            translation_max: r.vec3()?,
983            rotation_min: r.vec3()?,
984            rotation_max: r.vec3()?,
985            spring_translation: r.vec3()?,
986            spring_rotation: r.vec3()?,
987        });
988    }
989    Ok(joints)
990}
991
992fn is_plausible_pmd_physics_tail(r: &Reader<'_>) -> bool {
993    let start = r.pos;
994    let Some(rigid_body_count) = r.peek_u32_at(start).map(|count| count as usize) else {
995        return false;
996    };
997    let Some(rigid_body_bytes) = rigid_body_count.checked_mul(83) else {
998        return false;
999    };
1000    let joint_count_offset = start + 4 + rigid_body_bytes;
1001    if joint_count_offset == r.data.len() {
1002        return true;
1003    }
1004    let Some(joint_count) = r
1005        .peek_u32_at(joint_count_offset)
1006        .map(|count| count as usize)
1007    else {
1008        return false;
1009    };
1010    let Some(joint_bytes) = joint_count.checked_mul(124) else {
1011        return false;
1012    };
1013    joint_count_offset + 4 + joint_bytes == r.data.len()
1014}
1015
1016fn normalize_index(index: u16) -> i32 {
1017    if index == 0xffff { -1 } else { index as i32 }
1018}
1019
1020fn write_fixed_text(out: &mut Vec<u8>, text: &str, raw: &[u8], len: usize) {
1021    let mut bytes = vec![0u8; len];
1022    if !raw.is_empty() {
1023        let copy_len = raw.len().min(len);
1024        bytes[..copy_len].copy_from_slice(&raw[..copy_len]);
1025    } else {
1026        let mut cursor = 0;
1027        for ch in text.chars() {
1028            let mut buf = [0u8; 4];
1029            let s = ch.encode_utf8(&mut buf);
1030            let (encoded, _, _) = SHIFT_JIS.encode(s);
1031            let encoded = encoded.as_ref();
1032            if cursor + encoded.len() > len {
1033                break;
1034            }
1035            bytes[cursor..cursor + encoded.len()].copy_from_slice(encoded);
1036            cursor += encoded.len();
1037        }
1038    }
1039    out.extend_from_slice(&bytes);
1040}
1041
1042fn write_index_u16(out: &mut Vec<u8>, index: i32) {
1043    write_u16(out, if index < 0 { 0xffff } else { index as u16 });
1044}
1045
1046fn write_vec3(out: &mut Vec<u8>, values: [f32; 3]) {
1047    for value in values {
1048        write_f32(out, value);
1049    }
1050}
1051
1052fn write_f32(out: &mut Vec<u8>, value: f32) {
1053    out.extend_from_slice(&value.to_le_bytes());
1054}
1055
1056fn write_u16(out: &mut Vec<u8>, value: u16) {
1057    out.extend_from_slice(&value.to_le_bytes());
1058}
1059
1060fn write_u32(out: &mut Vec<u8>, value: u32) {
1061    out.extend_from_slice(&value.to_le_bytes());
1062}
1063
1064fn should_export_pmd_english(
1065    model: &PmdParsedModel,
1066    bone_display_frames: &[&PmdParsedDisplayFrame],
1067) -> bool {
1068    !model.metadata.english_name.is_empty()
1069        || !model.metadata.english_name_bytes.is_empty()
1070        || !model.metadata.english_comment.is_empty()
1071        || !model.metadata.english_comment_bytes.is_empty()
1072        || model
1073            .skeleton
1074            .bones
1075            .iter()
1076            .any(|bone| !bone.english_name.is_empty() || !bone.english_name_bytes.is_empty())
1077        || model
1078            .morphs
1079            .iter()
1080            .skip(1)
1081            .any(|morph| !morph.english_name.is_empty() || !morph.english_name_bytes.is_empty())
1082        || bone_display_frames
1083            .iter()
1084            .any(|frame| !frame.english_name.is_empty() || !frame.english_name_bytes.is_empty())
1085}
1086
1087fn should_export_pmd_toon(model: &PmdParsedModel) -> bool {
1088    model.toon_texture_bytes.len() == 10 || model.toon_textures.len() == 10
1089}
1090
1091fn decode_sjis_fixed(bytes: &[u8]) -> String {
1092    let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
1093    let (decoded, _, _) = SHIFT_JIS.decode(&bytes[..end]);
1094    decoded.trim().to_owned()
1095}
1096
1097#[cfg(test)]
1098mod tests {
1099    use super::*;
1100
1101    fn push_fixed(out: &mut Vec<u8>, value: &[u8], len: usize) {
1102        let mut bytes = vec![0u8; len];
1103        bytes[..value.len().min(len)].copy_from_slice(&value[..value.len().min(len)]);
1104        out.extend_from_slice(&bytes);
1105    }
1106
1107    fn push_bone(
1108        out: &mut Vec<u8>,
1109        name: &[u8],
1110        parent: u16,
1111        tail: u16,
1112        bone_type: u8,
1113        ik_index: u16,
1114        position: [f32; 3],
1115    ) {
1116        push_fixed(out, name, 20);
1117        out.extend_from_slice(&parent.to_le_bytes());
1118        out.extend_from_slice(&tail.to_le_bytes());
1119        out.push(bone_type);
1120        out.extend_from_slice(&ik_index.to_le_bytes());
1121        for value in position {
1122            out.extend_from_slice(&value.to_le_bytes());
1123        }
1124    }
1125
1126    fn minimal_pmd_with_ik() -> Vec<u8> {
1127        let mut out = Vec::new();
1128        out.extend_from_slice(b"Pmd");
1129        out.extend_from_slice(&1.0f32.to_le_bytes());
1130        push_fixed(&mut out, b"model", 20);
1131        push_fixed(&mut out, b"comment", 256);
1132        out.extend_from_slice(&0u32.to_le_bytes()); // vertices
1133        out.extend_from_slice(&0u32.to_le_bytes()); // indices
1134        out.extend_from_slice(&0u32.to_le_bytes()); // materials
1135
1136        out.extend_from_slice(&2u16.to_le_bytes());
1137        push_bone(&mut out, b"root", 0xffff, 1, 0, 0xffff, [0.0, 0.0, 0.0]);
1138        push_bone(&mut out, b"legIK", 0, 0, 2, 0, [0.0, 1.0, 0.0]);
1139
1140        out.extend_from_slice(&1u16.to_le_bytes());
1141        out.extend_from_slice(&1u16.to_le_bytes()); // IK bone
1142        out.extend_from_slice(&0u16.to_le_bytes()); // target bone
1143        out.push(1); // links
1144        out.extend_from_slice(&40u16.to_le_bytes());
1145        out.extend_from_slice(&1.0f32.to_le_bytes());
1146        out.extend_from_slice(&0u16.to_le_bytes());
1147
1148        out.extend_from_slice(&1u16.to_le_bytes());
1149        push_fixed(&mut out, b"base", 20);
1150        out.extend_from_slice(&0u32.to_le_bytes());
1151        out.push(0);
1152        out
1153    }
1154
1155    fn insert_empty_vertices(data: &mut Vec<u8>, vertex_count: u32) {
1156        let vertex_count_pos = 3 + 4 + 20 + 256;
1157        data[vertex_count_pos..vertex_count_pos + 4].copy_from_slice(&vertex_count.to_le_bytes());
1158        let insert_pos = vertex_count_pos + 4;
1159        data.splice(
1160            insert_pos..insert_pos,
1161            vec![0u8; vertex_count as usize * 38],
1162        );
1163    }
1164
1165    fn insert_vertex_and_indices(data: &mut Vec<u8>, vertex: &[u8], indices: &[u16]) {
1166        let vertex_count_pos = 3 + 4 + 20 + 256;
1167        data[vertex_count_pos..vertex_count_pos + 4].copy_from_slice(&1u32.to_le_bytes());
1168        let vertex_insert_pos = vertex_count_pos + 4;
1169        data.splice(vertex_insert_pos..vertex_insert_pos, vertex.iter().copied());
1170        let index_count_pos = vertex_insert_pos + vertex.len();
1171        data[index_count_pos..index_count_pos + 4]
1172            .copy_from_slice(&(indices.len() as u32).to_le_bytes());
1173        let index_insert_pos = index_count_pos + 4;
1174        let mut index_bytes = Vec::new();
1175        for index in indices {
1176            index_bytes.extend_from_slice(&index.to_le_bytes());
1177        }
1178        data.splice(index_insert_pos..index_insert_pos, index_bytes);
1179    }
1180
1181    fn insert_material(data: &mut Vec<u8>, material: &[u8]) {
1182        let vertex_count_pos = 3 + 4 + 20 + 256;
1183        let vertex_count = u32::from_le_bytes(
1184            data[vertex_count_pos..vertex_count_pos + 4]
1185                .try_into()
1186                .unwrap(),
1187        ) as usize;
1188        let index_count_pos = vertex_count_pos + 4 + vertex_count * 38;
1189        let index_count = u32::from_le_bytes(
1190            data[index_count_pos..index_count_pos + 4]
1191                .try_into()
1192                .unwrap(),
1193        ) as usize;
1194        let material_count_pos = index_count_pos + 4 + index_count * 2;
1195        data[material_count_pos..material_count_pos + 4].copy_from_slice(&1u32.to_le_bytes());
1196        data.splice(
1197            material_count_pos + 4..material_count_pos + 4,
1198            material.iter().copied(),
1199        );
1200    }
1201
1202    fn sample_vertex(edge_byte: u8) -> Vec<u8> {
1203        let mut vertex = Vec::new();
1204        for value in [1.0f32, 2.0, 3.0] {
1205            vertex.extend_from_slice(&value.to_le_bytes());
1206        }
1207        for value in [0.0f32, 1.0, 0.0] {
1208            vertex.extend_from_slice(&value.to_le_bytes());
1209        }
1210        for value in [0.25f32, 0.75] {
1211            vertex.extend_from_slice(&value.to_le_bytes());
1212        }
1213        vertex.extend_from_slice(&0u16.to_le_bytes());
1214        vertex.extend_from_slice(&1u16.to_le_bytes());
1215        vertex.push(80);
1216        vertex.push(edge_byte);
1217        vertex
1218    }
1219
1220    fn sample_material(edge_byte: u8) -> Vec<u8> {
1221        let mut material = Vec::new();
1222        for value in [0.1f32, 0.2, 0.3, 0.4] {
1223            material.extend_from_slice(&value.to_le_bytes());
1224        }
1225        material.extend_from_slice(&5.0f32.to_le_bytes());
1226        for value in [0.5f32, 0.6, 0.7] {
1227            material.extend_from_slice(&value.to_le_bytes());
1228        }
1229        for value in [0.8f32, 0.9, 1.0] {
1230            material.extend_from_slice(&value.to_le_bytes());
1231        }
1232        material.push(3);
1233        material.push(edge_byte);
1234        material.extend_from_slice(&3u32.to_le_bytes());
1235        push_fixed(&mut material, b"tex.bmp", 20);
1236        material
1237    }
1238
1239    fn push_vec3(out: &mut Vec<u8>, values: [f32; 3]) {
1240        for value in values {
1241            out.extend_from_slice(&value.to_le_bytes());
1242        }
1243    }
1244
1245    fn json_keys(value: &serde_json::Value) -> Vec<String> {
1246        let mut keys = value
1247            .as_object()
1248            .unwrap()
1249            .keys()
1250            .map(ToOwned::to_owned)
1251            .collect::<Vec<_>>();
1252        keys.sort();
1253        keys
1254    }
1255
1256    #[test]
1257    fn pmd_model_json_top_level_schema_is_stable() {
1258        let parsed = parse_pmd_model(&minimal_pmd_with_ik()).unwrap();
1259        let keys = json_keys(&serde_json::to_value(&parsed).unwrap());
1260
1261        assert_eq!(
1262            keys,
1263            vec![
1264                "diagnostics",
1265                "displayFrames",
1266                "geometry",
1267                "joints",
1268                "materials",
1269                "metadata",
1270                "morphs",
1271                "rigidBodies",
1272                "skeleton",
1273                "toonTextureBytes",
1274                "toonTextures",
1275            ]
1276        );
1277    }
1278
1279    #[test]
1280    fn imports_pmd_runtime_skeleton_and_name_maps() {
1281        let imported = import_pmd_runtime(&minimal_pmd_with_ik()).unwrap();
1282
1283        assert_eq!(imported.model.bone_count(), 2);
1284        assert_eq!(imported.model.ik_count(), 1);
1285        assert_eq!(imported.model.morph_count(), 1);
1286        assert_eq!(imported.bone_names, vec!["root", "legIK"]);
1287        assert_eq!(
1288            imported.bone_name_to_index.get(b"legIK".as_slice()),
1289            Some(&BoneIndex(1))
1290        );
1291        assert_eq!(
1292            imported.morph_name_to_index.get(b"base".as_slice()),
1293            Some(&MorphIndex(0))
1294        );
1295        assert_eq!(
1296            imported
1297                .ik_solver_bone_name_to_index
1298                .get(b"legIK".as_slice()),
1299            Some(&0)
1300        );
1301        assert_eq!(imported.diagnostics[0].code, "PMD_RUNTIME_PARTIAL");
1302    }
1303
1304    #[test]
1305    fn exports_minimal_pmd_roundtrip() {
1306        let data = minimal_pmd_with_ik();
1307        let parsed = parse_pmd_model(&data).unwrap();
1308        let exported = export_pmd_model(&parsed);
1309        let reparsed = parse_pmd_model(&exported).unwrap();
1310
1311        assert_eq!(exported, data);
1312        assert_eq!(reparsed.metadata.name, parsed.metadata.name);
1313        assert_eq!(reparsed.skeleton.bones.len(), 2);
1314        assert_eq!(reparsed.skeleton.ik.len(), 1);
1315        assert_eq!(reparsed.morphs.len(), 1);
1316    }
1317
1318    #[test]
1319    fn exports_pmd_json_dto_roundtrip() {
1320        let data = minimal_pmd_with_ik();
1321        let parsed = parse_pmd_model(&data).unwrap();
1322        let json = serde_json::to_string(&parsed).unwrap();
1323        let from_json: PmdParsedModel = serde_json::from_str(&json).unwrap();
1324        let exported = export_pmd_model(&from_json);
1325        let reparsed = parse_pmd_model(&exported).unwrap();
1326
1327        assert_eq!(exported, data);
1328        assert_eq!(reparsed.metadata.name, parsed.metadata.name);
1329        assert_eq!(reparsed.skeleton.bones.len(), 2);
1330        assert_eq!(reparsed.skeleton.ik.len(), 1);
1331        assert_eq!(reparsed.morphs.len(), 1);
1332    }
1333
1334    #[test]
1335    fn exports_pmd_english_block_roundtrip() {
1336        let mut data = minimal_pmd_with_ik();
1337        data.push(0); // morph display count
1338        data.push(1); // bone display frame name count
1339        push_fixed(&mut data, b"frame", 50);
1340        data.extend_from_slice(&1u32.to_le_bytes()); // bone display count
1341        data.extend_from_slice(&1u16.to_le_bytes());
1342        data.push(1);
1343        data.push(1); // english block enabled
1344        push_fixed(&mut data, b"model-en", 20);
1345        push_fixed(&mut data, b"comment-en", 256);
1346        push_fixed(&mut data, b"root-en", 20);
1347        push_fixed(&mut data, b"legIK-en", 20);
1348        push_fixed(&mut data, b"frame-en", 50);
1349
1350        let parsed = parse_pmd_model(&data).unwrap();
1351        let exported = export_pmd_model(&parsed);
1352        let reparsed = parse_pmd_model(&exported).unwrap();
1353
1354        assert_eq!(exported, data);
1355        assert_eq!(reparsed.metadata.english_name, "model-en");
1356        assert_eq!(reparsed.skeleton.bones[1].english_name, "legIK-en");
1357        assert_eq!(reparsed.display_frames[0].english_name, "frame-en");
1358        assert_eq!(reparsed.display_frames[0].frames[0].kind, "bone");
1359        assert_eq!(reparsed.display_frames[0].frames[0].index, 1);
1360    }
1361
1362    #[test]
1363    fn exports_pmd_toon_texture_block_roundtrip() {
1364        let mut data = minimal_pmd_with_ik();
1365        data.push(0); // morph display count
1366        data.push(0); // bone display frame name count
1367        data.extend_from_slice(&0u32.to_le_bytes()); // bone display count
1368        data.push(0); // english block disabled
1369        for i in 0..10 {
1370            push_fixed(&mut data, format!("toon{i}.bmp").as_bytes(), 100);
1371        }
1372
1373        let parsed = parse_pmd_model(&data).unwrap();
1374        let exported = export_pmd_model(&parsed);
1375        let reparsed = parse_pmd_model(&exported).unwrap();
1376
1377        assert_eq!(exported, data);
1378        assert_eq!(reparsed.toon_textures.len(), 10);
1379        assert_eq!(reparsed.toon_textures[9], "toon9.bmp");
1380    }
1381
1382    #[test]
1383    fn exports_pmd_physics_tail_roundtrip() {
1384        let mut data = minimal_pmd_with_ik();
1385        data.push(0); // morph display count
1386        data.push(0); // bone display frame name count
1387        data.extend_from_slice(&0u32.to_le_bytes()); // bone display count
1388        data.push(0); // english block disabled
1389
1390        data.extend_from_slice(&1u32.to_le_bytes()); // rigid bodies
1391        push_fixed(&mut data, b"body", 20);
1392        data.extend_from_slice(&1u16.to_le_bytes());
1393        data.push(2);
1394        data.extend_from_slice(&3u16.to_le_bytes());
1395        data.push(1);
1396        push_vec3(&mut data, [1.0, 2.0, 3.0]);
1397        push_vec3(&mut data, [4.0, 5.0, 6.0]);
1398        push_vec3(&mut data, [0.1, 0.2, 0.3]);
1399        for value in [10.0f32, 0.4, 0.5, 0.6, 0.7] {
1400            data.extend_from_slice(&value.to_le_bytes());
1401        }
1402        data.push(2);
1403
1404        data.extend_from_slice(&1u32.to_le_bytes()); // joints
1405        push_fixed(&mut data, b"joint", 20);
1406        data.extend_from_slice(&0u32.to_le_bytes());
1407        data.extend_from_slice(&1u32.to_le_bytes());
1408        for values in [
1409            [1.0, 1.1, 1.2],
1410            [2.0, 2.1, 2.2],
1411            [-1.0, -1.1, -1.2],
1412            [1.0, 1.1, 1.2],
1413            [-0.1, -0.2, -0.3],
1414            [0.1, 0.2, 0.3],
1415            [3.0, 3.1, 3.2],
1416            [4.0, 4.1, 4.2],
1417        ] {
1418            push_vec3(&mut data, values);
1419        }
1420
1421        let parsed = parse_pmd_model(&data).unwrap();
1422        let exported = export_pmd_model(&parsed);
1423        let reparsed = parse_pmd_model(&exported).unwrap();
1424
1425        assert_eq!(exported, data);
1426        assert_eq!(reparsed.rigid_bodies[0].mode, "dynamicBone");
1427        assert_eq!(reparsed.joints[0].spring_rotation, [4.0, 4.1, 4.2]);
1428    }
1429
1430    #[test]
1431    fn exports_pmd_edge_enabled_byte_polarity() {
1432        let mut data = minimal_pmd_with_ik();
1433        insert_vertex_and_indices(&mut data, &sample_vertex(0), &[0, 0, 0]);
1434        insert_material(&mut data, &sample_material(1));
1435
1436        let parsed = parse_pmd_model(&data).unwrap();
1437        let exported = export_pmd_model(&parsed);
1438        let reparsed = parse_pmd_model(&exported).unwrap();
1439
1440        assert_eq!(exported, data);
1441        assert!(reparsed.geometry.vertices[0].edge_enabled);
1442        assert!(reparsed.materials[0].edge_enabled);
1443    }
1444
1445    #[test]
1446    fn pmd_fixed_text_fallback_does_not_split_shift_jis_character() {
1447        let mut out = Vec::new();
1448
1449        write_fixed_text(&mut out, "abcd\u{3042}", &[], 5);
1450
1451        assert_eq!(out, vec![b'a', b'b', b'c', b'd', 0]);
1452        assert_eq!(decode_sjis_fixed(&out), "abcd");
1453    }
1454
1455    #[test]
1456    fn parses_pmd_morph_vertex_offsets() {
1457        let mut data = minimal_pmd_with_ik();
1458        let morph_tail_len = 2 + 20 + 4 + 1;
1459        data.truncate(data.len() - morph_tail_len);
1460        data.extend_from_slice(&1u16.to_le_bytes());
1461        push_fixed(&mut data, b"smile", 20);
1462        data.extend_from_slice(&1u32.to_le_bytes());
1463        data.push(1);
1464        data.extend_from_slice(&7u32.to_le_bytes());
1465        data.extend_from_slice(&1.0f32.to_le_bytes());
1466        data.extend_from_slice(&2.0f32.to_le_bytes());
1467        data.extend_from_slice(&3.0f32.to_le_bytes());
1468
1469        let parsed = parse_pmd_model(&data).unwrap();
1470
1471        assert_eq!(parsed.morphs.len(), 1);
1472        assert_eq!(parsed.morphs[0].name, "smile");
1473        assert_eq!(parsed.morphs[0].vertex_count, 1);
1474        assert_eq!(parsed.morphs[0].vertex_offsets[0].vertex_index, 7);
1475        assert_eq!(parsed.morphs[0].vertex_offsets[0].position, [1.0, 2.0, 3.0]);
1476    }
1477
1478    #[test]
1479    fn parses_pmd_geometry_vertices_and_indices() {
1480        let mut data = minimal_pmd_with_ik();
1481        let mut vertex = Vec::new();
1482        for value in [1.0f32, 2.0, 3.0] {
1483            vertex.extend_from_slice(&value.to_le_bytes());
1484        }
1485        for value in [0.0f32, 1.0, 0.0] {
1486            vertex.extend_from_slice(&value.to_le_bytes());
1487        }
1488        for value in [0.25f32, 0.75] {
1489            vertex.extend_from_slice(&value.to_le_bytes());
1490        }
1491        vertex.extend_from_slice(&0u16.to_le_bytes());
1492        vertex.extend_from_slice(&1u16.to_le_bytes());
1493        vertex.push(80);
1494        vertex.push(0);
1495        insert_vertex_and_indices(&mut data, &vertex, &[0, 0, 0]);
1496
1497        let parsed = parse_pmd_model(&data).unwrap();
1498
1499        assert_eq!(parsed.geometry.vertices.len(), 1);
1500        assert_eq!(parsed.geometry.vertices[0].position, [1.0, 2.0, 3.0]);
1501        assert_eq!(parsed.geometry.vertices[0].normal, [0.0, 1.0, 0.0]);
1502        assert_eq!(parsed.geometry.vertices[0].uv, [0.25, 0.75]);
1503        assert_eq!(parsed.geometry.vertices[0].bone_indices, [0, 1]);
1504        assert_eq!(parsed.geometry.vertices[0].bone_weight, 80);
1505        assert!(parsed.geometry.vertices[0].edge_enabled);
1506        assert_eq!(parsed.geometry.indices, vec![0, 0, 0]);
1507    }
1508
1509    #[test]
1510    fn parses_pmd_physics_tail_details() {
1511        let mut data = minimal_pmd_with_ik();
1512        data.push(0); // morph display count
1513        data.push(0); // bone display frame name count
1514        data.extend_from_slice(&0u32.to_le_bytes()); // bone display count
1515        data.push(0); // english block disabled
1516
1517        data.extend_from_slice(&1u32.to_le_bytes()); // rigid bodies
1518        push_fixed(&mut data, b"body", 20);
1519        data.extend_from_slice(&1u16.to_le_bytes());
1520        data.push(2);
1521        data.extend_from_slice(&3u16.to_le_bytes());
1522        data.push(1);
1523        push_vec3(&mut data, [1.0, 2.0, 3.0]);
1524        push_vec3(&mut data, [4.0, 5.0, 6.0]);
1525        push_vec3(&mut data, [0.1, 0.2, 0.3]);
1526        for value in [10.0f32, 0.4, 0.5, 0.6, 0.7] {
1527            data.extend_from_slice(&value.to_le_bytes());
1528        }
1529        data.push(2);
1530
1531        data.extend_from_slice(&1u32.to_le_bytes()); // joints
1532        push_fixed(&mut data, b"joint", 20);
1533        data.extend_from_slice(&0u32.to_le_bytes());
1534        data.extend_from_slice(&1u32.to_le_bytes());
1535        for values in [
1536            [1.0, 1.1, 1.2],
1537            [2.0, 2.1, 2.2],
1538            [-1.0, -1.1, -1.2],
1539            [1.0, 1.1, 1.2],
1540            [-0.1, -0.2, -0.3],
1541            [0.1, 0.2, 0.3],
1542            [3.0, 3.1, 3.2],
1543            [4.0, 4.1, 4.2],
1544        ] {
1545            push_vec3(&mut data, values);
1546        }
1547
1548        let parsed = parse_pmd_model(&data).unwrap();
1549
1550        assert_eq!(parsed.rigid_bodies.len(), 1);
1551        assert_eq!(parsed.rigid_bodies[0].shape, "box");
1552        assert_eq!(parsed.rigid_bodies[0].size, [1.0, 2.0, 3.0]);
1553        assert_eq!(parsed.rigid_bodies[0].position, [4.0, 5.0, 6.0]);
1554        assert_eq!(parsed.rigid_bodies[0].mode, "dynamicBone");
1555        assert_eq!(parsed.joints.len(), 1);
1556        assert_eq!(parsed.joints[0].position, [1.0, 1.1, 1.2]);
1557        assert_eq!(parsed.joints[0].spring_rotation, [4.0, 4.1, 4.2]);
1558    }
1559
1560    #[test]
1561    fn parses_pmd_toon_texture_block() {
1562        let mut data = minimal_pmd_with_ik();
1563        data.push(0); // morph display count
1564        data.push(0); // bone display frame name count
1565        data.extend_from_slice(&0u32.to_le_bytes()); // bone display count
1566        data.push(0); // english block disabled
1567        for i in 0..10 {
1568            push_fixed(&mut data, format!("toon{i}.bmp").as_bytes(), 100);
1569        }
1570
1571        let parsed = parse_pmd_model(&data).unwrap();
1572
1573        assert_eq!(parsed.toon_textures.len(), 10);
1574        assert_eq!(parsed.toon_textures[0], "toon0.bmp");
1575        assert_eq!(parsed.toon_textures[9], "toon9.bmp");
1576    }
1577
1578    #[test]
1579    fn preserves_pmd_fixed_text_raw_bytes() {
1580        let mut data = minimal_pmd_with_ik();
1581        data.push(0); // morph display count
1582        data.push(1); // bone display frame name count
1583        push_fixed(&mut data, b"frame", 50);
1584        data.extend_from_slice(&0u32.to_le_bytes()); // bone display count
1585        data.push(1); // english block enabled
1586        push_fixed(&mut data, b"model-en", 20);
1587        push_fixed(&mut data, b"comment-en", 256);
1588        push_fixed(&mut data, b"root-en", 20);
1589        push_fixed(&mut data, b"legIK-en", 20);
1590        push_fixed(&mut data, b"frame-en", 50);
1591        for i in 0..10 {
1592            push_fixed(&mut data, format!("toon{i}.bmp").as_bytes(), 100);
1593        }
1594
1595        let parsed = parse_pmd_model(&data).unwrap();
1596
1597        assert_eq!(parsed.metadata.name, "model");
1598        assert_eq!(parsed.metadata.name_bytes.len(), 20);
1599        assert_eq!(&parsed.metadata.name_bytes[..5], b"model");
1600        assert_eq!(parsed.metadata.english_name, "model-en");
1601        assert_eq!(parsed.metadata.english_name_bytes.len(), 20);
1602        assert_eq!(parsed.skeleton.bones[1].name, "legIK");
1603        assert_eq!(parsed.skeleton.bones[1].name_bytes.len(), 20);
1604        assert_eq!(parsed.skeleton.bones[1].english_name, "legIK-en");
1605        assert_eq!(parsed.display_frames[0].name, "frame");
1606        assert_eq!(parsed.display_frames[0].name_bytes.len(), 50);
1607        assert_eq!(parsed.display_frames[0].english_name, "frame-en");
1608        assert_eq!(parsed.toon_texture_bytes.len(), 10);
1609        assert_eq!(parsed.toon_texture_bytes[0].len(), 100);
1610    }
1611
1612    #[test]
1613    fn serializes_pmd_ik_fields_as_camel_case() {
1614        let ik = PmdParsedIk {
1615            bone_index: 1,
1616            target_bone_index: 2,
1617            loop_count: 3,
1618            limit_angle: 4.0,
1619            links: vec![5],
1620        };
1621
1622        let value = serde_json::to_value(ik).unwrap();
1623
1624        assert_eq!(value["boneIndex"], 1);
1625        assert_eq!(value["targetBoneIndex"], 2);
1626        assert_eq!(value["loopCount"], 3);
1627        assert_eq!(value["limitAngle"], 4.0);
1628        assert!(value.get("bone_index").is_none());
1629    }
1630
1631    #[test]
1632    fn imports_pmd_vertex_morph_offsets_through_base_morph_indices() {
1633        let mut data = minimal_pmd_with_ik();
1634        insert_empty_vertices(&mut data, 8);
1635        let morph_tail_len = 2 + 20 + 4 + 1;
1636        data.truncate(data.len() - morph_tail_len);
1637        data.extend_from_slice(&2u16.to_le_bytes());
1638
1639        push_fixed(&mut data, b"base", 20);
1640        data.extend_from_slice(&1u32.to_le_bytes());
1641        data.push(0);
1642        data.extend_from_slice(&7u32.to_le_bytes());
1643        data.extend_from_slice(&0.0f32.to_le_bytes());
1644        data.extend_from_slice(&0.0f32.to_le_bytes());
1645        data.extend_from_slice(&0.0f32.to_le_bytes());
1646
1647        push_fixed(&mut data, b"smile", 20);
1648        data.extend_from_slice(&1u32.to_le_bytes());
1649        data.push(1);
1650        data.extend_from_slice(&0u32.to_le_bytes());
1651        data.extend_from_slice(&1.0f32.to_le_bytes());
1652        data.extend_from_slice(&2.0f32.to_le_bytes());
1653        data.extend_from_slice(&3.0f32.to_le_bytes());
1654
1655        let imported = import_pmd_runtime(&data).unwrap();
1656
1657        assert_eq!(imported.model.morph_count(), 2);
1658        assert_eq!(
1659            imported.model.vertex_morph_spans()[1],
1660            MorphOffsetSpan { start: 0, count: 1 }
1661        );
1662        assert_eq!(
1663            imported.model.vertex_morph_offsets()[0],
1664            VertexMorphOffset {
1665                vertex_index: 7,
1666                position_offset: Vec3A::new(1.0, 2.0, 3.0),
1667            }
1668        );
1669        assert_eq!(
1670            imported.morph_name_to_index.get(b"smile".as_slice()),
1671            Some(&MorphIndex(1))
1672        );
1673        assert_eq!(imported.diagnostics[0].code, "PMD_RUNTIME_PARTIAL");
1674    }
1675
1676    #[test]
1677    fn skips_pmd_vertex_morph_offsets_outside_vertex_count() {
1678        let mut data = minimal_pmd_with_ik();
1679        insert_empty_vertices(&mut data, 1);
1680        let morph_tail_len = 2 + 20 + 4 + 1;
1681        data.truncate(data.len() - morph_tail_len);
1682        data.extend_from_slice(&2u16.to_le_bytes());
1683
1684        push_fixed(&mut data, b"base", 20);
1685        data.extend_from_slice(&1u32.to_le_bytes());
1686        data.push(0);
1687        data.extend_from_slice(&7u32.to_le_bytes());
1688        data.extend_from_slice(&0.0f32.to_le_bytes());
1689        data.extend_from_slice(&0.0f32.to_le_bytes());
1690        data.extend_from_slice(&0.0f32.to_le_bytes());
1691
1692        push_fixed(&mut data, b"smile", 20);
1693        data.extend_from_slice(&1u32.to_le_bytes());
1694        data.push(1);
1695        data.extend_from_slice(&0u32.to_le_bytes());
1696        data.extend_from_slice(&1.0f32.to_le_bytes());
1697        data.extend_from_slice(&2.0f32.to_le_bytes());
1698        data.extend_from_slice(&3.0f32.to_le_bytes());
1699
1700        let imported = import_pmd_runtime(&data).unwrap();
1701
1702        assert!(imported.model.vertex_morph_offsets().is_empty());
1703        assert_eq!(
1704            imported.diagnostics[0].code,
1705            "PMD_VERTEX_MORPH_VERTEX_INDEX_OUT_OF_RANGE"
1706        );
1707        assert_eq!(imported.diagnostics[1].code, "PMD_RUNTIME_PARTIAL");
1708    }
1709}