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, Copy, PartialEq)]
395pub struct VmdCameraState {
396 pub distance: f32,
397 pub position: [f32; 3],
398 pub rotation: [f32; 3],
399 pub fov: f32,
400 pub perspective: bool,
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
404#[serde(rename_all = "camelCase")]
405pub struct VmdParsedLightFrame {
406 pub frame: u32,
407 pub color: [f32; 3],
408 pub direction: [f32; 3],
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize)]
412#[serde(rename_all = "camelCase")]
413pub struct VmdParsedSelfShadowFrame {
414 pub frame: u32,
415 pub mode: u8,
416 pub distance: f32,
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize)]
420#[serde(rename_all = "camelCase")]
421pub struct VmdParsedPropertyFrame {
422 pub frame: u32,
423 pub visible: bool,
424 pub ik_states: Vec<VmdParsedIkState>,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
428#[serde(rename_all = "camelCase")]
429pub struct VmdParsedIkState {
430 pub bone_name: String,
431 #[serde(default, skip_serializing_if = "Vec::is_empty")]
432 pub bone_name_bytes: Vec<u8>,
433 pub enabled: bool,
434}
435
436pub fn parse_vmd_animation(data: &[u8]) -> Result<VmdParsedAnimation, ImportError> {
437 let (_header, pos) = read_header(data)?;
438 let mut r = Reader { data, pos };
439 let model_name = decode_sjis_fixed(&_header.model_name_bytes);
440 let model_name_bytes = trim_fixed_bytes(&_header.model_name_bytes).to_vec();
441 let mut max_frame = 0u32;
442
443 let bone_count = r.read_record_count(111)?;
444 let mut bone_frames = Vec::with_capacity(bone_count);
445 for _ in 0..bone_count {
446 let bone_name_bytes = r.read_slice(15)?;
447 let bone_name = decode_sjis_fixed(bone_name_bytes);
448 let frame = r.read_u32_le()?;
449 max_frame = max_frame.max(frame);
450 let position = r.read_vec3()?;
451 let rotation = r.read_quat()?;
452 let interpolation: [u8; 64] = r.read_slice(64)?.try_into().unwrap();
453 bone_frames.push(VmdParsedBoneFrame {
454 bone_name,
455 bone_name_bytes: trim_fixed_bytes(bone_name_bytes).to_vec(),
456 frame,
457 translation: [position.x, position.y, position.z],
458 rotation: [rotation.x, rotation.y, rotation.z, rotation.w],
459 interpolation: interpolation.to_vec(),
460 });
461 }
462
463 let Some(morph_count) = r.read_optional_u32_le()? else {
464 return Ok(vmd_parsed_animation(
465 model_name,
466 model_name_bytes,
467 max_frame,
468 VmdParsedSections {
469 bone_frames,
470 morph_frames: Vec::new(),
471 camera_frames: Vec::new(),
472 light_frames: Vec::new(),
473 self_shadow_frames: Vec::new(),
474 property_frames: Vec::new(),
475 },
476 ));
477 };
478 let morph_count = morph_count as usize;
479 r.require_record_bytes(morph_count, 23)?;
480 let mut morph_frames = Vec::with_capacity(morph_count);
481 for _ in 0..morph_count {
482 let morph_name_bytes = r.read_slice(15)?;
483 let morph_name = decode_sjis_fixed(morph_name_bytes);
484 let frame = r.read_u32_le()?;
485 max_frame = max_frame.max(frame);
486 morph_frames.push(VmdParsedMorphFrame {
487 morph_name,
488 morph_name_bytes: trim_fixed_bytes(morph_name_bytes).to_vec(),
489 frame,
490 weight: r.read_f32_le()?,
491 });
492 }
493
494 let camera_frames = read_parsed_camera_frames(&mut r, &mut max_frame)?;
495 let light_frames = read_parsed_light_frames(&mut r, &mut max_frame)?;
496 let self_shadow_frames = read_parsed_self_shadow_frames(&mut r, &mut max_frame)?;
497 let property_frames = read_parsed_property_frames(&mut r, &mut max_frame)?;
498
499 Ok(vmd_parsed_animation(
500 model_name,
501 model_name_bytes,
502 max_frame,
503 VmdParsedSections {
504 bone_frames,
505 morph_frames,
506 camera_frames,
507 light_frames,
508 self_shadow_frames,
509 property_frames,
510 },
511 ))
512}
513
514fn default_vmd_kind() -> &'static str {
515 "vmd"
516}
517
518fn default_vmd_format() -> &'static str {
519 "vmd"
520}
521
522pub fn export_vmd_animation(animation: &VmdParsedAnimation) -> Vec<u8> {
523 let mut out = Vec::new();
524 out.extend_from_slice(&VMD_MAGIC);
525 write_fixed_name_bytes(
526 &mut out,
527 &animation.metadata.model_name,
528 &animation.metadata.model_name_bytes,
529 20,
530 );
531
532 write_u32(&mut out, animation.bone_frames.len() as u32);
533 for frame in &animation.bone_frames {
534 write_fixed_name_bytes(&mut out, &frame.bone_name, &frame.bone_name_bytes, 15);
535 write_u32(&mut out, frame.frame);
536 write_f32(&mut out, frame.translation[0]);
537 write_f32(&mut out, frame.translation[1]);
538 write_f32(&mut out, frame.translation[2]);
539 write_f32(&mut out, frame.rotation[0]);
540 write_f32(&mut out, frame.rotation[1]);
541 write_f32(&mut out, frame.rotation[2]);
542 write_f32(&mut out, frame.rotation[3]);
543 write_fixed_bytes(&mut out, &frame.interpolation, 64);
544 }
545
546 write_u32(&mut out, animation.morph_frames.len() as u32);
547 for frame in &animation.morph_frames {
548 write_fixed_name_bytes(&mut out, &frame.morph_name, &frame.morph_name_bytes, 15);
549 write_u32(&mut out, frame.frame);
550 write_f32(&mut out, frame.weight);
551 }
552
553 write_u32(&mut out, animation.camera_frames.len() as u32);
554 for frame in &animation.camera_frames {
555 write_u32(&mut out, frame.frame);
556 write_f32(&mut out, frame.distance);
557 write_f32(&mut out, frame.position[0]);
558 write_f32(&mut out, frame.position[1]);
559 write_f32(&mut out, frame.position[2]);
560 write_f32(&mut out, frame.rotation[0]);
561 write_f32(&mut out, frame.rotation[1]);
562 write_f32(&mut out, frame.rotation[2]);
563 out.extend_from_slice(&frame.interpolation);
564 write_u32(&mut out, frame.fov);
565 out.push(if frame.perspective { 0 } else { 1 });
566 }
567
568 write_u32(&mut out, animation.light_frames.len() as u32);
569 for frame in &animation.light_frames {
570 write_u32(&mut out, frame.frame);
571 write_f32(&mut out, frame.color[0]);
572 write_f32(&mut out, frame.color[1]);
573 write_f32(&mut out, frame.color[2]);
574 write_f32(&mut out, frame.direction[0]);
575 write_f32(&mut out, frame.direction[1]);
576 write_f32(&mut out, frame.direction[2]);
577 }
578
579 write_u32(&mut out, animation.self_shadow_frames.len() as u32);
580 for frame in &animation.self_shadow_frames {
581 write_u32(&mut out, frame.frame);
582 out.push(frame.mode);
583 write_f32(&mut out, frame.distance);
584 }
585
586 write_u32(&mut out, animation.property_frames.len() as u32);
587 for frame in &animation.property_frames {
588 write_u32(&mut out, frame.frame);
589 out.push(u8::from(frame.visible));
590 write_u32(&mut out, frame.ik_states.len() as u32);
591 for state in &frame.ik_states {
592 write_fixed_name_bytes(&mut out, &state.bone_name, &state.bone_name_bytes, 20);
593 out.push(u8::from(state.enabled));
594 }
595 }
596
597 out
598}
599
600struct VmdParsedSections {
601 bone_frames: Vec<VmdParsedBoneFrame>,
602 morph_frames: Vec<VmdParsedMorphFrame>,
603 camera_frames: Vec<VmdParsedCameraFrame>,
604 light_frames: Vec<VmdParsedLightFrame>,
605 self_shadow_frames: Vec<VmdParsedSelfShadowFrame>,
606 property_frames: Vec<VmdParsedPropertyFrame>,
607}
608
609fn vmd_parsed_animation(
610 model_name: String,
611 model_name_bytes: Vec<u8>,
612 max_frame: u32,
613 sections: VmdParsedSections,
614) -> VmdParsedAnimation {
615 VmdParsedAnimation {
616 kind: "vmd",
617 metadata: VmdParsedMetadata {
618 format: "vmd",
619 model_name,
620 model_name_bytes,
621 counts: VmdParsedCounts {
622 bones: sections.bone_frames.len(),
623 morphs: sections.morph_frames.len(),
624 cameras: sections.camera_frames.len(),
625 lights: sections.light_frames.len(),
626 self_shadows: sections.self_shadow_frames.len(),
627 properties: sections.property_frames.len(),
628 },
629 max_frame,
630 },
631 bone_frames: sections.bone_frames,
632 morph_frames: sections.morph_frames,
633 camera_frames: sections.camera_frames,
634 light_frames: sections.light_frames,
635 self_shadow_frames: sections.self_shadow_frames,
636 property_frames: sections.property_frames,
637 }
638}
639
640fn read_parsed_camera_frames(
641 r: &mut Reader<'_>,
642 max_frame: &mut u32,
643) -> Result<Vec<VmdParsedCameraFrame>, ImportError> {
644 let Some(count) = r.read_optional_record_count(61)? else {
645 return Ok(Vec::new());
646 };
647 let mut frames = Vec::with_capacity(count);
648 for _ in 0..count {
649 let frame = r.read_u32_le()?;
650 *max_frame = (*max_frame).max(frame);
651 frames.push(VmdParsedCameraFrame {
652 frame,
653 distance: r.read_f32_le()?,
654 position: {
655 let p = r.read_vec3()?;
656 [p.x, p.y, p.z]
657 },
658 rotation: {
659 let p = r.read_vec3()?;
660 [p.x, p.y, p.z]
661 },
662 interpolation: r.read_slice(24)?.try_into().unwrap(),
663 fov: r.read_u32_le()?,
664 perspective: r.read_u8()? == 0,
665 });
666 }
667 Ok(frames)
668}
669
670fn read_parsed_light_frames(
671 r: &mut Reader<'_>,
672 max_frame: &mut u32,
673) -> Result<Vec<VmdParsedLightFrame>, ImportError> {
674 let Some(count) = r.read_optional_record_count(28)? else {
675 return Ok(Vec::new());
676 };
677 let mut frames = Vec::with_capacity(count);
678 for _ in 0..count {
679 let frame = r.read_u32_le()?;
680 *max_frame = (*max_frame).max(frame);
681 let color = r.read_vec3()?;
682 let direction = r.read_vec3()?;
683 frames.push(VmdParsedLightFrame {
684 frame,
685 color: [color.x, color.y, color.z],
686 direction: [direction.x, direction.y, direction.z],
687 });
688 }
689 Ok(frames)
690}
691
692fn read_parsed_self_shadow_frames(
693 r: &mut Reader<'_>,
694 max_frame: &mut u32,
695) -> Result<Vec<VmdParsedSelfShadowFrame>, ImportError> {
696 let Some(count) = r.read_optional_record_count(9)? else {
697 return Ok(Vec::new());
698 };
699 let mut frames = Vec::with_capacity(count);
700 for _ in 0..count {
701 let frame = r.read_u32_le()?;
702 *max_frame = (*max_frame).max(frame);
703 frames.push(VmdParsedSelfShadowFrame {
704 frame,
705 mode: r.read_u8()?,
706 distance: r.read_f32_le()?,
707 });
708 }
709 Ok(frames)
710}
711
712fn read_parsed_property_frames(
713 r: &mut Reader<'_>,
714 max_frame: &mut u32,
715) -> Result<Vec<VmdParsedPropertyFrame>, ImportError> {
716 let Some(count) = r.read_optional_u32_le()? else {
717 return Ok(Vec::new());
718 };
719 let count = count as usize;
720 r.require_record_bytes(count, 9)?;
721 let mut frames = Vec::with_capacity(count);
722 for _ in 0..count {
723 let frame = r.read_u32_le()?;
724 *max_frame = (*max_frame).max(frame);
725 let visible = r.read_u8()? != 0;
726 let ik_count = r.read_u32_le()? as usize;
727 r.require_record_bytes(ik_count, 21)?;
728 let mut ik_states = Vec::with_capacity(ik_count);
729 for _ in 0..ik_count {
730 let bone_name_bytes = r.read_slice(20)?;
731 ik_states.push(VmdParsedIkState {
732 bone_name: decode_sjis_fixed(bone_name_bytes),
733 bone_name_bytes: trim_fixed_bytes(bone_name_bytes).to_vec(),
734 enabled: r.read_u8()? != 0,
735 });
736 }
737 frames.push(VmdParsedPropertyFrame {
738 frame,
739 visible,
740 ik_states,
741 });
742 }
743 Ok(frames)
744}
745
746pub fn sample_vmd_camera_frames(
747 frames: &[VmdParsedCameraFrame],
748 frame: f32,
749) -> Option<VmdCameraState> {
750 if frames.is_empty() {
751 return None;
752 }
753
754 let mut sorted: Vec<&VmdParsedCameraFrame> = frames.iter().collect();
755 sorted.sort_by_key(|keyframe| keyframe.frame);
756
757 let mut index = 0usize;
758 while index + 1 < sorted.len() && sorted[index + 1].frame as f32 <= frame {
759 index += 1;
760 }
761
762 let previous = sorted[index];
763 let next = sorted.get(index + 1).copied().unwrap_or(previous);
764 let t = interpolation_ratio(previous.frame, next.frame, frame);
765 let interpolation = decode_camera_interpolation(&next.interpolation);
766
767 let distance_t = interpolation.distance.evaluate(t);
768 let position_x_t = interpolation.position.x.evaluate(t);
769 let position_y_t = interpolation.position.y.evaluate(t);
770 let position_z_t = interpolation.position.z.evaluate(t);
771 let rotation_t = interpolation.rotation.evaluate(t);
772 let fov_t = interpolation.fov.evaluate(t);
773
774 Some(VmdCameraState {
775 distance: lerp(previous.distance, next.distance, distance_t),
776 position: [
777 lerp(previous.position[0], next.position[0], position_x_t),
778 lerp(previous.position[1], next.position[1], position_y_t),
779 lerp(previous.position[2], next.position[2], position_z_t),
780 ],
781 rotation: [
782 lerp(previous.rotation[0], next.rotation[0], rotation_t),
783 lerp(previous.rotation[1], next.rotation[1], rotation_t),
784 lerp(previous.rotation[2], next.rotation[2], rotation_t),
785 ],
786 fov: lerp(previous.fov as f32, next.fov as f32, fov_t),
787 perspective: if t < 1.0 {
788 previous.perspective
789 } else {
790 next.perspective
791 },
792 })
793}
794
795struct CameraInterpolation {
796 position: InterpolationVector3,
797 rotation: InterpolationScalar,
798 distance: InterpolationScalar,
799 fov: InterpolationScalar,
800}
801
802fn decode_camera_interpolation(interpolation: &[u8; 24]) -> CameraInterpolation {
803 CameraInterpolation {
804 position: InterpolationVector3 {
805 x: decode_camera_interpolation_scalar(interpolation, 0),
806 y: decode_camera_interpolation_scalar(interpolation, 1),
807 z: decode_camera_interpolation_scalar(interpolation, 2),
808 },
809 rotation: decode_camera_interpolation_scalar(interpolation, 3),
810 distance: decode_camera_interpolation_scalar(interpolation, 4),
811 fov: decode_camera_interpolation_scalar(interpolation, 5),
812 }
813}
814
815fn decode_camera_interpolation_scalar(
816 interpolation: &[u8; 24],
817 channel: usize,
818) -> InterpolationScalar {
819 decode_interpolation_scalar([
820 interpolation[channel],
821 interpolation[channel + 6],
822 interpolation[channel + 12],
823 interpolation[channel + 18],
824 ])
825}
826
827fn interpolation_ratio(previous_frame: u32, next_frame: u32, frame: f32) -> f32 {
828 if next_frame <= previous_frame {
829 return 0.0;
830 }
831 ((frame - previous_frame as f32) / (next_frame - previous_frame) as f32).clamp(0.0, 1.0)
832}
833
834fn lerp(a: f32, b: f32, t: f32) -> f32 {
835 a + (b - a) * t
836}
837
838fn decode_sjis_fixed(bytes: &[u8]) -> String {
839 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
840 let (decoded, _, _) = encoding_rs::SHIFT_JIS.decode(&bytes[..end]);
841 decoded.trim().to_owned()
842}
843
844fn trim_fixed_bytes(bytes: &[u8]) -> &[u8] {
845 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
846 &bytes[..end]
847}
848
849fn write_u32(out: &mut Vec<u8>, value: u32) {
850 out.extend_from_slice(&value.to_le_bytes());
851}
852
853fn write_f32(out: &mut Vec<u8>, value: f32) {
854 out.extend_from_slice(&value.to_le_bytes());
855}
856
857fn write_fixed_name_bytes(out: &mut Vec<u8>, value: &str, raw_bytes: &[u8], len: usize) {
858 if raw_bytes.is_empty() {
859 let (encoded, _, _) = encoding_rs::SHIFT_JIS.encode(value);
860 write_fixed_bytes(out, encoded.as_ref(), len);
861 } else {
862 write_fixed_bytes(out, raw_bytes, len);
863 }
864}
865
866fn write_fixed_bytes(out: &mut Vec<u8>, value: &[u8], len: usize) {
867 let copied = value.len().min(len);
868 out.extend_from_slice(&value[..copied]);
869 out.resize(out.len() + len - copied, 0);
870}
871
872fn decode_interpolation_scalar(data: [u8; 4]) -> InterpolationScalar {
873 InterpolationScalar {
874 x1: data[0].min(127),
875 y1: data[1].min(127),
876 x2: data[2].min(127),
877 y2: data[3].min(127),
878 }
879}
880
881fn decode_bone_interpolation(
882 interpolation: &[u8; 64],
883) -> (InterpolationVector3, InterpolationScalar) {
884 let position = InterpolationVector3 {
885 x: decode_interpolation_scalar([
886 interpolation[0],
887 interpolation[4],
888 interpolation[8],
889 interpolation[12],
890 ]),
891 y: decode_interpolation_scalar([
892 interpolation[1],
893 interpolation[5],
894 interpolation[9],
895 interpolation[13],
896 ]),
897 z: decode_interpolation_scalar([
898 interpolation[2],
899 interpolation[6],
900 interpolation[10],
901 interpolation[14],
902 ]),
903 };
904 let rotation = decode_interpolation_scalar([
905 interpolation[3],
906 interpolation[7],
907 interpolation[11],
908 interpolation[15],
909 ]);
910 (position, rotation)
911}
912
913pub fn build_clip_from_import(
914 result: VmdImportResult,
915 bone_name_to_index: &dyn Fn(&[u8]) -> Option<BoneIndex>,
916 morph_name_to_index: &dyn Fn(&[u8]) -> Option<MorphIndex>,
917) -> AnimationClip {
918 let mut bone_tracks_map: std::collections::BTreeMap<u32, Vec<MovableBoneKeyframe>> =
919 std::collections::BTreeMap::new();
920
921 for kf in result.bone_keyframes {
922 let bone_index = match &kf.bone_mode {
923 VmdBoneImportMode::ByName(_name) => {
924 match bone_name_to_index(&kf.bone_name_normalized) {
925 Some(idx) => idx,
926 None => continue,
927 }
928 }
929 VmdBoneImportMode::ByIndex(idx) => BoneIndex(*idx),
930 };
931
932 let (pos_interp, rot_interp) = decode_bone_interpolation(&kf.interpolation);
933
934 bone_tracks_map
935 .entry(bone_index.0)
936 .or_default()
937 .push(MovableBoneKeyframe {
938 frame: kf.frame,
939 position: kf.position,
940 rotation: kf.rotation,
941 position_interpolation: pos_interp,
942 rotation_interpolation: rot_interp,
943 });
944 }
945
946 let bone_tracks: Vec<BoneAnimationBinding> = bone_tracks_map
947 .into_iter()
948 .map(|(bone_idx, kfs)| BoneAnimationBinding {
949 bone: BoneIndex(bone_idx),
950 track: MovableBoneTrack::from_keyframes(kfs),
951 })
952 .collect();
953
954 let mut morph_tracks_map: std::collections::BTreeMap<u32, Vec<MorphKeyframe>> =
955 std::collections::BTreeMap::new();
956
957 for (morph_name, frame, weight) in result.morph_keyframes {
958 let morph_name_normalized = normalize_vmd_name(&morph_name);
959 let morph_index = match morph_name_to_index(&morph_name_normalized) {
960 Some(idx) => idx,
961 None => continue,
962 };
963 morph_tracks_map
964 .entry(morph_index.0)
965 .or_default()
966 .push(MorphKeyframe::new(frame, weight));
967 }
968
969 let morph_tracks: Vec<MorphAnimationBinding> = morph_tracks_map
970 .into_iter()
971 .map(|(morph_idx, kfs)| MorphAnimationBinding {
972 morph: MorphIndex(morph_idx),
973 track: MorphTrack::from_keyframes(kfs),
974 })
975 .collect();
976
977 let property_track = if result.property_keyframes.is_empty() {
978 None
979 } else {
980 Some(PropertyAnimationBinding::from_keyframes(
981 result.property_keyframes,
982 ))
983 };
984
985 AnimationClip::new_full(bone_tracks, morph_tracks, property_track)
986}
987
988pub fn build_property_binding_with_ik_resolver(
989 ik_frames: &[VmdPropertyIkFrame],
990 ik_name_to_solver_index: &dyn Fn(&[u8]) -> Option<usize>,
991 solver_count: usize,
992) -> Option<PropertyAnimationBinding> {
993 if solver_count == 0 || ik_frames.is_empty() {
994 return None;
995 }
996
997 let keyframes: Vec<PropertyKeyframe> = ik_frames
998 .iter()
999 .map(|frame| {
1000 let mut ik_enabled = vec![1u8; solver_count];
1001 for entry in &frame.entries {
1002 match ik_name_to_solver_index(&entry.name_normalized) {
1003 Some(idx) if idx < solver_count => {
1004 ik_enabled[idx] = entry.enabled;
1005 }
1006 _ => {}
1007 }
1008 }
1009 PropertyKeyframe {
1010 frame: frame.frame,
1011 ik_enabled: ik_enabled.into_boxed_slice(),
1012 }
1013 })
1014 .collect();
1015
1016 Some(PropertyAnimationBinding::from_keyframes(keyframes))
1017}
1018
1019#[derive(Debug, Clone, Copy)]
1021pub struct VmdClipBuildOptions {
1022 pub honor_property_ik: bool,
1028}
1029
1030impl Default for VmdClipBuildOptions {
1031 fn default() -> Self {
1032 Self {
1033 honor_property_ik: true,
1034 }
1035 }
1036}
1037
1038pub fn build_pair_clip(
1039 result: &VmdImportResult,
1040 bone_name_to_index: &std::collections::HashMap<Vec<u8>, BoneIndex>,
1041 morph_name_to_index: &std::collections::HashMap<Vec<u8>, MorphIndex>,
1042 ik_solver_bone_name_to_index: &std::collections::HashMap<Vec<u8>, usize>,
1043 solver_count: usize,
1044) -> AnimationClip {
1045 build_pair_clip_with_options(
1046 result,
1047 bone_name_to_index,
1048 morph_name_to_index,
1049 ik_solver_bone_name_to_index,
1050 solver_count,
1051 VmdClipBuildOptions::default(),
1052 )
1053}
1054
1055pub fn build_pair_clip_with_options(
1056 result: &VmdImportResult,
1057 bone_name_to_index: &std::collections::HashMap<Vec<u8>, BoneIndex>,
1058 morph_name_to_index: &std::collections::HashMap<Vec<u8>, MorphIndex>,
1059 ik_solver_bone_name_to_index: &std::collections::HashMap<Vec<u8>, usize>,
1060 solver_count: usize,
1061 options: VmdClipBuildOptions,
1062) -> AnimationClip {
1063 let mut bone_tracks_map: std::collections::BTreeMap<u32, Vec<MovableBoneKeyframe>> =
1064 std::collections::BTreeMap::new();
1065
1066 for kf in &result.bone_keyframes {
1067 let bone_index = match bone_name_to_index.get(&kf.bone_name_normalized) {
1068 Some(idx) => *idx,
1069 None => continue,
1070 };
1071
1072 let (pos_interp, rot_interp) = decode_bone_interpolation(&kf.interpolation);
1073
1074 bone_tracks_map
1075 .entry(bone_index.0)
1076 .or_default()
1077 .push(MovableBoneKeyframe {
1078 frame: kf.frame,
1079 position: kf.position,
1080 rotation: kf.rotation,
1081 position_interpolation: pos_interp,
1082 rotation_interpolation: rot_interp,
1083 });
1084 }
1085
1086 let bone_tracks: Vec<BoneAnimationBinding> = bone_tracks_map
1087 .into_iter()
1088 .map(|(bone_idx, kfs)| BoneAnimationBinding {
1089 bone: BoneIndex(bone_idx),
1090 track: MovableBoneTrack::from_keyframes(kfs),
1091 })
1092 .collect();
1093
1094 let mut morph_tracks_map: std::collections::BTreeMap<u32, Vec<MorphKeyframe>> =
1095 std::collections::BTreeMap::new();
1096
1097 for (morph_name, frame, weight) in &result.morph_keyframes {
1098 let morph_name_normalized = normalize_vmd_name(morph_name);
1099 let morph_index = match morph_name_to_index.get(&morph_name_normalized) {
1100 Some(idx) => *idx,
1101 None => continue,
1102 };
1103 morph_tracks_map
1104 .entry(morph_index.0)
1105 .or_default()
1106 .push(MorphKeyframe::new(*frame, *weight));
1107 }
1108
1109 let morph_tracks: Vec<MorphAnimationBinding> = morph_tracks_map
1110 .into_iter()
1111 .map(|(morph_idx, kfs)| MorphAnimationBinding {
1112 morph: MorphIndex(morph_idx),
1113 track: MorphTrack::from_keyframes(kfs),
1114 })
1115 .collect();
1116
1117 let property_track = if options.honor_property_ik {
1118 build_property_binding_with_ik_resolver(
1119 &result.property_ik_frames,
1120 &|name| ik_solver_bone_name_to_index.get(name).copied(),
1121 solver_count,
1122 )
1123 } else {
1124 None
1125 };
1126
1127 AnimationClip::new_full(bone_tracks, morph_tracks, property_track)
1128}
1129
1130#[cfg(test)]
1131mod tests {
1132 use super::*;
1133 #[allow(unused_imports)]
1134 use serde_json;
1135
1136 fn build_vmd_header_bytes() -> Vec<u8> {
1137 let mut buf = Vec::new();
1138 buf.extend_from_slice(&VMD_MAGIC);
1139 buf.extend_from_slice(&[0u8; 20]);
1140 buf
1141 }
1142
1143 #[test]
1144 fn rejects_impossible_vmd_bone_count_before_allocation() {
1145 let mut buf = build_vmd_header_bytes();
1146 buf.extend_from_slice(&u32::MAX.to_le_bytes());
1147
1148 assert!(matches!(
1149 import_vmd_motion(&buf),
1150 Err(ImportError::UnexpectedEof(_))
1151 ));
1152 assert!(matches!(
1153 parse_vmd_animation(&buf),
1154 Err(ImportError::UnexpectedEof(_))
1155 ));
1156 }
1157
1158 #[test]
1159 fn rejects_impossible_vmd_property_ik_count_before_allocation() {
1160 let mut buf = build_vmd_header_bytes();
1161 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!(
1172 import_vmd_motion(&buf),
1173 Err(ImportError::UnexpectedEof(_))
1174 ));
1175 assert!(matches!(
1176 parse_vmd_animation(&buf),
1177 Err(ImportError::UnexpectedEof(_))
1178 ));
1179 }
1180
1181 #[test]
1182 fn parses_vmd_header() {
1183 let header_bytes = build_vmd_header_bytes();
1184 let (header, pos) = read_header(&header_bytes).unwrap();
1185 assert_eq!(header.model_name_bytes, [0u8; 20]);
1186 assert!(pos > 0);
1187 }
1188
1189 #[test]
1190 fn rejects_bad_vmd_magic() {
1191 let mut buf = build_vmd_header_bytes();
1192 buf[0] = 0xFF;
1193 assert_eq!(read_header(&buf).unwrap_err(), ImportError::InvalidVmdMagic);
1194 }
1195
1196 #[test]
1197 fn accepts_vmd_magic_with_nonzero_padding_bytes() {
1198 let mut buf = build_vmd_header_bytes();
1199 buf[26..30].copy_from_slice(b"JKLM");
1200
1201 let (_header, pos) = read_header(&buf).unwrap();
1202 assert_eq!(pos, 50);
1203 }
1204
1205 #[test]
1206 fn parses_minimal_vmd_motion() {
1207 let mut buf = build_vmd_header_bytes();
1208
1209 buf.extend_from_slice(&0u32.to_le_bytes());
1210 buf.extend_from_slice(&0u32.to_le_bytes());
1211 buf.extend_from_slice(&0u32.to_le_bytes());
1212 buf.extend_from_slice(&0u32.to_le_bytes());
1213 buf.extend_from_slice(&0u32.to_le_bytes());
1214 buf.extend_from_slice(&0u32.to_le_bytes());
1215
1216 let result = import_vmd_motion(&buf).unwrap();
1217 assert!(result.bone_keyframes.is_empty());
1218 assert!(result.morph_keyframes.is_empty());
1219 assert!(result.property_keyframes.is_empty());
1220 }
1221
1222 #[test]
1223 fn accepts_vmd_without_optional_tail_sections() {
1224 let mut buf = build_vmd_header_bytes();
1225 buf.extend_from_slice(&0u32.to_le_bytes());
1226
1227 let result = import_vmd_motion(&buf).unwrap();
1228 assert!(result.bone_keyframes.is_empty());
1229 assert!(result.morph_keyframes.is_empty());
1230 assert!(result.property_keyframes.is_empty());
1231 assert!(result.property_ik_frames.is_empty());
1232 }
1233
1234 #[test]
1235 fn accepts_vmd_ending_after_shadow_section() {
1236 let mut buf = build_vmd_header_bytes();
1237 buf.extend_from_slice(&0u32.to_le_bytes());
1238 buf.extend_from_slice(&0u32.to_le_bytes());
1239 buf.extend_from_slice(&0u32.to_le_bytes());
1240 buf.extend_from_slice(&0u32.to_le_bytes());
1241 buf.extend_from_slice(&0u32.to_le_bytes());
1242
1243 let result = import_vmd_motion(&buf).unwrap();
1244 assert!(result.bone_keyframes.is_empty());
1245 assert!(result.morph_keyframes.is_empty());
1246 assert!(result.property_keyframes.is_empty());
1247 assert!(result.property_ik_frames.is_empty());
1248 }
1249
1250 #[test]
1251 fn rejects_partial_optional_count() {
1252 let mut buf = build_vmd_header_bytes();
1253 buf.extend_from_slice(&0u32.to_le_bytes());
1254 buf.extend_from_slice(&[1, 2, 3]);
1255
1256 assert_eq!(
1257 import_vmd_motion(&buf).unwrap_err(),
1258 ImportError::UnexpectedEof(1)
1259 );
1260 }
1261
1262 #[test]
1263 fn ignores_truncated_unused_tail_sections() {
1264 let mut buf = build_vmd_header_bytes();
1265 buf.extend_from_slice(&0u32.to_le_bytes());
1266 buf.extend_from_slice(&0u32.to_le_bytes());
1267 buf.extend_from_slice(&0u32.to_le_bytes());
1268 buf.extend_from_slice(&0u32.to_le_bytes());
1269 buf.extend_from_slice(&10u32.to_le_bytes());
1270 buf.extend_from_slice(&[0u8; 8]);
1271
1272 let result = import_vmd_motion(&buf).unwrap();
1273 assert!(result.bone_keyframes.is_empty());
1274 assert!(result.morph_keyframes.is_empty());
1275 assert!(result.property_keyframes.is_empty());
1276 assert!(result.property_ik_frames.is_empty());
1277 }
1278
1279 #[test]
1280 fn parsed_animation_ignores_implausible_optional_tail() {
1281 let mut buf = build_vmd_header_bytes();
1282 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());
1290 buf.extend_from_slice(&[0u8; 57]);
1291
1292 let parsed = parse_vmd_animation(&buf).unwrap();
1293 assert!(parsed.camera_frames.is_empty());
1294 assert!(parsed.light_frames.is_empty());
1295 assert!(parsed.self_shadow_frames.is_empty());
1296 assert!(parsed.property_frames.is_empty());
1297 }
1298
1299 #[test]
1300 fn parses_single_bone_keyframe() {
1301 let mut buf = build_vmd_header_bytes();
1302
1303 buf.extend_from_slice(&1u32.to_le_bytes());
1304 let mut bone_name = [0u8; 15];
1305 bone_name[..4].copy_from_slice(b"Bone");
1306 buf.extend_from_slice(&bone_name);
1307 buf.extend_from_slice(&10u32.to_le_bytes());
1308 buf.extend_from_slice(&1.0f32.to_le_bytes());
1309 buf.extend_from_slice(&2.0f32.to_le_bytes());
1310 buf.extend_from_slice(&3.0f32.to_le_bytes());
1311 buf.extend_from_slice(&0.0f32.to_le_bytes());
1312 buf.extend_from_slice(&0.0f32.to_le_bytes());
1313 buf.extend_from_slice(&0.0f32.to_le_bytes());
1314 buf.extend_from_slice(&1.0f32.to_le_bytes());
1315 buf.extend_from_slice(&[20u8; 64]);
1316
1317 buf.extend_from_slice(&0u32.to_le_bytes());
1318 buf.extend_from_slice(&0u32.to_le_bytes());
1319 buf.extend_from_slice(&0u32.to_le_bytes());
1320 buf.extend_from_slice(&0u32.to_le_bytes());
1321 buf.extend_from_slice(&0u32.to_le_bytes());
1322
1323 let result = import_vmd_motion(&buf).unwrap();
1324 assert_eq!(result.bone_keyframes.len(), 1);
1325 assert_eq!(result.bone_keyframes[0].frame, 10);
1326 assert!((result.bone_keyframes[0].position.x - 1.0).abs() < 0.001);
1327 assert!((result.bone_keyframes[0].position.y - 2.0).abs() < 0.001);
1328 assert!((result.bone_keyframes[0].position.z - 3.0).abs() < 0.001);
1329 assert_eq!(&result.bone_keyframes[0].bone_name_normalized[..], b"Bone");
1330 }
1331
1332 #[test]
1333 fn exports_parsed_vmd_animation_for_roundtrip() {
1334 let mut buf = build_vmd_header_bytes();
1335
1336 buf.extend_from_slice(&1u32.to_le_bytes());
1337 let mut bone_name = [0u8; 15];
1338 bone_name[..4].copy_from_slice(b"Bone");
1339 buf.extend_from_slice(&bone_name);
1340 buf.extend_from_slice(&10u32.to_le_bytes());
1341 buf.extend_from_slice(&1.0f32.to_le_bytes());
1342 buf.extend_from_slice(&2.0f32.to_le_bytes());
1343 buf.extend_from_slice(&3.0f32.to_le_bytes());
1344 buf.extend_from_slice(&0.1f32.to_le_bytes());
1345 buf.extend_from_slice(&0.2f32.to_le_bytes());
1346 buf.extend_from_slice(&0.3f32.to_le_bytes());
1347 buf.extend_from_slice(&0.4f32.to_le_bytes());
1348 buf.extend_from_slice(&[20u8; 64]);
1349
1350 buf.extend_from_slice(&1u32.to_le_bytes());
1351 let mut morph_name = [0u8; 15];
1352 morph_name[..5].copy_from_slice(b"Smile");
1353 buf.extend_from_slice(&morph_name);
1354 buf.extend_from_slice(&11u32.to_le_bytes());
1355 buf.extend_from_slice(&0.75f32.to_le_bytes());
1356
1357 buf.extend_from_slice(&1u32.to_le_bytes());
1358 buf.extend_from_slice(&12u32.to_le_bytes());
1359 buf.extend_from_slice(&30.0f32.to_le_bytes());
1360 buf.extend_from_slice(&1.0f32.to_le_bytes());
1361 buf.extend_from_slice(&2.0f32.to_le_bytes());
1362 buf.extend_from_slice(&3.0f32.to_le_bytes());
1363 buf.extend_from_slice(&0.1f32.to_le_bytes());
1364 buf.extend_from_slice(&0.2f32.to_le_bytes());
1365 buf.extend_from_slice(&0.3f32.to_le_bytes());
1366 buf.extend_from_slice(&[30u8; 24]);
1367 buf.extend_from_slice(&45u32.to_le_bytes());
1368 buf.push(0);
1369
1370 buf.extend_from_slice(&1u32.to_le_bytes());
1371 buf.extend_from_slice(&13u32.to_le_bytes());
1372 buf.extend_from_slice(&1.0f32.to_le_bytes());
1373 buf.extend_from_slice(&0.5f32.to_le_bytes());
1374 buf.extend_from_slice(&0.25f32.to_le_bytes());
1375 buf.extend_from_slice(&(-1.0f32).to_le_bytes());
1376 buf.extend_from_slice(&(-0.5f32).to_le_bytes());
1377 buf.extend_from_slice(&(-0.25f32).to_le_bytes());
1378
1379 buf.extend_from_slice(&1u32.to_le_bytes());
1380 buf.extend_from_slice(&14u32.to_le_bytes());
1381 buf.push(2);
1382 buf.extend_from_slice(&0.6f32.to_le_bytes());
1383
1384 buf.extend_from_slice(&1u32.to_le_bytes());
1385 buf.extend_from_slice(&15u32.to_le_bytes());
1386 buf.push(1);
1387 buf.extend_from_slice(&1u32.to_le_bytes());
1388 let mut ik_name = [0u8; 20];
1389 ik_name[..2].copy_from_slice(b"IK");
1390 buf.extend_from_slice(&ik_name);
1391 buf.push(1);
1392
1393 let parsed = parse_vmd_animation(&buf).unwrap();
1394 let exported = export_vmd_animation(&parsed);
1395 let reparsed = parse_vmd_animation(&exported).unwrap();
1396
1397 assert_vmd_roundtrip_eq(&parsed, &reparsed);
1398 }
1399
1400 fn assert_vmd_roundtrip_eq(left: &VmdParsedAnimation, right: &VmdParsedAnimation) {
1401 assert_eq!(left.metadata.model_name, right.metadata.model_name);
1402 assert_eq!(left.metadata.max_frame, right.metadata.max_frame);
1403 assert_eq!(left.bone_frames.len(), right.bone_frames.len());
1404 assert_eq!(left.morph_frames.len(), right.morph_frames.len());
1405 assert_eq!(left.camera_frames.len(), right.camera_frames.len());
1406 assert_eq!(left.light_frames.len(), right.light_frames.len());
1407 assert_eq!(
1408 left.self_shadow_frames.len(),
1409 right.self_shadow_frames.len()
1410 );
1411 assert_eq!(left.property_frames.len(), right.property_frames.len());
1412 assert_eq!(
1413 left.bone_frames[0].bone_name,
1414 right.bone_frames[0].bone_name
1415 );
1416 assert_eq!(left.bone_frames[0].frame, right.bone_frames[0].frame);
1417 assert_eq!(
1418 left.bone_frames[0].translation,
1419 right.bone_frames[0].translation
1420 );
1421 assert_eq!(left.bone_frames[0].rotation, right.bone_frames[0].rotation);
1422 assert_eq!(
1423 left.bone_frames[0].interpolation,
1424 right.bone_frames[0].interpolation
1425 );
1426 assert_eq!(
1427 left.morph_frames[0].morph_name,
1428 right.morph_frames[0].morph_name
1429 );
1430 assert_eq!(left.morph_frames[0].frame, right.morph_frames[0].frame);
1431 assert_eq!(left.morph_frames[0].weight, right.morph_frames[0].weight);
1432 assert_eq!(left.camera_frames[0].frame, right.camera_frames[0].frame);
1433 assert_eq!(
1434 left.camera_frames[0].position,
1435 right.camera_frames[0].position
1436 );
1437 assert_eq!(
1438 left.camera_frames[0].rotation,
1439 right.camera_frames[0].rotation
1440 );
1441 assert_eq!(
1442 left.camera_frames[0].interpolation,
1443 right.camera_frames[0].interpolation
1444 );
1445 assert_eq!(
1446 left.camera_frames[0].perspective,
1447 right.camera_frames[0].perspective
1448 );
1449 assert_eq!(left.light_frames[0].color, right.light_frames[0].color);
1450 assert_eq!(
1451 left.light_frames[0].direction,
1452 right.light_frames[0].direction
1453 );
1454 assert_eq!(
1455 left.self_shadow_frames[0].mode,
1456 right.self_shadow_frames[0].mode
1457 );
1458 assert_eq!(
1459 left.self_shadow_frames[0].distance,
1460 right.self_shadow_frames[0].distance
1461 );
1462 assert_eq!(
1463 left.property_frames[0].visible,
1464 right.property_frames[0].visible
1465 );
1466 assert_eq!(
1467 left.property_frames[0].ik_states[0].bone_name,
1468 right.property_frames[0].ik_states[0].bone_name
1469 );
1470 assert_eq!(
1471 left.property_frames[0].ik_states[0].enabled,
1472 right.property_frames[0].ik_states[0].enabled
1473 );
1474 }
1475
1476 #[test]
1477 fn decodes_raw_vmd_bone_interpolation_as_strided_curves() {
1478 let mut interpolation = [0u8; 64];
1479 for (index, value) in interpolation.iter_mut().enumerate().take(16) {
1480 *value = index as u8;
1481 }
1482
1483 let (position, rotation) = decode_bone_interpolation(&interpolation);
1484
1485 assert_eq!(
1486 position.x,
1487 InterpolationScalar {
1488 x1: 0,
1489 y1: 4,
1490 x2: 8,
1491 y2: 12
1492 }
1493 );
1494 assert_eq!(
1495 position.y,
1496 InterpolationScalar {
1497 x1: 1,
1498 y1: 5,
1499 x2: 9,
1500 y2: 13
1501 }
1502 );
1503 assert_eq!(
1504 position.z,
1505 InterpolationScalar {
1506 x1: 2,
1507 y1: 6,
1508 x2: 10,
1509 y2: 14
1510 }
1511 );
1512 assert_eq!(
1513 rotation,
1514 InterpolationScalar {
1515 x1: 3,
1516 y1: 7,
1517 x2: 11,
1518 y2: 15
1519 }
1520 );
1521 }
1522
1523 #[test]
1524 fn skips_camera_light_shadow_and_reads_ik_property_names() {
1525 let mut buf = build_vmd_header_bytes();
1526
1527 buf.extend_from_slice(&0u32.to_le_bytes());
1528 buf.extend_from_slice(&0u32.to_le_bytes());
1529 buf.extend_from_slice(&1u32.to_le_bytes());
1530 buf.extend_from_slice(&[0u8; 61]);
1531 buf.extend_from_slice(&1u32.to_le_bytes());
1532 buf.extend_from_slice(&[0u8; 28]);
1533 buf.extend_from_slice(&1u32.to_le_bytes());
1534 buf.extend_from_slice(&0u32.to_le_bytes());
1535 buf.push(1);
1536 buf.extend_from_slice(&0.5f32.to_le_bytes());
1537 buf.extend_from_slice(&1u32.to_le_bytes());
1538 buf.extend_from_slice(&24u32.to_le_bytes());
1539 buf.push(1);
1540 buf.extend_from_slice(&1u32.to_le_bytes());
1541 let mut ik_name = [0u8; 20];
1542 ik_name[..6].copy_from_slice(b"LeftIK");
1543 buf.extend_from_slice(&ik_name);
1544 buf.push(0);
1545
1546 let result = import_vmd_motion(&buf).unwrap();
1547 assert_eq!(result.property_keyframes.len(), 1);
1548 assert_eq!(result.property_keyframes[0].frame, 24);
1549 assert_eq!(&*result.property_keyframes[0].ik_enabled, &[0]);
1550
1551 assert_eq!(result.property_ik_frames.len(), 1);
1552 assert_eq!(result.property_ik_frames[0].frame, 24);
1553 assert_eq!(result.property_ik_frames[0].show, 1);
1554 assert_eq!(result.property_ik_frames[0].entries.len(), 1);
1555 let name_end = result.property_ik_frames[0].entries[0]
1556 .name_bytes
1557 .iter()
1558 .position(|&b| b == 0)
1559 .unwrap_or(20);
1560 assert_eq!(
1561 &result.property_ik_frames[0].entries[0].name_bytes[..name_end],
1562 b"LeftIK"
1563 );
1564 assert_eq!(result.property_ik_frames[0].entries[0].enabled, 0);
1565 assert_eq!(
1566 &result.property_ik_frames[0].entries[0].name_normalized[..],
1567 b"LeftIK"
1568 );
1569 }
1570
1571 #[test]
1572 fn builds_clip_from_bone_track() {
1573 let kfs = vec![
1574 VmdBoneKeyframeRaw {
1575 bone_mode: VmdBoneImportMode::ByName(b"BoneA".to_vec()),
1576 frame: 0,
1577 position: Vec3A::ZERO,
1578 rotation: Quat::IDENTITY,
1579 interpolation: [
1580 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20,
1581 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107,
1582 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107,
1583 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107,
1584 ],
1585 bone_name_normalized: b"BoneA".to_vec(),
1586 },
1587 VmdBoneKeyframeRaw {
1588 bone_mode: VmdBoneImportMode::ByName(b"BoneA".to_vec()),
1589 frame: 30,
1590 position: Vec3A::new(10.0, 0.0, 0.0),
1591 rotation: Quat::IDENTITY,
1592 interpolation: [
1593 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20,
1594 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107,
1595 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107,
1596 20, 20, 107, 107, 20, 20, 107, 107, 20, 20, 107, 107,
1597 ],
1598 bone_name_normalized: b"BoneA".to_vec(),
1599 },
1600 ];
1601
1602 fn lookup(name: &[u8]) -> Option<BoneIndex> {
1603 if name == b"BoneA" {
1604 Some(BoneIndex(0))
1605 } else {
1606 None
1607 }
1608 }
1609 fn morph_lookup(_name: &[u8]) -> Option<MorphIndex> {
1610 None
1611 }
1612
1613 let result = VmdImportResult {
1614 bone_keyframes: kfs,
1615 morph_keyframes: Vec::new(),
1616 property_keyframes: Vec::new(),
1617 property_ik_frames: Vec::new(),
1618 };
1619
1620 let _clip = build_clip_from_import(result, &lookup, &morph_lookup);
1621 }
1622
1623 #[test]
1624 fn build_clip_from_import_resolves_morph_by_normalized_name() {
1625 let sjis_morph = vec![0x83, 0x65, 0x83, 0x58, 0x83, 0x67];
1626 let result = VmdImportResult {
1627 bone_keyframes: Vec::new(),
1628 morph_keyframes: vec![(sjis_morph, 0, 1.0)],
1629 property_keyframes: Vec::new(),
1630 property_ik_frames: Vec::new(),
1631 };
1632
1633 fn morph_lookup(name: &[u8]) -> Option<MorphIndex> {
1634 if name == b"\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88" {
1635 Some(MorphIndex(0))
1636 } else {
1637 None
1638 }
1639 }
1640 fn bone_lookup(_name: &[u8]) -> Option<BoneIndex> {
1641 None
1642 }
1643
1644 let clip = build_clip_from_import(result, &bone_lookup, &morph_lookup);
1645 assert_eq!(clip.morph_track_count(), 1);
1646 }
1647
1648 fn ik_name_bytes(name: &str) -> [u8; 20] {
1649 let mut buf = [0u8; 20];
1650 let name_bytes = name.as_bytes();
1651 let len = name_bytes.len().min(20);
1652 buf[..len].copy_from_slice(&name_bytes[..len]);
1653 buf
1654 }
1655
1656 #[test]
1657 fn parses_property_ik_entry_names() {
1658 let mut buf = build_vmd_header_bytes();
1659
1660 buf.extend_from_slice(&0u32.to_le_bytes());
1661 buf.extend_from_slice(&0u32.to_le_bytes());
1662 buf.extend_from_slice(&0u32.to_le_bytes());
1663 buf.extend_from_slice(&0u32.to_le_bytes());
1664 buf.extend_from_slice(&0u32.to_le_bytes());
1665 buf.extend_from_slice(&1u32.to_le_bytes());
1666 buf.extend_from_slice(&10u32.to_le_bytes());
1667 buf.push(0);
1668 buf.extend_from_slice(&2u32.to_le_bytes());
1669 buf.extend_from_slice(&ik_name_bytes("LeftLegIK"));
1670 buf.push(1);
1671 buf.extend_from_slice(&ik_name_bytes("RightLegIK"));
1672 buf.push(0);
1673
1674 let result = import_vmd_motion(&buf).unwrap();
1675 assert_eq!(result.property_ik_frames.len(), 1);
1676 assert_eq!(result.property_ik_frames[0].frame, 10);
1677 assert_eq!(result.property_ik_frames[0].entries.len(), 2);
1678
1679 let name1: Vec<u8> = result.property_ik_frames[0].entries[0]
1680 .name_bytes
1681 .iter()
1682 .copied()
1683 .take_while(|&b| b != 0)
1684 .collect();
1685 assert_eq!(&name1[..], b"LeftLegIK");
1686 assert_eq!(result.property_ik_frames[0].entries[0].enabled, 1);
1687 assert_eq!(
1688 &result.property_ik_frames[0].entries[0].name_normalized[..],
1689 b"LeftLegIK"
1690 );
1691
1692 let name2: Vec<u8> = result.property_ik_frames[0].entries[1]
1693 .name_bytes
1694 .iter()
1695 .copied()
1696 .take_while(|&b| b != 0)
1697 .collect();
1698 assert_eq!(&name2[..], b"RightLegIK");
1699 assert_eq!(result.property_ik_frames[0].entries[1].enabled, 0);
1700 }
1701
1702 #[test]
1703 fn reorders_ik_enabled_to_solver_order() {
1704 let frames = vec![VmdPropertyIkFrame {
1705 frame: 0,
1706 show: 0,
1707 entries: vec![
1708 VmdIkEntry {
1709 name_bytes: {
1710 let mut buf = [0u8; 20];
1711 buf[..9].copy_from_slice(b"RightLegI");
1712 buf.to_vec()
1713 },
1714 enabled: 1,
1715 name_normalized: b"RightLegI".to_vec(),
1716 },
1717 VmdIkEntry {
1718 name_bytes: {
1719 let mut buf = [0u8; 20];
1720 buf[..8].copy_from_slice(b"LeftLegI");
1721 buf.to_vec()
1722 },
1723 enabled: 0,
1724 name_normalized: b"LeftLegI".to_vec(),
1725 },
1726 ],
1727 }];
1728
1729 fn ik_resolver(name: &[u8]) -> Option<usize> {
1730 match name {
1731 b"LeftLegI" => Some(0),
1732 b"RightLegI" => Some(1),
1733 _ => None,
1734 }
1735 }
1736
1737 let binding = build_property_binding_with_ik_resolver(&frames, &ik_resolver, 2).unwrap();
1738 let sample = binding.sample(0.0).unwrap();
1739 assert_eq!(sample, &[0, 1]);
1740 }
1741
1742 #[test]
1743 fn unknown_and_unmentioned_ik_names_keep_default_enabled_state() {
1744 let frames = vec![VmdPropertyIkFrame {
1745 frame: 5,
1746 show: 0,
1747 entries: vec![
1748 VmdIkEntry {
1749 name_bytes: {
1750 let mut buf = [0u8; 20];
1751 buf[..7].copy_from_slice(b"KnownIK");
1752 buf.to_vec()
1753 },
1754 enabled: 1,
1755 name_normalized: b"KnownIK".to_vec(),
1756 },
1757 VmdIkEntry {
1758 name_bytes: {
1759 let mut buf = [0u8; 20];
1760 buf[..9].copy_from_slice(b"UnknownIK");
1761 buf.to_vec()
1762 },
1763 enabled: 1,
1764 name_normalized: b"UnknownIK".to_vec(),
1765 },
1766 ],
1767 }];
1768
1769 fn ik_resolver(name: &[u8]) -> Option<usize> {
1770 if name == b"KnownIK" { Some(0) } else { None }
1771 }
1772
1773 let binding = build_property_binding_with_ik_resolver(&frames, &ik_resolver, 2).unwrap();
1774 let sample = binding.sample(5.0).unwrap();
1775 assert_eq!(sample, &[1, 1]);
1776 }
1777
1778 #[test]
1779 fn empty_ik_frames_returns_none() {
1780 fn ik_resolver(_name: &[u8]) -> Option<usize> {
1781 None
1782 }
1783 let binding = build_property_binding_with_ik_resolver(&[], &ik_resolver, 2);
1784 assert!(binding.is_none());
1785 }
1786
1787 #[test]
1788 fn zero_solver_count_returns_none() {
1789 let frames = vec![VmdPropertyIkFrame {
1790 frame: 0,
1791 show: 0,
1792 entries: vec![VmdIkEntry {
1793 name_bytes: ik_name_bytes("IK").to_vec(),
1794 enabled: 1,
1795 name_normalized: b"IK".to_vec(),
1796 }],
1797 }];
1798 fn ik_resolver(_name: &[u8]) -> Option<usize> {
1799 Some(0)
1800 }
1801 let binding = build_property_binding_with_ik_resolver(&frames, &ik_resolver, 0);
1802 assert!(binding.is_none());
1803 }
1804
1805 #[test]
1806 fn build_property_binding_resolves_ik_by_normalized_name() {
1807 let frames = vec![VmdPropertyIkFrame {
1808 frame: 0,
1809 show: 0,
1810 entries: vec![VmdIkEntry {
1811 name_bytes: vec![0x8D, 0xB6, 0x91, 0xAB],
1812 enabled: 1,
1813 name_normalized: vec![0xE5, 0xB7, 0xA6, 0xE8, 0xB6, 0xB3],
1814 }],
1815 }];
1816
1817 fn ik_resolver(name: &[u8]) -> Option<usize> {
1818 if name == b"\xE5\xB7\xA6\xE8\xB6\xB3" {
1819 Some(0)
1820 } else {
1821 None
1822 }
1823 }
1824
1825 let binding = build_property_binding_with_ik_resolver(&frames, &ik_resolver, 1).unwrap();
1826 let sample = binding.sample(0.0).unwrap();
1827 assert_eq!(sample, &[1]);
1828 }
1829
1830 #[test]
1831 fn build_clip_from_import_resolves_bone_by_normalized_name() {
1832 let sjis_name: Vec<u8> = vec![0x8D, 0xB6, 0x91, 0xAB];
1833 let utf8_name: Vec<u8> = vec![0xE5, 0xB7, 0xA6, 0xE8, 0xB6, 0xB3];
1834
1835 let kf = VmdBoneKeyframeRaw {
1836 bone_mode: VmdBoneImportMode::ByName(sjis_name),
1837 frame: 0,
1838 position: Vec3A::ZERO,
1839 rotation: Quat::IDENTITY,
1840 interpolation: [20u8; 64],
1841 bone_name_normalized: utf8_name.clone(),
1842 };
1843
1844 fn lookup(name: &[u8]) -> Option<BoneIndex> {
1845 if name == b"\xE5\xB7\xA6\xE8\xB6\xB3" {
1846 Some(BoneIndex(0))
1847 } else {
1848 None
1849 }
1850 }
1851 fn morph_lookup(_name: &[u8]) -> Option<MorphIndex> {
1852 None
1853 }
1854
1855 let result = VmdImportResult {
1856 bone_keyframes: vec![kf],
1857 morph_keyframes: Vec::new(),
1858 property_keyframes: Vec::new(),
1859 property_ik_frames: Vec::new(),
1860 };
1861
1862 let clip = build_clip_from_import(result, &lookup, &morph_lookup);
1863 assert_eq!(
1864 clip.bone_track_count(),
1865 1,
1866 "bone should be resolved via normalized UTF-8 name, not raw Shift-JIS bytes"
1867 );
1868 }
1869
1870 #[test]
1871 fn japanese_vmd_name_matches_pmx_utf8_name_via_normalization() {
1872 let sjis_name: &[u8] = &[0x8D, 0xB6, 0x91, 0xAB];
1873 let utf8_name: &[u8] = &[0xE5, 0xB7, 0xA6, 0xE8, 0xB6, 0xB3];
1874
1875 let mut pmx_buf = Vec::new();
1876 pmx_buf.extend_from_slice(b"PMX ");
1877 pmx_buf.extend_from_slice(&2.0f32.to_le_bytes());
1878 pmx_buf.push(8);
1879 pmx_buf.push(1);
1880 pmx_buf.push(0);
1881 pmx_buf.push(4);
1882 pmx_buf.push(1);
1883 pmx_buf.push(1);
1884 pmx_buf.push(2);
1885 pmx_buf.push(1);
1886 pmx_buf.push(1);
1887 for _ in 0..4 {
1888 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1889 }
1890 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1891 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1892 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1893 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1894 pmx_buf.extend_from_slice(&1i32.to_le_bytes());
1895 pmx_buf.extend_from_slice(&(utf8_name.len() as i32).to_le_bytes());
1896 pmx_buf.extend_from_slice(utf8_name);
1897 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1898 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1899 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1900 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1901 pmx_buf.extend_from_slice(&(-1i16).to_le_bytes());
1902 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1903 pmx_buf.extend_from_slice(&0u16.to_le_bytes());
1904 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1905 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1906 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1907 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1908
1909 let pmx = crate::pmx::import_pmx_runtime(&pmx_buf).unwrap();
1910
1911 let mut vmd_buf = build_vmd_header_bytes();
1912 vmd_buf.extend_from_slice(&1u32.to_le_bytes());
1913 let mut bone_name = [0u8; 15];
1914 bone_name[..sjis_name.len()].copy_from_slice(sjis_name);
1915 vmd_buf.extend_from_slice(&bone_name);
1916 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1917 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1918 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1919 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1920 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1921 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1922 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
1923 vmd_buf.extend_from_slice(&1.0f32.to_le_bytes());
1924 vmd_buf.extend_from_slice(&[20u8; 64]);
1925 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1926 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1927 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1928 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1929 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
1930
1931 let vmd = import_vmd_motion(&vmd_buf).unwrap();
1932
1933 let normalized = normalize_vmd_name(sjis_name);
1934 assert_eq!(&normalized[..], utf8_name);
1935
1936 assert!(
1937 pmx.bone_name_to_index.contains_key(&normalized),
1938 "PMX should have normalized bone name key"
1939 );
1940
1941 let clip = build_pair_clip(
1942 &vmd,
1943 &pmx.bone_name_to_index,
1944 &pmx.morph_name_to_index,
1945 &pmx.ik_solver_bone_name_to_index,
1946 pmx.model.ik_count(),
1947 );
1948
1949 assert_eq!(clip.bone_track_count(), 1);
1950 assert_eq!(clip.morph_track_count(), 0);
1951 assert!(!clip.has_property_track());
1952 }
1953
1954 #[test]
1955 fn japanese_vmd_name_matches_pmx_utf16le_name_via_decoded_key() {
1956 let sjis_name: &[u8] = &[0x8D, 0xB6, 0x91, 0xAB];
1957 let utf8_name: &[u8] = &[0xE5, 0xB7, 0xA6, 0xE8, 0xB6, 0xB3];
1958 let utf16le_name: Vec<u8> = "\u{5DE6}\u{8DB3}"
1959 .encode_utf16()
1960 .flat_map(u16::to_le_bytes)
1961 .collect();
1962
1963 let mut pmx_buf = Vec::new();
1964 pmx_buf.extend_from_slice(b"PMX ");
1965 pmx_buf.extend_from_slice(&2.0f32.to_le_bytes());
1966 pmx_buf.push(8);
1967 pmx_buf.push(0);
1968 pmx_buf.push(0);
1969 pmx_buf.push(4);
1970 pmx_buf.push(1);
1971 pmx_buf.push(1);
1972 pmx_buf.push(2);
1973 pmx_buf.push(1);
1974 pmx_buf.push(1);
1975 for _ in 0..4 {
1976 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1977 }
1978 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1979 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1980 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1981 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1982 pmx_buf.extend_from_slice(&1i32.to_le_bytes());
1983 pmx_buf.extend_from_slice(&(utf16le_name.len() as i32).to_le_bytes());
1984 pmx_buf.extend_from_slice(&utf16le_name);
1985 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1986 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1987 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1988 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1989 pmx_buf.extend_from_slice(&(-1i16).to_le_bytes());
1990 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1991 pmx_buf.extend_from_slice(&0u16.to_le_bytes());
1992 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1993 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1994 pmx_buf.extend_from_slice(&0.0f32.to_le_bytes());
1995 pmx_buf.extend_from_slice(&0i32.to_le_bytes());
1996
1997 let pmx = crate::pmx::import_pmx_runtime(&pmx_buf).unwrap();
1998
1999 let mut vmd_buf = build_vmd_header_bytes();
2000 vmd_buf.extend_from_slice(&1u32.to_le_bytes());
2001 let mut bone_name = [0u8; 15];
2002 bone_name[..sjis_name.len()].copy_from_slice(sjis_name);
2003 vmd_buf.extend_from_slice(&bone_name);
2004 vmd_buf.extend_from_slice(&0u32.to_le_bytes());
2005 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
2006 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
2007 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
2008 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
2009 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
2010 vmd_buf.extend_from_slice(&0.0f32.to_le_bytes());
2011 vmd_buf.extend_from_slice(&1.0f32.to_le_bytes());
2012 vmd_buf.extend_from_slice(&[20u8; 64]);
2013
2014 let vmd = import_vmd_motion(&vmd_buf).unwrap();
2015 let normalized = normalize_vmd_name(sjis_name);
2016 assert_eq!(&normalized[..], utf8_name);
2017 assert!(
2018 pmx.bone_name_to_index.contains_key(&utf16le_name),
2019 "PMX should retain the raw UTF-16LE name key"
2020 );
2021 assert!(
2022 pmx.bone_name_to_index.contains_key(&normalized),
2023 "PMX should also index UTF-16LE names by decoded UTF-8 bytes"
2024 );
2025
2026 let clip = build_pair_clip(
2027 &vmd,
2028 &pmx.bone_name_to_index,
2029 &pmx.morph_name_to_index,
2030 &pmx.ik_solver_bone_name_to_index,
2031 pmx.model.ik_count(),
2032 );
2033
2034 assert_eq!(clip.bone_track_count(), 1);
2035 }
2036
2037 #[test]
2038 fn build_pair_clip_default_includes_property_ik() {
2039 let result = VmdImportResult {
2041 bone_keyframes: Vec::new(),
2042 morph_keyframes: Vec::new(),
2043 property_keyframes: Vec::new(),
2044 property_ik_frames: vec![VmdPropertyIkFrame {
2045 frame: 0,
2046 show: 0,
2047 entries: vec![VmdIkEntry {
2048 name_bytes: ik_name_bytes("LeftLegIK").to_vec(),
2049 enabled: 0,
2050 name_normalized: b"LeftLegIK".to_vec(),
2051 }],
2052 }],
2053 };
2054
2055 let bone_name_to_index: std::collections::HashMap<Vec<u8>, BoneIndex> =
2056 std::collections::HashMap::new();
2057 let morph_name_to_index: std::collections::HashMap<Vec<u8>, MorphIndex> =
2058 std::collections::HashMap::new();
2059 let mut ik_solver_bone_name_to_index: std::collections::HashMap<Vec<u8>, usize> =
2060 std::collections::HashMap::new();
2061 ik_solver_bone_name_to_index.insert(b"LeftLegIK".to_vec(), 0);
2062
2063 let clip = build_pair_clip(
2064 &result,
2065 &bone_name_to_index,
2066 &morph_name_to_index,
2067 &ik_solver_bone_name_to_index,
2068 2, );
2070
2071 assert!(
2072 clip.has_property_track(),
2073 "default build_pair_clip should include property IK track"
2074 );
2075 }
2076
2077 #[test]
2078 fn build_pair_clip_with_options_omits_property_ik() {
2079 let result = VmdImportResult {
2081 bone_keyframes: Vec::new(),
2082 morph_keyframes: Vec::new(),
2083 property_keyframes: Vec::new(),
2084 property_ik_frames: vec![VmdPropertyIkFrame {
2085 frame: 5,
2086 show: 0,
2087 entries: vec![VmdIkEntry {
2088 name_bytes: ik_name_bytes("RightLegIK").to_vec(),
2089 enabled: 0,
2090 name_normalized: b"RightLegIK".to_vec(),
2091 }],
2092 }],
2093 };
2094
2095 let bone_name_to_index: std::collections::HashMap<Vec<u8>, BoneIndex> =
2096 std::collections::HashMap::new();
2097 let morph_name_to_index: std::collections::HashMap<Vec<u8>, MorphIndex> =
2098 std::collections::HashMap::new();
2099 let mut ik_solver_bone_name_to_index: std::collections::HashMap<Vec<u8>, usize> =
2100 std::collections::HashMap::new();
2101 ik_solver_bone_name_to_index.insert(b"RightLegIK".to_vec(), 1);
2102
2103 let clip = build_pair_clip_with_options(
2104 &result,
2105 &bone_name_to_index,
2106 &morph_name_to_index,
2107 &ik_solver_bone_name_to_index,
2108 2,
2109 VmdClipBuildOptions {
2110 honor_property_ik: false,
2111 },
2112 );
2113
2114 assert!(
2115 !clip.has_property_track(),
2116 "build_pair_clip_with_options(honor_property_ik: false) should omit property IK track"
2117 );
2118 }
2119
2120 type SyntheticBoneFrame<'a> = (&'a str, u32, [f32; 3], [f32; 4], [u8; 64]);
2126 type SyntheticMorphFrame<'a> = (&'a str, u32, f32);
2127 type SyntheticPropertyFrame<'a> = (&'a [(&'a str, bool)], u32, bool);
2128
2129 fn assert_near(actual: f32, expected: f32) {
2130 let delta = (actual - expected).abs();
2131 assert!(
2132 delta < 1.0e-5,
2133 "actual={actual:?} expected={expected:?} delta={delta:?}"
2134 );
2135 }
2136
2137 fn assert_vec3_near(actual: [f32; 3], expected: [f32; 3]) {
2138 for (actual, expected) in actual.into_iter().zip(expected) {
2139 assert_near(actual, expected);
2140 }
2141 }
2142
2143 fn simple_camera_vmd_fixture() -> &'static [u8] {
2144 include_bytes!("../../fixtures/vmd/simple_camera.vmd")
2145 }
2146
2147 fn make_vmd_bytes(
2148 model_name_ascii: &str,
2149 bone_frames: &[SyntheticBoneFrame<'_>],
2150 morph_frames: &[SyntheticMorphFrame<'_>],
2151 property_frames: &[SyntheticPropertyFrame<'_>],
2152 ) -> Vec<u8> {
2153 let mut v = Vec::new();
2154 v.extend_from_slice(&VMD_MAGIC);
2155
2156 let mut name_buf = [0u8; 20];
2157 let nb = model_name_ascii.as_bytes();
2158 name_buf[..nb.len().min(20)].copy_from_slice(&nb[..nb.len().min(20)]);
2159 v.extend_from_slice(&name_buf);
2160
2161 v.extend_from_slice(&(bone_frames.len() as u32).to_le_bytes());
2162 for (name, frame, trans, rot, interp) in bone_frames {
2163 let mut nb = [0u8; 15];
2164 let b = name.as_bytes();
2165 nb[..b.len().min(15)].copy_from_slice(&b[..b.len().min(15)]);
2166 v.extend_from_slice(&nb);
2167 v.extend_from_slice(&frame.to_le_bytes());
2168 for &x in trans {
2169 v.extend_from_slice(&x.to_le_bytes());
2170 }
2171 for &x in rot {
2172 v.extend_from_slice(&x.to_le_bytes());
2173 }
2174 v.extend_from_slice(interp);
2175 }
2176
2177 v.extend_from_slice(&(morph_frames.len() as u32).to_le_bytes());
2178 for (name, frame, weight) in morph_frames {
2179 let mut nb = [0u8; 15];
2180 let b = name.as_bytes();
2181 nb[..b.len().min(15)].copy_from_slice(&b[..b.len().min(15)]);
2182 v.extend_from_slice(&nb);
2183 v.extend_from_slice(&frame.to_le_bytes());
2184 v.extend_from_slice(&weight.to_le_bytes());
2185 }
2186
2187 v.extend_from_slice(&0u32.to_le_bytes());
2189 v.extend_from_slice(&0u32.to_le_bytes());
2190 v.extend_from_slice(&0u32.to_le_bytes());
2191
2192 v.extend_from_slice(&(property_frames.len() as u32).to_le_bytes());
2193 for (ik_states, frame, visible) in property_frames {
2194 v.extend_from_slice(&frame.to_le_bytes());
2195 v.push(u8::from(*visible));
2196 v.extend_from_slice(&(ik_states.len() as u32).to_le_bytes());
2197 for (ik_name, enabled) in *ik_states {
2198 let mut nb = [0u8; 20];
2199 let b = ik_name.as_bytes();
2200 nb[..b.len().min(20)].copy_from_slice(&b[..b.len().min(20)]);
2201 v.extend_from_slice(&nb);
2202 v.push(u8::from(*enabled));
2203 }
2204 }
2205 v
2206 }
2207
2208 fn json_keys(value: &serde_json::Value) -> Vec<String> {
2209 let mut keys = value
2210 .as_object()
2211 .unwrap()
2212 .keys()
2213 .map(ToOwned::to_owned)
2214 .collect::<Vec<_>>();
2215 keys.sort();
2216 keys
2217 }
2218
2219 #[test]
2220 fn vmd_animation_json_top_level_schema_is_stable() {
2221 let vmd = make_vmd_bytes("miku", &[], &[], &[]);
2222 let parsed = parse_vmd_animation(&vmd).unwrap();
2223 let keys = json_keys(&serde_json::to_value(&parsed).unwrap());
2224
2225 assert_eq!(
2226 keys,
2227 vec![
2228 "boneFrames",
2229 "cameraFrames",
2230 "kind",
2231 "lightFrames",
2232 "metadata",
2233 "morphFrames",
2234 "propertyFrames",
2235 "selfShadowFrames",
2236 ]
2237 );
2238 }
2239
2240 #[test]
2241 fn parses_simple_camera_vmd_fixture() {
2242 let parsed = parse_vmd_animation(simple_camera_vmd_fixture()).unwrap();
2243
2244 assert_eq!(parsed.metadata.model_name, "camera_fixture");
2245 assert_eq!(parsed.metadata.counts.bones, 0);
2246 assert_eq!(parsed.metadata.counts.morphs, 0);
2247 assert_eq!(parsed.metadata.counts.cameras, 2);
2248 assert_eq!(parsed.metadata.counts.lights, 0);
2249 assert_eq!(parsed.metadata.counts.self_shadows, 0);
2250 assert_eq!(parsed.metadata.counts.properties, 0);
2251 assert_eq!(parsed.metadata.max_frame, 45);
2252 assert_eq!(parsed.camera_frames.len(), 2);
2253
2254 let first = &parsed.camera_frames[0];
2255 assert_eq!(first.frame, 0);
2256 assert_eq!(first.distance, -30.5);
2257 assert_eq!(first.position, [1.0, 2.0, 3.0]);
2258 assert_eq!(first.rotation, [0.1, -0.2, 0.3]);
2259 assert_eq!(
2260 first.interpolation,
2261 [
2262 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
2263 41, 42, 43
2264 ]
2265 );
2266 assert_eq!(first.fov, 35);
2267 assert!(first.perspective);
2268
2269 let second = &parsed.camera_frames[1];
2270 assert_eq!(second.frame, 45);
2271 assert_eq!(second.distance, -50.0);
2272 assert_eq!(second.position, [-1.5, 10.0, 0.25]);
2273 assert_eq!(second.rotation, [-0.3, 0.0, 1.2]);
2274 assert_eq!(
2275 second.interpolation,
2276 [
2277 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 127, 127, 127, 127, 127, 127, 127, 127,
2278 127, 127, 127
2279 ]
2280 );
2281 assert_eq!(second.fov, 60);
2282 assert!(!second.perspective);
2283 }
2284
2285 #[test]
2286 fn roundtrip_simple_camera_vmd_fixture_parse_export_parse() {
2287 let parsed = parse_vmd_animation(simple_camera_vmd_fixture()).unwrap();
2288 let exported = export_vmd_animation(&parsed);
2289 let reparsed = parse_vmd_animation(&exported).unwrap();
2290
2291 assert_eq!(reparsed.metadata.model_name, parsed.metadata.model_name);
2292 assert_eq!(reparsed.metadata.counts.cameras, 2);
2293 assert_eq!(reparsed.metadata.max_frame, 45);
2294 assert_eq!(reparsed.camera_frames.len(), parsed.camera_frames.len());
2295 for (left, right) in parsed.camera_frames.iter().zip(&reparsed.camera_frames) {
2296 assert_eq!(left.frame, right.frame);
2297 assert_eq!(left.distance, right.distance);
2298 assert_eq!(left.position, right.position);
2299 assert_eq!(left.rotation, right.rotation);
2300 assert_eq!(left.interpolation, right.interpolation);
2301 assert_eq!(left.fov, right.fov);
2302 assert_eq!(left.perspective, right.perspective);
2303 }
2304 }
2305
2306 #[test]
2307 fn samples_simple_camera_vmd_fixture_with_channel_interpolation() {
2308 let parsed = parse_vmd_animation(simple_camera_vmd_fixture()).unwrap();
2309 let camera = sample_vmd_camera_frames(&parsed.camera_frames, 22.5).unwrap();
2310
2311 assert_near(camera.distance, -40.25);
2312 assert_vec3_near(camera.position, [-0.25, 6.0, 1.625]);
2313 assert_vec3_near(camera.rotation, [-0.1, -0.1, 0.75]);
2314 assert_near(camera.fov, 47.5);
2315 assert!(camera.perspective);
2316
2317 let last = sample_vmd_camera_frames(&parsed.camera_frames, 45.0).unwrap();
2318 assert!(!last.perspective);
2319 }
2320
2321 #[test]
2322 fn roundtrip_bone_frame_parse_export_parse() {
2323 let interp = [20u8; 64];
2324 let vmd = make_vmd_bytes(
2325 "miku",
2326 &[("arm", 30, [1.0, 0.5, -0.5], [0.0, 0.0, 0.0, 1.0], interp)],
2327 &[],
2328 &[],
2329 );
2330 let parsed = parse_vmd_animation(&vmd).unwrap();
2331 assert_eq!(parsed.bone_frames.len(), 1);
2332 assert_eq!(parsed.bone_frames[0].bone_name, "arm");
2333 assert_eq!(parsed.bone_frames[0].frame, 30);
2334 assert_eq!(parsed.bone_frames[0].translation, [1.0, 0.5, -0.5]);
2335 assert_eq!(parsed.bone_frames[0].rotation, [0.0, 0.0, 0.0, 1.0]);
2336 assert_eq!(parsed.bone_frames[0].interpolation, vec![20u8; 64]);
2337
2338 let exported = export_vmd_animation(&parsed);
2339 let reparsed = parse_vmd_animation(&exported).unwrap();
2340 assert_eq!(reparsed.metadata.model_name, parsed.metadata.model_name);
2341 assert_eq!(reparsed.metadata.max_frame, 30);
2342 assert_eq!(reparsed.bone_frames.len(), 1);
2343 assert_eq!(reparsed.bone_frames[0].bone_name, "arm");
2344 assert_eq!(reparsed.bone_frames[0].frame, 30);
2345 assert_eq!(reparsed.bone_frames[0].translation, [1.0, 0.5, -0.5]);
2346 assert_eq!(reparsed.bone_frames[0].rotation, [0.0, 0.0, 0.0, 1.0]);
2347 assert_eq!(reparsed.bone_frames[0].interpolation, vec![20u8; 64]);
2348 }
2349
2350 #[test]
2351 fn roundtrip_morph_frame_parse_export_parse() {
2352 let vmd = make_vmd_bytes("miku", &[], &[("blink", 15, 0.75)], &[]);
2353 let parsed = parse_vmd_animation(&vmd).unwrap();
2354 let exported = export_vmd_animation(&parsed);
2355 let reparsed = parse_vmd_animation(&exported).unwrap();
2356 assert_eq!(reparsed.morph_frames.len(), 1);
2357 assert_eq!(reparsed.morph_frames[0].morph_name, "blink");
2358 assert_eq!(reparsed.morph_frames[0].frame, 15);
2359 assert!((reparsed.morph_frames[0].weight - 0.75f32).abs() < 1e-6);
2360 }
2361
2362 #[test]
2363 fn roundtrip_property_ik_frames_parse_export_parse() {
2364 let vmd = make_vmd_bytes(
2365 "camera",
2366 &[],
2367 &[],
2368 &[(&[("leftIK", true), ("rightIK", false)], 20, true)],
2369 );
2370 let parsed = parse_vmd_animation(&vmd).unwrap();
2371 let exported = export_vmd_animation(&parsed);
2372 let reparsed = parse_vmd_animation(&exported).unwrap();
2373 assert_eq!(reparsed.property_frames.len(), 1);
2374 assert_eq!(reparsed.property_frames[0].frame, 20);
2375 assert!(reparsed.property_frames[0].visible);
2376 assert_eq!(reparsed.property_frames[0].ik_states.len(), 2);
2377 assert_eq!(reparsed.property_frames[0].ik_states[0].bone_name, "leftIK");
2378 assert!(reparsed.property_frames[0].ik_states[0].enabled);
2379 assert_eq!(
2380 reparsed.property_frames[0].ik_states[1].bone_name,
2381 "rightIK"
2382 );
2383 assert!(!reparsed.property_frames[0].ik_states[1].enabled);
2384 }
2385
2386 #[test]
2387 fn roundtrip_json_dto_bone_and_morph() {
2388 let interp = [20u8; 64];
2389 let vmd = make_vmd_bytes(
2390 "miku",
2391 &[("spine", 60, [0.0, 1.0, 0.0], [0.1, 0.2, 0.3, 0.9], interp)],
2392 &[("mouth", 30, 0.8)],
2393 &[],
2394 );
2395 let parsed = parse_vmd_animation(&vmd).unwrap();
2396 let json = serde_json::to_string(&parsed).unwrap();
2397 let from_json: VmdParsedAnimation = serde_json::from_str(&json).unwrap();
2398 let exported = export_vmd_animation(&from_json);
2399 let reparsed = parse_vmd_animation(&exported).unwrap();
2400
2401 assert_eq!(
2402 reparsed.bone_frames[0].bone_name,
2403 parsed.bone_frames[0].bone_name
2404 );
2405 assert_eq!(reparsed.bone_frames[0].frame, parsed.bone_frames[0].frame);
2406 assert_eq!(
2407 reparsed.bone_frames[0].translation,
2408 parsed.bone_frames[0].translation
2409 );
2410 assert_eq!(
2411 reparsed.bone_frames[0].rotation,
2412 parsed.bone_frames[0].rotation
2413 );
2414 assert_eq!(
2415 reparsed.morph_frames[0].morph_name,
2416 parsed.morph_frames[0].morph_name
2417 );
2418 assert_eq!(reparsed.morph_frames[0].frame, parsed.morph_frames[0].frame);
2419 assert!((reparsed.morph_frames[0].weight - parsed.morph_frames[0].weight).abs() < 1e-6);
2420 }
2421
2422 fn expected_sjis_name_bytes(value: &str, len: usize) -> Vec<u8> {
2423 let (encoded, _, _) = encoding_rs::SHIFT_JIS.encode(value);
2424 encoded.as_ref()[..encoded.len().min(len)].to_vec()
2425 }
2426
2427 #[test]
2428 fn export_json_dto_encodes_shift_jis_when_raw_name_bytes_are_missing() {
2429 let animation = vmd_parsed_animation(
2430 "初音ミク".to_owned(),
2431 Vec::new(),
2432 30,
2433 VmdParsedSections {
2434 bone_frames: vec![VmdParsedBoneFrame {
2435 bone_name: "左足".to_owned(),
2436 bone_name_bytes: Vec::new(),
2437 frame: 10,
2438 translation: [1.0, 2.0, 3.0],
2439 rotation: [0.0, 0.0, 0.0, 1.0],
2440 interpolation: vec![20; 64],
2441 }],
2442 morph_frames: vec![VmdParsedMorphFrame {
2443 morph_name: "笑い".to_owned(),
2444 morph_name_bytes: Vec::new(),
2445 frame: 20,
2446 weight: 0.75,
2447 }],
2448 camera_frames: Vec::new(),
2449 light_frames: Vec::new(),
2450 self_shadow_frames: Vec::new(),
2451 property_frames: vec![VmdParsedPropertyFrame {
2452 frame: 30,
2453 visible: true,
2454 ik_states: vec![VmdParsedIkState {
2455 bone_name: "右足IK".to_owned(),
2456 bone_name_bytes: Vec::new(),
2457 enabled: false,
2458 }],
2459 }],
2460 },
2461 );
2462 let json = serde_json::to_string(&animation).unwrap();
2463 assert!(!json.contains("modelNameBytes"));
2464 assert!(!json.contains("boneNameBytes"));
2465 assert!(!json.contains("morphNameBytes"));
2466
2467 let from_json: VmdParsedAnimation = serde_json::from_str(&json).unwrap();
2468 let exported = export_vmd_animation(&from_json);
2469 let reparsed = parse_vmd_animation(&exported).unwrap();
2470
2471 assert_eq!(reparsed.metadata.model_name, "初音ミク");
2472 assert!(!reparsed.metadata.model_name_bytes.is_empty());
2473 assert_eq!(
2474 reparsed.metadata.model_name_bytes,
2475 expected_sjis_name_bytes("初音ミク", 20)
2476 );
2477 assert_eq!(reparsed.bone_frames[0].bone_name, "左足");
2478 assert!(!reparsed.bone_frames[0].bone_name_bytes.is_empty());
2479 assert_eq!(
2480 reparsed.bone_frames[0].bone_name_bytes,
2481 expected_sjis_name_bytes("左足", 15)
2482 );
2483 assert_eq!(reparsed.morph_frames[0].morph_name, "笑い");
2484 assert!(!reparsed.morph_frames[0].morph_name_bytes.is_empty());
2485 assert_eq!(
2486 reparsed.morph_frames[0].morph_name_bytes,
2487 expected_sjis_name_bytes("笑い", 15)
2488 );
2489 assert_eq!(reparsed.property_frames[0].ik_states[0].bone_name, "右足IK");
2490 assert!(
2491 !reparsed.property_frames[0].ik_states[0]
2492 .bone_name_bytes
2493 .is_empty()
2494 );
2495 assert_eq!(
2496 reparsed.property_frames[0].ik_states[0].bone_name_bytes,
2497 expected_sjis_name_bytes("右足IK", 20)
2498 );
2499 assert!(!reparsed.property_frames[0].ik_states[0].enabled);
2500 }
2501
2502 #[test]
2503 fn export_json_dto_uses_encoding_rs_replacement_for_non_shift_jis_names() {
2504 let animation = vmd_parsed_animation(
2505 "miku".to_owned(),
2506 Vec::new(),
2507 1,
2508 VmdParsedSections {
2509 bone_frames: vec![VmdParsedBoneFrame {
2510 bone_name: "左足🧪".to_owned(),
2511 bone_name_bytes: Vec::new(),
2512 frame: 1,
2513 translation: [0.0, 0.0, 0.0],
2514 rotation: [0.0, 0.0, 0.0, 1.0],
2515 interpolation: vec![20; 64],
2516 }],
2517 morph_frames: Vec::new(),
2518 camera_frames: Vec::new(),
2519 light_frames: Vec::new(),
2520 self_shadow_frames: Vec::new(),
2521 property_frames: Vec::new(),
2522 },
2523 );
2524 let json = serde_json::to_string(&animation).unwrap();
2525 let from_json: VmdParsedAnimation = serde_json::from_str(&json).unwrap();
2526 let exported = export_vmd_animation(&from_json);
2527 let reparsed = parse_vmd_animation(&exported).unwrap();
2528
2529 assert_eq!(
2530 reparsed.bone_frames[0].bone_name_bytes,
2531 expected_sjis_name_bytes("左足🧪", 15)
2532 );
2533 assert_eq!(
2534 reparsed.bone_frames[0].bone_name,
2535 encoding_rs::SHIFT_JIS
2536 .decode(&expected_sjis_name_bytes("左足🧪", 15))
2537 .0
2538 .trim()
2539 );
2540 }
2541}