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