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
13const PMX_MAGIC: [u8; 4] = [0x50, 0x4D, 0x58, 0x20];
14
15const BONE_FLAG_TAIL_INDEX: u16 = 0x0001;
16const BONE_FLAG_IK: u16 = 0x0020;
17const BONE_FLAG_LOCAL_APPEND: u16 = 0x0080;
18const BONE_FLAG_APPEND_ROTATE: u16 = 0x0100;
19const BONE_FLAG_APPEND_TRANSLATE: u16 = 0x0200;
20const BONE_FLAG_FIXED_AXIS: u16 = 0x0400;
21const BONE_FLAG_LOCAL_AXIS: u16 = 0x0800;
22const BONE_FLAG_EXTERNAL_PARENT: u16 = 0x2000;
23
24fn decode_utf16le_lossy(bytes: &[u8]) -> String {
25 let end = bytes.len().saturating_sub(bytes.len() % 2);
26 let units = bytes[..end]
27 .chunks_exact(2)
28 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
29 .take_while(|&code| code != 0);
30 std::char::decode_utf16(units)
31 .map(|item| item.unwrap_or(char::REPLACEMENT_CHARACTER))
32 .collect()
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum TextEncoding {
37 Utf16Le = 0,
38 Utf8 = 1,
39}
40
41#[derive(Debug, Clone)]
42pub struct PmxHeader {
43 pub version: f32,
44 pub encoding: TextEncoding,
45 pub extra_uv_count: u8,
46 pub vertex_index_size: u8,
47 pub texture_index_size: u8,
48 pub material_index_size: u8,
49 pub bone_index_size: u8,
50 pub morph_index_size: u8,
51 pub rigidbody_index_size: u8,
52}
53
54struct Reader<'a> {
55 data: &'a [u8],
56 pos: usize,
57}
58
59impl<'a> Reader<'a> {
60 fn new(data: &'a [u8]) -> Self {
61 Self { data, pos: 0 }
62 }
63
64 fn remaining(&self) -> usize {
65 self.data.len().saturating_sub(self.pos)
66 }
67
68 fn require(&self, n: usize) -> Result<(), ImportError> {
69 if self.remaining() >= n {
70 Ok(())
71 } else {
72 Err(ImportError::UnexpectedEof(
73 n.saturating_sub(self.remaining()),
74 ))
75 }
76 }
77
78 fn read_bytes(&mut self, n: usize) -> Result<&'a [u8], ImportError> {
79 self.require(n)?;
80 let slice = &self.data[self.pos..self.pos + n];
81 self.pos += n;
82 Ok(slice)
83 }
84
85 fn read_u8(&mut self) -> Result<u8, ImportError> {
86 Ok(self.read_bytes(1)?[0])
87 }
88
89 fn read_u16_le(&mut self) -> Result<u16, ImportError> {
90 let b = self.read_bytes(2)?;
91 Ok(u16::from_le_bytes([b[0], b[1]]))
92 }
93
94 fn read_i32_le(&mut self) -> Result<i32, ImportError> {
95 let b = self.read_bytes(4)?;
96 Ok(i32::from_le_bytes([b[0], b[1], b[2], b[3]]))
97 }
98
99 fn read_f32_le(&mut self) -> Result<f32, ImportError> {
100 let b = self.read_bytes(4)?;
101 Ok(f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
102 }
103
104 fn read_vec3(&mut self) -> Result<Vec3A, ImportError> {
105 Ok(Vec3A::new(
106 self.read_f32_le()?,
107 self.read_f32_le()?,
108 self.read_f32_le()?,
109 ))
110 }
111
112 fn read_vec2_array(&mut self) -> Result<[f32; 2], ImportError> {
113 Ok([self.read_f32_le()?, self.read_f32_le()?])
114 }
115
116 fn read_vec3_array(&mut self) -> Result<[f32; 3], ImportError> {
117 Ok([
118 self.read_f32_le()?,
119 self.read_f32_le()?,
120 self.read_f32_le()?,
121 ])
122 }
123
124 fn read_vec4_array(&mut self) -> Result<[f32; 4], ImportError> {
125 Ok([
126 self.read_f32_le()?,
127 self.read_f32_le()?,
128 self.read_f32_le()?,
129 self.read_f32_le()?,
130 ])
131 }
132
133 fn read_sized_index(&mut self, size: u8) -> Result<i32, ImportError> {
134 match size {
135 1 => Ok(self.read_u8()? as i8 as i32),
136 2 => Ok({
137 let b = self.read_bytes(2)?;
138 i16::from_le_bytes([b[0], b[1]]) as i32
139 }),
140 4 => self.read_i32_le(),
141 _ => Err(ImportError::InvalidIndexSize(size)),
142 }
143 }
144
145 fn read_index(&mut self, header: &PmxHeader) -> Result<i32, ImportError> {
146 self.read_sized_index(header.bone_index_size)
147 }
148
149 fn read_vertex_index(&mut self, size: u8) -> Result<u32, ImportError> {
150 match size {
151 1 => Ok(self.read_u8()? as u32),
152 2 => Ok(self.read_u16_le()? as u32),
153 4 => {
154 let raw = self.read_i32_le()?;
155 if raw < 0 {
156 Err(ImportError::SectionOverflow)
157 } else {
158 Ok(raw as u32)
159 }
160 }
161 _ => Err(ImportError::InvalidIndexSize(size)),
162 }
163 }
164
165 fn skip(&mut self, n: usize) -> Result<(), ImportError> {
166 self.require(n)?;
167 self.pos += n;
168 Ok(())
169 }
170
171 fn read_string(&mut self, encoding: TextEncoding) -> Result<String, ImportError> {
172 let len = self.read_i32_le()?;
173 if len <= 0 {
174 return Ok(String::new());
175 }
176 let bytes = self.read_bytes(len as usize)?;
177 match encoding {
178 TextEncoding::Utf8 => {
179 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
180 Ok(String::from_utf8_lossy(&bytes[..end]).into_owned())
181 }
182 TextEncoding::Utf16Le => Ok(decode_utf16le_lossy(bytes)),
183 }
184 }
185
186 fn read_string_owned(
187 &mut self,
188 encoding: TextEncoding,
189 ) -> Result<(Vec<u8>, String), ImportError> {
190 let len = self.read_i32_le()?;
191 if len <= 0 {
192 return Ok((Vec::new(), String::new()));
193 }
194 let bytes = self.read_bytes(len as usize)?.to_vec();
195 let decoded = match encoding {
196 TextEncoding::Utf8 => {
197 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
198 String::from_utf8_lossy(&bytes[..end]).into_owned()
199 }
200 TextEncoding::Utf16Le => decode_utf16le_lossy(&bytes),
201 };
202 Ok((bytes, decoded))
203 }
204
205 fn read_bone_index(&mut self, header: &PmxHeader) -> Result<Option<BoneIndex>, ImportError> {
206 let raw = self.read_sized_index(header.bone_index_size)?;
207 if raw < 0 {
208 Ok(None)
209 } else {
210 Ok(Some(BoneIndex(raw as u32)))
211 }
212 }
213}
214
215pub fn read_header(data: &[u8]) -> Result<(PmxHeader, usize), ImportError> {
216 let mut r = Reader::new(data);
217
218 let magic = r.read_bytes(4)?;
219 if magic != PMX_MAGIC {
220 return Err(ImportError::InvalidPmxMagic);
221 }
222
223 let version = r.read_f32_le()?;
224 if !(2.0..=2.2).contains(&version) {
225 return Err(ImportError::UnsupportedPmxVersion(version));
226 }
227
228 let data_count = r.read_u8()?;
229 if data_count < 8 {
230 return Err(ImportError::InvalidEncoding(data_count));
231 }
232
233 let encoding_byte = r.read_u8()?;
234 let encoding = match encoding_byte {
235 0 => TextEncoding::Utf16Le,
236 1 => TextEncoding::Utf8,
237 _ => return Err(ImportError::InvalidEncoding(encoding_byte)),
238 };
239
240 let extra_uv_count = r.read_u8()?;
241 let vertex_index_size = r.read_u8()?;
242 let texture_index_size = r.read_u8()?;
243 let material_index_size = r.read_u8()?;
244 let bone_index_size = r.read_u8()?;
245 let morph_index_size = r.read_u8()?;
246 let rigidbody_index_size = r.read_u8()?;
247 if data_count > 8 {
248 r.skip((data_count - 8) as usize)?;
249 }
250
251 let header = PmxHeader {
252 version,
253 encoding,
254 extra_uv_count,
255 vertex_index_size,
256 texture_index_size,
257 material_index_size,
258 bone_index_size,
259 morph_index_size,
260 rigidbody_index_size,
261 };
262
263 Ok((header, r.pos))
264}
265
266pub fn skip_model_info(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
267 let mut r = Reader { data, pos };
268 let _model_name = r.read_string(header.encoding)?;
269 let _english_name = r.read_string(header.encoding)?;
270 let _comment = r.read_string(header.encoding)?;
271 let _english_comment = r.read_string(header.encoding)?;
272 Ok(r.pos)
273}
274
275pub fn skip_vertices(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
276 let mut r = Reader { data, pos };
277 let count = r.read_i32_le()?;
278 if count < 0 {
279 return Err(ImportError::SectionOverflow);
280 }
281 let count = count as usize;
282
283 for _ in 0..count {
284 r.skip(12)?;
285 r.skip(12)?;
286 r.skip(8)?;
287 r.skip(header.extra_uv_count as usize * 16)?;
288 let vertex_deform_type = r.read_u8()?;
289
290 let bone_indices: usize = match vertex_deform_type {
291 0 => 1,
292 1 => 2,
293 2 | 4 => 4,
294 3 => 2,
295 _ => return Err(ImportError::SectionOverflow),
296 };
297 for _ in 0..bone_indices {
298 r.read_sized_index(header.bone_index_size)?;
299 }
300
301 let weight_count: usize = match vertex_deform_type {
302 0 => 0,
303 1 => 1,
304 2 | 4 => 4,
305 3 => 1,
306 _ => return Err(ImportError::SectionOverflow),
307 };
308 r.skip(weight_count * 4)?;
309
310 if vertex_deform_type == 3 {
311 r.skip(36)?;
312 }
313
314 r.skip(4)?;
315 }
316
317 Ok(r.pos)
318}
319
320pub fn skip_faces(data: &[u8], vertex_index_size: u8, pos: usize) -> Result<usize, ImportError> {
321 let mut r = Reader { data, pos };
322 let count = r.read_i32_le()?;
323 if count < 0 {
324 return Err(ImportError::SectionOverflow);
325 }
326 let count = count as usize;
327 r.skip(count * vertex_index_size as usize)?;
328 Ok(r.pos)
329}
330
331pub fn skip_textures(
332 data: &[u8],
333 encoding: TextEncoding,
334 pos: usize,
335) -> Result<usize, ImportError> {
336 let mut r = Reader { data, pos };
337 let count = r.read_i32_le()?;
338 if count < 0 {
339 return Err(ImportError::SectionOverflow);
340 }
341 for _ in 0..count {
342 let _path = r.read_string(encoding)?;
343 }
344 Ok(r.pos)
345}
346
347pub fn skip_materials(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
348 let mut r = Reader { data, pos };
349 let count = r.read_i32_le()?;
350 if count < 0 {
351 return Err(ImportError::SectionOverflow);
352 }
353 for _ in 0..count {
354 let _name = r.read_string(header.encoding)?;
355 let _english_name = r.read_string(header.encoding)?;
356 r.skip(16)?;
357 r.skip(16)?;
358 r.skip(12)?;
359 r.skip(1)?;
360 r.skip(16)?;
361 r.skip(4)?;
362 let _texture = r.read_sized_index(header.texture_index_size)?;
363 let _sphere_tex = r.read_sized_index(header.texture_index_size)?;
364 r.skip(1)?;
365 let _toon = r.read_u8()?;
366 if _toon == 0 {
367 r.read_sized_index(header.texture_index_size)?;
368 } else {
369 r.skip(1)?;
370 }
371 let _memo = r.read_string(header.encoding)?;
372 let _face_count = r.read_i32_le()?;
373 }
374 Ok(r.pos)
375}
376
377pub fn skip_morphs(data: &[u8], header: &PmxHeader, pos: usize) -> Result<usize, ImportError> {
378 let mut r = Reader { data, pos };
379 let count = r.read_i32_le()?;
380 if count < 0 {
381 return Err(ImportError::SectionOverflow);
382 }
383 for _ in 0..count {
384 let _name = r.read_string(header.encoding)?;
385 let _english_name = r.read_string(header.encoding)?;
386 r.skip(1)?;
387 let _morph_type = r.read_u8()?;
388 let _offset_count = r.read_i32_le()?;
389 match _morph_type {
390 0 => {
391 for _ in 0.._offset_count {
392 r.read_sized_index(header.morph_index_size)?;
393 r.skip(4)?;
394 }
395 }
396 1 => {
397 for _ in 0.._offset_count {
398 r.read_sized_index(header.vertex_index_size)?;
399 r.skip(12)?;
400 }
401 }
402 2 => {
403 for _ in 0.._offset_count {
404 r.read_sized_index(header.bone_index_size)?;
405 r.skip(12)?;
406 r.skip(16)?;
407 }
408 }
409 3..=7 => {
410 for _ in 0.._offset_count {
411 r.read_sized_index(header.vertex_index_size)?;
412 r.skip(16)?;
413 }
414 }
415 8 => {
416 for _ in 0.._offset_count {
417 r.read_sized_index(header.material_index_size)?;
418 r.skip(1)?;
419 r.skip(16)?;
420 r.skip(12)?;
421 r.skip(4)?;
422 r.skip(12)?;
423 r.skip(16)?;
424 r.skip(4)?;
425 r.skip(16)?;
426 r.skip(16)?;
427 r.skip(16)?;
428 }
429 }
430 9 => {
431 for _ in 0.._offset_count {
432 r.read_sized_index(header.rigidbody_index_size)?;
433 r.skip(1)?;
434 r.skip(12)?;
435 r.skip(12)?;
436 }
437 }
438 _ => return Err(ImportError::SectionOverflow),
439 }
440 }
441 Ok(r.pos)
442}
443
444#[derive(Debug, Clone)]
445pub struct PmxMorphNames {
446 pub name_bytes: Vec<Vec<u8>>,
447 pub names: Vec<String>,
448}
449
450pub fn read_morph_names(
451 data: &[u8],
452 header: &PmxHeader,
453 pos: usize,
454) -> Result<(PmxMorphNames, usize), ImportError> {
455 let mut r = Reader { data, pos };
456 let count = r.read_i32_le()?;
457 if count < 0 {
458 return Err(ImportError::SectionOverflow);
459 }
460 let count = count as usize;
461
462 let mut name_bytes = Vec::with_capacity(count);
463 let mut names = Vec::with_capacity(count);
464
465 for _ in 0..count {
466 let (raw, name) = r.read_string_owned(header.encoding)?;
467 let _english_name = r.read_string(header.encoding)?;
468 name_bytes.push(raw);
469 names.push(name);
470
471 r.skip(1)?;
472 let morph_type = r.read_u8()?;
473 let offset_count = r.read_i32_le()?;
474 if offset_count < 0 {
475 return Err(ImportError::SectionOverflow);
476 }
477 let offset_count = offset_count as usize;
478
479 let per_offset = match morph_type {
480 0 => header.morph_index_size as usize + 4,
481 1 => header.vertex_index_size as usize + 12,
482 2 => header.bone_index_size as usize + 12 + 16,
483 3..=7 => header.vertex_index_size as usize + 16,
484 8 => header.material_index_size as usize + 1 + 16 + 12 + 4 + 12 + 16 + 4 + 16 + 16 + 16,
485 9 => header.rigidbody_index_size as usize + 1 + 12 + 12,
486 _ => return Err(ImportError::SectionOverflow),
487 };
488 r.skip(
489 offset_count
490 .checked_mul(per_offset)
491 .ok_or(ImportError::SectionOverflow)?,
492 )?;
493 }
494
495 Ok((PmxMorphNames { name_bytes, names }, r.pos))
496}
497
498pub fn read_morph_offsets(
504 data: &[u8],
505 header: &PmxHeader,
506 pos: usize,
507) -> Result<(PmxMorphNames, MorphInit, usize), ImportError> {
508 let mut r = Reader { data, pos };
509 let count = r.read_i32_le()?;
510 if count < 0 {
511 return Err(ImportError::SectionOverflow);
512 }
513 let count = count as usize;
514
515 let mut name_bytes = Vec::with_capacity(count);
516 let mut names = Vec::with_capacity(count);
517
518 let mut bone_offsets_all: Vec<BoneMorphOffset> = Vec::new();
519 let mut bone_spans: Vec<MorphOffsetSpan> = Vec::with_capacity(count);
520 let mut group_offsets_all: Vec<GroupMorphOffset> = Vec::new();
521 let mut group_spans: Vec<MorphOffsetSpan> = Vec::with_capacity(count);
522
523 for _ in 0..count {
524 let (raw, name) = r.read_string_owned(header.encoding)?;
525 let _english_name = r.read_string(header.encoding)?;
526 name_bytes.push(raw);
527 names.push(name);
528
529 r.skip(1)?;
530 let morph_type = r.read_u8()?;
531 let offset_count = r.read_i32_le()?;
532 if offset_count < 0 {
533 return Err(ImportError::SectionOverflow);
534 }
535 let offset_count = offset_count as usize;
536
537 match morph_type {
538 0 => {
539 let start = group_offsets_all.len() as u32;
540 for _ in 0..offset_count {
541 let child_raw = r.read_sized_index(header.morph_index_size)?;
542 if child_raw < 0 {
543 return Err(ImportError::SectionOverflow);
544 }
545 let ratio = r.read_f32_le()?;
546 group_offsets_all.push(GroupMorphOffset {
547 child_morph: MorphIndex(child_raw as u32),
548 ratio,
549 });
550 }
551 group_spans.push(MorphOffsetSpan {
552 start,
553 count: offset_count as u32,
554 });
555 bone_spans.push(MorphOffsetSpan::default());
556 }
557 2 => {
558 let start = bone_offsets_all.len() as u32;
559 for _ in 0..offset_count {
560 let bone_raw = r.read_sized_index(header.bone_index_size)?;
561 if bone_raw < 0 {
562 return Err(ImportError::SectionOverflow);
563 }
564 let pos = r.read_vec3()?;
565 let qx = r.read_f32_le()?;
566 let qy = r.read_f32_le()?;
567 let qz = r.read_f32_le()?;
568 let qw = r.read_f32_le()?;
569 bone_offsets_all.push(BoneMorphOffset {
570 target_bone: BoneIndex(bone_raw as u32),
571 position_offset: pos,
572 rotation_offset: Quat::from_xyzw(qx, qy, qz, qw),
573 });
574 }
575 bone_spans.push(MorphOffsetSpan {
576 start,
577 count: offset_count as u32,
578 });
579 group_spans.push(MorphOffsetSpan::default());
580 }
581 _ => {
582 let per_offset = match morph_type {
583 1 => header.vertex_index_size as usize + 12,
584 3..=7 => header.vertex_index_size as usize + 16,
585 8 => {
586 header.material_index_size as usize
587 + 1
588 + 16
589 + 12
590 + 4
591 + 12
592 + 16
593 + 4
594 + 16
595 + 16
596 + 16
597 }
598 9 => header.rigidbody_index_size as usize + 1 + 12 + 12,
599 _ => return Err(ImportError::SectionOverflow),
600 };
601 r.skip(
602 offset_count
603 .checked_mul(per_offset)
604 .ok_or(ImportError::SectionOverflow)?,
605 )?;
606 bone_spans.push(MorphOffsetSpan::default());
607 group_spans.push(MorphOffsetSpan::default());
608 }
609 }
610 }
611
612 Ok((
613 PmxMorphNames { name_bytes, names },
614 MorphInit {
615 morph_count: count as u32,
616 vertex_spans: vec![MorphOffsetSpan::default(); count],
617 bone_offsets: bone_offsets_all,
618 bone_spans,
619 group_offsets: group_offsets_all,
620 group_spans,
621 ..MorphInit::default()
622 },
623 r.pos,
624 ))
625}
626
627#[derive(Debug, Clone)]
628pub struct PmxBoneImport {
629 pub bones: Vec<BoneInit>,
630 pub ik_solvers: Vec<IkSolverInit>,
631 pub append_transforms: Vec<AppendTransformInit>,
632 pub bone_name_bytes: Vec<Vec<u8>>,
633 pub bone_names: Vec<String>,
634}
635
636pub fn read_bones(
637 data: &[u8],
638 header: &PmxHeader,
639 pos: usize,
640) -> Result<(PmxBoneImport, usize), ImportError> {
641 let mut r = Reader { data, pos };
642 let count = r.read_i32_le()?;
643 if count < 0 {
644 return Err(ImportError::SectionOverflow);
645 }
646 let count = count as usize;
647
648 let mut bones = Vec::with_capacity(count);
649 let mut ik_solvers = Vec::new();
650 let mut append_transforms = Vec::new();
651
652 let mut bone_name_bytes = Vec::with_capacity(count);
653 let mut bone_names = Vec::with_capacity(count);
654
655 let mut absolute_positions: Vec<Vec3A> = Vec::with_capacity(count);
656
657 for _bone_i in 0..count {
658 let (name_bytes, name) = r.read_string_owned(header.encoding)?;
659 let _english_name = r.read_string(header.encoding)?;
660 let position = r.read_vec3()?;
661 absolute_positions.push(position);
662 let parent = r.read_bone_index(header)?;
663 let transform_order = r.read_i32_le()?;
664 let flags = r.read_u16_le()?;
665
666 if flags & BONE_FLAG_TAIL_INDEX != 0 {
667 r.read_index(header)?;
668 } else {
669 r.read_vec3()?;
670 }
671
672 let has_append =
673 flags & BONE_FLAG_APPEND_ROTATE != 0 || flags & BONE_FLAG_APPEND_TRANSLATE != 0;
674
675 if has_append {
676 let append_parent = r.read_bone_index(header)?;
677 let append_ratio = r.read_f32_le()?;
678
679 if let Some(source) = append_parent {
680 let target_idx = bones.len();
681 append_transforms.push(
682 AppendTransformInit::new(BoneIndex(target_idx as u32), source, append_ratio)
683 .with_rotation_if(flags & BONE_FLAG_APPEND_ROTATE != 0)
684 .with_translation_if(flags & BONE_FLAG_APPEND_TRANSLATE != 0)
685 .with_local_if(flags & BONE_FLAG_LOCAL_APPEND != 0),
686 );
687 }
688 }
689
690 let fixed_axis = if flags & BONE_FLAG_FIXED_AXIS != 0 {
691 Some(r.read_vec3()?)
692 } else {
693 None
694 };
695
696 if flags & BONE_FLAG_LOCAL_AXIS != 0 {
697 r.read_vec3()?;
698 r.read_vec3()?;
699 }
700
701 if flags & BONE_FLAG_EXTERNAL_PARENT != 0 {
702 r.read_i32_le()?;
703 }
704
705 if flags & BONE_FLAG_IK != 0 {
706 let ik_target = r.read_bone_index(header)?;
707 let ik_loop_count = r.read_i32_le()?;
708 let ik_limit_angle = r.read_f32_le()?;
709 let link_count = r.read_i32_le()?;
710 if link_count < 0 {
711 return Err(ImportError::SectionOverflow);
712 }
713
714 let mut links = Vec::with_capacity(link_count as usize);
715 for _ in 0..link_count {
716 let link_bone = r.read_bone_index(header)?.unwrap_or(BoneIndex(0));
717 let has_limit = r.read_u8()?;
718 let angle_limit = if has_limit != 0 {
719 let min = r.read_vec3()?;
720 let max = r.read_vec3()?;
721 Some(IkAngleLimit::new(min, max))
722 } else {
723 None
724 };
725 links.push(match angle_limit {
726 Some(lim) => IkLinkInit::new(link_bone).with_angle_limit(lim),
727 None => IkLinkInit::new(link_bone),
728 });
729 }
730
731 let ik_bone = BoneIndex(bones.len() as u32);
732 if let Some(target) = ik_target {
733 ik_solvers.push(IkSolverInit {
734 ik_bone,
735 target_bone: target,
736 links,
737 iteration_count: ik_loop_count as u32,
738 limit_angle: ik_limit_angle,
739 });
740 }
741 }
742
743 bone_name_bytes.push(name_bytes);
744 bone_names.push(name);
745
746 bones.push(BoneInit {
747 parent,
748 rest_position: position,
749 inverse_bind_matrix: Mat4::from_translation((-position).into()),
750 transform_order,
751 fixed_axis,
752 });
753 }
754
755 for bone in bones.iter_mut() {
756 if let Some(parent_idx) = bone.parent
757 && let Some(parent_abs) = absolute_positions.get(parent_idx.as_usize())
758 {
759 bone.rest_position -= parent_abs;
760 }
761 }
762
763 Ok((
764 PmxBoneImport {
765 bones,
766 ik_solvers,
767 append_transforms,
768 bone_name_bytes,
769 bone_names,
770 },
771 r.pos,
772 ))
773}
774
775pub fn import_pmx_model(data: &[u8]) -> Result<PmxBoneImport, ImportError> {
776 let (header, pos) = read_header(data)?;
777 let pos = skip_model_info(data, &header, pos)?;
778 let pos = skip_vertices(data, &header, pos)?;
779 let pos = skip_faces(data, header.vertex_index_size, pos)?;
780 let pos = skip_textures(data, header.encoding, pos)?;
781 let pos = skip_materials(data, &header, pos)?;
782 let (bones, _pos) = read_bones(data, &header, pos)?;
783 Ok(bones)
784}
785
786#[derive(Debug, Clone, Serialize, Deserialize)]
787#[serde(rename_all = "camelCase")]
788pub struct PmxParsedModel {
789 pub metadata: PmxParsedMetadata,
790 pub geometry: PmxParsedGeometry,
791 pub materials: Vec<PmxParsedMaterial>,
792 pub skeleton: PmxParsedSkeleton,
793 pub morphs: Vec<PmxParsedMorph>,
794 pub display_frames: Vec<PmxParsedDisplayFrame>,
795 pub rigid_bodies: Vec<PmxParsedRigidBody>,
796 pub joints: Vec<PmxParsedJoint>,
797 pub soft_bodies: Vec<PmxParsedSoftBody>,
798 pub diagnostics: Vec<PmxParserDiagnostic>,
799}
800
801#[derive(Debug, Clone, Serialize, Deserialize)]
802#[serde(rename_all = "camelCase")]
803pub struct PmxParsedMetadata {
804 #[serde(default = "default_pmx_format")]
805 pub format: String,
806 pub version: f32,
807 pub encoding: String,
808 pub name: String,
809 pub english_name: String,
810 pub comment: String,
811 pub english_comment: String,
812 pub counts: PmxParsedCounts,
813 pub index_sizes: PmxParsedIndexSizes,
814 pub additional_uv_count: u8,
815}
816
817#[derive(Debug, Clone, Serialize, Deserialize)]
818#[serde(rename_all = "camelCase")]
819pub struct PmxParsedCounts {
820 pub vertices: usize,
821 pub faces: usize,
822 pub materials: usize,
823 pub bones: usize,
824 pub morphs: usize,
825 pub display_frames: usize,
826 pub rigid_bodies: usize,
827 pub joints: usize,
828 pub soft_bodies: usize,
829}
830
831#[derive(Debug, Clone, Serialize, Deserialize)]
832pub struct PmxParsedIndexSizes {
833 pub vertex: u8,
834 pub texture: u8,
835 pub material: u8,
836 pub bone: u8,
837 pub morph: u8,
838 #[serde(rename = "rigidBody")]
839 pub rigid_body: u8,
840}
841
842#[derive(Debug, Clone, Serialize, Deserialize)]
843#[serde(rename_all = "camelCase")]
844pub struct PmxParsedGeometry {
845 pub positions: Vec<f32>,
846 pub normals: Vec<f32>,
847 pub uvs: Vec<f32>,
848 pub additional_uvs: Vec<Vec<f32>>,
849 pub indices: Vec<u32>,
850 pub skin_indices: Vec<u32>,
851 pub skin_weights: Vec<f32>,
852 pub edge_scale: Vec<f32>,
853 pub material_groups: Vec<PmxParsedMaterialGroup>,
854}
855
856#[derive(Debug, Clone, Serialize, Deserialize)]
857#[serde(rename_all = "camelCase")]
858pub struct PmxParsedMaterialGroup {
859 pub start: usize,
860 pub count: usize,
861 pub material_index: usize,
862}
863
864#[derive(Debug, Clone, Serialize, Deserialize)]
865#[serde(rename_all = "camelCase")]
866pub struct PmxParsedMaterial {
867 pub name: String,
868 pub english_name: String,
869 pub texture_path: String,
870 pub sphere_texture_path: String,
871 pub sphere_mode: String,
872 pub toon_texture_path: String,
873 pub shared_toon_index: Option<u8>,
874 pub diffuse: [f32; 4],
875 pub specular: [f32; 3],
876 pub specular_power: f32,
877 pub ambient: [f32; 3],
878 pub edge_color: [f32; 4],
879 pub edge_size: f32,
880 pub flags: PmxParsedMaterialFlags,
881 pub face_count: i32,
882}
883
884#[derive(Debug, Clone, Serialize, Deserialize)]
885#[serde(rename_all = "camelCase")]
886pub struct PmxParsedMaterialFlags {
887 pub double_sided: bool,
888 pub ground_shadow: bool,
889 pub self_shadow_map: bool,
890 pub self_shadow: bool,
891 pub edge: bool,
892 pub vertex_color: bool,
893 pub point_draw: bool,
894 pub line_draw: bool,
895}
896
897#[derive(Debug, Clone, Serialize, Deserialize)]
898pub struct PmxParsedSkeleton {
899 pub bones: Vec<PmxParsedBone>,
900}
901
902#[derive(Debug, Clone, Serialize, Deserialize)]
903#[serde(rename_all = "camelCase")]
904pub struct PmxParsedBone {
905 pub name: String,
906 pub english_name: String,
907 pub parent_index: i32,
908 pub layer: i32,
909 pub position: [f32; 3],
910 pub tail_index: i32,
911 pub tail_position: Option<[f32; 3]>,
912 pub flags: PmxParsedBoneFlags,
913 pub append_transform: Option<PmxParsedAppendTransform>,
914 pub fixed_axis: Option<[f32; 3]>,
915 pub local_axis: Option<PmxParsedLocalAxis>,
916 pub external_parent_key: Option<i32>,
917 pub ik: Option<PmxParsedIk>,
918}
919
920#[derive(Debug, Clone, Serialize, Deserialize)]
921#[serde(rename_all = "camelCase")]
922pub struct PmxParsedBoneFlags {
923 pub indexed_tail: bool,
924 pub rotatable: bool,
925 pub translatable: bool,
926 pub visible: bool,
927 pub enabled: bool,
928 pub ik: bool,
929 pub append_local: bool,
930 pub append_rotate: bool,
931 pub append_translate: bool,
932 pub fixed_axis: bool,
933 pub local_axis: bool,
934 pub transform_after_physics: bool,
935 pub external_parent_transform: bool,
936}
937
938#[derive(Debug, Clone, Serialize, Deserialize)]
939#[serde(rename_all = "camelCase")]
940pub struct PmxParsedAppendTransform {
941 pub parent_index: i32,
942 pub weight: f32,
943}
944
945#[derive(Debug, Clone, Serialize, Deserialize)]
946pub struct PmxParsedLocalAxis {
947 pub x: [f32; 3],
948 pub z: [f32; 3],
949}
950
951#[derive(Debug, Clone, Serialize, Deserialize)]
952#[serde(rename_all = "camelCase")]
953pub struct PmxParsedIk {
954 pub target_index: i32,
955 pub loop_count: i32,
956 pub limit_angle: f32,
957 pub links: Vec<PmxParsedIkLink>,
958}
959
960#[derive(Debug, Clone, Serialize, Deserialize)]
961#[serde(rename_all = "camelCase")]
962pub struct PmxParsedIkLink {
963 pub bone_index: i32,
964 pub limits: Option<PmxParsedIkLimit>,
965}
966
967#[derive(Debug, Clone, Serialize, Deserialize)]
968pub struct PmxParsedIkLimit {
969 pub lower: [f32; 3],
970 pub upper: [f32; 3],
971}
972
973#[derive(Debug, Clone, Serialize, Deserialize)]
974#[serde(rename_all = "camelCase")]
975pub struct PmxParsedMorph {
976 pub name: String,
977 pub english_name: String,
978 #[serde(rename = "type")]
979 pub kind: String,
980 pub vertex_offsets: Vec<PmxParsedVertexMorphOffset>,
981 pub group_offsets: Vec<PmxParsedGroupMorphOffset>,
982 pub bone_offsets: Vec<PmxParsedBoneMorphOffset>,
983 pub uv_offsets: Vec<PmxParsedUvMorphOffset>,
984 pub additional_uv_offsets: Vec<PmxParsedAdditionalUvMorphOffset>,
985 pub material_offsets: Vec<PmxParsedMaterialMorphOffset>,
986 pub flip_offsets: Vec<PmxParsedGroupMorphOffset>,
987 pub impulse_offsets: Vec<PmxParsedImpulseMorphOffset>,
988}
989
990#[derive(Debug, Clone, Serialize, Deserialize)]
991#[serde(rename_all = "camelCase")]
992pub struct PmxParsedVertexMorphOffset {
993 pub vertex_index: u32,
994 pub position: [f32; 3],
995}
996
997#[derive(Debug, Clone, Serialize, Deserialize)]
998#[serde(rename_all = "camelCase")]
999pub struct PmxParsedGroupMorphOffset {
1000 pub morph_index: i32,
1001 pub weight: f32,
1002}
1003
1004#[derive(Debug, Clone, Serialize, Deserialize)]
1005#[serde(rename_all = "camelCase")]
1006pub struct PmxParsedBoneMorphOffset {
1007 pub bone_index: i32,
1008 pub translation: [f32; 3],
1009 pub rotation: [f32; 4],
1010}
1011
1012#[derive(Debug, Clone, Serialize, Deserialize)]
1013#[serde(rename_all = "camelCase")]
1014pub struct PmxParsedUvMorphOffset {
1015 pub vertex_index: u32,
1016 pub uv: [f32; 4],
1017}
1018
1019#[derive(Debug, Clone, Serialize, Deserialize)]
1020#[serde(rename_all = "camelCase")]
1021pub struct PmxParsedAdditionalUvMorphOffset {
1022 pub vertex_index: u32,
1023 pub uv_index: u8,
1024 pub uv: [f32; 4],
1025}
1026
1027#[derive(Debug, Clone, Serialize, Deserialize)]
1028#[serde(rename_all = "camelCase")]
1029pub struct PmxParsedMaterialMorphOffset {
1030 pub material_index: i32,
1031 pub operation: String,
1032 pub diffuse: [f32; 4],
1033 pub specular: [f32; 3],
1034 pub specular_power: f32,
1035 pub ambient: [f32; 3],
1036 pub edge_color: [f32; 4],
1037 pub edge_size: f32,
1038 pub texture_factor: [f32; 4],
1039 pub sphere_texture_factor: [f32; 4],
1040 pub toon_texture_factor: [f32; 4],
1041}
1042
1043#[derive(Debug, Clone, Serialize, Deserialize)]
1044#[serde(rename_all = "camelCase")]
1045pub struct PmxParsedImpulseMorphOffset {
1046 pub rigid_body_index: i32,
1047 pub local: bool,
1048 pub velocity: [f32; 3],
1049 pub torque: [f32; 3],
1050}
1051
1052#[derive(Debug, Clone, Serialize, Deserialize)]
1053#[serde(rename_all = "camelCase")]
1054pub struct PmxParsedDisplayFrame {
1055 pub name: String,
1056 pub english_name: String,
1057 pub special: bool,
1058 pub frames: Vec<PmxParsedDisplayFrameElement>,
1059}
1060
1061#[derive(Debug, Clone, Serialize, Deserialize)]
1062pub struct PmxParsedDisplayFrameElement {
1063 #[serde(rename = "type")]
1064 pub kind: String,
1065 pub index: i32,
1066}
1067
1068#[derive(Debug, Clone, Serialize, Deserialize)]
1069#[serde(rename_all = "camelCase")]
1070pub struct PmxParsedRigidBody {
1071 pub name: String,
1072 pub english_name: String,
1073 pub bone_index: i32,
1074 pub group: u8,
1075 pub mask: u16,
1076 pub shape: String,
1077 pub size: [f32; 3],
1078 pub position: [f32; 3],
1079 pub rotation: [f32; 3],
1080 pub mass: f32,
1081 pub linear_damping: f32,
1082 pub angular_damping: f32,
1083 pub restitution: f32,
1084 pub friction: f32,
1085 pub mode: String,
1086}
1087
1088#[derive(Debug, Clone, Serialize, Deserialize)]
1089#[serde(rename_all = "camelCase")]
1090pub struct PmxParsedJoint {
1091 pub name: String,
1092 pub english_name: String,
1093 #[serde(rename = "type")]
1094 pub kind: String,
1095 pub rigid_body_index_a: i32,
1096 pub rigid_body_index_b: i32,
1097 pub position: [f32; 3],
1098 pub rotation: [f32; 3],
1099 pub translation_lower_limit: [f32; 3],
1100 pub translation_upper_limit: [f32; 3],
1101 pub rotation_lower_limit: [f32; 3],
1102 pub rotation_upper_limit: [f32; 3],
1103 pub spring_translation_factor: [f32; 3],
1104 pub spring_rotation_factor: [f32; 3],
1105}
1106
1107#[derive(Debug, Clone, Serialize, Deserialize)]
1108#[serde(rename_all = "camelCase")]
1109pub struct PmxParsedSoftBody {
1110 pub name: String,
1111 pub english_name: String,
1112 #[serde(rename = "type")]
1113 pub kind: String,
1114 pub material_index: i32,
1115 pub collision_group: u8,
1116 pub collision_mask: u16,
1117 pub flags: u8,
1118 pub bending_constraints_distance: i32,
1119 pub cluster_count: i32,
1120 pub total_mass: f32,
1121 pub collision_margin: f32,
1122}
1123
1124#[derive(Debug, Clone, Serialize, Deserialize)]
1125pub struct PmxParserDiagnostic {
1126 pub level: String,
1127 pub code: String,
1128 pub message: String,
1129}
1130
1131#[derive(Debug, Clone, Serialize, Deserialize)]
1132#[serde(rename_all = "camelCase")]
1133pub struct PmxPartsDescriptor {
1134 #[serde(default = "default_pmx_parts_version")]
1135 pub version: f32,
1136 #[serde(default = "default_pmx_parts_encoding")]
1137 pub encoding: String,
1138 #[serde(default = "default_pmx_parts_name")]
1139 pub name: String,
1140 #[serde(default)]
1141 pub english_name: String,
1142 #[serde(default)]
1143 pub comment: String,
1144 #[serde(default)]
1145 pub english_comment: String,
1146 #[serde(default)]
1147 pub material_name: String,
1148 #[serde(default)]
1149 pub english_material_name: String,
1150 #[serde(default)]
1151 pub materials: Vec<PmxPartsMaterialDescriptor>,
1152 #[serde(default)]
1153 pub bones: Vec<PmxPartsBoneDescriptor>,
1154 #[serde(default)]
1155 pub morphs: Vec<PmxPartsMorphDescriptor>,
1156 #[serde(default)]
1157 pub display_frames: Vec<PmxPartsDisplayFrameDescriptor>,
1158 #[serde(default)]
1159 pub rigid_bodies: Vec<PmxPartsRigidBodyDescriptor>,
1160 #[serde(default)]
1161 pub joints: Vec<PmxPartsJointDescriptor>,
1162 #[serde(default)]
1163 pub index_sizes: PmxPartsIndexSizes,
1164}
1165
1166#[derive(Debug, Clone, Serialize, Deserialize)]
1167#[serde(rename_all = "camelCase")]
1168pub struct PmxPartsMaterialDescriptor {
1169 #[serde(default = "default_pmx_parts_material_name")]
1170 pub name: String,
1171 #[serde(default)]
1172 pub english_name: String,
1173 #[serde(default)]
1174 pub texture_path: String,
1175 #[serde(default)]
1176 pub sphere_texture_path: String,
1177 #[serde(default)]
1178 pub sphere_mode: String,
1179 #[serde(default)]
1180 pub toon_texture_path: String,
1181 #[serde(default = "default_pmx_parts_shared_toon_index")]
1182 pub shared_toon_index: Option<u8>,
1183 #[serde(default = "default_pmx_parts_diffuse")]
1184 pub diffuse: [f32; 4],
1185 #[serde(default)]
1186 pub specular: [f32; 3],
1187 #[serde(default = "default_pmx_parts_specular_power")]
1188 pub specular_power: f32,
1189 #[serde(default = "default_pmx_parts_ambient")]
1190 pub ambient: [f32; 3],
1191 #[serde(default = "default_pmx_parts_edge_color")]
1192 pub edge_color: [f32; 4],
1193 #[serde(default = "default_pmx_parts_edge_size")]
1194 pub edge_size: f32,
1195 #[serde(default)]
1196 pub flags: PmxPartsMaterialFlags,
1197 #[serde(default)]
1198 pub face_count: i32,
1199}
1200
1201#[derive(Debug, Clone, Serialize, Deserialize)]
1202#[serde(rename_all = "camelCase")]
1203pub struct PmxPartsMaterialFlags {
1204 #[serde(default = "default_true")]
1205 pub double_sided: bool,
1206 #[serde(default = "default_true")]
1207 pub ground_shadow: bool,
1208 #[serde(default = "default_true")]
1209 pub self_shadow_map: bool,
1210 #[serde(default = "default_true")]
1211 pub self_shadow: bool,
1212 #[serde(default)]
1213 pub edge: bool,
1214 #[serde(default)]
1215 pub vertex_color: bool,
1216 #[serde(default)]
1217 pub point_draw: bool,
1218 #[serde(default)]
1219 pub line_draw: bool,
1220}
1221
1222impl Default for PmxPartsMaterialFlags {
1223 fn default() -> Self {
1224 Self {
1225 double_sided: true,
1226 ground_shadow: true,
1227 self_shadow_map: true,
1228 self_shadow: true,
1229 edge: false,
1230 vertex_color: false,
1231 point_draw: false,
1232 line_draw: false,
1233 }
1234 }
1235}
1236
1237#[derive(Debug, Clone, Serialize, Deserialize)]
1238#[serde(rename_all = "camelCase")]
1239pub struct PmxPartsBoneDescriptor {
1240 #[serde(default = "default_pmx_parts_root_bone_name")]
1241 pub name: String,
1242 #[serde(default)]
1243 pub english_name: String,
1244 #[serde(default = "default_negative_index")]
1245 pub parent_index: i32,
1246 #[serde(default)]
1247 pub layer: i32,
1248 #[serde(default)]
1249 pub position: [f32; 3],
1250 #[serde(default = "default_negative_index")]
1251 pub tail_index: i32,
1252 #[serde(default)]
1253 pub tail_position: Option<[f32; 3]>,
1254 #[serde(default)]
1255 pub rotatable: bool,
1256 #[serde(default)]
1257 pub translatable: bool,
1258 #[serde(default = "default_true")]
1259 pub visible: bool,
1260 #[serde(default = "default_true")]
1261 pub enabled: bool,
1262}
1263
1264#[derive(Debug, Clone, Serialize, Deserialize)]
1265#[serde(rename_all = "camelCase")]
1266pub struct PmxPartsMorphDescriptor {
1267 #[serde(default)]
1268 pub name: String,
1269 #[serde(default)]
1270 pub english_name: String,
1271 #[serde(default = "default_pmx_parts_morph_kind", alias = "type")]
1272 pub kind: String,
1273 #[serde(default)]
1274 pub vertex_offsets: Vec<PmxPartsVertexMorphOffset>,
1275 #[serde(default)]
1276 pub group_offsets: Vec<PmxPartsGroupMorphOffset>,
1277}
1278
1279#[derive(Debug, Clone, Serialize, Deserialize)]
1280#[serde(rename_all = "camelCase")]
1281pub struct PmxPartsVertexMorphOffset {
1282 pub vertex_index: u32,
1283 pub position: [f32; 3],
1284}
1285
1286#[derive(Debug, Clone, Serialize, Deserialize)]
1287#[serde(rename_all = "camelCase")]
1288pub struct PmxPartsGroupMorphOffset {
1289 pub morph_index: i32,
1290 pub weight: f32,
1291}
1292
1293#[derive(Debug, Clone, Serialize, Deserialize)]
1294#[serde(rename_all = "camelCase")]
1295pub struct PmxPartsDisplayFrameDescriptor {
1296 #[serde(default)]
1297 pub name: String,
1298 #[serde(default)]
1299 pub english_name: String,
1300 #[serde(default)]
1301 pub special: bool,
1302 #[serde(default)]
1303 pub frames: Vec<PmxPartsDisplayFrameItem>,
1304}
1305
1306#[derive(Debug, Clone, Serialize, Deserialize)]
1307#[serde(rename_all = "camelCase")]
1308pub struct PmxPartsDisplayFrameItem {
1309 #[serde(default = "default_pmx_parts_display_frame_kind")]
1310 pub kind: String,
1311 #[serde(default)]
1312 pub index: i32,
1313}
1314
1315#[derive(Debug, Clone, Serialize, Deserialize)]
1316#[serde(rename_all = "camelCase")]
1317pub struct PmxPartsRigidBodyDescriptor {
1318 #[serde(default)]
1319 pub name: String,
1320 #[serde(default)]
1321 pub english_name: String,
1322 #[serde(default = "default_negative_index")]
1323 pub bone_index: i32,
1324 #[serde(default)]
1325 pub group: u8,
1326 #[serde(default)]
1327 pub mask: u16,
1328 #[serde(default = "default_pmx_parts_rigid_body_shape")]
1329 pub shape: String,
1330 #[serde(default = "default_unit_vec3")]
1331 pub size: [f32; 3],
1332 #[serde(default)]
1333 pub position: [f32; 3],
1334 #[serde(default)]
1335 pub rotation: [f32; 3],
1336 #[serde(default = "default_pmx_parts_mass")]
1337 pub mass: f32,
1338 #[serde(default)]
1339 pub linear_damping: f32,
1340 #[serde(default)]
1341 pub angular_damping: f32,
1342 #[serde(default)]
1343 pub restitution: f32,
1344 #[serde(default)]
1345 pub friction: f32,
1346 #[serde(default = "default_pmx_parts_rigid_body_mode")]
1347 pub mode: String,
1348}
1349
1350#[derive(Debug, Clone, Serialize, Deserialize)]
1351#[serde(rename_all = "camelCase")]
1352pub struct PmxPartsJointDescriptor {
1353 #[serde(default)]
1354 pub name: String,
1355 #[serde(default)]
1356 pub english_name: String,
1357 #[serde(
1358 default = "default_pmx_parts_joint_kind",
1359 rename = "type",
1360 alias = "kind"
1361 )]
1362 pub kind: String,
1363 #[serde(default = "default_negative_index")]
1364 pub rigid_body_index_a: i32,
1365 #[serde(default = "default_negative_index")]
1366 pub rigid_body_index_b: i32,
1367 #[serde(default)]
1368 pub position: [f32; 3],
1369 #[serde(default)]
1370 pub rotation: [f32; 3],
1371 #[serde(default)]
1372 pub translation_lower_limit: [f32; 3],
1373 #[serde(default)]
1374 pub translation_upper_limit: [f32; 3],
1375 #[serde(default)]
1376 pub rotation_lower_limit: [f32; 3],
1377 #[serde(default)]
1378 pub rotation_upper_limit: [f32; 3],
1379 #[serde(default)]
1380 pub spring_translation_factor: [f32; 3],
1381 #[serde(default)]
1382 pub spring_rotation_factor: [f32; 3],
1383}
1384
1385#[derive(Debug, Clone, Serialize, Deserialize)]
1386#[serde(rename_all = "camelCase")]
1387pub struct PmxPartsIndexSizes {
1388 #[serde(default = "default_pmx_vertex_index_size")]
1389 pub vertex: u8,
1390 #[serde(default = "default_pmx_small_index_size")]
1391 pub texture: u8,
1392 #[serde(default = "default_pmx_small_index_size")]
1393 pub material: u8,
1394 #[serde(default = "default_pmx_small_index_size")]
1395 pub bone: u8,
1396 #[serde(default = "default_pmx_small_index_size")]
1397 pub morph: u8,
1398 #[serde(default = "default_pmx_small_index_size")]
1399 pub rigid_body: u8,
1400}
1401
1402impl Default for PmxPartsIndexSizes {
1403 fn default() -> Self {
1404 Self {
1405 vertex: default_pmx_vertex_index_size(),
1406 texture: default_pmx_small_index_size(),
1407 material: default_pmx_small_index_size(),
1408 bone: default_pmx_small_index_size(),
1409 morph: default_pmx_small_index_size(),
1410 rigid_body: default_pmx_small_index_size(),
1411 }
1412 }
1413}
1414
1415pub struct PmxPartsInput<'a> {
1416 pub descriptor: PmxPartsDescriptor,
1417 pub positions_xyz: &'a [f32],
1418 pub normals_xyz: &'a [f32],
1419 pub uvs_xy: &'a [f32],
1420 pub indices: &'a [u32],
1421 pub skin_indices: &'a [u32],
1422 pub skin_weights: &'a [f32],
1423 pub edge_scale: &'a [f32],
1424}
1425
1426fn default_pmx_format() -> String {
1427 "pmx".to_owned()
1428}
1429
1430fn default_pmx_parts_version() -> f32 {
1431 2.0
1432}
1433
1434fn default_pmx_parts_encoding() -> String {
1435 "utf-8".to_owned()
1436}
1437
1438fn default_pmx_parts_name() -> String {
1439 "model".to_owned()
1440}
1441
1442fn default_pmx_parts_root_bone_name() -> String {
1443 "root".to_owned()
1444}
1445
1446fn default_negative_index() -> i32 {
1447 -1
1448}
1449
1450fn default_pmx_parts_display_frame_kind() -> String {
1451 "bone".to_owned()
1452}
1453
1454fn default_pmx_parts_morph_kind() -> String {
1455 "vertex".to_owned()
1456}
1457
1458fn default_pmx_parts_rigid_body_shape() -> String {
1459 "sphere".to_owned()
1460}
1461
1462fn default_pmx_parts_rigid_body_mode() -> String {
1463 "static".to_owned()
1464}
1465
1466fn default_pmx_parts_joint_kind() -> String {
1467 "generic6dofSpring".to_owned()
1468}
1469
1470fn default_unit_vec3() -> [f32; 3] {
1471 [1.0, 1.0, 1.0]
1472}
1473
1474fn default_pmx_parts_mass() -> f32 {
1475 1.0
1476}
1477
1478fn default_pmx_parts_material_name() -> String {
1479 "material".to_owned()
1480}
1481
1482fn default_pmx_parts_shared_toon_index() -> Option<u8> {
1483 Some(0)
1484}
1485
1486fn default_pmx_parts_diffuse() -> [f32; 4] {
1487 [0.8, 0.8, 0.8, 1.0]
1488}
1489
1490fn default_pmx_parts_specular_power() -> f32 {
1491 1.0
1492}
1493
1494fn default_pmx_parts_ambient() -> [f32; 3] {
1495 [0.2, 0.2, 0.2]
1496}
1497
1498fn default_pmx_parts_edge_color() -> [f32; 4] {
1499 [0.0, 0.0, 0.0, 1.0]
1500}
1501
1502fn default_pmx_parts_edge_size() -> f32 {
1503 1.0
1504}
1505
1506fn default_true() -> bool {
1507 true
1508}
1509
1510fn default_pmx_vertex_index_size() -> u8 {
1511 4
1512}
1513
1514fn default_pmx_small_index_size() -> u8 {
1515 1
1516}
1517
1518pub fn build_pmx_model_from_parts(input: PmxPartsInput<'_>) -> Result<PmxParsedModel, String> {
1519 let vertex_count = validate_pmx_parts_geometry(
1520 input.positions_xyz,
1521 input.normals_xyz,
1522 input.uvs_xy,
1523 input.indices,
1524 input.skin_indices,
1525 input.skin_weights,
1526 input.edge_scale,
1527 )?;
1528 validate_pmx_parts_descriptor(
1529 &input.descriptor,
1530 vertex_count,
1531 input.indices,
1532 input.skin_indices,
1533 )?;
1534
1535 let skin_indices = if input.skin_indices.is_empty() {
1536 vec![0; vertex_count * 4]
1537 } else {
1538 input.skin_indices.to_vec()
1539 };
1540 let skin_weights = if input.skin_weights.is_empty() {
1541 default_pmx_skin_weights(vertex_count)
1542 } else {
1543 input.skin_weights.to_vec()
1544 };
1545 let edge_scale = if input.edge_scale.is_empty() {
1546 vec![1.0; vertex_count]
1547 } else {
1548 input.edge_scale.to_vec()
1549 };
1550 let face_count = input.indices.len() / 3;
1551 let (materials, material_groups) = build_pmx_parts_materials(&input.descriptor, face_count);
1552 let bones = build_pmx_parts_bones(&input.descriptor);
1553 let morphs = build_pmx_parts_morphs(&input.descriptor);
1554 let display_frames = build_pmx_parts_display_frames(&input.descriptor);
1555 let rigid_bodies = build_pmx_parts_rigid_bodies(&input.descriptor);
1556 let joints = build_pmx_parts_joints(&input.descriptor);
1557
1558 let model = PmxParsedModel {
1559 metadata: PmxParsedMetadata {
1560 format: "pmx".to_owned(),
1561 version: input.descriptor.version,
1562 encoding: input.descriptor.encoding,
1563 name: input.descriptor.name,
1564 english_name: input.descriptor.english_name,
1565 comment: input.descriptor.comment,
1566 english_comment: input.descriptor.english_comment,
1567 counts: PmxParsedCounts {
1568 vertices: vertex_count,
1569 faces: face_count,
1570 materials: materials.len(),
1571 bones: bones.len(),
1572 morphs: morphs.len(),
1573 display_frames: display_frames.len(),
1574 rigid_bodies: rigid_bodies.len(),
1575 joints: joints.len(),
1576 soft_bodies: 0,
1577 },
1578 index_sizes: PmxParsedIndexSizes {
1579 vertex: input.descriptor.index_sizes.vertex,
1580 texture: input.descriptor.index_sizes.texture,
1581 material: input.descriptor.index_sizes.material,
1582 bone: input.descriptor.index_sizes.bone,
1583 morph: input.descriptor.index_sizes.morph,
1584 rigid_body: input.descriptor.index_sizes.rigid_body,
1585 },
1586 additional_uv_count: 0,
1587 },
1588 geometry: PmxParsedGeometry {
1589 positions: input.positions_xyz.to_vec(),
1590 normals: input.normals_xyz.to_vec(),
1591 uvs: input.uvs_xy.to_vec(),
1592 additional_uvs: Vec::new(),
1593 indices: input.indices.to_vec(),
1594 skin_indices,
1595 skin_weights,
1596 edge_scale,
1597 material_groups,
1598 },
1599 materials,
1600 skeleton: PmxParsedSkeleton { bones },
1601 morphs,
1602 display_frames,
1603 rigid_bodies,
1604 joints,
1605 soft_bodies: Vec::new(),
1606 diagnostics: Vec::new(),
1607 };
1608 validate_pmx_export_model(&model)?;
1609 Ok(model)
1610}
1611
1612pub fn validate_pmx_export_model(model: &PmxParsedModel) -> Result<(), String> {
1613 let geometry = &model.geometry;
1614 if !geometry.positions.len().is_multiple_of(3) {
1615 return Err(format!(
1616 "PMX export positions length must be divisible by 3: {}",
1617 geometry.positions.len()
1618 ));
1619 }
1620 let vertex_count = geometry.positions.len() / 3;
1621 if geometry.normals.len() != vertex_count * 3 {
1622 return Err(format!(
1623 "PMX export normals length mismatch: expected {}, got {}",
1624 vertex_count * 3,
1625 geometry.normals.len()
1626 ));
1627 }
1628 if geometry.uvs.len() != vertex_count * 2 {
1629 return Err(format!(
1630 "PMX export uvs length mismatch: expected {}, got {}",
1631 vertex_count * 2,
1632 geometry.uvs.len()
1633 ));
1634 }
1635 if geometry.additional_uvs.len() != model.metadata.additional_uv_count as usize {
1636 return Err(format!(
1637 "PMX export additionalUvs set count mismatch: expected {}, got {}",
1638 model.metadata.additional_uv_count,
1639 geometry.additional_uvs.len()
1640 ));
1641 }
1642 for (index, values) in geometry.additional_uvs.iter().enumerate() {
1643 if values.len() != vertex_count * 4 {
1644 return Err(format!(
1645 "PMX export additionalUvs[{}] length mismatch: expected {}, got {}",
1646 index,
1647 vertex_count * 4,
1648 values.len()
1649 ));
1650 }
1651 }
1652 if !geometry.indices.len().is_multiple_of(3) {
1653 return Err(format!(
1654 "PMX export indices length must be divisible by 3: {}",
1655 geometry.indices.len()
1656 ));
1657 }
1658 let expected_skin_len = vertex_count * 4;
1659 if geometry.skin_indices.is_empty() != geometry.skin_weights.is_empty() {
1660 return Err(
1661 "PMX export skinIndices and skinWeights must be both empty or both provided".to_owned(),
1662 );
1663 }
1664 if !geometry.skin_indices.is_empty() && geometry.skin_indices.len() != expected_skin_len {
1665 return Err(format!(
1666 "PMX export skinIndices length mismatch: expected {}, got {}",
1667 expected_skin_len,
1668 geometry.skin_indices.len()
1669 ));
1670 }
1671 if !geometry.skin_weights.is_empty() && geometry.skin_weights.len() != expected_skin_len {
1672 return Err(format!(
1673 "PMX export skinWeights length mismatch: expected {}, got {}",
1674 expected_skin_len,
1675 geometry.skin_weights.len()
1676 ));
1677 }
1678 Ok(())
1679}
1680
1681fn validate_pmx_parts_geometry(
1682 positions_xyz: &[f32],
1683 normals_xyz: &[f32],
1684 uvs_xy: &[f32],
1685 indices: &[u32],
1686 skin_indices: &[u32],
1687 skin_weights: &[f32],
1688 edge_scale: &[f32],
1689) -> Result<usize, String> {
1690 if !positions_xyz.len().is_multiple_of(3) {
1691 return Err(format!(
1692 "positions_xyz must contain vertex_count * 3 values, got {}",
1693 positions_xyz.len()
1694 ));
1695 }
1696 let vertex_count = positions_xyz.len() / 3;
1697 if vertex_count == 0 {
1698 return Err("PMX parts export requires at least one vertex".to_owned());
1699 }
1700 if normals_xyz.len() != vertex_count * 3 {
1701 return Err(format!(
1702 "normals_xyz must contain vertex_count * 3 values, expected {}, got {}",
1703 vertex_count * 3,
1704 normals_xyz.len()
1705 ));
1706 }
1707 if uvs_xy.len() != vertex_count * 2 {
1708 return Err(format!(
1709 "uvs_xy must contain vertex_count * 2 values, expected {}, got {}",
1710 vertex_count * 2,
1711 uvs_xy.len()
1712 ));
1713 }
1714 if !indices.len().is_multiple_of(3) {
1715 return Err(format!(
1716 "indices must contain triangle indices and be divisible by 3, got {}",
1717 indices.len()
1718 ));
1719 }
1720 if let Some(&index) = indices
1721 .iter()
1722 .find(|&&index| index as usize >= vertex_count)
1723 {
1724 return Err(format!(
1725 "indices contains out-of-range vertex index {index} for vertex_count {vertex_count}"
1726 ));
1727 }
1728 if !skin_indices.is_empty() && skin_indices.len() != vertex_count * 4 {
1729 return Err(format!(
1730 "skin_indices must contain vertex_count * 4 values when provided, expected {}, got {}",
1731 vertex_count * 4,
1732 skin_indices.len()
1733 ));
1734 }
1735 if !skin_weights.is_empty() && skin_weights.len() != vertex_count * 4 {
1736 return Err(format!(
1737 "skin_weights must contain vertex_count * 4 values when provided, expected {}, got {}",
1738 vertex_count * 4,
1739 skin_weights.len()
1740 ));
1741 }
1742 if skin_indices.is_empty() != skin_weights.is_empty() {
1743 return Err(
1744 "skin_indices and skin_weights must either both be provided or both be empty"
1745 .to_owned(),
1746 );
1747 }
1748 if !edge_scale.is_empty() && edge_scale.len() != vertex_count {
1749 return Err(format!(
1750 "edge_scale must contain vertex_count values when provided, expected {}, got {}",
1751 vertex_count,
1752 edge_scale.len()
1753 ));
1754 }
1755 Ok(vertex_count)
1756}
1757
1758fn validate_pmx_parts_descriptor(
1759 descriptor: &PmxPartsDescriptor,
1760 vertex_count: usize,
1761 indices: &[u32],
1762 skin_indices: &[u32],
1763) -> Result<(), String> {
1764 if descriptor.version < 2.0 {
1765 return Err(format!(
1766 "PMX parts export supports PMX 2.0 or newer, got {}",
1767 descriptor.version
1768 ));
1769 }
1770 if descriptor.encoding != "utf-8" && descriptor.encoding != "utf-16-le" {
1771 return Err(format!(
1772 "PMX parts export encoding must be utf-8 or utf-16-le, got {}",
1773 descriptor.encoding
1774 ));
1775 }
1776 validate_pmx_index_size("vertex", descriptor.index_sizes.vertex)?;
1777 validate_pmx_index_size("texture", descriptor.index_sizes.texture)?;
1778 validate_pmx_index_size("material", descriptor.index_sizes.material)?;
1779 validate_pmx_index_size("bone", descriptor.index_sizes.bone)?;
1780 validate_pmx_index_size("morph", descriptor.index_sizes.morph)?;
1781 validate_pmx_index_size("rigidBody", descriptor.index_sizes.rigid_body)?;
1782 if indices.len() / 3 > i32::MAX as usize {
1783 return Err(format!(
1784 "face count {} exceeds PMX i32 material face count limit",
1785 indices.len() / 3
1786 ));
1787 }
1788 if !pmx_vertex_index_size_can_hold(descriptor.index_sizes.vertex, vertex_count) {
1789 return Err(format!(
1790 "vertex index size {} cannot hold vertex_count {}",
1791 descriptor.index_sizes.vertex, vertex_count
1792 ));
1793 }
1794 if !indices.is_empty()
1795 && !pmx_vertex_index_size_can_hold(
1796 descriptor.index_sizes.vertex,
1797 indices.iter().copied().max().unwrap_or(0) as usize + 1,
1798 )
1799 {
1800 return Err(format!(
1801 "vertex index size {} cannot hold max index {}",
1802 descriptor.index_sizes.vertex,
1803 indices.iter().copied().max().unwrap_or(0)
1804 ));
1805 }
1806 validate_pmx_parts_materials(descriptor, indices.len() / 3)?;
1807 validate_pmx_parts_bones(descriptor)?;
1808 validate_pmx_parts_morphs(descriptor, vertex_count)?;
1809 validate_pmx_parts_skin_indices(descriptor, skin_indices)?;
1810 validate_pmx_parts_display_frames(descriptor)?;
1811 validate_pmx_parts_physics(descriptor)?;
1812 Ok(())
1813}
1814
1815fn validate_pmx_parts_materials(
1816 descriptor: &PmxPartsDescriptor,
1817 face_count: usize,
1818) -> Result<(), String> {
1819 if descriptor.materials.is_empty() {
1820 return Ok(());
1821 }
1822 if !pmx_signed_index_size_can_hold_count(
1823 descriptor.index_sizes.material,
1824 descriptor.materials.len(),
1825 ) {
1826 return Err(format!(
1827 "material index size {} cannot hold material count {}",
1828 descriptor.index_sizes.material,
1829 descriptor.materials.len()
1830 ));
1831 }
1832 let mut total = 0usize;
1833 for (index, material) in descriptor.materials.iter().enumerate() {
1834 if material.face_count < 0 {
1835 return Err(format!(
1836 "materials[{index}].faceCount must be non-negative, got {}",
1837 material.face_count
1838 ));
1839 }
1840 total += material.face_count as usize;
1841 }
1842 if total != face_count {
1843 return Err(format!(
1844 "materials faceCount sum must match index face count, expected {}, got {}",
1845 face_count, total
1846 ));
1847 }
1848 Ok(())
1849}
1850
1851fn validate_pmx_parts_bones(descriptor: &PmxPartsDescriptor) -> Result<(), String> {
1852 let bone_count = descriptor.bones.len().max(1);
1853 if !pmx_signed_index_size_can_hold_count(descriptor.index_sizes.bone, bone_count) {
1854 return Err(format!(
1855 "bone index size {} cannot hold bone count {}",
1856 descriptor.index_sizes.bone, bone_count
1857 ));
1858 }
1859 for (index, bone) in descriptor.bones.iter().enumerate() {
1860 if bone.parent_index < -1
1861 || (bone.parent_index != -1 && bone.parent_index as usize >= bone_count)
1862 {
1863 return Err(format!(
1864 "bones[{index}].parentIndex must be -1 or a valid bone index, got {}",
1865 bone.parent_index
1866 ));
1867 }
1868 if bone.tail_index < -1 || (bone.tail_index != -1 && bone.tail_index as usize >= bone_count)
1869 {
1870 return Err(format!(
1871 "bones[{index}].tailIndex must be -1 or a valid bone index, got {}",
1872 bone.tail_index
1873 ));
1874 }
1875 }
1876 Ok(())
1877}
1878
1879fn validate_pmx_parts_skin_indices(
1880 descriptor: &PmxPartsDescriptor,
1881 skin_indices: &[u32],
1882) -> Result<(), String> {
1883 if skin_indices.is_empty() {
1884 return Ok(());
1885 }
1886 let bone_count = descriptor.bones.len().max(1);
1887 if let Some(&index) = skin_indices
1888 .iter()
1889 .find(|&&index| index as usize >= bone_count)
1890 {
1891 return Err(format!(
1892 "skin_indices contains out-of-range bone index {index} for bone count {bone_count}"
1893 ));
1894 }
1895 Ok(())
1896}
1897
1898fn validate_pmx_parts_morphs(
1899 descriptor: &PmxPartsDescriptor,
1900 vertex_count: usize,
1901) -> Result<(), String> {
1902 if !pmx_signed_index_size_can_hold_count(descriptor.index_sizes.morph, descriptor.morphs.len())
1903 {
1904 return Err(format!(
1905 "morph index size {} cannot hold morph count {}",
1906 descriptor.index_sizes.morph,
1907 descriptor.morphs.len()
1908 ));
1909 }
1910 for (morph_index, morph) in descriptor.morphs.iter().enumerate() {
1911 match morph.kind.as_str() {
1912 "vertex" => {
1913 for (offset_index, offset) in morph.vertex_offsets.iter().enumerate() {
1914 if offset.vertex_index as usize >= vertex_count {
1915 return Err(format!(
1916 "morphs[{morph_index}].vertexOffsets[{offset_index}].vertexIndex out of range: {} for vertex_count {vertex_count}",
1917 offset.vertex_index
1918 ));
1919 }
1920 }
1921 }
1922 "group" => {
1923 for (offset_index, offset) in morph.group_offsets.iter().enumerate() {
1924 if offset.morph_index < 0
1925 || offset.morph_index as usize >= descriptor.morphs.len()
1926 {
1927 return Err(format!(
1928 "morphs[{morph_index}].groupOffsets[{offset_index}].morphIndex out of range: {}",
1929 offset.morph_index
1930 ));
1931 }
1932 }
1933 }
1934 other => {
1935 return Err(format!(
1936 "morphs[{morph_index}].kind must be vertex or group, got {other}"
1937 ));
1938 }
1939 }
1940 }
1941 Ok(())
1942}
1943
1944fn validate_pmx_parts_display_frames(descriptor: &PmxPartsDescriptor) -> Result<(), String> {
1945 let bone_count = descriptor.bones.len().max(1);
1946 let morph_count = descriptor.morphs.len();
1947 for (frame_index, frame) in descriptor.display_frames.iter().enumerate() {
1948 for (item_index, item) in frame.frames.iter().enumerate() {
1949 match item.kind.as_str() {
1950 "bone" => {
1951 if item.index < 0 || item.index as usize >= bone_count {
1952 return Err(format!(
1953 "displayFrames[{frame_index}].frames[{item_index}].index must reference an existing bone, got {}",
1954 item.index
1955 ));
1956 }
1957 }
1958 "morph" => {
1959 if item.index < 0 || item.index as usize >= morph_count {
1960 return Err(format!(
1961 "displayFrames[{frame_index}].frames[{item_index}].index must reference an existing morph, got {}",
1962 item.index
1963 ));
1964 }
1965 }
1966 other => {
1967 return Err(format!(
1968 "displayFrames[{frame_index}].frames[{item_index}].kind must be bone or morph, got {other}"
1969 ));
1970 }
1971 }
1972 }
1973 }
1974 Ok(())
1975}
1976
1977fn validate_pmx_parts_physics(descriptor: &PmxPartsDescriptor) -> Result<(), String> {
1978 let bone_count = descriptor.bones.len().max(1);
1979 if !pmx_signed_index_size_can_hold_count(
1980 descriptor.index_sizes.rigid_body,
1981 descriptor.rigid_bodies.len(),
1982 ) {
1983 return Err(format!(
1984 "rigidBody index size {} cannot hold rigid body count {}",
1985 descriptor.index_sizes.rigid_body,
1986 descriptor.rigid_bodies.len()
1987 ));
1988 }
1989 for (index, body) in descriptor.rigid_bodies.iter().enumerate() {
1990 if body.bone_index < -1 || (body.bone_index != -1 && body.bone_index as usize >= bone_count)
1991 {
1992 return Err(format!(
1993 "rigidBodies[{index}].boneIndex must be -1 or a valid bone index, got {}",
1994 body.bone_index
1995 ));
1996 }
1997 if !matches!(body.shape.as_str(), "sphere" | "box" | "capsule") {
1998 return Err(format!(
1999 "rigidBodies[{index}].shape must be sphere, box, or capsule, got {}",
2000 body.shape
2001 ));
2002 }
2003 if !matches!(body.mode.as_str(), "static" | "dynamic" | "dynamicBone") {
2004 return Err(format!(
2005 "rigidBodies[{index}].mode must be static, dynamic, or dynamicBone, got {}",
2006 body.mode
2007 ));
2008 }
2009 }
2010 for (index, joint) in descriptor.joints.iter().enumerate() {
2011 validate_pmx_parts_rigid_body_ref(
2012 "rigidBodyIndexA",
2013 index,
2014 joint.rigid_body_index_a,
2015 descriptor.rigid_bodies.len(),
2016 )?;
2017 validate_pmx_parts_rigid_body_ref(
2018 "rigidBodyIndexB",
2019 index,
2020 joint.rigid_body_index_b,
2021 descriptor.rigid_bodies.len(),
2022 )?;
2023 if !matches!(
2024 joint.kind.as_str(),
2025 "generic6dofSpring" | "generic6dof" | "point2point" | "coneTwist" | "slider" | "hinge"
2026 ) {
2027 return Err(format!(
2028 "joints[{index}].kind has unsupported PMX joint type {}",
2029 joint.kind
2030 ));
2031 }
2032 }
2033 Ok(())
2034}
2035
2036fn validate_pmx_parts_rigid_body_ref(
2037 field: &str,
2038 joint_index: usize,
2039 value: i32,
2040 rigid_body_count: usize,
2041) -> Result<(), String> {
2042 if value < -1 || (value != -1 && value as usize >= rigid_body_count) {
2043 Err(format!(
2044 "joints[{joint_index}].{field} must be -1 or a valid rigid body index, got {value}"
2045 ))
2046 } else {
2047 Ok(())
2048 }
2049}
2050
2051fn validate_pmx_index_size(name: &str, value: u8) -> Result<(), String> {
2052 if matches!(value, 1 | 2 | 4) {
2053 Ok(())
2054 } else {
2055 Err(format!(
2056 "PMX {name} index size must be 1, 2, or 4, got {value}"
2057 ))
2058 }
2059}
2060
2061fn pmx_vertex_index_size_can_hold(size: u8, count: usize) -> bool {
2062 match size {
2063 1 => count <= u8::MAX as usize + 1,
2064 2 => count <= u16::MAX as usize + 1,
2065 4 => true,
2066 _ => false,
2067 }
2068}
2069
2070fn pmx_signed_index_size_can_hold_count(size: u8, count: usize) -> bool {
2071 match size {
2072 1 => count <= i8::MAX as usize + 1,
2073 2 => count <= i16::MAX as usize + 1,
2074 4 => true,
2075 _ => false,
2076 }
2077}
2078
2079fn default_pmx_skin_weights(vertex_count: usize) -> Vec<f32> {
2080 let mut weights = vec![0.0; vertex_count * 4];
2081 for vertex in 0..vertex_count {
2082 weights[vertex * 4] = 1.0;
2083 }
2084 weights
2085}
2086
2087fn build_pmx_parts_materials(
2088 descriptor: &PmxPartsDescriptor,
2089 face_count: usize,
2090) -> (Vec<PmxParsedMaterial>, Vec<PmxParsedMaterialGroup>) {
2091 if face_count == 0 {
2092 return (Vec::new(), Vec::new());
2093 }
2094 if descriptor.materials.is_empty() {
2095 return (
2096 vec![default_pmx_parts_material(
2097 &descriptor.material_name,
2098 &descriptor.english_material_name,
2099 face_count as i32,
2100 )],
2101 vec![PmxParsedMaterialGroup {
2102 start: 0,
2103 count: face_count * 3,
2104 material_index: 0,
2105 }],
2106 );
2107 }
2108
2109 let mut start = 0usize;
2110 let mut materials = Vec::with_capacity(descriptor.materials.len());
2111 let mut groups = Vec::with_capacity(descriptor.materials.len());
2112 for (index, material) in descriptor.materials.iter().enumerate() {
2113 let index_count = material.face_count as usize * 3;
2114 materials.push(pmx_parts_material_from_descriptor(material));
2115 groups.push(PmxParsedMaterialGroup {
2116 start,
2117 count: index_count,
2118 material_index: index,
2119 });
2120 start += index_count;
2121 }
2122 (materials, groups)
2123}
2124
2125fn pmx_parts_material_from_descriptor(material: &PmxPartsMaterialDescriptor) -> PmxParsedMaterial {
2126 PmxParsedMaterial {
2127 name: material.name.clone(),
2128 english_name: material.english_name.clone(),
2129 texture_path: material.texture_path.clone(),
2130 sphere_texture_path: material.sphere_texture_path.clone(),
2131 sphere_mode: if material.sphere_mode.is_empty() {
2132 "none".to_owned()
2133 } else {
2134 material.sphere_mode.clone()
2135 },
2136 toon_texture_path: material.toon_texture_path.clone(),
2137 shared_toon_index: material.shared_toon_index,
2138 diffuse: material.diffuse,
2139 specular: material.specular,
2140 specular_power: material.specular_power,
2141 ambient: material.ambient,
2142 edge_color: material.edge_color,
2143 edge_size: material.edge_size,
2144 flags: PmxParsedMaterialFlags {
2145 double_sided: material.flags.double_sided,
2146 ground_shadow: material.flags.ground_shadow,
2147 self_shadow_map: material.flags.self_shadow_map,
2148 self_shadow: material.flags.self_shadow,
2149 edge: material.flags.edge,
2150 vertex_color: material.flags.vertex_color,
2151 point_draw: material.flags.point_draw,
2152 line_draw: material.flags.line_draw,
2153 },
2154 face_count: material.face_count,
2155 }
2156}
2157
2158fn build_pmx_parts_bones(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedBone> {
2159 if descriptor.bones.is_empty() {
2160 return vec![default_pmx_parts_root_bone()];
2161 }
2162 descriptor
2163 .bones
2164 .iter()
2165 .map(pmx_parts_bone_from_descriptor)
2166 .collect()
2167}
2168
2169fn build_pmx_parts_display_frames(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedDisplayFrame> {
2170 descriptor
2171 .display_frames
2172 .iter()
2173 .map(|frame| PmxParsedDisplayFrame {
2174 name: frame.name.clone(),
2175 english_name: frame.english_name.clone(),
2176 special: frame.special,
2177 frames: frame
2178 .frames
2179 .iter()
2180 .map(|item| PmxParsedDisplayFrameElement {
2181 kind: item.kind.clone(),
2182 index: item.index,
2183 })
2184 .collect(),
2185 })
2186 .collect()
2187}
2188
2189fn build_pmx_parts_morphs(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedMorph> {
2190 descriptor
2191 .morphs
2192 .iter()
2193 .map(pmx_parts_morph_from_descriptor)
2194 .collect()
2195}
2196
2197fn build_pmx_parts_rigid_bodies(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedRigidBody> {
2198 descriptor
2199 .rigid_bodies
2200 .iter()
2201 .map(|body| PmxParsedRigidBody {
2202 name: body.name.clone(),
2203 english_name: body.english_name.clone(),
2204 bone_index: body.bone_index,
2205 group: body.group,
2206 mask: body.mask,
2207 shape: body.shape.clone(),
2208 size: body.size,
2209 position: body.position,
2210 rotation: body.rotation,
2211 mass: body.mass,
2212 linear_damping: body.linear_damping,
2213 angular_damping: body.angular_damping,
2214 restitution: body.restitution,
2215 friction: body.friction,
2216 mode: body.mode.clone(),
2217 })
2218 .collect()
2219}
2220
2221fn build_pmx_parts_joints(descriptor: &PmxPartsDescriptor) -> Vec<PmxParsedJoint> {
2222 descriptor
2223 .joints
2224 .iter()
2225 .map(|joint| PmxParsedJoint {
2226 name: joint.name.clone(),
2227 english_name: joint.english_name.clone(),
2228 kind: joint.kind.clone(),
2229 rigid_body_index_a: joint.rigid_body_index_a,
2230 rigid_body_index_b: joint.rigid_body_index_b,
2231 position: joint.position,
2232 rotation: joint.rotation,
2233 translation_lower_limit: joint.translation_lower_limit,
2234 translation_upper_limit: joint.translation_upper_limit,
2235 rotation_lower_limit: joint.rotation_lower_limit,
2236 rotation_upper_limit: joint.rotation_upper_limit,
2237 spring_translation_factor: joint.spring_translation_factor,
2238 spring_rotation_factor: joint.spring_rotation_factor,
2239 })
2240 .collect()
2241}
2242
2243fn pmx_parts_morph_from_descriptor(morph: &PmxPartsMorphDescriptor) -> PmxParsedMorph {
2244 let mut parsed = PmxParsedMorph {
2245 name: morph.name.clone(),
2246 english_name: morph.english_name.clone(),
2247 kind: morph.kind.clone(),
2248 vertex_offsets: Vec::new(),
2249 group_offsets: Vec::new(),
2250 bone_offsets: Vec::new(),
2251 uv_offsets: Vec::new(),
2252 additional_uv_offsets: Vec::new(),
2253 material_offsets: Vec::new(),
2254 flip_offsets: Vec::new(),
2255 impulse_offsets: Vec::new(),
2256 };
2257 match morph.kind.as_str() {
2258 "vertex" => {
2259 parsed.vertex_offsets = morph
2260 .vertex_offsets
2261 .iter()
2262 .map(|offset| PmxParsedVertexMorphOffset {
2263 vertex_index: offset.vertex_index,
2264 position: offset.position,
2265 })
2266 .collect();
2267 }
2268 "group" => {
2269 parsed.group_offsets = morph
2270 .group_offsets
2271 .iter()
2272 .map(|offset| PmxParsedGroupMorphOffset {
2273 morph_index: offset.morph_index,
2274 weight: offset.weight,
2275 })
2276 .collect();
2277 }
2278 _ => unreachable!("PmxPartsMorphDescriptor was validated before build"),
2279 }
2280 parsed
2281}
2282
2283fn pmx_parts_bone_from_descriptor(bone: &PmxPartsBoneDescriptor) -> PmxParsedBone {
2284 let tail_position = if bone.tail_index >= 0 {
2285 None
2286 } else {
2287 Some(bone.tail_position.unwrap_or([0.0, 1.0, 0.0]))
2288 };
2289 PmxParsedBone {
2290 name: bone.name.clone(),
2291 english_name: bone.english_name.clone(),
2292 parent_index: bone.parent_index,
2293 layer: bone.layer,
2294 position: bone.position,
2295 tail_index: bone.tail_index,
2296 tail_position,
2297 flags: PmxParsedBoneFlags {
2298 indexed_tail: bone.tail_index >= 0,
2299 rotatable: bone.rotatable,
2300 translatable: bone.translatable,
2301 visible: bone.visible,
2302 enabled: bone.enabled,
2303 ik: false,
2304 append_local: false,
2305 append_rotate: false,
2306 append_translate: false,
2307 fixed_axis: false,
2308 local_axis: false,
2309 external_parent_transform: false,
2310 transform_after_physics: false,
2311 },
2312 append_transform: None,
2313 fixed_axis: None,
2314 local_axis: None,
2315 external_parent_key: None,
2316 ik: None,
2317 }
2318}
2319
2320fn default_pmx_parts_material(
2321 name: &str,
2322 english_name: &str,
2323 face_count: i32,
2324) -> PmxParsedMaterial {
2325 PmxParsedMaterial {
2326 name: if name.is_empty() {
2327 "material".to_owned()
2328 } else {
2329 name.to_owned()
2330 },
2331 english_name: english_name.to_owned(),
2332 texture_path: String::new(),
2333 sphere_texture_path: String::new(),
2334 sphere_mode: "none".to_owned(),
2335 toon_texture_path: String::new(),
2336 shared_toon_index: Some(0),
2337 diffuse: [0.8, 0.8, 0.8, 1.0],
2338 specular: [0.0, 0.0, 0.0],
2339 specular_power: 1.0,
2340 ambient: [0.2, 0.2, 0.2],
2341 edge_color: [0.0, 0.0, 0.0, 1.0],
2342 edge_size: 1.0,
2343 flags: PmxParsedMaterialFlags {
2344 double_sided: true,
2345 ground_shadow: true,
2346 self_shadow_map: true,
2347 self_shadow: true,
2348 edge: false,
2349 vertex_color: false,
2350 point_draw: false,
2351 line_draw: false,
2352 },
2353 face_count,
2354 }
2355}
2356
2357fn default_pmx_parts_root_bone() -> PmxParsedBone {
2358 PmxParsedBone {
2359 name: "root".to_owned(),
2360 english_name: "root".to_owned(),
2361 parent_index: -1,
2362 layer: 0,
2363 position: [0.0, 0.0, 0.0],
2364 tail_index: -1,
2365 tail_position: Some([0.0, 1.0, 0.0]),
2366 flags: PmxParsedBoneFlags {
2367 indexed_tail: false,
2368 rotatable: true,
2369 translatable: true,
2370 visible: true,
2371 enabled: true,
2372 ik: false,
2373 append_local: false,
2374 append_rotate: false,
2375 append_translate: false,
2376 fixed_axis: false,
2377 local_axis: false,
2378 transform_after_physics: false,
2379 external_parent_transform: false,
2380 },
2381 append_transform: None,
2382 fixed_axis: None,
2383 local_axis: None,
2384 external_parent_key: None,
2385 ik: None,
2386 }
2387}
2388
2389pub fn parse_pmx_model(data: &[u8]) -> Result<PmxParsedModel, ImportError> {
2390 let (header, pos) = read_header(data)?;
2391 let mut r = Reader { data, pos };
2392 let name = r.read_string(header.encoding)?;
2393 let english_name = r.read_string(header.encoding)?;
2394 let comment = r.read_string(header.encoding)?;
2395 let english_comment = r.read_string(header.encoding)?;
2396
2397 let vertex_count = read_section_count(&mut r)?;
2398 let geometry = read_parsed_geometry(&mut r, &header, vertex_count)?;
2399 let index_count = geometry.indices.len();
2400 let textures = read_parsed_textures(&mut r, header.encoding)?;
2401 let materials = read_parsed_materials(&mut r, &header, &textures)?;
2402 let material_groups = build_material_groups(&materials);
2403 let bone_count = read_section_count(&mut r)?;
2404 let skeleton = PmxParsedSkeleton {
2405 bones: read_parsed_bones(&mut r, &header, bone_count)?,
2406 };
2407 let morphs = read_parsed_morphs(&mut r, &header)?;
2408 let display_frames = read_parsed_display_frames(&mut r, &header)?;
2409 let rigid_bodies = if r.remaining() >= 4 {
2410 read_parsed_rigid_bodies(&mut r, &header)?
2411 } else {
2412 Vec::new()
2413 };
2414 let joints = if r.remaining() >= 4 {
2415 read_parsed_joints(&mut r, &header)?
2416 } else {
2417 Vec::new()
2418 };
2419 let soft_bodies = if header.version >= 2.05 && r.remaining() >= 4 {
2420 read_parsed_soft_bodies(&mut r, &header)?
2421 } else {
2422 Vec::new()
2423 };
2424
2425 let mut geometry = geometry;
2426 geometry.material_groups = material_groups;
2427 let mut diagnostics = Vec::new();
2428 if !soft_bodies.is_empty() {
2429 diagnostics.push(PmxParserDiagnostic {
2430 level: "warning".to_owned(),
2431 code: "PMX_SOFT_BODY_UNSUPPORTED".to_owned(),
2432 message: format!(
2433 "{} PMX soft bodies are parsed only as header records by mmd-anim.",
2434 soft_bodies.len()
2435 ),
2436 });
2437 }
2438 if r.remaining() > 0 {
2439 diagnostics.push(PmxParserDiagnostic {
2440 level: "warning".to_owned(),
2441 code: "PMX_TRAILING_DATA_UNPARSED".to_owned(),
2442 message: format!("{} trailing PMX bytes were left unparsed.", r.remaining()),
2443 });
2444 }
2445
2446 Ok(PmxParsedModel {
2447 metadata: PmxParsedMetadata {
2448 format: "pmx".to_owned(),
2449 version: header.version,
2450 encoding: match header.encoding {
2451 TextEncoding::Utf8 => "utf-8".to_owned(),
2452 TextEncoding::Utf16Le => "utf-16-le".to_owned(),
2453 },
2454 name,
2455 english_name,
2456 comment,
2457 english_comment,
2458 counts: PmxParsedCounts {
2459 vertices: vertex_count,
2460 faces: index_count / 3,
2461 materials: materials.len(),
2462 bones: skeleton.bones.len(),
2463 morphs: morphs.len(),
2464 display_frames: display_frames.len(),
2465 rigid_bodies: rigid_bodies.len(),
2466 joints: joints.len(),
2467 soft_bodies: soft_bodies.len(),
2468 },
2469 index_sizes: PmxParsedIndexSizes {
2470 vertex: header.vertex_index_size,
2471 texture: header.texture_index_size,
2472 material: header.material_index_size,
2473 bone: header.bone_index_size,
2474 morph: header.morph_index_size,
2475 rigid_body: header.rigidbody_index_size,
2476 },
2477 additional_uv_count: header.extra_uv_count,
2478 },
2479 geometry,
2480 materials,
2481 skeleton,
2482 morphs,
2483 display_frames,
2484 rigid_bodies,
2485 joints,
2486 soft_bodies,
2487 diagnostics,
2488 })
2489}
2490
2491pub fn export_pmx_model(model: &PmxParsedModel) -> Vec<u8> {
2492 let encoding = pmx_text_encoding(&model.metadata);
2493 let mut out = Vec::new();
2494 out.extend_from_slice(&PMX_MAGIC);
2495 write_f32(&mut out, model.metadata.version);
2496 out.push(8);
2497 out.push(encoding as u8);
2498 out.push(model.metadata.additional_uv_count);
2499 out.push(model.metadata.index_sizes.vertex);
2500 out.push(model.metadata.index_sizes.texture);
2501 out.push(model.metadata.index_sizes.material);
2502 out.push(model.metadata.index_sizes.bone);
2503 out.push(model.metadata.index_sizes.morph);
2504 out.push(model.metadata.index_sizes.rigid_body);
2505
2506 write_pmx_string(&mut out, &model.metadata.name, encoding);
2507 write_pmx_string(&mut out, &model.metadata.english_name, encoding);
2508 write_pmx_string(&mut out, &model.metadata.comment, encoding);
2509 write_pmx_string(&mut out, &model.metadata.english_comment, encoding);
2510
2511 let vertex_count = model.geometry.positions.len() / 3;
2512 write_i32(&mut out, vertex_count as i32);
2513 for index in 0..vertex_count {
2514 write_f32_slice(
2515 &mut out,
2516 &model.geometry.positions[index * 3..index * 3 + 3],
2517 );
2518 write_f32_slice(&mut out, &model.geometry.normals[index * 3..index * 3 + 3]);
2519 write_f32_slice(&mut out, &model.geometry.uvs[index * 2..index * 2 + 2]);
2520 for uv in &model.geometry.additional_uvs {
2521 write_f32_slice(&mut out, &uv[index * 4..index * 4 + 4]);
2522 }
2523 out.push(2); for slot in 0..4 {
2525 write_sized_index(
2526 &mut out,
2527 model
2528 .geometry
2529 .skin_indices
2530 .get(index * 4 + slot)
2531 .copied()
2532 .unwrap_or(0) as i32,
2533 model.metadata.index_sizes.bone,
2534 );
2535 }
2536 for slot in 0..4 {
2537 write_f32(
2538 &mut out,
2539 model
2540 .geometry
2541 .skin_weights
2542 .get(index * 4 + slot)
2543 .copied()
2544 .unwrap_or(if slot == 0 { 1.0 } else { 0.0 }),
2545 );
2546 }
2547 write_f32(
2548 &mut out,
2549 model.geometry.edge_scale.get(index).copied().unwrap_or(1.0),
2550 );
2551 }
2552
2553 write_i32(&mut out, model.geometry.indices.len() as i32);
2554 for &index in &model.geometry.indices {
2555 write_vertex_index(&mut out, index, model.metadata.index_sizes.vertex);
2556 }
2557
2558 let textures = collect_pmx_textures(&model.materials);
2559 write_i32(&mut out, textures.len() as i32);
2560 for texture in &textures {
2561 write_pmx_string(&mut out, texture, encoding);
2562 }
2563
2564 write_i32(&mut out, model.materials.len() as i32);
2565 for material in &model.materials {
2566 write_pmx_string(&mut out, &material.name, encoding);
2567 write_pmx_string(&mut out, &material.english_name, encoding);
2568 write_f32_slice(&mut out, &material.diffuse);
2569 write_f32_slice(&mut out, &material.specular);
2570 write_f32(&mut out, material.specular_power);
2571 write_f32_slice(&mut out, &material.ambient);
2572 out.push(material_flag_bits(&material.flags));
2573 write_f32_slice(&mut out, &material.edge_color);
2574 write_f32(&mut out, material.edge_size);
2575 write_sized_index(
2576 &mut out,
2577 texture_index(&textures, &material.texture_path),
2578 model.metadata.index_sizes.texture,
2579 );
2580 write_sized_index(
2581 &mut out,
2582 texture_index(&textures, &material.sphere_texture_path),
2583 model.metadata.index_sizes.texture,
2584 );
2585 out.push(match material.sphere_mode.as_str() {
2586 "multiply" => 1,
2587 "add" => 2,
2588 "subTexture" => 3,
2589 _ => 0,
2590 });
2591 if let Some(shared) = material.shared_toon_index {
2592 out.push(1);
2593 out.push(shared);
2594 } else {
2595 out.push(0);
2596 write_sized_index(
2597 &mut out,
2598 texture_index(&textures, &material.toon_texture_path),
2599 model.metadata.index_sizes.texture,
2600 );
2601 }
2602 write_pmx_string(&mut out, "", encoding);
2603 write_i32(&mut out, material.face_count.saturating_mul(3));
2604 }
2605
2606 write_i32(&mut out, model.skeleton.bones.len() as i32);
2607 for bone in &model.skeleton.bones {
2608 write_pmx_string(&mut out, &bone.name, encoding);
2609 write_pmx_string(&mut out, &bone.english_name, encoding);
2610 write_f32_slice(&mut out, &bone.position);
2611 write_sized_index(&mut out, bone.parent_index, model.metadata.index_sizes.bone);
2612 write_i32(&mut out, bone.layer);
2613 let bits = bone_flag_bits(&bone.flags);
2614 write_u16(&mut out, bits);
2615 if bone.flags.indexed_tail {
2616 write_sized_index(&mut out, bone.tail_index, model.metadata.index_sizes.bone);
2617 } else {
2618 write_f32_slice(&mut out, &bone.tail_position.unwrap_or([0.0; 3]));
2619 }
2620 if bone.flags.append_rotate || bone.flags.append_translate {
2621 let append = bone.append_transform.as_ref();
2622 write_sized_index(
2623 &mut out,
2624 append.map(|value| value.parent_index).unwrap_or(-1),
2625 model.metadata.index_sizes.bone,
2626 );
2627 write_f32(&mut out, append.map(|value| value.weight).unwrap_or(0.0));
2628 }
2629 if bone.flags.fixed_axis {
2630 write_f32_slice(&mut out, &bone.fixed_axis.unwrap_or([0.0; 3]));
2631 }
2632 if bone.flags.local_axis {
2633 if let Some(axis) = &bone.local_axis {
2634 write_f32_slice(&mut out, &axis.x);
2635 write_f32_slice(&mut out, &axis.z);
2636 } else {
2637 write_f32_slice(&mut out, &[1.0, 0.0, 0.0]);
2638 write_f32_slice(&mut out, &[0.0, 0.0, 1.0]);
2639 }
2640 }
2641 if bone.flags.external_parent_transform {
2642 write_i32(&mut out, bone.external_parent_key.unwrap_or(0));
2643 }
2644 if bone.flags.ik {
2645 if let Some(ik) = &bone.ik {
2646 write_sized_index(&mut out, ik.target_index, model.metadata.index_sizes.bone);
2647 write_i32(&mut out, ik.loop_count);
2648 write_f32(&mut out, ik.limit_angle);
2649 write_i32(&mut out, ik.links.len() as i32);
2650 for link in &ik.links {
2651 write_sized_index(&mut out, link.bone_index, model.metadata.index_sizes.bone);
2652 out.push(u8::from(link.limits.is_some()));
2653 if let Some(limits) = &link.limits {
2654 write_f32_slice(&mut out, &limits.lower);
2655 write_f32_slice(&mut out, &limits.upper);
2656 }
2657 }
2658 } else {
2659 write_sized_index(&mut out, -1, model.metadata.index_sizes.bone);
2660 write_i32(&mut out, 0);
2661 write_f32(&mut out, 0.0);
2662 write_i32(&mut out, 0);
2663 }
2664 }
2665 }
2666
2667 write_i32(&mut out, model.morphs.len() as i32);
2668 for morph in &model.morphs {
2669 write_pmx_string(&mut out, &morph.name, encoding);
2670 write_pmx_string(&mut out, &morph.english_name, encoding);
2671 out.push(0);
2672 let morph_type = morph_type_byte(morph);
2673 out.push(morph_type);
2674 write_i32(&mut out, morph_offset_count(morph, morph_type) as i32);
2675 match morph_type {
2676 0 => write_group_morph_offsets(&mut out, &morph.group_offsets, model),
2677 1 => {
2678 for offset in &morph.vertex_offsets {
2679 write_vertex_index(
2680 &mut out,
2681 offset.vertex_index,
2682 model.metadata.index_sizes.vertex,
2683 );
2684 write_f32_slice(&mut out, &offset.position);
2685 }
2686 }
2687 2 => {
2688 for offset in &morph.bone_offsets {
2689 write_sized_index(&mut out, offset.bone_index, model.metadata.index_sizes.bone);
2690 write_f32_slice(&mut out, &offset.translation);
2691 write_f32_slice(&mut out, &offset.rotation);
2692 }
2693 }
2694 3 => write_uv_morph_offsets(&mut out, &morph.uv_offsets, model),
2695 4..=7 => {
2696 for offset in &morph.additional_uv_offsets {
2697 write_vertex_index(
2698 &mut out,
2699 offset.vertex_index,
2700 model.metadata.index_sizes.vertex,
2701 );
2702 write_f32_slice(&mut out, &offset.uv);
2703 }
2704 }
2705 8 => {
2706 for offset in &morph.material_offsets {
2707 write_sized_index(
2708 &mut out,
2709 offset.material_index,
2710 model.metadata.index_sizes.material,
2711 );
2712 out.push(if offset.operation == "multiply" { 0 } else { 1 });
2713 write_f32_slice(&mut out, &offset.diffuse);
2714 write_f32_slice(&mut out, &offset.specular);
2715 write_f32(&mut out, offset.specular_power);
2716 write_f32_slice(&mut out, &offset.ambient);
2717 write_f32_slice(&mut out, &offset.edge_color);
2718 write_f32(&mut out, offset.edge_size);
2719 write_f32_slice(&mut out, &offset.texture_factor);
2720 write_f32_slice(&mut out, &offset.sphere_texture_factor);
2721 write_f32_slice(&mut out, &offset.toon_texture_factor);
2722 }
2723 }
2724 9 => write_group_morph_offsets(&mut out, &morph.flip_offsets, model),
2725 10 => {
2726 for offset in &morph.impulse_offsets {
2727 write_sized_index(
2728 &mut out,
2729 offset.rigid_body_index,
2730 model.metadata.index_sizes.rigid_body,
2731 );
2732 out.push(u8::from(offset.local));
2733 write_f32_slice(&mut out, &offset.velocity);
2734 write_f32_slice(&mut out, &offset.torque);
2735 }
2736 }
2737 _ => {}
2738 }
2739 }
2740
2741 write_i32(&mut out, model.display_frames.len() as i32);
2742 for frame in &model.display_frames {
2743 write_pmx_string(&mut out, &frame.name, encoding);
2744 write_pmx_string(&mut out, &frame.english_name, encoding);
2745 out.push(u8::from(frame.special));
2746 write_i32(&mut out, frame.frames.len() as i32);
2747 for item in &frame.frames {
2748 if item.kind == "morph" {
2749 out.push(1);
2750 write_sized_index(&mut out, item.index, model.metadata.index_sizes.morph);
2751 } else {
2752 out.push(0);
2753 write_sized_index(&mut out, item.index, model.metadata.index_sizes.bone);
2754 }
2755 }
2756 }
2757
2758 write_i32(&mut out, model.rigid_bodies.len() as i32);
2759 for body in &model.rigid_bodies {
2760 write_pmx_string(&mut out, &body.name, encoding);
2761 write_pmx_string(&mut out, &body.english_name, encoding);
2762 write_sized_index(&mut out, body.bone_index, model.metadata.index_sizes.bone);
2763 out.push(body.group);
2764 write_u16(&mut out, body.mask);
2765 out.push(match body.shape.as_str() {
2766 "box" => 1,
2767 "capsule" => 2,
2768 _ => 0,
2769 });
2770 write_f32_slice(&mut out, &body.size);
2771 write_f32_slice(&mut out, &body.position);
2772 write_f32_slice(&mut out, &body.rotation);
2773 write_f32(&mut out, body.mass);
2774 write_f32(&mut out, body.linear_damping);
2775 write_f32(&mut out, body.angular_damping);
2776 write_f32(&mut out, body.restitution);
2777 write_f32(&mut out, body.friction);
2778 out.push(match body.mode.as_str() {
2779 "dynamic" => 1,
2780 "dynamicBone" => 2,
2781 _ => 0,
2782 });
2783 }
2784
2785 write_i32(&mut out, model.joints.len() as i32);
2786 for joint in &model.joints {
2787 write_pmx_string(&mut out, &joint.name, encoding);
2788 write_pmx_string(&mut out, &joint.english_name, encoding);
2789 out.push(match joint.kind.as_str() {
2790 "generic6dof" => 1,
2791 "point2point" => 2,
2792 "coneTwist" => 3,
2793 "slider" => 4,
2794 "hinge" => 5,
2795 _ => 0,
2796 });
2797 write_sized_index(
2798 &mut out,
2799 joint.rigid_body_index_a,
2800 model.metadata.index_sizes.rigid_body,
2801 );
2802 write_sized_index(
2803 &mut out,
2804 joint.rigid_body_index_b,
2805 model.metadata.index_sizes.rigid_body,
2806 );
2807 write_f32_slice(&mut out, &joint.position);
2808 write_f32_slice(&mut out, &joint.rotation);
2809 write_f32_slice(&mut out, &joint.translation_lower_limit);
2810 write_f32_slice(&mut out, &joint.translation_upper_limit);
2811 write_f32_slice(&mut out, &joint.rotation_lower_limit);
2812 write_f32_slice(&mut out, &joint.rotation_upper_limit);
2813 write_f32_slice(&mut out, &joint.spring_translation_factor);
2814 write_f32_slice(&mut out, &joint.spring_rotation_factor);
2815 }
2816
2817 if model.metadata.version >= 2.05 {
2818 write_i32(&mut out, model.soft_bodies.len() as i32);
2819 for soft_body in &model.soft_bodies {
2820 write_pmx_string(&mut out, &soft_body.name, encoding);
2821 write_pmx_string(&mut out, &soft_body.english_name, encoding);
2822 out.push(if soft_body.kind == "rope" { 1 } else { 0 });
2823 write_sized_index(
2824 &mut out,
2825 soft_body.material_index,
2826 model.metadata.index_sizes.material,
2827 );
2828 out.push(soft_body.collision_group);
2829 write_u16(&mut out, soft_body.collision_mask);
2830 out.push(soft_body.flags);
2831 write_i32(&mut out, soft_body.bending_constraints_distance);
2832 write_i32(&mut out, soft_body.cluster_count);
2833 write_f32(&mut out, soft_body.total_mass);
2834 write_f32(&mut out, soft_body.collision_margin);
2835 out.extend_from_slice(&[0u8; 4 + 12 * 4 + 6 * 4 + 4 * 4 + 3 * 4]);
2836 write_i32(&mut out, 0);
2837 write_i32(&mut out, 0);
2838 }
2839 }
2840
2841 out
2842}
2843
2844fn read_section_count(r: &mut Reader<'_>) -> Result<usize, ImportError> {
2845 let count = r.read_i32_le()?;
2846 if count < 0 {
2847 return Err(ImportError::SectionOverflow);
2848 }
2849 Ok(count as usize)
2850}
2851
2852fn read_parsed_geometry(
2853 r: &mut Reader<'_>,
2854 header: &PmxHeader,
2855 vertex_count: usize,
2856) -> Result<PmxParsedGeometry, ImportError> {
2857 let mut positions = Vec::with_capacity(vertex_count * 3);
2858 let mut normals = Vec::with_capacity(vertex_count * 3);
2859 let mut uvs = Vec::with_capacity(vertex_count * 2);
2860 let mut additional_uvs =
2861 vec![Vec::with_capacity(vertex_count * 4); header.extra_uv_count as usize];
2862 let mut skin_indices = Vec::with_capacity(vertex_count * 4);
2863 let mut skin_weights = Vec::with_capacity(vertex_count * 4);
2864 let mut edge_scale = Vec::with_capacity(vertex_count);
2865
2866 for _ in 0..vertex_count {
2867 positions.extend_from_slice(&r.read_vec3_array()?);
2868 normals.extend_from_slice(&r.read_vec3_array()?);
2869 uvs.extend_from_slice(&r.read_vec2_array()?);
2870 for uv in additional_uvs.iter_mut() {
2871 uv.extend_from_slice(&r.read_vec4_array()?);
2872 }
2873 let weight_type = r.read_u8()?;
2874 let mut indices = [0u32; 4];
2875 let mut weights = [0.0f32; 4];
2876 match weight_type {
2877 0 => {
2878 indices[0] =
2879 normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
2880 weights[0] = 1.0;
2881 }
2882 1 | 3 => {
2883 indices[0] =
2884 normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
2885 indices[1] =
2886 normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
2887 let w = r.read_f32_le()?;
2888 weights[0] = w;
2889 weights[1] = 1.0 - w;
2890 if weight_type == 3 {
2891 r.skip(36)?;
2892 }
2893 }
2894 2 | 4 => {
2895 for index in &mut indices {
2896 *index =
2897 normalize_nonnegative_index(r.read_sized_index(header.bone_index_size)?);
2898 }
2899 for weight in &mut weights {
2900 *weight = r.read_f32_le()?;
2901 }
2902 }
2903 _ => return Err(ImportError::SectionOverflow),
2904 }
2905 skin_indices.extend_from_slice(&indices);
2906 skin_weights.extend_from_slice(&weights);
2907 edge_scale.push(r.read_f32_le()?);
2908 }
2909
2910 let index_count = read_section_count(r)?;
2911 let mut indices = Vec::with_capacity(index_count);
2912 for _ in 0..index_count {
2913 indices.push(r.read_vertex_index(header.vertex_index_size)?);
2914 }
2915
2916 Ok(PmxParsedGeometry {
2917 positions,
2918 normals,
2919 uvs,
2920 additional_uvs,
2921 indices,
2922 skin_indices,
2923 skin_weights,
2924 edge_scale,
2925 material_groups: Vec::new(),
2926 })
2927}
2928
2929fn normalize_nonnegative_index(index: i32) -> u32 {
2930 if index < 0 { 0 } else { index as u32 }
2931}
2932
2933fn read_parsed_textures(
2934 r: &mut Reader<'_>,
2935 encoding: TextEncoding,
2936) -> Result<Vec<String>, ImportError> {
2937 let count = read_section_count(r)?;
2938 let mut textures = Vec::with_capacity(count);
2939 for _ in 0..count {
2940 textures.push(r.read_string(encoding)?);
2941 }
2942 Ok(textures)
2943}
2944
2945fn read_parsed_materials(
2946 r: &mut Reader<'_>,
2947 header: &PmxHeader,
2948 textures: &[String],
2949) -> Result<Vec<PmxParsedMaterial>, ImportError> {
2950 let count = read_section_count(r)?;
2951 let mut materials = Vec::with_capacity(count);
2952 for _ in 0..count {
2953 let name = r.read_string(header.encoding)?;
2954 let english_name = r.read_string(header.encoding)?;
2955 let diffuse = r.read_vec4_array()?;
2956 let specular = r.read_vec3_array()?;
2957 let specular_power = r.read_f32_le()?;
2958 let ambient = r.read_vec3_array()?;
2959 let flag_bits = r.read_u8()?;
2960 let edge_color = r.read_vec4_array()?;
2961 let edge_size = r.read_f32_le()?;
2962 let texture_index = r.read_sized_index(header.texture_index_size)?;
2963 let sphere_texture_index = r.read_sized_index(header.texture_index_size)?;
2964 let sphere_mode_raw = r.read_u8()?;
2965 let toon_flag = r.read_u8()?;
2966 let (toon_texture_path, shared_toon_index) = if toon_flag == 0 {
2967 let toon_texture_index = r.read_sized_index(header.texture_index_size)?;
2968 (texture_path(textures, toon_texture_index), None)
2969 } else {
2970 (String::new(), Some(r.read_u8()?))
2971 };
2972 let _memo = r.read_string(header.encoding)?;
2973 let face_count = r.read_i32_le()? / 3;
2974 materials.push(PmxParsedMaterial {
2975 name,
2976 english_name,
2977 texture_path: texture_path(textures, texture_index),
2978 sphere_texture_path: texture_path(textures, sphere_texture_index),
2979 sphere_mode: match sphere_mode_raw {
2980 1 => "multiply",
2981 2 => "add",
2982 3 => "subTexture",
2983 _ => "none",
2984 }
2985 .to_owned(),
2986 toon_texture_path,
2987 shared_toon_index,
2988 diffuse,
2989 specular,
2990 specular_power,
2991 ambient,
2992 edge_color,
2993 edge_size,
2994 flags: PmxParsedMaterialFlags {
2995 double_sided: flag_bits & 0x01 != 0,
2996 ground_shadow: flag_bits & 0x02 != 0,
2997 self_shadow_map: flag_bits & 0x04 != 0,
2998 self_shadow: flag_bits & 0x08 != 0,
2999 edge: flag_bits & 0x10 != 0,
3000 vertex_color: flag_bits & 0x20 != 0,
3001 point_draw: flag_bits & 0x40 != 0,
3002 line_draw: flag_bits & 0x80 != 0,
3003 },
3004 face_count,
3005 });
3006 }
3007 Ok(materials)
3008}
3009
3010fn texture_path(textures: &[String], index: i32) -> String {
3011 if index < 0 {
3012 String::new()
3013 } else {
3014 textures.get(index as usize).cloned().unwrap_or_default()
3015 }
3016}
3017
3018fn build_material_groups(materials: &[PmxParsedMaterial]) -> Vec<PmxParsedMaterialGroup> {
3019 let mut start = 0usize;
3020 materials
3021 .iter()
3022 .enumerate()
3023 .map(|(material_index, material)| {
3024 let count = (material.face_count.max(0) as usize) * 3;
3025 let group = PmxParsedMaterialGroup {
3026 start,
3027 count,
3028 material_index,
3029 };
3030 start += count;
3031 group
3032 })
3033 .collect()
3034}
3035
3036fn pmx_text_encoding(metadata: &PmxParsedMetadata) -> TextEncoding {
3037 match metadata.encoding.as_str() {
3038 "utf-16-le" => TextEncoding::Utf16Le,
3039 _ => TextEncoding::Utf8,
3040 }
3041}
3042
3043fn write_pmx_string(out: &mut Vec<u8>, text: &str, encoding: TextEncoding) {
3044 match encoding {
3045 TextEncoding::Utf8 => {
3046 write_i32(out, text.len() as i32);
3047 out.extend_from_slice(text.as_bytes());
3048 }
3049 TextEncoding::Utf16Le => {
3050 let bytes = text
3051 .encode_utf16()
3052 .flat_map(u16::to_le_bytes)
3053 .collect::<Vec<_>>();
3054 write_i32(out, bytes.len() as i32);
3055 out.extend_from_slice(&bytes);
3056 }
3057 }
3058}
3059
3060fn write_f32_slice(out: &mut Vec<u8>, values: &[f32]) {
3061 for &value in values {
3062 write_f32(out, value);
3063 }
3064}
3065
3066fn write_f32(out: &mut Vec<u8>, value: f32) {
3067 out.extend_from_slice(&value.to_le_bytes());
3068}
3069
3070fn write_i32(out: &mut Vec<u8>, value: i32) {
3071 out.extend_from_slice(&value.to_le_bytes());
3072}
3073
3074fn write_u16(out: &mut Vec<u8>, value: u16) {
3075 out.extend_from_slice(&value.to_le_bytes());
3076}
3077
3078fn write_sized_index(out: &mut Vec<u8>, value: i32, size: u8) {
3079 match size {
3080 1 => out.push(value as i8 as u8),
3081 2 => out.extend_from_slice(&(value as i16).to_le_bytes()),
3082 4 => out.extend_from_slice(&value.to_le_bytes()),
3083 _ => {}
3084 }
3085}
3086
3087fn write_vertex_index(out: &mut Vec<u8>, value: u32, size: u8) {
3088 match size {
3089 1 => out.push(value as u8),
3090 2 => out.extend_from_slice(&(value as u16).to_le_bytes()),
3091 4 => out.extend_from_slice(&(value as i32).to_le_bytes()),
3092 _ => {}
3093 }
3094}
3095
3096fn material_flag_bits(flags: &PmxParsedMaterialFlags) -> u8 {
3097 u8::from(flags.double_sided)
3098 | (u8::from(flags.ground_shadow) << 1)
3099 | (u8::from(flags.self_shadow_map) << 2)
3100 | (u8::from(flags.self_shadow) << 3)
3101 | (u8::from(flags.edge) << 4)
3102 | (u8::from(flags.vertex_color) << 5)
3103 | (u8::from(flags.point_draw) << 6)
3104 | (u8::from(flags.line_draw) << 7)
3105}
3106
3107fn bone_flag_bits(flags: &PmxParsedBoneFlags) -> u16 {
3108 u16::from(flags.indexed_tail)
3109 | (u16::from(flags.rotatable) << 1)
3110 | (u16::from(flags.translatable) << 2)
3111 | (u16::from(flags.visible) << 3)
3112 | (u16::from(flags.enabled) << 4)
3113 | if flags.ik { BONE_FLAG_IK } else { 0 }
3114 | if flags.append_local {
3115 BONE_FLAG_LOCAL_APPEND
3116 } else {
3117 0
3118 }
3119 | if flags.append_rotate {
3120 BONE_FLAG_APPEND_ROTATE
3121 } else {
3122 0
3123 }
3124 | if flags.append_translate {
3125 BONE_FLAG_APPEND_TRANSLATE
3126 } else {
3127 0
3128 }
3129 | if flags.fixed_axis {
3130 BONE_FLAG_FIXED_AXIS
3131 } else {
3132 0
3133 }
3134 | if flags.local_axis {
3135 BONE_FLAG_LOCAL_AXIS
3136 } else {
3137 0
3138 }
3139 | (u16::from(flags.transform_after_physics) << 12)
3140 | if flags.external_parent_transform {
3141 BONE_FLAG_EXTERNAL_PARENT
3142 } else {
3143 0
3144 }
3145}
3146
3147fn collect_pmx_textures(materials: &[PmxParsedMaterial]) -> Vec<String> {
3148 let mut textures = Vec::new();
3149 for texture in materials.iter().flat_map(|material| {
3150 [
3151 material.texture_path.as_str(),
3152 material.sphere_texture_path.as_str(),
3153 material.toon_texture_path.as_str(),
3154 ]
3155 }) {
3156 if !texture.is_empty() && !textures.iter().any(|existing| existing == texture) {
3157 textures.push(texture.to_owned());
3158 }
3159 }
3160 textures
3161}
3162
3163fn texture_index(textures: &[String], texture: &str) -> i32 {
3164 if texture.is_empty() {
3165 -1
3166 } else {
3167 textures
3168 .iter()
3169 .position(|candidate| candidate == texture)
3170 .map(|index| index as i32)
3171 .unwrap_or(-1)
3172 }
3173}
3174
3175fn morph_type_byte(morph: &PmxParsedMorph) -> u8 {
3176 match morph.kind.as_str() {
3177 "group" => 0,
3178 "vertex" => 1,
3179 "bone" => 2,
3180 "uv" => 3,
3181 "additionalUv" => morph
3182 .additional_uv_offsets
3183 .first()
3184 .map(|offset| offset.uv_index.saturating_add(4).min(7))
3185 .unwrap_or(4),
3186 "material" => 8,
3187 "flip" => 9,
3188 "impulse" => 10,
3189 _ => 0,
3190 }
3191}
3192
3193fn morph_offset_count(morph: &PmxParsedMorph, morph_type: u8) -> usize {
3194 match morph_type {
3195 0 => morph.group_offsets.len(),
3196 1 => morph.vertex_offsets.len(),
3197 2 => morph.bone_offsets.len(),
3198 3 => morph.uv_offsets.len(),
3199 4..=7 => morph.additional_uv_offsets.len(),
3200 8 => morph.material_offsets.len(),
3201 9 => morph.flip_offsets.len(),
3202 10 => morph.impulse_offsets.len(),
3203 _ => 0,
3204 }
3205}
3206
3207fn write_group_morph_offsets(
3208 out: &mut Vec<u8>,
3209 offsets: &[PmxParsedGroupMorphOffset],
3210 model: &PmxParsedModel,
3211) {
3212 for offset in offsets {
3213 write_sized_index(out, offset.morph_index, model.metadata.index_sizes.morph);
3214 write_f32(out, offset.weight);
3215 }
3216}
3217
3218fn write_uv_morph_offsets(
3219 out: &mut Vec<u8>,
3220 offsets: &[PmxParsedUvMorphOffset],
3221 model: &PmxParsedModel,
3222) {
3223 for offset in offsets {
3224 write_vertex_index(out, offset.vertex_index, model.metadata.index_sizes.vertex);
3225 write_f32_slice(out, &offset.uv);
3226 }
3227}
3228
3229fn read_parsed_bones(
3230 r: &mut Reader<'_>,
3231 header: &PmxHeader,
3232 count: usize,
3233) -> Result<Vec<PmxParsedBone>, ImportError> {
3234 let mut bones = Vec::with_capacity(count);
3235 for _ in 0..count {
3236 let name = r.read_string(header.encoding)?;
3237 let english_name = r.read_string(header.encoding)?;
3238 let position = r.read_vec3_array()?;
3239 let parent_index = r.read_sized_index(header.bone_index_size)?;
3240 let layer = r.read_i32_le()?;
3241 let bits = r.read_u16_le()?;
3242 let flags = parsed_bone_flags(bits);
3243 let (tail_index, tail_position) = if flags.indexed_tail {
3244 (r.read_sized_index(header.bone_index_size)?, None)
3245 } else {
3246 (-1, Some(r.read_vec3_array()?))
3247 };
3248 let append_transform = if flags.append_rotate || flags.append_translate {
3249 Some(PmxParsedAppendTransform {
3250 parent_index: r.read_sized_index(header.bone_index_size)?,
3251 weight: r.read_f32_le()?,
3252 })
3253 } else {
3254 None
3255 };
3256 let fixed_axis = if flags.fixed_axis {
3257 Some(r.read_vec3_array()?)
3258 } else {
3259 None
3260 };
3261 let local_axis = if flags.local_axis {
3262 Some(PmxParsedLocalAxis {
3263 x: r.read_vec3_array()?,
3264 z: r.read_vec3_array()?,
3265 })
3266 } else {
3267 None
3268 };
3269 let external_parent_key = if flags.external_parent_transform {
3270 Some(r.read_i32_le()?)
3271 } else {
3272 None
3273 };
3274 let ik = if flags.ik {
3275 let target_index = r.read_sized_index(header.bone_index_size)?;
3276 let loop_count = r.read_i32_le()?;
3277 let limit_angle = r.read_f32_le()?;
3278 let link_count = read_section_count(r)?;
3279 let mut links = Vec::with_capacity(link_count);
3280 for _ in 0..link_count {
3281 let bone_index = r.read_sized_index(header.bone_index_size)?;
3282 let has_limit = r.read_u8()? != 0;
3283 links.push(PmxParsedIkLink {
3284 bone_index,
3285 limits: if has_limit {
3286 Some(PmxParsedIkLimit {
3287 lower: r.read_vec3_array()?,
3288 upper: r.read_vec3_array()?,
3289 })
3290 } else {
3291 None
3292 },
3293 });
3294 }
3295 Some(PmxParsedIk {
3296 target_index,
3297 loop_count,
3298 limit_angle,
3299 links,
3300 })
3301 } else {
3302 None
3303 };
3304 bones.push(PmxParsedBone {
3305 name,
3306 english_name,
3307 parent_index,
3308 layer,
3309 position,
3310 tail_index,
3311 tail_position,
3312 flags,
3313 append_transform,
3314 fixed_axis,
3315 local_axis,
3316 external_parent_key,
3317 ik,
3318 });
3319 }
3320 Ok(bones)
3321}
3322
3323fn parsed_bone_flags(bits: u16) -> PmxParsedBoneFlags {
3324 PmxParsedBoneFlags {
3325 indexed_tail: bits & BONE_FLAG_TAIL_INDEX != 0,
3326 rotatable: bits & 0x0002 != 0,
3327 translatable: bits & 0x0004 != 0,
3328 visible: bits & 0x0008 != 0,
3329 enabled: bits & 0x0010 != 0,
3330 ik: bits & BONE_FLAG_IK != 0,
3331 append_local: bits & BONE_FLAG_LOCAL_APPEND != 0,
3332 append_rotate: bits & BONE_FLAG_APPEND_ROTATE != 0,
3333 append_translate: bits & BONE_FLAG_APPEND_TRANSLATE != 0,
3334 fixed_axis: bits & BONE_FLAG_FIXED_AXIS != 0,
3335 local_axis: bits & BONE_FLAG_LOCAL_AXIS != 0,
3336 transform_after_physics: bits & 0x1000 != 0,
3337 external_parent_transform: bits & BONE_FLAG_EXTERNAL_PARENT != 0,
3338 }
3339}
3340
3341fn read_parsed_morphs(
3342 r: &mut Reader<'_>,
3343 header: &PmxHeader,
3344) -> Result<Vec<PmxParsedMorph>, ImportError> {
3345 let count = read_section_count(r)?;
3346 let mut morphs = Vec::with_capacity(count);
3347 for _ in 0..count {
3348 let name = r.read_string(header.encoding)?;
3349 let english_name = r.read_string(header.encoding)?;
3350 let _panel = r.read_u8()?;
3351 let morph_type = r.read_u8()?;
3352 let offset_count = read_section_count(r)?;
3353 let mut morph = PmxParsedMorph {
3354 name,
3355 english_name,
3356 kind: match morph_type {
3357 0 => "group",
3358 1 => "vertex",
3359 2 => "bone",
3360 3 => "uv",
3361 4..=7 => "additionalUv",
3362 8 => "material",
3363 9 => "flip",
3364 10 => "impulse",
3365 _ => "unknown",
3366 }
3367 .to_owned(),
3368 vertex_offsets: Vec::new(),
3369 group_offsets: Vec::new(),
3370 bone_offsets: Vec::new(),
3371 uv_offsets: Vec::new(),
3372 additional_uv_offsets: Vec::new(),
3373 material_offsets: Vec::new(),
3374 flip_offsets: Vec::new(),
3375 impulse_offsets: Vec::new(),
3376 };
3377 for _ in 0..offset_count {
3378 match morph_type {
3379 0 => morph.group_offsets.push(PmxParsedGroupMorphOffset {
3380 morph_index: r.read_sized_index(header.morph_index_size)?,
3381 weight: r.read_f32_le()?,
3382 }),
3383 1 => morph.vertex_offsets.push(PmxParsedVertexMorphOffset {
3384 vertex_index: r.read_vertex_index(header.vertex_index_size)?,
3385 position: r.read_vec3_array()?,
3386 }),
3387 2 => morph.bone_offsets.push(PmxParsedBoneMorphOffset {
3388 bone_index: r.read_sized_index(header.bone_index_size)?,
3389 translation: r.read_vec3_array()?,
3390 rotation: r.read_vec4_array()?,
3391 }),
3392 3 => morph.uv_offsets.push(PmxParsedUvMorphOffset {
3393 vertex_index: r.read_vertex_index(header.vertex_index_size)?,
3394 uv: r.read_vec4_array()?,
3395 }),
3396 4..=7 => morph
3397 .additional_uv_offsets
3398 .push(PmxParsedAdditionalUvMorphOffset {
3399 vertex_index: r.read_vertex_index(header.vertex_index_size)?,
3400 uv_index: morph_type - 4,
3401 uv: r.read_vec4_array()?,
3402 }),
3403 8 => morph.material_offsets.push(PmxParsedMaterialMorphOffset {
3404 material_index: r.read_sized_index(header.material_index_size)?,
3405 operation: if r.read_u8()? == 0 {
3406 "multiply".to_owned()
3407 } else {
3408 "add".to_owned()
3409 },
3410 diffuse: r.read_vec4_array()?,
3411 specular: r.read_vec3_array()?,
3412 specular_power: r.read_f32_le()?,
3413 ambient: r.read_vec3_array()?,
3414 edge_color: r.read_vec4_array()?,
3415 edge_size: r.read_f32_le()?,
3416 texture_factor: r.read_vec4_array()?,
3417 sphere_texture_factor: r.read_vec4_array()?,
3418 toon_texture_factor: r.read_vec4_array()?,
3419 }),
3420 9 => morph.flip_offsets.push(PmxParsedGroupMorphOffset {
3421 morph_index: r.read_sized_index(header.morph_index_size)?,
3422 weight: r.read_f32_le()?,
3423 }),
3424 10 => morph.impulse_offsets.push(PmxParsedImpulseMorphOffset {
3425 rigid_body_index: r.read_sized_index(header.rigidbody_index_size)?,
3426 local: r.read_u8()? != 0,
3427 velocity: r.read_vec3_array()?,
3428 torque: r.read_vec3_array()?,
3429 }),
3430 _ => return Err(ImportError::SectionOverflow),
3431 }
3432 }
3433 morphs.push(morph);
3434 }
3435 Ok(morphs)
3436}
3437
3438fn read_parsed_display_frames(
3439 r: &mut Reader<'_>,
3440 header: &PmxHeader,
3441) -> Result<Vec<PmxParsedDisplayFrame>, ImportError> {
3442 let count = read_section_count(r)?;
3443 let mut frames = Vec::with_capacity(count);
3444 for _ in 0..count {
3445 let name = r.read_string(header.encoding)?;
3446 let english_name = r.read_string(header.encoding)?;
3447 let special = r.read_u8()? == 1;
3448 let item_count = read_section_count(r)?;
3449 let mut items = Vec::with_capacity(item_count);
3450 for _ in 0..item_count {
3451 let kind = r.read_u8()?;
3452 let index = match kind {
3453 0 => r.read_sized_index(header.bone_index_size)?,
3454 1 => r.read_sized_index(header.morph_index_size)?,
3455 _ => return Err(ImportError::SectionOverflow),
3456 };
3457 items.push(PmxParsedDisplayFrameElement {
3458 kind: if kind == 0 {
3459 "bone".to_owned()
3460 } else {
3461 "morph".to_owned()
3462 },
3463 index,
3464 });
3465 }
3466 frames.push(PmxParsedDisplayFrame {
3467 name,
3468 english_name,
3469 special,
3470 frames: items,
3471 });
3472 }
3473 Ok(frames)
3474}
3475
3476fn read_parsed_rigid_bodies(
3477 r: &mut Reader<'_>,
3478 header: &PmxHeader,
3479) -> Result<Vec<PmxParsedRigidBody>, ImportError> {
3480 let count = read_section_count(r)?;
3481 let mut bodies = Vec::with_capacity(count);
3482 for _ in 0..count {
3483 bodies.push(PmxParsedRigidBody {
3484 name: r.read_string(header.encoding)?,
3485 english_name: r.read_string(header.encoding)?,
3486 bone_index: r.read_sized_index(header.bone_index_size)?,
3487 group: r.read_u8()?,
3488 mask: r.read_u16_le()?,
3489 shape: match r.read_u8()? {
3490 0 => "sphere",
3491 1 => "box",
3492 2 => "capsule",
3493 _ => "unknown",
3494 }
3495 .to_owned(),
3496 size: r.read_vec3_array()?,
3497 position: r.read_vec3_array()?,
3498 rotation: r.read_vec3_array()?,
3499 mass: r.read_f32_le()?,
3500 linear_damping: r.read_f32_le()?,
3501 angular_damping: r.read_f32_le()?,
3502 restitution: r.read_f32_le()?,
3503 friction: r.read_f32_le()?,
3504 mode: match r.read_u8()? {
3505 0 => "static",
3506 1 => "dynamic",
3507 2 => "dynamicBone",
3508 _ => "unknown",
3509 }
3510 .to_owned(),
3511 });
3512 }
3513 Ok(bodies)
3514}
3515
3516fn read_parsed_joints(
3517 r: &mut Reader<'_>,
3518 header: &PmxHeader,
3519) -> Result<Vec<PmxParsedJoint>, ImportError> {
3520 let count = read_section_count(r)?;
3521 let mut joints = Vec::with_capacity(count);
3522 for _ in 0..count {
3523 joints.push(PmxParsedJoint {
3524 name: r.read_string(header.encoding)?,
3525 english_name: r.read_string(header.encoding)?,
3526 kind: match r.read_u8()? {
3527 0 => "generic6dofSpring",
3528 1 => "generic6dof",
3529 2 => "point2point",
3530 3 => "coneTwist",
3531 4 => "slider",
3532 5 => "hinge",
3533 _ => "unknown",
3534 }
3535 .to_owned(),
3536 rigid_body_index_a: r.read_sized_index(header.rigidbody_index_size)?,
3537 rigid_body_index_b: r.read_sized_index(header.rigidbody_index_size)?,
3538 position: r.read_vec3_array()?,
3539 rotation: r.read_vec3_array()?,
3540 translation_lower_limit: r.read_vec3_array()?,
3541 translation_upper_limit: r.read_vec3_array()?,
3542 rotation_lower_limit: r.read_vec3_array()?,
3543 rotation_upper_limit: r.read_vec3_array()?,
3544 spring_translation_factor: r.read_vec3_array()?,
3545 spring_rotation_factor: r.read_vec3_array()?,
3546 });
3547 }
3548 Ok(joints)
3549}
3550
3551fn read_parsed_soft_bodies(
3552 r: &mut Reader<'_>,
3553 header: &PmxHeader,
3554) -> Result<Vec<PmxParsedSoftBody>, ImportError> {
3555 let count = read_section_count(r)?;
3556 let mut soft_bodies = Vec::with_capacity(count);
3557 for _ in 0..count {
3558 let name = r.read_string(header.encoding)?;
3559 let english_name = r.read_string(header.encoding)?;
3560 let kind = match r.read_u8()? {
3561 0 => "triMesh",
3562 1 => "rope",
3563 _ => "unknown",
3564 }
3565 .to_owned();
3566 let material_index = r.read_sized_index(header.material_index_size)?;
3567 let collision_group = r.read_u8()?;
3568 let collision_mask = r.read_u16_le()?;
3569 let flags = r.read_u8()?;
3570 let bending_constraints_distance = r.read_i32_le()?;
3571 let cluster_count = r.read_i32_le()?;
3572 let total_mass = r.read_f32_le()?;
3573 let collision_margin = r.read_f32_le()?;
3574 r.skip(4 + 12 * 4 + 6 * 4 + 4 * 4 + 3 * 4)?;
3575 let anchor_count = read_section_count(r)?;
3576 for _ in 0..anchor_count {
3577 r.read_sized_index(header.rigidbody_index_size)?;
3578 r.read_vertex_index(header.vertex_index_size)?;
3579 r.skip(1)?;
3580 }
3581 let pin_count = read_section_count(r)?;
3582 for _ in 0..pin_count {
3583 r.read_vertex_index(header.vertex_index_size)?;
3584 }
3585 soft_bodies.push(PmxParsedSoftBody {
3586 name,
3587 english_name,
3588 kind,
3589 material_index,
3590 collision_group,
3591 collision_mask,
3592 flags,
3593 bending_constraints_distance,
3594 cluster_count,
3595 total_mass,
3596 collision_margin,
3597 });
3598 }
3599 Ok(soft_bodies)
3600}
3601
3602#[derive(Debug)]
3603pub struct PmxRuntimeImport {
3604 pub model: ModelArena,
3605 pub bone_names: Vec<String>,
3606 pub bone_name_to_index: HashMap<Vec<u8>, BoneIndex>,
3607 pub morph_name_to_index: HashMap<Vec<u8>, MorphIndex>,
3608 pub ik_solver_bone_name_to_index: HashMap<Vec<u8>, usize>,
3609}
3610
3611pub fn import_pmx_runtime(data: &[u8]) -> Result<PmxRuntimeImport, ImportError> {
3612 let (header, pos) = read_header(data)?;
3613 let pos = skip_model_info(data, &header, pos)?;
3614 let pos = skip_vertices(data, &header, pos)?;
3615 let pos = skip_faces(data, header.vertex_index_size, pos)?;
3616 let pos = skip_textures(data, header.encoding, pos)?;
3617 let pos = skip_materials(data, &header, pos)?;
3618 let (bone_import, pos) = read_bones(data, &header, pos)?;
3619 let (morph_names, morph_init, _pos) = read_morph_offsets(data, &header, pos)?;
3620
3621 let mut bone_name_to_index = HashMap::with_capacity(bone_import.bone_name_bytes.len() * 2);
3622 for (i, bytes) in bone_import.bone_name_bytes.iter().enumerate() {
3623 let index = BoneIndex(i as u32);
3624 bone_name_to_index.insert(bytes.clone(), index);
3625 let decoded = bone_import.bone_names[i].as_bytes();
3626 if decoded != bytes.as_slice() && !decoded.is_empty() {
3627 bone_name_to_index.insert(decoded.to_vec(), index);
3628 }
3629 }
3630
3631 let mut morph_name_to_index = HashMap::with_capacity(morph_names.name_bytes.len() * 2);
3632 for (i, bytes) in morph_names.name_bytes.iter().enumerate() {
3633 let index = MorphIndex(i as u32);
3634 morph_name_to_index.insert(bytes.clone(), index);
3635 let decoded = morph_names.names[i].as_bytes();
3636 if decoded != bytes.as_slice() && !decoded.is_empty() {
3637 morph_name_to_index.insert(decoded.to_vec(), index);
3638 }
3639 }
3640
3641 let mut ik_solver_bone_name_to_index = HashMap::with_capacity(bone_import.ik_solvers.len() * 2);
3642 for (solver_idx, solver) in bone_import.ik_solvers.iter().enumerate() {
3643 let bone_idx = solver.ik_bone.as_usize();
3644 if bone_idx < bone_import.bone_name_bytes.len() {
3645 let bytes = &bone_import.bone_name_bytes[bone_idx];
3646 ik_solver_bone_name_to_index.insert(bytes.clone(), solver_idx);
3647 let decoded = bone_import.bone_names[bone_idx].as_bytes();
3648 if decoded != bytes.as_slice() && !decoded.is_empty() {
3649 ik_solver_bone_name_to_index.insert(decoded.to_vec(), solver_idx);
3650 }
3651 }
3652 }
3653
3654 let model = ModelArena::new_with_morphs(
3655 bone_import.bones,
3656 bone_import.ik_solvers,
3657 bone_import.append_transforms,
3658 morph_init,
3659 )
3660 .map_err(ImportError::ModelBuildFailed)?;
3661
3662 Ok(PmxRuntimeImport {
3663 model,
3664 bone_names: bone_import.bone_names,
3665 bone_name_to_index,
3666 morph_name_to_index,
3667 ik_solver_bone_name_to_index,
3668 })
3669}
3670
3671trait AppendTransformInitExt {
3672 fn with_rotation_if(self, on: bool) -> Self;
3673 fn with_translation_if(self, on: bool) -> Self;
3674 fn with_local_if(self, on: bool) -> Self;
3675}
3676
3677impl AppendTransformInitExt for AppendTransformInit {
3678 fn with_rotation_if(mut self, on: bool) -> Self {
3679 if on {
3680 self.affect_rotation = true;
3681 }
3682 self
3683 }
3684
3685 fn with_translation_if(mut self, on: bool) -> Self {
3686 if on {
3687 self.affect_translation = true;
3688 }
3689 self
3690 }
3691
3692 fn with_local_if(mut self, on: bool) -> Self {
3693 if on {
3694 self.local = true;
3695 }
3696 self
3697 }
3698}
3699
3700#[cfg(test)]
3701mod tests {
3702 use super::*;
3703 use glam::Vec3;
3704 use mmd_anim_runtime::RuntimeInstance;
3705 use std::sync::Arc;
3706
3707 fn build_small_pmx_header_bytes(bone_index_size: u8, encoding: TextEncoding) -> Vec<u8> {
3708 let mut buf = Vec::new();
3709 buf.extend_from_slice(b"PMX ");
3710 buf.extend_from_slice(&2.0f32.to_le_bytes());
3711 buf.push(8);
3712 buf.push(encoding as u8);
3713 buf.push(0);
3714 buf.push(4);
3715 buf.push(1);
3716 buf.push(1);
3717 buf.push(bone_index_size);
3718 buf.push(1);
3719 buf.push(1);
3720 buf
3721 }
3722
3723 fn build_empty_model_info(_encoding: TextEncoding) -> Vec<u8> {
3724 let mut buf = Vec::new();
3725 for _ in 0..4 {
3726 buf.extend_from_slice(&0i32.to_le_bytes());
3727 }
3728 buf
3729 }
3730
3731 fn build_empty_vertex_section() -> Vec<u8> {
3732 vec![0u8, 0, 0, 0]
3733 }
3734
3735 fn build_empty_face_section() -> Vec<u8> {
3736 vec![0u8, 0, 0, 0]
3737 }
3738
3739 fn build_empty_texture_section() -> Vec<u8> {
3740 vec![0u8, 0, 0, 0]
3741 }
3742
3743 fn build_empty_material_section() -> Vec<u8> {
3744 vec![0u8, 0, 0, 0]
3745 }
3746
3747 fn build_bone_name_bytes(name: &str) -> Vec<u8> {
3748 let mut buf = Vec::new();
3749 let name_bytes = name.as_bytes();
3750 buf.extend_from_slice(&(name_bytes.len() as i32).to_le_bytes());
3751 buf.extend_from_slice(name_bytes);
3752 buf
3753 }
3754
3755 fn build_bone_section_header(count: u32) -> Vec<u8> {
3756 let mut buf = Vec::new();
3757 buf.extend_from_slice(&count.to_le_bytes());
3758 buf
3759 }
3760
3761 fn build_morph_name_bytes(name: &str) -> Vec<u8> {
3762 let mut buf = Vec::new();
3763 let name_bytes = name.as_bytes();
3764 buf.extend_from_slice(&(name_bytes.len() as i32).to_le_bytes());
3765 buf.extend_from_slice(name_bytes);
3766 buf
3767 }
3768
3769 fn empty_pmx_flags() -> PmxParsedMaterialFlags {
3770 PmxParsedMaterialFlags {
3771 double_sided: false,
3772 ground_shadow: false,
3773 self_shadow_map: false,
3774 self_shadow: false,
3775 edge: false,
3776 vertex_color: false,
3777 point_draw: false,
3778 line_draw: false,
3779 }
3780 }
3781
3782 fn basic_bone_flags(indexed_tail: bool) -> PmxParsedBoneFlags {
3783 PmxParsedBoneFlags {
3784 indexed_tail,
3785 rotatable: true,
3786 translatable: false,
3787 visible: true,
3788 enabled: true,
3789 ik: false,
3790 append_local: false,
3791 append_rotate: false,
3792 append_translate: false,
3793 fixed_axis: false,
3794 local_axis: false,
3795 transform_after_physics: false,
3796 external_parent_transform: false,
3797 }
3798 }
3799
3800 fn parsed_pmx_fixture() -> PmxParsedModel {
3801 PmxParsedModel {
3802 metadata: PmxParsedMetadata {
3803 format: "pmx".to_owned(),
3804 version: 2.0,
3805 encoding: "utf-8".to_owned(),
3806 name: "model".to_owned(),
3807 english_name: "model-en".to_owned(),
3808 comment: "comment".to_owned(),
3809 english_comment: "comment-en".to_owned(),
3810 counts: PmxParsedCounts {
3811 vertices: 1,
3812 faces: 1,
3813 materials: 1,
3814 bones: 1,
3815 morphs: 1,
3816 display_frames: 1,
3817 rigid_bodies: 1,
3818 joints: 1,
3819 soft_bodies: 0,
3820 },
3821 index_sizes: PmxParsedIndexSizes {
3822 vertex: 4,
3823 texture: 1,
3824 material: 1,
3825 bone: 2,
3826 morph: 1,
3827 rigid_body: 1,
3828 },
3829 additional_uv_count: 1,
3830 },
3831 geometry: PmxParsedGeometry {
3832 positions: vec![1.0, 2.0, 3.0],
3833 normals: vec![0.0, 1.0, 0.0],
3834 uvs: vec![0.25, 0.75],
3835 additional_uvs: vec![vec![1.0, 2.0, 3.0, 4.0]],
3836 indices: vec![0, 0, 0],
3837 skin_indices: vec![0, 0, 0, 0],
3838 skin_weights: vec![1.0, 0.0, 0.0, 0.0],
3839 edge_scale: vec![1.25],
3840 material_groups: vec![PmxParsedMaterialGroup {
3841 start: 0,
3842 count: 3,
3843 material_index: 0,
3844 }],
3845 },
3846 materials: vec![PmxParsedMaterial {
3847 name: "mat".to_owned(),
3848 english_name: "mat-en".to_owned(),
3849 texture_path: "tex.png".to_owned(),
3850 sphere_texture_path: "sphere.spa".to_owned(),
3851 sphere_mode: "add".to_owned(),
3852 toon_texture_path: "toon.bmp".to_owned(),
3853 shared_toon_index: None,
3854 diffuse: [0.1, 0.2, 0.3, 0.4],
3855 specular: [0.5, 0.6, 0.7],
3856 specular_power: 8.0,
3857 ambient: [0.8, 0.9, 1.0],
3858 edge_color: [0.0, 0.1, 0.2, 0.3],
3859 edge_size: 1.5,
3860 flags: PmxParsedMaterialFlags {
3861 double_sided: true,
3862 edge: true,
3863 ..empty_pmx_flags()
3864 },
3865 face_count: 1,
3866 }],
3867 skeleton: PmxParsedSkeleton {
3868 bones: vec![PmxParsedBone {
3869 name: "Root".to_owned(),
3870 english_name: "RootEn".to_owned(),
3871 parent_index: -1,
3872 layer: 0,
3873 position: [0.0, 1.0, 2.0],
3874 tail_index: -1,
3875 tail_position: Some([0.0, 2.0, 0.0]),
3876 flags: basic_bone_flags(false),
3877 append_transform: None,
3878 fixed_axis: None,
3879 local_axis: None,
3880 external_parent_key: None,
3881 ik: None,
3882 }],
3883 },
3884 morphs: vec![PmxParsedMorph {
3885 name: "Smile".to_owned(),
3886 english_name: "SmileEn".to_owned(),
3887 kind: "vertex".to_owned(),
3888 vertex_offsets: vec![PmxParsedVertexMorphOffset {
3889 vertex_index: 0,
3890 position: [0.1, 0.2, 0.3],
3891 }],
3892 group_offsets: Vec::new(),
3893 bone_offsets: Vec::new(),
3894 uv_offsets: Vec::new(),
3895 additional_uv_offsets: Vec::new(),
3896 material_offsets: Vec::new(),
3897 flip_offsets: Vec::new(),
3898 impulse_offsets: Vec::new(),
3899 }],
3900 display_frames: vec![PmxParsedDisplayFrame {
3901 name: "RootFrame".to_owned(),
3902 english_name: "RootFrameEn".to_owned(),
3903 special: true,
3904 frames: vec![
3905 PmxParsedDisplayFrameElement {
3906 kind: "bone".to_owned(),
3907 index: 0,
3908 },
3909 PmxParsedDisplayFrameElement {
3910 kind: "morph".to_owned(),
3911 index: 0,
3912 },
3913 ],
3914 }],
3915 rigid_bodies: vec![PmxParsedRigidBody {
3916 name: "body".to_owned(),
3917 english_name: "body-en".to_owned(),
3918 bone_index: 0,
3919 group: 1,
3920 mask: 2,
3921 shape: "box".to_owned(),
3922 size: [1.0, 2.0, 3.0],
3923 position: [4.0, 5.0, 6.0],
3924 rotation: [0.1, 0.2, 0.3],
3925 mass: 10.0,
3926 linear_damping: 0.4,
3927 angular_damping: 0.5,
3928 restitution: 0.6,
3929 friction: 0.7,
3930 mode: "dynamicBone".to_owned(),
3931 }],
3932 joints: vec![PmxParsedJoint {
3933 name: "joint".to_owned(),
3934 english_name: "joint-en".to_owned(),
3935 kind: "generic6dofSpring".to_owned(),
3936 rigid_body_index_a: 0,
3937 rigid_body_index_b: -1,
3938 position: [1.0, 1.1, 1.2],
3939 rotation: [2.0, 2.1, 2.2],
3940 translation_lower_limit: [-1.0, -1.1, -1.2],
3941 translation_upper_limit: [1.0, 1.1, 1.2],
3942 rotation_lower_limit: [-0.1, -0.2, -0.3],
3943 rotation_upper_limit: [0.1, 0.2, 0.3],
3944 spring_translation_factor: [3.0, 3.1, 3.2],
3945 spring_rotation_factor: [4.0, 4.1, 4.2],
3946 }],
3947 soft_bodies: Vec::new(),
3948 diagnostics: Vec::new(),
3949 }
3950 }
3951
3952 fn assert_pmx_roundtrip_eq(left: &PmxParsedModel, right: &PmxParsedModel) {
3953 assert_eq!(
3954 serde_json::to_value(left).unwrap(),
3955 serde_json::to_value(right).unwrap()
3956 );
3957 }
3958
3959 fn json_keys(value: &serde_json::Value) -> Vec<String> {
3960 let mut keys = value
3961 .as_object()
3962 .unwrap()
3963 .keys()
3964 .map(ToOwned::to_owned)
3965 .collect::<Vec<_>>();
3966 keys.sort();
3967 keys
3968 }
3969
3970 #[test]
3971 fn pmx_model_json_top_level_schema_is_stable() {
3972 let parsed = parsed_pmx_fixture();
3973 let keys = json_keys(&serde_json::to_value(&parsed).unwrap());
3974
3975 assert_eq!(
3976 keys,
3977 vec![
3978 "diagnostics",
3979 "displayFrames",
3980 "geometry",
3981 "joints",
3982 "materials",
3983 "metadata",
3984 "morphs",
3985 "rigidBodies",
3986 "skeleton",
3987 "softBodies",
3988 ]
3989 );
3990 }
3991
3992 #[test]
3993 fn parses_pmx_header() {
3994 let header_bytes = build_small_pmx_header_bytes(4, TextEncoding::Utf8);
3995 let (header, pos) = read_header(&header_bytes).unwrap();
3996
3997 assert_eq!(header.version, 2.0);
3998 assert_eq!(header.encoding, TextEncoding::Utf8);
3999 assert_eq!(header.bone_index_size, 4);
4000 assert_eq!(header.vertex_index_size, 4);
4001 assert!(pos > 0);
4002 }
4003
4004 #[test]
4005 fn pmx_bone_flag_values_match_reference_layout() {
4006 assert_eq!(BONE_FLAG_TAIL_INDEX, 0x0001);
4007 assert_eq!(BONE_FLAG_IK, 0x0020);
4008 assert_eq!(BONE_FLAG_LOCAL_APPEND, 0x0080);
4009 assert_eq!(BONE_FLAG_APPEND_ROTATE, 0x0100);
4010 assert_eq!(BONE_FLAG_APPEND_TRANSLATE, 0x0200);
4011 assert_eq!(BONE_FLAG_FIXED_AXIS, 0x0400);
4012 assert_eq!(BONE_FLAG_LOCAL_AXIS, 0x0800);
4013 assert_eq!(BONE_FLAG_EXTERNAL_PARENT, 0x2000);
4014 }
4015
4016 #[test]
4017 fn rejects_bad_magic() {
4018 let mut header_bytes = build_small_pmx_header_bytes(4, TextEncoding::Utf8);
4019 header_bytes[0] = 0xFF;
4020 assert_eq!(
4021 read_header(&header_bytes).unwrap_err(),
4022 ImportError::InvalidPmxMagic
4023 );
4024 }
4025
4026 #[test]
4027 fn reads_single_bone_no_parent() {
4028 let mut buf = Vec::new();
4029 buf.extend_from_slice(&build_small_pmx_header_bytes(4, TextEncoding::Utf8));
4030 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4031 buf.extend_from_slice(&build_empty_vertex_section());
4032 buf.extend_from_slice(&build_empty_face_section());
4033 buf.extend_from_slice(&build_empty_texture_section());
4034 buf.extend_from_slice(&build_empty_material_section());
4035 buf.extend_from_slice(&build_bone_section_header(1));
4036
4037 buf.extend_from_slice(&build_bone_name_bytes("Root"));
4038 buf.extend_from_slice(&build_bone_name_bytes(""));
4039 buf.extend_from_slice(&0.0f32.to_le_bytes());
4040 buf.extend_from_slice(&1.0f32.to_le_bytes());
4041 buf.extend_from_slice(&2.0f32.to_le_bytes());
4042 buf.extend_from_slice(&(-1i32).to_le_bytes());
4043 buf.extend_from_slice(&0i32.to_le_bytes());
4044 buf.extend_from_slice(&0u16.to_le_bytes());
4045 buf.extend_from_slice(&0.0f32.to_le_bytes());
4046 buf.extend_from_slice(&0.0f32.to_le_bytes());
4047 buf.extend_from_slice(&0.0f32.to_le_bytes());
4048
4049 let result = import_pmx_model(&buf).unwrap();
4050 assert_eq!(result.bones.len(), 1);
4051 assert_eq!(result.bone_names.len(), 1);
4052 assert_eq!(result.bone_names[0], "Root");
4053 assert_eq!(&result.bone_name_bytes[0][..], b"Root");
4054 assert_eq!(result.bones[0].parent, None);
4055 assert!((result.bones[0].rest_position.x - 0.0).abs() < 0.001);
4056 assert!((result.bones[0].rest_position.y - 1.0).abs() < 0.001);
4057 assert!((result.bones[0].rest_position.z - 2.0).abs() < 0.001);
4058 assert!(result.ik_solvers.is_empty());
4059 assert!(result.append_transforms.is_empty());
4060 }
4061
4062 #[test]
4063 fn reads_bone_with_parent() {
4064 let mut buf = Vec::new();
4065 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4066 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4067 buf.extend_from_slice(&build_empty_vertex_section());
4068 buf.extend_from_slice(&build_empty_face_section());
4069 buf.extend_from_slice(&build_empty_texture_section());
4070 buf.extend_from_slice(&build_empty_material_section());
4071 buf.extend_from_slice(&build_bone_section_header(2));
4072
4073 buf.extend_from_slice(&build_bone_name_bytes("Root"));
4074 buf.extend_from_slice(&build_bone_name_bytes(""));
4075 buf.extend_from_slice(&0.0f32.to_le_bytes());
4076 buf.extend_from_slice(&0.0f32.to_le_bytes());
4077 buf.extend_from_slice(&0.0f32.to_le_bytes());
4078 buf.extend_from_slice(&(-1i16).to_le_bytes());
4079 buf.extend_from_slice(&0i32.to_le_bytes());
4080 buf.extend_from_slice(&0u16.to_le_bytes());
4081 buf.extend_from_slice(&0.0f32.to_le_bytes());
4082 buf.extend_from_slice(&0.0f32.to_le_bytes());
4083 buf.extend_from_slice(&0.0f32.to_le_bytes());
4084
4085 buf.extend_from_slice(&build_bone_name_bytes("Child"));
4086 buf.extend_from_slice(&build_bone_name_bytes(""));
4087 buf.extend_from_slice(&1.0f32.to_le_bytes());
4088 buf.extend_from_slice(&0.0f32.to_le_bytes());
4089 buf.extend_from_slice(&0.0f32.to_le_bytes());
4090 buf.extend_from_slice(&0i16.to_le_bytes());
4091 buf.extend_from_slice(&0i32.to_le_bytes());
4092 buf.extend_from_slice(&0u16.to_le_bytes());
4093 buf.extend_from_slice(&0.0f32.to_le_bytes());
4094 buf.extend_from_slice(&0.0f32.to_le_bytes());
4095 buf.extend_from_slice(&0.0f32.to_le_bytes());
4096
4097 let result = import_pmx_model(&buf).unwrap();
4098 assert_eq!(result.bones.len(), 2);
4099 assert_eq!(result.bone_names[0], "Root");
4100 assert_eq!(result.bone_names[1], "Child");
4101 assert_eq!(result.bones[0].parent, None);
4102 assert_eq!(result.bones[1].parent, Some(BoneIndex(0)));
4103 }
4104
4105 #[test]
4106 fn reads_bone_with_append_transform() {
4107 let mut buf = Vec::new();
4108 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4109 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4110 buf.extend_from_slice(&build_empty_vertex_section());
4111 buf.extend_from_slice(&build_empty_face_section());
4112 buf.extend_from_slice(&build_empty_texture_section());
4113 buf.extend_from_slice(&build_empty_material_section());
4114 buf.extend_from_slice(&build_bone_section_header(2));
4115
4116 buf.extend_from_slice(&build_bone_name_bytes("Root"));
4117 buf.extend_from_slice(&build_bone_name_bytes(""));
4118 buf.extend_from_slice(&0.0f32.to_le_bytes());
4119 buf.extend_from_slice(&0.0f32.to_le_bytes());
4120 buf.extend_from_slice(&0.0f32.to_le_bytes());
4121 buf.extend_from_slice(&(-1i16).to_le_bytes());
4122 buf.extend_from_slice(&0i32.to_le_bytes());
4123 buf.extend_from_slice(&0u16.to_le_bytes());
4124 buf.extend_from_slice(&0.0f32.to_le_bytes());
4125 buf.extend_from_slice(&0.0f32.to_le_bytes());
4126 buf.extend_from_slice(&0.0f32.to_le_bytes());
4127
4128 buf.extend_from_slice(&build_bone_name_bytes("AppendBone"));
4129 buf.extend_from_slice(&build_bone_name_bytes(""));
4130 buf.extend_from_slice(&1.0f32.to_le_bytes());
4131 buf.extend_from_slice(&0.0f32.to_le_bytes());
4132 buf.extend_from_slice(&0.0f32.to_le_bytes());
4133 buf.extend_from_slice(&0i16.to_le_bytes());
4134 buf.extend_from_slice(&0i32.to_le_bytes());
4135 buf.extend_from_slice(
4136 &(BONE_FLAG_LOCAL_APPEND | BONE_FLAG_APPEND_ROTATE | BONE_FLAG_APPEND_TRANSLATE)
4137 .to_le_bytes(),
4138 );
4139 buf.extend_from_slice(&0.0f32.to_le_bytes());
4140 buf.extend_from_slice(&0.0f32.to_le_bytes());
4141 buf.extend_from_slice(&0.0f32.to_le_bytes());
4142 buf.extend_from_slice(&0i16.to_le_bytes());
4143 buf.extend_from_slice(&0.5f32.to_le_bytes());
4144
4145 let result = import_pmx_model(&buf).unwrap();
4146 assert_eq!(result.bones.len(), 2);
4147 assert_eq!(result.append_transforms.len(), 1);
4148 assert_eq!(result.append_transforms[0].target_bone, BoneIndex(1));
4149 assert_eq!(result.append_transforms[0].source_bone, BoneIndex(0));
4150 assert!((result.append_transforms[0].ratio - 0.5).abs() < 0.001);
4151 assert!(result.append_transforms[0].affect_rotation);
4152 assert!(result.append_transforms[0].affect_translation);
4153 assert!(result.append_transforms[0].local);
4154 }
4155
4156 #[test]
4157 fn reads_ik_bone_with_links_and_angle_limits() {
4158 let mut buf = Vec::new();
4159 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4160 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4161 buf.extend_from_slice(&build_empty_vertex_section());
4162 buf.extend_from_slice(&build_empty_face_section());
4163 buf.extend_from_slice(&build_empty_texture_section());
4164 buf.extend_from_slice(&build_empty_material_section());
4165 buf.extend_from_slice(&build_bone_section_header(3));
4166
4167 buf.extend_from_slice(&build_bone_name_bytes("Root"));
4168 buf.extend_from_slice(&build_bone_name_bytes(""));
4169 buf.extend_from_slice(&0.0f32.to_le_bytes());
4170 buf.extend_from_slice(&0.0f32.to_le_bytes());
4171 buf.extend_from_slice(&0.0f32.to_le_bytes());
4172 buf.extend_from_slice(&(-1i16).to_le_bytes());
4173 buf.extend_from_slice(&0i32.to_le_bytes());
4174 buf.extend_from_slice(&0u16.to_le_bytes());
4175 buf.extend_from_slice(&0.0f32.to_le_bytes());
4176 buf.extend_from_slice(&0.0f32.to_le_bytes());
4177 buf.extend_from_slice(&0.0f32.to_le_bytes());
4178
4179 buf.extend_from_slice(&build_bone_name_bytes("IKLink"));
4180 buf.extend_from_slice(&build_bone_name_bytes(""));
4181 buf.extend_from_slice(&2.0f32.to_le_bytes());
4182 buf.extend_from_slice(&0.0f32.to_le_bytes());
4183 buf.extend_from_slice(&0.0f32.to_le_bytes());
4184 buf.extend_from_slice(&0i16.to_le_bytes());
4185 buf.extend_from_slice(&0i32.to_le_bytes());
4186 buf.extend_from_slice(&0u16.to_le_bytes());
4187 buf.extend_from_slice(&0.0f32.to_le_bytes());
4188 buf.extend_from_slice(&0.0f32.to_le_bytes());
4189 buf.extend_from_slice(&0.0f32.to_le_bytes());
4190
4191 buf.extend_from_slice(&build_bone_name_bytes("IKBone"));
4192 buf.extend_from_slice(&build_bone_name_bytes(""));
4193 buf.extend_from_slice(&4.0f32.to_le_bytes());
4194 buf.extend_from_slice(&0.0f32.to_le_bytes());
4195 buf.extend_from_slice(&0.0f32.to_le_bytes());
4196 buf.extend_from_slice(&0i16.to_le_bytes());
4197 buf.extend_from_slice(&0i32.to_le_bytes());
4198 buf.extend_from_slice(&(BONE_FLAG_TAIL_INDEX | BONE_FLAG_IK).to_le_bytes());
4199 buf.extend_from_slice(&1i16.to_le_bytes());
4200 buf.extend_from_slice(&1i16.to_le_bytes());
4201 buf.extend_from_slice(&10i32.to_le_bytes());
4202 buf.extend_from_slice(&0.5f32.to_le_bytes());
4203 buf.extend_from_slice(&1i32.to_le_bytes());
4204 buf.extend_from_slice(&1i16.to_le_bytes());
4205 buf.push(1u8);
4206 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4207 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4208 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4209 buf.extend_from_slice(&1.0f32.to_le_bytes());
4210 buf.extend_from_slice(&1.0f32.to_le_bytes());
4211 buf.extend_from_slice(&1.0f32.to_le_bytes());
4212
4213 let result = import_pmx_model(&buf).unwrap();
4214 assert_eq!(result.bones.len(), 3);
4215 assert_eq!(result.ik_solvers.len(), 1);
4216 assert_eq!(result.ik_solvers[0].ik_bone, BoneIndex(2));
4217 assert_eq!(result.ik_solvers[0].target_bone, BoneIndex(1));
4218 assert_eq!(result.ik_solvers[0].links.len(), 1);
4219 assert_eq!(result.ik_solvers[0].links[0].bone, BoneIndex(1));
4220 assert!(result.ik_solvers[0].links[0].angle_limit.is_some());
4221 assert_eq!(result.ik_solvers[0].iteration_count, 10);
4222 assert!((result.ik_solvers[0].limit_angle - 0.5).abs() < 0.001);
4223 }
4224
4225 #[test]
4226 fn handles_utf16le_encoding() {
4227 let header_bytes = build_small_pmx_header_bytes(4, TextEncoding::Utf16Le);
4228 let (header, _) = read_header(&header_bytes).unwrap();
4229 assert_eq!(header.encoding, TextEncoding::Utf16Le);
4230 }
4231
4232 #[test]
4233 fn decodes_utf16le_surrogate_pairs() {
4234 let bytes = [0x3D, 0xD8, 0x00, 0xDE, 0x00, 0x00];
4235 assert_eq!(decode_utf16le_lossy(&bytes), "\u{1F600}");
4236 }
4237
4238 #[test]
4239 fn reports_eof_on_truncated_data() {
4240 let buf = vec![0x50, 0x4D, 0x58]; assert!(matches!(
4242 read_header(&buf).unwrap_err(),
4243 ImportError::UnexpectedEof(_)
4244 ));
4245 }
4246
4247 #[test]
4248 fn bone_names_retained_and_mapped_to_indices() {
4249 let mut buf = Vec::new();
4250 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4251 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4252 buf.extend_from_slice(&build_empty_vertex_section());
4253 buf.extend_from_slice(&build_empty_face_section());
4254 buf.extend_from_slice(&build_empty_texture_section());
4255 buf.extend_from_slice(&build_empty_material_section());
4256 buf.extend_from_slice(&build_bone_section_header(2));
4257
4258 buf.extend_from_slice(&build_bone_name_bytes("Hips"));
4259 buf.extend_from_slice(&build_bone_name_bytes(""));
4260 buf.extend_from_slice(&0.0f32.to_le_bytes());
4261 buf.extend_from_slice(&0.0f32.to_le_bytes());
4262 buf.extend_from_slice(&0.0f32.to_le_bytes());
4263 buf.extend_from_slice(&(-1i16).to_le_bytes());
4264 buf.extend_from_slice(&0i32.to_le_bytes());
4265 buf.extend_from_slice(&0u16.to_le_bytes());
4266 buf.extend_from_slice(&0.0f32.to_le_bytes());
4267 buf.extend_from_slice(&0.0f32.to_le_bytes());
4268 buf.extend_from_slice(&0.0f32.to_le_bytes());
4269
4270 buf.extend_from_slice(&build_bone_name_bytes("Spine"));
4271 buf.extend_from_slice(&build_bone_name_bytes(""));
4272 buf.extend_from_slice(&1.0f32.to_le_bytes());
4273 buf.extend_from_slice(&0.0f32.to_le_bytes());
4274 buf.extend_from_slice(&0.0f32.to_le_bytes());
4275 buf.extend_from_slice(&0i16.to_le_bytes());
4276 buf.extend_from_slice(&0i32.to_le_bytes());
4277 buf.extend_from_slice(&0u16.to_le_bytes());
4278 buf.extend_from_slice(&0.0f32.to_le_bytes());
4279 buf.extend_from_slice(&0.0f32.to_le_bytes());
4280 buf.extend_from_slice(&0.0f32.to_le_bytes());
4281
4282 let result = import_pmx_model(&buf).unwrap();
4283 assert_eq!(result.bone_name_bytes.len(), 2);
4284 assert_eq!(&result.bone_name_bytes[0][..], b"Hips");
4285 assert_eq!(&result.bone_name_bytes[1][..], b"Spine");
4286 assert_eq!(result.bone_names[0], "Hips");
4287 assert_eq!(result.bone_names[1], "Spine");
4288
4289 let bone_map: HashMap<Vec<u8>, BoneIndex> = result
4290 .bone_name_bytes
4291 .iter()
4292 .enumerate()
4293 .map(|(i, b)| (b.clone(), BoneIndex(i as u32)))
4294 .collect();
4295
4296 assert_eq!(bone_map.get(b"Hips".as_slice()), Some(&BoneIndex(0)));
4297 assert_eq!(bone_map.get(b"Spine".as_slice()), Some(&BoneIndex(1)));
4298 assert_eq!(bone_map.get(b"Unknown".as_slice()), None);
4299 }
4300
4301 #[test]
4302 fn ik_solver_name_mapped_to_solver_index() {
4303 let mut buf = Vec::new();
4304 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4305 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4306 buf.extend_from_slice(&build_empty_vertex_section());
4307 buf.extend_from_slice(&build_empty_face_section());
4308 buf.extend_from_slice(&build_empty_texture_section());
4309 buf.extend_from_slice(&build_empty_material_section());
4310 buf.extend_from_slice(&build_bone_section_header(3));
4311
4312 buf.extend_from_slice(&build_bone_name_bytes("Root"));
4313 buf.extend_from_slice(&build_bone_name_bytes(""));
4314 buf.extend_from_slice(&0.0f32.to_le_bytes());
4315 buf.extend_from_slice(&0.0f32.to_le_bytes());
4316 buf.extend_from_slice(&0.0f32.to_le_bytes());
4317 buf.extend_from_slice(&(-1i16).to_le_bytes());
4318 buf.extend_from_slice(&0i32.to_le_bytes());
4319 buf.extend_from_slice(&0u16.to_le_bytes());
4320 buf.extend_from_slice(&0.0f32.to_le_bytes());
4321 buf.extend_from_slice(&0.0f32.to_le_bytes());
4322 buf.extend_from_slice(&0.0f32.to_le_bytes());
4323
4324 buf.extend_from_slice(&build_bone_name_bytes("IKLink"));
4325 buf.extend_from_slice(&build_bone_name_bytes(""));
4326 buf.extend_from_slice(&2.0f32.to_le_bytes());
4327 buf.extend_from_slice(&0.0f32.to_le_bytes());
4328 buf.extend_from_slice(&0.0f32.to_le_bytes());
4329 buf.extend_from_slice(&0i16.to_le_bytes());
4330 buf.extend_from_slice(&0i32.to_le_bytes());
4331 buf.extend_from_slice(&0u16.to_le_bytes());
4332 buf.extend_from_slice(&0.0f32.to_le_bytes());
4333 buf.extend_from_slice(&0.0f32.to_le_bytes());
4334 buf.extend_from_slice(&0.0f32.to_le_bytes());
4335
4336 buf.extend_from_slice(&build_bone_name_bytes("LeftLegIK"));
4337 buf.extend_from_slice(&build_bone_name_bytes(""));
4338 buf.extend_from_slice(&4.0f32.to_le_bytes());
4339 buf.extend_from_slice(&0.0f32.to_le_bytes());
4340 buf.extend_from_slice(&0.0f32.to_le_bytes());
4341 buf.extend_from_slice(&0i16.to_le_bytes());
4342 buf.extend_from_slice(&0i32.to_le_bytes());
4343 buf.extend_from_slice(&(BONE_FLAG_TAIL_INDEX | BONE_FLAG_IK).to_le_bytes());
4344 buf.extend_from_slice(&1i16.to_le_bytes());
4345 buf.extend_from_slice(&1i16.to_le_bytes());
4346 buf.extend_from_slice(&10i32.to_le_bytes());
4347 buf.extend_from_slice(&0.5f32.to_le_bytes());
4348 buf.extend_from_slice(&1i32.to_le_bytes());
4349 buf.extend_from_slice(&1i16.to_le_bytes());
4350 buf.push(1u8);
4351 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4352 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4353 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4354 buf.extend_from_slice(&1.0f32.to_le_bytes());
4355 buf.extend_from_slice(&1.0f32.to_le_bytes());
4356 buf.extend_from_slice(&1.0f32.to_le_bytes());
4357
4358 let bone_import = import_pmx_model(&buf).unwrap();
4359 assert_eq!(bone_import.ik_solvers.len(), 1);
4360 assert_eq!(bone_import.ik_solvers[0].ik_bone, BoneIndex(2));
4361 assert_eq!(
4362 bone_import.bone_names[bone_import.ik_solvers[0].ik_bone.as_usize()],
4363 "LeftLegIK"
4364 );
4365
4366 let ik_name_map: HashMap<Vec<u8>, usize> = bone_import
4367 .ik_solvers
4368 .iter()
4369 .enumerate()
4370 .filter_map(|(solver_idx, solver)| {
4371 let bone_idx = solver.ik_bone.as_usize();
4372 if bone_idx < bone_import.bone_name_bytes.len() {
4373 Some((bone_import.bone_name_bytes[bone_idx].clone(), solver_idx))
4374 } else {
4375 None
4376 }
4377 })
4378 .collect();
4379
4380 assert_eq!(ik_name_map.get(b"LeftLegIK".as_slice()), Some(&0));
4381 }
4382
4383 #[test]
4384 fn reads_morph_names_without_payloads() {
4385 let (header, pos) =
4386 read_header(&build_small_pmx_header_bytes(4, TextEncoding::Utf8)).unwrap();
4387
4388 let mut buf = vec![0u8; pos];
4389 buf.extend_from_slice(&1i32.to_le_bytes());
4390 buf.extend_from_slice(&build_morph_name_bytes("Smile"));
4391 buf.extend_from_slice(&build_morph_name_bytes(""));
4392 buf.push(4u8);
4393 buf.push(0u8);
4394 buf.extend_from_slice(&0i32.to_le_bytes());
4395
4396 let (morphs, new_pos) = read_morph_names(&buf[..], &header, pos).unwrap();
4397 assert_eq!(morphs.name_bytes.len(), 1);
4398 assert_eq!(&morphs.name_bytes[0][..], b"Smile");
4399 assert_eq!(morphs.names[0], "Smile");
4400 assert!(new_pos > pos);
4401 }
4402
4403 #[test]
4404 fn reads_morph_type_zero_with_vertex_group_offsets() {
4405 let (header, pos) =
4406 read_header(&build_small_pmx_header_bytes(4, TextEncoding::Utf8)).unwrap();
4407 assert_eq!(header.vertex_index_size, 4);
4408 assert_eq!(header.morph_index_size, 1);
4409
4410 let mut buf = vec![0u8; pos];
4411 buf.extend_from_slice(&1i32.to_le_bytes());
4412 buf.extend_from_slice(&build_morph_name_bytes("Blink"));
4413 buf.extend_from_slice(&build_morph_name_bytes(""));
4414 buf.push(0u8);
4415 buf.push(0u8);
4416 buf.extend_from_slice(&2i32.to_le_bytes());
4417 buf.push(0u8);
4418 buf.extend_from_slice(&[0u8; 4]);
4419 buf.push(1u8);
4420 buf.extend_from_slice(&[0u8; 4]);
4421
4422 let (morphs, new_pos) = read_morph_names(&buf[..], &header, pos).unwrap();
4423 assert_eq!(morphs.name_bytes.len(), 1);
4424 assert_eq!(&morphs.name_bytes[0][..], b"Blink");
4425 assert_eq!(morphs.names[0], "Blink");
4426 assert_eq!(new_pos, buf.len());
4427 }
4428
4429 #[test]
4430 fn runtime_import_builds_model_arena_with_name_lookup() {
4431 let mut buf = Vec::new();
4432 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4433 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4434 buf.extend_from_slice(&build_empty_vertex_section());
4435 buf.extend_from_slice(&build_empty_face_section());
4436 buf.extend_from_slice(&build_empty_texture_section());
4437 buf.extend_from_slice(&build_empty_material_section());
4438 buf.extend_from_slice(&build_bone_section_header(2));
4439
4440 buf.extend_from_slice(&build_bone_name_bytes("Hips"));
4441 buf.extend_from_slice(&build_bone_name_bytes(""));
4442 buf.extend_from_slice(&0.0f32.to_le_bytes());
4443 buf.extend_from_slice(&0.0f32.to_le_bytes());
4444 buf.extend_from_slice(&0.0f32.to_le_bytes());
4445 buf.extend_from_slice(&(-1i16).to_le_bytes());
4446 buf.extend_from_slice(&0i32.to_le_bytes());
4447 buf.extend_from_slice(&0u16.to_le_bytes());
4448 buf.extend_from_slice(&0.0f32.to_le_bytes());
4449 buf.extend_from_slice(&0.0f32.to_le_bytes());
4450 buf.extend_from_slice(&0.0f32.to_le_bytes());
4451
4452 buf.extend_from_slice(&build_bone_name_bytes("Spine"));
4453 buf.extend_from_slice(&build_bone_name_bytes(""));
4454 buf.extend_from_slice(&0.0f32.to_le_bytes());
4455 buf.extend_from_slice(&1.0f32.to_le_bytes());
4456 buf.extend_from_slice(&0.0f32.to_le_bytes());
4457 buf.extend_from_slice(&0i16.to_le_bytes());
4458 buf.extend_from_slice(&0i32.to_le_bytes());
4459 buf.extend_from_slice(&0u16.to_le_bytes());
4460 buf.extend_from_slice(&0.0f32.to_le_bytes());
4461 buf.extend_from_slice(&0.0f32.to_le_bytes());
4462 buf.extend_from_slice(&0.0f32.to_le_bytes());
4463 buf.extend_from_slice(&0i32.to_le_bytes());
4464
4465 let rt = import_pmx_runtime(&buf).unwrap();
4466 assert_eq!(rt.model.bone_count(), 2);
4467 assert_eq!(rt.model.parent_index(BoneIndex(1)), Some(BoneIndex(0)));
4468 assert_eq!(
4469 rt.bone_name_to_index.get(b"Hips".as_slice()),
4470 Some(&BoneIndex(0))
4471 );
4472 assert_eq!(
4473 rt.bone_name_to_index.get(b"Spine".as_slice()),
4474 Some(&BoneIndex(1))
4475 );
4476 assert!(rt.morph_name_to_index.is_empty());
4477 assert!(rt.ik_solver_bone_name_to_index.is_empty());
4478 }
4479
4480 #[test]
4481 fn parse_pmx_model_exposes_three_loader_shaped_sections() {
4482 let mut buf = Vec::new();
4483 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4484 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4485 buf.extend_from_slice(&build_empty_vertex_section());
4486 buf.extend_from_slice(&build_empty_face_section());
4487 buf.extend_from_slice(&build_empty_texture_section());
4488 buf.extend_from_slice(&build_empty_material_section());
4489 buf.extend_from_slice(&build_bone_section_header(2));
4490
4491 buf.extend_from_slice(&build_bone_name_bytes("Root"));
4492 buf.extend_from_slice(&build_bone_name_bytes(""));
4493 buf.extend_from_slice(&0.0f32.to_le_bytes());
4494 buf.extend_from_slice(&0.0f32.to_le_bytes());
4495 buf.extend_from_slice(&0.0f32.to_le_bytes());
4496 buf.extend_from_slice(&(-1i16).to_le_bytes());
4497 buf.extend_from_slice(&0i32.to_le_bytes());
4498 buf.extend_from_slice(&0u16.to_le_bytes());
4499 buf.extend_from_slice(&0.0f32.to_le_bytes());
4500 buf.extend_from_slice(&0.0f32.to_le_bytes());
4501 buf.extend_from_slice(&0.0f32.to_le_bytes());
4502
4503 buf.extend_from_slice(&build_bone_name_bytes("Child"));
4504 buf.extend_from_slice(&build_bone_name_bytes(""));
4505 buf.extend_from_slice(&0.0f32.to_le_bytes());
4506 buf.extend_from_slice(&1.0f32.to_le_bytes());
4507 buf.extend_from_slice(&0.0f32.to_le_bytes());
4508 buf.extend_from_slice(&0i16.to_le_bytes());
4509 buf.extend_from_slice(&0i32.to_le_bytes());
4510 buf.extend_from_slice(&0u16.to_le_bytes());
4511 buf.extend_from_slice(&0.0f32.to_le_bytes());
4512 buf.extend_from_slice(&0.0f32.to_le_bytes());
4513 buf.extend_from_slice(&0.0f32.to_le_bytes());
4514
4515 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"));
4518 buf.extend_from_slice(&build_bone_name_bytes(""));
4519 buf.push(1);
4520 buf.extend_from_slice(&1i32.to_le_bytes());
4521 buf.push(0);
4522 buf.extend_from_slice(&1i16.to_le_bytes());
4523 buf.extend_from_slice(&0i32.to_le_bytes()); buf.extend_from_slice(&0i32.to_le_bytes()); let parsed = parse_pmx_model(&buf).unwrap();
4527 assert_eq!(parsed.metadata.format, "pmx");
4528 assert_eq!(parsed.metadata.counts.bones, 2);
4529 assert_eq!(parsed.skeleton.bones[0].name, "Root");
4530 assert_eq!(parsed.skeleton.bones[1].parent_index, 0);
4531 assert_eq!(parsed.display_frames.len(), 1);
4532 assert_eq!(parsed.display_frames[0].frames[0].kind, "bone");
4533 assert_eq!(parsed.display_frames[0].frames[0].index, 1);
4534 assert!(parsed.diagnostics.is_empty());
4535 }
4536
4537 #[test]
4538 fn exports_pmx_semantic_roundtrip() {
4539 let parsed = parsed_pmx_fixture();
4540 let exported = export_pmx_model(&parsed);
4541 let reparsed = parse_pmx_model(&exported).unwrap();
4542
4543 assert_pmx_roundtrip_eq(&parsed, &reparsed);
4544 }
4545
4546 #[test]
4547 fn exports_pmx_json_dto_semantic_roundtrip() {
4548 let parsed = parsed_pmx_fixture();
4549 let json = serde_json::to_string(&parsed).unwrap();
4550 let from_json: PmxParsedModel = serde_json::from_str(&json).unwrap();
4551 let exported = export_pmx_model(&from_json);
4552 let reparsed = parse_pmx_model(&exported).unwrap();
4553
4554 assert_pmx_roundtrip_eq(&parsed, &reparsed);
4555 }
4556
4557 #[test]
4558 fn builds_pmx_parts_with_material_descriptors() {
4559 let descriptor: PmxPartsDescriptor = serde_json::from_value(serde_json::json!({
4560 "name": "material-parts",
4561 "materials": [
4562 {
4563 "name": "red",
4564 "englishName": "red-en",
4565 "diffuse": [1.0, 0.0, 0.0, 1.0],
4566 "ambient": [0.5, 0.0, 0.0],
4567 "flags": { "edge": true },
4568 "faceCount": 1
4569 },
4570 {
4571 "name": "blue",
4572 "diffuse": [0.0, 0.0, 1.0, 1.0],
4573 "faceCount": 1
4574 }
4575 ],
4576 "bones": [
4577 { "name": "root", "englishName": "root-en", "tailIndex": 1 },
4578 { "name": "child", "parentIndex": 0, "position": [0.0, 1.0, 0.0], "rotatable": true }
4579 ],
4580 "morphs": [
4581 {
4582 "name": "raise",
4583 "englishName": "raise-en",
4584 "kind": "vertex",
4585 "vertexOffsets": [{ "vertexIndex": 0, "position": [0.0, 0.1, 0.0] }]
4586 },
4587 {
4588 "name": "combo",
4589 "kind": "group",
4590 "groupOffsets": [{ "morphIndex": 0, "weight": 0.5 }]
4591 }
4592 ],
4593 "displayFrames": [
4594 {
4595 "name": "Root",
4596 "englishName": "Root-en",
4597 "special": true,
4598 "frames": [
4599 { "kind": "bone", "index": 0 },
4600 { "kind": "bone", "index": 1 },
4601 { "kind": "morph", "index": 0 }
4602 ]
4603 }
4604 ],
4605 "rigidBodies": [
4606 {
4607 "name": "body",
4608 "englishName": "body-en",
4609 "boneIndex": 1,
4610 "group": 2,
4611 "mask": 3,
4612 "shape": "box",
4613 "size": [1.0, 2.0, 3.0],
4614 "position": [0.0, 1.0, 0.0],
4615 "rotation": [0.1, 0.2, 0.3],
4616 "mass": 2.0,
4617 "linearDamping": 0.4,
4618 "angularDamping": 0.5,
4619 "restitution": 0.6,
4620 "friction": 0.7,
4621 "mode": "dynamicBone"
4622 }
4623 ],
4624 "joints": [
4625 {
4626 "name": "joint",
4627 "type": "generic6dofSpring",
4628 "rigidBodyIndexA": 0,
4629 "rigidBodyIndexB": -1,
4630 "position": [0.0, 1.0, 0.0],
4631 "translationLowerLimit": [-1.0, -1.0, -1.0],
4632 "translationUpperLimit": [1.0, 1.0, 1.0],
4633 "springTranslationFactor": [0.1, 0.2, 0.3]
4634 }
4635 ],
4636 "indexSizes": {
4637 "vertex": 1,
4638 "material": 1,
4639 "texture": 1,
4640 "bone": 1,
4641 "morph": 1,
4642 "rigidBody": 1
4643 }
4644 }))
4645 .unwrap();
4646 let model = build_pmx_model_from_parts(PmxPartsInput {
4647 descriptor,
4648 positions_xyz: &[
4649 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
4653 ],
4654 normals_xyz: &[
4655 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
4659 ],
4660 uvs_xy: &[0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0],
4661 indices: &[0, 1, 2, 0, 2, 3],
4662 skin_indices: &[0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
4663 skin_weights: &[
4664 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,
4665 ],
4666 edge_scale: &[],
4667 })
4668 .unwrap();
4669 let exported = export_pmx_model(&model);
4670 let reparsed = parse_pmx_model(&exported).unwrap();
4671
4672 assert_eq!(reparsed.metadata.name, "material-parts");
4673 assert_eq!(reparsed.metadata.counts.materials, 2);
4674 assert_eq!(reparsed.materials[0].name, "red");
4675 assert_eq!(reparsed.materials[0].english_name, "red-en");
4676 assert_eq!(reparsed.materials[0].face_count, 1);
4677 assert!(reparsed.materials[0].flags.edge);
4678 assert_eq!(reparsed.materials[1].name, "blue");
4679 assert_eq!(reparsed.materials[1].face_count, 1);
4680 assert_eq!(reparsed.geometry.material_groups.len(), 2);
4681 assert_eq!(reparsed.geometry.material_groups[0].start, 0);
4682 assert_eq!(reparsed.geometry.material_groups[0].count, 3);
4683 assert_eq!(reparsed.geometry.material_groups[1].start, 3);
4684 assert_eq!(reparsed.geometry.material_groups[1].count, 3);
4685 assert_eq!(reparsed.metadata.counts.bones, 2);
4686 assert_eq!(reparsed.skeleton.bones[0].name, "root");
4687 assert_eq!(reparsed.skeleton.bones[0].english_name, "root-en");
4688 assert_eq!(reparsed.skeleton.bones[0].tail_index, 1);
4689 assert!(reparsed.skeleton.bones[0].flags.indexed_tail);
4690 assert_eq!(reparsed.skeleton.bones[1].name, "child");
4691 assert_eq!(reparsed.skeleton.bones[1].parent_index, 0);
4692 assert!(reparsed.skeleton.bones[1].flags.rotatable);
4693 assert_eq!(reparsed.geometry.skin_indices[1], 1);
4694 assert_eq!(reparsed.geometry.skin_weights[1], 0.25);
4695 assert_eq!(reparsed.metadata.counts.morphs, 2);
4696 assert_eq!(reparsed.morphs[0].name, "raise");
4697 assert_eq!(reparsed.morphs[0].english_name, "raise-en");
4698 assert_eq!(reparsed.morphs[0].kind, "vertex");
4699 assert_eq!(reparsed.morphs[0].vertex_offsets[0].vertex_index, 0);
4700 assert_eq!(
4701 reparsed.morphs[0].vertex_offsets[0].position,
4702 [0.0, 0.1, 0.0]
4703 );
4704 assert_eq!(reparsed.morphs[1].name, "combo");
4705 assert_eq!(reparsed.morphs[1].kind, "group");
4706 assert_eq!(reparsed.morphs[1].group_offsets[0].morph_index, 0);
4707 assert_eq!(reparsed.morphs[1].group_offsets[0].weight, 0.5);
4708 assert_eq!(reparsed.display_frames.len(), 1);
4709 assert_eq!(reparsed.display_frames[0].name, "Root");
4710 assert_eq!(reparsed.display_frames[0].english_name, "Root-en");
4711 assert!(reparsed.display_frames[0].special);
4712 assert_eq!(reparsed.display_frames[0].frames.len(), 3);
4713 assert_eq!(reparsed.display_frames[0].frames[1].kind, "bone");
4714 assert_eq!(reparsed.display_frames[0].frames[1].index, 1);
4715 assert_eq!(reparsed.display_frames[0].frames[2].kind, "morph");
4716 assert_eq!(reparsed.display_frames[0].frames[2].index, 0);
4717 assert_eq!(reparsed.rigid_bodies.len(), 1);
4718 assert_eq!(reparsed.rigid_bodies[0].name, "body");
4719 assert_eq!(reparsed.rigid_bodies[0].english_name, "body-en");
4720 assert_eq!(reparsed.rigid_bodies[0].bone_index, 1);
4721 assert_eq!(reparsed.rigid_bodies[0].group, 2);
4722 assert_eq!(reparsed.rigid_bodies[0].mask, 3);
4723 assert_eq!(reparsed.rigid_bodies[0].shape, "box");
4724 assert_eq!(reparsed.rigid_bodies[0].mode, "dynamicBone");
4725 assert_eq!(reparsed.joints.len(), 1);
4726 assert_eq!(reparsed.joints[0].name, "joint");
4727 assert_eq!(reparsed.joints[0].kind, "generic6dofSpring");
4728 assert_eq!(reparsed.joints[0].rigid_body_index_a, 0);
4729 assert_eq!(reparsed.joints[0].rigid_body_index_b, -1);
4730 }
4731
4732 #[test]
4733 fn rejects_pmx_parts_material_face_count_mismatch() {
4734 let descriptor: PmxPartsDescriptor = serde_json::from_value(serde_json::json!({
4735 "materials": [{ "name": "bad", "faceCount": 2 }]
4736 }))
4737 .unwrap();
4738 let error = build_pmx_model_from_parts(PmxPartsInput {
4739 descriptor,
4740 positions_xyz: &[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
4741 normals_xyz: &[0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0],
4742 uvs_xy: &[0.0, 0.0, 1.0, 0.0, 0.0, 1.0],
4743 indices: &[0, 1, 2],
4744 skin_indices: &[],
4745 skin_weights: &[],
4746 edge_scale: &[],
4747 })
4748 .unwrap_err();
4749
4750 assert!(error.contains("materials faceCount sum"));
4751 }
4752
4753 #[test]
4754 fn rejects_pmx_export_model_partial_skin_arrays() {
4755 let mut parsed = parsed_pmx_fixture();
4756 parsed.geometry.skin_weights.clear();
4757
4758 let error = validate_pmx_export_model(&parsed).unwrap_err();
4759
4760 assert!(error.contains("skinIndices and skinWeights"));
4761 }
4762
4763 #[test]
4766 fn exports_pmx_22_synthetic_roundtrip() {
4767 let mut parsed = parsed_pmx_fixture();
4768 parsed.metadata.version = 2.2;
4769
4770 let exported = export_pmx_model(&parsed);
4771 let reparsed = parse_pmx_model(&exported).unwrap();
4772
4773 assert_eq!(
4774 reparsed.metadata.version, 2.2,
4775 "PMX 2.2 version must survive the export-parse cycle"
4776 );
4777 assert_pmx_roundtrip_eq(&parsed, &reparsed);
4778 }
4779
4780 #[test]
4781 fn exports_pmx_soft_body_header_fields_with_diagnostic_for_pmx_21_and_22() {
4782 for version in [2.1, 2.2] {
4783 let mut parsed = parsed_pmx_fixture();
4784 parsed.metadata.version = version;
4785 parsed.soft_bodies = vec![PmxParsedSoftBody {
4786 name: "soft".to_owned(),
4787 english_name: "soft-en".to_owned(),
4788 kind: "rope".to_owned(),
4789 material_index: 0,
4790 collision_group: 1,
4791 collision_mask: 2,
4792 flags: 3,
4793 bending_constraints_distance: 4,
4794 cluster_count: 5,
4795 total_mass: 6.0,
4796 collision_margin: 0.7,
4797 }];
4798 let exported = export_pmx_model(&parsed);
4799 let reparsed = parse_pmx_model(&exported).unwrap();
4800
4801 assert_eq!(reparsed.metadata.version, version);
4802 assert_eq!(reparsed.metadata.counts.soft_bodies, 1);
4803 assert_eq!(reparsed.soft_bodies.len(), 1);
4804 assert_eq!(reparsed.soft_bodies[0].name, "soft");
4805 assert_eq!(reparsed.soft_bodies[0].english_name, "soft-en");
4806 assert_eq!(reparsed.soft_bodies[0].kind, "rope");
4807 assert_eq!(reparsed.soft_bodies[0].material_index, 0);
4808 assert_eq!(reparsed.soft_bodies[0].collision_group, 1);
4809 assert_eq!(reparsed.soft_bodies[0].collision_mask, 2);
4810 assert_eq!(reparsed.soft_bodies[0].flags, 3);
4811 assert_eq!(reparsed.soft_bodies[0].bending_constraints_distance, 4);
4812 assert_eq!(reparsed.soft_bodies[0].cluster_count, 5);
4813 assert_eq!(reparsed.soft_bodies[0].total_mass, 6.0);
4814 assert_eq!(reparsed.soft_bodies[0].collision_margin, 0.7);
4815 assert_eq!(reparsed.diagnostics.len(), 1);
4816 assert_eq!(reparsed.diagnostics[0].code, "PMX_SOFT_BODY_UNSUPPORTED");
4817 }
4818 }
4819
4820 #[test]
4821 fn exports_parsed_pmx_skeleton_display_roundtrip() {
4822 let mut buf = Vec::new();
4823 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4824 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4825 buf.extend_from_slice(&build_empty_vertex_section());
4826 buf.extend_from_slice(&build_empty_face_section());
4827 buf.extend_from_slice(&build_empty_texture_section());
4828 buf.extend_from_slice(&build_empty_material_section());
4829 buf.extend_from_slice(&build_bone_section_header(2));
4830
4831 buf.extend_from_slice(&build_bone_name_bytes("Root"));
4832 buf.extend_from_slice(&build_bone_name_bytes(""));
4833 buf.extend_from_slice(&0.0f32.to_le_bytes());
4834 buf.extend_from_slice(&0.0f32.to_le_bytes());
4835 buf.extend_from_slice(&0.0f32.to_le_bytes());
4836 buf.extend_from_slice(&(-1i16).to_le_bytes());
4837 buf.extend_from_slice(&0i32.to_le_bytes());
4838 buf.extend_from_slice(&0u16.to_le_bytes());
4839 buf.extend_from_slice(&0.0f32.to_le_bytes());
4840 buf.extend_from_slice(&0.0f32.to_le_bytes());
4841 buf.extend_from_slice(&0.0f32.to_le_bytes());
4842
4843 buf.extend_from_slice(&build_bone_name_bytes("Child"));
4844 buf.extend_from_slice(&build_bone_name_bytes(""));
4845 buf.extend_from_slice(&0.0f32.to_le_bytes());
4846 buf.extend_from_slice(&1.0f32.to_le_bytes());
4847 buf.extend_from_slice(&0.0f32.to_le_bytes());
4848 buf.extend_from_slice(&0i16.to_le_bytes());
4849 buf.extend_from_slice(&0i32.to_le_bytes());
4850 buf.extend_from_slice(&0u16.to_le_bytes());
4851 buf.extend_from_slice(&0.0f32.to_le_bytes());
4852 buf.extend_from_slice(&0.0f32.to_le_bytes());
4853 buf.extend_from_slice(&0.0f32.to_le_bytes());
4854
4855 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"));
4858 buf.extend_from_slice(&build_bone_name_bytes(""));
4859 buf.push(1);
4860 buf.extend_from_slice(&1i32.to_le_bytes());
4861 buf.push(0);
4862 buf.extend_from_slice(&1i16.to_le_bytes());
4863 buf.extend_from_slice(&0i32.to_le_bytes()); buf.extend_from_slice(&0i32.to_le_bytes()); let parsed = parse_pmx_model(&buf).unwrap();
4867 let exported = export_pmx_model(&parsed);
4868 let reparsed = parse_pmx_model(&exported).unwrap();
4869
4870 assert_pmx_roundtrip_eq(&parsed, &reparsed);
4871 }
4872
4873 #[test]
4874 fn runtime_import_ik_solver_name_lookup() {
4875 let mut buf = Vec::new();
4876 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4877 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4878 buf.extend_from_slice(&build_empty_vertex_section());
4879 buf.extend_from_slice(&build_empty_face_section());
4880 buf.extend_from_slice(&build_empty_texture_section());
4881 buf.extend_from_slice(&build_empty_material_section());
4882 buf.extend_from_slice(&build_bone_section_header(3));
4883
4884 buf.extend_from_slice(&build_bone_name_bytes("Root"));
4885 buf.extend_from_slice(&build_bone_name_bytes(""));
4886 buf.extend_from_slice(&0.0f32.to_le_bytes());
4887 buf.extend_from_slice(&0.0f32.to_le_bytes());
4888 buf.extend_from_slice(&0.0f32.to_le_bytes());
4889 buf.extend_from_slice(&(-1i16).to_le_bytes());
4890 buf.extend_from_slice(&0i32.to_le_bytes());
4891 buf.extend_from_slice(&0u16.to_le_bytes());
4892 buf.extend_from_slice(&0.0f32.to_le_bytes());
4893 buf.extend_from_slice(&0.0f32.to_le_bytes());
4894 buf.extend_from_slice(&0.0f32.to_le_bytes());
4895
4896 buf.extend_from_slice(&build_bone_name_bytes("IKLink"));
4897 buf.extend_from_slice(&build_bone_name_bytes(""));
4898 buf.extend_from_slice(&2.0f32.to_le_bytes());
4899 buf.extend_from_slice(&0.0f32.to_le_bytes());
4900 buf.extend_from_slice(&0.0f32.to_le_bytes());
4901 buf.extend_from_slice(&0i16.to_le_bytes());
4902 buf.extend_from_slice(&0i32.to_le_bytes());
4903 buf.extend_from_slice(&0u16.to_le_bytes());
4904 buf.extend_from_slice(&0.0f32.to_le_bytes());
4905 buf.extend_from_slice(&0.0f32.to_le_bytes());
4906 buf.extend_from_slice(&0.0f32.to_le_bytes());
4907
4908 buf.extend_from_slice(&build_bone_name_bytes("LeftLegIK"));
4909 buf.extend_from_slice(&build_bone_name_bytes(""));
4910 buf.extend_from_slice(&4.0f32.to_le_bytes());
4911 buf.extend_from_slice(&0.0f32.to_le_bytes());
4912 buf.extend_from_slice(&0.0f32.to_le_bytes());
4913 buf.extend_from_slice(&0i16.to_le_bytes());
4914 buf.extend_from_slice(&0i32.to_le_bytes());
4915 buf.extend_from_slice(&(BONE_FLAG_TAIL_INDEX | BONE_FLAG_IK).to_le_bytes());
4916 buf.extend_from_slice(&1i16.to_le_bytes());
4917 buf.extend_from_slice(&1i16.to_le_bytes());
4918 buf.extend_from_slice(&10i32.to_le_bytes());
4919 buf.extend_from_slice(&0.5f32.to_le_bytes());
4920 buf.extend_from_slice(&1i32.to_le_bytes());
4921 buf.extend_from_slice(&1i16.to_le_bytes());
4922 buf.push(1u8);
4923 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4924 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4925 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
4926 buf.extend_from_slice(&1.0f32.to_le_bytes());
4927 buf.extend_from_slice(&1.0f32.to_le_bytes());
4928 buf.extend_from_slice(&1.0f32.to_le_bytes());
4929 buf.extend_from_slice(&0i32.to_le_bytes());
4930
4931 let rt = import_pmx_runtime(&buf).unwrap();
4932 assert_eq!(rt.model.bone_count(), 3);
4933 assert_eq!(rt.model.ik_count(), 1);
4934 assert_eq!(
4935 rt.ik_solver_bone_name_to_index.get(b"LeftLegIK".as_slice()),
4936 Some(&0)
4937 );
4938 assert_eq!(
4939 rt.bone_name_to_index.get(b"LeftLegIK".as_slice()),
4940 Some(&BoneIndex(2))
4941 );
4942 assert_eq!(
4943 rt.ik_solver_bone_name_to_index.get(b"NoSuchIK".as_slice()),
4944 None
4945 );
4946 }
4947
4948 #[test]
4949 fn converts_absolute_pmx_positions_to_local_rest_positions() {
4950 let mut buf = Vec::new();
4951 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4952 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
4953 buf.extend_from_slice(&build_empty_vertex_section());
4954 buf.extend_from_slice(&build_empty_face_section());
4955 buf.extend_from_slice(&build_empty_texture_section());
4956 buf.extend_from_slice(&build_empty_material_section());
4957 buf.extend_from_slice(&build_bone_section_header(2));
4958
4959 buf.extend_from_slice(&build_bone_name_bytes("Parent"));
4960 buf.extend_from_slice(&build_bone_name_bytes(""));
4961 buf.extend_from_slice(&1.0f32.to_le_bytes());
4962 buf.extend_from_slice(&2.0f32.to_le_bytes());
4963 buf.extend_from_slice(&0.0f32.to_le_bytes());
4964 buf.extend_from_slice(&(-1i16).to_le_bytes());
4965 buf.extend_from_slice(&0i32.to_le_bytes());
4966 buf.extend_from_slice(&0u16.to_le_bytes());
4967 buf.extend_from_slice(&0.0f32.to_le_bytes());
4968 buf.extend_from_slice(&0.0f32.to_le_bytes());
4969 buf.extend_from_slice(&0.0f32.to_le_bytes());
4970
4971 buf.extend_from_slice(&build_bone_name_bytes("Child"));
4972 buf.extend_from_slice(&build_bone_name_bytes(""));
4973 buf.extend_from_slice(&1.0f32.to_le_bytes());
4974 buf.extend_from_slice(&3.0f32.to_le_bytes());
4975 buf.extend_from_slice(&0.0f32.to_le_bytes());
4976 buf.extend_from_slice(&0i16.to_le_bytes());
4977 buf.extend_from_slice(&0i32.to_le_bytes());
4978 buf.extend_from_slice(&0u16.to_le_bytes());
4979 buf.extend_from_slice(&0.0f32.to_le_bytes());
4980 buf.extend_from_slice(&0.0f32.to_le_bytes());
4981 buf.extend_from_slice(&0.0f32.to_le_bytes());
4982
4983 let result = import_pmx_model(&buf).unwrap();
4984 assert_eq!(result.bones.len(), 2);
4985 assert_eq!(result.bones[0].parent, None);
4986 assert!((result.bones[0].rest_position.x - 1.0).abs() < 0.001);
4987 assert!((result.bones[0].rest_position.y - 2.0).abs() < 0.001);
4988 assert!((result.bones[0].rest_position.z - 0.0).abs() < 0.001);
4989 assert_eq!(result.bones[1].parent, Some(BoneIndex(0)));
4990 assert!((result.bones[1].rest_position.x - 0.0).abs() < 0.001);
4991 assert!((result.bones[1].rest_position.y - 1.0).abs() < 0.001);
4992 assert!((result.bones[1].rest_position.z - 0.0).abs() < 0.001);
4993 }
4994
4995 #[test]
4996 fn root_inverse_bind_cancels_rest_world() {
4997 let mut buf = Vec::new();
4998 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
4999 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5000 buf.extend_from_slice(&build_empty_vertex_section());
5001 buf.extend_from_slice(&build_empty_face_section());
5002 buf.extend_from_slice(&build_empty_texture_section());
5003 buf.extend_from_slice(&build_empty_material_section());
5004 buf.extend_from_slice(&build_bone_section_header(1));
5005
5006 buf.extend_from_slice(&build_bone_name_bytes("Root"));
5007 buf.extend_from_slice(&build_bone_name_bytes(""));
5008 buf.extend_from_slice(&1.0f32.to_le_bytes());
5009 buf.extend_from_slice(&2.0f32.to_le_bytes());
5010 buf.extend_from_slice(&3.0f32.to_le_bytes());
5011 buf.extend_from_slice(&(-1i16).to_le_bytes());
5012 buf.extend_from_slice(&0i32.to_le_bytes());
5013 buf.extend_from_slice(&0u16.to_le_bytes());
5014 buf.extend_from_slice(&0.0f32.to_le_bytes());
5015 buf.extend_from_slice(&0.0f32.to_le_bytes());
5016 buf.extend_from_slice(&0.0f32.to_le_bytes());
5017
5018 let result = import_pmx_model(&buf).unwrap();
5019 let ibm = result.bones[0].inverse_bind_matrix;
5020 let rest_world = Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0));
5021 assert_eq!(rest_world * ibm, Mat4::IDENTITY);
5022 }
5023
5024 #[test]
5025 fn reads_pmx_fixed_axis_bone_descriptor() {
5026 let mut buf = Vec::new();
5027 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5028 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5029 buf.extend_from_slice(&build_empty_vertex_section());
5030 buf.extend_from_slice(&build_empty_face_section());
5031 buf.extend_from_slice(&build_empty_texture_section());
5032 buf.extend_from_slice(&build_empty_material_section());
5033 buf.extend_from_slice(&build_bone_section_header(1));
5034
5035 buf.extend_from_slice(&build_bone_name_bytes("Fixed"));
5036 buf.extend_from_slice(&build_bone_name_bytes(""));
5037 buf.extend_from_slice(&0.0f32.to_le_bytes());
5038 buf.extend_from_slice(&0.0f32.to_le_bytes());
5039 buf.extend_from_slice(&0.0f32.to_le_bytes());
5040 buf.extend_from_slice(&(-1i16).to_le_bytes());
5041 buf.extend_from_slice(&0i32.to_le_bytes());
5042 buf.extend_from_slice(&BONE_FLAG_FIXED_AXIS.to_le_bytes());
5043 buf.extend_from_slice(&0.0f32.to_le_bytes());
5044 buf.extend_from_slice(&0.0f32.to_le_bytes());
5045 buf.extend_from_slice(&0.0f32.to_le_bytes());
5046 buf.extend_from_slice(&0.0f32.to_le_bytes());
5047 buf.extend_from_slice(&1.0f32.to_le_bytes());
5048 buf.extend_from_slice(&0.0f32.to_le_bytes());
5049
5050 let result = import_pmx_model(&buf).unwrap();
5051 assert_eq!(result.bones[0].fixed_axis, Some(Vec3A::Y));
5052 }
5053
5054 #[test]
5055 fn child_inverse_bind_uses_absolute_not_local_position() {
5056 let mut buf = Vec::new();
5057 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5058 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5059 buf.extend_from_slice(&build_empty_vertex_section());
5060 buf.extend_from_slice(&build_empty_face_section());
5061 buf.extend_from_slice(&build_empty_texture_section());
5062 buf.extend_from_slice(&build_empty_material_section());
5063 buf.extend_from_slice(&build_bone_section_header(2));
5064
5065 buf.extend_from_slice(&build_bone_name_bytes("Root"));
5066 buf.extend_from_slice(&build_bone_name_bytes(""));
5067 buf.extend_from_slice(&1.0f32.to_le_bytes());
5068 buf.extend_from_slice(&0.0f32.to_le_bytes());
5069 buf.extend_from_slice(&0.0f32.to_le_bytes());
5070 buf.extend_from_slice(&(-1i16).to_le_bytes());
5071 buf.extend_from_slice(&0i32.to_le_bytes());
5072 buf.extend_from_slice(&0u16.to_le_bytes());
5073 buf.extend_from_slice(&0.0f32.to_le_bytes());
5074 buf.extend_from_slice(&0.0f32.to_le_bytes());
5075 buf.extend_from_slice(&0.0f32.to_le_bytes());
5076
5077 buf.extend_from_slice(&build_bone_name_bytes("Child"));
5078 buf.extend_from_slice(&build_bone_name_bytes(""));
5079 buf.extend_from_slice(&5.0f32.to_le_bytes());
5080 buf.extend_from_slice(&0.0f32.to_le_bytes());
5081 buf.extend_from_slice(&0.0f32.to_le_bytes());
5082 buf.extend_from_slice(&0i16.to_le_bytes());
5083 buf.extend_from_slice(&0i32.to_le_bytes());
5084 buf.extend_from_slice(&0u16.to_le_bytes());
5085 buf.extend_from_slice(&0.0f32.to_le_bytes());
5086 buf.extend_from_slice(&0.0f32.to_le_bytes());
5087 buf.extend_from_slice(&0.0f32.to_le_bytes());
5088
5089 let result = import_pmx_model(&buf).unwrap();
5090 assert_eq!(result.bones[1].parent, Some(BoneIndex(0)));
5091 assert_eq!(result.bones[1].rest_position, Vec3A::new(4.0, 0.0, 0.0));
5092 let ibm = result.bones[1].inverse_bind_matrix;
5093 let rest_world = Mat4::from_translation(Vec3::new(5.0, 0.0, 0.0));
5094 assert_eq!(rest_world * ibm, Mat4::IDENTITY);
5095 }
5096
5097 #[test]
5098 fn runtime_rest_pose_skinning_matrices_are_identity() {
5099 let mut buf = Vec::new();
5100 buf.extend_from_slice(&build_small_pmx_header_bytes(2, TextEncoding::Utf8));
5101 buf.extend_from_slice(&build_empty_model_info(TextEncoding::Utf8));
5102 buf.extend_from_slice(&build_empty_vertex_section());
5103 buf.extend_from_slice(&build_empty_face_section());
5104 buf.extend_from_slice(&build_empty_texture_section());
5105 buf.extend_from_slice(&build_empty_material_section());
5106 buf.extend_from_slice(&build_bone_section_header(2));
5107
5108 buf.extend_from_slice(&build_bone_name_bytes("Parent"));
5109 buf.extend_from_slice(&build_bone_name_bytes(""));
5110 buf.extend_from_slice(&1.0f32.to_le_bytes());
5111 buf.extend_from_slice(&2.0f32.to_le_bytes());
5112 buf.extend_from_slice(&0.0f32.to_le_bytes());
5113 buf.extend_from_slice(&(-1i16).to_le_bytes());
5114 buf.extend_from_slice(&0i32.to_le_bytes());
5115 buf.extend_from_slice(&0u16.to_le_bytes());
5116 buf.extend_from_slice(&0.0f32.to_le_bytes());
5117 buf.extend_from_slice(&0.0f32.to_le_bytes());
5118 buf.extend_from_slice(&0.0f32.to_le_bytes());
5119
5120 buf.extend_from_slice(&build_bone_name_bytes("Child"));
5121 buf.extend_from_slice(&build_bone_name_bytes(""));
5122 buf.extend_from_slice(&1.0f32.to_le_bytes());
5123 buf.extend_from_slice(&3.0f32.to_le_bytes());
5124 buf.extend_from_slice(&0.0f32.to_le_bytes());
5125 buf.extend_from_slice(&0i16.to_le_bytes());
5126 buf.extend_from_slice(&0i32.to_le_bytes());
5127 buf.extend_from_slice(&0u16.to_le_bytes());
5128 buf.extend_from_slice(&0.0f32.to_le_bytes());
5129 buf.extend_from_slice(&0.0f32.to_le_bytes());
5130 buf.extend_from_slice(&0.0f32.to_le_bytes());
5131
5132 buf.extend_from_slice(&0i32.to_le_bytes());
5133
5134 let rt = import_pmx_runtime(&buf).unwrap();
5135 let model = Arc::new(rt.model);
5136 let mut instance = RuntimeInstance::new(model);
5137 instance.evaluate_current_pose();
5138 let matrices = instance.skinning_matrices();
5139 assert_eq!(matrices.len(), 2);
5140 assert_eq!(matrices[0], Mat4::IDENTITY);
5141 assert_eq!(matrices[1], Mat4::IDENTITY);
5142 }
5143}