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