Skip to main content

mmd_anim_format/
pmd.rs

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