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