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