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