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()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); 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()); out.extend_from_slice(&0u16.to_le_bytes()); out.push(1); 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); data.push(1); push_fixed(&mut data, b"frame", 50);
1291 data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&1u16.to_le_bytes());
1293 data.push(1);
1294 data.push(1); 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); data.push(0); data.extend_from_slice(&0u32.to_le_bytes()); data.push(0); 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); 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);
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()); 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); 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);
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()); 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); data.push(0); data.extend_from_slice(&0u32.to_le_bytes()); data.push(0); 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); data.push(1); push_fixed(&mut data, b"frame", 50);
1535 data.extend_from_slice(&0u32.to_le_bytes()); data.push(1); 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}