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