1use glam::{Quat, Vec3A};
2use serde::{Deserialize, Serialize};
3
4use mmd_anim_runtime::{
5 AnimationClip, BoneAnimationBinding, BoneIndex, InterpolationScalar, InterpolationVector3,
6 MorphAnimationBinding, MorphIndex, MorphKeyframe, MorphTrack, MovableBoneKeyframe,
7 MovableBoneTrack, PropertyAnimationBinding, PropertyKeyframe,
8};
9
10use crate::error::ImportError;
11use crate::normalize::normalize_vmd_name;
12
13const VMD_MAGIC: [u8; 30] = *b"Vocaloid Motion Data 0002\0\0\0\0\0";
14const VMD_MAGIC_PREFIX: &[u8] = b"Vocaloid Motion Data 0002\0";
15
16struct Reader<'a> {
17 data: &'a [u8],
18 pos: usize,
19}
20
21impl<'a> Reader<'a> {
22 fn new(data: &'a [u8]) -> Self {
23 Self { data, pos: 0 }
24 }
25
26 fn remaining(&self) -> usize {
27 self.data.len().saturating_sub(self.pos)
28 }
29
30 fn require(&self, n: usize) -> Result<(), ImportError> {
31 if self.remaining() >= n {
32 Ok(())
33 } else {
34 Err(ImportError::UnexpectedEof(
35 n.saturating_sub(self.remaining()),
36 ))
37 }
38 }
39
40 fn read_slice(&mut self, n: usize) -> Result<&'a [u8], ImportError> {
41 self.require(n)?;
42 let slice = &self.data[self.pos..self.pos + n];
43 self.pos += n;
44 Ok(slice)
45 }
46
47 fn read_u8(&mut self) -> Result<u8, ImportError> {
48 Ok(self.read_slice(1)?[0])
49 }
50
51 fn read_u32_le(&mut self) -> Result<u32, ImportError> {
52 let b = self.read_slice(4)?;
53 Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
54 }
55
56 fn read_optional_u32_le(&mut self) -> Result<Option<u32>, ImportError> {
57 if self.remaining() == 0 {
58 Ok(None)
59 } else {
60 self.read_u32_le().map(Some)
61 }
62 }
63
64 fn require_record_bytes(&self, count: usize, record_size: usize) -> Result<(), ImportError> {
65 let bytes = count
66 .checked_mul(record_size)
67 .ok_or(ImportError::SectionOverflow)?;
68 self.require(bytes)
69 }
70
71 fn read_record_count(&mut self, record_size: usize) -> Result<usize, ImportError> {
72 let count = self.read_u32_le()? as usize;
73 self.require_record_bytes(count, record_size)?;
74 Ok(count)
75 }
76
77 fn skip_optional_ignored_records(&mut self, record_size: usize) -> Result<bool, ImportError> {
78 let Some(count) = self.read_optional_u32_le()? else {
79 return Ok(false);
80 };
81 let Some(bytes) = (count as usize).checked_mul(record_size) else {
82 self.pos = self.data.len();
83 return Ok(false);
84 };
85 if bytes > self.remaining() {
86 self.pos = self.data.len();
87 return Ok(false);
88 }
89 self.read_slice(bytes)?;
90 Ok(true)
91 }
92
93 fn read_optional_record_count(
94 &mut self,
95 record_size: usize,
96 ) -> Result<Option<usize>, ImportError> {
97 let Some(count) = self.read_optional_u32_le()? else {
98 return Ok(None);
99 };
100 let count = count as usize;
101 let Some(bytes) = count.checked_mul(record_size) else {
102 self.pos = self.data.len();
103 return Ok(None);
104 };
105 if bytes > self.remaining() {
106 self.pos = self.data.len();
107 return Ok(None);
108 }
109 Ok(Some(count))
110 }
111
112 fn read_f32_le(&mut self) -> Result<f32, ImportError> {
113 let b = self.read_slice(4)?;
114 Ok(f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
115 }
116
117 fn read_vec3(&mut self) -> Result<Vec3A, ImportError> {
118 Ok(Vec3A::new(
119 self.read_f32_le()?,
120 self.read_f32_le()?,
121 self.read_f32_le()?,
122 ))
123 }
124
125 fn read_quat(&mut self) -> Result<Quat, ImportError> {
126 Ok(Quat::from_xyzw(
127 self.read_f32_le()?,
128 self.read_f32_le()?,
129 self.read_f32_le()?,
130 self.read_f32_le()?,
131 ))
132 }
133
134 fn read_shifts_jis_name(&mut self) -> Result<Vec<u8>, ImportError> {
135 let raw = self.read_slice(15)?;
136 let end = raw.iter().position(|&b| b == 0).unwrap_or(raw.len());
137 Ok(raw[..end].to_vec())
138 }
139}
140
141#[derive(Debug, Clone)]
142pub struct VmdHeader {
143 pub model_name_bytes: [u8; 20],
144}
145
146#[derive(Debug, Clone)]
147pub enum VmdBoneImportMode {
148 ByName(Vec<u8>),
149 ByIndex(u32),
150}
151
152#[derive(Debug, Clone)]
153pub struct VmdBoneKeyframeRaw {
154 pub bone_mode: VmdBoneImportMode,
155 pub frame: u32,
156 pub position: Vec3A,
157 pub rotation: Quat,
158 pub interpolation: [u8; 64],
159 pub bone_name_normalized: Vec<u8>,
160}
161
162#[derive(Debug, Clone)]
163pub struct VmdIkEntry {
164 pub name_bytes: Vec<u8>,
165 pub enabled: u8,
166 pub name_normalized: Vec<u8>,
167}
168
169#[derive(Debug, Clone)]
170pub struct VmdPropertyIkFrame {
171 pub frame: u32,
172 pub show: u8,
173 pub entries: Vec<VmdIkEntry>,
174}
175
176#[derive(Debug, Clone)]
177pub struct VmdImportResult {
178 pub bone_keyframes: Vec<VmdBoneKeyframeRaw>,
179 pub morph_keyframes: Vec<(Vec<u8>, u32, f32)>,
180 pub property_keyframes: Vec<PropertyKeyframe>,
181 pub property_ik_frames: Vec<VmdPropertyIkFrame>,
182}
183
184pub fn read_header(data: &[u8]) -> Result<(VmdHeader, usize), ImportError> {
185 let mut r = Reader::new(data);
186
187 let magic = r.read_slice(30)?;
188 if magic != VMD_MAGIC && !magic.starts_with(VMD_MAGIC_PREFIX) {
189 return Err(ImportError::InvalidVmdMagic);
190 }
191
192 let model_name_bytes: [u8; 20] = r
193 .read_slice(20)?
194 .try_into()
195 .map_err(|_| ImportError::InvalidVmdModelName)?;
196
197 Ok((VmdHeader { model_name_bytes }, r.pos))
198}
199
200pub fn import_vmd_motion(data: &[u8]) -> Result<VmdImportResult, ImportError> {
201 let (_header, pos) = read_header(data)?;
202 let mut r = Reader { data, pos };
203
204 let bone_count = r.read_record_count(111)?;
205 let mut bone_keyframes = Vec::with_capacity(bone_count);
206 for _ in 0..bone_count {
207 let bone_name = r.read_shifts_jis_name()?;
208 let frame = r.read_u32_le()?;
209 let position = r.read_vec3()?;
210 let rotation = r.read_quat()?;
211 let interpolation: [u8; 64] = r.read_slice(64)?.try_into().unwrap();
212
213 let bone_name_normalized = normalize_vmd_name(&bone_name);
214
215 bone_keyframes.push(VmdBoneKeyframeRaw {
216 bone_mode: VmdBoneImportMode::ByName(bone_name),
217 frame,
218 position,
219 rotation,
220 interpolation,
221 bone_name_normalized,
222 });
223 }
224
225 let Some(morph_count) = r.read_optional_u32_le()? else {
226 return Ok(VmdImportResult {
227 bone_keyframes,
228 morph_keyframes: Vec::new(),
229 property_keyframes: Vec::new(),
230 property_ik_frames: Vec::new(),
231 });
232 };
233 let morph_count = morph_count as usize;
234 r.require_record_bytes(morph_count, 23)?;
235 let mut morph_keyframes = Vec::with_capacity(morph_count);
236 for _ in 0..morph_count {
237 let morph_name = r.read_shifts_jis_name()?;
238 let frame = r.read_u32_le()?;
239 let weight = r.read_f32_le()?;
240 morph_keyframes.push((morph_name, frame, weight));
241 }
242
243 if !r.skip_optional_ignored_records(61)? {
244 return Ok(VmdImportResult {
245 bone_keyframes,
246 morph_keyframes,
247 property_keyframes: Vec::new(),
248 property_ik_frames: Vec::new(),
249 });
250 }
251
252 if !r.skip_optional_ignored_records(28)? {
253 return Ok(VmdImportResult {
254 bone_keyframes,
255 morph_keyframes,
256 property_keyframes: Vec::new(),
257 property_ik_frames: Vec::new(),
258 });
259 }
260
261 if !r.skip_optional_ignored_records(9)? {
262 return Ok(VmdImportResult {
263 bone_keyframes,
264 morph_keyframes,
265 property_keyframes: Vec::new(),
266 property_ik_frames: Vec::new(),
267 });
268 }
269
270 let Some(show_ik_count) = r.read_optional_u32_le()? else {
271 return Ok(VmdImportResult {
272 bone_keyframes,
273 morph_keyframes,
274 property_keyframes: Vec::new(),
275 property_ik_frames: Vec::new(),
276 });
277 };
278 let show_ik_count = show_ik_count as usize;
279 r.require_record_bytes(show_ik_count, 9)?;
280 let mut property_keyframes = Vec::with_capacity(show_ik_count);
281 let mut property_ik_frames = Vec::with_capacity(show_ik_count);
282 for _ in 0..show_ik_count {
283 let frame = r.read_u32_le()?;
284 let show = r.read_u8()?;
285 let ik_count = r.read_u32_le()? as usize;
286 r.require_record_bytes(ik_count, 21)?;
287 let mut ik_enabled = Vec::with_capacity(ik_count);
288 let mut ik_entries = Vec::with_capacity(ik_count);
289 for _ in 0..ik_count {
290 let ik_name_bytes = r.read_slice(20)?.to_vec();
291 let enabled = r.read_u8()?;
292 let end = ik_name_bytes
293 .iter()
294 .position(|&b| b == 0)
295 .unwrap_or(ik_name_bytes.len());
296 let name_normalized = normalize_vmd_name(&ik_name_bytes[..end]);
297 ik_enabled.push(enabled);
298 ik_entries.push(VmdIkEntry {
299 name_bytes: ik_name_bytes,
300 enabled,
301 name_normalized,
302 });
303 }
304 property_keyframes.push(PropertyKeyframe::new(
305 frame,
306 ik_enabled.iter().map(|&b| b != 0).collect(),
307 ));
308 property_ik_frames.push(VmdPropertyIkFrame {
309 frame,
310 show,
311 entries: ik_entries,
312 });
313 }
314
315 Ok(VmdImportResult {
316 bone_keyframes,
317 morph_keyframes,
318 property_keyframes,
319 property_ik_frames,
320 })
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
324#[serde(rename_all = "camelCase")]
325pub struct VmdParsedAnimation {
326 #[serde(default = "default_vmd_kind", skip_deserializing)]
327 pub kind: &'static str,
328 pub metadata: VmdParsedMetadata,
329 pub bone_frames: Vec<VmdParsedBoneFrame>,
330 pub morph_frames: Vec<VmdParsedMorphFrame>,
331 pub camera_frames: Vec<VmdParsedCameraFrame>,
332 pub light_frames: Vec<VmdParsedLightFrame>,
333 pub self_shadow_frames: Vec<VmdParsedSelfShadowFrame>,
334 pub property_frames: Vec<VmdParsedPropertyFrame>,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
338#[serde(rename_all = "camelCase")]
339pub struct VmdParsedMetadata {
340 #[serde(default = "default_vmd_format", skip_deserializing)]
341 pub format: &'static str,
342 pub model_name: String,
343 #[serde(default, skip_serializing_if = "Vec::is_empty")]
344 pub model_name_bytes: Vec<u8>,
345 pub counts: VmdParsedCounts,
346 pub max_frame: u32,
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct VmdParsedCounts {
351 pub bones: usize,
352 pub morphs: usize,
353 pub cameras: usize,
354 pub lights: usize,
355 #[serde(rename = "selfShadows")]
356 pub self_shadows: usize,
357 pub properties: usize,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
361#[serde(rename_all = "camelCase")]
362pub struct VmdParsedBoneFrame {
363 pub bone_name: String,
364 #[serde(default, skip_serializing_if = "Vec::is_empty")]
365 pub bone_name_bytes: Vec<u8>,
366 pub frame: u32,
367 pub translation: [f32; 3],
368 pub rotation: [f32; 4],
369 pub interpolation: Vec<u8>,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
373#[serde(rename_all = "camelCase")]
374pub struct VmdParsedMorphFrame {
375 pub morph_name: String,
376 #[serde(default, skip_serializing_if = "Vec::is_empty")]
377 pub morph_name_bytes: Vec<u8>,
378 pub frame: u32,
379 pub weight: f32,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
383#[serde(rename_all = "camelCase")]
384pub struct VmdParsedCameraFrame {
385 pub frame: u32,
386 pub distance: f32,
387 pub position: [f32; 3],
388 pub rotation: [f32; 3],
389 pub interpolation: [u8; 24],
390 pub fov: u32,
391 pub perspective: bool,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct VmdParsedLightFrame {
397 pub frame: u32,
398 pub color: [f32; 3],
399 pub direction: [f32; 3],
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
403#[serde(rename_all = "camelCase")]
404pub struct VmdParsedSelfShadowFrame {
405 pub frame: u32,
406 pub mode: u8,
407 pub distance: f32,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
411#[serde(rename_all = "camelCase")]
412pub struct VmdParsedPropertyFrame {
413 pub frame: u32,
414 pub visible: bool,
415 pub ik_states: Vec<VmdParsedIkState>,
416}
417
418#[derive(Debug, Clone, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase")]
420pub struct VmdParsedIkState {
421 pub bone_name: String,
422 #[serde(default, skip_serializing_if = "Vec::is_empty")]
423 pub bone_name_bytes: Vec<u8>,
424 pub enabled: bool,
425}
426
427pub fn parse_vmd_animation(data: &[u8]) -> Result<VmdParsedAnimation, ImportError> {
428 let (_header, pos) = read_header(data)?;
429 let mut r = Reader { data, pos };
430 let model_name = decode_sjis_fixed(&_header.model_name_bytes);
431 let model_name_bytes = trim_fixed_bytes(&_header.model_name_bytes).to_vec();
432 let mut max_frame = 0u32;
433
434 let bone_count = r.read_record_count(111)?;
435 let mut bone_frames = Vec::with_capacity(bone_count);
436 for _ in 0..bone_count {
437 let bone_name_bytes = r.read_slice(15)?;
438 let bone_name = decode_sjis_fixed(bone_name_bytes);
439 let frame = r.read_u32_le()?;
440 max_frame = max_frame.max(frame);
441 let position = r.read_vec3()?;
442 let rotation = r.read_quat()?;
443 let interpolation: [u8; 64] = r.read_slice(64)?.try_into().unwrap();
444 bone_frames.push(VmdParsedBoneFrame {
445 bone_name,
446 bone_name_bytes: trim_fixed_bytes(bone_name_bytes).to_vec(),
447 frame,
448 translation: [position.x, position.y, position.z],
449 rotation: [rotation.x, rotation.y, rotation.z, rotation.w],
450 interpolation: interpolation.to_vec(),
451 });
452 }
453
454 let Some(morph_count) = r.read_optional_u32_le()? else {
455 return Ok(vmd_parsed_animation(
456 model_name,
457 model_name_bytes,
458 max_frame,
459 VmdParsedSections {
460 bone_frames,
461 morph_frames: Vec::new(),
462 camera_frames: Vec::new(),
463 light_frames: Vec::new(),
464 self_shadow_frames: Vec::new(),
465 property_frames: Vec::new(),
466 },
467 ));
468 };
469 let morph_count = morph_count as usize;
470 r.require_record_bytes(morph_count, 23)?;
471 let mut morph_frames = Vec::with_capacity(morph_count);
472 for _ in 0..morph_count {
473 let morph_name_bytes = r.read_slice(15)?;
474 let morph_name = decode_sjis_fixed(morph_name_bytes);
475 let frame = r.read_u32_le()?;
476 max_frame = max_frame.max(frame);
477 morph_frames.push(VmdParsedMorphFrame {
478 morph_name,
479 morph_name_bytes: trim_fixed_bytes(morph_name_bytes).to_vec(),
480 frame,
481 weight: r.read_f32_le()?,
482 });
483 }
484
485 let camera_frames = read_parsed_camera_frames(&mut r, &mut max_frame)?;
486 let light_frames = read_parsed_light_frames(&mut r, &mut max_frame)?;
487 let self_shadow_frames = read_parsed_self_shadow_frames(&mut r, &mut max_frame)?;
488 let property_frames = read_parsed_property_frames(&mut r, &mut max_frame)?;
489
490 Ok(vmd_parsed_animation(
491 model_name,
492 model_name_bytes,
493 max_frame,
494 VmdParsedSections {
495 bone_frames,
496 morph_frames,
497 camera_frames,
498 light_frames,
499 self_shadow_frames,
500 property_frames,
501 },
502 ))
503}
504
505fn default_vmd_kind() -> &'static str {
506 "vmd"
507}
508
509fn default_vmd_format() -> &'static str {
510 "vmd"
511}
512
513pub fn export_vmd_animation(animation: &VmdParsedAnimation) -> Vec<u8> {
514 let mut out = Vec::new();
515 out.extend_from_slice(&VMD_MAGIC);
516 write_fixed_name_bytes(
517 &mut out,
518 &animation.metadata.model_name,
519 &animation.metadata.model_name_bytes,
520 20,
521 );
522
523 write_u32(&mut out, animation.bone_frames.len() as u32);
524 for frame in &animation.bone_frames {
525 write_fixed_name_bytes(&mut out, &frame.bone_name, &frame.bone_name_bytes, 15);
526 write_u32(&mut out, frame.frame);
527 write_f32(&mut out, frame.translation[0]);
528 write_f32(&mut out, frame.translation[1]);
529 write_f32(&mut out, frame.translation[2]);
530 write_f32(&mut out, frame.rotation[0]);
531 write_f32(&mut out, frame.rotation[1]);
532 write_f32(&mut out, frame.rotation[2]);
533 write_f32(&mut out, frame.rotation[3]);
534 write_fixed_bytes(&mut out, &frame.interpolation, 64);
535 }
536
537 write_u32(&mut out, animation.morph_frames.len() as u32);
538 for frame in &animation.morph_frames {
539 write_fixed_name_bytes(&mut out, &frame.morph_name, &frame.morph_name_bytes, 15);
540 write_u32(&mut out, frame.frame);
541 write_f32(&mut out, frame.weight);
542 }
543
544 write_u32(&mut out, animation.camera_frames.len() as u32);
545 for frame in &animation.camera_frames {
546 write_u32(&mut out, frame.frame);
547 write_f32(&mut out, frame.distance);
548 write_f32(&mut out, frame.position[0]);
549 write_f32(&mut out, frame.position[1]);
550 write_f32(&mut out, frame.position[2]);
551 write_f32(&mut out, frame.rotation[0]);
552 write_f32(&mut out, frame.rotation[1]);
553 write_f32(&mut out, frame.rotation[2]);
554 out.extend_from_slice(&frame.interpolation);
555 write_u32(&mut out, frame.fov);
556 out.push(if frame.perspective { 0 } else { 1 });
557 }
558
559 write_u32(&mut out, animation.light_frames.len() as u32);
560 for frame in &animation.light_frames {
561 write_u32(&mut out, frame.frame);
562 write_f32(&mut out, frame.color[0]);
563 write_f32(&mut out, frame.color[1]);
564 write_f32(&mut out, frame.color[2]);
565 write_f32(&mut out, frame.direction[0]);
566 write_f32(&mut out, frame.direction[1]);
567 write_f32(&mut out, frame.direction[2]);
568 }
569
570 write_u32(&mut out, animation.self_shadow_frames.len() as u32);
571 for frame in &animation.self_shadow_frames {
572 write_u32(&mut out, frame.frame);
573 out.push(frame.mode);
574 write_f32(&mut out, frame.distance);
575 }
576
577 write_u32(&mut out, animation.property_frames.len() as u32);
578 for frame in &animation.property_frames {
579 write_u32(&mut out, frame.frame);
580 out.push(u8::from(frame.visible));
581 write_u32(&mut out, frame.ik_states.len() as u32);
582 for state in &frame.ik_states {
583 write_fixed_name_bytes(&mut out, &state.bone_name, &state.bone_name_bytes, 20);
584 out.push(u8::from(state.enabled));
585 }
586 }
587
588 out
589}
590
591struct VmdParsedSections {
592 bone_frames: Vec<VmdParsedBoneFrame>,
593 morph_frames: Vec<VmdParsedMorphFrame>,
594 camera_frames: Vec<VmdParsedCameraFrame>,
595 light_frames: Vec<VmdParsedLightFrame>,
596 self_shadow_frames: Vec<VmdParsedSelfShadowFrame>,
597 property_frames: Vec<VmdParsedPropertyFrame>,
598}
599
600fn vmd_parsed_animation(
601 model_name: String,
602 model_name_bytes: Vec<u8>,
603 max_frame: u32,
604 sections: VmdParsedSections,
605) -> VmdParsedAnimation {
606 VmdParsedAnimation {
607 kind: "vmd",
608 metadata: VmdParsedMetadata {
609 format: "vmd",
610 model_name,
611 model_name_bytes,
612 counts: VmdParsedCounts {
613 bones: sections.bone_frames.len(),
614 morphs: sections.morph_frames.len(),
615 cameras: sections.camera_frames.len(),
616 lights: sections.light_frames.len(),
617 self_shadows: sections.self_shadow_frames.len(),
618 properties: sections.property_frames.len(),
619 },
620 max_frame,
621 },
622 bone_frames: sections.bone_frames,
623 morph_frames: sections.morph_frames,
624 camera_frames: sections.camera_frames,
625 light_frames: sections.light_frames,
626 self_shadow_frames: sections.self_shadow_frames,
627 property_frames: sections.property_frames,
628 }
629}
630
631fn read_parsed_camera_frames(
632 r: &mut Reader<'_>,
633 max_frame: &mut u32,
634) -> Result<Vec<VmdParsedCameraFrame>, ImportError> {
635 let Some(count) = r.read_optional_record_count(61)? else {
636 return Ok(Vec::new());
637 };
638 let mut frames = Vec::with_capacity(count);
639 for _ in 0..count {
640 let frame = r.read_u32_le()?;
641 *max_frame = (*max_frame).max(frame);
642 frames.push(VmdParsedCameraFrame {
643 frame,
644 distance: r.read_f32_le()?,
645 position: {
646 let p = r.read_vec3()?;
647 [p.x, p.y, p.z]
648 },
649 rotation: {
650 let p = r.read_vec3()?;
651 [p.x, p.y, p.z]
652 },
653 interpolation: r.read_slice(24)?.try_into().unwrap(),
654 fov: r.read_u32_le()?,
655 perspective: r.read_u8()? == 0,
656 });
657 }
658 Ok(frames)
659}
660
661fn read_parsed_light_frames(
662 r: &mut Reader<'_>,
663 max_frame: &mut u32,
664) -> Result<Vec<VmdParsedLightFrame>, ImportError> {
665 let Some(count) = r.read_optional_record_count(28)? else {
666 return Ok(Vec::new());
667 };
668 let mut frames = Vec::with_capacity(count);
669 for _ in 0..count {
670 let frame = r.read_u32_le()?;
671 *max_frame = (*max_frame).max(frame);
672 let color = r.read_vec3()?;
673 let direction = r.read_vec3()?;
674 frames.push(VmdParsedLightFrame {
675 frame,
676 color: [color.x, color.y, color.z],
677 direction: [direction.x, direction.y, direction.z],
678 });
679 }
680 Ok(frames)
681}
682
683fn read_parsed_self_shadow_frames(
684 r: &mut Reader<'_>,
685 max_frame: &mut u32,
686) -> Result<Vec<VmdParsedSelfShadowFrame>, ImportError> {
687 let Some(count) = r.read_optional_record_count(9)? else {
688 return Ok(Vec::new());
689 };
690 let mut frames = Vec::with_capacity(count);
691 for _ in 0..count {
692 let frame = r.read_u32_le()?;
693 *max_frame = (*max_frame).max(frame);
694 frames.push(VmdParsedSelfShadowFrame {
695 frame,
696 mode: r.read_u8()?,
697 distance: r.read_f32_le()?,
698 });
699 }
700 Ok(frames)
701}
702
703fn read_parsed_property_frames(
704 r: &mut Reader<'_>,
705 max_frame: &mut u32,
706) -> Result<Vec<VmdParsedPropertyFrame>, ImportError> {
707 let Some(count) = r.read_optional_u32_le()? else {
708 return Ok(Vec::new());
709 };
710 let count = count as usize;
711 r.require_record_bytes(count, 9)?;
712 let mut frames = Vec::with_capacity(count);
713 for _ in 0..count {
714 let frame = r.read_u32_le()?;
715 *max_frame = (*max_frame).max(frame);
716 let visible = r.read_u8()? != 0;
717 let ik_count = r.read_u32_le()? as usize;
718 r.require_record_bytes(ik_count, 21)?;
719 let mut ik_states = Vec::with_capacity(ik_count);
720 for _ in 0..ik_count {
721 let bone_name_bytes = r.read_slice(20)?;
722 ik_states.push(VmdParsedIkState {
723 bone_name: decode_sjis_fixed(bone_name_bytes),
724 bone_name_bytes: trim_fixed_bytes(bone_name_bytes).to_vec(),
725 enabled: r.read_u8()? != 0,
726 });
727 }
728 frames.push(VmdParsedPropertyFrame {
729 frame,
730 visible,
731 ik_states,
732 });
733 }
734 Ok(frames)
735}
736
737fn decode_sjis_fixed(bytes: &[u8]) -> String {
738 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
739 let (decoded, _, _) = encoding_rs::SHIFT_JIS.decode(&bytes[..end]);
740 decoded.trim().to_owned()
741}
742
743fn trim_fixed_bytes(bytes: &[u8]) -> &[u8] {
744 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
745 &bytes[..end]
746}
747
748fn write_u32(out: &mut Vec<u8>, value: u32) {
749 out.extend_from_slice(&value.to_le_bytes());
750}
751
752fn write_f32(out: &mut Vec<u8>, value: f32) {
753 out.extend_from_slice(&value.to_le_bytes());
754}
755
756fn write_fixed_name_bytes(out: &mut Vec<u8>, value: &str, raw_bytes: &[u8], len: usize) {
757 if raw_bytes.is_empty() {
758 let (encoded, _, _) = encoding_rs::SHIFT_JIS.encode(value);
759 write_fixed_bytes(out, encoded.as_ref(), len);
760 } else {
761 write_fixed_bytes(out, raw_bytes, len);
762 }
763}
764
765fn write_fixed_bytes(out: &mut Vec<u8>, value: &[u8], len: usize) {
766 let copied = value.len().min(len);
767 out.extend_from_slice(&value[..copied]);
768 out.resize(out.len() + len - copied, 0);
769}
770
771fn decode_interpolation_scalar(data: [u8; 4]) -> InterpolationScalar {
772 InterpolationScalar {
773 x1: data[0].min(127),
774 y1: data[1].min(127),
775 x2: data[2].min(127),
776 y2: data[3].min(127),
777 }
778}
779
780fn decode_bone_interpolation(
781 interpolation: &[u8; 64],
782) -> (InterpolationVector3, InterpolationScalar) {
783 let position = InterpolationVector3 {
784 x: decode_interpolation_scalar([
785 interpolation[0],
786 interpolation[4],
787 interpolation[8],
788 interpolation[12],
789 ]),
790 y: decode_interpolation_scalar([
791 interpolation[1],
792 interpolation[5],
793 interpolation[9],
794 interpolation[13],
795 ]),
796 z: decode_interpolation_scalar([
797 interpolation[2],
798 interpolation[6],
799 interpolation[10],
800 interpolation[14],
801 ]),
802 };
803 let rotation = decode_interpolation_scalar([
804 interpolation[3],
805 interpolation[7],
806 interpolation[11],
807 interpolation[15],
808 ]);
809 (position, rotation)
810}
811
812pub fn build_clip_from_import(
813 result: VmdImportResult,
814 bone_name_to_index: &dyn Fn(&[u8]) -> Option<BoneIndex>,
815 morph_name_to_index: &dyn Fn(&[u8]) -> Option<MorphIndex>,
816) -> AnimationClip {
817 let mut bone_tracks_map: std::collections::BTreeMap<u32, Vec<MovableBoneKeyframe>> =
818 std::collections::BTreeMap::new();
819
820 for kf in result.bone_keyframes {
821 let bone_index = match &kf.bone_mode {
822 VmdBoneImportMode::ByName(_name) => {
823 match bone_name_to_index(&kf.bone_name_normalized) {
824 Some(idx) => idx,
825 None => continue,
826 }
827 }
828 VmdBoneImportMode::ByIndex(idx) => BoneIndex(*idx),
829 };
830
831 let (pos_interp, rot_interp) = decode_bone_interpolation(&kf.interpolation);
832
833 bone_tracks_map
834 .entry(bone_index.0)
835 .or_default()
836 .push(MovableBoneKeyframe {
837 frame: kf.frame,
838 position: kf.position,
839 rotation: kf.rotation,
840 position_interpolation: pos_interp,
841 rotation_interpolation: rot_interp,
842 });
843 }
844
845 let bone_tracks: Vec<BoneAnimationBinding> = bone_tracks_map
846 .into_iter()
847 .map(|(bone_idx, kfs)| BoneAnimationBinding {
848 bone: BoneIndex(bone_idx),
849 track: MovableBoneTrack::from_keyframes(kfs),
850 })
851 .collect();
852
853 let mut morph_tracks_map: std::collections::BTreeMap<u32, Vec<MorphKeyframe>> =
854 std::collections::BTreeMap::new();
855
856 for (morph_name, frame, weight) in result.morph_keyframes {
857 let morph_name_normalized = normalize_vmd_name(&morph_name);
858 let morph_index = match morph_name_to_index(&morph_name_normalized) {
859 Some(idx) => idx,
860 None => continue,
861 };
862 morph_tracks_map
863 .entry(morph_index.0)
864 .or_default()
865 .push(MorphKeyframe::new(frame, weight));
866 }
867
868 let morph_tracks: Vec<MorphAnimationBinding> = morph_tracks_map
869 .into_iter()
870 .map(|(morph_idx, kfs)| MorphAnimationBinding {
871 morph: MorphIndex(morph_idx),
872 track: MorphTrack::from_keyframes(kfs),
873 })
874 .collect();
875
876 let property_track = if result.property_keyframes.is_empty() {
877 None
878 } else {
879 Some(PropertyAnimationBinding::from_keyframes(
880 result.property_keyframes,
881 ))
882 };
883
884 AnimationClip::new_full(bone_tracks, morph_tracks, property_track)
885}
886
887pub fn build_property_binding_with_ik_resolver(
888 ik_frames: &[VmdPropertyIkFrame],
889 ik_name_to_solver_index: &dyn Fn(&[u8]) -> Option<usize>,
890 solver_count: usize,
891) -> Option<PropertyAnimationBinding> {
892 if solver_count == 0 || ik_frames.is_empty() {
893 return None;
894 }
895
896 let keyframes: Vec<PropertyKeyframe> = ik_frames
897 .iter()
898 .map(|frame| {
899 let mut ik_enabled = vec![1u8; solver_count];
900 for entry in &frame.entries {
901 match ik_name_to_solver_index(&entry.name_normalized) {
902 Some(idx) if idx < solver_count => {
903 ik_enabled[idx] = entry.enabled;
904 }
905 _ => {}
906 }
907 }
908 PropertyKeyframe {
909 frame: frame.frame,
910 ik_enabled: ik_enabled.into_boxed_slice(),
911 }
912 })
913 .collect();
914
915 Some(PropertyAnimationBinding::from_keyframes(keyframes))
916}
917
918#[derive(Debug, Clone, Copy)]
920pub struct VmdClipBuildOptions {
921 pub honor_property_ik: bool,
927}
928
929impl Default for VmdClipBuildOptions {
930 fn default() -> Self {
931 Self {
932 honor_property_ik: true,
933 }
934 }
935}
936
937pub fn build_pair_clip(
938 result: &VmdImportResult,
939 bone_name_to_index: &std::collections::HashMap<Vec<u8>, BoneIndex>,
940 morph_name_to_index: &std::collections::HashMap<Vec<u8>, MorphIndex>,
941 ik_solver_bone_name_to_index: &std::collections::HashMap<Vec<u8>, usize>,
942 solver_count: usize,
943) -> AnimationClip {
944 build_pair_clip_with_options(
945 result,
946 bone_name_to_index,
947 morph_name_to_index,
948 ik_solver_bone_name_to_index,
949 solver_count,
950 VmdClipBuildOptions::default(),
951 )
952}
953
954pub fn build_pair_clip_with_options(
955 result: &VmdImportResult,
956 bone_name_to_index: &std::collections::HashMap<Vec<u8>, BoneIndex>,
957 morph_name_to_index: &std::collections::HashMap<Vec<u8>, MorphIndex>,
958 ik_solver_bone_name_to_index: &std::collections::HashMap<Vec<u8>, usize>,
959 solver_count: usize,
960 options: VmdClipBuildOptions,
961) -> AnimationClip {
962 let mut bone_tracks_map: std::collections::BTreeMap<u32, Vec<MovableBoneKeyframe>> =
963 std::collections::BTreeMap::new();
964
965 for kf in &result.bone_keyframes {
966 let bone_index = match bone_name_to_index.get(&kf.bone_name_normalized) {
967 Some(idx) => *idx,
968 None => continue,
969 };
970
971 let (pos_interp, rot_interp) = decode_bone_interpolation(&kf.interpolation);
972
973 bone_tracks_map
974 .entry(bone_index.0)
975 .or_default()
976 .push(MovableBoneKeyframe {
977 frame: kf.frame,
978 position: kf.position,
979 rotation: kf.rotation,
980 position_interpolation: pos_interp,
981 rotation_interpolation: rot_interp,
982 });
983 }
984
985 let bone_tracks: Vec<BoneAnimationBinding> = bone_tracks_map
986 .into_iter()
987 .map(|(bone_idx, kfs)| BoneAnimationBinding {
988 bone: BoneIndex(bone_idx),
989 track: MovableBoneTrack::from_keyframes(kfs),
990 })
991 .collect();
992
993 let mut morph_tracks_map: std::collections::BTreeMap<u32, Vec<MorphKeyframe>> =
994 std::collections::BTreeMap::new();
995
996 for (morph_name, frame, weight) in &result.morph_keyframes {
997 let morph_name_normalized = normalize_vmd_name(morph_name);
998 let morph_index = match morph_name_to_index.get(&morph_name_normalized) {
999 Some(idx) => *idx,
1000 None => continue,
1001 };
1002 morph_tracks_map
1003 .entry(morph_index.0)
1004 .or_default()
1005 .push(MorphKeyframe::new(*frame, *weight));
1006 }
1007
1008 let morph_tracks: Vec<MorphAnimationBinding> = morph_tracks_map
1009 .into_iter()
1010 .map(|(morph_idx, kfs)| MorphAnimationBinding {
1011 morph: MorphIndex(morph_idx),
1012 track: MorphTrack::from_keyframes(kfs),
1013 })
1014 .collect();
1015
1016 let property_track = if options.honor_property_ik {
1017 build_property_binding_with_ik_resolver(
1018 &result.property_ik_frames,
1019 &|name| ik_solver_bone_name_to_index.get(name).copied(),
1020 solver_count,
1021 )
1022 } else {
1023 None
1024 };
1025
1026 AnimationClip::new_full(bone_tracks, morph_tracks, property_track)
1027}
1028
1029#[cfg(test)]
1030mod tests {
1031 use super::*;
1032 #[allow(unused_imports)]
1033 use serde_json;
1034
1035 fn build_vmd_header_bytes() -> Vec<u8> {
1036 let mut buf = Vec::new();
1037 buf.extend_from_slice(&VMD_MAGIC);
1038 buf.extend_from_slice(&[0u8; 20]);
1039 buf
1040 }
1041
1042 #[test]
1043 fn rejects_impossible_vmd_bone_count_before_allocation() {
1044 let mut buf = build_vmd_header_bytes();
1045 buf.extend_from_slice(&u32::MAX.to_le_bytes());
1046
1047 assert!(matches!(
1048 import_vmd_motion(&buf),
1049 Err(ImportError::UnexpectedEof(_))
1050 ));
1051 assert!(matches!(
1052 parse_vmd_animation(&buf),
1053 Err(ImportError::UnexpectedEof(_))
1054 ));
1055 }
1056
1057 #[test]
1058 fn rejects_impossible_vmd_property_ik_count_before_allocation() {
1059 let mut buf = build_vmd_header_bytes();
1060 buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.push(1); buf.extend_from_slice(&u32::MAX.to_le_bytes()); assert!(matches!(
1071 import_vmd_motion(&buf),
1072 Err(ImportError::UnexpectedEof(_))
1073 ));
1074 assert!(matches!(
1075 parse_vmd_animation(&buf),
1076 Err(ImportError::UnexpectedEof(_))
1077 ));
1078 }
1079
1080 #[test]
1081 fn parses_vmd_header() {
1082 let header_bytes = build_vmd_header_bytes();
1083 let (header, pos) = read_header(&header_bytes).unwrap();
1084 assert_eq!(header.model_name_bytes, [0u8; 20]);
1085 assert!(pos > 0);
1086 }
1087
1088 #[test]
1089 fn rejects_bad_vmd_magic() {
1090 let mut buf = build_vmd_header_bytes();
1091 buf[0] = 0xFF;
1092 assert_eq!(read_header(&buf).unwrap_err(), ImportError::InvalidVmdMagic);
1093 }
1094
1095 #[test]
1096 fn accepts_vmd_magic_with_nonzero_padding_bytes() {
1097 let mut buf = build_vmd_header_bytes();
1098 buf[26..30].copy_from_slice(b"JKLM");
1099
1100 let (_header, pos) = read_header(&buf).unwrap();
1101 assert_eq!(pos, 50);
1102 }
1103
1104 #[test]
1105 fn parses_minimal_vmd_motion() {
1106 let mut buf = build_vmd_header_bytes();
1107
1108 buf.extend_from_slice(&0u32.to_le_bytes());
1109 buf.extend_from_slice(&0u32.to_le_bytes());
1110 buf.extend_from_slice(&0u32.to_le_bytes());
1111 buf.extend_from_slice(&0u32.to_le_bytes());
1112 buf.extend_from_slice(&0u32.to_le_bytes());
1113 buf.extend_from_slice(&0u32.to_le_bytes());
1114
1115 let result = import_vmd_motion(&buf).unwrap();
1116 assert!(result.bone_keyframes.is_empty());
1117 assert!(result.morph_keyframes.is_empty());
1118 assert!(result.property_keyframes.is_empty());
1119 }
1120
1121 #[test]
1122 fn accepts_vmd_without_optional_tail_sections() {
1123 let mut buf = build_vmd_header_bytes();
1124 buf.extend_from_slice(&0u32.to_le_bytes());
1125
1126 let result = import_vmd_motion(&buf).unwrap();
1127 assert!(result.bone_keyframes.is_empty());
1128 assert!(result.morph_keyframes.is_empty());
1129 assert!(result.property_keyframes.is_empty());
1130 assert!(result.property_ik_frames.is_empty());
1131 }
1132
1133 #[test]
1134 fn accepts_vmd_ending_after_shadow_section() {
1135 let mut buf = build_vmd_header_bytes();
1136 buf.extend_from_slice(&0u32.to_le_bytes());
1137 buf.extend_from_slice(&0u32.to_le_bytes());
1138 buf.extend_from_slice(&0u32.to_le_bytes());
1139 buf.extend_from_slice(&0u32.to_le_bytes());
1140 buf.extend_from_slice(&0u32.to_le_bytes());
1141
1142 let result = import_vmd_motion(&buf).unwrap();
1143 assert!(result.bone_keyframes.is_empty());
1144 assert!(result.morph_keyframes.is_empty());
1145 assert!(result.property_keyframes.is_empty());
1146 assert!(result.property_ik_frames.is_empty());
1147 }
1148
1149 #[test]
1150 fn rejects_partial_optional_count() {
1151 let mut buf = build_vmd_header_bytes();
1152 buf.extend_from_slice(&0u32.to_le_bytes());
1153 buf.extend_from_slice(&[1, 2, 3]);
1154
1155 assert_eq!(
1156 import_vmd_motion(&buf).unwrap_err(),
1157 ImportError::UnexpectedEof(1)
1158 );
1159 }
1160
1161 #[test]
1162 fn ignores_truncated_unused_tail_sections() {
1163 let mut buf = build_vmd_header_bytes();
1164 buf.extend_from_slice(&0u32.to_le_bytes());
1165 buf.extend_from_slice(&0u32.to_le_bytes());
1166 buf.extend_from_slice(&0u32.to_le_bytes());
1167 buf.extend_from_slice(&0u32.to_le_bytes());
1168 buf.extend_from_slice(&10u32.to_le_bytes());
1169 buf.extend_from_slice(&[0u8; 8]);
1170
1171 let result = import_vmd_motion(&buf).unwrap();
1172 assert!(result.bone_keyframes.is_empty());
1173 assert!(result.morph_keyframes.is_empty());
1174 assert!(result.property_keyframes.is_empty());
1175 assert!(result.property_ik_frames.is_empty());
1176 }
1177
1178 #[test]
1179 fn parsed_animation_ignores_implausible_optional_tail() {
1180 let mut buf = build_vmd_header_bytes();
1181 buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&300u32.to_le_bytes());
1189 buf.extend_from_slice(&[0u8; 57]);
1190
1191 let parsed = parse_vmd_animation(&buf).unwrap();
1192 assert!(parsed.camera_frames.is_empty());
1193 assert!(parsed.light_frames.is_empty());
1194 assert!(parsed.self_shadow_frames.is_empty());
1195 assert!(parsed.property_frames.is_empty());
1196 }
1197
1198 #[test]
1199 fn parses_single_bone_keyframe() {
1200 let mut buf = build_vmd_header_bytes();
1201
1202 buf.extend_from_slice(&1u32.to_le_bytes());
1203 let mut bone_name = [0u8; 15];
1204 bone_name[..4].copy_from_slice(b"Bone");
1205 buf.extend_from_slice(&bone_name);
1206 buf.extend_from_slice(&10u32.to_le_bytes());
1207 buf.extend_from_slice(&1.0f32.to_le_bytes());
1208 buf.extend_from_slice(&2.0f32.to_le_bytes());
1209 buf.extend_from_slice(&3.0f32.to_le_bytes());
1210 buf.extend_from_slice(&0.0f32.to_le_bytes());
1211 buf.extend_from_slice(&0.0f32.to_le_bytes());
1212 buf.extend_from_slice(&0.0f32.to_le_bytes());
1213 buf.extend_from_slice(&1.0f32.to_le_bytes());
1214 buf.extend_from_slice(&[20u8; 64]);
1215
1216 buf.extend_from_slice(&0u32.to_le_bytes());
1217 buf.extend_from_slice(&0u32.to_le_bytes());
1218 buf.extend_from_slice(&0u32.to_le_bytes());
1219 buf.extend_from_slice(&0u32.to_le_bytes());
1220 buf.extend_from_slice(&0u32.to_le_bytes());
1221
1222 let result = import_vmd_motion(&buf).unwrap();
1223 assert_eq!(result.bone_keyframes.len(), 1);
1224 assert_eq!(result.bone_keyframes[0].frame, 10);
1225 assert!((result.bone_keyframes[0].position.x - 1.0).abs() < 0.001);
1226 assert!((result.bone_keyframes[0].position.y - 2.0).abs() < 0.001);
1227 assert!((result.bone_keyframes[0].position.z - 3.0).abs() < 0.001);
1228 assert_eq!(&result.bone_keyframes[0].bone_name_normalized[..], b"Bone");
1229 }
1230
1231 #[test]
1232 fn exports_parsed_vmd_animation_for_roundtrip() {
1233 let mut buf = build_vmd_header_bytes();
1234
1235 buf.extend_from_slice(&1u32.to_le_bytes());
1236 let mut bone_name = [0u8; 15];
1237 bone_name[..4].copy_from_slice(b"Bone");
1238 buf.extend_from_slice(&bone_name);
1239 buf.extend_from_slice(&10u32.to_le_bytes());
1240 buf.extend_from_slice(&1.0f32.to_le_bytes());
1241 buf.extend_from_slice(&2.0f32.to_le_bytes());
1242 buf.extend_from_slice(&3.0f32.to_le_bytes());
1243 buf.extend_from_slice(&0.1f32.to_le_bytes());
1244 buf.extend_from_slice(&0.2f32.to_le_bytes());
1245 buf.extend_from_slice(&0.3f32.to_le_bytes());
1246 buf.extend_from_slice(&0.4f32.to_le_bytes());
1247 buf.extend_from_slice(&[20u8; 64]);
1248
1249 buf.extend_from_slice(&1u32.to_le_bytes());
1250 let mut morph_name = [0u8; 15];
1251 morph_name[..5].copy_from_slice(b"Smile");
1252 buf.extend_from_slice(&morph_name);
1253 buf.extend_from_slice(&11u32.to_le_bytes());
1254 buf.extend_from_slice(&0.75f32.to_le_bytes());
1255
1256 buf.extend_from_slice(&1u32.to_le_bytes());
1257 buf.extend_from_slice(&12u32.to_le_bytes());
1258 buf.extend_from_slice(&30.0f32.to_le_bytes());
1259 buf.extend_from_slice(&1.0f32.to_le_bytes());
1260 buf.extend_from_slice(&2.0f32.to_le_bytes());
1261 buf.extend_from_slice(&3.0f32.to_le_bytes());
1262 buf.extend_from_slice(&0.1f32.to_le_bytes());
1263 buf.extend_from_slice(&0.2f32.to_le_bytes());
1264 buf.extend_from_slice(&0.3f32.to_le_bytes());
1265 buf.extend_from_slice(&[30u8; 24]);
1266 buf.extend_from_slice(&45u32.to_le_bytes());
1267 buf.push(0);
1268
1269 buf.extend_from_slice(&1u32.to_le_bytes());
1270 buf.extend_from_slice(&13u32.to_le_bytes());
1271 buf.extend_from_slice(&1.0f32.to_le_bytes());
1272 buf.extend_from_slice(&0.5f32.to_le_bytes());
1273 buf.extend_from_slice(&0.25f32.to_le_bytes());
1274 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
1275 buf.extend_from_slice(&(-0.5f32).to_le_bytes());
1276 buf.extend_from_slice(&(-0.25f32).to_le_bytes());
1277
1278 buf.extend_from_slice(&1u32.to_le_bytes());
1279 buf.extend_from_slice(&14u32.to_le_bytes());
1280 buf.push(2);
1281 buf.extend_from_slice(&0.6f32.to_le_bytes());
1282
1283 buf.extend_from_slice(&1u32.to_le_bytes());
1284 buf.extend_from_slice(&15u32.to_le_bytes());
1285 buf.push(1);
1286 buf.extend_from_slice(&1u32.to_le_bytes());
1287 let mut ik_name = [0u8; 20];
1288 ik_name[..2].copy_from_slice(b"IK");
1289 buf.extend_from_slice(&ik_name);
1290 buf.push(1);
1291
1292 let parsed = parse_vmd_animation(&buf).unwrap();
1293 let exported = export_vmd_animation(&parsed);
1294 let reparsed = parse_vmd_animation(&exported).unwrap();
1295
1296 assert_vmd_roundtrip_eq(&parsed, &reparsed);
1297 }
1298
1299 fn assert_vmd_roundtrip_eq(left: &VmdParsedAnimation, right: &VmdParsedAnimation) {
1300 assert_eq!(left.metadata.model_name, right.metadata.model_name);
1301 assert_eq!(left.metadata.max_frame, right.metadata.max_frame);
1302 assert_eq!(left.bone_frames.len(), right.bone_frames.len());
1303 assert_eq!(left.morph_frames.len(), right.morph_frames.len());
1304 assert_eq!(left.camera_frames.len(), right.camera_frames.len());
1305 assert_eq!(left.light_frames.len(), right.light_frames.len());
1306 assert_eq!(
1307 left.self_shadow_frames.len(),
1308 right.self_shadow_frames.len()
1309 );
1310 assert_eq!(left.property_frames.len(), right.property_frames.len());
1311 assert_eq!(
1312 left.bone_frames[0].bone_name,
1313 right.bone_frames[0].bone_name
1314 );
1315 assert_eq!(left.bone_frames[0].frame, right.bone_frames[0].frame);
1316 assert_eq!(
1317 left.bone_frames[0].translation,
1318 right.bone_frames[0].translation
1319 );
1320 assert_eq!(left.bone_frames[0].rotation, right.bone_frames[0].rotation);
1321 assert_eq!(
1322 left.bone_frames[0].interpolation,
1323 right.bone_frames[0].interpolation
1324 );
1325 assert_eq!(
1326 left.morph_frames[0].morph_name,
1327 right.morph_frames[0].morph_name
1328 );
1329 assert_eq!(left.morph_frames[0].frame, right.morph_frames[0].frame);
1330 assert_eq!(left.morph_frames[0].weight, right.morph_frames[0].weight);
1331 assert_eq!(left.camera_frames[0].frame, right.camera_frames[0].frame);
1332 assert_eq!(
1333 left.camera_frames[0].position,
1334 right.camera_frames[0].position
1335 );
1336 assert_eq!(
1337 left.camera_frames[0].rotation,
1338 right.camera_frames[0].rotation
1339 );
1340 assert_eq!(
1341 left.camera_frames[0].interpolation,
1342 right.camera_frames[0].interpolation
1343 );
1344 assert_eq!(
1345 left.camera_frames[0].perspective,
1346 right.camera_frames[0].perspective
1347 );
1348 assert_eq!(left.light_frames[0].color, right.light_frames[0].color);
1349 assert_eq!(
1350 left.light_frames[0].direction,
1351 right.light_frames[0].direction
1352 );
1353 assert_eq!(
1354 left.self_shadow_frames[0].mode,
1355 right.self_shadow_frames[0].mode
1356 );
1357 assert_eq!(
1358 left.self_shadow_frames[0].distance,
1359 right.self_shadow_frames[0].distance
1360 );
1361 assert_eq!(
1362 left.property_frames[0].visible,
1363 right.property_frames[0].visible
1364 );
1365 assert_eq!(
1366 left.property_frames[0].ik_states[0].bone_name,
1367 right.property_frames[0].ik_states[0].bone_name
1368 );
1369 assert_eq!(
1370 left.property_frames[0].ik_states[0].enabled,
1371 right.property_frames[0].ik_states[0].enabled
1372 );
1373 }
1374
1375 #[test]
1376 fn decodes_raw_vmd_bone_interpolation_as_strided_curves() {
1377 let mut interpolation = [0u8; 64];
1378 for (index, value) in interpolation.iter_mut().enumerate().take(16) {
1379 *value = index as u8;
1380 }
1381
1382 let (position, rotation) = decode_bone_interpolation(&interpolation);
1383
1384 assert_eq!(
1385 position.x,
1386 InterpolationScalar {
1387 x1: 0,
1388 y1: 4,
1389 x2: 8,
1390 y2: 12
1391 }
1392 );
1393 assert_eq!(
1394 position.y,
1395 InterpolationScalar {
1396 x1: 1,
1397 y1: 5,
1398 x2: 9,
1399 y2: 13
1400 }
1401 );
1402 assert_eq!(
1403 position.z,
1404 InterpolationScalar {
1405 x1: 2,
1406 y1: 6,
1407 x2: 10,
1408 y2: 14
1409 }
1410 );
1411 assert_eq!(
1412 rotation,
1413 InterpolationScalar {
1414 x1: 3,
1415 y1: 7,
1416 x2: 11,
1417 y2: 15
1418 }
1419 );
1420 }
1421
1422 #[test]
1423 fn skips_camera_light_shadow_and_reads_ik_property_names() {
1424 let mut buf = build_vmd_header_bytes();
1425
1426 buf.extend_from_slice(&0u32.to_le_bytes());
1427 buf.extend_from_slice(&0u32.to_le_bytes());
1428 buf.extend_from_slice(&1u32.to_le_bytes());
1429 buf.extend_from_slice(&[0u8; 61]);
1430 buf.extend_from_slice(&1u32.to_le_bytes());
1431 buf.extend_from_slice(&[0u8; 28]);
1432 buf.extend_from_slice(&1u32.to_le_bytes());
1433 buf.extend_from_slice(&0u32.to_le_bytes());
1434 buf.push(1);
1435 buf.extend_from_slice(&0.5f32.to_le_bytes());
1436 buf.extend_from_slice(&1u32.to_le_bytes());
1437 buf.extend_from_slice(&24u32.to_le_bytes());
1438 buf.push(1);
1439 buf.extend_from_slice(&1u32.to_le_bytes());
1440 let mut ik_name = [0u8; 20];
1441 ik_name[..6].copy_from_slice(b"LeftIK");
1442 buf.extend_from_slice(&ik_name);
1443 buf.push(0);
1444
1445 let result = import_vmd_motion(&buf).unwrap();
1446 assert_eq!(result.property_keyframes.len(), 1);
1447 assert_eq!(result.property_keyframes[0].frame, 24);
1448 assert_eq!(&*result.property_keyframes[0].ik_enabled, &[0]);
1449
1450 assert_eq!(result.property_ik_frames.len(), 1);
1451 assert_eq!(result.property_ik_frames[0].frame, 24);
1452 assert_eq!(result.property_ik_frames[0].show, 1);
1453 assert_eq!(result.property_ik_frames[0].entries.len(), 1);
1454 let name_end = result.property_ik_frames[0].entries[0]
1455 .name_bytes
1456 .iter()
1457 .position(|&b| b == 0)
1458 .unwrap_or(20);
1459 assert_eq!(
1460 &result.property_ik_frames[0].entries[0].name_bytes[..name_end],
1461 b"LeftIK"
1462 );
1463 assert_eq!(result.property_ik_frames[0].entries[0].enabled, 0);
1464 assert_eq!(
1465 &result.property_ik_frames[0].entries[0].name_normalized[..],
1466 b"LeftIK"
1467 );
1468 }
1469
1470 #[test]
1471 fn builds_clip_from_bone_track() {
1472 let kfs = vec![
1473 VmdBoneKeyframeRaw {
1474 bone_mode: VmdBoneImportMode::ByName(b"BoneA".to_vec()),
1475 frame: 0,
1476 position: Vec3A::ZERO,
1477 rotation: Quat::IDENTITY,
1478 interpolation: [
1479 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20,
1480 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107,
1481 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107,
1482 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107,
1483 ],
1484 bone_name_normalized: b"BoneA".to_vec(),
1485 },
1486 VmdBoneKeyframeRaw {
1487 bone_mode: VmdBoneImportMode::ByName(b"BoneA".to_vec()),
1488 frame: 30,
1489 position: Vec3A::new(10.0, 0.0, 0.0),
1490 rotation: Quat::IDENTITY,
1491 interpolation: [
1492 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20,
1493 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107,
1494 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107,
1495 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107,
1496 ],
1497 bone_name_normalized: b"BoneA".to_vec(),
1498 },
1499 ];
1500
1501 fn lookup(name: &[u8]) -> Option<BoneIndex> {
1502 if name == b"BoneA" {
1503 Some(BoneIndex(0))
1504 } else {
1505 None
1506 }
1507 }
1508 fn morph_lookup(_name: &[u8]) -> Option<MorphIndex> {
1509 None
1510 }
1511
1512 let result = VmdImportResult {
1513 bone_keyframes: kfs,
1514 morph_keyframes: Vec::new(),
1515 property_keyframes: Vec::new(),
1516 property_ik_frames: Vec::new(),
1517 };
1518
1519 let _clip = build_clip_from_import(result, &lookup, &morph_lookup);
1520 }
1521
1522 #[test]
1523 fn build_clip_from_import_resolves_morph_by_normalized_name() {
1524 let sjis_morph = vec![0x83, 0x65, 0x83, 0x58, 0x83, 0x67];
1525 let result = VmdImportResult {
1526 bone_keyframes: Vec::new(),
1527 morph_keyframes: vec![(sjis_morph, 0, 1.0)],
1528 property_keyframes: Vec::new(),
1529 property_ik_frames: Vec::new(),
1530 };
1531
1532 fn morph_lookup(name: &[u8]) -> Option<MorphIndex> {
1533 if name == b"\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88" {
1534 Some(MorphIndex(0))
1535 } else {
1536 None
1537 }
1538 }
1539 fn bone_lookup(_name: &[u8]) -> Option<BoneIndex> {
1540 None
1541 }
1542
1543 let clip = build_clip_from_import(result, &bone_lookup, &morph_lookup);
1544 assert_eq!(clip.morph_track_count(), 1);
1545 }
1546
1547 fn ik_name_bytes(name: &str) -> [u8; 20] {
1548 let mut buf = [0u8; 20];
1549 let name_bytes = name.as_bytes();
1550 let len = name_bytes.len().min(20);
1551 buf[..len].copy_from_slice(&name_bytes[..len]);
1552 buf
1553 }
1554
1555 #[test]
1556 fn parses_property_ik_entry_names() {
1557 let mut buf = build_vmd_header_bytes();
1558
1559 buf.extend_from_slice(&0u32.to_le_bytes());
1560 buf.extend_from_slice(&0u32.to_le_bytes());
1561 buf.extend_from_slice(&0u32.to_le_bytes());
1562 buf.extend_from_slice(&0u32.to_le_bytes());
1563 buf.extend_from_slice(&0u32.to_le_bytes());
1564 buf.extend_from_slice(&1u32.to_le_bytes());
1565 buf.extend_from_slice(&10u32.to_le_bytes());
1566 buf.push(0);
1567 buf.extend_from_slice(&2u32.to_le_bytes());
1568 buf.extend_from_slice(&ik_name_bytes("LeftLegIK"));
1569 buf.push(1);
1570 buf.extend_from_slice(&ik_name_bytes("RightLegIK"));
1571 buf.push(0);
1572
1573 let result = import_vmd_motion(&buf).unwrap();
1574 assert_eq!(result.property_ik_frames.len(), 1);
1575 assert_eq!(result.property_ik_frames[0].frame, 10);
1576 assert_eq!(result.property_ik_frames[0].entries.len(), 2);
1577
1578 let name1: Vec<u8> = result.property_ik_frames[0].entries[0]
1579 .name_bytes
1580 .iter()
1581 .copied()
1582 .take_while(|&b| b != 0)
1583 .collect();
1584 assert_eq!(&name1[..], b"LeftLegIK");
1585 assert_eq!(result.property_ik_frames[0].entries[0].enabled, 1);
1586 assert_eq!(
1587 &result.property_ik_frames[0].entries[0].name_normalized[..],
1588 b"LeftLegIK"
1589 );
1590
1591 let name2: Vec<u8> = result.property_ik_frames[0].entries[1]
1592 .name_bytes
1593 .iter()
1594 .copied()
1595 .take_while(|&b| b != 0)
1596 .collect();
1597 assert_eq!(&name2[..], b"RightLegIK");
1598 assert_eq!(result.property_ik_frames[0].entries[1].enabled, 0);
1599 }
1600
1601 #[test]
1602 fn reorders_ik_enabled_to_solver_order() {
1603 let frames = vec![VmdPropertyIkFrame {
1604 frame: 0,
1605 show: 0,
1606 entries: vec![
1607 VmdIkEntry {
1608 name_bytes: {
1609 let mut buf = [0u8; 20];
1610 buf[..9].copy_from_slice(b"RightLegI");
1611 buf.to_vec()
1612 },
1613 enabled: 1,
1614 name_normalized: b"RightLegI".to_vec(),
1615 },
1616 VmdIkEntry {
1617 name_bytes: {
1618 let mut buf = [0u8; 20];
1619 buf[..8].copy_from_slice(b"LeftLegI");
1620 buf.to_vec()
1621 },
1622 enabled: 0,
1623 name_normalized: b"LeftLegI".to_vec(),
1624 },
1625 ],
1626 }];
1627
1628 fn ik_resolver(name: &[u8]) -> Option<usize> {
1629 match name {
1630 b"LeftLegI" => Some(0),
1631 b"RightLegI" => Some(1),
1632 _ => None,
1633 }
1634 }
1635
1636 let binding = build_property_binding_with_ik_resolver(&frames, &ik_resolver, 2).unwrap();
1637 let sample = binding.sample(0.0).unwrap();
1638 assert_eq!(sample, &[0, 1]);
1639 }
1640
1641 #[test]
1642 fn unknown_and_unmentioned_ik_names_keep_default_enabled_state() {
1643 let frames = vec![VmdPropertyIkFrame {
1644 frame: 5,
1645 show: 0,
1646 entries: vec![
1647 VmdIkEntry {
1648 name_bytes: {
1649 let mut buf = [0u8; 20];
1650 buf[..7].copy_from_slice(b"KnownIK");
1651 buf.to_vec()
1652 },
1653 enabled: 1,
1654 name_normalized: b"KnownIK".to_vec(),
1655 },
1656 VmdIkEntry {
1657 name_bytes: {
1658 let mut buf = [0u8; 20];
1659 buf[..9].copy_from_slice(b"UnknownIK");
1660 buf.to_vec()
1661 },
1662 enabled: 1,
1663 name_normalized: b"UnknownIK".to_vec(),
1664 },
1665 ],
1666 }];
1667
1668 fn ik_resolver(name: &[u8]) -> Option<usize> {
1669 if name == b"KnownIK" { Some(0) } else { None }
1670 }
1671
1672 let binding = build_property_binding_with_ik_resolver(&frames, &ik_resolver, 2).unwrap();
1673 let sample = binding.sample(5.0).unwrap();
1674 assert_eq!(sample, &[1, 1]);
1675 }
1676
1677 #[test]
1678 fn empty_ik_frames_returns_none() {
1679 fn ik_resolver(_name: &[u8]) -> Option<usize> {
1680 None
1681 }
1682 let binding = build_property_binding_with_ik_resolver(&[], &ik_resolver, 2);
1683 assert!(binding.is_none());
1684 }
1685
1686 #[test]
1687 fn zero_solver_count_returns_none() {
1688 let frames = vec![VmdPropertyIkFrame {
1689 frame: 0,
1690 show: 0,
1691 entries: vec![VmdIkEntry {
1692 name_bytes: ik_name_bytes("IK").to_vec(),
1693 enabled: 1,
1694 name_normalized: b"IK".to_vec(),
1695 }],
1696 }];
1697 fn ik_resolver(_name: &[u8]) -> Option<usize> {
1698 Some(0)
1699 }
1700 let binding = build_property_binding_with_ik_resolver(&frames, &ik_resolver, 0);
1701 assert!(binding.is_none());
1702 }
1703
1704 #[test]
1705 fn build_property_binding_resolves_ik_by_normalized_name() {
1706 let frames = vec![VmdPropertyIkFrame {
1707 frame: 0,
1708 show: 0,
1709 entries: vec![VmdIkEntry {
1710 name_bytes: vec![0x8D, 0xB6, 0x91, 0xAB],
1711 enabled: 1,
1712 name_normalized: vec![0xE5, 0xB7, 0xA6, 0xE8, 0xB6, 0xB3],
1713 }],
1714 }];
1715
1716 fn ik_resolver(name: &[u8]) -> Option<usize> {
1717 if name == b"\xE5\xB7\xA6\xE8\xB6\xB3" {
1718 Some(0)
1719 } else {
1720 None
1721 }
1722 }
1723
1724 let binding = build_property_binding_with_ik_resolver(&frames, &ik_resolver, 1).unwrap();
1725 let sample = binding.sample(0.0).unwrap();
1726 assert_eq!(sample, &[1]);
1727 }
1728
1729 #[test]
1730 fn build_clip_from_import_resolves_bone_by_normalized_name() {
1731 let sjis_name: Vec<u8> = vec![0x8D, 0xB6, 0x91, 0xAB];
1732 let utf8_name: Vec<u8> = vec![0xE5, 0xB7, 0xA6, 0xE8, 0xB6, 0xB3];
1733
1734 let kf = VmdBoneKeyframeRaw {
1735 bone_mode: VmdBoneImportMode::ByName(sjis_name),
1736 frame: 0,
1737 position: Vec3A::ZERO,
1738 rotation: Quat::IDENTITY,
1739 interpolation: [20u8; 64],
1740 bone_name_normalized: utf8_name.clone(),
1741 };
1742
1743 fn lookup(name: &[u8]) -> Option<BoneIndex> {
1744 if name == b"\xE5\xB7\xA6\xE8\xB6\xB3" {
1745 Some(BoneIndex(0))
1746 } else {
1747 None
1748 }
1749 }
1750 fn morph_lookup(_name: &[u8]) -> Option<MorphIndex> {
1751 None
1752 }
1753
1754 let result = VmdImportResult {
1755 bone_keyframes: vec![kf],
1756 morph_keyframes: Vec::new(),
1757 property_keyframes: Vec::new(),
1758 property_ik_frames: Vec::new(),
1759 };
1760
1761 let clip = build_clip_from_import(result, &lookup, &morph_lookup);
1762 assert_eq!(
1763 clip.bone_track_count(),
1764 1,
1765 "bone should be resolved via normalized UTF-8 name, not raw Shift-JIS bytes"
1766 );
1767 }
1768
1769 #[test]
1770 fn japanese_vmd_name_matches_pmx_utf8_name_via_normalization() {
1771 let sjis_name: &[u8] = &[0x8D, 0xB6, 0x91, 0xAB];
1772 let utf8_name: &[u8] = &[0xE5, 0xB7, 0xA6, 0xE8, 0xB6, 0xB3];
1773
1774 let mut pmx_buf = Vec::new();
1775 pmx_buf.extend_from_slice(b"PMX ");
1776 pmx_buf.extend_from_slice(&2.0f32.to_le_bytes());
1777 pmx_buf.push(8);
1778 pmx_buf.push(1);
1779 pmx_buf.push(0);
1780 pmx_buf.push(4);
1781 pmx_buf.push(1);
1782 pmx_buf.push(1);
1783 pmx_buf.push(2);
1784 pmx_buf.push(1);
1785 pmx_buf.push(1);
1786 for _ in 0..4 {
1787 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1788 }
1789 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1790 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1791 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1792 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1793 pmx_buf.extend_from_slice(&1i32.to_le_bytes());
1794 pmx_buf.extend_from_slice(&(utf8_name.len() as i32).to_le_bytes());
1795 pmx_buf.extend_from_slice(utf8_name);
1796 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1797 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1798 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1799 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1800 pmx_buf.extend_from_slice(&(-1i16).to_le_bytes());
1801 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1802 pmx_buf.extend_from_slice(&0u16.to_le_bytes());
1803 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1804 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1805 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1806 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1807
1808 let pmx = crate::pmx::import_pmx_runtime(&pmx_buf).unwrap();
1809
1810 let mut vmd_buf = build_vmd_header_bytes();
1811 vmd_buf.extend_from_slice(&1u32.to_le_bytes());
1812 let mut bone_name = [0u8; 15];
1813 bone_name[..sjis_name.len()].copy_from_slice(sjis_name);
1814 vmd_buf.extend_from_slice(&bone_name);
1815 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1816 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1817 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1818 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1819 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1820 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1821 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1822 vmd_buf.extend_from_slice(&1.0f32.to_le_bytes());
1823 vmd_buf.extend_from_slice(&[20u8; 64]);
1824 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1825 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1826 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1827 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1828 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1829
1830 let vmd = import_vmd_motion(&vmd_buf).unwrap();
1831
1832 let normalized = normalize_vmd_name(sjis_name);
1833 assert_eq!(&normalized[..], utf8_name);
1834
1835 assert!(
1836 pmx.bone_name_to_index.contains_key(&normalized),
1837 "PMX should have normalized bone name key"
1838 );
1839
1840 let clip = build_pair_clip(
1841 &vmd,
1842 &pmx.bone_name_to_index,
1843 &pmx.morph_name_to_index,
1844 &pmx.ik_solver_bone_name_to_index,
1845 pmx.model.ik_count(),
1846 );
1847
1848 assert_eq!(clip.bone_track_count(), 1);
1849 assert_eq!(clip.morph_track_count(), 0);
1850 assert!(!clip.has_property_track());
1851 }
1852
1853 #[test]
1854 fn japanese_vmd_name_matches_pmx_utf16le_name_via_decoded_key() {
1855 let sjis_name: &[u8] = &[0x8D, 0xB6, 0x91, 0xAB];
1856 let utf8_name: &[u8] = &[0xE5, 0xB7, 0xA6, 0xE8, 0xB6, 0xB3];
1857 let utf16le_name: Vec<u8> = "\u{5DE6}\u{8DB3}"
1858 .encode_utf16()
1859 .flat_map(u16::to_le_bytes)
1860 .collect();
1861
1862 let mut pmx_buf = Vec::new();
1863 pmx_buf.extend_from_slice(b"PMX ");
1864 pmx_buf.extend_from_slice(&2.0f32.to_le_bytes());
1865 pmx_buf.push(8);
1866 pmx_buf.push(0);
1867 pmx_buf.push(0);
1868 pmx_buf.push(4);
1869 pmx_buf.push(1);
1870 pmx_buf.push(1);
1871 pmx_buf.push(2);
1872 pmx_buf.push(1);
1873 pmx_buf.push(1);
1874 for _ in 0..4 {
1875 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1876 }
1877 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1878 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1879 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1880 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1881 pmx_buf.extend_from_slice(&1i32.to_le_bytes());
1882 pmx_buf.extend_from_slice(&(utf16le_name.len() as i32).to_le_bytes());
1883 pmx_buf.extend_from_slice(&utf16le_name);
1884 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1885 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1886 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1887 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1888 pmx_buf.extend_from_slice(&(-1i16).to_le_bytes());
1889 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1890 pmx_buf.extend_from_slice(&0u16.to_le_bytes());
1891 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1892 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1893 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1894 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1895
1896 let pmx = crate::pmx::import_pmx_runtime(&pmx_buf).unwrap();
1897
1898 let mut vmd_buf = build_vmd_header_bytes();
1899 vmd_buf.extend_from_slice(&1u32.to_le_bytes());
1900 let mut bone_name = [0u8; 15];
1901 bone_name[..sjis_name.len()].copy_from_slice(sjis_name);
1902 vmd_buf.extend_from_slice(&bone_name);
1903 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1904 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1905 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1906 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1907 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1908 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1909 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1910 vmd_buf.extend_from_slice(&1.0f32.to_le_bytes());
1911 vmd_buf.extend_from_slice(&[20u8; 64]);
1912
1913 let vmd = import_vmd_motion(&vmd_buf).unwrap();
1914 let normalized = normalize_vmd_name(sjis_name);
1915 assert_eq!(&normalized[..], utf8_name);
1916 assert!(
1917 pmx.bone_name_to_index.contains_key(&utf16le_name),
1918 "PMX should retain the raw UTF-16LE name key"
1919 );
1920 assert!(
1921 pmx.bone_name_to_index.contains_key(&normalized),
1922 "PMX should also index UTF-16LE names by decoded UTF-8 bytes"
1923 );
1924
1925 let clip = build_pair_clip(
1926 &vmd,
1927 &pmx.bone_name_to_index,
1928 &pmx.morph_name_to_index,
1929 &pmx.ik_solver_bone_name_to_index,
1930 pmx.model.ik_count(),
1931 );
1932
1933 assert_eq!(clip.bone_track_count(), 1);
1934 }
1935
1936 #[test]
1937 fn build_pair_clip_default_includes_property_ik() {
1938 let result = VmdImportResult {
1940 bone_keyframes: Vec::new(),
1941 morph_keyframes: Vec::new(),
1942 property_keyframes: Vec::new(),
1943 property_ik_frames: vec![VmdPropertyIkFrame {
1944 frame: 0,
1945 show: 0,
1946 entries: vec![VmdIkEntry {
1947 name_bytes: ik_name_bytes("LeftLegIK").to_vec(),
1948 enabled: 0,
1949 name_normalized: b"LeftLegIK".to_vec(),
1950 }],
1951 }],
1952 };
1953
1954 let bone_name_to_index: std::collections::HashMap<Vec<u8>, BoneIndex> =
1955 std::collections::HashMap::new();
1956 let morph_name_to_index: std::collections::HashMap<Vec<u8>, MorphIndex> =
1957 std::collections::HashMap::new();
1958 let mut ik_solver_bone_name_to_index: std::collections::HashMap<Vec<u8>, usize> =
1959 std::collections::HashMap::new();
1960 ik_solver_bone_name_to_index.insert(b"LeftLegIK".to_vec(), 0);
1961
1962 let clip = build_pair_clip(
1963 &result,
1964 &bone_name_to_index,
1965 &morph_name_to_index,
1966 &ik_solver_bone_name_to_index,
1967 2, );
1969
1970 assert!(
1971 clip.has_property_track(),
1972 "default build_pair_clip should include property IK track"
1973 );
1974 }
1975
1976 #[test]
1977 fn build_pair_clip_with_options_omits_property_ik() {
1978 let result = VmdImportResult {
1980 bone_keyframes: Vec::new(),
1981 morph_keyframes: Vec::new(),
1982 property_keyframes: Vec::new(),
1983 property_ik_frames: vec![VmdPropertyIkFrame {
1984 frame: 5,
1985 show: 0,
1986 entries: vec![VmdIkEntry {
1987 name_bytes: ik_name_bytes("RightLegIK").to_vec(),
1988 enabled: 0,
1989 name_normalized: b"RightLegIK".to_vec(),
1990 }],
1991 }],
1992 };
1993
1994 let bone_name_to_index: std::collections::HashMap<Vec<u8>, BoneIndex> =
1995 std::collections::HashMap::new();
1996 let morph_name_to_index: std::collections::HashMap<Vec<u8>, MorphIndex> =
1997 std::collections::HashMap::new();
1998 let mut ik_solver_bone_name_to_index: std::collections::HashMap<Vec<u8>, usize> =
1999 std::collections::HashMap::new();
2000 ik_solver_bone_name_to_index.insert(b"RightLegIK".to_vec(), 1);
2001
2002 let clip = build_pair_clip_with_options(
2003 &result,
2004 &bone_name_to_index,
2005 &morph_name_to_index,
2006 &ik_solver_bone_name_to_index,
2007 2,
2008 VmdClipBuildOptions {
2009 honor_property_ik: false,
2010 },
2011 );
2012
2013 assert!(
2014 !clip.has_property_track(),
2015 "build_pair_clip_with_options(honor_property_ik: false) should omit property IK track"
2016 );
2017 }
2018
2019 type SyntheticBoneFrame<'a> = (&'a str, u32, [f32; 3], [f32; 4], [u8; 64]);
2025 type SyntheticMorphFrame<'a> = (&'a str, u32, f32);
2026 type SyntheticPropertyFrame<'a> = (&'a [(&'a str, bool)], u32, bool);
2027
2028 fn make_vmd_bytes(
2029 model_name_ascii: &str,
2030 bone_frames: &[SyntheticBoneFrame<'_>],
2031 morph_frames: &[SyntheticMorphFrame<'_>],
2032 property_frames: &[SyntheticPropertyFrame<'_>],
2033 ) -> Vec<u8> {
2034 let mut v = Vec::new();
2035 v.extend_from_slice(&VMD_MAGIC);
2036
2037 let mut name_buf = [0u8; 20];
2038 let nb = model_name_ascii.as_bytes();
2039 name_buf[..nb.len().min(20)].copy_from_slice(&nb[..nb.len().min(20)]);
2040 v.extend_from_slice(&name_buf);
2041
2042 v.extend_from_slice(&(bone_frames.len() as u32).to_le_bytes());
2043 for (name, frame, trans, rot, interp) in bone_frames {
2044 let mut nb = [0u8; 15];
2045 let b = name.as_bytes();
2046 nb[..b.len().min(15)].copy_from_slice(&b[..b.len().min(15)]);
2047 v.extend_from_slice(&nb);
2048 v.extend_from_slice(&frame.to_le_bytes());
2049 for &x in trans {
2050 v.extend_from_slice(&x.to_le_bytes());
2051 }
2052 for &x in rot {
2053 v.extend_from_slice(&x.to_le_bytes());
2054 }
2055 v.extend_from_slice(interp);
2056 }
2057
2058 v.extend_from_slice(&(morph_frames.len() as u32).to_le_bytes());
2059 for (name, frame, weight) in morph_frames {
2060 let mut nb = [0u8; 15];
2061 let b = name.as_bytes();
2062 nb[..b.len().min(15)].copy_from_slice(&b[..b.len().min(15)]);
2063 v.extend_from_slice(&nb);
2064 v.extend_from_slice(&frame.to_le_bytes());
2065 v.extend_from_slice(&weight.to_le_bytes());
2066 }
2067
2068 v.extend_from_slice(&0u32.to_le_bytes());
2070 v.extend_from_slice(&0u32.to_le_bytes());
2071 v.extend_from_slice(&0u32.to_le_bytes());
2072
2073 v.extend_from_slice(&(property_frames.len() as u32).to_le_bytes());
2074 for (ik_states, frame, visible) in property_frames {
2075 v.extend_from_slice(&frame.to_le_bytes());
2076 v.push(u8::from(*visible));
2077 v.extend_from_slice(&(ik_states.len() as u32).to_le_bytes());
2078 for (ik_name, enabled) in *ik_states {
2079 let mut nb = [0u8; 20];
2080 let b = ik_name.as_bytes();
2081 nb[..b.len().min(20)].copy_from_slice(&b[..b.len().min(20)]);
2082 v.extend_from_slice(&nb);
2083 v.push(u8::from(*enabled));
2084 }
2085 }
2086 v
2087 }
2088
2089 fn json_keys(value: &serde_json::Value) -> Vec<String> {
2090 let mut keys = value
2091 .as_object()
2092 .unwrap()
2093 .keys()
2094 .map(ToOwned::to_owned)
2095 .collect::<Vec<_>>();
2096 keys.sort();
2097 keys
2098 }
2099
2100 #[test]
2101 fn vmd_animation_json_top_level_schema_is_stable() {
2102 let vmd = make_vmd_bytes("miku", &[], &[], &[]);
2103 let parsed = parse_vmd_animation(&vmd).unwrap();
2104 let keys = json_keys(&serde_json::to_value(&parsed).unwrap());
2105
2106 assert_eq!(
2107 keys,
2108 vec![
2109 "boneFrames",
2110 "cameraFrames",
2111 "kind",
2112 "lightFrames",
2113 "metadata",
2114 "morphFrames",
2115 "propertyFrames",
2116 "selfShadowFrames",
2117 ]
2118 );
2119 }
2120
2121 #[test]
2122 fn roundtrip_bone_frame_parse_export_parse() {
2123 let interp = [20u8; 64];
2124 let vmd = make_vmd_bytes(
2125 "miku",
2126 &[("arm", 30, [1.0, 0.5, -0.5], [0.0, 0.0, 0.0, 1.0], interp)],
2127 &[],
2128 &[],
2129 );
2130 let parsed = parse_vmd_animation(&vmd).unwrap();
2131 assert_eq!(parsed.bone_frames.len(), 1);
2132 assert_eq!(parsed.bone_frames[0].bone_name, "arm");
2133 assert_eq!(parsed.bone_frames[0].frame, 30);
2134 assert_eq!(parsed.bone_frames[0].translation, [1.0, 0.5, -0.5]);
2135 assert_eq!(parsed.bone_frames[0].rotation, [0.0, 0.0, 0.0, 1.0]);
2136 assert_eq!(parsed.bone_frames[0].interpolation, vec![20u8; 64]);
2137
2138 let exported = export_vmd_animation(&parsed);
2139 let reparsed = parse_vmd_animation(&exported).unwrap();
2140 assert_eq!(reparsed.metadata.model_name, parsed.metadata.model_name);
2141 assert_eq!(reparsed.metadata.max_frame, 30);
2142 assert_eq!(reparsed.bone_frames.len(), 1);
2143 assert_eq!(reparsed.bone_frames[0].bone_name, "arm");
2144 assert_eq!(reparsed.bone_frames[0].frame, 30);
2145 assert_eq!(reparsed.bone_frames[0].translation, [1.0, 0.5, -0.5]);
2146 assert_eq!(reparsed.bone_frames[0].rotation, [0.0, 0.0, 0.0, 1.0]);
2147 assert_eq!(reparsed.bone_frames[0].interpolation, vec![20u8; 64]);
2148 }
2149
2150 #[test]
2151 fn roundtrip_morph_frame_parse_export_parse() {
2152 let vmd = make_vmd_bytes("miku", &[], &[("blink", 15, 0.75)], &[]);
2153 let parsed = parse_vmd_animation(&vmd).unwrap();
2154 let exported = export_vmd_animation(&parsed);
2155 let reparsed = parse_vmd_animation(&exported).unwrap();
2156 assert_eq!(reparsed.morph_frames.len(), 1);
2157 assert_eq!(reparsed.morph_frames[0].morph_name, "blink");
2158 assert_eq!(reparsed.morph_frames[0].frame, 15);
2159 assert!((reparsed.morph_frames[0].weight - 0.75f32).abs() < 1e-6);
2160 }
2161
2162 #[test]
2163 fn roundtrip_property_ik_frames_parse_export_parse() {
2164 let vmd = make_vmd_bytes(
2165 "camera",
2166 &[],
2167 &[],
2168 &[(&[("leftIK", true), ("rightIK", false)], 20, true)],
2169 );
2170 let parsed = parse_vmd_animation(&vmd).unwrap();
2171 let exported = export_vmd_animation(&parsed);
2172 let reparsed = parse_vmd_animation(&exported).unwrap();
2173 assert_eq!(reparsed.property_frames.len(), 1);
2174 assert_eq!(reparsed.property_frames[0].frame, 20);
2175 assert!(reparsed.property_frames[0].visible);
2176 assert_eq!(reparsed.property_frames[0].ik_states.len(), 2);
2177 assert_eq!(reparsed.property_frames[0].ik_states[0].bone_name, "leftIK");
2178 assert!(reparsed.property_frames[0].ik_states[0].enabled);
2179 assert_eq!(
2180 reparsed.property_frames[0].ik_states[1].bone_name,
2181 "rightIK"
2182 );
2183 assert!(!reparsed.property_frames[0].ik_states[1].enabled);
2184 }
2185
2186 #[test]
2187 fn roundtrip_json_dto_bone_and_morph() {
2188 let interp = [20u8; 64];
2189 let vmd = make_vmd_bytes(
2190 "miku",
2191 &[("spine", 60, [0.0, 1.0, 0.0], [0.1, 0.2, 0.3, 0.9], interp)],
2192 &[("mouth", 30, 0.8)],
2193 &[],
2194 );
2195 let parsed = parse_vmd_animation(&vmd).unwrap();
2196 let json = serde_json::to_string(&parsed).unwrap();
2197 let from_json: VmdParsedAnimation = serde_json::from_str(&json).unwrap();
2198 let exported = export_vmd_animation(&from_json);
2199 let reparsed = parse_vmd_animation(&exported).unwrap();
2200
2201 assert_eq!(
2202 reparsed.bone_frames[0].bone_name,
2203 parsed.bone_frames[0].bone_name
2204 );
2205 assert_eq!(reparsed.bone_frames[0].frame, parsed.bone_frames[0].frame);
2206 assert_eq!(
2207 reparsed.bone_frames[0].translation,
2208 parsed.bone_frames[0].translation
2209 );
2210 assert_eq!(
2211 reparsed.bone_frames[0].rotation,
2212 parsed.bone_frames[0].rotation
2213 );
2214 assert_eq!(
2215 reparsed.morph_frames[0].morph_name,
2216 parsed.morph_frames[0].morph_name
2217 );
2218 assert_eq!(reparsed.morph_frames[0].frame, parsed.morph_frames[0].frame);
2219 assert!((reparsed.morph_frames[0].weight - parsed.morph_frames[0].weight).abs() < 1e-6);
2220 }
2221
2222 fn expected_sjis_name_bytes(value: &str, len: usize) -> Vec<u8> {
2223 let (encoded, _, _) = encoding_rs::SHIFT_JIS.encode(value);
2224 encoded.as_ref()[..encoded.len().min(len)].to_vec()
2225 }
2226
2227 #[test]
2228 fn export_json_dto_encodes_shift_jis_when_raw_name_bytes_are_missing() {
2229 let animation = vmd_parsed_animation(
2230 "初音ミク".to_owned(),
2231 Vec::new(),
2232 30,
2233 VmdParsedSections {
2234 bone_frames: vec![VmdParsedBoneFrame {
2235 bone_name: "左足".to_owned(),
2236 bone_name_bytes: Vec::new(),
2237 frame: 10,
2238 translation: [1.0, 2.0, 3.0],
2239 rotation: [0.0, 0.0, 0.0, 1.0],
2240 interpolation: vec![20; 64],
2241 }],
2242 morph_frames: vec![VmdParsedMorphFrame {
2243 morph_name: "笑い".to_owned(),
2244 morph_name_bytes: Vec::new(),
2245 frame: 20,
2246 weight: 0.75,
2247 }],
2248 camera_frames: Vec::new(),
2249 light_frames: Vec::new(),
2250 self_shadow_frames: Vec::new(),
2251 property_frames: vec![VmdParsedPropertyFrame {
2252 frame: 30,
2253 visible: true,
2254 ik_states: vec![VmdParsedIkState {
2255 bone_name: "右足IK".to_owned(),
2256 bone_name_bytes: Vec::new(),
2257 enabled: false,
2258 }],
2259 }],
2260 },
2261 );
2262 let json = serde_json::to_string(&animation).unwrap();
2263 assert!(!json.contains("modelNameBytes"));
2264 assert!(!json.contains("boneNameBytes"));
2265 assert!(!json.contains("morphNameBytes"));
2266
2267 let from_json: VmdParsedAnimation = serde_json::from_str(&json).unwrap();
2268 let exported = export_vmd_animation(&from_json);
2269 let reparsed = parse_vmd_animation(&exported).unwrap();
2270
2271 assert_eq!(reparsed.metadata.model_name, "初音ミク");
2272 assert!(!reparsed.metadata.model_name_bytes.is_empty());
2273 assert_eq!(
2274 reparsed.metadata.model_name_bytes,
2275 expected_sjis_name_bytes("初音ミク", 20)
2276 );
2277 assert_eq!(reparsed.bone_frames[0].bone_name, "左足");
2278 assert!(!reparsed.bone_frames[0].bone_name_bytes.is_empty());
2279 assert_eq!(
2280 reparsed.bone_frames[0].bone_name_bytes,
2281 expected_sjis_name_bytes("左足", 15)
2282 );
2283 assert_eq!(reparsed.morph_frames[0].morph_name, "笑い");
2284 assert!(!reparsed.morph_frames[0].morph_name_bytes.is_empty());
2285 assert_eq!(
2286 reparsed.morph_frames[0].morph_name_bytes,
2287 expected_sjis_name_bytes("笑い", 15)
2288 );
2289 assert_eq!(reparsed.property_frames[0].ik_states[0].bone_name, "右足IK");
2290 assert!(
2291 !reparsed.property_frames[0].ik_states[0]
2292 .bone_name_bytes
2293 .is_empty()
2294 );
2295 assert_eq!(
2296 reparsed.property_frames[0].ik_states[0].bone_name_bytes,
2297 expected_sjis_name_bytes("右足IK", 20)
2298 );
2299 assert!(!reparsed.property_frames[0].ik_states[0].enabled);
2300 }
2301
2302 #[test]
2303 fn export_json_dto_uses_encoding_rs_replacement_for_non_shift_jis_names() {
2304 let animation = vmd_parsed_animation(
2305 "miku".to_owned(),
2306 Vec::new(),
2307 1,
2308 VmdParsedSections {
2309 bone_frames: vec![VmdParsedBoneFrame {
2310 bone_name: "左足🧪".to_owned(),
2311 bone_name_bytes: Vec::new(),
2312 frame: 1,
2313 translation: [0.0, 0.0, 0.0],
2314 rotation: [0.0, 0.0, 0.0, 1.0],
2315 interpolation: vec![20; 64],
2316 }],
2317 morph_frames: Vec::new(),
2318 camera_frames: Vec::new(),
2319 light_frames: Vec::new(),
2320 self_shadow_frames: Vec::new(),
2321 property_frames: Vec::new(),
2322 },
2323 );
2324 let json = serde_json::to_string(&animation).unwrap();
2325 let from_json: VmdParsedAnimation = serde_json::from_str(&json).unwrap();
2326 let exported = export_vmd_animation(&from_json);
2327 let reparsed = parse_vmd_animation(&exported).unwrap();
2328
2329 assert_eq!(
2330 reparsed.bone_frames[0].bone_name_bytes,
2331 expected_sjis_name_bytes("左足🧪", 15)
2332 );
2333 assert_eq!(
2334 reparsed.bone_frames[0].bone_name,
2335 encoding_rs::SHIFT_JIS
2336 .decode(&expected_sjis_name_bytes("左足🧪", 15))
2337 .0
2338 .trim()
2339 );
2340 }
2341}