1use std::collections::HashMap;
2
3use glam::{Mat4, Quat, Vec3A};
4use serde::{Deserialize, Serialize};
5
6use mmd_anim_runtime::{
7 AppendTransformInit, BoneIndex, BoneInit, BoneMorphOffset, GroupMorphOffset, IkAngleLimit,
8 IkLinkInit, IkSolverInit, ModelArena, MorphIndex, MorphInit, MorphOffsetSpan,
9};
10
11use crate::error::ImportError;
12
13mod json_f32;
14
15const PMX_MAGIC: [u8; 4] = [0x50, 0x4D, 0x58, 0x20];
16
17const BONE_FLAG_TAIL_INDEX: u16 = 0x0001;
18const BONE_FLAG_IK: u16 = 0x0020;
19const BONE_FLAG_LOCAL_APPEND: u16 = 0x0080;
20const BONE_FLAG_APPEND_ROTATE: u16 = 0x0100;
21const BONE_FLAG_APPEND_TRANSLATE: u16 = 0x0200;
22const BONE_FLAG_FIXED_AXIS: u16 = 0x0400;
23const BONE_FLAG_LOCAL_AXIS: u16 = 0x0800;
24const BONE_FLAG_TRANSFORM_AFTER_PHYSICS: u16 = 0x1000;
25const BONE_FLAG_EXTERNAL_PARENT: u16 = 0x2000;
26const PMX_SKINNING_MODE_BDEF1: &str = "bdef1";
27const PMX_SKINNING_MODE_BDEF2: &str = "bdef2";
28const PMX_SKINNING_MODE_BDEF4: &str = "bdef4";
29const PMX_SKINNING_MODE_SDEF: &str = "sdef";
30const PMX_SKINNING_MODE_QDEF: &str = "qdef";
31
32fn decode_utf16le_lossy(bytes: &[u8]) -> String {
33 let end = bytes.len().saturating_sub(bytes.len() % 2);
34 let units = bytes[..end]
35 .chunks_exact(2)
36 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
37 .take_while(|&code| code != 0);
38 std::char::decode_utf16(units)
39 .map(|item| item.unwrap_or(char::REPLACEMENT_CHARACTER))
40 .collect()
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum TextEncoding {
45 Utf16Le = 0,
46 Utf8 = 1,
47}
48
49#[derive(Debug, Clone)]
50pub struct PmxHeader {
51 pub version: f32,
52 pub encoding: TextEncoding,
53 pub extra_uv_count: u8,
54 pub vertex_index_size: u8,
55 pub texture_index_size: u8,
56 pub material_index_size: u8,
57 pub bone_index_size: u8,
58 pub morph_index_size: u8,
59 pub rigidbody_index_size: u8,
60}
61
62struct Reader<'a> {
63 data: &'a [u8],
64 pos: usize,
65}
66
67impl<'a> Reader<'a> {
68 fn new(data: &'a [u8]) -> Self {
69 Self { data, pos: 0 }
70 }
71
72 fn remaining(&self) -> usize {
73 self.data.len().saturating_sub(self.pos)
74 }
75
76 fn require(&self, n: usize) -> Result<(), ImportError> {
77 if self.remaining() >= n {
78 Ok(())
79 } else {
80 Err(ImportError::UnexpectedEof(
81 n.saturating_sub(self.remaining()),
82 ))
83 }
84 }
85
86 fn require_record_bytes(&self, count: usize, record_size: usize) -> Result<(), ImportError> {
87 let bytes = count
88 .checked_mul(record_size)
89 .ok_or(ImportError::SectionOverflow)?;
90 self.require(bytes)
91 }
92
93 fn read_bytes(&mut self, n: usize) -> Result<&'a [u8], ImportError> {
94 self.require(n)?;
95 let slice = &self.data[self.pos..self.pos + n];
96 self.pos += n;
97 Ok(slice)
98 }
99
100 fn read_u8(&mut self) -> Result<u8, ImportError> {
101 Ok(self.read_bytes(1)?[0])
102 }
103
104 fn read_u16_le(&mut self) -> Result<u16, ImportError> {
105 let b = self.read_bytes(2)?;
106 Ok(u16::from_le_bytes([b[0], b[1]]))
107 }
108
109 fn read_i32_le(&mut self) -> Result<i32, ImportError> {
110 let b = self.read_bytes(4)?;
111 Ok(i32::from_le_bytes([b[0], b[1], b[2], b[3]]))
112 }
113
114 fn read_f32_le(&mut self) -> Result<f32, ImportError> {
115 let b = self.read_bytes(4)?;
116 Ok(f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
117 }
118
119 fn read_vec3(&mut self) -> Result<Vec3A, ImportError> {
120 Ok(Vec3A::new(
121 self.read_f32_le()?,
122 self.read_f32_le()?,
123 self.read_f32_le()?,
124 ))
125 }
126
127 fn read_vec2_array(&mut self) -> Result<[f32; 2], ImportError> {
128 Ok([self.read_f32_le()?, self.read_f32_le()?])
129 }
130
131 fn read_vec3_array(&mut self) -> Result<[f32; 3], ImportError> {
132 Ok([
133 self.read_f32_le()?,
134 self.read_f32_le()?,
135 self.read_f32_le()?,
136 ])
137 }
138
139 fn read_vec4_array(&mut self) -> Result<[f32; 4], ImportError> {
140 Ok([
141 self.read_f32_le()?,
142 self.read_f32_le()?,
143 self.read_f32_le()?,
144 self.read_f32_le()?,
145 ])
146 }
147
148 fn read_sized_index(&mut self, size: u8) -> Result<i32, ImportError> {
149 match size {
150 1 => Ok(self.read_u8()? as i8 as i32),
151 2 => Ok({
152 let b = self.read_bytes(2)?;
153 i16::from_le_bytes([b[0], b[1]]) as i32
154 }),
155 4 => self.read_i32_le(),
156 _ => Err(ImportError::InvalidIndexSize(size)),
157 }
158 }
159
160 fn read_index(&mut self, header: &PmxHeader) -> Result<i32, ImportError> {
161 self.read_sized_index(header.bone_index_size)
162 }
163
164 fn read_vertex_index(&mut self, size: u8) -> Result<u32, ImportError> {
165 match size {
166 1 => Ok(self.read_u8()? as u32),
167 2 => Ok(self.read_u16_le()? as u32),
168 4 => {
169 let raw = self.read_i32_le()?;
170 if raw < 0 {
171 Err(ImportError::SectionOverflow)
172 } else {
173 Ok(raw as u32)
174 }
175 }
176 _ => Err(ImportError::InvalidIndexSize(size)),
177 }
178 }
179
180 fn skip(&mut self, n: usize) -> Result<(), ImportError> {
181 self.require(n)?;
182 self.pos += n;
183 Ok(())
184 }
185
186 fn read_string(&mut self, encoding: TextEncoding) -> Result<String, ImportError> {
187 let len = self.read_i32_le()?;
188 if len <= 0 {
189 return Ok(String::new());
190 }
191 let bytes = self.read_bytes(len as usize)?;
192 match encoding {
193 TextEncoding::Utf8 => {
194 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
195 Ok(String::from_utf8_lossy(&bytes[..end]).into_owned())
196 }
197 TextEncoding::Utf16Le => Ok(decode_utf16le_lossy(bytes)),
198 }
199 }
200
201 fn read_string_owned(
202 &mut self,
203 encoding: TextEncoding,
204 ) -> Result<(Vec<u8>, String), ImportError> {
205 let len = self.read_i32_le()?;
206 if len <= 0 {
207 return Ok((Vec::new(), String::new()));
208 }
209 let bytes = self.read_bytes(len as usize)?.to_vec();
210 let decoded = match encoding {
211 TextEncoding::Utf8 => {
212 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
213 String::from_utf8_lossy(&bytes[..end]).into_owned()
214 }
215 TextEncoding::Utf16Le => decode_utf16le_lossy(&bytes),
216 };
217 Ok((bytes, decoded))
218 }
219
220 fn read_bone_index(&mut self, header: &PmxHeader) -> Result<Option<BoneIndex>, ImportError> {
221 let raw = self.read_sized_index(header.bone_index_size)?;
222 if raw < 0 {
223 Ok(None)
224 } else {
225 Ok(Some(BoneIndex(raw as u32)))
226 }
227 }
228}
229
230pub fn read_header(data: &[u8]) -> Result<(PmxHeader, usize), ImportError> {
231 let mut r = Reader::new(data);
232
233 let magic = r.read_bytes(4)?;
234 if magic != PMX_MAGIC {
235 return Err(ImportError::InvalidPmxMagic);
236 }
237
238 let version = r.read_f32_le()?;
239 if !(2.0..=2.2).contains(&version) {
240 return Err(ImportError::UnsupportedPmxVersion(version));
241 }
242
243 let data_count = r.read_u8()?;
244 if data_count < 8 {
245 return Err(ImportError::InvalidEncoding(data_count));
246 }
247
248 let encoding_byte = r.read_u8()?;
249 let encoding = match encoding_byte {
250 0 => TextEncoding::Utf16Le,
251 1 => TextEncoding::Utf8,
252 _ => return Err(ImportError::InvalidEncoding(encoding_byte)),
253 };
254
255 let extra_uv_count = r.read_u8()?;
256 let vertex_index_size = r.read_u8()?;
257 let texture_index_size = r.read_u8()?;
258 let material_index_size = r.read_u8()?;
259 let bone_index_size = r.read_u8()?;
260 let morph_index_size = r.read_u8()?;
261 let rigidbody_index_size = r.read_u8()?;
262 for size in [
263 vertex_index_size,
264 texture_index_size,
265 material_index_size,
266 bone_index_size,
267 morph_index_size,
268 rigidbody_index_size,
269 ] {
270 if !matches!(size, 1 | 2 | 4) {
271 return Err(ImportError::InvalidIndexSize(size));
272 }
273 }
274 if data_count > 8 {
275 r.skip((data_count - 8) as usize)?;
276 }
277
278 let header = PmxHeader {
279 version,
280 encoding,
281 extra_uv_count,
282 vertex_index_size,
283 texture_index_size,
284 material_index_size,
285 bone_index_size,
286 morph_index_size,
287 rigidbody_index_size,
288 };
289
290 Ok((header, r.pos))
291}
292
293pub fn skip_model_info(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
294 let mut r = Reader { data, pos };
295 let _model_name = r.read_string(header.encoding)?;
296 let _english_name = r.read_string(header.encoding)?;
297 let _comment = r.read_string(header.encoding)?;
298 let _english_comment = r.read_string(header.encoding)?;
299 Ok(r.pos)
300}
301
302pub fn skip_vertices(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
303 let mut r = Reader { data, pos };
304 let count = r.read_i32_le()?;
305 if count < 0 {
306 return Err(ImportError::SectionOverflow);
307 }
308 let count = count as usize;
309
310 for _ in 0..count {
311 r.skip(12)?;
312 r.skip(12)?;
313 r.skip(8)?;
314 r.skip(header.extra_uv_count as usize * 16)?;
315 let vertex_deform_type = r.read_u8()?;
316
317 let bone_indices: usize = match vertex_deform_type {
318 0 => 1,
319 1 => 2,
320 2 | 4 => 4,
321 3 => 2,
322 _ => return Err(ImportError::SectionOverflow),
323 };
324 for _ in 0..bone_indices {
325 r.read_sized_index(header.bone_index_size)?;
326 }
327
328 let weight_count: usize = match vertex_deform_type {
329 0 => 0,
330 1 => 1,
331 2 | 4 => 4,
332 3 => 1,
333 _ => return Err(ImportError::SectionOverflow),
334 };
335 r.skip(weight_count * 4)?;
336
337 if vertex_deform_type == 3 {
338 r.skip(36)?;
339 }
340
341 r.skip(4)?;
342 }
343
344 Ok(r.pos)
345}
346
347pub fn skip_faces(data: &[u8], vertex_index_size: u8, pos: usize) -> Result<usize, ImportError> {
348 let mut r = Reader { data, pos };
349 let count = r.read_i32_le()?;
350 if count < 0 {
351 return Err(ImportError::SectionOverflow);
352 }
353 let count = count as usize;
354 let bytes = count
355 .checked_mul(vertex_index_size as usize)
356 .ok_or(ImportError::SectionOverflow)?;
357 r.skip(bytes)?;
358 Ok(r.pos)
359}
360
361pub fn skip_textures(
362 data: &[u8],
363 encoding: TextEncoding,
364 pos: usize,
365) -> Result<usize, ImportError> {
366 let mut r = Reader { data, pos };
367 let count = r.read_i32_le()?;
368 if count < 0 {
369 return Err(ImportError::SectionOverflow);
370 }
371 for _ in 0..count {
372 let _path = r.read_string(encoding)?;
373 }
374 Ok(r.pos)
375}
376
377pub fn skip_materials(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
378 let mut r = Reader { data, pos };
379 let count = r.read_i32_le()?;
380 if count < 0 {
381 return Err(ImportError::SectionOverflow);
382 }
383 for _ in 0..count {
384 let _name = r.read_string(header.encoding)?;
385 let _english_name = r.read_string(header.encoding)?;
386 r.skip(16)?;
387 r.skip(16)?;
388 r.skip(12)?;
389 r.skip(1)?;
390 r.skip(16)?;
391 r.skip(4)?;
392 let _texture = r.read_sized_index(header.texture_index_size)?;
393 let _sphere_tex = r.read_sized_index(header.texture_index_size)?;
394 r.skip(1)?;
395 let _toon = r.read_u8()?;
396 if _toon == 0 {
397 r.read_sized_index(header.texture_index_size)?;
398 } else {
399 r.skip(1)?;
400 }
401 let _memo = r.read_string(header.encoding)?;
402 let _face_count = r.read_i32_le()?;
403 }
404 Ok(r.pos)
405}
406
407pub fn skip_morphs(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
408 let mut r = Reader { data, pos };
409 let count = r.read_i32_le()?;
410 if count < 0 {
411 return Err(ImportError::SectionOverflow);
412 }
413 for _ in 0..count {
414 let _name = r.read_string(header.encoding)?;
415 let _english_name = r.read_string(header.encoding)?;
416 r.skip(1)?;
417 let _morph_type = r.read_u8()?;
418 let _offset_count = r.read_i32_le()?;
419 match _morph_type {
420 0 => {
421 for _ in 0.._offset_count {
422 r.read_sized_index(header.morph_index_size)?;
423 r.skip(4)?;
424 }
425 }
426 1 => {
427 for _ in 0.._offset_count {
428 r.read_sized_index(header.vertex_index_size)?;
429 r.skip(12)?;
430 }
431 }
432 2 => {
433 for _ in 0.._offset_count {
434 r.read_sized_index(header.bone_index_size)?;
435 r.skip(12)?;
436 r.skip(16)?;
437 }
438 }
439 3..=7 => {
440 for _ in 0.._offset_count {
441 r.read_sized_index(header.vertex_index_size)?;
442 r.skip(16)?;
443 }
444 }
445 8 => {
446 for _ in 0.._offset_count {
447 r.read_sized_index(header.material_index_size)?;
448 r.skip(1)?;
449 r.skip(16)?;
450 r.skip(12)?;
451 r.skip(4)?;
452 r.skip(12)?;
453 r.skip(16)?;
454 r.skip(4)?;
455 r.skip(16)?;
456 r.skip(16)?;
457 r.skip(16)?;
458 }
459 }
460 9 => {
461 for _ in 0.._offset_count {
462 r.read_sized_index(header.rigidbody_index_size)?;
463 r.skip(1)?;
464 r.skip(12)?;
465 r.skip(12)?;
466 }
467 }
468 _ => return Err(ImportError::SectionOverflow),
469 }
470 }
471 Ok(r.pos)
472}
473
474#[derive(Debug, Clone)]
475pub struct PmxMorphNames {
476 pub name_bytes: Vec<Vec<u8>>,
477 pub names: Vec<String>,
478}
479
480pub fn read_morph_names(
481 data: &[u8],
482 header: &PmxHeader,
483 pos: usize,
484) -> Result<(PmxMorphNames, usize), ImportError> {
485 let mut r = Reader { data, pos };
486 let count = r.read_i32_le()?;
487 if count < 0 {
488 return Err(ImportError::SectionOverflow);
489 }
490 let count = count as usize;
491 r.require_record_bytes(count, 1)?;
492
493 let mut name_bytes = Vec::with_capacity(count);
494 let mut names = Vec::with_capacity(count);
495
496 for _ in 0..count {
497 let (raw, name) = r.read_string_owned(header.encoding)?;
498 let _english_name = r.read_string(header.encoding)?;
499 name_bytes.push(raw);
500 names.push(name);
501
502 r.skip(1)?;
503 let morph_type = r.read_u8()?;
504 let offset_count = r.read_i32_le()?;
505 if offset_count < 0 {
506 return Err(ImportError::SectionOverflow);
507 }
508 let offset_count = offset_count as usize;
509
510 let per_offset = match morph_type {
511 0 => header.morph_index_size as usize + 4,
512 1 => header.vertex_index_size as usize + 12,
513 2 => header.bone_index_size as usize + 12 + 16,
514 3..=7 => header.vertex_index_size as usize + 16,
515 8 => header.material_index_size as usize + 1 + 16 + 12 + 4 + 12 + 16 + 4 + 16 + 16 + 16,
516 9 => header.rigidbody_index_size as usize + 1 + 12 + 12,
517 _ => return Err(ImportError::SectionOverflow),
518 };
519 r.skip(
520 offset_count
521 .checked_mul(per_offset)
522 .ok_or(ImportError::SectionOverflow)?,
523 )?;
524 }
525
526 Ok((PmxMorphNames { name_bytes, names }, r.pos))
527}
528
529pub fn read_morph_offsets(
535 data: &[u8],
536 header: &PmxHeader,
537 pos: usize,
538) -> Result<(PmxMorphNames, MorphInit, usize), ImportError> {
539 let mut r = Reader { data, pos };
540 let count = r.read_i32_le()?;
541 if count < 0 {
542 return Err(ImportError::SectionOverflow);
543 }
544 let count = count as usize;
545 r.require_record_bytes(count, 1)?;
546
547 let mut name_bytes = Vec::with_capacity(count);
548 let mut names = Vec::with_capacity(count);
549
550 let mut bone_offsets_all: Vec<BoneMorphOffset> = Vec::new();
551 let mut bone_spans: Vec<MorphOffsetSpan> = Vec::with_capacity(count);
552 let mut group_offsets_all: Vec<GroupMorphOffset> = Vec::new();
553 let mut group_spans: Vec<MorphOffsetSpan> = Vec::with_capacity(count);
554
555 for _ in 0..count {
556 let (raw, name) = r.read_string_owned(header.encoding)?;
557 let _english_name = r.read_string(header.encoding)?;
558 name_bytes.push(raw);
559 names.push(name);
560
561 r.skip(1)?;
562 let morph_type = r.read_u8()?;
563 let offset_count = r.read_i32_le()?;
564 if offset_count < 0 {
565 return Err(ImportError::SectionOverflow);
566 }
567 let offset_count = offset_count as usize;
568
569 match morph_type {
570 0 => {
571 let start = group_offsets_all.len() as u32;
572 for _ in 0..offset_count {
573 let child_raw = r.read_sized_index(header.morph_index_size)?;
574 if child_raw < 0 {
575 return Err(ImportError::SectionOverflow);
576 }
577 let ratio = r.read_f32_le()?;
578 group_offsets_all.push(GroupMorphOffset {
579 child_morph: MorphIndex(child_raw as u32),
580 ratio,
581 });
582 }
583 group_spans.push(MorphOffsetSpan {
584 start,
585 count: offset_count as u32,
586 });
587 bone_spans.push(MorphOffsetSpan::default());
588 }
589 2 => {
590 let start = bone_offsets_all.len() as u32;
591 for _ in 0..offset_count {
592 let bone_raw = r.read_sized_index(header.bone_index_size)?;
593 if bone_raw < 0 {
594 return Err(ImportError::SectionOverflow);
595 }
596 let pos = r.read_vec3()?;
597 let qx = r.read_f32_le()?;
598 let qy = r.read_f32_le()?;
599 let qz = r.read_f32_le()?;
600 let qw = r.read_f32_le()?;
601 bone_offsets_all.push(BoneMorphOffset {
602 target_bone: BoneIndex(bone_raw as u32),
603 position_offset: pos,
604 rotation_offset: Quat::from_xyzw(qx, qy, qz, qw),
605 });
606 }
607 bone_spans.push(MorphOffsetSpan {
608 start,
609 count: offset_count as u32,
610 });
611 group_spans.push(MorphOffsetSpan::default());
612 }
613 _ => {
614 let per_offset = match morph_type {
615 1 => header.vertex_index_size as usize + 12,
616 3..=7 => header.vertex_index_size as usize + 16,
617 8 => {
618 header.material_index_size as usize
619 + 1
620 + 16
621 + 12
622 + 4
623 + 12
624 + 16
625 + 4
626 + 16
627 + 16
628 + 16
629 }
630 9 => header.rigidbody_index_size as usize + 1 + 12 + 12,
631 _ => return Err(ImportError::SectionOverflow),
632 };
633 r.skip(
634 offset_count
635 .checked_mul(per_offset)
636 .ok_or(ImportError::SectionOverflow)?,
637 )?;
638 bone_spans.push(MorphOffsetSpan::default());
639 group_spans.push(MorphOffsetSpan::default());
640 }
641 }
642 }
643
644 Ok((
645 PmxMorphNames { name_bytes, names },
646 MorphInit {
647 morph_count: count as u32,
648 vertex_spans: vec![MorphOffsetSpan::default(); count],
649 bone_offsets: bone_offsets_all,
650 bone_spans,
651 group_offsets: group_offsets_all,
652 group_spans,
653 ..MorphInit::default()
654 },
655 r.pos,
656 ))
657}
658
659#[derive(Debug, Clone)]
660pub struct PmxBoneImport {
661 pub bones: Vec<BoneInit>,
662 pub ik_solvers: Vec<IkSolverInit>,
663 pub append_transforms: Vec<AppendTransformInit>,
664 pub bone_name_bytes: Vec<Vec<u8>>,
665 pub bone_names: Vec<String>,
666}
667
668pub fn read_bones(
669 data: &[u8],
670 header: &PmxHeader,
671 pos: usize,
672) -> Result<(PmxBoneImport, usize), ImportError> {
673 let mut r = Reader { data, pos };
674 let count = r.read_i32_le()?;
675 if count < 0 {
676 return Err(ImportError::SectionOverflow);
677 }
678 let count = count as usize;
679 r.require_record_bytes(count, 1)?;
680
681 let mut bones = Vec::with_capacity(count);
682 let mut ik_solvers = Vec::new();
683 let mut append_transforms = Vec::new();
684
685 let mut bone_name_bytes = Vec::with_capacity(count);
686 let mut bone_names = Vec::with_capacity(count);
687
688 let mut absolute_positions: Vec<Vec3A> = Vec::with_capacity(count);
689
690 for _bone_i in 0..count {
691 let (name_bytes, name) = r.read_string_owned(header.encoding)?;
692 let _english_name = r.read_string(header.encoding)?;
693 let position = r.read_vec3()?;
694 absolute_positions.push(position);
695 let parent = r.read_bone_index(header)?;
696 let transform_order = r.read_i32_le()?;
697 let flags = r.read_u16_le()?;
698
699 if flags & BONE_FLAG_TAIL_INDEX != 0 {
700 r.read_index(header)?;
701 } else {
702 r.read_vec3()?;
703 }
704
705 let has_append =
706 flags & BONE_FLAG_APPEND_ROTATE != 0 || flags & BONE_FLAG_APPEND_TRANSLATE != 0;
707
708 if has_append {
709 let append_parent = r.read_bone_index(header)?;
710 let append_ratio = r.read_f32_le()?;
711
712 if let Some(source) = append_parent {
713 let target_idx = bones.len();
714 append_transforms.push(
715 AppendTransformInit::new(BoneIndex(target_idx as u32), source, append_ratio)
716 .with_rotation_if(flags & BONE_FLAG_APPEND_ROTATE != 0)
717 .with_translation_if(flags & BONE_FLAG_APPEND_TRANSLATE != 0)
718 .with_local_if(flags & BONE_FLAG_LOCAL_APPEND != 0),
719 );
720 }
721 }
722
723 let fixed_axis = if flags & BONE_FLAG_FIXED_AXIS != 0 {
724 Some(r.read_vec3()?)
725 } else {
726 None
727 };
728
729 if flags & BONE_FLAG_LOCAL_AXIS != 0 {
730 r.read_vec3()?;
731 r.read_vec3()?;
732 }
733
734 if flags & BONE_FLAG_EXTERNAL_PARENT != 0 {
735 r.read_i32_le()?;
736 }
737
738 if flags & BONE_FLAG_IK != 0 {
739 let ik_target = r.read_bone_index(header)?;
740 let ik_loop_count = r.read_i32_le()?;
741 let ik_limit_angle = r.read_f32_le()?;
742 let link_count = r.read_i32_le()?;
743 if link_count < 0 {
744 return Err(ImportError::SectionOverflow);
745 }
746 r.require_record_bytes(link_count as usize, header.bone_index_size as usize + 1)?;
747
748 let mut links = Vec::with_capacity(link_count as usize);
749 for _ in 0..link_count {
750 let link_bone = r.read_bone_index(header)?.unwrap_or(BoneIndex(0));
751 let has_limit = r.read_u8()?;
752 let angle_limit = if has_limit != 0 {
753 let min = r.read_vec3()?;
754 let max = r.read_vec3()?;
755 Some(IkAngleLimit::new(min, max))
756 } else {
757 None
758 };
759 links.push(match angle_limit {
760 Some(lim) => IkLinkInit::new(link_bone).with_angle_limit(lim),
761 None => IkLinkInit::new(link_bone),
762 });
763 }
764
765 let ik_bone = BoneIndex(bones.len() as u32);
766 if let Some(target) = ik_target {
767 ik_solvers.push(IkSolverInit {
768 ik_bone,
769 target_bone: target,
770 links,
771 iteration_count: ik_loop_count as u32,
772 limit_angle: ik_limit_angle,
773 });
774 }
775 }
776
777 bone_name_bytes.push(name_bytes);
778 bone_names.push(name);
779
780 bones.push(BoneInit {
781 parent,
782 rest_position: position,
783 inverse_bind_matrix: Mat4::from_translation((-position).into()),
784 transform_order,
785 transform_after_physics: flags & BONE_FLAG_TRANSFORM_AFTER_PHYSICS != 0,
786 fixed_axis,
787 enforce_fixed_axis: false,
790 });
791 }
792
793 for bone in bones.iter_mut() {
794 if let Some(parent_idx) = bone.parent
795 && let Some(parent_abs) = absolute_positions.get(parent_idx.as_usize())
796 {
797 bone.rest_position -= parent_abs;
798 }
799 }
800
801 Ok((
802 PmxBoneImport {
803 bones,
804 ik_solvers,
805 append_transforms,
806 bone_name_bytes,
807 bone_names,
808 },
809 r.pos,
810 ))
811}
812
813pub fn import_pmx_model(data: &[u8]) -> Result<PmxBoneImport, ImportError> {
814 let (header, pos) = read_header(data)?;
815 let pos = skip_model_info(data, &header, pos)?;
816 let pos = skip_vertices(data, &header, pos)?;
817 let pos = skip_faces(data, header.vertex_index_size, pos)?;
818 let pos = skip_textures(data, header.encoding, pos)?;
819 let pos = skip_materials(data, &header, pos)?;
820 let (bones, _pos) = read_bones(data, &header, pos)?;
821 Ok(bones)
822}
823
824#[derive(Debug, Clone, Serialize, Deserialize)]
825#[serde(rename_all = "camelCase")]
826pub struct PmxParsedModel {
827 pub metadata: PmxParsedMetadata,
828 pub geometry: PmxParsedGeometry,
829 pub materials: Vec<PmxParsedMaterial>,
830 pub skeleton: PmxParsedSkeleton,
831 pub morphs: Vec<PmxParsedMorph>,
832 pub display_frames: Vec<PmxParsedDisplayFrame>,
833 pub rigid_bodies: Vec<PmxParsedRigidBody>,
834 pub joints: Vec<PmxParsedJoint>,
835 pub soft_bodies: Vec<PmxParsedSoftBody>,
836 pub diagnostics: Vec<PmxParserDiagnostic>,
837}
838
839#[derive(Debug, Clone, Serialize, Deserialize)]
840#[serde(rename_all = "camelCase")]
841pub struct PmxParsedMetadata {
842 #[serde(default = "default_pmx_format")]
843 pub format: String,
844 #[serde(with = "json_f32")]
845 pub version: f32,
846 pub encoding: String,
847 pub name: String,
848 pub english_name: String,
849 pub comment: String,
850 pub english_comment: String,
851 pub counts: PmxParsedCounts,
852 pub index_sizes: PmxParsedIndexSizes,
853 pub additional_uv_count: u8,
854}
855
856#[derive(Debug, Clone, Serialize, Deserialize)]
857#[serde(rename_all = "camelCase")]
858pub struct PmxParsedCounts {
859 pub vertices: usize,
860 pub faces: usize,
861 pub materials: usize,
862 pub bones: usize,
863 pub morphs: usize,
864 pub display_frames: usize,
865 pub rigid_bodies: usize,
866 pub joints: usize,
867 pub soft_bodies: usize,
868}
869
870#[derive(Debug, Clone, Serialize, Deserialize)]
871pub struct PmxParsedIndexSizes {
872 pub vertex: u8,
873 pub texture: u8,
874 pub material: u8,
875 pub bone: u8,
876 pub morph: u8,
877 #[serde(rename = "rigidBody")]
878 pub rigid_body: u8,
879}
880
881#[derive(Debug, Clone, Serialize, Deserialize, Default)]
882#[serde(rename_all = "camelCase")]
883pub struct PmxParsedSdef {
884 #[serde(default)]
885 pub skinning_modes: Vec<String>,
886 #[serde(with = "json_f32::vec_f32")]
887 pub enabled: Vec<f32>,
888 #[serde(with = "json_f32::vec_f32")]
889 pub c: Vec<f32>,
890 #[serde(with = "json_f32::vec_f32")]
891 pub r0: Vec<f32>,
892 #[serde(with = "json_f32::vec_f32")]
893 pub r1: Vec<f32>,
894 #[serde(with = "json_f32::vec_f32")]
895 pub rw0: Vec<f32>,
896 #[serde(with = "json_f32::vec_f32")]
897 pub rw1: Vec<f32>,
898}
899
900#[derive(Debug, Clone, Serialize, Deserialize, Default)]
901#[serde(rename_all = "camelCase")]
902pub struct PmxParsedQdef {
903 #[serde(with = "json_f32::vec_f32")]
904 pub enabled: Vec<f32>,
905}
906
907#[derive(Debug, Clone, Serialize, Deserialize)]
908#[serde(rename_all = "camelCase")]
909pub struct PmxParsedGeometry {
910 #[serde(with = "json_f32::vec_f32")]
911 pub positions: Vec<f32>,
912 #[serde(with = "json_f32::vec_f32")]
913 pub normals: Vec<f32>,
914 #[serde(with = "json_f32::vec_f32")]
915 pub uvs: Vec<f32>,
916 #[serde(with = "json_f32::nested_vec")]
917 pub additional_uvs: Vec<Vec<f32>>,
918 pub indices: Vec<u32>,
919 pub skin_indices: Vec<u32>,
920 #[serde(with = "json_f32::vec_f32")]
921 pub skin_weights: Vec<f32>,
922 #[serde(with = "json_f32::vec_f32")]
923 pub edge_scale: Vec<f32>,
924 pub material_groups: Vec<PmxParsedMaterialGroup>,
925 pub sdef: PmxParsedSdef,
926 pub qdef: PmxParsedQdef,
927}
928
929#[derive(Debug, Clone, Serialize, Deserialize)]
930#[serde(rename_all = "camelCase")]
931pub struct PmxParsedMaterialGroup {
932 pub start: usize,
933 pub count: usize,
934 pub material_index: usize,
935}
936
937#[derive(Debug, Clone, Serialize, Deserialize)]
938#[serde(rename_all = "camelCase")]
939pub struct PmxParsedMaterial {
940 pub name: String,
941 pub english_name: String,
942 pub texture_path: String,
943 pub sphere_texture_path: String,
944 pub sphere_mode: String,
945 pub toon_texture_path: String,
946 pub shared_toon_index: Option<u8>,
947 #[serde(with = "json_f32::array4")]
948 pub diffuse: [f32; 4],
949 #[serde(with = "json_f32::array3")]
950 pub specular: [f32; 3],
951 #[serde(with = "json_f32")]
952 pub specular_power: f32,
953 #[serde(with = "json_f32::array3")]
954 pub ambient: [f32; 3],
955 #[serde(with = "json_f32::array4")]
956 pub edge_color: [f32; 4],
957 #[serde(with = "json_f32")]
958 pub edge_size: f32,
959 pub flags: PmxParsedMaterialFlags,
960 pub face_count: i32,
961}
962
963#[derive(Debug, Clone, Serialize, Deserialize)]
964#[serde(rename_all = "camelCase")]
965pub struct PmxParsedMaterialFlags {
966 pub double_sided: bool,
967 pub ground_shadow: bool,
968 pub self_shadow_map: bool,
969 pub self_shadow: bool,
970 pub edge: bool,
971 pub vertex_color: bool,
972 pub point_draw: bool,
973 pub line_draw: bool,
974}
975
976#[derive(Debug, Clone, Serialize, Deserialize)]
977pub struct PmxParsedSkeleton {
978 pub bones: Vec<PmxParsedBone>,
979}
980
981#[derive(Debug, Clone, Serialize, Deserialize)]
982#[serde(rename_all = "camelCase")]
983pub struct PmxParsedBone {
984 pub name: String,
985 pub english_name: String,
986 pub parent_index: i32,
987 pub layer: i32,
988 #[serde(with = "json_f32::array3")]
989 pub position: [f32; 3],
990 pub tail_index: i32,
991 #[serde(with = "json_f32::option_array3")]
992 pub tail_position: Option<[f32; 3]>,
993 pub flags: PmxParsedBoneFlags,
994 pub append_transform: Option<PmxParsedAppendTransform>,
995 #[serde(with = "json_f32::option_array3")]
996 pub fixed_axis: Option<[f32; 3]>,
997 pub local_axis: Option<PmxParsedLocalAxis>,
998 pub external_parent_key: Option<i32>,
999 pub ik: Option<PmxParsedIk>,
1000}
1001
1002#[derive(Debug, Clone, Serialize, Deserialize)]
1003#[serde(rename_all = "camelCase")]
1004pub struct PmxParsedBoneFlags {
1005 pub indexed_tail: bool,
1006 pub rotatable: bool,
1007 pub translatable: bool,
1008 pub visible: bool,
1009 pub enabled: bool,
1010 pub ik: bool,
1011 pub append_local: bool,
1012 pub append_rotate: bool,
1013 pub append_translate: bool,
1014 pub fixed_axis: bool,
1015 pub local_axis: bool,
1016 pub transform_after_physics: bool,
1017 pub external_parent_transform: bool,
1018}
1019
1020#[derive(Debug, Clone, Serialize, Deserialize)]
1021#[serde(rename_all = "camelCase")]
1022pub struct PmxParsedAppendTransform {
1023 pub parent_index: i32,
1024 #[serde(with = "json_f32")]
1025 pub weight: f32,
1026}
1027
1028#[derive(Debug, Clone, Serialize, Deserialize)]
1029pub struct PmxParsedLocalAxis {
1030 #[serde(with = "json_f32::array3")]
1031 pub x: [f32; 3],
1032 #[serde(with = "json_f32::array3")]
1033 pub z: [f32; 3],
1034}
1035
1036#[derive(Debug, Clone, Serialize, Deserialize)]
1037#[serde(rename_all = "camelCase")]
1038pub struct PmxParsedIk {
1039 pub target_index: i32,
1040 pub loop_count: i32,
1041 #[serde(with = "json_f32")]
1042 pub limit_angle: f32,
1043 pub links: Vec<PmxParsedIkLink>,
1044}
1045
1046#[derive(Debug, Clone, Serialize, Deserialize)]
1047#[serde(rename_all = "camelCase")]
1048pub struct PmxParsedIkLink {
1049 pub bone_index: i32,
1050 pub limits: Option<PmxParsedIkLimit>,
1051}
1052
1053#[derive(Debug, Clone, Serialize, Deserialize)]
1054pub struct PmxParsedIkLimit {
1055 #[serde(with = "json_f32::array3")]
1056 pub lower: [f32; 3],
1057 #[serde(with = "json_f32::array3")]
1058 pub upper: [f32; 3],
1059}
1060
1061#[derive(Debug, Clone, Serialize, Deserialize)]
1062#[serde(rename_all = "camelCase")]
1063pub struct PmxRigSpec {
1064 pub bone_count: usize,
1065 pub ik_chain_count: usize,
1066 pub grant_count: usize,
1067 pub bones: Vec<PmxRigSpecBone>,
1068 pub ik_chains: Vec<PmxRigSpecIkChain>,
1069 pub grants: Vec<PmxRigSpecGrant>,
1070}
1071
1072#[derive(Debug, Clone, Serialize, Deserialize)]
1073#[serde(rename_all = "camelCase")]
1074pub struct PmxRigSpecBone {
1075 pub name: String,
1076 pub name_bytes: String,
1077 pub parent_index: i32,
1078 #[serde(with = "json_f32::array3")]
1079 pub rest_position: [f32; 3],
1080 pub deform_layer: i32,
1081 #[serde(with = "json_f32::option_array3")]
1082 pub fixed_axis: Option<[f32; 3]>,
1083 pub local_axis: Option<PmxRigSpecLocalAxis>,
1084 pub transform_after_physics: bool,
1085}
1086
1087#[derive(Debug, Clone, Serialize, Deserialize)]
1088pub struct PmxRigSpecLocalAxis {
1089 #[serde(with = "json_f32::array3")]
1090 pub x: [f32; 3],
1091 #[serde(with = "json_f32::array3")]
1092 pub z: [f32; 3],
1093}
1094
1095#[derive(Debug, Clone, Serialize, Deserialize)]
1096#[serde(rename_all = "camelCase")]
1097pub struct PmxRigSpecIkChain {
1098 pub controller_bone_index: i32,
1099 pub target_bone_index: i32,
1100 pub iteration_count: u32,
1101 #[serde(with = "json_f32")]
1102 pub limit_angle: f32,
1103 pub links: Vec<PmxRigSpecIkLink>,
1104}
1105
1106#[derive(Debug, Clone, Serialize, Deserialize)]
1107#[serde(rename_all = "camelCase")]
1108pub struct PmxRigSpecIkLink {
1109 pub bone_index: i32,
1110 pub has_angle_limit: bool,
1111 #[serde(with = "json_f32::array3")]
1112 pub angle_limit_min: [f32; 3],
1113 #[serde(with = "json_f32::array3")]
1114 pub angle_limit_max: [f32; 3],
1115}
1116
1117#[derive(Debug, Clone, Serialize, Deserialize)]
1118#[serde(rename_all = "camelCase")]
1119pub struct PmxRigSpecGrant {
1120 pub target_bone_index: i32,
1121 pub source_bone_index: i32,
1122 #[serde(with = "json_f32")]
1123 pub ratio: f32,
1124 pub affect_rotation: bool,
1125 pub affect_translation: bool,
1126 pub local: bool,
1127}
1128
1129pub fn parse_pmx_rig_spec(data: &[u8]) -> Result<PmxRigSpec, ImportError> {
1130 let model = parse_pmx_model(data)?;
1131 Ok(pmx_model_to_rig_spec(&model))
1132}
1133
1134pub fn pmx_model_to_rig_spec(model: &PmxParsedModel) -> PmxRigSpec {
1135 let mut bones = Vec::with_capacity(model.skeleton.bones.len());
1136 let mut ik_chains = Vec::new();
1137 let mut grants = Vec::new();
1138
1139 for (index, bone) in model.skeleton.bones.iter().enumerate() {
1140 bones.push(PmxRigSpecBone {
1141 name: bone.name.clone(),
1142 name_bytes: shift_jis_hex(&bone.name),
1143 parent_index: bone.parent_index,
1144 rest_position: bone.position,
1145 deform_layer: bone.layer,
1146 fixed_axis: bone.fixed_axis,
1147 local_axis: bone.local_axis.as_ref().map(|axis| PmxRigSpecLocalAxis {
1148 x: axis.x,
1149 z: axis.z,
1150 }),
1151 transform_after_physics: bone.flags.transform_after_physics,
1152 });
1153
1154 if let Some(ik) = &bone.ik {
1155 ik_chains.push(PmxRigSpecIkChain {
1156 controller_bone_index: index as i32,
1157 target_bone_index: ik.target_index,
1158 iteration_count: ik.loop_count.max(0) as u32,
1159 limit_angle: ik.limit_angle,
1160 links: ik
1161 .links
1162 .iter()
1163 .map(|link| {
1164 let (angle_limit_min, angle_limit_max) = link
1165 .limits
1166 .as_ref()
1167 .map_or(([0.0; 3], [0.0; 3]), |limit| (limit.lower, limit.upper));
1168 PmxRigSpecIkLink {
1169 bone_index: link.bone_index,
1170 has_angle_limit: link.limits.is_some(),
1171 angle_limit_min,
1172 angle_limit_max,
1173 }
1174 })
1175 .collect(),
1176 });
1177 }
1178
1179 if let Some(append) = &bone.append_transform {
1180 grants.push(PmxRigSpecGrant {
1181 target_bone_index: index as i32,
1182 source_bone_index: append.parent_index,
1183 ratio: append.weight,
1184 affect_rotation: bone.flags.append_rotate,
1185 affect_translation: bone.flags.append_translate,
1186 local: bone.flags.append_local,
1187 });
1188 }
1189 }
1190
1191 PmxRigSpec {
1192 bone_count: bones.len(),
1193 ik_chain_count: ik_chains.len(),
1194 grant_count: grants.len(),
1195 bones,
1196 ik_chains,
1197 grants,
1198 }
1199}
1200
1201fn shift_jis_hex(text: &str) -> String {
1202 let (encoded, _, _) = encoding_rs::SHIFT_JIS.encode(text);
1203 bytes_to_hex(&encoded)
1204}
1205
1206fn bytes_to_hex(bytes: &[u8]) -> String {
1207 const HEX: &[u8; 16] = b"0123456789abcdef";
1208 let mut out = String::with_capacity(bytes.len() * 2);
1209 for &byte in bytes {
1210 out.push(HEX[(byte >> 4) as usize] as char);
1211 out.push(HEX[(byte & 0x0f) as usize] as char);
1212 }
1213 out
1214}
1215
1216#[derive(Debug, Clone, Serialize, Deserialize)]
1217#[serde(rename_all = "camelCase")]
1218pub struct PmxParsedMorph {
1219 pub name: String,
1220 pub english_name: String,
1221 #[serde(rename = "type")]
1222 pub kind: String,
1223 pub vertex_offsets: Vec<PmxParsedVertexMorphOffset>,
1224 pub group_offsets: Vec<PmxParsedGroupMorphOffset>,
1225 pub bone_offsets: Vec<PmxParsedBoneMorphOffset>,
1226 pub uv_offsets: Vec<PmxParsedUvMorphOffset>,
1227 pub additional_uv_offsets: Vec<PmxParsedAdditionalUvMorphOffset>,
1228 pub material_offsets: Vec<PmxParsedMaterialMorphOffset>,
1229 pub flip_offsets: Vec<PmxParsedGroupMorphOffset>,
1230 pub impulse_offsets: Vec<PmxParsedImpulseMorphOffset>,
1231}
1232
1233#[derive(Debug, Clone, Serialize, Deserialize)]
1234#[serde(rename_all = "camelCase")]
1235pub struct PmxParsedVertexMorphOffset {
1236 pub vertex_index: u32,
1237 #[serde(with = "json_f32::array3")]
1238 pub position: [f32; 3],
1239}
1240
1241#[derive(Debug, Clone, Serialize, Deserialize)]
1242#[serde(rename_all = "camelCase")]
1243pub struct PmxParsedGroupMorphOffset {
1244 pub morph_index: i32,
1245 #[serde(with = "json_f32")]
1246 pub weight: f32,
1247}
1248
1249#[derive(Debug, Clone, Serialize, Deserialize)]
1250#[serde(rename_all = "camelCase")]
1251pub struct PmxParsedBoneMorphOffset {
1252 pub bone_index: i32,
1253 #[serde(with = "json_f32::array3")]
1254 pub translation: [f32; 3],
1255 #[serde(with = "json_f32::array4")]
1256 pub rotation: [f32; 4],
1257}
1258
1259#[derive(Debug, Clone, Serialize, Deserialize)]
1260#[serde(rename_all = "camelCase")]
1261pub struct PmxParsedUvMorphOffset {
1262 pub vertex_index: u32,
1263 #[serde(with = "json_f32::array4")]
1264 pub uv: [f32; 4],
1265}
1266
1267#[derive(Debug, Clone, Serialize, Deserialize)]
1268#[serde(rename_all = "camelCase")]
1269pub struct PmxParsedAdditionalUvMorphOffset {
1270 pub vertex_index: u32,
1271 pub uv_index: u8,
1272 #[serde(with = "json_f32::array4")]
1273 pub uv: [f32; 4],
1274}
1275
1276#[derive(Debug, Clone, Serialize, Deserialize)]
1277#[serde(rename_all = "camelCase")]
1278pub struct PmxParsedMaterialMorphOffset {
1279 pub material_index: i32,
1280 pub operation: String,
1281 #[serde(with = "json_f32::array4")]
1282 pub diffuse: [f32; 4],
1283 #[serde(with = "json_f32::array3")]
1284 pub specular: [f32; 3],
1285 #[serde(with = "json_f32")]
1286 pub specular_power: f32,
1287 #[serde(with = "json_f32::array3")]
1288 pub ambient: [f32; 3],
1289 #[serde(with = "json_f32::array4")]
1290 pub edge_color: [f32; 4],
1291 #[serde(with = "json_f32")]
1292 pub edge_size: f32,
1293 #[serde(with = "json_f32::array4")]
1294 pub texture_factor: [f32; 4],
1295 #[serde(with = "json_f32::array4")]
1296 pub sphere_texture_factor: [f32; 4],
1297 #[serde(with = "json_f32::array4")]
1298 pub toon_texture_factor: [f32; 4],
1299}
1300
1301#[derive(Debug, Clone, Serialize, Deserialize)]
1302#[serde(rename_all = "camelCase")]
1303pub struct PmxParsedImpulseMorphOffset {
1304 pub rigid_body_index: i32,
1305 pub local: bool,
1306 #[serde(with = "json_f32::array3")]
1307 pub velocity: [f32; 3],
1308 #[serde(with = "json_f32::array3")]
1309 pub torque: [f32; 3],
1310}
1311
1312#[derive(Debug, Clone, Serialize, Deserialize)]
1313#[serde(rename_all = "camelCase")]
1314pub struct PmxMaterialSplitResult {
1315 pub meshes: Vec<PmxMaterialSplitMesh>,
1316 pub manifest: PmxMaterialSplitManifest,
1317}
1318
1319#[derive(Debug, Clone, Serialize, Deserialize)]
1320#[serde(rename_all = "camelCase")]
1321pub struct PmxMaterialSplitMesh {
1322 pub original_material_index: usize,
1323 pub original_vertex_indices: Vec<u32>,
1324 pub geometry: PmxParsedGeometry,
1325 pub material: PmxParsedMaterial,
1326 pub morphs: Vec<PmxParsedMorph>,
1327 pub morph_index_map: Vec<PmxMaterialSplitMorphIndexMap>,
1328 pub diagnostics: Vec<PmxParserDiagnostic>,
1329}
1330
1331#[derive(Debug, Clone, Serialize, Deserialize)]
1332#[serde(rename_all = "camelCase")]
1333pub struct PmxMaterialSplit {
1334 pub mesh: PmxMaterialSplitMesh,
1335}
1336
1337#[derive(Debug, Clone, Serialize, Deserialize)]
1338#[serde(rename_all = "camelCase")]
1339pub struct PmxMaterialSplitManifest {
1340 pub mesh_count: usize,
1341 pub meshes: Vec<PmxMaterialSplitManifestMesh>,
1342 pub diagnostics: Vec<PmxParserDiagnostic>,
1343}
1344
1345#[derive(Debug, Clone, Serialize, Deserialize)]
1346#[serde(rename_all = "camelCase")]
1347pub struct PmxMaterialSplitManifestMesh {
1348 pub mesh_index: usize,
1349 pub original_material_index: usize,
1350 pub original_vertex_indices: Vec<u32>,
1351 pub morph_index_map: Vec<PmxMaterialSplitMorphIndexMap>,
1352 pub diagnostics: Vec<PmxParserDiagnostic>,
1353}
1354
1355#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1356#[serde(rename_all = "camelCase")]
1357pub struct PmxMaterialSplitMorphIndexMap {
1358 pub original_index: usize,
1359 pub local_index: usize,
1360}
1361
1362#[derive(Debug, Clone, Serialize, Deserialize)]
1363#[serde(rename_all = "camelCase")]
1364pub struct PmxParsedDisplayFrame {
1365 pub name: String,
1366 pub english_name: String,
1367 pub special: bool,
1368 pub frames: Vec<PmxParsedDisplayFrameElement>,
1369}
1370
1371#[derive(Debug, Clone, Serialize, Deserialize)]
1372pub struct PmxParsedDisplayFrameElement {
1373 #[serde(rename = "type")]
1374 pub kind: String,
1375 pub index: i32,
1376}
1377
1378#[derive(Debug, Clone, Serialize, Deserialize)]
1379#[serde(rename_all = "camelCase")]
1380pub struct PmxParsedRigidBody {
1381 pub name: String,
1382 pub english_name: String,
1383 pub bone_index: i32,
1384 pub group: u8,
1385 pub mask: u16,
1386 pub shape: String,
1387 #[serde(with = "json_f32::array3")]
1388 pub size: [f32; 3],
1389 #[serde(with = "json_f32::array3")]
1390 pub position: [f32; 3],
1391 #[serde(with = "json_f32::array3")]
1392 pub rotation: [f32; 3],
1393 #[serde(with = "json_f32")]
1394 pub mass: f32,
1395 #[serde(with = "json_f32")]
1396 pub linear_damping: f32,
1397 #[serde(with = "json_f32")]
1398 pub angular_damping: f32,
1399 #[serde(with = "json_f32")]
1400 pub restitution: f32,
1401 #[serde(with = "json_f32")]
1402 pub friction: f32,
1403 pub mode: String,
1404}
1405
1406#[derive(Debug, Clone, Serialize, Deserialize)]
1407#[serde(rename_all = "camelCase")]
1408pub struct PmxParsedJoint {
1409 pub name: String,
1410 pub english_name: String,
1411 #[serde(rename = "type")]
1412 pub kind: String,
1413 pub rigid_body_index_a: i32,
1414 pub rigid_body_index_b: i32,
1415 #[serde(with = "json_f32::array3")]
1416 pub position: [f32; 3],
1417 #[serde(with = "json_f32::array3")]
1418 pub rotation: [f32; 3],
1419 #[serde(with = "json_f32::array3")]
1420 pub translation_lower_limit: [f32; 3],
1421 #[serde(with = "json_f32::array3")]
1422 pub translation_upper_limit: [f32; 3],
1423 #[serde(with = "json_f32::array3")]
1424 pub rotation_lower_limit: [f32; 3],
1425 #[serde(with = "json_f32::array3")]
1426 pub rotation_upper_limit: [f32; 3],
1427 #[serde(with = "json_f32::array3")]
1428 pub spring_translation_factor: [f32; 3],
1429 #[serde(with = "json_f32::array3")]
1430 pub spring_rotation_factor: [f32; 3],
1431}
1432
1433#[derive(Debug, Clone, Serialize, Deserialize)]
1434#[serde(rename_all = "camelCase")]
1435pub struct PmxParsedSoftBody {
1436 pub name: String,
1437 pub english_name: String,
1438 #[serde(rename = "type")]
1439 pub kind: String,
1440 pub material_index: i32,
1441 pub collision_group: u8,
1442 pub collision_mask: u16,
1443 pub flags: u8,
1444 pub bending_constraints_distance: i32,
1445 pub cluster_count: i32,
1446 #[serde(with = "json_f32")]
1447 pub total_mass: f32,
1448 #[serde(with = "json_f32")]
1449 pub collision_margin: f32,
1450}
1451
1452#[derive(Debug, Clone, Serialize, Deserialize)]
1453pub struct PmxParserDiagnostic {
1454 pub level: String,
1455 pub code: String,
1456 pub message: String,
1457}
1458
1459#[derive(Debug, Clone, Serialize, Deserialize)]
1460#[serde(rename_all = "camelCase")]
1461pub struct PmxPartsDescriptor {
1462 #[serde(default = "default_pmx_parts_version")]
1463 #[serde(with = "json_f32")]
1464 pub version: f32,
1465 #[serde(default = "default_pmx_parts_encoding")]
1466 pub encoding: String,
1467 #[serde(default = "default_pmx_parts_name")]
1468 pub name: String,
1469 #[serde(default)]
1470 pub english_name: String,
1471 #[serde(default)]
1472 pub comment: String,
1473 #[serde(default)]
1474 pub english_comment: String,
1475 #[serde(default)]
1476 pub material_name: String,
1477 #[serde(default)]
1478 pub english_material_name: String,
1479 #[serde(default)]
1480 pub materials: Vec<PmxPartsMaterialDescriptor>,
1481 #[serde(default)]
1482 pub bones: Vec<PmxPartsBoneDescriptor>,
1483 #[serde(default)]
1484 pub morphs: Vec<PmxPartsMorphDescriptor>,
1485 #[serde(default)]
1486 pub display_frames: Vec<PmxPartsDisplayFrameDescriptor>,
1487 #[serde(default)]
1488 pub rigid_bodies: Vec<PmxPartsRigidBodyDescriptor>,
1489 #[serde(default)]
1490 pub joints: Vec<PmxPartsJointDescriptor>,
1491 #[serde(default)]
1492 pub index_sizes: PmxPartsIndexSizes,
1493}
1494
1495#[derive(Debug, Clone, Serialize, Deserialize)]
1496#[serde(rename_all = "camelCase")]
1497pub struct PmxPartsMaterialDescriptor {
1498 #[serde(default = "default_pmx_parts_material_name")]
1499 pub name: String,
1500 #[serde(default)]
1501 pub english_name: String,
1502 #[serde(default)]
1503 pub texture_path: String,
1504 #[serde(default)]
1505 pub sphere_texture_path: String,
1506 #[serde(default)]
1507 pub sphere_mode: String,
1508 #[serde(default)]
1509 pub toon_texture_path: String,
1510 #[serde(default = "default_pmx_parts_shared_toon_index")]
1511 pub shared_toon_index: Option<u8>,
1512 #[serde(default = "default_pmx_parts_diffuse")]
1513 #[serde(with = "json_f32::array4")]
1514 pub diffuse: [f32; 4],
1515 #[serde(default)]
1516 #[serde(with = "json_f32::array3")]
1517 pub specular: [f32; 3],
1518 #[serde(default = "default_pmx_parts_specular_power")]
1519 #[serde(with = "json_f32")]
1520 pub specular_power: f32,
1521 #[serde(default = "default_pmx_parts_ambient")]
1522 #[serde(with = "json_f32::array3")]
1523 pub ambient: [f32; 3],
1524 #[serde(default = "default_pmx_parts_edge_color")]
1525 #[serde(with = "json_f32::array4")]
1526 pub edge_color: [f32; 4],
1527 #[serde(default = "default_pmx_parts_edge_size")]
1528 #[serde(with = "json_f32")]
1529 pub edge_size: f32,
1530 #[serde(default)]
1531 pub flags: PmxPartsMaterialFlags,
1532 #[serde(default)]
1533 pub face_count: i32,
1534}
1535
1536#[derive(Debug, Clone, Serialize, Deserialize)]
1537#[serde(rename_all = "camelCase")]
1538pub struct PmxPartsMaterialFlags {
1539 #[serde(default = "default_true")]
1540 pub double_sided: bool,
1541 #[serde(default = "default_true")]
1542 pub ground_shadow: bool,
1543 #[serde(default = "default_true")]
1544 pub self_shadow_map: bool,
1545 #[serde(default = "default_true")]
1546 pub self_shadow: bool,
1547 #[serde(default)]
1548 pub edge: bool,
1549 #[serde(default)]
1550 pub vertex_color: bool,
1551 #[serde(default)]
1552 pub point_draw: bool,
1553 #[serde(default)]
1554 pub line_draw: bool,
1555}
1556
1557impl Default for PmxPartsMaterialFlags {
1558 fn default() -> Self {
1559 Self {
1560 double_sided: true,
1561 ground_shadow: true,
1562 self_shadow_map: true,
1563 self_shadow: true,
1564 edge: false,
1565 vertex_color: false,
1566 point_draw: false,
1567 line_draw: false,
1568 }
1569 }
1570}
1571
1572#[derive(Debug, Clone, Serialize, Deserialize)]
1573#[serde(rename_all = "camelCase")]
1574pub struct PmxPartsBoneDescriptor {
1575 #[serde(default = "default_pmx_parts_root_bone_name")]
1576 pub name: String,
1577 #[serde(default)]
1578 pub english_name: String,
1579 #[serde(default = "default_negative_index")]
1580 pub parent_index: i32,
1581 #[serde(default)]
1582 pub layer: i32,
1583 #[serde(default)]
1584 #[serde(with = "json_f32::array3")]
1585 pub position: [f32; 3],
1586 #[serde(default = "default_negative_index")]
1587 pub tail_index: i32,
1588 #[serde(default)]
1589 #[serde(with = "json_f32::option_array3")]
1590 pub tail_position: Option<[f32; 3]>,
1591 #[serde(default)]
1592 pub rotatable: bool,
1593 #[serde(default)]
1594 pub translatable: bool,
1595 #[serde(default = "default_true")]
1596 pub visible: bool,
1597 #[serde(default = "default_true")]
1598 pub enabled: bool,
1599}
1600
1601#[derive(Debug, Clone, Serialize, Deserialize)]
1602#[serde(rename_all = "camelCase")]
1603pub struct PmxPartsMorphDescriptor {
1604 #[serde(default)]
1605 pub name: String,
1606 #[serde(default)]
1607 pub english_name: String,
1608 #[serde(default = "default_pmx_parts_morph_kind", alias = "type")]
1609 pub kind: String,
1610 #[serde(default)]
1611 pub vertex_offsets: Vec<PmxPartsVertexMorphOffset>,
1612 #[serde(default)]
1613 pub group_offsets: Vec<PmxPartsGroupMorphOffset>,
1614}
1615
1616#[derive(Debug, Clone, Serialize, Deserialize)]
1617#[serde(rename_all = "camelCase")]
1618pub struct PmxPartsVertexMorphOffset {
1619 pub vertex_index: u32,
1620 #[serde(with = "json_f32::array3")]
1621 pub position: [f32; 3],
1622}
1623
1624#[derive(Debug, Clone, Serialize, Deserialize)]
1625#[serde(rename_all = "camelCase")]
1626pub struct PmxPartsGroupMorphOffset {
1627 pub morph_index: i32,
1628 #[serde(with = "json_f32")]
1629 pub weight: f32,
1630}
1631
1632#[derive(Debug, Clone, Serialize, Deserialize)]
1633#[serde(rename_all = "camelCase")]
1634pub struct PmxPartsDisplayFrameDescriptor {
1635 #[serde(default)]
1636 pub name: String,
1637 #[serde(default)]
1638 pub english_name: String,
1639 #[serde(default)]
1640 pub special: bool,
1641 #[serde(default)]
1642 pub frames: Vec<PmxPartsDisplayFrameItem>,
1643}
1644
1645#[derive(Debug, Clone, Serialize, Deserialize)]
1646#[serde(rename_all = "camelCase")]
1647pub struct PmxPartsDisplayFrameItem {
1648 #[serde(default = "default_pmx_parts_display_frame_kind")]
1649 pub kind: String,
1650 #[serde(default)]
1651 pub index: i32,
1652}
1653
1654#[derive(Debug, Clone, Serialize, Deserialize)]
1655#[serde(rename_all = "camelCase")]
1656pub struct PmxPartsRigidBodyDescriptor {
1657 #[serde(default)]
1658 pub name: String,
1659 #[serde(default)]
1660 pub english_name: String,
1661 #[serde(default = "default_negative_index")]
1662 pub bone_index: i32,
1663 #[serde(default)]
1664 pub group: u8,
1665 #[serde(default)]
1666 pub mask: u16,
1667 #[serde(default = "default_pmx_parts_rigid_body_shape")]
1668 pub shape: String,
1669 #[serde(default = "default_unit_vec3")]
1670 #[serde(with = "json_f32::array3")]
1671 pub size: [f32; 3],
1672 #[serde(default)]
1673 #[serde(with = "json_f32::array3")]
1674 pub position: [f32; 3],
1675 #[serde(default)]
1676 #[serde(with = "json_f32::array3")]
1677 pub rotation: [f32; 3],
1678 #[serde(default = "default_pmx_parts_mass")]
1679 #[serde(with = "json_f32")]
1680 pub mass: f32,
1681 #[serde(default)]
1682 #[serde(with = "json_f32")]
1683 pub linear_damping: f32,
1684 #[serde(default)]
1685 #[serde(with = "json_f32")]
1686 pub angular_damping: f32,
1687 #[serde(default)]
1688 #[serde(with = "json_f32")]
1689 pub restitution: f32,
1690 #[serde(default)]
1691 #[serde(with = "json_f32")]
1692 pub friction: f32,
1693 #[serde(default = "default_pmx_parts_rigid_body_mode")]
1694 pub mode: String,
1695}
1696
1697#[derive(Debug, Clone, Serialize, Deserialize)]
1698#[serde(rename_all = "camelCase")]
1699pub struct PmxPartsJointDescriptor {
1700 #[serde(default)]
1701 pub name: String,
1702 #[serde(default)]
1703 pub english_name: String,
1704 #[serde(
1705 default = "default_pmx_parts_joint_kind",
1706 rename = "type",
1707 alias = "kind"
1708 )]
1709 pub kind: String,
1710 #[serde(default = "default_negative_index")]
1711 pub rigid_body_index_a: i32,
1712 #[serde(default = "default_negative_index")]
1713 pub rigid_body_index_b: i32,
1714 #[serde(default)]
1715 #[serde(with = "json_f32::array3")]
1716 pub position: [f32; 3],
1717 #[serde(default)]
1718 #[serde(with = "json_f32::array3")]
1719 pub rotation: [f32; 3],
1720 #[serde(default)]
1721 #[serde(with = "json_f32::array3")]
1722 pub translation_lower_limit: [f32; 3],
1723 #[serde(default)]
1724 #[serde(with = "json_f32::array3")]
1725 pub translation_upper_limit: [f32; 3],
1726 #[serde(default)]
1727 #[serde(with = "json_f32::array3")]
1728 pub rotation_lower_limit: [f32; 3],
1729 #[serde(default)]
1730 #[serde(with = "json_f32::array3")]
1731 pub rotation_upper_limit: [f32; 3],
1732 #[serde(default)]
1733 #[serde(with = "json_f32::array3")]
1734 pub spring_translation_factor: [f32; 3],
1735 #[serde(default)]
1736 #[serde(with = "json_f32::array3")]
1737 pub spring_rotation_factor: [f32; 3],
1738}
1739
1740#[derive(Debug, Clone, Serialize, Deserialize)]
1741#[serde(rename_all = "camelCase")]
1742pub struct PmxPartsIndexSizes {
1743 #[serde(default = "default_pmx_vertex_index_size")]
1744 pub vertex: u8,
1745 #[serde(default = "default_pmx_small_index_size")]
1746 pub texture: u8,
1747 #[serde(default = "default_pmx_small_index_size")]
1748 pub material: u8,
1749 #[serde(default = "default_pmx_small_index_size")]
1750 pub bone: u8,
1751 #[serde(default = "default_pmx_small_index_size")]
1752 pub morph: u8,
1753 #[serde(default = "default_pmx_small_index_size")]
1754 pub rigid_body: u8,
1755}
1756
1757impl Default for PmxPartsIndexSizes {
1758 fn default() -> Self {
1759 Self {
1760 vertex: default_pmx_vertex_index_size(),
1761 texture: default_pmx_small_index_size(),
1762 material: default_pmx_small_index_size(),
1763 bone: default_pmx_small_index_size(),
1764 morph: default_pmx_small_index_size(),
1765 rigid_body: default_pmx_small_index_size(),
1766 }
1767 }
1768}
1769
1770pub struct PmxPartsInput<'a> {
1771 pub descriptor: PmxPartsDescriptor,
1772 pub positions_xyz: &'a [f32],
1773 pub normals_xyz: &'a [f32],
1774 pub uvs_xy: &'a [f32],
1775 pub indices: &'a [u32],
1776 pub skin_indices: &'a [u32],
1777 pub skin_weights: &'a [f32],
1778 pub edge_scale: &'a [f32],
1779}
1780
1781fn default_pmx_format() -> String {
1782 "pmx".to_owned()
1783}
1784
1785fn default_pmx_parts_version() -> f32 {
1786 2.0
1787}
1788
1789fn default_pmx_parts_encoding() -> String {
1790 "utf-8".to_owned()
1791}
1792
1793fn default_pmx_parts_name() -> String {
1794 "model".to_owned()
1795}
1796
1797fn default_pmx_parts_root_bone_name() -> String {
1798 "root".to_owned()
1799}
1800
1801fn default_negative_index() -> i32 {
1802 -1
1803}
1804
1805fn default_pmx_parts_display_frame_kind() -> String {
1806 "bone".to_owned()
1807}
1808
1809fn default_pmx_parts_morph_kind() -> String {
1810 "vertex".to_owned()
1811}
1812
1813fn default_pmx_parts_rigid_body_shape() -> String {
1814 "sphere".to_owned()
1815}
1816
1817fn default_pmx_parts_rigid_body_mode() -> String {
1818 "static".to_owned()
1819}
1820
1821fn default_pmx_parts_joint_kind() -> String {
1822 "generic6dofSpring".to_owned()
1823}
1824
1825fn default_unit_vec3() -> [f32; 3] {
1826 [1.0, 1.0, 1.0]
1827}
1828
1829fn default_pmx_parts_mass() -> f32 {
1830 1.0
1831}
1832
1833fn default_pmx_parts_material_name() -> String {
1834 "material".to_owned()
1835}
1836
1837fn default_pmx_parts_shared_toon_index() -> Option<u8> {
1838 Some(0)
1839}
1840
1841fn default_pmx_parts_diffuse() -> [f32; 4] {
1842 [0.8, 0.8, 0.8, 1.0]
1843}
1844
1845fn default_pmx_parts_specular_power() -> f32 {
1846 1.0
1847}
1848
1849fn default_pmx_parts_ambient() -> [f32; 3] {
1850 [0.2, 0.2, 0.2]
1851}
1852
1853fn default_pmx_parts_edge_color() -> [f32; 4] {
1854 [0.0, 0.0, 0.0, 1.0]
1855}
1856
1857fn default_pmx_parts_edge_size() -> f32 {
1858 1.0
1859}
1860
1861fn default_true() -> bool {
1862 true
1863}
1864
1865fn default_pmx_vertex_index_size() -> u8 {
1866 4
1867}
1868
1869fn default_pmx_small_index_size() -> u8 {
1870 1
1871}
1872
1873pub fn build_pmx_model_from_parts(input: PmxPartsInput<'_>) -> Result<PmxParsedModel, String> {
1874 let vertex_count = validate_pmx_parts_geometry(
1875 input.positions_xyz,
1876 input.normals_xyz,
1877 input.uvs_xy,
1878 input.indices,
1879 input.skin_indices,
1880 input.skin_weights,
1881 input.edge_scale,
1882 )?;
1883 validate_pmx_parts_descriptor(
1884 &input.descriptor,
1885 vertex_count,
1886 input.indices,
1887 input.skin_indices,
1888 )?;
1889
1890 let skin_indices = if input.skin_indices.is_empty() {
1891 vec![0; vertex_count * 4]
1892 } else {
1893 input.skin_indices.to_vec()
1894 };
1895 let skin_weights = if input.skin_weights.is_empty() {
1896 default_pmx_skin_weights(vertex_count)
1897 } else {
1898 input.skin_weights.to_vec()
1899 };
1900 let edge_scale = if input.edge_scale.is_empty() {
1901 vec![1.0; vertex_count]
1902 } else {
1903 input.edge_scale.to_vec()
1904 };
1905 let skinning_modes = vec![PMX_SKINNING_MODE_BDEF4.to_owned(); vertex_count];
1906 let face_count = input.indices.len() / 3;
1907 let (materials, material_groups) = build_pmx_parts_materials(&input.descriptor, face_count);
1908 let bones = build_pmx_parts_bones(&input.descriptor);
1909 let morphs = build_pmx_parts_morphs(&input.descriptor);
1910 let display_frames = build_pmx_parts_display_frames(&input.descriptor);
1911 let rigid_bodies = build_pmx_parts_rigid_bodies(&input.descriptor);
1912 let joints = build_pmx_parts_joints(&input.descriptor);
1913
1914 let model = PmxParsedModel {
1915 metadata: PmxParsedMetadata {
1916 format: "pmx".to_owned(),
1917 version: input.descriptor.version,
1918 encoding: input.descriptor.encoding,
1919 name: input.descriptor.name,
1920 english_name: input.descriptor.english_name,
1921 comment: input.descriptor.comment,
1922 english_comment: input.descriptor.english_comment,
1923 counts: PmxParsedCounts {
1924 vertices: vertex_count,
1925 faces: face_count,
1926 materials: materials.len(),
1927 bones: bones.len(),
1928 morphs: morphs.len(),
1929 display_frames: display_frames.len(),
1930 rigid_bodies: rigid_bodies.len(),
1931 joints: joints.len(),
1932 soft_bodies: 0,
1933 },
1934 index_sizes: PmxParsedIndexSizes {
1935 vertex: input.descriptor.index_sizes.vertex,
1936 texture: input.descriptor.index_sizes.texture,
1937 material: input.descriptor.index_sizes.material,
1938 bone: input.descriptor.index_sizes.bone,
1939 morph: input.descriptor.index_sizes.morph,
1940 rigid_body: input.descriptor.index_sizes.rigid_body,
1941 },
1942 additional_uv_count: 0,
1943 },
1944 geometry: PmxParsedGeometry {
1945 positions: input.positions_xyz.to_vec(),
1946 normals: input.normals_xyz.to_vec(),
1947 uvs: input.uvs_xy.to_vec(),
1948 additional_uvs: Vec::new(),
1949 indices: input.indices.to_vec(),
1950 skin_indices,
1951 skin_weights,
1952 edge_scale,
1953 material_groups,
1954 sdef: PmxParsedSdef {
1955 skinning_modes,
1956 ..PmxParsedSdef::default()
1957 },
1958 qdef: PmxParsedQdef::default(),
1959 },
1960 materials,
1961 skeleton: PmxParsedSkeleton { bones },
1962 morphs,
1963 display_frames,
1964 rigid_bodies,
1965 joints,
1966 soft_bodies: Vec::new(),
1967 diagnostics: Vec::new(),
1968 };
1969 validate_pmx_export_model(&model)?;
1970 Ok(model)
1971}
1972
1973pub fn validate_pmx_export_model(model: &PmxParsedModel) -> Result<(), String> {
1974 let geometry = &model.geometry;
1975 if !geometry.positions.len().is_multiple_of(3) {
1976 return Err(format!(
1977 "PMX export positions length must be divisible by 3: {}",
1978 geometry.positions.len()
1979 ));
1980 }
1981 let vertex_count = geometry.positions.len() / 3;
1982 if geometry.normals.len() != vertex_count * 3 {
1983 return Err(format!(
1984 "PMX export normals length mismatch: expected {}, got {}",
1985 vertex_count * 3,
1986 geometry.normals.len()
1987 ));
1988 }
1989 if geometry.uvs.len() != vertex_count * 2 {
1990 return Err(format!(
1991 "PMX export uvs length mismatch: expected {}, got {}",
1992 vertex_count * 2,
1993 geometry.uvs.len()
1994 ));
1995 }
1996 if geometry.additional_uvs.len() != model.metadata.additional_uv_count as usize {
1997 return Err(format!(
1998 "PMX export additionalUvs set count mismatch: expected {}, got {}",
1999 model.metadata.additional_uv_count,
2000 geometry.additional_uvs.len()
2001 ));
2002 }
2003 for (index, values) in geometry.additional_uvs.iter().enumerate() {
2004 if values.len() != vertex_count * 4 {
2005 return Err(format!(
2006 "PMX export additionalUvs[{}] length mismatch: expected {}, got {}",
2007 index,
2008 vertex_count * 4,
2009 values.len()
2010 ));
2011 }
2012 }
2013 if !geometry.indices.len().is_multiple_of(3) {
2014 return Err(format!(
2015 "PMX export indices length must be divisible by 3: {}",
2016 geometry.indices.len()
2017 ));
2018 }
2019 let expected_skin_len = vertex_count * 4;
2020 if geometry.skin_indices.is_empty() != geometry.skin_weights.is_empty() {
2021 return Err(
2022 "PMX export skinIndices and skinWeights must be both empty or both provided".to_owned(),
2023 );
2024 }
2025 if !geometry.skin_indices.is_empty() && geometry.skin_indices.len() != expected_skin_len {
2026 return Err(format!(
2027 "PMX export skinIndices length mismatch: expected {}, got {}",
2028 expected_skin_len,
2029 geometry.skin_indices.len()
2030 ));
2031 }
2032 if !geometry.skin_weights.is_empty() && geometry.skin_weights.len() != expected_skin_len {
2033 return Err(format!(
2034 "PMX export skinWeights length mismatch: expected {}, got {}",
2035 expected_skin_len,
2036 geometry.skin_weights.len()
2037 ));
2038 }
2039 if !geometry.sdef.skinning_modes.is_empty()
2040 && geometry.sdef.skinning_modes.len() != vertex_count
2041 {
2042 return Err(format!(
2043 "PMX export skinningModes length mismatch: expected {}, got {}",
2044 vertex_count,
2045 geometry.sdef.skinning_modes.len()
2046 ));
2047 }
2048 for (index, mode) in geometry.sdef.skinning_modes.iter().enumerate() {
2049 if !matches!(
2050 mode.as_str(),
2051 PMX_SKINNING_MODE_BDEF1
2052 | PMX_SKINNING_MODE_BDEF2
2053 | PMX_SKINNING_MODE_BDEF4
2054 | PMX_SKINNING_MODE_SDEF
2055 | PMX_SKINNING_MODE_QDEF
2056 ) {
2057 return Err(format!(
2058 "PMX export skinningModes[{index}] must be bdef1, bdef2, bdef4, sdef, or qdef, got {mode}"
2059 ));
2060 }
2061 }
2062 Ok(())
2063}
2064
2065fn validate_pmx_parts_geometry(
2066 positions_xyz: &[f32],
2067 normals_xyz: &[f32],
2068 uvs_xy: &[f32],
2069 indices: &[u32],
2070 skin_indices: &[u32],
2071 skin_weights: &[f32],
2072 edge_scale: &[f32],
2073) -> Result<usize, String> {
2074 if !positions_xyz.len().is_multiple_of(3) {
2075 return Err(format!(
2076 "positions_xyz must contain vertex_count * 3 values, got {}",
2077 positions_xyz.len()
2078 ));
2079 }
2080 let vertex_count = positions_xyz.len() / 3;
2081 if vertex_count == 0 {
2082 return Err("PMX parts export requires at least one vertex".to_owned());
2083 }
2084 if normals_xyz.len() != vertex_count * 3 {
2085 return Err(format!(
2086 "normals_xyz must contain vertex_count * 3 values, expected {}, got {}",
2087 vertex_count * 3,
2088 normals_xyz.len()
2089 ));
2090 }
2091 if uvs_xy.len() != vertex_count * 2 {
2092 return Err(format!(
2093 "uvs_xy must contain vertex_count * 2 values, expected {}, got {}",
2094 vertex_count * 2,
2095 uvs_xy.len()
2096 ));
2097 }
2098 if !indices.len().is_multiple_of(3) {
2099 return Err(format!(
2100 "indices must contain triangle indices and be divisible by 3, got {}",
2101 indices.len()
2102 ));
2103 }
2104 if let Some(&index) = indices
2105 .iter()
2106 .find(|&&index| index as usize >= vertex_count)
2107 {
2108 return Err(format!(
2109 "indices contains out-of-range vertex index {index} for vertex_count {vertex_count}"
2110 ));
2111 }
2112 if !skin_indices.is_empty() && skin_indices.len() != vertex_count * 4 {
2113 return Err(format!(
2114 "skin_indices must contain vertex_count * 4 values when provided, expected {}, got {}",
2115 vertex_count * 4,
2116 skin_indices.len()
2117 ));
2118 }
2119 if !skin_weights.is_empty() && skin_weights.len() != vertex_count * 4 {
2120 return Err(format!(
2121 "skin_weights must contain vertex_count * 4 values when provided, expected {}, got {}",
2122 vertex_count * 4,
2123 skin_weights.len()
2124 ));
2125 }
2126 if skin_indices.is_empty() != skin_weights.is_empty() {
2127 return Err(
2128 "skin_indices and skin_weights must either both be provided or both be empty"
2129 .to_owned(),
2130 );
2131 }
2132 if !edge_scale.is_empty() && edge_scale.len() != vertex_count {
2133 return Err(format!(
2134 "edge_scale must contain vertex_count values when provided, expected {}, got {}",
2135 vertex_count,
2136 edge_scale.len()
2137 ));
2138 }
2139 Ok(vertex_count)
2140}
2141
2142fn validate_pmx_parts_descriptor(
2143 descriptor: &PmxPartsDescriptor,
2144 vertex_count: usize,
2145 indices: &[u32],
2146 skin_indices: &[u32],
2147) -> Result<(), String> {
2148 if descriptor.version < 2.0 {
2149 return Err(format!(
2150 "PMX parts export supports PMX 2.0 or newer, got {}",
2151 descriptor.version
2152 ));
2153 }
2154 if descriptor.encoding != "utf-8" && descriptor.encoding != "utf-16-le" {
2155 return Err(format!(
2156 "PMX parts export encoding must be utf-8 or utf-16-le, got {}",
2157 descriptor.encoding
2158 ));
2159 }
2160 validate_pmx_index_size("vertex", descriptor.index_sizes.vertex)?;
2161 validate_pmx_index_size("texture", descriptor.index_sizes.texture)?;
2162 validate_pmx_index_size("material", descriptor.index_sizes.material)?;
2163 validate_pmx_index_size("bone", descriptor.index_sizes.bone)?;
2164 validate_pmx_index_size("morph", descriptor.index_sizes.morph)?;
2165 validate_pmx_index_size("rigidBody", descriptor.index_sizes.rigid_body)?;
2166 if indices.len() / 3 > i32::MAX as usize {
2167 return Err(format!(
2168 "face count {} exceeds PMX i32 material face count limit",
2169 indices.len() / 3
2170 ));
2171 }
2172 if !pmx_vertex_index_size_can_hold(descriptor.index_sizes.vertex, vertex_count) {
2173 return Err(format!(
2174 "vertex index size {} cannot hold vertex_count {}",
2175 descriptor.index_sizes.vertex, vertex_count
2176 ));
2177 }
2178 if !indices.is_empty()
2179 && !pmx_vertex_index_size_can_hold(
2180 descriptor.index_sizes.vertex,
2181 indices.iter().copied().max().unwrap_or(0) as usize + 1,
2182 )
2183 {
2184 return Err(format!(
2185 "vertex index size {} cannot hold max index {}",
2186 descriptor.index_sizes.vertex,
2187 indices.iter().copied().max().unwrap_or(0)
2188 ));
2189 }
2190 validate_pmx_parts_materials(descriptor, indices.len() / 3)?;
2191 validate_pmx_parts_bones(descriptor)?;
2192 validate_pmx_parts_morphs(descriptor, vertex_count)?;
2193 validate_pmx_parts_skin_indices(descriptor, skin_indices)?;
2194 validate_pmx_parts_display_frames(descriptor)?;
2195 validate_pmx_parts_physics(descriptor)?;
2196 Ok(())
2197}
2198
2199fn validate_pmx_parts_materials(
2200 descriptor: &PmxPartsDescriptor,
2201 face_count: usize,
2202) -> Result<(), String> {
2203 if descriptor.materials.is_empty() {
2204 return Ok(());
2205 }
2206 if !pmx_signed_index_size_can_hold_count(
2207 descriptor.index_sizes.material,
2208 descriptor.materials.len(),
2209 ) {
2210 return Err(format!(
2211 "material index size {} cannot hold material count {}",
2212 descriptor.index_sizes.material,
2213 descriptor.materials.len()
2214 ));
2215 }
2216 let mut total = 0usize;
2217 for (index, material) in descriptor.materials.iter().enumerate() {
2218 if material.face_count < 0 {
2219 return Err(format!(
2220 "materials[{index}].faceCount must be non-negative, got {}",
2221 material.face_count
2222 ));
2223 }
2224 total += material.face_count as usize;
2225 }
2226 if total != face_count {
2227 return Err(format!(
2228 "materials faceCount sum must match index face count, expected {}, got {}",
2229 face_count, total
2230 ));
2231 }
2232 Ok(())
2233}
2234
2235fn validate_pmx_parts_bones(descriptor: &PmxPartsDescriptor) -> Result<(), String> {
2236 let bone_count = descriptor.bones.len().max(1);
2237 if !pmx_signed_index_size_can_hold_count(descriptor.index_sizes.bone, bone_count) {
2238 return Err(format!(
2239 "bone index size {} cannot hold bone count {}",
2240 descriptor.index_sizes.bone, bone_count
2241 ));
2242 }
2243 for (index, bone) in descriptor.bones.iter().enumerate() {
2244 if bone.parent_index < -1
2245 || (bone.parent_index != -1 && bone.parent_index as usize >= bone_count)
2246 {
2247 return Err(format!(
2248 "bones[{index}].parentIndex must be -1 or a valid bone index, got {}",
2249 bone.parent_index
2250 ));
2251 }
2252 if bone.tail_index < -1 || (bone.tail_index != -1 && bone.tail_index as usize >= bone_count)
2253 {
2254 return Err(format!(
2255 "bones[{index}].tailIndex must be -1 or a valid bone index, got {}",
2256 bone.tail_index
2257 ));
2258 }
2259 }
2260 Ok(())
2261}
2262
2263fn validate_pmx_parts_skin_indices(
2264 descriptor: &PmxPartsDescriptor,
2265 skin_indices: &[u32],
2266) -> Result<(), String> {
2267 if skin_indices.is_empty() {
2268 return Ok(());
2269 }
2270 let bone_count = descriptor.bones.len().max(1);
2271 if let Some(&index) = skin_indices
2272 .iter()
2273 .find(|&&index| index as usize >= bone_count)
2274 {
2275 return Err(format!(
2276 "skin_indices contains out-of-range bone index {index} for bone count {bone_count}"
2277 ));
2278 }
2279 Ok(())
2280}
2281
2282fn validate_pmx_parts_morphs(
2283 descriptor: &PmxPartsDescriptor,
2284 vertex_count: usize,
2285) -> Result<(), String> {
2286 if !pmx_signed_index_size_can_hold_count(descriptor.index_sizes.morph, descriptor.morphs.len())
2287 {
2288 return Err(format!(
2289 "morph index size {} cannot hold morph count {}",
2290 descriptor.index_sizes.morph,
2291 descriptor.morphs.len()
2292 ));
2293 }
2294 for (morph_index, morph) in descriptor.morphs.iter().enumerate() {
2295 match morph.kind.as_str() {
2296 "vertex" => {
2297 for (offset_index, offset) in morph.vertex_offsets.iter().enumerate() {
2298 if offset.vertex_index as usize >= vertex_count {
2299 return Err(format!(
2300 "morphs[{morph_index}].vertexOffsets[{offset_index}].vertexIndex out of range: {} for vertex_count {vertex_count}",
2301 offset.vertex_index
2302 ));
2303 }
2304 }
2305 }
2306 "group" => {
2307 for (offset_index, offset) in morph.group_offsets.iter().enumerate() {
2308 if offset.morph_index < 0
2309 || offset.morph_index as usize >= descriptor.morphs.len()
2310 {
2311 return Err(format!(
2312 "morphs[{morph_index}].groupOffsets[{offset_index}].morphIndex out of range: {}",
2313 offset.morph_index
2314 ));
2315 }
2316 }
2317 }
2318 other => {
2319 return Err(format!(
2320 "morphs[{morph_index}].kind must be vertex or group, got {other}"
2321 ));
2322 }
2323 }
2324 }
2325 Ok(())
2326}
2327
2328fn validate_pmx_parts_display_frames(descriptor: &PmxPartsDescriptor) -> Result<(), String> {
2329 let bone_count = descriptor.bones.len().max(1);
2330 let morph_count = descriptor.morphs.len();
2331 for (frame_index, frame) in descriptor.display_frames.iter().enumerate() {
2332 for (item_index, item) in frame.frames.iter().enumerate() {
2333 match item.kind.as_str() {
2334 "bone" => {
2335 if item.index < 0 || item.index as usize >= bone_count {
2336 return Err(format!(
2337 "displayFrames[{frame_index}].frames[{item_index}].index must reference an existing bone, got {}",
2338 item.index
2339 ));
2340 }
2341 }
2342 "morph" => {
2343 if item.index < 0 || item.index as usize >= morph_count {
2344 return Err(format!(
2345 "displayFrames[{frame_index}].frames[{item_index}].index must reference an existing morph, got {}",
2346 item.index
2347 ));
2348 }
2349 }
2350 other => {
2351 return Err(format!(
2352 "displayFrames[{frame_index}].frames[{item_index}].kind must be bone or morph, got {other}"
2353 ));
2354 }
2355 }
2356 }
2357 }
2358 Ok(())
2359}
2360
2361fn validate_pmx_parts_physics(descriptor: &PmxPartsDescriptor) -> Result<(), String> {
2362 let bone_count = descriptor.bones.len().max(1);
2363 if !pmx_signed_index_size_can_hold_count(
2364 descriptor.index_sizes.rigid_body,
2365 descriptor.rigid_bodies.len(),
2366 ) {
2367 return Err(format!(
2368 "rigidBody index size {} cannot hold rigid body count {}",
2369 descriptor.index_sizes.rigid_body,
2370 descriptor.rigid_bodies.len()
2371 ));
2372 }
2373 for (index, body) in descriptor.rigid_bodies.iter().enumerate() {
2374 if body.bone_index < -1 || (body.bone_index != -1 && body.bone_index as usize >= bone_count)
2375 {
2376 return Err(format!(
2377 "rigidBodies[{index}].boneIndex must be -1 or a valid bone index, got {}",
2378 body.bone_index
2379 ));
2380 }
2381 if !matches!(body.shape.as_str(), "sphere" | "box" | "capsule") {
2382 return Err(format!(
2383 "rigidBodies[{index}].shape must be sphere, box, or capsule, got {}",
2384 body.shape
2385 ));
2386 }
2387 if !matches!(body.mode.as_str(), "static" | "dynamic" | "dynamicBone") {
2388 return Err(format!(
2389 "rigidBodies[{index}].mode must be static, dynamic, or dynamicBone, got {}",
2390 body.mode
2391 ));
2392 }
2393 }
2394 for (index, joint) in descriptor.joints.iter().enumerate() {
2395 validate_pmx_parts_rigid_body_ref(
2396 "rigidBodyIndexA",
2397 index,
2398 joint.rigid_body_index_a,
2399 descriptor.rigid_bodies.len(),
2400 )?;
2401 validate_pmx_parts_rigid_body_ref(
2402 "rigidBodyIndexB",
2403 index,
2404 joint.rigid_body_index_b,
2405 descriptor.rigid_bodies.len(),
2406 )?;
2407 if !matches!(
2408 joint.kind.as_str(),
2409 "generic6dofSpring" | "generic6dof" | "point2point" | "coneTwist" | "slider" | "hinge"
2410 ) {
2411 return Err(format!(
2412 "joints[{index}].kind has unsupported PMX joint type {}",
2413 joint.kind
2414 ));
2415 }
2416 }
2417 Ok(())
2418}
2419
2420fn validate_pmx_parts_rigid_body_ref(
2421 field: &str,
2422 joint_index: usize,
2423 value: i32,
2424 rigid_body_count: usize,
2425) -> Result<(), String> {
2426 if value < -1 || (value != -1 && value as usize >= rigid_body_count) {
2427 Err(format!(
2428 "joints[{joint_index}].{field} must be -1 or a valid rigid body index, got {value}"
2429 ))
2430 } else {
2431 Ok(())
2432 }
2433}
2434
2435fn validate_pmx_index_size(name: &str, value: u8) -> Result<(), String> {
2436 if matches!(value, 1 | 2 | 4) {
2437 Ok(())
2438 } else {
2439 Err(format!(
2440 "PMX {name} index size must be 1, 2, or 4, got {value}"
2441 ))
2442 }
2443}
2444
2445fn pmx_vertex_index_size_can_hold(size: u8, count: usize) -> bool {
2446 match size {
2447 1 => count <= u8::MAX as usize + 1,
2448 2 => count <= u16::MAX as usize + 1,
2449 4 => true,
2450 _ => false,
2451 }
2452}
2453
2454fn pmx_signed_index_size_can_hold_count(size: u8, count: usize) -> bool {
2455 match size {
2456 1 => count <= i8::MAX as usize + 1,
2457 2 => count <= i16::MAX as usize + 1,
2458 4 => true,
2459 _ => false,
2460 }
2461}
2462
2463fn default_pmx_skin_weights(vertex_count: usize) -> Vec<f32> {
2464 let mut weights = vec![0.0; vertex_count * 4];
2465 for vertex in 0..vertex_count {
2466 weights[vertex * 4] = 1.0;
2467 }
2468 weights
2469}
2470
2471fn build_pmx_parts_materials(
2472 descriptor: &PmxPartsDescriptor,
2473 face_count: usize,
2474) -> (Vec<PmxParsedMaterial>, Vec<PmxParsedMaterialGroup>) {
2475 if face_count == 0 {
2476 return (Vec::new(), Vec::new());
2477 }
2478 if descriptor.materials.is_empty() {
2479 return (
2480 vec![default_pmx_parts_material(
2481 &descriptor.material_name,
2482 &descriptor.english_material_name,
2483 face_count as i32,
2484 )],
2485 vec![PmxParsedMaterialGroup {
2486 start: 0,
2487 count: face_count * 3,
2488 material_index: 0,
2489 }],
2490 );
2491 }
2492
2493 let mut start = 0usize;
2494 let mut materials = Vec::with_capacity(descriptor.materials.len());
2495 let mut groups = Vec::with_capacity(descriptor.materials.len());
2496 for (index, material) in descriptor.materials.iter().enumerate() {
2497 let index_count = material.face_count as usize * 3;
2498 materials.push(pmx_parts_material_from_descriptor(material));
2499 groups.push(PmxParsedMaterialGroup {
2500 start,
2501 count: index_count,
2502 material_index: index,
2503 });
2504 start += index_count;
2505 }
2506 (materials, groups)
2507}
2508
2509fn pmx_parts_material_from_descriptor(material: &PmxPartsMaterialDescriptor) -> PmxParsedMaterial {
2510 PmxParsedMaterial {
2511 name: material.name.clone(),
2512 english_name: material.english_name.clone(),
2513 texture_path: material.texture_path.clone(),
2514 sphere_texture_path: material.sphere_texture_path.clone(),
2515 sphere_mode: if material.sphere_mode.is_empty() {
2516 "none".to_owned()
2517 } else {
2518 material.sphere_mode.clone()
2519 },
2520 toon_texture_path: material.toon_texture_path.clone(),
2521 shared_toon_index: material.shared_toon_index,
2522 diffuse: material.diffuse,
2523 specular: material.specular,
2524 specular_power: material.specular_power,
2525 ambient: material.ambient,
2526 edge_color: material.edge_color,
2527 edge_size: material.edge_size,
2528 flags: PmxParsedMaterialFlags {
2529 double_sided: material.flags.double_sided,
2530 ground_shadow: material.flags.ground_shadow,
2531 self_shadow_map: material.flags.self_shadow_map,
2532 self_shadow: material.flags.self_shadow,
2533 edge: material.flags.edge,
2534 vertex_color: material.flags.vertex_color,
2535 point_draw: material.flags.point_draw,
2536 line_draw: material.flags.line_draw,
2537 },
2538 face_count: material.face_count,
2539 }
2540}
2541
2542fn build_pmx_parts_bones(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedBone> {
2543 if descriptor.bones.is_empty() {
2544 return vec![default_pmx_parts_root_bone()];
2545 }
2546 descriptor
2547 .bones
2548 .iter()
2549 .map(pmx_parts_bone_from_descriptor)
2550 .collect()
2551}
2552
2553fn build_pmx_parts_display_frames(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedDisplayFrame> {
2554 descriptor
2555 .display_frames
2556 .iter()
2557 .map(|frame| PmxParsedDisplayFrame {
2558 name: frame.name.clone(),
2559 english_name: frame.english_name.clone(),
2560 special: frame.special,
2561 frames: frame
2562 .frames
2563 .iter()
2564 .map(|item| PmxParsedDisplayFrameElement {
2565 kind: item.kind.clone(),
2566 index: item.index,
2567 })
2568 .collect(),
2569 })
2570 .collect()
2571}
2572
2573fn build_pmx_parts_morphs(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedMorph> {
2574 descriptor
2575 .morphs
2576 .iter()
2577 .map(pmx_parts_morph_from_descriptor)
2578 .collect()
2579}
2580
2581fn build_pmx_parts_rigid_bodies(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedRigidBody> {
2582 descriptor
2583 .rigid_bodies
2584 .iter()
2585 .map(|body| PmxParsedRigidBody {
2586 name: body.name.clone(),
2587 english_name: body.english_name.clone(),
2588 bone_index: body.bone_index,
2589 group: body.group,
2590 mask: body.mask,
2591 shape: body.shape.clone(),
2592 size: body.size,
2593 position: body.position,
2594 rotation: body.rotation,
2595 mass: body.mass,
2596 linear_damping: body.linear_damping,
2597 angular_damping: body.angular_damping,
2598 restitution: body.restitution,
2599 friction: body.friction,
2600 mode: body.mode.clone(),
2601 })
2602 .collect()
2603}
2604
2605fn build_pmx_parts_joints(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedJoint> {
2606 descriptor
2607 .joints
2608 .iter()
2609 .map(|joint| PmxParsedJoint {
2610 name: joint.name.clone(),
2611 english_name: joint.english_name.clone(),
2612 kind: joint.kind.clone(),
2613 rigid_body_index_a: joint.rigid_body_index_a,
2614 rigid_body_index_b: joint.rigid_body_index_b,
2615 position: joint.position,
2616 rotation: joint.rotation,
2617 translation_lower_limit: joint.translation_lower_limit,
2618 translation_upper_limit: joint.translation_upper_limit,
2619 rotation_lower_limit: joint.rotation_lower_limit,
2620 rotation_upper_limit: joint.rotation_upper_limit,
2621 spring_translation_factor: joint.spring_translation_factor,
2622 spring_rotation_factor: joint.spring_rotation_factor,
2623 })
2624 .collect()
2625}
2626
2627fn pmx_parts_morph_from_descriptor(morph: &PmxPartsMorphDescriptor) -> PmxParsedMorph {
2628 let mut parsed = PmxParsedMorph {
2629 name: morph.name.clone(),
2630 english_name: morph.english_name.clone(),
2631 kind: morph.kind.clone(),
2632 vertex_offsets: Vec::new(),
2633 group_offsets: Vec::new(),
2634 bone_offsets: Vec::new(),
2635 uv_offsets: Vec::new(),
2636 additional_uv_offsets: Vec::new(),
2637 material_offsets: Vec::new(),
2638 flip_offsets: Vec::new(),
2639 impulse_offsets: Vec::new(),
2640 };
2641 match morph.kind.as_str() {
2642 "vertex" => {
2643 parsed.vertex_offsets = morph
2644 .vertex_offsets
2645 .iter()
2646 .map(|offset| PmxParsedVertexMorphOffset {
2647 vertex_index: offset.vertex_index,
2648 position: offset.position,
2649 })
2650 .collect();
2651 }
2652 "group" => {
2653 parsed.group_offsets = morph
2654 .group_offsets
2655 .iter()
2656 .map(|offset| PmxParsedGroupMorphOffset {
2657 morph_index: offset.morph_index,
2658 weight: offset.weight,
2659 })
2660 .collect();
2661 }
2662 _ => unreachable!("PmxPartsMorphDescriptor was validated before build"),
2663 }
2664 parsed
2665}
2666
2667fn pmx_parts_bone_from_descriptor(bone: &PmxPartsBoneDescriptor) -> PmxParsedBone {
2668 let tail_position = if bone.tail_index >= 0 {
2669 None
2670 } else {
2671 Some(bone.tail_position.unwrap_or([0.0, 1.0, 0.0]))
2672 };
2673 PmxParsedBone {
2674 name: bone.name.clone(),
2675 english_name: bone.english_name.clone(),
2676 parent_index: bone.parent_index,
2677 layer: bone.layer,
2678 position: bone.position,
2679 tail_index: bone.tail_index,
2680 tail_position,
2681 flags: PmxParsedBoneFlags {
2682 indexed_tail: bone.tail_index >= 0,
2683 rotatable: bone.rotatable,
2684 translatable: bone.translatable,
2685 visible: bone.visible,
2686 enabled: bone.enabled,
2687 ik: false,
2688 append_local: false,
2689 append_rotate: false,
2690 append_translate: false,
2691 fixed_axis: false,
2692 local_axis: false,
2693 external_parent_transform: false,
2694 transform_after_physics: false,
2695 },
2696 append_transform: None,
2697 fixed_axis: None,
2698 local_axis: None,
2699 external_parent_key: None,
2700 ik: None,
2701 }
2702}
2703
2704fn default_pmx_parts_material(
2705 name: &str,
2706 english_name: &str,
2707 face_count: i32,
2708) -> PmxParsedMaterial {
2709 PmxParsedMaterial {
2710 name: if name.is_empty() {
2711 "material".to_owned()
2712 } else {
2713 name.to_owned()
2714 },
2715 english_name: english_name.to_owned(),
2716 texture_path: String::new(),
2717 sphere_texture_path: String::new(),
2718 sphere_mode: "none".to_owned(),
2719 toon_texture_path: String::new(),
2720 shared_toon_index: Some(0),
2721 diffuse: [0.8, 0.8, 0.8, 1.0],
2722 specular: [0.0, 0.0, 0.0],
2723 specular_power: 1.0,
2724 ambient: [0.2, 0.2, 0.2],
2725 edge_color: [0.0, 0.0, 0.0, 1.0],
2726 edge_size: 1.0,
2727 flags: PmxParsedMaterialFlags {
2728 double_sided: true,
2729 ground_shadow: true,
2730 self_shadow_map: true,
2731 self_shadow: true,
2732 edge: false,
2733 vertex_color: false,
2734 point_draw: false,
2735 line_draw: false,
2736 },
2737 face_count,
2738 }
2739}
2740
2741fn default_pmx_parts_root_bone() -> PmxParsedBone {
2742 PmxParsedBone {
2743 name: "root".to_owned(),
2744 english_name: "root".to_owned(),
2745 parent_index: -1,
2746 layer: 0,
2747 position: [0.0, 0.0, 0.0],
2748 tail_index: -1,
2749 tail_position: Some([0.0, 1.0, 0.0]),
2750 flags: PmxParsedBoneFlags {
2751 indexed_tail: false,
2752 rotatable: true,
2753 translatable: true,
2754 visible: true,
2755 enabled: true,
2756 ik: false,
2757 append_local: false,
2758 append_rotate: false,
2759 append_translate: false,
2760 fixed_axis: false,
2761 local_axis: false,
2762 transform_after_physics: false,
2763 external_parent_transform: false,
2764 },
2765 append_transform: None,
2766 fixed_axis: None,
2767 local_axis: None,
2768 external_parent_key: None,
2769 ik: None,
2770 }
2771}
2772
2773pub fn parse_pmx_model(data: &[u8]) -> Result<PmxParsedModel, ImportError> {
2774 let (header, pos) = read_header(data)?;
2775 let mut r = Reader { data, pos };
2776 let name = r.read_string(header.encoding)?;
2777 let english_name = r.read_string(header.encoding)?;
2778 let comment = r.read_string(header.encoding)?;
2779 let english_comment = r.read_string(header.encoding)?;
2780
2781 let vertex_count = read_section_count_with_min_record(&mut r, 12 + 12 + 8 + 1 + 4)?;
2782 let geometry = read_parsed_geometry(&mut r, &header, vertex_count)?;
2783 let index_count = geometry.indices.len();
2784 let textures = read_parsed_textures(&mut r, header.encoding)?;
2785 let materials = read_parsed_materials(&mut r, &header, &textures)?;
2786 let material_groups = build_material_groups(&materials);
2787 let bone_count = read_section_count_with_min_record(&mut r, 4 + 4 + 12)?;
2788 let skeleton = PmxParsedSkeleton {
2789 bones: read_parsed_bones(&mut r, &header, bone_count)?,
2790 };
2791 let morphs = read_parsed_morphs(&mut r, &header)?;
2792 let display_frames = read_parsed_display_frames(&mut r, &header)?;
2793 let rigid_bodies = if r.remaining() >= 4 {
2794 read_parsed_rigid_bodies(&mut r, &header)?
2795 } else {
2796 Vec::new()
2797 };
2798 let joints = if r.remaining() >= 4 {
2799 read_parsed_joints(&mut r, &header)?
2800 } else {
2801 Vec::new()
2802 };
2803 let soft_bodies = if header.version >= 2.05 && r.remaining() >= 4 {
2804 read_parsed_soft_bodies(&mut r, &header)?
2805 } else {
2806 Vec::new()
2807 };
2808
2809 let mut geometry = geometry;
2810 geometry.material_groups = material_groups;
2811 let mut diagnostics = Vec::new();
2812 if !soft_bodies.is_empty() {
2813 diagnostics.push(PmxParserDiagnostic {
2814 level: "warning".to_owned(),
2815 code: "PMX_SOFT_BODY_UNSUPPORTED".to_owned(),
2816 message: format!(
2817 "{} PMX soft bodies are parsed only as header records by mmd-anim.",
2818 soft_bodies.len()
2819 ),
2820 });
2821 }
2822 if r.remaining() > 0 {
2823 diagnostics.push(PmxParserDiagnostic {
2824 level: "warning".to_owned(),
2825 code: "PMX_TRAILING_DATA_UNPARSED".to_owned(),
2826 message: format!("{} trailing PMX bytes were left unparsed.", r.remaining()),
2827 });
2828 }
2829
2830 Ok(PmxParsedModel {
2831 metadata: PmxParsedMetadata {
2832 format: "pmx".to_owned(),
2833 version: header.version,
2834 encoding: match header.encoding {
2835 TextEncoding::Utf8 => "utf-8".to_owned(),
2836 TextEncoding::Utf16Le => "utf-16-le".to_owned(),
2837 },
2838 name,
2839 english_name,
2840 comment,
2841 english_comment,
2842 counts: PmxParsedCounts {
2843 vertices: vertex_count,
2844 faces: index_count / 3,
2845 materials: materials.len(),
2846 bones: skeleton.bones.len(),
2847 morphs: morphs.len(),
2848 display_frames: display_frames.len(),
2849 rigid_bodies: rigid_bodies.len(),
2850 joints: joints.len(),
2851 soft_bodies: soft_bodies.len(),
2852 },
2853 index_sizes: PmxParsedIndexSizes {
2854 vertex: header.vertex_index_size,
2855 texture: header.texture_index_size,
2856 material: header.material_index_size,
2857 bone: header.bone_index_size,
2858 morph: header.morph_index_size,
2859 rigid_body: header.rigidbody_index_size,
2860 },
2861 additional_uv_count: header.extra_uv_count,
2862 },
2863 geometry,
2864 materials,
2865 skeleton,
2866 morphs,
2867 display_frames,
2868 rigid_bodies,
2869 joints,
2870 soft_bodies,
2871 diagnostics,
2872 })
2873}
2874
2875pub fn export_pmx_model(model: &PmxParsedModel) -> Vec<u8> {
2876 let encoding = pmx_text_encoding(&model.metadata);
2877 let mut out = Vec::new();
2878 out.extend_from_slice(&PMX_MAGIC);
2879 write_f32(&mut out, model.metadata.version);
2880 out.push(8);
2881 out.push(encoding as u8);
2882 out.push(model.metadata.additional_uv_count);
2883 out.push(model.metadata.index_sizes.vertex);
2884 out.push(model.metadata.index_sizes.texture);
2885 out.push(model.metadata.index_sizes.material);
2886 out.push(model.metadata.index_sizes.bone);
2887 out.push(model.metadata.index_sizes.morph);
2888 out.push(model.metadata.index_sizes.rigid_body);
2889
2890 write_pmx_string(&mut out, &model.metadata.name, encoding);
2891 write_pmx_string(&mut out, &model.metadata.english_name, encoding);
2892 write_pmx_string(&mut out, &model.metadata.comment, encoding);
2893 write_pmx_string(&mut out, &model.metadata.english_comment, encoding);
2894
2895 let vertex_count = model.geometry.positions.len() / 3;
2896 write_i32(&mut out, vertex_count as i32);
2897 for index in 0..vertex_count {
2898 write_f32_slice(
2899 &mut out,
2900 &model.geometry.positions[index * 3..index * 3 + 3],
2901 );
2902 write_f32_slice(&mut out, &model.geometry.normals[index * 3..index * 3 + 3]);
2903 write_f32_slice(&mut out, &model.geometry.uvs[index * 2..index * 2 + 2]);
2904 for uv in &model.geometry.additional_uvs {
2905 write_f32_slice(&mut out, &uv[index * 4..index * 4 + 4]);
2906 }
2907 write_pmx_vertex_skinning(&mut out, model, index);
2908 write_f32(
2909 &mut out,
2910 model.geometry.edge_scale.get(index).copied().unwrap_or(1.0),
2911 );
2912 }
2913
2914 write_i32(&mut out, model.geometry.indices.len() as i32);
2915 for &index in &model.geometry.indices {
2916 write_vertex_index(&mut out, index, model.metadata.index_sizes.vertex);
2917 }
2918
2919 let textures = collect_pmx_textures(&model.materials);
2920 write_i32(&mut out, textures.len() as i32);
2921 for texture in &textures {
2922 write_pmx_string(&mut out, texture, encoding);
2923 }
2924
2925 write_i32(&mut out, model.materials.len() as i32);
2926 for material in &model.materials {
2927 write_pmx_string(&mut out, &material.name, encoding);
2928 write_pmx_string(&mut out, &material.english_name, encoding);
2929 write_f32_slice(&mut out, &material.diffuse);
2930 write_f32_slice(&mut out, &material.specular);
2931 write_f32(&mut out, material.specular_power);
2932 write_f32_slice(&mut out, &material.ambient);
2933 out.push(material_flag_bits(&material.flags));
2934 write_f32_slice(&mut out, &material.edge_color);
2935 write_f32(&mut out, material.edge_size);
2936 write_sized_index(
2937 &mut out,
2938 texture_index(&textures, &material.texture_path),
2939 model.metadata.index_sizes.texture,
2940 );
2941 write_sized_index(
2942 &mut out,
2943 texture_index(&textures, &material.sphere_texture_path),
2944 model.metadata.index_sizes.texture,
2945 );
2946 out.push(match material.sphere_mode.as_str() {
2947 "multiply" => 1,
2948 "add" => 2,
2949 "subTexture" => 3,
2950 _ => 0,
2951 });
2952 if let Some(shared) = material.shared_toon_index {
2953 out.push(1);
2954 out.push(shared);
2955 } else {
2956 out.push(0);
2957 write_sized_index(
2958 &mut out,
2959 texture_index(&textures, &material.toon_texture_path),
2960 model.metadata.index_sizes.texture,
2961 );
2962 }
2963 write_pmx_string(&mut out, "", encoding);
2964 write_i32(&mut out, material.face_count.saturating_mul(3));
2965 }
2966
2967 write_i32(&mut out, model.skeleton.bones.len() as i32);
2968 for bone in &model.skeleton.bones {
2969 write_pmx_string(&mut out, &bone.name, encoding);
2970 write_pmx_string(&mut out, &bone.english_name, encoding);
2971 write_f32_slice(&mut out, &bone.position);
2972 write_sized_index(&mut out, bone.parent_index, model.metadata.index_sizes.bone);
2973 write_i32(&mut out, bone.layer);
2974 let bits = bone_flag_bits(&bone.flags);
2975 write_u16(&mut out, bits);
2976 if bone.flags.indexed_tail {
2977 write_sized_index(&mut out, bone.tail_index, model.metadata.index_sizes.bone);
2978 } else {
2979 write_f32_slice(&mut out, &bone.tail_position.unwrap_or([0.0; 3]));
2980 }
2981 if bone.flags.append_rotate || bone.flags.append_translate {
2982 let append = bone.append_transform.as_ref();
2983 write_sized_index(
2984 &mut out,
2985 append.map(|value| value.parent_index).unwrap_or(-1),
2986 model.metadata.index_sizes.bone,
2987 );
2988 write_f32(&mut out, append.map(|value| value.weight).unwrap_or(0.0));
2989 }
2990 if bone.flags.fixed_axis {
2991 write_f32_slice(&mut out, &bone.fixed_axis.unwrap_or([0.0; 3]));
2992 }
2993 if bone.flags.local_axis {
2994 if let Some(axis) = &bone.local_axis {
2995 write_f32_slice(&mut out, &axis.x);
2996 write_f32_slice(&mut out, &axis.z);
2997 } else {
2998 write_f32_slice(&mut out, &[1.0, 0.0, 0.0]);
2999 write_f32_slice(&mut out, &[0.0, 0.0, 1.0]);
3000 }
3001 }
3002 if bone.flags.external_parent_transform {
3003 write_i32(&mut out, bone.external_parent_key.unwrap_or(0));
3004 }
3005 if bone.flags.ik {
3006 if let Some(ik) = &bone.ik {
3007 write_sized_index(&mut out, ik.target_index, model.metadata.index_sizes.bone);
3008 write_i32(&mut out, ik.loop_count);
3009 write_f32(&mut out, ik.limit_angle);
3010 write_i32(&mut out, ik.links.len() as i32);
3011 for link in &ik.links {
3012 write_sized_index(&mut out, link.bone_index, model.metadata.index_sizes.bone);
3013 out.push(u8::from(link.limits.is_some()));
3014 if let Some(limits) = &link.limits {
3015 write_f32_slice(&mut out, &limits.lower);
3016 write_f32_slice(&mut out, &limits.upper);
3017 }
3018 }
3019 } else {
3020 write_sized_index(&mut out, -1, model.metadata.index_sizes.bone);
3021 write_i32(&mut out, 0);
3022 write_f32(&mut out, 0.0);
3023 write_i32(&mut out, 0);
3024 }
3025 }
3026 }
3027
3028 write_i32(&mut out, model.morphs.len() as i32);
3029 for morph in &model.morphs {
3030 write_pmx_string(&mut out, &morph.name, encoding);
3031 write_pmx_string(&mut out, &morph.english_name, encoding);
3032 out.push(0);
3033 let morph_type = morph_type_byte(morph);
3034 out.push(morph_type);
3035 write_i32(&mut out, morph_offset_count(morph, morph_type) as i32);
3036 match morph_type {
3037 0 => write_group_morph_offsets(&mut out, &morph.group_offsets, model),
3038 1 => {
3039 for offset in &morph.vertex_offsets {
3040 write_vertex_index(
3041 &mut out,
3042 offset.vertex_index,
3043 model.metadata.index_sizes.vertex,
3044 );
3045 write_f32_slice(&mut out, &offset.position);
3046 }
3047 }
3048 2 => {
3049 for offset in &morph.bone_offsets {
3050 write_sized_index(&mut out, offset.bone_index, model.metadata.index_sizes.bone);
3051 write_f32_slice(&mut out, &offset.translation);
3052 write_f32_slice(&mut out, &offset.rotation);
3053 }
3054 }
3055 3 => write_uv_morph_offsets(&mut out, &morph.uv_offsets, model),
3056 4..=7 => {
3057 for offset in &morph.additional_uv_offsets {
3058 write_vertex_index(
3059 &mut out,
3060 offset.vertex_index,
3061 model.metadata.index_sizes.vertex,
3062 );
3063 write_f32_slice(&mut out, &offset.uv);
3064 }
3065 }
3066 8 => {
3067 for offset in &morph.material_offsets {
3068 write_sized_index(
3069 &mut out,
3070 offset.material_index,
3071 model.metadata.index_sizes.material,
3072 );
3073 out.push(if offset.operation == "multiply" { 0 } else { 1 });
3074 write_f32_slice(&mut out, &offset.diffuse);
3075 write_f32_slice(&mut out, &offset.specular);
3076 write_f32(&mut out, offset.specular_power);
3077 write_f32_slice(&mut out, &offset.ambient);
3078 write_f32_slice(&mut out, &offset.edge_color);
3079 write_f32(&mut out, offset.edge_size);
3080 write_f32_slice(&mut out, &offset.texture_factor);
3081 write_f32_slice(&mut out, &offset.sphere_texture_factor);
3082 write_f32_slice(&mut out, &offset.toon_texture_factor);
3083 }
3084 }
3085 9 => write_group_morph_offsets(&mut out, &morph.flip_offsets, model),
3086 10 => {
3087 for offset in &morph.impulse_offsets {
3088 write_sized_index(
3089 &mut out,
3090 offset.rigid_body_index,
3091 model.metadata.index_sizes.rigid_body,
3092 );
3093 out.push(u8::from(offset.local));
3094 write_f32_slice(&mut out, &offset.velocity);
3095 write_f32_slice(&mut out, &offset.torque);
3096 }
3097 }
3098 _ => {}
3099 }
3100 }
3101
3102 write_i32(&mut out, model.display_frames.len() as i32);
3103 for frame in &model.display_frames {
3104 write_pmx_string(&mut out, &frame.name, encoding);
3105 write_pmx_string(&mut out, &frame.english_name, encoding);
3106 out.push(u8::from(frame.special));
3107 write_i32(&mut out, frame.frames.len() as i32);
3108 for item in &frame.frames {
3109 if item.kind == "morph" {
3110 out.push(1);
3111 write_sized_index(&mut out, item.index, model.metadata.index_sizes.morph);
3112 } else {
3113 out.push(0);
3114 write_sized_index(&mut out, item.index, model.metadata.index_sizes.bone);
3115 }
3116 }
3117 }
3118
3119 write_i32(&mut out, model.rigid_bodies.len() as i32);
3120 for body in &model.rigid_bodies {
3121 write_pmx_string(&mut out, &body.name, encoding);
3122 write_pmx_string(&mut out, &body.english_name, encoding);
3123 write_sized_index(&mut out, body.bone_index, model.metadata.index_sizes.bone);
3124 out.push(body.group);
3125 write_u16(&mut out, body.mask);
3126 out.push(match body.shape.as_str() {
3127 "box" => 1,
3128 "capsule" => 2,
3129 _ => 0,
3130 });
3131 write_f32_slice(&mut out, &body.size);
3132 write_f32_slice(&mut out, &body.position);
3133 write_f32_slice(&mut out, &body.rotation);
3134 write_f32(&mut out, body.mass);
3135 write_f32(&mut out, body.linear_damping);
3136 write_f32(&mut out, body.angular_damping);
3137 write_f32(&mut out, body.restitution);
3138 write_f32(&mut out, body.friction);
3139 out.push(match body.mode.as_str() {
3140 "dynamic" => 1,
3141 "dynamicBone" => 2,
3142 _ => 0,
3143 });
3144 }
3145
3146 write_i32(&mut out, model.joints.len() as i32);
3147 for joint in &model.joints {
3148 write_pmx_string(&mut out, &joint.name, encoding);
3149 write_pmx_string(&mut out, &joint.english_name, encoding);
3150 out.push(match joint.kind.as_str() {
3151 "generic6dof" => 1,
3152 "point2point" => 2,
3153 "coneTwist" => 3,
3154 "slider" => 4,
3155 "hinge" => 5,
3156 _ => 0,
3157 });
3158 write_sized_index(
3159 &mut out,
3160 joint.rigid_body_index_a,
3161 model.metadata.index_sizes.rigid_body,
3162 );
3163 write_sized_index(
3164 &mut out,
3165 joint.rigid_body_index_b,
3166 model.metadata.index_sizes.rigid_body,
3167 );
3168 write_f32_slice(&mut out, &joint.position);
3169 write_f32_slice(&mut out, &joint.rotation);
3170 write_f32_slice(&mut out, &joint.translation_lower_limit);
3171 write_f32_slice(&mut out, &joint.translation_upper_limit);
3172 write_f32_slice(&mut out, &joint.rotation_lower_limit);
3173 write_f32_slice(&mut out, &joint.rotation_upper_limit);
3174 write_f32_slice(&mut out, &joint.spring_translation_factor);
3175 write_f32_slice(&mut out, &joint.spring_rotation_factor);
3176 }
3177
3178 if model.metadata.version >= 2.05 {
3179 write_i32(&mut out, model.soft_bodies.len() as i32);
3180 for soft_body in &model.soft_bodies {
3181 write_pmx_string(&mut out, &soft_body.name, encoding);
3182 write_pmx_string(&mut out, &soft_body.english_name, encoding);
3183 out.push(if soft_body.kind == "rope" { 1 } else { 0 });
3184 write_sized_index(
3185 &mut out,
3186 soft_body.material_index,
3187 model.metadata.index_sizes.material,
3188 );
3189 out.push(soft_body.collision_group);
3190 write_u16(&mut out, soft_body.collision_mask);
3191 out.push(soft_body.flags);
3192 write_i32(&mut out, soft_body.bending_constraints_distance);
3193 write_i32(&mut out, soft_body.cluster_count);
3194 write_f32(&mut out, soft_body.total_mass);
3195 write_f32(&mut out, soft_body.collision_margin);
3196 out.extend_from_slice(&[0u8; 4 + 12 * 4 + 6 * 4 + 4 * 4 + 3 * 4]);
3197 write_i32(&mut out, 0);
3198 write_i32(&mut out, 0);
3199 }
3200 }
3201
3202 out
3203}
3204
3205fn read_section_count(r: &mut Reader<'_>) -> Result<usize, ImportError> {
3206 let count = r.read_i32_le()?;
3207 if count < 0 {
3208 return Err(ImportError::SectionOverflow);
3209 }
3210 Ok(count as usize)
3211}
3212
3213fn read_section_count_with_min_record(
3214 r: &mut Reader<'_>,
3215 min_record_size: usize,
3216) -> Result<usize, ImportError> {
3217 let count = read_section_count(r)?;
3218 r.require_record_bytes(count, min_record_size)?;
3219 Ok(count)
3220}
3221
3222fn read_parsed_geometry(
3223 r: &mut Reader<'_>,
3224 header: &PmxHeader,
3225 vertex_count: usize,
3226) -> Result<PmxParsedGeometry, ImportError> {
3227 r.require_record_bytes(vertex_count, 12 + 12 + 8 + 1 + 4)?;
3228 let position_capacity = vertex_count
3229 .checked_mul(3)
3230 .ok_or(ImportError::SectionOverflow)?;
3231 let uv_capacity = vertex_count
3232 .checked_mul(2)
3233 .ok_or(ImportError::SectionOverflow)?;
3234 let skin_capacity = vertex_count
3235 .checked_mul(4)
3236 .ok_or(ImportError::SectionOverflow)?;
3237
3238 let mut positions = Vec::with_capacity(position_capacity);
3239 let mut normals = Vec::with_capacity(position_capacity);
3240 let mut uvs = Vec::with_capacity(uv_capacity);
3241 let mut additional_uvs =
3242 vec![Vec::with_capacity(skin_capacity); header.extra_uv_count as usize];
3243 let mut skin_indices = Vec::with_capacity(skin_capacity);
3244 let mut skin_weights = Vec::with_capacity(skin_capacity);
3245 let mut skinning_modes = Vec::with_capacity(vertex_count);
3246 let mut edge_scale = Vec::with_capacity(vertex_count);
3247 let mut sdef_enabled = vec![0.0f32; vertex_count];
3248 let mut sdef_c = vec![0.0f32; vertex_count * 3];
3249 let mut sdef_r0 = vec![0.0f32; vertex_count * 3];
3250 let mut sdef_r1 = vec![0.0f32; vertex_count * 3];
3251 let mut sdef_rw0 = vec![0.0f32; vertex_count * 3];
3252 let mut sdef_rw1 = vec![0.0f32; vertex_count * 3];
3253 let mut qdef_enabled = vec![0.0f32; vertex_count];
3254
3255 for vertex_i in 0..vertex_count {
3256 positions.extend_from_slice(&r.read_vec3_array()?);
3257 normals.extend_from_slice(&r.read_vec3_array()?);
3258 uvs.extend_from_slice(&r.read_vec2_array()?);
3259 for uv in additional_uvs.iter_mut() {
3260 uv.extend_from_slice(&r.read_vec4_array()?);
3261 }
3262 let weight_type = r.read_u8()?;
3263 let mut indices = [0u32; 4];
3264 let mut weights = [0.0f32; 4];
3265 match weight_type {
3266 0 => {
3267 skinning_modes.push(PMX_SKINNING_MODE_BDEF1.to_owned());
3268 indices[0] =
3269 normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
3270 weights[0] = 1.0;
3271 }
3272 1 | 3 => {
3273 skinning_modes.push(
3274 if weight_type == 3 {
3275 PMX_SKINNING_MODE_SDEF
3276 } else {
3277 PMX_SKINNING_MODE_BDEF2
3278 }
3279 .to_owned(),
3280 );
3281 indices[0] =
3282 normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
3283 indices[1] =
3284 normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
3285 let w = r.read_f32_le()?;
3286 weights[0] = w;
3287 weights[1] = 1.0 - w;
3288 if weight_type == 3 {
3289 let c = r.read_vec3_array()?;
3290 let r0 = r.read_vec3_array()?;
3291 let r1 = r.read_vec3_array()?;
3292 let w1 = 1.0 - w;
3293 let offset = vertex_i * 3;
3294 sdef_enabled[vertex_i] = 1.0;
3295 sdef_c[offset..offset + 3].copy_from_slice(&c);
3296 sdef_r0[offset..offset + 3].copy_from_slice(&r0);
3297 sdef_r1[offset..offset + 3].copy_from_slice(&r1);
3298 for k in 0..3 {
3299 let rw = r0[k] * w + r1[k] * w1;
3300 sdef_rw0[offset + k] = (c[k] * 2.0 + r0[k] - rw) * 0.5;
3301 sdef_rw1[offset + k] = (c[k] * 2.0 + r1[k] - rw) * 0.5;
3302 }
3303 }
3304 }
3305 2 => {
3306 skinning_modes.push(PMX_SKINNING_MODE_BDEF4.to_owned());
3307 for index in &mut indices {
3308 *index =
3309 normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
3310 }
3311 for weight in &mut weights {
3312 *weight = r.read_f32_le()?;
3313 }
3314 }
3315 4 => {
3316 skinning_modes.push(PMX_SKINNING_MODE_QDEF.to_owned());
3317 for index in &mut indices {
3318 *index =
3319 normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
3320 }
3321 for weight in &mut weights {
3322 *weight = r.read_f32_le()?;
3323 }
3324 qdef_enabled[vertex_i] = 1.0;
3325 }
3326 _ => return Err(ImportError::SectionOverflow),
3327 }
3328 skin_indices.extend_from_slice(&indices);
3329 skin_weights.extend_from_slice(&weights);
3330 edge_scale.push(r.read_f32_le()?);
3331 }
3332
3333 let index_count = read_section_count_with_min_record(r, header.vertex_index_size as usize)?;
3334 let mut indices = Vec::with_capacity(index_count);
3335 for _ in 0..index_count {
3336 indices.push(r.read_vertex_index(header.vertex_index_size)?);
3337 }
3338
3339 Ok(PmxParsedGeometry {
3340 positions,
3341 normals,
3342 uvs,
3343 additional_uvs,
3344 indices,
3345 skin_indices,
3346 skin_weights,
3347 edge_scale,
3348 material_groups: Vec::new(),
3349 sdef: PmxParsedSdef {
3350 skinning_modes,
3351 enabled: sdef_enabled,
3352 c: sdef_c,
3353 r0: sdef_r0,
3354 r1: sdef_r1,
3355 rw0: sdef_rw0,
3356 rw1: sdef_rw1,
3357 },
3358 qdef: PmxParsedQdef {
3359 enabled: qdef_enabled,
3360 },
3361 })
3362}
3363
3364fn normalize_nonnegative_index(index: i32) -> u32 {
3365 if index < 0 { 0 } else { index as u32 }
3366}
3367
3368pub fn parse_pmx_material_split(
3369 data: &[u8],
3370 _flags: u32,
3371) -> Result<PmxMaterialSplitResult, ImportError> {
3372 let model = parse_pmx_model(data)?;
3373 Ok(split_pmx_model_by_material(&model))
3374}
3375
3376pub fn split_pmx_model_by_material(model: &PmxParsedModel) -> PmxMaterialSplitResult {
3377 let mut meshes = Vec::with_capacity(model.geometry.material_groups.len());
3378 let mut manifest_meshes = Vec::with_capacity(model.geometry.material_groups.len());
3379 let mut diagnostics = Vec::new();
3380
3381 for (mesh_index, group) in model.geometry.material_groups.iter().enumerate() {
3382 if group.material_index >= model.materials.len() {
3383 let diagnostic = pmx_material_split_diagnostic(
3384 "PMX_MATERIAL_SPLIT_MATERIAL_INDEX_OUT_OF_RANGE",
3385 format!(
3386 "material group {mesh_index} references material {} but material count is {}",
3387 group.material_index,
3388 model.materials.len()
3389 ),
3390 );
3391 diagnostics.push(diagnostic);
3392 continue;
3393 }
3394
3395 let mesh = split_pmx_material_group(model, group, mesh_index);
3396 manifest_meshes.push(PmxMaterialSplitManifestMesh {
3397 mesh_index,
3398 original_material_index: mesh.original_material_index,
3399 original_vertex_indices: mesh.original_vertex_indices.clone(),
3400 morph_index_map: mesh.morph_index_map.clone(),
3401 diagnostics: mesh.diagnostics.clone(),
3402 });
3403 diagnostics.extend(mesh.diagnostics.iter().cloned());
3404 meshes.push(mesh);
3405 }
3406
3407 PmxMaterialSplitResult {
3408 manifest: PmxMaterialSplitManifest {
3409 mesh_count: meshes.len(),
3410 meshes: manifest_meshes,
3411 diagnostics,
3412 },
3413 meshes,
3414 }
3415}
3416
3417fn split_pmx_material_group(
3418 model: &PmxParsedModel,
3419 group: &PmxParsedMaterialGroup,
3420 mesh_index: usize,
3421) -> PmxMaterialSplitMesh {
3422 let mut diagnostics = Vec::new();
3423 let index_end = group.start.saturating_add(group.count);
3424 let index_slice = match model.geometry.indices.get(group.start..index_end) {
3425 Some(slice) => slice,
3426 None => {
3427 diagnostics.push(pmx_material_split_diagnostic(
3428 "PMX_MATERIAL_SPLIT_INDEX_RANGE_OUT_OF_RANGE",
3429 format!(
3430 "material group {mesh_index} index range {}..{} exceeds index count {}",
3431 group.start,
3432 index_end,
3433 model.geometry.indices.len()
3434 ),
3435 ));
3436 &[][..]
3437 }
3438 };
3439
3440 let vertex_count = model.geometry.positions.len() / 3;
3441 let mut original_vertex_indices = Vec::new();
3442 let mut vertex_remap = HashMap::<u32, u32>::new();
3443 let mut local_indices = Vec::with_capacity(index_slice.len());
3444 for &original_index in index_slice {
3445 if original_index as usize >= vertex_count {
3446 diagnostics.push(pmx_material_split_diagnostic(
3447 "PMX_MATERIAL_SPLIT_VERTEX_INDEX_OUT_OF_RANGE",
3448 format!(
3449 "material group {mesh_index} references vertex {original_index} but vertex count is {vertex_count}"
3450 ),
3451 ));
3452 continue;
3453 }
3454 let local_index = match vertex_remap.get(&original_index) {
3455 Some(&local_index) => local_index,
3456 None => {
3457 let local_index = original_vertex_indices.len() as u32;
3458 vertex_remap.insert(original_index, local_index);
3459 original_vertex_indices.push(original_index);
3460 local_index
3461 }
3462 };
3463 local_indices.push(local_index);
3464 }
3465
3466 let local_index_count = local_indices.len();
3467 let geometry = remap_pmx_geometry(
3468 &model.geometry,
3469 &original_vertex_indices,
3470 local_indices,
3471 local_index_count,
3472 );
3473 let material = {
3474 let mut material = model.materials[group.material_index].clone();
3475 material.face_count = (geometry.indices.len() / 3) as i32;
3476 material
3477 };
3478 let (morphs, morph_index_map, morph_diagnostics) =
3479 remap_pmx_material_split_morphs(model, &vertex_remap, group.material_index, mesh_index);
3480 diagnostics.extend(morph_diagnostics);
3481
3482 PmxMaterialSplitMesh {
3483 original_material_index: group.material_index,
3484 original_vertex_indices,
3485 geometry,
3486 material,
3487 morphs,
3488 morph_index_map,
3489 diagnostics,
3490 }
3491}
3492
3493fn remap_pmx_geometry(
3494 geometry: &PmxParsedGeometry,
3495 original_vertex_indices: &[u32],
3496 indices: Vec<u32>,
3497 local_index_count: usize,
3498) -> PmxParsedGeometry {
3499 PmxParsedGeometry {
3500 positions: gather_vertex_f32(&geometry.positions, 3, original_vertex_indices),
3501 normals: gather_vertex_f32(&geometry.normals, 3, original_vertex_indices),
3502 uvs: gather_vertex_f32(&geometry.uvs, 2, original_vertex_indices),
3503 additional_uvs: geometry
3504 .additional_uvs
3505 .iter()
3506 .map(|uvs| gather_vertex_f32(uvs, 4, original_vertex_indices))
3507 .collect(),
3508 indices,
3509 skin_indices: gather_vertex_u32(&geometry.skin_indices, 4, original_vertex_indices),
3510 skin_weights: gather_vertex_f32(&geometry.skin_weights, 4, original_vertex_indices),
3511 edge_scale: gather_vertex_f32(&geometry.edge_scale, 1, original_vertex_indices),
3512 material_groups: vec![PmxParsedMaterialGroup {
3513 start: 0,
3514 count: local_index_count,
3515 material_index: 0,
3516 }],
3517 sdef: PmxParsedSdef {
3518 skinning_modes: gather_vertex_string(
3519 &geometry.sdef.skinning_modes,
3520 original_vertex_indices,
3521 ),
3522 enabled: gather_vertex_f32(&geometry.sdef.enabled, 1, original_vertex_indices),
3523 c: gather_vertex_f32(&geometry.sdef.c, 3, original_vertex_indices),
3524 r0: gather_vertex_f32(&geometry.sdef.r0, 3, original_vertex_indices),
3525 r1: gather_vertex_f32(&geometry.sdef.r1, 3, original_vertex_indices),
3526 rw0: gather_vertex_f32(&geometry.sdef.rw0, 3, original_vertex_indices),
3527 rw1: gather_vertex_f32(&geometry.sdef.rw1, 3, original_vertex_indices),
3528 },
3529 qdef: PmxParsedQdef {
3530 enabled: gather_vertex_f32(&geometry.qdef.enabled, 1, original_vertex_indices),
3531 },
3532 }
3533}
3534
3535fn gather_vertex_f32(values: &[f32], stride: usize, original_vertex_indices: &[u32]) -> Vec<f32> {
3536 let mut out = Vec::with_capacity(original_vertex_indices.len() * stride);
3537 for &index in original_vertex_indices {
3538 let start = index as usize * stride;
3539 let end = start + stride;
3540 if let Some(slice) = values.get(start..end) {
3541 out.extend_from_slice(slice);
3542 }
3543 }
3544 out
3545}
3546
3547fn gather_vertex_string(values: &[String], original_vertex_indices: &[u32]) -> Vec<String> {
3548 let mut out = Vec::with_capacity(original_vertex_indices.len());
3549 for &index in original_vertex_indices {
3550 if let Some(value) = values.get(index as usize) {
3551 out.push(value.clone());
3552 }
3553 }
3554 out
3555}
3556
3557fn gather_vertex_u32(values: &[u32], stride: usize, original_vertex_indices: &[u32]) -> Vec<u32> {
3558 let mut out = Vec::with_capacity(original_vertex_indices.len() * stride);
3559 for &index in original_vertex_indices {
3560 let start = index as usize * stride;
3561 let end = start + stride;
3562 if let Some(slice) = values.get(start..end) {
3563 out.extend_from_slice(slice);
3564 }
3565 }
3566 out
3567}
3568
3569fn remap_pmx_material_split_morphs(
3570 model: &PmxParsedModel,
3571 vertex_remap: &HashMap<u32, u32>,
3572 original_material_index: usize,
3573 mesh_index: usize,
3574) -> (
3575 Vec<PmxParsedMorph>,
3576 Vec<PmxMaterialSplitMorphIndexMap>,
3577 Vec<PmxParserDiagnostic>,
3578) {
3579 let mut diagnostics = Vec::new();
3580 let mut direct_morphs = vec![None; model.morphs.len()];
3581 let mut survives = vec![false; model.morphs.len()];
3582 let mut morph_remap = HashMap::<usize, usize>::new();
3583
3584 for (original_index, morph) in model.morphs.iter().enumerate() {
3585 match morph.kind.as_str() {
3586 "vertex" | "uv" | "additionalUv" | "material" => {
3587 let remapped = remap_direct_pmx_material_split_morph(
3588 morph,
3589 vertex_remap,
3590 original_material_index,
3591 );
3592 if pmx_material_split_morph_has_offsets(&remapped) {
3593 direct_morphs[original_index] = Some(remapped);
3594 survives[original_index] = true;
3595 }
3596 }
3597 "bone" => {
3598 if !morph.bone_offsets.is_empty() {
3599 diagnostics.push(pmx_material_split_diagnostic(
3600 "PMX_MATERIAL_SPLIT_BONE_MORPH_SHARED_SKELETON",
3601 format!(
3602 "mesh {mesh_index} excludes bone morph {original_index} because skeleton morphs are shared"
3603 ),
3604 ));
3605 }
3606 }
3607 "impulse" => {
3608 if !morph.impulse_offsets.is_empty() {
3609 diagnostics.push(pmx_material_split_diagnostic(
3610 "PMX_MATERIAL_SPLIT_IMPULSE_MORPH_UNSUPPORTED",
3611 format!(
3612 "mesh {mesh_index} excludes impulse morph {original_index} because physics morphs are not exposed in the initial material split ABI"
3613 ),
3614 ));
3615 }
3616 }
3617 "group" | "flip" => {}
3618 _ => {
3619 diagnostics.push(pmx_material_split_diagnostic(
3620 "PMX_MATERIAL_SPLIT_MORPH_KIND_UNSUPPORTED",
3621 format!(
3622 "mesh {mesh_index} excludes morph {original_index} with unsupported kind {}",
3623 morph.kind
3624 ),
3625 ));
3626 }
3627 }
3628 }
3629
3630 let mut changed = true;
3631 while changed {
3632 changed = false;
3633 for (original_index, morph) in model.morphs.iter().enumerate() {
3634 if survives[original_index] || (morph.kind != "group" && morph.kind != "flip") {
3635 continue;
3636 }
3637 if pmx_material_split_reference_offsets(morph)
3638 .iter()
3639 .any(|offset| {
3640 offset.morph_index >= 0
3641 && survives
3642 .get(offset.morph_index as usize)
3643 .copied()
3644 .unwrap_or(false)
3645 })
3646 {
3647 survives[original_index] = true;
3648 changed = true;
3649 }
3650 }
3651 }
3652
3653 for (original_index, survives) in survives.iter().copied().enumerate() {
3654 if survives {
3655 let local_index = morph_remap.len();
3656 morph_remap.insert(original_index, local_index);
3657 }
3658 }
3659
3660 let mut morphs = Vec::with_capacity(morph_remap.len());
3661 for (original_index, morph) in model.morphs.iter().enumerate() {
3662 if !morph_remap.contains_key(&original_index) {
3663 continue;
3664 }
3665 let remapped = match direct_morphs[original_index].take() {
3666 Some(remapped) => remapped,
3667 None => remap_reference_pmx_material_split_morph(morph, &morph_remap),
3668 };
3669 morphs.push((original_index, remapped));
3670 }
3671
3672 let morph_index_map = morphs
3673 .iter()
3674 .enumerate()
3675 .map(
3676 |(local_index, (original_index, _))| PmxMaterialSplitMorphIndexMap {
3677 original_index: *original_index,
3678 local_index,
3679 },
3680 )
3681 .collect();
3682 let morphs = morphs
3683 .into_iter()
3684 .map(|(_, morph)| morph)
3685 .collect::<Vec<_>>();
3686
3687 (morphs, morph_index_map, diagnostics)
3688}
3689
3690fn remap_direct_pmx_material_split_morph(
3691 morph: &PmxParsedMorph,
3692 vertex_remap: &HashMap<u32, u32>,
3693 original_material_index: usize,
3694) -> PmxParsedMorph {
3695 let mut remapped = empty_pmx_material_split_morph_like(morph);
3696 remapped.vertex_offsets = morph
3697 .vertex_offsets
3698 .iter()
3699 .filter_map(|offset| {
3700 vertex_remap
3701 .get(&offset.vertex_index)
3702 .map(|&vertex_index| PmxParsedVertexMorphOffset {
3703 vertex_index,
3704 position: offset.position,
3705 })
3706 })
3707 .collect();
3708 remapped.uv_offsets = morph
3709 .uv_offsets
3710 .iter()
3711 .filter_map(|offset| {
3712 vertex_remap
3713 .get(&offset.vertex_index)
3714 .map(|&vertex_index| PmxParsedUvMorphOffset {
3715 vertex_index,
3716 uv: offset.uv,
3717 })
3718 })
3719 .collect();
3720 remapped.additional_uv_offsets = morph
3721 .additional_uv_offsets
3722 .iter()
3723 .filter_map(|offset| {
3724 vertex_remap.get(&offset.vertex_index).map(|&vertex_index| {
3725 PmxParsedAdditionalUvMorphOffset {
3726 vertex_index,
3727 uv_index: offset.uv_index,
3728 uv: offset.uv,
3729 }
3730 })
3731 })
3732 .collect();
3733 remapped.material_offsets = morph
3734 .material_offsets
3735 .iter()
3736 .filter_map(|offset| {
3737 if offset.material_index == -1
3738 || offset.material_index as usize == original_material_index
3739 {
3740 let mut remapped = offset.clone();
3741 if remapped.material_index >= 0 {
3742 remapped.material_index = 0;
3743 }
3744 Some(remapped)
3745 } else {
3746 None
3747 }
3748 })
3749 .collect();
3750 remapped
3751}
3752
3753fn remap_reference_pmx_material_split_morph(
3754 morph: &PmxParsedMorph,
3755 morph_remap: &HashMap<usize, usize>,
3756) -> PmxParsedMorph {
3757 let mut remapped = empty_pmx_material_split_morph_like(morph);
3758 if morph.kind == "group" {
3759 remapped.group_offsets =
3760 remap_pmx_material_split_morph_refs(&morph.group_offsets, morph_remap);
3761 } else {
3762 remapped.flip_offsets =
3763 remap_pmx_material_split_morph_refs(&morph.flip_offsets, morph_remap);
3764 }
3765 remapped
3766}
3767
3768fn pmx_material_split_reference_offsets(morph: &PmxParsedMorph) -> &[PmxParsedGroupMorphOffset] {
3769 if morph.kind == "group" {
3770 &morph.group_offsets
3771 } else {
3772 &morph.flip_offsets
3773 }
3774}
3775
3776fn remap_pmx_material_split_morph_refs(
3777 offsets: &[PmxParsedGroupMorphOffset],
3778 morph_remap: &HashMap<usize, usize>,
3779) -> Vec<PmxParsedGroupMorphOffset> {
3780 offsets
3781 .iter()
3782 .filter_map(|offset| {
3783 if offset.morph_index < 0 {
3784 return None;
3785 }
3786 morph_remap
3787 .get(&(offset.morph_index as usize))
3788 .map(|&morph_index| PmxParsedGroupMorphOffset {
3789 morph_index: morph_index as i32,
3790 weight: offset.weight,
3791 })
3792 })
3793 .collect()
3794}
3795
3796fn empty_pmx_material_split_morph_like(morph: &PmxParsedMorph) -> PmxParsedMorph {
3797 PmxParsedMorph {
3798 name: morph.name.clone(),
3799 english_name: morph.english_name.clone(),
3800 kind: morph.kind.clone(),
3801 vertex_offsets: Vec::new(),
3802 group_offsets: Vec::new(),
3803 bone_offsets: Vec::new(),
3804 uv_offsets: Vec::new(),
3805 additional_uv_offsets: Vec::new(),
3806 material_offsets: Vec::new(),
3807 flip_offsets: Vec::new(),
3808 impulse_offsets: Vec::new(),
3809 }
3810}
3811
3812fn pmx_material_split_morph_has_offsets(morph: &PmxParsedMorph) -> bool {
3813 !morph.vertex_offsets.is_empty()
3814 || !morph.group_offsets.is_empty()
3815 || !morph.bone_offsets.is_empty()
3816 || !morph.uv_offsets.is_empty()
3817 || !morph.additional_uv_offsets.is_empty()
3818 || !morph.material_offsets.is_empty()
3819 || !morph.flip_offsets.is_empty()
3820 || !morph.impulse_offsets.is_empty()
3821}
3822
3823fn pmx_material_split_diagnostic(
3824 code: impl Into<String>,
3825 message: impl Into<String>,
3826) -> PmxParserDiagnostic {
3827 PmxParserDiagnostic {
3828 level: "info".to_owned(),
3829 code: code.into(),
3830 message: message.into(),
3831 }
3832}
3833
3834fn read_parsed_textures(
3835 r: &mut Reader<'_>,
3836 encoding: TextEncoding,
3837) -> Result<Vec<String>, ImportError> {
3838 let count = read_section_count_with_min_record(r, 4)?;
3839 let mut textures = Vec::with_capacity(count);
3840 for _ in 0..count {
3841 textures.push(r.read_string(encoding)?);
3842 }
3843 Ok(textures)
3844}
3845
3846fn read_parsed_materials(
3847 r: &mut Reader<'_>,
3848 header: &PmxHeader,
3849 textures: &[String],
3850) -> Result<Vec<PmxParsedMaterial>, ImportError> {
3851 let count = read_section_count_with_min_record(r, 4 + 4 + 16 + 16 + 12 + 1 + 16 + 4)?;
3852 let mut materials = Vec::with_capacity(count);
3853 for _ in 0..count {
3854 let name = r.read_string(header.encoding)?;
3855 let english_name = r.read_string(header.encoding)?;
3856 let diffuse = r.read_vec4_array()?;
3857 let specular = r.read_vec3_array()?;
3858 let specular_power = r.read_f32_le()?;
3859 let ambient = r.read_vec3_array()?;
3860 let flag_bits = r.read_u8()?;
3861 let edge_color = r.read_vec4_array()?;
3862 let edge_size = r.read_f32_le()?;
3863 let texture_index = r.read_sized_index(header.texture_index_size)?;
3864 let sphere_texture_index = r.read_sized_index(header.texture_index_size)?;
3865 let sphere_mode_raw = r.read_u8()?;
3866 let toon_flag = r.read_u8()?;
3867 let (toon_texture_path, shared_toon_index) = if toon_flag == 0 {
3868 let toon_texture_index = r.read_sized_index(header.texture_index_size)?;
3869 (texture_path(textures, toon_texture_index), None)
3870 } else {
3871 (String::new(), Some(r.read_u8()?))
3872 };
3873 let _memo = r.read_string(header.encoding)?;
3874 let face_count = r.read_i32_le()? / 3;
3875 materials.push(PmxParsedMaterial {
3876 name,
3877 english_name,
3878 texture_path: texture_path(textures, texture_index),
3879 sphere_texture_path: texture_path(textures, sphere_texture_index),
3880 sphere_mode: match sphere_mode_raw {
3881 1 => "multiply",
3882 2 => "add",
3883 3 => "subTexture",
3884 _ => "none",
3885 }
3886 .to_owned(),
3887 toon_texture_path,
3888 shared_toon_index,
3889 diffuse,
3890 specular,
3891 specular_power,
3892 ambient,
3893 edge_color,
3894 edge_size,
3895 flags: PmxParsedMaterialFlags {
3896 double_sided: flag_bits & 0x01 != 0,
3897 ground_shadow: flag_bits & 0x02 != 0,
3898 self_shadow_map: flag_bits & 0x04 != 0,
3899 self_shadow: flag_bits & 0x08 != 0,
3900 edge: flag_bits & 0x10 != 0,
3901 vertex_color: flag_bits & 0x20 != 0,
3902 point_draw: flag_bits & 0x40 != 0,
3903 line_draw: flag_bits & 0x80 != 0,
3904 },
3905 face_count,
3906 });
3907 }
3908 Ok(materials)
3909}
3910
3911fn texture_path(textures: &[String], index: i32) -> String {
3912 if index < 0 {
3913 String::new()
3914 } else {
3915 textures.get(index as usize).cloned().unwrap_or_default()
3916 }
3917}
3918
3919fn build_material_groups(materials: &[PmxParsedMaterial]) -> Vec<PmxParsedMaterialGroup> {
3920 let mut start = 0usize;
3921 materials
3922 .iter()
3923 .enumerate()
3924 .map(|(material_index, material)| {
3925 let count = (material.face_count.max(0) as usize) * 3;
3926 let group = PmxParsedMaterialGroup {
3927 start,
3928 count,
3929 material_index,
3930 };
3931 start += count;
3932 group
3933 })
3934 .collect()
3935}
3936
3937fn pmx_text_encoding(metadata: &PmxParsedMetadata) -> TextEncoding {
3938 match metadata.encoding.as_str() {
3939 "utf-16-le" => TextEncoding::Utf16Le,
3940 _ => TextEncoding::Utf8,
3941 }
3942}
3943
3944fn write_pmx_string(out: &mut Vec<u8>, text: &str, encoding: TextEncoding) {
3945 match encoding {
3946 TextEncoding::Utf8 => {
3947 write_i32(out, text.len() as i32);
3948 out.extend_from_slice(text.as_bytes());
3949 }
3950 TextEncoding::Utf16Le => {
3951 let bytes = text
3952 .encode_utf16()
3953 .flat_map(u16::to_le_bytes)
3954 .collect::<Vec<_>>();
3955 write_i32(out, bytes.len() as i32);
3956 out.extend_from_slice(&bytes);
3957 }
3958 }
3959}
3960
3961fn write_f32_slice(out: &mut Vec<u8>, values: &[f32]) {
3962 for &value in values {
3963 write_f32(out, value);
3964 }
3965}
3966
3967fn write_f32(out: &mut Vec<u8>, value: f32) {
3968 out.extend_from_slice(&value.to_le_bytes());
3969}
3970
3971fn write_i32(out: &mut Vec<u8>, value: i32) {
3972 out.extend_from_slice(&value.to_le_bytes());
3973}
3974
3975fn write_u16(out: &mut Vec<u8>, value: u16) {
3976 out.extend_from_slice(&value.to_le_bytes());
3977}
3978
3979fn write_sized_index(out: &mut Vec<u8>, value: i32, size: u8) {
3980 match size {
3981 1 => out.push(value as i8 as u8),
3982 2 => out.extend_from_slice(&(value as i16).to_le_bytes()),
3983 4 => out.extend_from_slice(&value.to_le_bytes()),
3984 _ => {}
3985 }
3986}
3987
3988fn write_vertex_index(out: &mut Vec<u8>, value: u32, size: u8) {
3989 match size {
3990 1 => out.push(value as u8),
3991 2 => out.extend_from_slice(&(value as u16).to_le_bytes()),
3992 4 => out.extend_from_slice(&(value as i32).to_le_bytes()),
3993 _ => {}
3994 }
3995}
3996
3997fn write_pmx_vertex_skinning(out: &mut Vec<u8>, model: &PmxParsedModel, vertex_index: usize) {
3998 match pmx_vertex_skinning_mode(&model.geometry, vertex_index) {
3999 PMX_SKINNING_MODE_BDEF1 => {
4000 out.push(0);
4001 write_pmx_vertex_bone_index(out, model, vertex_index, 0);
4002 }
4003 PMX_SKINNING_MODE_BDEF2 => {
4004 out.push(1);
4005 write_pmx_vertex_bone_index(out, model, vertex_index, 0);
4006 write_pmx_vertex_bone_index(out, model, vertex_index, 1);
4007 write_f32(
4008 out,
4009 pmx_vertex_skin_weight(&model.geometry, vertex_index, 0),
4010 );
4011 }
4012 PMX_SKINNING_MODE_SDEF => {
4013 out.push(3);
4014 write_pmx_vertex_bone_index(out, model, vertex_index, 0);
4015 write_pmx_vertex_bone_index(out, model, vertex_index, 1);
4016 write_f32(
4017 out,
4018 pmx_vertex_skin_weight(&model.geometry, vertex_index, 0),
4019 );
4020 write_f32_slice(
4021 out,
4022 &pmx_vertex_sdef_vec3(&model.geometry.sdef.c, vertex_index),
4023 );
4024 write_f32_slice(
4025 out,
4026 &pmx_vertex_sdef_vec3(&model.geometry.sdef.r0, vertex_index),
4027 );
4028 write_f32_slice(
4029 out,
4030 &pmx_vertex_sdef_vec3(&model.geometry.sdef.r1, vertex_index),
4031 );
4032 }
4033 PMX_SKINNING_MODE_QDEF => {
4034 out.push(4);
4035 write_pmx_vertex_bdef4_skinning(out, model, vertex_index);
4036 }
4037 _ => {
4038 out.push(2);
4039 write_pmx_vertex_bdef4_skinning(out, model, vertex_index);
4040 }
4041 }
4042}
4043
4044fn pmx_vertex_skinning_mode(geometry: &PmxParsedGeometry, vertex_index: usize) -> &str {
4045 if let Some(mode) = geometry.sdef.skinning_modes.get(vertex_index) {
4046 return mode.as_str();
4047 }
4048 if geometry
4049 .sdef
4050 .enabled
4051 .get(vertex_index)
4052 .copied()
4053 .unwrap_or(0.0)
4054 > 0.5
4055 {
4056 PMX_SKINNING_MODE_SDEF
4057 } else if geometry
4058 .qdef
4059 .enabled
4060 .get(vertex_index)
4061 .copied()
4062 .unwrap_or(0.0)
4063 > 0.5
4064 {
4065 PMX_SKINNING_MODE_QDEF
4066 } else {
4067 PMX_SKINNING_MODE_BDEF4
4068 }
4069}
4070
4071fn write_pmx_vertex_bdef4_skinning(out: &mut Vec<u8>, model: &PmxParsedModel, vertex_index: usize) {
4072 for slot in 0..4 {
4073 write_pmx_vertex_bone_index(out, model, vertex_index, slot);
4074 }
4075 for slot in 0..4 {
4076 write_f32(
4077 out,
4078 pmx_vertex_skin_weight(&model.geometry, vertex_index, slot),
4079 );
4080 }
4081}
4082
4083fn write_pmx_vertex_bone_index(
4084 out: &mut Vec<u8>,
4085 model: &PmxParsedModel,
4086 vertex_index: usize,
4087 slot: usize,
4088) {
4089 write_sized_index(
4090 out,
4091 model
4092 .geometry
4093 .skin_indices
4094 .get(vertex_index * 4 + slot)
4095 .copied()
4096 .unwrap_or(0) as i32,
4097 model.metadata.index_sizes.bone,
4098 );
4099}
4100
4101fn pmx_vertex_skin_weight(geometry: &PmxParsedGeometry, vertex_index: usize, slot: usize) -> f32 {
4102 geometry
4103 .skin_weights
4104 .get(vertex_index * 4 + slot)
4105 .copied()
4106 .unwrap_or(if slot == 0 { 1.0 } else { 0.0 })
4107}
4108
4109fn pmx_vertex_sdef_vec3(values: &[f32], vertex_index: usize) -> [f32; 3] {
4110 let start = vertex_index * 3;
4111 [
4112 values.get(start).copied().unwrap_or(0.0),
4113 values.get(start + 1).copied().unwrap_or(0.0),
4114 values.get(start + 2).copied().unwrap_or(0.0),
4115 ]
4116}
4117
4118fn material_flag_bits(flags: &PmxParsedMaterialFlags) -> u8 {
4119 u8::from(flags.double_sided)
4120 | (u8::from(flags.ground_shadow) << 1)
4121 | (u8::from(flags.self_shadow_map) << 2)
4122 | (u8::from(flags.self_shadow) << 3)
4123 | (u8::from(flags.edge) << 4)
4124 | (u8::from(flags.vertex_color) << 5)
4125 | (u8::from(flags.point_draw) << 6)
4126 | (u8::from(flags.line_draw) << 7)
4127}
4128
4129fn bone_flag_bits(flags: &PmxParsedBoneFlags) -> u16 {
4130 u16::from(flags.indexed_tail)
4131 | (u16::from(flags.rotatable) << 1)
4132 | (u16::from(flags.translatable) << 2)
4133 | (u16::from(flags.visible) << 3)
4134 | (u16::from(flags.enabled) << 4)
4135 | if flags.ik { BONE_FLAG_IK } else { 0 }
4136 | if flags.append_local {
4137 BONE_FLAG_LOCAL_APPEND
4138 } else {
4139 0
4140 }
4141 | if flags.append_rotate {
4142 BONE_FLAG_APPEND_ROTATE
4143 } else {
4144 0
4145 }
4146 | if flags.append_translate {
4147 BONE_FLAG_APPEND_TRANSLATE
4148 } else {
4149 0
4150 }
4151 | if flags.fixed_axis {
4152 BONE_FLAG_FIXED_AXIS
4153 } else {
4154 0
4155 }
4156 | if flags.local_axis {
4157 BONE_FLAG_LOCAL_AXIS
4158 } else {
4159 0
4160 }
4161 | if flags.transform_after_physics {
4162 BONE_FLAG_TRANSFORM_AFTER_PHYSICS
4163 } else {
4164 0
4165 }
4166 | if flags.external_parent_transform {
4167 BONE_FLAG_EXTERNAL_PARENT
4168 } else {
4169 0
4170 }
4171}
4172
4173fn collect_pmx_textures(materials: &[PmxParsedMaterial]) -> Vec<String> {
4174 let mut textures = Vec::new();
4175 for texture in materials.iter().flat_map(|material| {
4176 [
4177 material.texture_path.as_str(),
4178 material.sphere_texture_path.as_str(),
4179 material.toon_texture_path.as_str(),
4180 ]
4181 }) {
4182 if !texture.is_empty() && !textures.iter().any(|existing| existing == texture) {
4183 textures.push(texture.to_owned());
4184 }
4185 }
4186 textures
4187}
4188
4189fn texture_index(textures: &[String], texture: &str) -> i32 {
4190 if texture.is_empty() {
4191 -1
4192 } else {
4193 textures
4194 .iter()
4195 .position(|candidate| candidate == texture)
4196 .map(|index| index as i32)
4197 .unwrap_or(-1)
4198 }
4199}
4200
4201fn morph_type_byte(morph: &PmxParsedMorph) -> u8 {
4202 match morph.kind.as_str() {
4203 "group" => 0,
4204 "vertex" => 1,
4205 "bone" => 2,
4206 "uv" => 3,
4207 "additionalUv" => morph
4208 .additional_uv_offsets
4209 .first()
4210 .map(|offset| offset.uv_index.saturating_add(4).min(7))
4211 .unwrap_or(4),
4212 "material" => 8,
4213 "flip" => 9,
4214 "impulse" => 10,
4215 _ => 0,
4216 }
4217}
4218
4219fn morph_offset_count(morph: &PmxParsedMorph, morph_type: u8) -> usize {
4220 match morph_type {
4221 0 => morph.group_offsets.len(),
4222 1 => morph.vertex_offsets.len(),
4223 2 => morph.bone_offsets.len(),
4224 3 => morph.uv_offsets.len(),
4225 4..=7 => morph.additional_uv_offsets.len(),
4226 8 => morph.material_offsets.len(),
4227 9 => morph.flip_offsets.len(),
4228 10 => morph.impulse_offsets.len(),
4229 _ => 0,
4230 }
4231}
4232
4233fn write_group_morph_offsets(
4234 out: &mut Vec<u8>,
4235 offsets: &[PmxParsedGroupMorphOffset],
4236 model: &PmxParsedModel,
4237) {
4238 for offset in offsets {
4239 write_sized_index(out, offset.morph_index, model.metadata.index_sizes.morph);
4240 write_f32(out, offset.weight);
4241 }
4242}
4243
4244fn write_uv_morph_offsets(
4245 out: &mut Vec<u8>,
4246 offsets: &[PmxParsedUvMorphOffset],
4247 model: &PmxParsedModel,
4248) {
4249 for offset in offsets {
4250 write_vertex_index(out, offset.vertex_index, model.metadata.index_sizes.vertex);
4251 write_f32_slice(out, &offset.uv);
4252 }
4253}
4254
4255fn read_parsed_bones(
4256 r: &mut Reader<'_>,
4257 header: &PmxHeader,
4258 count: usize,
4259) -> Result<Vec<PmxParsedBone>, ImportError> {
4260 r.require_record_bytes(count, 4 + 4 + 12)?;
4261 let mut bones = Vec::with_capacity(count);
4262 for _ in 0..count {
4263 let name = r.read_string(header.encoding)?;
4264 let english_name = r.read_string(header.encoding)?;
4265 let position = r.read_vec3_array()?;
4266 let parent_index = r.read_sized_index(header.bone_index_size)?;
4267 let layer = r.read_i32_le()?;
4268 let bits = r.read_u16_le()?;
4269 let flags = parsed_bone_flags(bits);
4270 let (tail_index, tail_position) = if flags.indexed_tail {
4271 (r.read_sized_index(header.bone_index_size)?, None)
4272 } else {
4273 (-1, Some(r.read_vec3_array()?))
4274 };
4275 let append_transform = if flags.append_rotate || flags.append_translate {
4276 Some(PmxParsedAppendTransform {
4277 parent_index: r.read_sized_index(header.bone_index_size)?,
4278 weight: r.read_f32_le()?,
4279 })
4280 } else {
4281 None
4282 };
4283 let fixed_axis = if flags.fixed_axis {
4284 Some(r.read_vec3_array()?)
4285 } else {
4286 None
4287 };
4288 let local_axis = if flags.local_axis {
4289 Some(PmxParsedLocalAxis {
4290 x: r.read_vec3_array()?,
4291 z: r.read_vec3_array()?,
4292 })
4293 } else {
4294 None
4295 };
4296 let external_parent_key = if flags.external_parent_transform {
4297 Some(r.read_i32_le()?)
4298 } else {
4299 None
4300 };
4301 let ik = if flags.ik {
4302 let target_index = r.read_sized_index(header.bone_index_size)?;
4303 let loop_count = r.read_i32_le()?;
4304 let limit_angle = r.read_f32_le()?;
4305 let link_count =
4306 read_section_count_with_min_record(r, header.bone_index_size as usize + 1)?;
4307 let mut links = Vec::with_capacity(link_count);
4308 for _ in 0..link_count {
4309 let bone_index = r.read_sized_index(header.bone_index_size)?;
4310 let has_limit = r.read_u8()? != 0;
4311 links.push(PmxParsedIkLink {
4312 bone_index,
4313 limits: if has_limit {
4314 Some(PmxParsedIkLimit {
4315 lower: r.read_vec3_array()?,
4316 upper: r.read_vec3_array()?,
4317 })
4318 } else {
4319 None
4320 },
4321 });
4322 }
4323 Some(PmxParsedIk {
4324 target_index,
4325 loop_count,
4326 limit_angle,
4327 links,
4328 })
4329 } else {
4330 None
4331 };
4332 bones.push(PmxParsedBone {
4333 name,
4334 english_name,
4335 parent_index,
4336 layer,
4337 position,
4338 tail_index,
4339 tail_position,
4340 flags,
4341 append_transform,
4342 fixed_axis,
4343 local_axis,
4344 external_parent_key,
4345 ik,
4346 });
4347 }
4348 Ok(bones)
4349}
4350
4351fn parsed_bone_flags(bits: u16) -> PmxParsedBoneFlags {
4352 PmxParsedBoneFlags {
4353 indexed_tail: bits & BONE_FLAG_TAIL_INDEX != 0,
4354 rotatable: bits & 0x0002 != 0,
4355 translatable: bits & 0x0004 != 0,
4356 visible: bits & 0x0008 != 0,
4357 enabled: bits & 0x0010 != 0,
4358 ik: bits & BONE_FLAG_IK != 0,
4359 append_local: bits & BONE_FLAG_LOCAL_APPEND != 0,
4360 append_rotate: bits & BONE_FLAG_APPEND_ROTATE != 0,
4361 append_translate: bits & BONE_FLAG_APPEND_TRANSLATE != 0,
4362 fixed_axis: bits & BONE_FLAG_FIXED_AXIS != 0,
4363 local_axis: bits & BONE_FLAG_LOCAL_AXIS != 0,
4364 transform_after_physics: bits & 0x1000 != 0,
4365 external_parent_transform: bits & BONE_FLAG_EXTERNAL_PARENT != 0,
4366 }
4367}
4368
4369fn read_parsed_morphs(
4370 r: &mut Reader<'_>,
4371 header: &PmxHeader,
4372) -> Result<Vec<PmxParsedMorph>, ImportError> {
4373 let count = read_section_count_with_min_record(r, 4 + 4 + 1 + 1 + 4)?;
4374 let mut morphs = Vec::with_capacity(count);
4375 for _ in 0..count {
4376 let name = r.read_string(header.encoding)?;
4377 let english_name = r.read_string(header.encoding)?;
4378 let _panel = r.read_u8()?;
4379 let morph_type = r.read_u8()?;
4380 let min_offset_size = match morph_type {
4381 0 => header.morph_index_size as usize + 4,
4382 1 => header.vertex_index_size as usize + 12,
4383 2 => header.bone_index_size as usize + 12 + 16,
4384 3..=7 => header.vertex_index_size as usize + 16,
4385 8 => header.material_index_size as usize + 1 + 16 + 12 + 4 + 12 + 16 + 4 + 16 + 16 + 16,
4386 9 => header.morph_index_size as usize + 4,
4387 10 => header.rigidbody_index_size as usize + 1 + 12 + 12,
4388 _ => return Err(ImportError::SectionOverflow),
4389 };
4390 let offset_count = read_section_count_with_min_record(r, min_offset_size)?;
4391 let mut morph = PmxParsedMorph {
4392 name,
4393 english_name,
4394 kind: match morph_type {
4395 0 => "group",
4396 1 => "vertex",
4397 2 => "bone",
4398 3 => "uv",
4399 4..=7 => "additionalUv",
4400 8 => "material",
4401 9 => "flip",
4402 10 => "impulse",
4403 _ => "unknown",
4404 }
4405 .to_owned(),
4406 vertex_offsets: Vec::new(),
4407 group_offsets: Vec::new(),
4408 bone_offsets: Vec::new(),
4409 uv_offsets: Vec::new(),
4410 additional_uv_offsets: Vec::new(),
4411 material_offsets: Vec::new(),
4412 flip_offsets: Vec::new(),
4413 impulse_offsets: Vec::new(),
4414 };
4415 for _ in 0..offset_count {
4416 match morph_type {
4417 0 => morph.group_offsets.push(PmxParsedGroupMorphOffset {
4418 morph_index: r.read_sized_index(header.morph_index_size)?,
4419 weight: r.read_f32_le()?,
4420 }),
4421 1 => morph.vertex_offsets.push(PmxParsedVertexMorphOffset {
4422 vertex_index: r.read_vertex_index(header.vertex_index_size)?,
4423 position: r.read_vec3_array()?,
4424 }),
4425 2 => morph.bone_offsets.push(PmxParsedBoneMorphOffset {
4426 bone_index: r.read_sized_index(header.bone_index_size)?,
4427 translation: r.read_vec3_array()?,
4428 rotation: r.read_vec4_array()?,
4429 }),
4430 3 => morph.uv_offsets.push(PmxParsedUvMorphOffset {
4431 vertex_index: r.read_vertex_index(header.vertex_index_size)?,
4432 uv: r.read_vec4_array()?,
4433 }),
4434 4..=7 => morph
4435 .additional_uv_offsets
4436 .push(PmxParsedAdditionalUvMorphOffset {
4437 vertex_index: r.read_vertex_index(header.vertex_index_size)?,
4438 uv_index: morph_type - 4,
4439 uv: r.read_vec4_array()?,
4440 }),
4441 8 => morph.material_offsets.push(PmxParsedMaterialMorphOffset {
4442 material_index: r.read_sized_index(header.material_index_size)?,
4443 operation: if r.read_u8()? == 0 {
4444 "multiply".to_owned()
4445 } else {
4446 "add".to_owned()
4447 },
4448 diffuse: r.read_vec4_array()?,
4449 specular: r.read_vec3_array()?,
4450 specular_power: r.read_f32_le()?,
4451 ambient: r.read_vec3_array()?,
4452 edge_color: r.read_vec4_array()?,
4453 edge_size: r.read_f32_le()?,
4454 texture_factor: r.read_vec4_array()?,
4455 sphere_texture_factor: r.read_vec4_array()?,
4456 toon_texture_factor: r.read_vec4_array()?,
4457 }),
4458 9 => morph.flip_offsets.push(PmxParsedGroupMorphOffset {
4459 morph_index: r.read_sized_index(header.morph_index_size)?,
4460 weight: r.read_f32_le()?,
4461 }),
4462 10 => morph.impulse_offsets.push(PmxParsedImpulseMorphOffset {
4463 rigid_body_index: r.read_sized_index(header.rigidbody_index_size)?,
4464 local: r.read_u8()? != 0,
4465 velocity: r.read_vec3_array()?,
4466 torque: r.read_vec3_array()?,
4467 }),
4468 _ => return Err(ImportError::SectionOverflow),
4469 }
4470 }
4471 morphs.push(morph);
4472 }
4473 Ok(morphs)
4474}
4475
4476fn read_parsed_display_frames(
4477 r: &mut Reader<'_>,
4478 header: &PmxHeader,
4479) -> Result<Vec<PmxParsedDisplayFrame>, ImportError> {
4480 let count = read_section_count_with_min_record(r, 4 + 4 + 1 + 4)?;
4481 let mut frames = Vec::with_capacity(count);
4482 for _ in 0..count {
4483 let name = r.read_string(header.encoding)?;
4484 let english_name = r.read_string(header.encoding)?;
4485 let special = r.read_u8()? == 1;
4486 let item_count = read_section_count_with_min_record(r, 1)?;
4487 let mut items = Vec::with_capacity(item_count);
4488 for _ in 0..item_count {
4489 let kind = r.read_u8()?;
4490 let index = match kind {
4491 0 => r.read_sized_index(header.bone_index_size)?,
4492 1 => r.read_sized_index(header.morph_index_size)?,
4493 _ => return Err(ImportError::SectionOverflow),
4494 };
4495 items.push(PmxParsedDisplayFrameElement {
4496 kind: if kind == 0 {
4497 "bone".to_owned()
4498 } else {
4499 "morph".to_owned()
4500 },
4501 index,
4502 });
4503 }
4504 frames.push(PmxParsedDisplayFrame {
4505 name,
4506 english_name,
4507 special,
4508 frames: items,
4509 });
4510 }
4511 Ok(frames)
4512}
4513
4514fn read_parsed_rigid_bodies(
4515 r: &mut Reader<'_>,
4516 header: &PmxHeader,
4517) -> Result<Vec<PmxParsedRigidBody>, ImportError> {
4518 let count = read_section_count_with_min_record(
4519 r,
4520 4 + 4 + header.bone_index_size as usize + 1 + 2 + 1 + 12 + 12 + 12 + 4 + 4 + 4 + 4 + 4 + 1,
4521 )?;
4522 let mut bodies = Vec::with_capacity(count);
4523 for _ in 0..count {
4524 bodies.push(PmxParsedRigidBody {
4525 name: r.read_string(header.encoding)?,
4526 english_name: r.read_string(header.encoding)?,
4527 bone_index: r.read_sized_index(header.bone_index_size)?,
4528 group: r.read_u8()?,
4529 mask: r.read_u16_le()?,
4530 shape: match r.read_u8()? {
4531 0 => "sphere",
4532 1 => "box",
4533 2 => "capsule",
4534 _ => "unknown",
4535 }
4536 .to_owned(),
4537 size: r.read_vec3_array()?,
4538 position: r.read_vec3_array()?,
4539 rotation: r.read_vec3_array()?,
4540 mass: r.read_f32_le()?,
4541 linear_damping: r.read_f32_le()?,
4542 angular_damping: r.read_f32_le()?,
4543 restitution: r.read_f32_le()?,
4544 friction: r.read_f32_le()?,
4545 mode: match r.read_u8()? {
4546 0 => "static",
4547 1 => "dynamic",
4548 2 => "dynamicBone",
4549 _ => "unknown",
4550 }
4551 .to_owned(),
4552 });
4553 }
4554 Ok(bodies)
4555}
4556
4557fn read_parsed_joints(
4558 r: &mut Reader<'_>,
4559 header: &PmxHeader,
4560) -> Result<Vec<PmxParsedJoint>, ImportError> {
4561 let count = read_section_count_with_min_record(
4562 r,
4563 4 + 4 + 1 + header.rigidbody_index_size as usize * 2 + 12 * 8,
4564 )?;
4565 let mut joints = Vec::with_capacity(count);
4566 for _ in 0..count {
4567 joints.push(PmxParsedJoint {
4568 name: r.read_string(header.encoding)?,
4569 english_name: r.read_string(header.encoding)?,
4570 kind: match r.read_u8()? {
4571 0 => "generic6dofSpring",
4572 1 => "generic6dof",
4573 2 => "point2point",
4574 3 => "coneTwist",
4575 4 => "slider",
4576 5 => "hinge",
4577 _ => "unknown",
4578 }
4579 .to_owned(),
4580 rigid_body_index_a: r.read_sized_index(header.rigidbody_index_size)?,
4581 rigid_body_index_b: r.read_sized_index(header.rigidbody_index_size)?,
4582 position: r.read_vec3_array()?,
4583 rotation: r.read_vec3_array()?,
4584 translation_lower_limit: r.read_vec3_array()?,
4585 translation_upper_limit: r.read_vec3_array()?,
4586 rotation_lower_limit: r.read_vec3_array()?,
4587 rotation_upper_limit: r.read_vec3_array()?,
4588 spring_translation_factor: r.read_vec3_array()?,
4589 spring_rotation_factor: r.read_vec3_array()?,
4590 });
4591 }
4592 Ok(joints)
4593}
4594
4595fn read_parsed_soft_bodies(
4596 r: &mut Reader<'_>,
4597 header: &PmxHeader,
4598) -> Result<Vec<PmxParsedSoftBody>, ImportError> {
4599 let count = read_section_count_with_min_record(
4600 r,
4601 4 + 4
4602 + 1
4603 + header.material_index_size as usize
4604 + 1
4605 + 2
4606 + 1
4607 + 4
4608 + 4
4609 + 4
4610 + 4
4611 + 4
4612 + 12 * 4
4613 + 6 * 4
4614 + 4 * 4
4615 + 3 * 4
4616 + 4
4617 + 4,
4618 )?;
4619 let mut soft_bodies = Vec::with_capacity(count);
4620 for _ in 0..count {
4621 let name = r.read_string(header.encoding)?;
4622 let english_name = r.read_string(header.encoding)?;
4623 let kind = match r.read_u8()? {
4624 0 => "triMesh",
4625 1 => "rope",
4626 _ => "unknown",
4627 }
4628 .to_owned();
4629 let material_index = r.read_sized_index(header.material_index_size)?;
4630 let collision_group = r.read_u8()?;
4631 let collision_mask = r.read_u16_le()?;
4632 let flags = r.read_u8()?;
4633 let bending_constraints_distance = r.read_i32_le()?;
4634 let cluster_count = r.read_i32_le()?;
4635 let total_mass = r.read_f32_le()?;
4636 let collision_margin = r.read_f32_le()?;
4637 r.skip(4 + 12 * 4 + 6 * 4 + 4 * 4 + 3 * 4)?;
4638 let anchor_count = read_section_count_with_min_record(
4639 r,
4640 header.rigidbody_index_size as usize + header.vertex_index_size as usize + 1,
4641 )?;
4642 for _ in 0..anchor_count {
4643 r.read_sized_index(header.rigidbody_index_size)?;
4644 r.read_vertex_index(header.vertex_index_size)?;
4645 r.skip(1)?;
4646 }
4647 let pin_count = read_section_count_with_min_record(r, header.vertex_index_size as usize)?;
4648 for _ in 0..pin_count {
4649 r.read_vertex_index(header.vertex_index_size)?;
4650 }
4651 soft_bodies.push(PmxParsedSoftBody {
4652 name,
4653 english_name,
4654 kind,
4655 material_index,
4656 collision_group,
4657 collision_mask,
4658 flags,
4659 bending_constraints_distance,
4660 cluster_count,
4661 total_mass,
4662 collision_margin,
4663 });
4664 }
4665 Ok(soft_bodies)
4666}
4667
4668#[derive(Debug)]
4669pub struct PmxRuntimeImport {
4670 pub model: ModelArena,
4671 pub bone_names: Vec<String>,
4672 pub bone_name_to_index: HashMap<Vec<u8>, BoneIndex>,
4673 pub morph_name_to_index: HashMap<Vec<u8>, MorphIndex>,
4674 pub ik_solver_bone_name_to_index: HashMap<Vec<u8>, usize>,
4675}
4676
4677pub fn import_pmx_runtime(data: &[u8]) -> Result<PmxRuntimeImport, ImportError> {
4678 let (header, pos) = read_header(data)?;
4679 let pos = skip_model_info(data, &header, pos)?;
4680 let pos = skip_vertices(data, &header, pos)?;
4681 let pos = skip_faces(data, header.vertex_index_size, pos)?;
4682 let pos = skip_textures(data, header.encoding, pos)?;
4683 let pos = skip_materials(data, &header, pos)?;
4684 let (bone_import, pos) = read_bones(data, &header, pos)?;
4685 let (morph_names, morph_init, _pos) = read_morph_offsets(data, &header, pos)?;
4686
4687 let mut bone_name_to_index = HashMap::with_capacity(bone_import.bone_name_bytes.len() * 2);
4688 for (i, bytes) in bone_import.bone_name_bytes.iter().enumerate() {
4689 let index = BoneIndex(i as u32);
4690 bone_name_to_index.insert(bytes.clone(), index);
4691 let decoded = bone_import.bone_names[i].as_bytes();
4692 if decoded != bytes.as_slice() && !decoded.is_empty() {
4693 bone_name_to_index.insert(decoded.to_vec(), index);
4694 }
4695 }
4696
4697 let mut morph_name_to_index = HashMap::with_capacity(morph_names.name_bytes.len() * 2);
4698 for (i, bytes) in morph_names.name_bytes.iter().enumerate() {
4699 let index = MorphIndex(i as u32);
4700 morph_name_to_index.insert(bytes.clone(), index);
4701 let decoded = morph_names.names[i].as_bytes();
4702 if decoded != bytes.as_slice() && !decoded.is_empty() {
4703 morph_name_to_index.insert(decoded.to_vec(), index);
4704 }
4705 }
4706
4707 let mut ik_solver_bone_name_to_index = HashMap::with_capacity(bone_import.ik_solvers.len() * 2);
4708 for (solver_idx, solver) in bone_import.ik_solvers.iter().enumerate() {
4709 let bone_idx = solver.ik_bone.as_usize();
4710 if bone_idx < bone_import.bone_name_bytes.len() {
4711 let bytes = &bone_import.bone_name_bytes[bone_idx];
4712 ik_solver_bone_name_to_index.insert(bytes.clone(), solver_idx);
4713 let decoded = bone_import.bone_names[bone_idx].as_bytes();
4714 if decoded != bytes.as_slice() && !decoded.is_empty() {
4715 ik_solver_bone_name_to_index.insert(decoded.to_vec(), solver_idx);
4716 }
4717 }
4718 }
4719
4720 let model = ModelArena::new_with_morphs(
4721 bone_import.bones,
4722 bone_import.ik_solvers,
4723 bone_import.append_transforms,
4724 morph_init,
4725 )
4726 .map_err(ImportError::ModelBuildFailed)?;
4727
4728 Ok(PmxRuntimeImport {
4729 model,
4730 bone_names: bone_import.bone_names,
4731 bone_name_to_index,
4732 morph_name_to_index,
4733 ik_solver_bone_name_to_index,
4734 })
4735}
4736
4737trait AppendTransformInitExt {
4738 fn with_rotation_if(self, on: bool) -> Self;
4739 fn with_translation_if(self, on: bool) -> Self;
4740 fn with_local_if(self, on: bool) -> Self;
4741}
4742
4743impl AppendTransformInitExt for AppendTransformInit {
4744 fn with_rotation_if(mut self, on: bool) -> Self {
4745 if on {
4746 self.affect_rotation = true;
4747 }
4748 self
4749 }
4750
4751 fn with_translation_if(mut self, on: bool) -> Self {
4752 if on {
4753 self.affect_translation = true;
4754 }
4755 self
4756 }
4757
4758 fn with_local_if(mut self, on: bool) -> Self {
4759 if on {
4760 self.local = true;
4761 }
4762 self
4763 }
4764}
4765
4766#[cfg(test)]
4767mod tests {
4768 use super::*;
4769 use glam::Vec3;
4770 use mmd_anim_runtime::RuntimeInstance;
4771 use std::sync::Arc;
4772
4773 fn build_small_pmx_header_bytes(bone_index_size: u8, encoding: TextEncoding) -> Vec<u8> {
4774 let mut buf = Vec::new();
4775 buf.extend_from_slice(b"PMX ");
4776 buf.extend_from_slice(&2.0f32.to_le_bytes());
4777 buf.push(8);
4778 buf.push(encoding as u8);
4779 buf.push(0);
4780 buf.push(4);
4781 buf.push(1);
4782 buf.push(1);
4783 buf.push(bone_index_size);
4784 buf.push(1);
4785 buf.push(1);
4786 buf
4787 }
4788
4789 fn build_empty_model_info(_encoding: TextEncoding) -> Vec<u8> {
4790 let mut buf = Vec::new();
4791 for _ in 0..4 {
4792 buf.extend_from_slice(&0i32.to_le_bytes());
4793 }
4794 buf
4795 }
4796
4797 fn build_empty_vertex_section() -> Vec<u8> {
4798 vec![0u8, 0, 0, 0]
4799 }
4800
4801 fn build_empty_face_section() -> Vec<u8> {
4802 vec![0u8, 0, 0, 0]
4803 }
4804
4805 fn build_empty_texture_section() -> Vec<u8> {
4806 vec![0u8, 0, 0, 0]
4807 }
4808
4809 fn build_empty_material_section() -> Vec<u8> {
4810 vec![0u8, 0, 0, 0]
4811 }
4812
4813 #[test]
4814 fn rejects_impossible_pmx_vertex_count_before_allocation() {
4815 let mut buf = build_small_pmx_header_bytes(1, TextEncoding::Utf8);
4816 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4817 buf.extend_from_slice(&i32::MAX.to_le_bytes());
4818
4819 assert!(matches!(
4820 parse_pmx_model(&buf),
4821 Err(ImportError::UnexpectedEof(_))
4822 ));
4823 assert!(matches!(
4824 import_pmx_runtime(&buf),
4825 Err(ImportError::UnexpectedEof(_))
4826 ));
4827 }
4828
4829 #[test]
4830 fn rejects_invalid_pmx_index_sizes_in_header() {
4831 let buf = build_small_pmx_header_bytes(3, TextEncoding::Utf8);
4832
4833 assert!(matches!(
4834 read_header(&buf),
4835 Err(ImportError::InvalidIndexSize(3))
4836 ));
4837 }
4838
4839 fn build_bone_name_bytes(name: &str) -> Vec<u8> {
4840 let mut buf = Vec::new();
4841 let name_bytes = name.as_bytes();
4842 buf.extend_from_slice(&(name_bytes.len() as i32).to_le_bytes());
4843 buf.extend_from_slice(name_bytes);
4844 buf
4845 }
4846
4847 fn build_bone_section_header(count: u32) -> Vec<u8> {
4848 let mut buf = Vec::new();
4849 buf.extend_from_slice(&count.to_le_bytes());
4850 buf
4851 }
4852
4853 fn build_morph_name_bytes(name: &str) -> Vec<u8> {
4854 let mut buf = Vec::new();
4855 let name_bytes = name.as_bytes();
4856 buf.extend_from_slice(&(name_bytes.len() as i32).to_le_bytes());
4857 buf.extend_from_slice(name_bytes);
4858 buf
4859 }
4860
4861 fn empty_pmx_flags() -> PmxParsedMaterialFlags {
4862 PmxParsedMaterialFlags {
4863 double_sided: false,
4864 ground_shadow: false,
4865 self_shadow_map: false,
4866 self_shadow: false,
4867 edge: false,
4868 vertex_color: false,
4869 point_draw: false,
4870 line_draw: false,
4871 }
4872 }
4873
4874 fn basic_bone_flags(indexed_tail: bool) -> PmxParsedBoneFlags {
4875 PmxParsedBoneFlags {
4876 indexed_tail,
4877 rotatable: true,
4878 translatable: false,
4879 visible: true,
4880 enabled: true,
4881 ik: false,
4882 append_local: false,
4883 append_rotate: false,
4884 append_translate: false,
4885 fixed_axis: false,
4886 local_axis: false,
4887 transform_after_physics: false,
4888 external_parent_transform: false,
4889 }
4890 }
4891
4892 fn parsed_pmx_fixture() -> PmxParsedModel {
4893 PmxParsedModel {
4894 metadata: PmxParsedMetadata {
4895 format: "pmx".to_owned(),
4896 version: 2.0,
4897 encoding: "utf-8".to_owned(),
4898 name: "model".to_owned(),
4899 english_name: "model-en".to_owned(),
4900 comment: "comment".to_owned(),
4901 english_comment: "comment-en".to_owned(),
4902 counts: PmxParsedCounts {
4903 vertices: 1,
4904 faces: 1,
4905 materials: 1,
4906 bones: 1,
4907 morphs: 1,
4908 display_frames: 1,
4909 rigid_bodies: 1,
4910 joints: 1,
4911 soft_bodies: 0,
4912 },
4913 index_sizes: PmxParsedIndexSizes {
4914 vertex: 4,
4915 texture: 1,
4916 material: 1,
4917 bone: 2,
4918 morph: 1,
4919 rigid_body: 1,
4920 },
4921 additional_uv_count: 1,
4922 },
4923 geometry: PmxParsedGeometry {
4924 positions: vec![1.0, 2.0, 3.0],
4925 normals: vec![0.0, 1.0, 0.0],
4926 uvs: vec![0.25, 0.75],
4927 additional_uvs: vec![vec![1.0, 2.0, 3.0, 4.0]],
4928 indices: vec![0, 0, 0],
4929 skin_indices: vec![0, 0, 0, 0],
4930 skin_weights: vec![1.0, 0.0, 0.0, 0.0],
4931 edge_scale: vec![1.25],
4932 material_groups: vec![PmxParsedMaterialGroup {
4933 start: 0,
4934 count: 3,
4935 material_index: 0,
4936 }],
4937 sdef: PmxParsedSdef {
4938 skinning_modes: vec![PMX_SKINNING_MODE_BDEF4.to_owned()],
4939 enabled: vec![0.0],
4940 c: vec![0.0, 0.0, 0.0],
4941 r0: vec![0.0, 0.0, 0.0],
4942 r1: vec![0.0, 0.0, 0.0],
4943 rw0: vec![0.0, 0.0, 0.0],
4944 rw1: vec![0.0, 0.0, 0.0],
4945 },
4946 qdef: PmxParsedQdef { enabled: vec![0.0] },
4947 },
4948 materials: vec![PmxParsedMaterial {
4949 name: "mat".to_owned(),
4950 english_name: "mat-en".to_owned(),
4951 texture_path: "tex.png".to_owned(),
4952 sphere_texture_path: "sphere.spa".to_owned(),
4953 sphere_mode: "add".to_owned(),
4954 toon_texture_path: "toon.bmp".to_owned(),
4955 shared_toon_index: None,
4956 diffuse: [0.1, 0.2, 0.3, 0.4],
4957 specular: [0.5, 0.6, 0.7],
4958 specular_power: 8.0,
4959 ambient: [0.8, 0.9, 1.0],
4960 edge_color: [0.0, 0.1, 0.2, 0.3],
4961 edge_size: 1.5,
4962 flags: PmxParsedMaterialFlags {
4963 double_sided: true,
4964 edge: true,
4965 ..empty_pmx_flags()
4966 },
4967 face_count: 1,
4968 }],
4969 skeleton: PmxParsedSkeleton {
4970 bones: vec![PmxParsedBone {
4971 name: "Root".to_owned(),
4972 english_name: "RootEn".to_owned(),
4973 parent_index: -1,
4974 layer: 0,
4975 position: [0.0, 1.0, 2.0],
4976 tail_index: -1,
4977 tail_position: Some([0.0, 2.0, 0.0]),
4978 flags: basic_bone_flags(false),
4979 append_transform: None,
4980 fixed_axis: None,
4981 local_axis: None,
4982 external_parent_key: None,
4983 ik: None,
4984 }],
4985 },
4986 morphs: vec![PmxParsedMorph {
4987 name: "Smile".to_owned(),
4988 english_name: "SmileEn".to_owned(),
4989 kind: "vertex".to_owned(),
4990 vertex_offsets: vec![PmxParsedVertexMorphOffset {
4991 vertex_index: 0,
4992 position: [0.1, 0.2, 0.3],
4993 }],
4994 group_offsets: Vec::new(),
4995 bone_offsets: Vec::new(),
4996 uv_offsets: Vec::new(),
4997 additional_uv_offsets: Vec::new(),
4998 material_offsets: Vec::new(),
4999 flip_offsets: Vec::new(),
5000 impulse_offsets: Vec::new(),
5001 }],
5002 display_frames: vec![PmxParsedDisplayFrame {
5003 name: "RootFrame".to_owned(),
5004 english_name: "RootFrameEn".to_owned(),
5005 special: true,
5006 frames: vec![
5007 PmxParsedDisplayFrameElement {
5008 kind: "bone".to_owned(),
5009 index: 0,
5010 },
5011 PmxParsedDisplayFrameElement {
5012 kind: "morph".to_owned(),
5013 index: 0,
5014 },
5015 ],
5016 }],
5017 rigid_bodies: vec![PmxParsedRigidBody {
5018 name: "body".to_owned(),
5019 english_name: "body-en".to_owned(),
5020 bone_index: 0,
5021 group: 1,
5022 mask: 2,
5023 shape: "box".to_owned(),
5024 size: [1.0, 2.0, 3.0],
5025 position: [4.0, 5.0, 6.0],
5026 rotation: [0.1, 0.2, 0.3],
5027 mass: 10.0,
5028 linear_damping: 0.4,
5029 angular_damping: 0.5,
5030 restitution: 0.6,
5031 friction: 0.7,
5032 mode: "dynamicBone".to_owned(),
5033 }],
5034 joints: vec![PmxParsedJoint {
5035 name: "joint".to_owned(),
5036 english_name: "joint-en".to_owned(),
5037 kind: "generic6dofSpring".to_owned(),
5038 rigid_body_index_a: 0,
5039 rigid_body_index_b: -1,
5040 position: [1.0, 1.1, 1.2],
5041 rotation: [2.0, 2.1, 2.2],
5042 translation_lower_limit: [-1.0, -1.1, -1.2],
5043 translation_upper_limit: [1.0, 1.1, 1.2],
5044 rotation_lower_limit: [-0.1, -0.2, -0.3],
5045 rotation_upper_limit: [0.1, 0.2, 0.3],
5046 spring_translation_factor: [3.0, 3.1, 3.2],
5047 spring_rotation_factor: [4.0, 4.1, 4.2],
5048 }],
5049 soft_bodies: Vec::new(),
5050 diagnostics: Vec::new(),
5051 }
5052 }
5053
5054 fn material_morph_offset(material_index: i32) -> PmxParsedMaterialMorphOffset {
5055 PmxParsedMaterialMorphOffset {
5056 material_index,
5057 operation: "add".to_owned(),
5058 diffuse: [0.1, 0.2, 0.3, 0.4],
5059 specular: [0.5, 0.6, 0.7],
5060 specular_power: 0.8,
5061 ambient: [0.9, 1.0, 1.1],
5062 edge_color: [0.0, 0.1, 0.2, 0.3],
5063 edge_size: 0.4,
5064 texture_factor: [1.0, 0.0, 0.0, 1.0],
5065 sphere_texture_factor: [0.0, 1.0, 0.0, 1.0],
5066 toon_texture_factor: [0.0, 0.0, 1.0, 1.0],
5067 }
5068 }
5069
5070 fn material_split_fixture() -> PmxParsedModel {
5071 let mut model = parsed_pmx_fixture();
5072 model.metadata.counts.vertices = 4;
5073 model.metadata.counts.faces = 2;
5074 model.metadata.counts.materials = 2;
5075 model.metadata.counts.morphs = 5;
5076 model.metadata.additional_uv_count = 1;
5077 model.geometry = PmxParsedGeometry {
5078 positions: vec![
5079 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0,
5083 ],
5084 normals: vec![
5085 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
5089 ],
5090 uvs: vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0],
5091 additional_uvs: vec![vec![
5092 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0,
5096 ]],
5097 indices: vec![0, 1, 2, 2, 3, 0],
5098 skin_indices: vec![
5099 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5103 ],
5104 skin_weights: vec![
5105 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
5109 ],
5110 edge_scale: vec![1.0, 1.1, 1.2, 1.3],
5111 material_groups: vec![
5112 PmxParsedMaterialGroup {
5113 start: 0,
5114 count: 3,
5115 material_index: 0,
5116 },
5117 PmxParsedMaterialGroup {
5118 start: 3,
5119 count: 3,
5120 material_index: 1,
5121 },
5122 ],
5123 sdef: PmxParsedSdef {
5124 skinning_modes: vec![
5125 PMX_SKINNING_MODE_BDEF4.to_owned(),
5126 PMX_SKINNING_MODE_SDEF.to_owned(),
5127 PMX_SKINNING_MODE_BDEF4.to_owned(),
5128 PMX_SKINNING_MODE_QDEF.to_owned(),
5129 ],
5130 enabled: vec![0.0, 1.0, 0.0, 0.0],
5131 c: vec![0.0; 12],
5132 r0: vec![0.0; 12],
5133 r1: vec![0.0; 12],
5134 rw0: vec![0.0; 12],
5135 rw1: vec![0.0; 12],
5136 },
5137 qdef: PmxParsedQdef {
5138 enabled: vec![0.0, 0.0, 0.0, 1.0],
5139 },
5140 };
5141 let mut material_b = model.materials[0].clone();
5142 material_b.name = "mat-b".to_owned();
5143 material_b.english_name = "mat-b-en".to_owned();
5144 model.materials = vec![model.materials[0].clone(), material_b];
5145 model.morphs = vec![
5146 PmxParsedMorph {
5147 name: "left-raise".to_owned(),
5148 english_name: "left-raise-en".to_owned(),
5149 kind: "vertex".to_owned(),
5150 vertex_offsets: vec![PmxParsedVertexMorphOffset {
5151 vertex_index: 1,
5152 position: [0.0, 0.5, 0.0],
5153 }],
5154 group_offsets: Vec::new(),
5155 bone_offsets: Vec::new(),
5156 uv_offsets: Vec::new(),
5157 additional_uv_offsets: Vec::new(),
5158 material_offsets: Vec::new(),
5159 flip_offsets: Vec::new(),
5160 impulse_offsets: Vec::new(),
5161 },
5162 PmxParsedMorph {
5163 name: "right-raise".to_owned(),
5164 english_name: "right-raise-en".to_owned(),
5165 kind: "vertex".to_owned(),
5166 vertex_offsets: vec![PmxParsedVertexMorphOffset {
5167 vertex_index: 3,
5168 position: [0.0, 0.75, 0.0],
5169 }],
5170 group_offsets: Vec::new(),
5171 bone_offsets: Vec::new(),
5172 uv_offsets: Vec::new(),
5173 additional_uv_offsets: Vec::new(),
5174 material_offsets: Vec::new(),
5175 flip_offsets: Vec::new(),
5176 impulse_offsets: Vec::new(),
5177 },
5178 PmxParsedMorph {
5179 name: "right-material".to_owned(),
5180 english_name: "right-material-en".to_owned(),
5181 kind: "material".to_owned(),
5182 vertex_offsets: Vec::new(),
5183 group_offsets: Vec::new(),
5184 bone_offsets: Vec::new(),
5185 uv_offsets: Vec::new(),
5186 additional_uv_offsets: Vec::new(),
5187 material_offsets: vec![material_morph_offset(1)],
5188 flip_offsets: Vec::new(),
5189 impulse_offsets: Vec::new(),
5190 },
5191 PmxParsedMorph {
5192 name: "all-material".to_owned(),
5193 english_name: "all-material-en".to_owned(),
5194 kind: "material".to_owned(),
5195 vertex_offsets: Vec::new(),
5196 group_offsets: Vec::new(),
5197 bone_offsets: Vec::new(),
5198 uv_offsets: Vec::new(),
5199 additional_uv_offsets: Vec::new(),
5200 material_offsets: vec![material_morph_offset(-1)],
5201 flip_offsets: Vec::new(),
5202 impulse_offsets: Vec::new(),
5203 },
5204 PmxParsedMorph {
5205 name: "combo".to_owned(),
5206 english_name: "combo-en".to_owned(),
5207 kind: "group".to_owned(),
5208 vertex_offsets: Vec::new(),
5209 group_offsets: vec![
5210 PmxParsedGroupMorphOffset {
5211 morph_index: 0,
5212 weight: 0.25,
5213 },
5214 PmxParsedGroupMorphOffset {
5215 morph_index: 1,
5216 weight: 0.5,
5217 },
5218 PmxParsedGroupMorphOffset {
5219 morph_index: 2,
5220 weight: 0.75,
5221 },
5222 PmxParsedGroupMorphOffset {
5223 morph_index: 3,
5224 weight: 1.0,
5225 },
5226 ],
5227 bone_offsets: Vec::new(),
5228 uv_offsets: Vec::new(),
5229 additional_uv_offsets: Vec::new(),
5230 material_offsets: Vec::new(),
5231 flip_offsets: Vec::new(),
5232 impulse_offsets: Vec::new(),
5233 },
5234 ];
5235 model
5236 }
5237
5238 fn ik_multi_axis_limit_pmx_fixture() -> &'static [u8] {
5239 include_bytes!("../../fixtures/pmx/ik_multi_axis_limit.pmx")
5240 }
5241
5242 fn assert_pmx_roundtrip_eq(left: &PmxParsedModel, right: &PmxParsedModel) {
5243 assert_eq!(
5244 serde_json::to_value(left).unwrap(),
5245 serde_json::to_value(right).unwrap()
5246 );
5247 }
5248
5249 fn json_keys(value: &serde_json::Value) -> Vec<String> {
5250 let mut keys = value
5251 .as_object()
5252 .unwrap()
5253 .keys()
5254 .map(ToOwned::to_owned)
5255 .collect::<Vec<_>>();
5256 keys.sort();
5257 keys
5258 }
5259
5260 #[test]
5261 fn pmx_material_split_remaps_geometry_and_prunes_morphs() {
5262 let model = material_split_fixture();
5263 let split = split_pmx_model_by_material(&model);
5264
5265 assert_eq!(split.meshes.len(), 2);
5266 assert_eq!(split.manifest.mesh_count, 2);
5267 assert_eq!(split.manifest.meshes[0].original_material_index, 0);
5268 assert_eq!(
5269 split.manifest.meshes[0].original_vertex_indices,
5270 vec![0, 1, 2]
5271 );
5272 assert_eq!(split.manifest.meshes[1].original_material_index, 1);
5273 assert_eq!(
5274 split.manifest.meshes[1].original_vertex_indices,
5275 vec![2, 3, 0]
5276 );
5277
5278 let left = &split.meshes[0];
5279 assert_eq!(left.geometry.indices, vec![0, 1, 2]);
5280 assert_eq!(
5281 left.geometry.positions,
5282 vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]
5283 );
5284 assert_eq!(left.geometry.uvs, vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0]);
5285 assert_eq!(left.geometry.edge_scale, vec![1.0, 1.1, 1.2]);
5286 assert_eq!(left.geometry.sdef.enabled, vec![0.0, 1.0, 0.0]);
5287 assert_eq!(left.geometry.qdef.enabled, vec![0.0, 0.0, 0.0]);
5288 assert_eq!(
5289 left.morph_index_map,
5290 vec![
5291 PmxMaterialSplitMorphIndexMap {
5292 original_index: 0,
5293 local_index: 0,
5294 },
5295 PmxMaterialSplitMorphIndexMap {
5296 original_index: 3,
5297 local_index: 1,
5298 },
5299 PmxMaterialSplitMorphIndexMap {
5300 original_index: 4,
5301 local_index: 2,
5302 },
5303 ]
5304 );
5305 assert_eq!(left.morphs.len(), 3);
5306 assert_eq!(left.morphs[0].name, "left-raise");
5307 assert_eq!(left.morphs[0].vertex_offsets[0].vertex_index, 1);
5308 assert_eq!(left.morphs[1].name, "all-material");
5309 assert_eq!(left.morphs[1].material_offsets[0].material_index, -1);
5310 assert_eq!(left.morphs[2].name, "combo");
5311 assert_eq!(
5312 left.morphs[2]
5313 .group_offsets
5314 .iter()
5315 .map(|offset| (offset.morph_index, offset.weight))
5316 .collect::<Vec<_>>(),
5317 vec![(0, 0.25), (1, 1.0)]
5318 );
5319
5320 let right = &split.meshes[1];
5321 assert_eq!(right.geometry.indices, vec![0, 1, 2]);
5322 assert_eq!(
5323 right.geometry.positions,
5324 vec![0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0]
5325 );
5326 assert_eq!(right.geometry.uvs, vec![0.0, 1.0, 1.0, 1.0, 0.0, 0.0]);
5327 assert_eq!(right.geometry.edge_scale, vec![1.2, 1.3, 1.0]);
5328 assert_eq!(right.geometry.sdef.enabled, vec![0.0, 0.0, 0.0]);
5329 assert_eq!(right.geometry.qdef.enabled, vec![0.0, 1.0, 0.0]);
5330 assert_eq!(
5331 right.morph_index_map,
5332 vec![
5333 PmxMaterialSplitMorphIndexMap {
5334 original_index: 1,
5335 local_index: 0,
5336 },
5337 PmxMaterialSplitMorphIndexMap {
5338 original_index: 2,
5339 local_index: 1,
5340 },
5341 PmxMaterialSplitMorphIndexMap {
5342 original_index: 3,
5343 local_index: 2,
5344 },
5345 PmxMaterialSplitMorphIndexMap {
5346 original_index: 4,
5347 local_index: 3,
5348 },
5349 ]
5350 );
5351 assert_eq!(right.morphs.len(), 4);
5352 assert_eq!(right.morphs[0].name, "right-raise");
5353 assert_eq!(right.morphs[0].vertex_offsets[0].vertex_index, 1);
5354 assert_eq!(right.morphs[1].name, "right-material");
5355 assert_eq!(right.morphs[1].material_offsets[0].material_index, 0);
5356 assert_eq!(right.morphs[2].name, "all-material");
5357 assert_eq!(right.morphs[2].material_offsets[0].material_index, -1);
5358 assert_eq!(
5359 right.morphs[3]
5360 .group_offsets
5361 .iter()
5362 .map(|offset| (offset.morph_index, offset.weight))
5363 .collect::<Vec<_>>(),
5364 vec![(0, 0.5), (1, 0.75), (2, 1.0)]
5365 );
5366 }
5367
5368 #[test]
5369 fn pmx_material_split_preserves_forward_group_morph_references() {
5370 let mut model = material_split_fixture();
5371 model.morphs.push(PmxParsedMorph {
5372 name: "forward-combo".to_owned(),
5373 english_name: "forward-combo-en".to_owned(),
5374 kind: "group".to_owned(),
5375 vertex_offsets: Vec::new(),
5376 group_offsets: vec![PmxParsedGroupMorphOffset {
5377 morph_index: 6,
5378 weight: 0.8,
5379 }],
5380 bone_offsets: Vec::new(),
5381 uv_offsets: Vec::new(),
5382 additional_uv_offsets: Vec::new(),
5383 material_offsets: Vec::new(),
5384 flip_offsets: Vec::new(),
5385 impulse_offsets: Vec::new(),
5386 });
5387 model.morphs.push(PmxParsedMorph {
5388 name: "later-combo".to_owned(),
5389 english_name: "later-combo-en".to_owned(),
5390 kind: "group".to_owned(),
5391 vertex_offsets: Vec::new(),
5392 group_offsets: vec![PmxParsedGroupMorphOffset {
5393 morph_index: 0,
5394 weight: 0.4,
5395 }],
5396 bone_offsets: Vec::new(),
5397 uv_offsets: Vec::new(),
5398 additional_uv_offsets: Vec::new(),
5399 material_offsets: Vec::new(),
5400 flip_offsets: Vec::new(),
5401 impulse_offsets: Vec::new(),
5402 });
5403 model.metadata.counts.morphs = model.morphs.len();
5404
5405 let split = split_pmx_model_by_material(&model);
5406 let left = &split.meshes[0];
5407
5408 assert_eq!(
5409 left.morph_index_map,
5410 vec![
5411 PmxMaterialSplitMorphIndexMap {
5412 original_index: 0,
5413 local_index: 0,
5414 },
5415 PmxMaterialSplitMorphIndexMap {
5416 original_index: 3,
5417 local_index: 1,
5418 },
5419 PmxMaterialSplitMorphIndexMap {
5420 original_index: 4,
5421 local_index: 2,
5422 },
5423 PmxMaterialSplitMorphIndexMap {
5424 original_index: 5,
5425 local_index: 3,
5426 },
5427 PmxMaterialSplitMorphIndexMap {
5428 original_index: 6,
5429 local_index: 4,
5430 },
5431 ]
5432 );
5433 assert_eq!(left.morphs[3].name, "forward-combo");
5434 assert_eq!(
5435 left.morphs[3]
5436 .group_offsets
5437 .iter()
5438 .map(|offset| (offset.morph_index, offset.weight))
5439 .collect::<Vec<_>>(),
5440 vec![(4, 0.8)]
5441 );
5442 assert_eq!(left.morphs[4].name, "later-combo");
5443 assert_eq!(
5444 left.morphs[4]
5445 .group_offsets
5446 .iter()
5447 .map(|offset| (offset.morph_index, offset.weight))
5448 .collect::<Vec<_>>(),
5449 vec![(0, 0.4)]
5450 );
5451 }
5452
5453 #[test]
5454 fn pmx_model_json_top_level_schema_is_stable() {
5455 let parsed = parsed_pmx_fixture();
5456 let keys = json_keys(&serde_json::to_value(&parsed).unwrap());
5457
5458 assert_eq!(
5459 keys,
5460 vec![
5461 "diagnostics",
5462 "displayFrames",
5463 "geometry",
5464 "joints",
5465 "materials",
5466 "metadata",
5467 "morphs",
5468 "rigidBodies",
5469 "skeleton",
5470 "softBodies",
5471 ]
5472 );
5473 }
5474
5475 #[test]
5476 fn parses_pmx_header() {
5477 let header_bytes = build_small_pmx_header_bytes(4, TextEncoding::Utf8);
5478 let (header, pos) = read_header(&header_bytes).unwrap();
5479
5480 assert_eq!(header.version, 2.0);
5481 assert_eq!(header.encoding, TextEncoding::Utf8);
5482 assert_eq!(header.bone_index_size, 4);
5483 assert_eq!(header.vertex_index_size, 4);
5484 assert!(pos > 0);
5485 }
5486
5487 #[test]
5488 fn pmx_bone_flag_values_match_reference_layout() {
5489 assert_eq!(BONE_FLAG_TAIL_INDEX, 0x0001);
5490 assert_eq!(BONE_FLAG_IK, 0x0020);
5491 assert_eq!(BONE_FLAG_LOCAL_APPEND, 0x0080);
5492 assert_eq!(BONE_FLAG_APPEND_ROTATE, 0x0100);
5493 assert_eq!(BONE_FLAG_APPEND_TRANSLATE, 0x0200);
5494 assert_eq!(BONE_FLAG_FIXED_AXIS, 0x0400);
5495 assert_eq!(BONE_FLAG_LOCAL_AXIS, 0x0800);
5496 assert_eq!(BONE_FLAG_TRANSFORM_AFTER_PHYSICS, 0x1000);
5497 assert_eq!(BONE_FLAG_EXTERNAL_PARENT, 0x2000);
5498 }
5499
5500 #[test]
5501 fn parses_ik_multi_axis_limit_pmx_fixture() {
5502 let parsed = parse_pmx_model(ik_multi_axis_limit_pmx_fixture()).unwrap();
5503 assert_eq!(parsed.metadata.name, "ik_multi_axis_limit_fixture");
5504 assert_eq!(parsed.metadata.counts.bones, 3);
5505 assert_eq!(parsed.skeleton.bones[2].name, "ik_controller");
5506 let ik = parsed.skeleton.bones[2].ik.as_ref().unwrap();
5507 assert_eq!(ik.target_index, 1);
5508 assert_eq!(ik.loop_count, 1);
5509 assert_eq!(ik.links.len(), 1);
5510 assert_eq!(ik.links[0].bone_index, 0);
5511 let limits = ik.links[0].limits.as_ref().unwrap();
5512 assert_eq!(limits.lower, [0.0, -1.0, -1.0]);
5513 assert_eq!(limits.upper, [0.0, 1.0, 1.0]);
5514
5515 let runtime = import_pmx_runtime(ik_multi_axis_limit_pmx_fixture()).unwrap();
5516 assert_eq!(runtime.model.bone_count(), 3);
5517 assert_eq!(runtime.model.ik_count(), 1);
5518 let solver = &runtime.model.ik_solvers()[0];
5519 assert_eq!(solver.ik_bone.as_usize(), 2);
5520 assert_eq!(solver.target_bone.as_usize(), 1);
5521 let limit = solver.links[0].angle_limit.unwrap();
5522 assert_eq!(limit.min.to_array(), [0.0, -1.0, -1.0]);
5523 assert_eq!(limit.max.to_array(), [0.0, 1.0, 1.0]);
5524 }
5525
5526 #[test]
5527 fn rejects_bad_magic() {
5528 let mut header_bytes = build_small_pmx_header_bytes(4, TextEncoding::Utf8);
5529 header_bytes[0] = 0xFF;
5530 assert_eq!(
5531 read_header(&header_bytes).unwrap_err(),
5532 ImportError::InvalidPmxMagic
5533 );
5534 }
5535
5536 #[test]
5537 fn reads_single_bone_no_parent() {
5538 let mut buf = Vec::new();
5539 buf.extend_from_slice(&build_small_pmx_header_bytes(4, TextEncoding::Utf8));
5540 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5541 buf.extend_from_slice(&build_empty_vertex_section());
5542 buf.extend_from_slice(&build_empty_face_section());
5543 buf.extend_from_slice(&build_empty_texture_section());
5544 buf.extend_from_slice(&build_empty_material_section());
5545 buf.extend_from_slice(&build_bone_section_header(1));
5546
5547 buf.extend_from_slice(&build_bone_name_bytes("Root"));
5548 buf.extend_from_slice(&build_bone_name_bytes(""));
5549 buf.extend_from_slice(&0.0f32.to_le_bytes());
5550 buf.extend_from_slice(&1.0f32.to_le_bytes());
5551 buf.extend_from_slice(&2.0f32.to_le_bytes());
5552 buf.extend_from_slice(&(-1i32).to_le_bytes());
5553 buf.extend_from_slice(&0i32.to_le_bytes());
5554 buf.extend_from_slice(&0u16.to_le_bytes());
5555 buf.extend_from_slice(&0.0f32.to_le_bytes());
5556 buf.extend_from_slice(&0.0f32.to_le_bytes());
5557 buf.extend_from_slice(&0.0f32.to_le_bytes());
5558
5559 let result = import_pmx_model(&buf).unwrap();
5560 assert_eq!(result.bones.len(), 1);
5561 assert_eq!(result.bone_names.len(), 1);
5562 assert_eq!(result.bone_names[0], "Root");
5563 assert_eq!(&result.bone_name_bytes[0][..], b"Root");
5564 assert_eq!(result.bones[0].parent, None);
5565 assert!((result.bones[0].rest_position.x - 0.0).abs() < 0.001);
5566 assert!((result.bones[0].rest_position.y - 1.0).abs() < 0.001);
5567 assert!((result.bones[0].rest_position.z - 2.0).abs() < 0.001);
5568 assert!(result.ik_solvers.is_empty());
5569 assert!(result.append_transforms.is_empty());
5570 }
5571
5572 #[test]
5573 fn reads_bone_with_parent() {
5574 let mut buf = Vec::new();
5575 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5576 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5577 buf.extend_from_slice(&build_empty_vertex_section());
5578 buf.extend_from_slice(&build_empty_face_section());
5579 buf.extend_from_slice(&build_empty_texture_section());
5580 buf.extend_from_slice(&build_empty_material_section());
5581 buf.extend_from_slice(&build_bone_section_header(2));
5582
5583 buf.extend_from_slice(&build_bone_name_bytes("Root"));
5584 buf.extend_from_slice(&build_bone_name_bytes(""));
5585 buf.extend_from_slice(&0.0f32.to_le_bytes());
5586 buf.extend_from_slice(&0.0f32.to_le_bytes());
5587 buf.extend_from_slice(&0.0f32.to_le_bytes());
5588 buf.extend_from_slice(&(-1i16).to_le_bytes());
5589 buf.extend_from_slice(&0i32.to_le_bytes());
5590 buf.extend_from_slice(&0u16.to_le_bytes());
5591 buf.extend_from_slice(&0.0f32.to_le_bytes());
5592 buf.extend_from_slice(&0.0f32.to_le_bytes());
5593 buf.extend_from_slice(&0.0f32.to_le_bytes());
5594
5595 buf.extend_from_slice(&build_bone_name_bytes("Child"));
5596 buf.extend_from_slice(&build_bone_name_bytes(""));
5597 buf.extend_from_slice(&1.0f32.to_le_bytes());
5598 buf.extend_from_slice(&0.0f32.to_le_bytes());
5599 buf.extend_from_slice(&0.0f32.to_le_bytes());
5600 buf.extend_from_slice(&0i16.to_le_bytes());
5601 buf.extend_from_slice(&0i32.to_le_bytes());
5602 buf.extend_from_slice(&0u16.to_le_bytes());
5603 buf.extend_from_slice(&0.0f32.to_le_bytes());
5604 buf.extend_from_slice(&0.0f32.to_le_bytes());
5605 buf.extend_from_slice(&0.0f32.to_le_bytes());
5606
5607 let result = import_pmx_model(&buf).unwrap();
5608 assert_eq!(result.bones.len(), 2);
5609 assert_eq!(result.bone_names[0], "Root");
5610 assert_eq!(result.bone_names[1], "Child");
5611 assert_eq!(result.bones[0].parent, None);
5612 assert_eq!(result.bones[1].parent, Some(BoneIndex(0)));
5613 }
5614
5615 #[test]
5616 fn reads_bone_with_append_transform() {
5617 let mut buf = Vec::new();
5618 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5619 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5620 buf.extend_from_slice(&build_empty_vertex_section());
5621 buf.extend_from_slice(&build_empty_face_section());
5622 buf.extend_from_slice(&build_empty_texture_section());
5623 buf.extend_from_slice(&build_empty_material_section());
5624 buf.extend_from_slice(&build_bone_section_header(2));
5625
5626 buf.extend_from_slice(&build_bone_name_bytes("Root"));
5627 buf.extend_from_slice(&build_bone_name_bytes(""));
5628 buf.extend_from_slice(&0.0f32.to_le_bytes());
5629 buf.extend_from_slice(&0.0f32.to_le_bytes());
5630 buf.extend_from_slice(&0.0f32.to_le_bytes());
5631 buf.extend_from_slice(&(-1i16).to_le_bytes());
5632 buf.extend_from_slice(&0i32.to_le_bytes());
5633 buf.extend_from_slice(&0u16.to_le_bytes());
5634 buf.extend_from_slice(&0.0f32.to_le_bytes());
5635 buf.extend_from_slice(&0.0f32.to_le_bytes());
5636 buf.extend_from_slice(&0.0f32.to_le_bytes());
5637
5638 buf.extend_from_slice(&build_bone_name_bytes("AppendBone"));
5639 buf.extend_from_slice(&build_bone_name_bytes(""));
5640 buf.extend_from_slice(&1.0f32.to_le_bytes());
5641 buf.extend_from_slice(&0.0f32.to_le_bytes());
5642 buf.extend_from_slice(&0.0f32.to_le_bytes());
5643 buf.extend_from_slice(&0i16.to_le_bytes());
5644 buf.extend_from_slice(&0i32.to_le_bytes());
5645 buf.extend_from_slice(
5646 &(BONE_FLAG_LOCAL_APPEND | BONE_FLAG_APPEND_ROTATE | BONE_FLAG_APPEND_TRANSLATE)
5647 .to_le_bytes(),
5648 );
5649 buf.extend_from_slice(&0.0f32.to_le_bytes());
5650 buf.extend_from_slice(&0.0f32.to_le_bytes());
5651 buf.extend_from_slice(&0.0f32.to_le_bytes());
5652 buf.extend_from_slice(&0i16.to_le_bytes());
5653 buf.extend_from_slice(&0.5f32.to_le_bytes());
5654
5655 let result = import_pmx_model(&buf).unwrap();
5656 assert_eq!(result.bones.len(), 2);
5657 assert_eq!(result.append_transforms.len(), 1);
5658 assert_eq!(result.append_transforms[0].target_bone, BoneIndex(1));
5659 assert_eq!(result.append_transforms[0].source_bone, BoneIndex(0));
5660 assert!((result.append_transforms[0].ratio - 0.5).abs() < 0.001);
5661 assert!(result.append_transforms[0].affect_rotation);
5662 assert!(result.append_transforms[0].affect_translation);
5663 assert!(result.append_transforms[0].local);
5664 }
5665
5666 #[test]
5667 fn reads_ik_bone_with_links_and_angle_limits() {
5668 let mut buf = Vec::new();
5669 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5670 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5671 buf.extend_from_slice(&build_empty_vertex_section());
5672 buf.extend_from_slice(&build_empty_face_section());
5673 buf.extend_from_slice(&build_empty_texture_section());
5674 buf.extend_from_slice(&build_empty_material_section());
5675 buf.extend_from_slice(&build_bone_section_header(3));
5676
5677 buf.extend_from_slice(&build_bone_name_bytes("Root"));
5678 buf.extend_from_slice(&build_bone_name_bytes(""));
5679 buf.extend_from_slice(&0.0f32.to_le_bytes());
5680 buf.extend_from_slice(&0.0f32.to_le_bytes());
5681 buf.extend_from_slice(&0.0f32.to_le_bytes());
5682 buf.extend_from_slice(&(-1i16).to_le_bytes());
5683 buf.extend_from_slice(&0i32.to_le_bytes());
5684 buf.extend_from_slice(&0u16.to_le_bytes());
5685 buf.extend_from_slice(&0.0f32.to_le_bytes());
5686 buf.extend_from_slice(&0.0f32.to_le_bytes());
5687 buf.extend_from_slice(&0.0f32.to_le_bytes());
5688
5689 buf.extend_from_slice(&build_bone_name_bytes("IKLink"));
5690 buf.extend_from_slice(&build_bone_name_bytes(""));
5691 buf.extend_from_slice(&2.0f32.to_le_bytes());
5692 buf.extend_from_slice(&0.0f32.to_le_bytes());
5693 buf.extend_from_slice(&0.0f32.to_le_bytes());
5694 buf.extend_from_slice(&0i16.to_le_bytes());
5695 buf.extend_from_slice(&0i32.to_le_bytes());
5696 buf.extend_from_slice(&0u16.to_le_bytes());
5697 buf.extend_from_slice(&0.0f32.to_le_bytes());
5698 buf.extend_from_slice(&0.0f32.to_le_bytes());
5699 buf.extend_from_slice(&0.0f32.to_le_bytes());
5700
5701 buf.extend_from_slice(&build_bone_name_bytes("IKBone"));
5702 buf.extend_from_slice(&build_bone_name_bytes(""));
5703 buf.extend_from_slice(&4.0f32.to_le_bytes());
5704 buf.extend_from_slice(&0.0f32.to_le_bytes());
5705 buf.extend_from_slice(&0.0f32.to_le_bytes());
5706 buf.extend_from_slice(&0i16.to_le_bytes());
5707 buf.extend_from_slice(&0i32.to_le_bytes());
5708 buf.extend_from_slice(&(BONE_FLAG_TAIL_INDEX | BONE_FLAG_IK).to_le_bytes());
5709 buf.extend_from_slice(&1i16.to_le_bytes());
5710 buf.extend_from_slice(&1i16.to_le_bytes());
5711 buf.extend_from_slice(&10i32.to_le_bytes());
5712 buf.extend_from_slice(&0.5f32.to_le_bytes());
5713 buf.extend_from_slice(&1i32.to_le_bytes());
5714 buf.extend_from_slice(&1i16.to_le_bytes());
5715 buf.push(1u8);
5716 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
5717 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
5718 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
5719 buf.extend_from_slice(&1.0f32.to_le_bytes());
5720 buf.extend_from_slice(&1.0f32.to_le_bytes());
5721 buf.extend_from_slice(&1.0f32.to_le_bytes());
5722
5723 let result = import_pmx_model(&buf).unwrap();
5724 assert_eq!(result.bones.len(), 3);
5725 assert_eq!(result.ik_solvers.len(), 1);
5726 assert_eq!(result.ik_solvers[0].ik_bone, BoneIndex(2));
5727 assert_eq!(result.ik_solvers[0].target_bone, BoneIndex(1));
5728 assert_eq!(result.ik_solvers[0].links.len(), 1);
5729 assert_eq!(result.ik_solvers[0].links[0].bone, BoneIndex(1));
5730 assert!(result.ik_solvers[0].links[0].angle_limit.is_some());
5731 assert_eq!(result.ik_solvers[0].iteration_count, 10);
5732 assert!((result.ik_solvers[0].limit_angle - 0.5).abs() < 0.001);
5733 }
5734
5735 #[test]
5736 fn handles_utf16le_encoding() {
5737 let header_bytes = build_small_pmx_header_bytes(4, TextEncoding::Utf16Le);
5738 let (header, _) = read_header(&header_bytes).unwrap();
5739 assert_eq!(header.encoding, TextEncoding::Utf16Le);
5740 }
5741
5742 #[test]
5743 fn decodes_utf16le_surrogate_pairs() {
5744 let bytes = [0x3D, 0xD8, 0x00, 0xDE, 0x00, 0x00];
5745 assert_eq!(decode_utf16le_lossy(&bytes), "\u{1F600}");
5746 }
5747
5748 #[test]
5749 fn reports_eof_on_truncated_data() {
5750 let buf = vec![0x50, 0x4D, 0x58]; assert!(matches!(
5752 read_header(&buf).unwrap_err(),
5753 ImportError::UnexpectedEof(_)
5754 ));
5755 }
5756
5757 #[test]
5758 fn bone_names_retained_and_mapped_to_indices() {
5759 let mut buf = Vec::new();
5760 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5761 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5762 buf.extend_from_slice(&build_empty_vertex_section());
5763 buf.extend_from_slice(&build_empty_face_section());
5764 buf.extend_from_slice(&build_empty_texture_section());
5765 buf.extend_from_slice(&build_empty_material_section());
5766 buf.extend_from_slice(&build_bone_section_header(2));
5767
5768 buf.extend_from_slice(&build_bone_name_bytes("Hips"));
5769 buf.extend_from_slice(&build_bone_name_bytes(""));
5770 buf.extend_from_slice(&0.0f32.to_le_bytes());
5771 buf.extend_from_slice(&0.0f32.to_le_bytes());
5772 buf.extend_from_slice(&0.0f32.to_le_bytes());
5773 buf.extend_from_slice(&(-1i16).to_le_bytes());
5774 buf.extend_from_slice(&0i32.to_le_bytes());
5775 buf.extend_from_slice(&0u16.to_le_bytes());
5776 buf.extend_from_slice(&0.0f32.to_le_bytes());
5777 buf.extend_from_slice(&0.0f32.to_le_bytes());
5778 buf.extend_from_slice(&0.0f32.to_le_bytes());
5779
5780 buf.extend_from_slice(&build_bone_name_bytes("Spine"));
5781 buf.extend_from_slice(&build_bone_name_bytes(""));
5782 buf.extend_from_slice(&1.0f32.to_le_bytes());
5783 buf.extend_from_slice(&0.0f32.to_le_bytes());
5784 buf.extend_from_slice(&0.0f32.to_le_bytes());
5785 buf.extend_from_slice(&0i16.to_le_bytes());
5786 buf.extend_from_slice(&0i32.to_le_bytes());
5787 buf.extend_from_slice(&0u16.to_le_bytes());
5788 buf.extend_from_slice(&0.0f32.to_le_bytes());
5789 buf.extend_from_slice(&0.0f32.to_le_bytes());
5790 buf.extend_from_slice(&0.0f32.to_le_bytes());
5791
5792 let result = import_pmx_model(&buf).unwrap();
5793 assert_eq!(result.bone_name_bytes.len(), 2);
5794 assert_eq!(&result.bone_name_bytes[0][..], b"Hips");
5795 assert_eq!(&result.bone_name_bytes[1][..], b"Spine");
5796 assert_eq!(result.bone_names[0], "Hips");
5797 assert_eq!(result.bone_names[1], "Spine");
5798
5799 let bone_map: HashMap<Vec<u8>, BoneIndex> = result
5800 .bone_name_bytes
5801 .iter()
5802 .enumerate()
5803 .map(|(i, b)| (b.clone(), BoneIndex(i as u32)))
5804 .collect();
5805
5806 assert_eq!(bone_map.get(b"Hips".as_slice()), Some(&BoneIndex(0)));
5807 assert_eq!(bone_map.get(b"Spine".as_slice()), Some(&BoneIndex(1)));
5808 assert_eq!(bone_map.get(b"Unknown".as_slice()), None);
5809 }
5810
5811 #[test]
5812 fn ik_solver_name_mapped_to_solver_index() {
5813 let mut buf = Vec::new();
5814 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5815 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5816 buf.extend_from_slice(&build_empty_vertex_section());
5817 buf.extend_from_slice(&build_empty_face_section());
5818 buf.extend_from_slice(&build_empty_texture_section());
5819 buf.extend_from_slice(&build_empty_material_section());
5820 buf.extend_from_slice(&build_bone_section_header(3));
5821
5822 buf.extend_from_slice(&build_bone_name_bytes("Root"));
5823 buf.extend_from_slice(&build_bone_name_bytes(""));
5824 buf.extend_from_slice(&0.0f32.to_le_bytes());
5825 buf.extend_from_slice(&0.0f32.to_le_bytes());
5826 buf.extend_from_slice(&0.0f32.to_le_bytes());
5827 buf.extend_from_slice(&(-1i16).to_le_bytes());
5828 buf.extend_from_slice(&0i32.to_le_bytes());
5829 buf.extend_from_slice(&0u16.to_le_bytes());
5830 buf.extend_from_slice(&0.0f32.to_le_bytes());
5831 buf.extend_from_slice(&0.0f32.to_le_bytes());
5832 buf.extend_from_slice(&0.0f32.to_le_bytes());
5833
5834 buf.extend_from_slice(&build_bone_name_bytes("IKLink"));
5835 buf.extend_from_slice(&build_bone_name_bytes(""));
5836 buf.extend_from_slice(&2.0f32.to_le_bytes());
5837 buf.extend_from_slice(&0.0f32.to_le_bytes());
5838 buf.extend_from_slice(&0.0f32.to_le_bytes());
5839 buf.extend_from_slice(&0i16.to_le_bytes());
5840 buf.extend_from_slice(&0i32.to_le_bytes());
5841 buf.extend_from_slice(&0u16.to_le_bytes());
5842 buf.extend_from_slice(&0.0f32.to_le_bytes());
5843 buf.extend_from_slice(&0.0f32.to_le_bytes());
5844 buf.extend_from_slice(&0.0f32.to_le_bytes());
5845
5846 buf.extend_from_slice(&build_bone_name_bytes("LeftLegIK"));
5847 buf.extend_from_slice(&build_bone_name_bytes(""));
5848 buf.extend_from_slice(&4.0f32.to_le_bytes());
5849 buf.extend_from_slice(&0.0f32.to_le_bytes());
5850 buf.extend_from_slice(&0.0f32.to_le_bytes());
5851 buf.extend_from_slice(&0i16.to_le_bytes());
5852 buf.extend_from_slice(&0i32.to_le_bytes());
5853 buf.extend_from_slice(&(BONE_FLAG_TAIL_INDEX | BONE_FLAG_IK).to_le_bytes());
5854 buf.extend_from_slice(&1i16.to_le_bytes());
5855 buf.extend_from_slice(&1i16.to_le_bytes());
5856 buf.extend_from_slice(&10i32.to_le_bytes());
5857 buf.extend_from_slice(&0.5f32.to_le_bytes());
5858 buf.extend_from_slice(&1i32.to_le_bytes());
5859 buf.extend_from_slice(&1i16.to_le_bytes());
5860 buf.push(1u8);
5861 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
5862 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
5863 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
5864 buf.extend_from_slice(&1.0f32.to_le_bytes());
5865 buf.extend_from_slice(&1.0f32.to_le_bytes());
5866 buf.extend_from_slice(&1.0f32.to_le_bytes());
5867
5868 let bone_import = import_pmx_model(&buf).unwrap();
5869 assert_eq!(bone_import.ik_solvers.len(), 1);
5870 assert_eq!(bone_import.ik_solvers[0].ik_bone, BoneIndex(2));
5871 assert_eq!(
5872 bone_import.bone_names[bone_import.ik_solvers[0].ik_bone.as_usize()],
5873 "LeftLegIK"
5874 );
5875
5876 let ik_name_map: HashMap<Vec<u8>, usize> = bone_import
5877 .ik_solvers
5878 .iter()
5879 .enumerate()
5880 .filter_map(|(solver_idx, solver)| {
5881 let bone_idx = solver.ik_bone.as_usize();
5882 if bone_idx < bone_import.bone_name_bytes.len() {
5883 Some((bone_import.bone_name_bytes[bone_idx].clone(), solver_idx))
5884 } else {
5885 None
5886 }
5887 })
5888 .collect();
5889
5890 assert_eq!(ik_name_map.get(b"LeftLegIK".as_slice()), Some(&0));
5891 }
5892
5893 #[test]
5894 fn reads_morph_names_without_payloads() {
5895 let (header, pos) =
5896 read_header(&build_small_pmx_header_bytes(4, TextEncoding::Utf8)).unwrap();
5897
5898 let mut buf = vec![0u8; pos];
5899 buf.extend_from_slice(&1i32.to_le_bytes());
5900 buf.extend_from_slice(&build_morph_name_bytes("Smile"));
5901 buf.extend_from_slice(&build_morph_name_bytes(""));
5902 buf.push(4u8);
5903 buf.push(0u8);
5904 buf.extend_from_slice(&0i32.to_le_bytes());
5905
5906 let (morphs, new_pos) = read_morph_names(&buf[..], &header, pos).unwrap();
5907 assert_eq!(morphs.name_bytes.len(), 1);
5908 assert_eq!(&morphs.name_bytes[0][..], b"Smile");
5909 assert_eq!(morphs.names[0], "Smile");
5910 assert!(new_pos > pos);
5911 }
5912
5913 #[test]
5914 fn reads_morph_type_zero_with_vertex_group_offsets() {
5915 let (header, pos) =
5916 read_header(&build_small_pmx_header_bytes(4, TextEncoding::Utf8)).unwrap();
5917 assert_eq!(header.vertex_index_size, 4);
5918 assert_eq!(header.morph_index_size, 1);
5919
5920 let mut buf = vec![0u8; pos];
5921 buf.extend_from_slice(&1i32.to_le_bytes());
5922 buf.extend_from_slice(&build_morph_name_bytes("Blink"));
5923 buf.extend_from_slice(&build_morph_name_bytes(""));
5924 buf.push(0u8);
5925 buf.push(0u8);
5926 buf.extend_from_slice(&2i32.to_le_bytes());
5927 buf.push(0u8);
5928 buf.extend_from_slice(&[0u8; 4]);
5929 buf.push(1u8);
5930 buf.extend_from_slice(&[0u8; 4]);
5931
5932 let (morphs, new_pos) = read_morph_names(&buf[..], &header, pos).unwrap();
5933 assert_eq!(morphs.name_bytes.len(), 1);
5934 assert_eq!(&morphs.name_bytes[0][..], b"Blink");
5935 assert_eq!(morphs.names[0], "Blink");
5936 assert_eq!(new_pos, buf.len());
5937 }
5938
5939 #[test]
5940 fn runtime_import_builds_model_arena_with_name_lookup() {
5941 let mut buf = Vec::new();
5942 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5943 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5944 buf.extend_from_slice(&build_empty_vertex_section());
5945 buf.extend_from_slice(&build_empty_face_section());
5946 buf.extend_from_slice(&build_empty_texture_section());
5947 buf.extend_from_slice(&build_empty_material_section());
5948 buf.extend_from_slice(&build_bone_section_header(2));
5949
5950 buf.extend_from_slice(&build_bone_name_bytes("Hips"));
5951 buf.extend_from_slice(&build_bone_name_bytes(""));
5952 buf.extend_from_slice(&0.0f32.to_le_bytes());
5953 buf.extend_from_slice(&0.0f32.to_le_bytes());
5954 buf.extend_from_slice(&0.0f32.to_le_bytes());
5955 buf.extend_from_slice(&(-1i16).to_le_bytes());
5956 buf.extend_from_slice(&0i32.to_le_bytes());
5957 buf.extend_from_slice(&0u16.to_le_bytes());
5958 buf.extend_from_slice(&0.0f32.to_le_bytes());
5959 buf.extend_from_slice(&0.0f32.to_le_bytes());
5960 buf.extend_from_slice(&0.0f32.to_le_bytes());
5961
5962 buf.extend_from_slice(&build_bone_name_bytes("Spine"));
5963 buf.extend_from_slice(&build_bone_name_bytes(""));
5964 buf.extend_from_slice(&0.0f32.to_le_bytes());
5965 buf.extend_from_slice(&1.0f32.to_le_bytes());
5966 buf.extend_from_slice(&0.0f32.to_le_bytes());
5967 buf.extend_from_slice(&0i16.to_le_bytes());
5968 buf.extend_from_slice(&0i32.to_le_bytes());
5969 buf.extend_from_slice(&0u16.to_le_bytes());
5970 buf.extend_from_slice(&0.0f32.to_le_bytes());
5971 buf.extend_from_slice(&0.0f32.to_le_bytes());
5972 buf.extend_from_slice(&0.0f32.to_le_bytes());
5973 buf.extend_from_slice(&0i32.to_le_bytes());
5974
5975 let rt = import_pmx_runtime(&buf).unwrap();
5976 assert_eq!(rt.model.bone_count(), 2);
5977 assert_eq!(rt.model.parent_index(BoneIndex(1)), Some(BoneIndex(0)));
5978 assert_eq!(
5979 rt.bone_name_to_index.get(b"Hips".as_slice()),
5980 Some(&BoneIndex(0))
5981 );
5982 assert_eq!(
5983 rt.bone_name_to_index.get(b"Spine".as_slice()),
5984 Some(&BoneIndex(1))
5985 );
5986 assert!(rt.morph_name_to_index.is_empty());
5987 assert!(rt.ik_solver_bone_name_to_index.is_empty());
5988 }
5989
5990 #[test]
5991 fn parse_pmx_model_exposes_three_loader_shaped_sections() {
5992 let mut buf = Vec::new();
5993 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5994 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5995 buf.extend_from_slice(&build_empty_vertex_section());
5996 buf.extend_from_slice(&build_empty_face_section());
5997 buf.extend_from_slice(&build_empty_texture_section());
5998 buf.extend_from_slice(&build_empty_material_section());
5999 buf.extend_from_slice(&build_bone_section_header(2));
6000
6001 buf.extend_from_slice(&build_bone_name_bytes("Root"));
6002 buf.extend_from_slice(&build_bone_name_bytes(""));
6003 buf.extend_from_slice(&0.0f32.to_le_bytes());
6004 buf.extend_from_slice(&0.0f32.to_le_bytes());
6005 buf.extend_from_slice(&0.0f32.to_le_bytes());
6006 buf.extend_from_slice(&(-1i16).to_le_bytes());
6007 buf.extend_from_slice(&0i32.to_le_bytes());
6008 buf.extend_from_slice(&0u16.to_le_bytes());
6009 buf.extend_from_slice(&0.0f32.to_le_bytes());
6010 buf.extend_from_slice(&0.0f32.to_le_bytes());
6011 buf.extend_from_slice(&0.0f32.to_le_bytes());
6012
6013 buf.extend_from_slice(&build_bone_name_bytes("Child"));
6014 buf.extend_from_slice(&build_bone_name_bytes(""));
6015 buf.extend_from_slice(&0.0f32.to_le_bytes());
6016 buf.extend_from_slice(&1.0f32.to_le_bytes());
6017 buf.extend_from_slice(&0.0f32.to_le_bytes());
6018 buf.extend_from_slice(&0i16.to_le_bytes());
6019 buf.extend_from_slice(&0i32.to_le_bytes());
6020 buf.extend_from_slice(&0u16.to_le_bytes());
6021 buf.extend_from_slice(&0.0f32.to_le_bytes());
6022 buf.extend_from_slice(&0.0f32.to_le_bytes());
6023 buf.extend_from_slice(&0.0f32.to_le_bytes());
6024
6025 buf.extend_from_slice(&0i32.to_le_bytes()); buf.extend_from_slice(&1i32.to_le_bytes()); buf.extend_from_slice(&build_bone_name_bytes("RootFrame"));
6028 buf.extend_from_slice(&build_bone_name_bytes(""));
6029 buf.push(1);
6030 buf.extend_from_slice(&1i32.to_le_bytes());
6031 buf.push(0);
6032 buf.extend_from_slice(&1i16.to_le_bytes());
6033 buf.extend_from_slice(&0i32.to_le_bytes()); buf.extend_from_slice(&0i32.to_le_bytes()); let parsed = parse_pmx_model(&buf).unwrap();
6037 assert_eq!(parsed.metadata.format, "pmx");
6038 assert_eq!(parsed.metadata.counts.bones, 2);
6039 assert_eq!(parsed.skeleton.bones[0].name, "Root");
6040 assert_eq!(parsed.skeleton.bones[1].parent_index, 0);
6041 assert_eq!(parsed.display_frames.len(), 1);
6042 assert_eq!(parsed.display_frames[0].frames[0].kind, "bone");
6043 assert_eq!(parsed.display_frames[0].frames[0].index, 1);
6044 assert!(parsed.diagnostics.is_empty());
6045 }
6046
6047 #[test]
6048 fn exports_pmx_semantic_roundtrip() {
6049 let parsed = parsed_pmx_fixture();
6050 let exported = export_pmx_model(&parsed);
6051 let reparsed = parse_pmx_model(&exported).unwrap();
6052
6053 assert_pmx_roundtrip_eq(&parsed, &reparsed);
6054 }
6055
6056 #[test]
6057 fn exports_pmx_json_dto_semantic_roundtrip() {
6058 let parsed = parsed_pmx_fixture();
6059 let json = serde_json::to_string(&parsed).unwrap();
6060 let from_json: PmxParsedModel = serde_json::from_str(&json).unwrap();
6061 let exported = export_pmx_model(&from_json);
6062 let reparsed = parse_pmx_model(&exported).unwrap();
6063
6064 assert_pmx_roundtrip_eq(&parsed, &reparsed);
6065 }
6066
6067 #[test]
6068 fn pmx_json_dto_roundtrips_non_finite_f32_values() {
6069 let mut parsed = parsed_pmx_fixture();
6070 let finite_position = parsed.geometry.positions[1];
6071 parsed.geometry.positions[0] = f32::NAN;
6072 parsed.geometry.normals[1] = f32::INFINITY;
6073 parsed.geometry.uvs[0] = f32::NEG_INFINITY;
6074 parsed.materials[0].diffuse[0] = f32::INFINITY;
6075 parsed.materials[0].edge_size = f32::NAN;
6076 parsed.skeleton.bones[0].position[0] = f32::NEG_INFINITY;
6077 parsed.morphs[0].vertex_offsets[0].position[1] = f32::NAN;
6078
6079 let json = serde_json::to_string(&parsed).unwrap();
6080 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
6081
6082 assert!(json.contains("\"NaN\""));
6083 assert!(json.contains("\"Infinity\""));
6084 assert!(json.contains("\"-Infinity\""));
6085 assert_eq!(value["geometry"]["positions"][0], serde_json::json!("NaN"));
6086 assert_eq!(
6087 value["geometry"]["normals"][1],
6088 serde_json::json!("Infinity")
6089 );
6090 assert_eq!(value["geometry"]["uvs"][0], serde_json::json!("-Infinity"));
6091 assert_eq!(
6092 value["geometry"]["positions"][1],
6093 serde_json::json!(finite_position)
6094 );
6095
6096 let from_json: PmxParsedModel = serde_json::from_str(&json).unwrap();
6097
6098 assert!(from_json.geometry.positions[0].is_nan());
6099 assert_eq!(from_json.geometry.normals[1], f32::INFINITY);
6100 assert_eq!(from_json.geometry.uvs[0], f32::NEG_INFINITY);
6101 assert_eq!(from_json.materials[0].diffuse[0], f32::INFINITY);
6102 assert!(from_json.materials[0].edge_size.is_nan());
6103 assert_eq!(from_json.skeleton.bones[0].position[0], f32::NEG_INFINITY);
6104 assert!(from_json.morphs[0].vertex_offsets[0].position[1].is_nan());
6105 assert_eq!(from_json.geometry.positions[1], finite_position);
6106 }
6107
6108 #[test]
6109 fn builds_pmx_parts_with_material_descriptors() {
6110 let descriptor: PmxPartsDescriptor = serde_json::from_value(serde_json::json!({
6111 "name": "material-parts",
6112 "materials": [
6113 {
6114 "name": "red",
6115 "englishName": "red-en",
6116 "diffuse": [1.0, 0.0, 0.0, 1.0],
6117 "ambient": [0.5, 0.0, 0.0],
6118 "flags": { "edge": true },
6119 "faceCount": 1
6120 },
6121 {
6122 "name": "blue",
6123 "diffuse": [0.0, 0.0, 1.0, 1.0],
6124 "faceCount": 1
6125 }
6126 ],
6127 "bones": [
6128 { "name": "root", "englishName": "root-en", "tailIndex": 1 },
6129 { "name": "child", "parentIndex": 0, "position": [0.0, 1.0, 0.0], "rotatable": true }
6130 ],
6131 "morphs": [
6132 {
6133 "name": "raise",
6134 "englishName": "raise-en",
6135 "kind": "vertex",
6136 "vertexOffsets": [{ "vertexIndex": 0, "position": [0.0, 0.1, 0.0] }]
6137 },
6138 {
6139 "name": "combo",
6140 "kind": "group",
6141 "groupOffsets": [{ "morphIndex": 0, "weight": 0.5 }]
6142 }
6143 ],
6144 "displayFrames": [
6145 {
6146 "name": "Root",
6147 "englishName": "Root-en",
6148 "special": true,
6149 "frames": [
6150 { "kind": "bone", "index": 0 },
6151 { "kind": "bone", "index": 1 },
6152 { "kind": "morph", "index": 0 }
6153 ]
6154 }
6155 ],
6156 "rigidBodies": [
6157 {
6158 "name": "body",
6159 "englishName": "body-en",
6160 "boneIndex": 1,
6161 "group": 2,
6162 "mask": 3,
6163 "shape": "box",
6164 "size": [1.0, 2.0, 3.0],
6165 "position": [0.0, 1.0, 0.0],
6166 "rotation": [0.1, 0.2, 0.3],
6167 "mass": 2.0,
6168 "linearDamping": 0.4,
6169 "angularDamping": 0.5,
6170 "restitution": 0.6,
6171 "friction": 0.7,
6172 "mode": "dynamicBone"
6173 }
6174 ],
6175 "joints": [
6176 {
6177 "name": "joint",
6178 "type": "generic6dofSpring",
6179 "rigidBodyIndexA": 0,
6180 "rigidBodyIndexB": -1,
6181 "position": [0.0, 1.0, 0.0],
6182 "translationLowerLimit": [-1.0, -1.0, -1.0],
6183 "translationUpperLimit": [1.0, 1.0, 1.0],
6184 "springTranslationFactor": [0.1, 0.2, 0.3]
6185 }
6186 ],
6187 "indexSizes": {
6188 "vertex": 1,
6189 "material": 1,
6190 "texture": 1,
6191 "bone": 1,
6192 "morph": 1,
6193 "rigidBody": 1
6194 }
6195 }))
6196 .unwrap();
6197 let model = build_pmx_model_from_parts(PmxPartsInput {
6198 descriptor,
6199 positions_xyz: &[
6200 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
6204 ],
6205 normals_xyz: &[
6206 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
6210 ],
6211 uvs_xy: &[0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0],
6212 indices: &[0, 1, 2, 0, 2, 3],
6213 skin_indices: &[0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
6214 skin_weights: &[
6215 0.75, 0.25, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
6216 ],
6217 edge_scale: &[],
6218 })
6219 .unwrap();
6220 let exported = export_pmx_model(&model);
6221 let reparsed = parse_pmx_model(&exported).unwrap();
6222
6223 assert_eq!(reparsed.metadata.name, "material-parts");
6224 assert_eq!(reparsed.metadata.counts.materials, 2);
6225 assert_eq!(reparsed.materials[0].name, "red");
6226 assert_eq!(reparsed.materials[0].english_name, "red-en");
6227 assert_eq!(reparsed.materials[0].face_count, 1);
6228 assert!(reparsed.materials[0].flags.edge);
6229 assert_eq!(reparsed.materials[1].name, "blue");
6230 assert_eq!(reparsed.materials[1].face_count, 1);
6231 assert_eq!(reparsed.geometry.material_groups.len(), 2);
6232 assert_eq!(reparsed.geometry.material_groups[0].start, 0);
6233 assert_eq!(reparsed.geometry.material_groups[0].count, 3);
6234 assert_eq!(reparsed.geometry.material_groups[1].start, 3);
6235 assert_eq!(reparsed.geometry.material_groups[1].count, 3);
6236 assert_eq!(reparsed.metadata.counts.bones, 2);
6237 assert_eq!(reparsed.skeleton.bones[0].name, "root");
6238 assert_eq!(reparsed.skeleton.bones[0].english_name, "root-en");
6239 assert_eq!(reparsed.skeleton.bones[0].tail_index, 1);
6240 assert!(reparsed.skeleton.bones[0].flags.indexed_tail);
6241 assert_eq!(reparsed.skeleton.bones[1].name, "child");
6242 assert_eq!(reparsed.skeleton.bones[1].parent_index, 0);
6243 assert!(reparsed.skeleton.bones[1].flags.rotatable);
6244 assert_eq!(reparsed.geometry.skin_indices[1], 1);
6245 assert_eq!(reparsed.geometry.skin_weights[1], 0.25);
6246 assert_eq!(reparsed.metadata.counts.morphs, 2);
6247 assert_eq!(reparsed.morphs[0].name, "raise");
6248 assert_eq!(reparsed.morphs[0].english_name, "raise-en");
6249 assert_eq!(reparsed.morphs[0].kind, "vertex");
6250 assert_eq!(reparsed.morphs[0].vertex_offsets[0].vertex_index, 0);
6251 assert_eq!(
6252 reparsed.morphs[0].vertex_offsets[0].position,
6253 [0.0, 0.1, 0.0]
6254 );
6255 assert_eq!(reparsed.morphs[1].name, "combo");
6256 assert_eq!(reparsed.morphs[1].kind, "group");
6257 assert_eq!(reparsed.morphs[1].group_offsets[0].morph_index, 0);
6258 assert_eq!(reparsed.morphs[1].group_offsets[0].weight, 0.5);
6259 assert_eq!(reparsed.display_frames.len(), 1);
6260 assert_eq!(reparsed.display_frames[0].name, "Root");
6261 assert_eq!(reparsed.display_frames[0].english_name, "Root-en");
6262 assert!(reparsed.display_frames[0].special);
6263 assert_eq!(reparsed.display_frames[0].frames.len(), 3);
6264 assert_eq!(reparsed.display_frames[0].frames[1].kind, "bone");
6265 assert_eq!(reparsed.display_frames[0].frames[1].index, 1);
6266 assert_eq!(reparsed.display_frames[0].frames[2].kind, "morph");
6267 assert_eq!(reparsed.display_frames[0].frames[2].index, 0);
6268 assert_eq!(reparsed.rigid_bodies.len(), 1);
6269 assert_eq!(reparsed.rigid_bodies[0].name, "body");
6270 assert_eq!(reparsed.rigid_bodies[0].english_name, "body-en");
6271 assert_eq!(reparsed.rigid_bodies[0].bone_index, 1);
6272 assert_eq!(reparsed.rigid_bodies[0].group, 2);
6273 assert_eq!(reparsed.rigid_bodies[0].mask, 3);
6274 assert_eq!(reparsed.rigid_bodies[0].shape, "box");
6275 assert_eq!(reparsed.rigid_bodies[0].mode, "dynamicBone");
6276 assert_eq!(reparsed.joints.len(), 1);
6277 assert_eq!(reparsed.joints[0].name, "joint");
6278 assert_eq!(reparsed.joints[0].kind, "generic6dofSpring");
6279 assert_eq!(reparsed.joints[0].rigid_body_index_a, 0);
6280 assert_eq!(reparsed.joints[0].rigid_body_index_b, -1);
6281 }
6282
6283 #[test]
6284 fn rejects_pmx_parts_material_face_count_mismatch() {
6285 let descriptor: PmxPartsDescriptor = serde_json::from_value(serde_json::json!({
6286 "materials": [{ "name": "bad", "faceCount": 2 }]
6287 }))
6288 .unwrap();
6289 let error = build_pmx_model_from_parts(PmxPartsInput {
6290 descriptor,
6291 positions_xyz: &[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
6292 normals_xyz: &[0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0],
6293 uvs_xy: &[0.0, 0.0, 1.0, 0.0, 0.0, 1.0],
6294 indices: &[0, 1, 2],
6295 skin_indices: &[],
6296 skin_weights: &[],
6297 edge_scale: &[],
6298 })
6299 .unwrap_err();
6300
6301 assert!(error.contains("materials faceCount sum"));
6302 }
6303
6304 #[test]
6305 fn rejects_pmx_export_model_partial_skin_arrays() {
6306 let mut parsed = parsed_pmx_fixture();
6307 parsed.geometry.skin_weights.clear();
6308
6309 let error = validate_pmx_export_model(&parsed).unwrap_err();
6310
6311 assert!(error.contains("skinIndices and skinWeights"));
6312 }
6313
6314 #[test]
6317 fn exports_pmx_22_synthetic_roundtrip() {
6318 let mut parsed = parsed_pmx_fixture();
6319 parsed.metadata.version = 2.2;
6320
6321 let exported = export_pmx_model(&parsed);
6322 let reparsed = parse_pmx_model(&exported).unwrap();
6323
6324 assert_eq!(
6325 reparsed.metadata.version, 2.2,
6326 "PMX 2.2 version must survive the export-parse cycle"
6327 );
6328 assert_pmx_roundtrip_eq(&parsed, &reparsed);
6329 }
6330
6331 #[test]
6332 fn exports_pmx_soft_body_header_fields_with_diagnostic_for_pmx_21_and_22() {
6333 for version in [2.1, 2.2] {
6334 let mut parsed = parsed_pmx_fixture();
6335 parsed.metadata.version = version;
6336 parsed.soft_bodies = vec![PmxParsedSoftBody {
6337 name: "soft".to_owned(),
6338 english_name: "soft-en".to_owned(),
6339 kind: "rope".to_owned(),
6340 material_index: 0,
6341 collision_group: 1,
6342 collision_mask: 2,
6343 flags: 3,
6344 bending_constraints_distance: 4,
6345 cluster_count: 5,
6346 total_mass: 6.0,
6347 collision_margin: 0.7,
6348 }];
6349 let exported = export_pmx_model(&parsed);
6350 let reparsed = parse_pmx_model(&exported).unwrap();
6351
6352 assert_eq!(reparsed.metadata.version, version);
6353 assert_eq!(reparsed.metadata.counts.soft_bodies, 1);
6354 assert_eq!(reparsed.soft_bodies.len(), 1);
6355 assert_eq!(reparsed.soft_bodies[0].name, "soft");
6356 assert_eq!(reparsed.soft_bodies[0].english_name, "soft-en");
6357 assert_eq!(reparsed.soft_bodies[0].kind, "rope");
6358 assert_eq!(reparsed.soft_bodies[0].material_index, 0);
6359 assert_eq!(reparsed.soft_bodies[0].collision_group, 1);
6360 assert_eq!(reparsed.soft_bodies[0].collision_mask, 2);
6361 assert_eq!(reparsed.soft_bodies[0].flags, 3);
6362 assert_eq!(reparsed.soft_bodies[0].bending_constraints_distance, 4);
6363 assert_eq!(reparsed.soft_bodies[0].cluster_count, 5);
6364 assert_eq!(reparsed.soft_bodies[0].total_mass, 6.0);
6365 assert_eq!(reparsed.soft_bodies[0].collision_margin, 0.7);
6366 assert_eq!(reparsed.diagnostics.len(), 1);
6367 assert_eq!(reparsed.diagnostics[0].code, "PMX_SOFT_BODY_UNSUPPORTED");
6368 }
6369 }
6370
6371 #[test]
6372 fn exports_parsed_pmx_skeleton_display_roundtrip() {
6373 let mut buf = Vec::new();
6374 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
6375 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
6376 buf.extend_from_slice(&build_empty_vertex_section());
6377 buf.extend_from_slice(&build_empty_face_section());
6378 buf.extend_from_slice(&build_empty_texture_section());
6379 buf.extend_from_slice(&build_empty_material_section());
6380 buf.extend_from_slice(&build_bone_section_header(2));
6381
6382 buf.extend_from_slice(&build_bone_name_bytes("Root"));
6383 buf.extend_from_slice(&build_bone_name_bytes(""));
6384 buf.extend_from_slice(&0.0f32.to_le_bytes());
6385 buf.extend_from_slice(&0.0f32.to_le_bytes());
6386 buf.extend_from_slice(&0.0f32.to_le_bytes());
6387 buf.extend_from_slice(&(-1i16).to_le_bytes());
6388 buf.extend_from_slice(&0i32.to_le_bytes());
6389 buf.extend_from_slice(&0u16.to_le_bytes());
6390 buf.extend_from_slice(&0.0f32.to_le_bytes());
6391 buf.extend_from_slice(&0.0f32.to_le_bytes());
6392 buf.extend_from_slice(&0.0f32.to_le_bytes());
6393
6394 buf.extend_from_slice(&build_bone_name_bytes("Child"));
6395 buf.extend_from_slice(&build_bone_name_bytes(""));
6396 buf.extend_from_slice(&0.0f32.to_le_bytes());
6397 buf.extend_from_slice(&1.0f32.to_le_bytes());
6398 buf.extend_from_slice(&0.0f32.to_le_bytes());
6399 buf.extend_from_slice(&0i16.to_le_bytes());
6400 buf.extend_from_slice(&0i32.to_le_bytes());
6401 buf.extend_from_slice(&0u16.to_le_bytes());
6402 buf.extend_from_slice(&0.0f32.to_le_bytes());
6403 buf.extend_from_slice(&0.0f32.to_le_bytes());
6404 buf.extend_from_slice(&0.0f32.to_le_bytes());
6405
6406 buf.extend_from_slice(&0i32.to_le_bytes()); buf.extend_from_slice(&1i32.to_le_bytes()); buf.extend_from_slice(&build_bone_name_bytes("RootFrame"));
6409 buf.extend_from_slice(&build_bone_name_bytes(""));
6410 buf.push(1);
6411 buf.extend_from_slice(&1i32.to_le_bytes());
6412 buf.push(0);
6413 buf.extend_from_slice(&1i16.to_le_bytes());
6414 buf.extend_from_slice(&0i32.to_le_bytes()); buf.extend_from_slice(&0i32.to_le_bytes()); let parsed = parse_pmx_model(&buf).unwrap();
6418 let exported = export_pmx_model(&parsed);
6419 let reparsed = parse_pmx_model(&exported).unwrap();
6420
6421 assert_pmx_roundtrip_eq(&parsed, &reparsed);
6422 }
6423
6424 #[test]
6425 fn runtime_import_ik_solver_name_lookup() {
6426 let mut buf = Vec::new();
6427 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
6428 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
6429 buf.extend_from_slice(&build_empty_vertex_section());
6430 buf.extend_from_slice(&build_empty_face_section());
6431 buf.extend_from_slice(&build_empty_texture_section());
6432 buf.extend_from_slice(&build_empty_material_section());
6433 buf.extend_from_slice(&build_bone_section_header(3));
6434
6435 buf.extend_from_slice(&build_bone_name_bytes("Root"));
6436 buf.extend_from_slice(&build_bone_name_bytes(""));
6437 buf.extend_from_slice(&0.0f32.to_le_bytes());
6438 buf.extend_from_slice(&0.0f32.to_le_bytes());
6439 buf.extend_from_slice(&0.0f32.to_le_bytes());
6440 buf.extend_from_slice(&(-1i16).to_le_bytes());
6441 buf.extend_from_slice(&0i32.to_le_bytes());
6442 buf.extend_from_slice(&0u16.to_le_bytes());
6443 buf.extend_from_slice(&0.0f32.to_le_bytes());
6444 buf.extend_from_slice(&0.0f32.to_le_bytes());
6445 buf.extend_from_slice(&0.0f32.to_le_bytes());
6446
6447 buf.extend_from_slice(&build_bone_name_bytes("IKLink"));
6448 buf.extend_from_slice(&build_bone_name_bytes(""));
6449 buf.extend_from_slice(&2.0f32.to_le_bytes());
6450 buf.extend_from_slice(&0.0f32.to_le_bytes());
6451 buf.extend_from_slice(&0.0f32.to_le_bytes());
6452 buf.extend_from_slice(&0i16.to_le_bytes());
6453 buf.extend_from_slice(&0i32.to_le_bytes());
6454 buf.extend_from_slice(&0u16.to_le_bytes());
6455 buf.extend_from_slice(&0.0f32.to_le_bytes());
6456 buf.extend_from_slice(&0.0f32.to_le_bytes());
6457 buf.extend_from_slice(&0.0f32.to_le_bytes());
6458
6459 buf.extend_from_slice(&build_bone_name_bytes("LeftLegIK"));
6460 buf.extend_from_slice(&build_bone_name_bytes(""));
6461 buf.extend_from_slice(&4.0f32.to_le_bytes());
6462 buf.extend_from_slice(&0.0f32.to_le_bytes());
6463 buf.extend_from_slice(&0.0f32.to_le_bytes());
6464 buf.extend_from_slice(&0i16.to_le_bytes());
6465 buf.extend_from_slice(&0i32.to_le_bytes());
6466 buf.extend_from_slice(&(BONE_FLAG_TAIL_INDEX | BONE_FLAG_IK).to_le_bytes());
6467 buf.extend_from_slice(&1i16.to_le_bytes());
6468 buf.extend_from_slice(&1i16.to_le_bytes());
6469 buf.extend_from_slice(&10i32.to_le_bytes());
6470 buf.extend_from_slice(&0.5f32.to_le_bytes());
6471 buf.extend_from_slice(&1i32.to_le_bytes());
6472 buf.extend_from_slice(&1i16.to_le_bytes());
6473 buf.push(1u8);
6474 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
6475 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
6476 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
6477 buf.extend_from_slice(&1.0f32.to_le_bytes());
6478 buf.extend_from_slice(&1.0f32.to_le_bytes());
6479 buf.extend_from_slice(&1.0f32.to_le_bytes());
6480 buf.extend_from_slice(&0i32.to_le_bytes());
6481
6482 let rt = import_pmx_runtime(&buf).unwrap();
6483 assert_eq!(rt.model.bone_count(), 3);
6484 assert_eq!(rt.model.ik_count(), 1);
6485 assert_eq!(
6486 rt.ik_solver_bone_name_to_index.get(b"LeftLegIK".as_slice()),
6487 Some(&0)
6488 );
6489 assert_eq!(
6490 rt.bone_name_to_index.get(b"LeftLegIK".as_slice()),
6491 Some(&BoneIndex(2))
6492 );
6493 assert_eq!(
6494 rt.ik_solver_bone_name_to_index.get(b"NoSuchIK".as_slice()),
6495 None
6496 );
6497 }
6498
6499 #[test]
6500 fn converts_absolute_pmx_positions_to_local_rest_positions() {
6501 let mut buf = Vec::new();
6502 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
6503 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
6504 buf.extend_from_slice(&build_empty_vertex_section());
6505 buf.extend_from_slice(&build_empty_face_section());
6506 buf.extend_from_slice(&build_empty_texture_section());
6507 buf.extend_from_slice(&build_empty_material_section());
6508 buf.extend_from_slice(&build_bone_section_header(2));
6509
6510 buf.extend_from_slice(&build_bone_name_bytes("Parent"));
6511 buf.extend_from_slice(&build_bone_name_bytes(""));
6512 buf.extend_from_slice(&1.0f32.to_le_bytes());
6513 buf.extend_from_slice(&2.0f32.to_le_bytes());
6514 buf.extend_from_slice(&0.0f32.to_le_bytes());
6515 buf.extend_from_slice(&(-1i16).to_le_bytes());
6516 buf.extend_from_slice(&0i32.to_le_bytes());
6517 buf.extend_from_slice(&0u16.to_le_bytes());
6518 buf.extend_from_slice(&0.0f32.to_le_bytes());
6519 buf.extend_from_slice(&0.0f32.to_le_bytes());
6520 buf.extend_from_slice(&0.0f32.to_le_bytes());
6521
6522 buf.extend_from_slice(&build_bone_name_bytes("Child"));
6523 buf.extend_from_slice(&build_bone_name_bytes(""));
6524 buf.extend_from_slice(&1.0f32.to_le_bytes());
6525 buf.extend_from_slice(&3.0f32.to_le_bytes());
6526 buf.extend_from_slice(&0.0f32.to_le_bytes());
6527 buf.extend_from_slice(&0i16.to_le_bytes());
6528 buf.extend_from_slice(&0i32.to_le_bytes());
6529 buf.extend_from_slice(&0u16.to_le_bytes());
6530 buf.extend_from_slice(&0.0f32.to_le_bytes());
6531 buf.extend_from_slice(&0.0f32.to_le_bytes());
6532 buf.extend_from_slice(&0.0f32.to_le_bytes());
6533
6534 let result = import_pmx_model(&buf).unwrap();
6535 assert_eq!(result.bones.len(), 2);
6536 assert_eq!(result.bones[0].parent, None);
6537 assert!((result.bones[0].rest_position.x - 1.0).abs() < 0.001);
6538 assert!((result.bones[0].rest_position.y - 2.0).abs() < 0.001);
6539 assert!((result.bones[0].rest_position.z - 0.0).abs() < 0.001);
6540 assert_eq!(result.bones[1].parent, Some(BoneIndex(0)));
6541 assert!((result.bones[1].rest_position.x - 0.0).abs() < 0.001);
6542 assert!((result.bones[1].rest_position.y - 1.0).abs() < 0.001);
6543 assert!((result.bones[1].rest_position.z - 0.0).abs() < 0.001);
6544 }
6545
6546 #[test]
6547 fn root_inverse_bind_cancels_rest_world() {
6548 let mut buf = Vec::new();
6549 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
6550 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
6551 buf.extend_from_slice(&build_empty_vertex_section());
6552 buf.extend_from_slice(&build_empty_face_section());
6553 buf.extend_from_slice(&build_empty_texture_section());
6554 buf.extend_from_slice(&build_empty_material_section());
6555 buf.extend_from_slice(&build_bone_section_header(1));
6556
6557 buf.extend_from_slice(&build_bone_name_bytes("Root"));
6558 buf.extend_from_slice(&build_bone_name_bytes(""));
6559 buf.extend_from_slice(&1.0f32.to_le_bytes());
6560 buf.extend_from_slice(&2.0f32.to_le_bytes());
6561 buf.extend_from_slice(&3.0f32.to_le_bytes());
6562 buf.extend_from_slice(&(-1i16).to_le_bytes());
6563 buf.extend_from_slice(&0i32.to_le_bytes());
6564 buf.extend_from_slice(&0u16.to_le_bytes());
6565 buf.extend_from_slice(&0.0f32.to_le_bytes());
6566 buf.extend_from_slice(&0.0f32.to_le_bytes());
6567 buf.extend_from_slice(&0.0f32.to_le_bytes());
6568
6569 let result = import_pmx_model(&buf).unwrap();
6570 let ibm = result.bones[0].inverse_bind_matrix;
6571 let rest_world = Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0));
6572 assert_eq!(rest_world * ibm, Mat4::IDENTITY);
6573 }
6574
6575 #[test]
6576 fn reads_pmx_fixed_axis_bone_descriptor() {
6577 let mut buf = Vec::new();
6578 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
6579 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
6580 buf.extend_from_slice(&build_empty_vertex_section());
6581 buf.extend_from_slice(&build_empty_face_section());
6582 buf.extend_from_slice(&build_empty_texture_section());
6583 buf.extend_from_slice(&build_empty_material_section());
6584 buf.extend_from_slice(&build_bone_section_header(1));
6585
6586 buf.extend_from_slice(&build_bone_name_bytes("Fixed"));
6587 buf.extend_from_slice(&build_bone_name_bytes(""));
6588 buf.extend_from_slice(&0.0f32.to_le_bytes());
6589 buf.extend_from_slice(&0.0f32.to_le_bytes());
6590 buf.extend_from_slice(&0.0f32.to_le_bytes());
6591 buf.extend_from_slice(&(-1i16).to_le_bytes());
6592 buf.extend_from_slice(&0i32.to_le_bytes());
6593 buf.extend_from_slice(&BONE_FLAG_FIXED_AXIS.to_le_bytes());
6594 buf.extend_from_slice(&0.0f32.to_le_bytes());
6595 buf.extend_from_slice(&0.0f32.to_le_bytes());
6596 buf.extend_from_slice(&0.0f32.to_le_bytes());
6597 buf.extend_from_slice(&0.0f32.to_le_bytes());
6598 buf.extend_from_slice(&1.0f32.to_le_bytes());
6599 buf.extend_from_slice(&0.0f32.to_le_bytes());
6600
6601 let result = import_pmx_model(&buf).unwrap();
6602 assert_eq!(result.bones[0].fixed_axis, Some(Vec3A::Y));
6603 assert!(!result.bones[0].enforce_fixed_axis);
6604 }
6605
6606 #[test]
6607 fn child_inverse_bind_uses_absolute_not_local_position() {
6608 let mut buf = Vec::new();
6609 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
6610 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
6611 buf.extend_from_slice(&build_empty_vertex_section());
6612 buf.extend_from_slice(&build_empty_face_section());
6613 buf.extend_from_slice(&build_empty_texture_section());
6614 buf.extend_from_slice(&build_empty_material_section());
6615 buf.extend_from_slice(&build_bone_section_header(2));
6616
6617 buf.extend_from_slice(&build_bone_name_bytes("Root"));
6618 buf.extend_from_slice(&build_bone_name_bytes(""));
6619 buf.extend_from_slice(&1.0f32.to_le_bytes());
6620 buf.extend_from_slice(&0.0f32.to_le_bytes());
6621 buf.extend_from_slice(&0.0f32.to_le_bytes());
6622 buf.extend_from_slice(&(-1i16).to_le_bytes());
6623 buf.extend_from_slice(&0i32.to_le_bytes());
6624 buf.extend_from_slice(&0u16.to_le_bytes());
6625 buf.extend_from_slice(&0.0f32.to_le_bytes());
6626 buf.extend_from_slice(&0.0f32.to_le_bytes());
6627 buf.extend_from_slice(&0.0f32.to_le_bytes());
6628
6629 buf.extend_from_slice(&build_bone_name_bytes("Child"));
6630 buf.extend_from_slice(&build_bone_name_bytes(""));
6631 buf.extend_from_slice(&5.0f32.to_le_bytes());
6632 buf.extend_from_slice(&0.0f32.to_le_bytes());
6633 buf.extend_from_slice(&0.0f32.to_le_bytes());
6634 buf.extend_from_slice(&0i16.to_le_bytes());
6635 buf.extend_from_slice(&0i32.to_le_bytes());
6636 buf.extend_from_slice(&0u16.to_le_bytes());
6637 buf.extend_from_slice(&0.0f32.to_le_bytes());
6638 buf.extend_from_slice(&0.0f32.to_le_bytes());
6639 buf.extend_from_slice(&0.0f32.to_le_bytes());
6640
6641 let result = import_pmx_model(&buf).unwrap();
6642 assert_eq!(result.bones[1].parent, Some(BoneIndex(0)));
6643 assert_eq!(result.bones[1].rest_position, Vec3A::new(4.0, 0.0, 0.0));
6644 let ibm = result.bones[1].inverse_bind_matrix;
6645 let rest_world = Mat4::from_translation(Vec3::new(5.0, 0.0, 0.0));
6646 assert_eq!(rest_world * ibm, Mat4::IDENTITY);
6647 }
6648
6649 #[test]
6650 fn runtime_rest_pose_skinning_matrices_are_identity() {
6651 let mut buf = Vec::new();
6652 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
6653 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
6654 buf.extend_from_slice(&build_empty_vertex_section());
6655 buf.extend_from_slice(&build_empty_face_section());
6656 buf.extend_from_slice(&build_empty_texture_section());
6657 buf.extend_from_slice(&build_empty_material_section());
6658 buf.extend_from_slice(&build_bone_section_header(2));
6659
6660 buf.extend_from_slice(&build_bone_name_bytes("Parent"));
6661 buf.extend_from_slice(&build_bone_name_bytes(""));
6662 buf.extend_from_slice(&1.0f32.to_le_bytes());
6663 buf.extend_from_slice(&2.0f32.to_le_bytes());
6664 buf.extend_from_slice(&0.0f32.to_le_bytes());
6665 buf.extend_from_slice(&(-1i16).to_le_bytes());
6666 buf.extend_from_slice(&0i32.to_le_bytes());
6667 buf.extend_from_slice(&0u16.to_le_bytes());
6668 buf.extend_from_slice(&0.0f32.to_le_bytes());
6669 buf.extend_from_slice(&0.0f32.to_le_bytes());
6670 buf.extend_from_slice(&0.0f32.to_le_bytes());
6671
6672 buf.extend_from_slice(&build_bone_name_bytes("Child"));
6673 buf.extend_from_slice(&build_bone_name_bytes(""));
6674 buf.extend_from_slice(&1.0f32.to_le_bytes());
6675 buf.extend_from_slice(&3.0f32.to_le_bytes());
6676 buf.extend_from_slice(&0.0f32.to_le_bytes());
6677 buf.extend_from_slice(&0i16.to_le_bytes());
6678 buf.extend_from_slice(&0i32.to_le_bytes());
6679 buf.extend_from_slice(&0u16.to_le_bytes());
6680 buf.extend_from_slice(&0.0f32.to_le_bytes());
6681 buf.extend_from_slice(&0.0f32.to_le_bytes());
6682 buf.extend_from_slice(&0.0f32.to_le_bytes());
6683
6684 buf.extend_from_slice(&0i32.to_le_bytes());
6685
6686 let rt = import_pmx_runtime(&buf).unwrap();
6687 let model = Arc::new(rt.model);
6688 let mut instance = RuntimeInstance::new(model);
6689 instance.evaluate_current_pose();
6690 let matrices = instance.skinning_matrices();
6691 assert_eq!(matrices.len(), 2);
6692 assert_eq!(matrices[0], Mat4::IDENTITY);
6693 assert_eq!(matrices[1], Mat4::IDENTITY);
6694 }
6695
6696 fn build_sdef_vertex_pmx() -> Vec<u8> {
6697 let mut buf = Vec::new();
6700 buf.extend_from_slice(b"PMX ");
6701 buf.extend_from_slice(&2.0f32.to_le_bytes());
6702 buf.push(8); buf.push(1); buf.push(0); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
6712 buf.extend_from_slice(&1i32.to_le_bytes());
6715 buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&1.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.push(3u8); buf.push(0u8); buf.push(0u8); buf.extend_from_slice(&0.25f32.to_le_bytes()); buf.extend_from_slice(&1.0f32.to_le_bytes()); buf.extend_from_slice(&2.0f32.to_le_bytes()); buf.extend_from_slice(&3.0f32.to_le_bytes()); buf.extend_from_slice(&4.0f32.to_le_bytes()); buf.extend_from_slice(&5.0f32.to_le_bytes()); buf.extend_from_slice(&6.0f32.to_le_bytes()); buf.extend_from_slice(&7.0f32.to_le_bytes()); buf.extend_from_slice(&8.0f32.to_le_bytes()); buf.extend_from_slice(&9.0f32.to_le_bytes()); buf.extend_from_slice(&1.0f32.to_le_bytes()); for _ in 0..8 {
6738 buf.extend_from_slice(&0i32.to_le_bytes()); }
6740 buf
6741 }
6742
6743 fn build_qdef_vertex_pmx() -> Vec<u8> {
6744 let mut buf = Vec::new();
6746 buf.extend_from_slice(b"PMX ");
6747 buf.extend_from_slice(&2.0f32.to_le_bytes());
6748 buf.push(8);
6749 buf.push(1); buf.push(0); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
6758 buf.extend_from_slice(&1i32.to_le_bytes());
6760 buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes());
6762 buf.extend_from_slice(&0.0f32.to_le_bytes());
6763 buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&1.0f32.to_le_bytes());
6765 buf.extend_from_slice(&0.0f32.to_le_bytes());
6766 buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes());
6768 buf.push(4u8); buf.push(0u8);
6770 buf.push(0u8);
6771 buf.push(0u8);
6772 buf.push(0u8); buf.extend_from_slice(&1.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&0.0f32.to_le_bytes()); buf.extend_from_slice(&1.0f32.to_le_bytes()); for _ in 0..8 {
6779 buf.extend_from_slice(&0i32.to_le_bytes());
6780 }
6781 buf
6782 }
6783
6784 #[test]
6785 fn pmx_sdef_geometry_json_includes_sdef_fields() {
6786 let buf = build_sdef_vertex_pmx();
6787 let model = parse_pmx_model(&buf).unwrap();
6788 assert_eq!(
6789 model.geometry.sdef.skinning_modes,
6790 vec![PMX_SKINNING_MODE_SDEF.to_owned()]
6791 );
6792 assert_eq!(model.geometry.sdef.enabled.len(), 1);
6793 assert!((model.geometry.sdef.enabled[0] - 1.0).abs() < 1e-5);
6794 assert_eq!(model.geometry.sdef.c, vec![1.0, 2.0, 3.0]);
6795 assert_eq!(model.geometry.sdef.r0, vec![4.0, 5.0, 6.0]);
6796 assert_eq!(model.geometry.sdef.r1, vec![7.0, 8.0, 9.0]);
6797 let w = 0.25f32;
6802 let w1 = 0.75f32;
6803 let c = [1.0f32, 2.0, 3.0];
6804 let r0 = [4.0f32, 5.0, 6.0];
6805 let r1 = [7.0f32, 8.0, 9.0];
6806 for k in 0..3 {
6807 let rw = r0[k] * w + r1[k] * w1;
6808 let expected_rw0 = (c[k] * 2.0 + r0[k] - rw) * 0.5;
6809 let expected_rw1 = (c[k] * 2.0 + r1[k] - rw) * 0.5;
6810 assert!(
6811 (model.geometry.sdef.rw0[k] - expected_rw0).abs() < 1e-4,
6812 "rw0[{k}]: expected {expected_rw0}, got {}",
6813 model.geometry.sdef.rw0[k]
6814 );
6815 assert!(
6816 (model.geometry.sdef.rw1[k] - expected_rw1).abs() < 1e-4,
6817 "rw1[{k}]: expected {expected_rw1}, got {}",
6818 model.geometry.sdef.rw1[k]
6819 );
6820 }
6821 assert_eq!(model.geometry.qdef.enabled[0], 0.0);
6822 let json = serde_json::to_string(&model).unwrap();
6824 assert!(json.contains("\"sdef\""), "JSON must contain sdef key");
6825 assert!(json.contains("\"qdef\""), "JSON must contain qdef key");
6826 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
6827 assert_eq!(parsed["geometry"]["sdef"]["enabled"][0], 1.0);
6828 }
6829
6830 #[test]
6831 fn pmx_qdef_vertex_marks_enabled() {
6832 let buf = build_qdef_vertex_pmx();
6833 let model = parse_pmx_model(&buf).unwrap();
6834 assert_eq!(
6835 model.geometry.sdef.skinning_modes,
6836 vec![PMX_SKINNING_MODE_QDEF.to_owned()]
6837 );
6838 assert_eq!(model.geometry.qdef.enabled.len(), 1);
6839 assert!((model.geometry.qdef.enabled[0] - 1.0).abs() < 1e-5);
6840 assert_eq!(model.geometry.sdef.enabled[0], 0.0);
6841 }
6842
6843 #[test]
6844 fn exports_parsed_pmx_deform_type_binary_roundtrip() {
6845 for original in [build_sdef_vertex_pmx(), build_qdef_vertex_pmx()] {
6846 let parsed = parse_pmx_model(&original).unwrap();
6847 let exported = export_pmx_model(&parsed);
6848 assert_eq!(exported, original);
6849 }
6850 }
6851}