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()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); 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()); out.extend_from_slice(&0u16.to_le_bytes()); out.push(1); 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); data.push(1); push_fixed(&mut data, b"frame", 50);
1340 data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&1u16.to_le_bytes());
1342 data.push(1);
1343 data.push(1); 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); data.push(0); data.extend_from_slice(&0u32.to_le_bytes()); data.push(0); 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); data.push(0); data.extend_from_slice(&0u32.to_le_bytes()); data.push(0); data.extend_from_slice(&1u32.to_le_bytes()); 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()); 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); data.push(0); data.extend_from_slice(&0u32.to_le_bytes()); data.push(0); data.extend_from_slice(&1u32.to_le_bytes()); 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()); 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); data.push(0); data.extend_from_slice(&0u32.to_le_bytes()); data.push(0); 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); data.push(1); push_fixed(&mut data, b"frame", 50);
1584 data.extend_from_slice(&0u32.to_le_bytes()); data.push(1); 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}