1use glam::{Quat, Vec3A};
2
3use crate::{BoneIndex, MorphIndex, PoseArena};
4
5const BEZIER_ITERATIONS: usize = 12;
6const MMD_INTERPOLATION_SCALE: f32 = 1.0 / 127.0;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct InterpolationScalar {
10 pub x1: u8,
11 pub y1: u8,
12 pub x2: u8,
13 pub y2: u8,
14}
15
16impl InterpolationScalar {
17 pub const fn linear() -> Self {
18 Self {
19 x1: 20,
20 y1: 20,
21 x2: 107,
22 y2: 107,
23 }
24 }
25
26 pub fn evaluate(self, x: f32) -> f32 {
27 let x = x.clamp(0.0, 1.0);
28 if x <= 0.0 {
29 return 0.0;
30 }
31 if x >= 1.0 {
32 return 1.0;
33 }
34 if self.x1 == self.y1 && self.x2 == self.y2 {
35 return x;
36 }
37 bezier_interpolation(
38 self.x1 as f32 * MMD_INTERPOLATION_SCALE,
39 self.x2 as f32 * MMD_INTERPOLATION_SCALE,
40 self.y1 as f32 * MMD_INTERPOLATION_SCALE,
41 self.y2 as f32 * MMD_INTERPOLATION_SCALE,
42 x,
43 )
44 }
45}
46
47impl Default for InterpolationScalar {
48 fn default() -> Self {
49 Self::linear()
50 }
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub struct InterpolationVector3 {
55 pub x: InterpolationScalar,
56 pub y: InterpolationScalar,
57 pub z: InterpolationScalar,
58}
59
60impl InterpolationVector3 {
61 pub const fn linear() -> Self {
62 Self {
63 x: InterpolationScalar::linear(),
64 y: InterpolationScalar::linear(),
65 z: InterpolationScalar::linear(),
66 }
67 }
68}
69
70impl Default for InterpolationVector3 {
71 fn default() -> Self {
72 Self::linear()
73 }
74}
75
76#[derive(Clone, Debug)]
77pub struct MovableBoneKeyframe {
78 pub frame: u32,
79 pub position: Vec3A,
80 pub rotation: Quat,
81 pub position_interpolation: InterpolationVector3,
82 pub rotation_interpolation: InterpolationScalar,
83}
84
85impl MovableBoneKeyframe {
86 pub fn new(frame: u32, position: Vec3A, rotation: Quat) -> Self {
87 Self {
88 frame,
89 position,
90 rotation,
91 position_interpolation: InterpolationVector3::linear(),
92 rotation_interpolation: InterpolationScalar::linear(),
93 }
94 }
95}
96
97#[derive(Clone, Debug)]
98pub struct MovableBoneTrack {
99 frame_numbers: Box<[u32]>,
100 positions: Box<[Vec3A]>,
101 rotations: Box<[Quat]>,
102 position_interpolations: Box<[InterpolationVector3]>,
103 rotation_interpolations: Box<[InterpolationScalar]>,
104}
105
106impl MovableBoneTrack {
107 pub fn from_keyframes(mut keyframes: Vec<MovableBoneKeyframe>) -> Self {
108 keyframes.sort_by_key(|keyframe| keyframe.frame);
109
110 let mut frame_numbers = Vec::with_capacity(keyframes.len());
111 let mut positions = Vec::with_capacity(keyframes.len());
112 let mut rotations = Vec::with_capacity(keyframes.len());
113 let mut position_interpolations = Vec::with_capacity(keyframes.len());
114 let mut rotation_interpolations = Vec::with_capacity(keyframes.len());
115
116 for keyframe in keyframes {
117 frame_numbers.push(keyframe.frame);
118 positions.push(keyframe.position);
119 rotations.push(keyframe.rotation.normalize());
120 position_interpolations.push(keyframe.position_interpolation);
121 rotation_interpolations.push(keyframe.rotation_interpolation);
122 }
123
124 Self {
125 frame_numbers: frame_numbers.into_boxed_slice(),
126 positions: positions.into_boxed_slice(),
127 rotations: rotations.into_boxed_slice(),
128 position_interpolations: position_interpolations.into_boxed_slice(),
129 rotation_interpolations: rotation_interpolations.into_boxed_slice(),
130 }
131 }
132
133 pub fn sample(&self, frame: f32) -> Option<(Vec3A, Quat)> {
134 match self.frame_numbers.len() {
135 0 => None,
136 1 => Some((self.positions[0], self.rotations[0])),
137 _ => {
138 let next_index = self.find_next_keyframe(frame);
139 if next_index == 0 {
140 return Some((self.positions[0], self.rotations[0]));
141 }
142 if next_index >= self.frame_numbers.len() {
143 let last = self.frame_numbers.len() - 1;
144 return Some((self.positions[last], self.rotations[last]));
145 }
146
147 let prev_index = next_index - 1;
148 let prev_frame = self.frame_numbers[prev_index] as f32;
149 let next_frame = self.frame_numbers[next_index] as f32;
150 let frame_t = if next_frame == prev_frame {
151 0.0
152 } else {
153 ((frame - prev_frame) / (next_frame - prev_frame)).clamp(0.0, 1.0)
154 };
155
156 let interpolation = self.position_interpolations[next_index];
157 let position = Vec3A::new(
158 lerp(
159 self.positions[prev_index].x,
160 self.positions[next_index].x,
161 interpolation.x.evaluate(frame_t),
162 ),
163 lerp(
164 self.positions[prev_index].y,
165 self.positions[next_index].y,
166 interpolation.y.evaluate(frame_t),
167 ),
168 lerp(
169 self.positions[prev_index].z,
170 self.positions[next_index].z,
171 interpolation.z.evaluate(frame_t),
172 ),
173 );
174
175 let rotation_t = self.rotation_interpolations[next_index].evaluate(frame_t);
176 let rotation =
177 self.rotations[prev_index].slerp(self.rotations[next_index], rotation_t);
178
179 Some((position, rotation))
180 }
181 }
182 }
183
184 fn find_next_keyframe(&self, frame: f32) -> usize {
185 self.frame_numbers
186 .partition_point(|keyframe| (*keyframe as f32) <= frame)
187 }
188
189 fn frame_range(&self) -> Option<(u32, u32)> {
190 Some((*self.frame_numbers.first()?, *self.frame_numbers.last()?))
191 }
192}
193
194#[derive(Clone, Debug)]
195pub struct BoneAnimationBinding {
196 pub bone: BoneIndex,
197 pub track: MovableBoneTrack,
198}
199
200#[derive(Clone, Copy, Debug, PartialEq)]
201pub struct MorphKeyframe {
202 pub frame: u32,
203 pub weight: f32,
204}
205
206impl MorphKeyframe {
207 pub fn new(frame: u32, weight: f32) -> Self {
208 Self { frame, weight }
209 }
210}
211
212#[derive(Clone, Debug)]
213pub struct MorphTrack {
214 frame_numbers: Box<[u32]>,
215 weights: Box<[f32]>,
216}
217
218impl MorphTrack {
219 pub fn from_keyframes(mut keyframes: Vec<MorphKeyframe>) -> Self {
220 keyframes.sort_by_key(|keyframe| keyframe.frame);
221 let mut frame_numbers = Vec::with_capacity(keyframes.len());
222 let mut weights = Vec::with_capacity(keyframes.len());
223 for keyframe in keyframes {
224 frame_numbers.push(keyframe.frame);
225 weights.push(keyframe.weight);
226 }
227 Self {
228 frame_numbers: frame_numbers.into_boxed_slice(),
229 weights: weights.into_boxed_slice(),
230 }
231 }
232
233 pub fn sample(&self, frame: f32) -> Option<f32> {
234 match self.frame_numbers.len() {
235 0 => None,
236 1 => Some(self.weights[0]),
237 _ => {
238 let next_index = self
239 .frame_numbers
240 .partition_point(|keyframe| (*keyframe as f32) <= frame);
241 if next_index == 0 {
242 return Some(self.weights[0]);
243 }
244 if next_index >= self.frame_numbers.len() {
245 return Some(self.weights[self.weights.len() - 1]);
246 }
247
248 let prev_index = next_index - 1;
249 let prev_frame = self.frame_numbers[prev_index] as f32;
250 let next_frame = self.frame_numbers[next_index] as f32;
251 let frame_t = if next_frame == prev_frame {
252 0.0
253 } else {
254 ((frame - prev_frame) / (next_frame - prev_frame)).clamp(0.0, 1.0)
255 };
256 Some(lerp(
257 self.weights[prev_index],
258 self.weights[next_index],
259 frame_t,
260 ))
261 }
262 }
263 }
264
265 fn frame_range(&self) -> Option<(u32, u32)> {
266 Some((*self.frame_numbers.first()?, *self.frame_numbers.last()?))
267 }
268}
269
270#[derive(Clone, Debug)]
271pub struct MorphAnimationBinding {
272 pub morph: MorphIndex,
273 pub track: MorphTrack,
274}
275
276#[derive(Clone, Debug, PartialEq, Eq)]
277pub struct PropertyKeyframe {
278 pub frame: u32,
279 pub ik_enabled: Box<[u8]>,
280}
281
282impl PropertyKeyframe {
283 pub fn new(frame: u32, ik_enabled: Vec<bool>) -> Self {
284 Self {
285 frame,
286 ik_enabled: ik_enabled
287 .into_iter()
288 .map(u8::from)
289 .collect::<Vec<_>>()
290 .into_boxed_slice(),
291 }
292 }
293}
294
295#[derive(Clone, Debug)]
296pub struct PropertyAnimationBinding {
297 frame_numbers: Box<[u32]>,
298 ik_enabled: Box<[Box<[u8]>]>,
299}
300
301impl PropertyAnimationBinding {
302 pub fn from_keyframes(mut keyframes: Vec<PropertyKeyframe>) -> Self {
303 keyframes.sort_by_key(|keyframe| keyframe.frame);
304
305 let mut frame_numbers = Vec::with_capacity(keyframes.len());
306 let mut ik_enabled = Vec::with_capacity(keyframes.len());
307 for keyframe in keyframes {
308 frame_numbers.push(keyframe.frame);
309 ik_enabled.push(keyframe.ik_enabled);
310 }
311
312 Self {
313 frame_numbers: frame_numbers.into_boxed_slice(),
314 ik_enabled: ik_enabled.into_boxed_slice(),
315 }
316 }
317
318 pub fn sample(&self, frame: f32) -> Option<&[u8]> {
319 match self.frame_numbers.len() {
320 0 => None,
321 _ => {
322 let next_index = self
323 .frame_numbers
324 .partition_point(|keyframe| (*keyframe as f32) <= frame);
325 if next_index == 0 {
326 None
327 } else {
328 Some(&self.ik_enabled[next_index - 1])
329 }
330 }
331 }
332 }
333
334 fn frame_range(&self) -> Option<(u32, u32)> {
335 Some((*self.frame_numbers.first()?, *self.frame_numbers.last()?))
336 }
337}
338
339#[derive(Clone, Debug, Default)]
340pub struct AnimationClip {
341 bone_tracks: Box<[BoneAnimationBinding]>,
342 morph_tracks: Box<[MorphAnimationBinding]>,
343 property_track: Option<PropertyAnimationBinding>,
344}
345
346impl AnimationClip {
347 pub fn new(bone_tracks: Vec<BoneAnimationBinding>) -> Self {
348 Self::new_with_morphs(bone_tracks, Vec::new())
349 }
350
351 pub fn new_with_morphs(
352 bone_tracks: Vec<BoneAnimationBinding>,
353 morph_tracks: Vec<MorphAnimationBinding>,
354 ) -> Self {
355 Self::new_full(bone_tracks, morph_tracks, None)
356 }
357
358 pub fn new_full(
359 bone_tracks: Vec<BoneAnimationBinding>,
360 morph_tracks: Vec<MorphAnimationBinding>,
361 property_track: Option<PropertyAnimationBinding>,
362 ) -> Self {
363 Self {
364 bone_tracks: bone_tracks.into_boxed_slice(),
365 morph_tracks: morph_tracks.into_boxed_slice(),
366 property_track,
367 }
368 }
369
370 pub fn apply_to_pose(&self, frame: f32, pose: &mut PoseArena) {
371 pose.reset_local_pose();
372 for binding in self.bone_tracks.iter() {
373 if let Some((position, rotation)) = binding.track.sample(frame) {
374 pose.set_local_position_offset(binding.bone, position);
375 pose.set_local_rotation(binding.bone, rotation);
376 }
377 }
378 for binding in self.morph_tracks.iter() {
379 if let Some(weight) = binding.track.sample(frame) {
380 pose.set_morph_weight(binding.morph, weight);
381 }
382 }
383 if let Some(ik_enabled) = self
384 .property_track
385 .as_ref()
386 .and_then(|track| track.sample(frame))
387 {
388 for (ik_index, enabled) in ik_enabled.iter().enumerate() {
389 pose.set_ik_enabled(ik_index, *enabled != 0);
390 }
391 }
392 }
393
394 pub fn bone_track_count(&self) -> usize {
395 self.bone_tracks.len()
396 }
397
398 pub fn morph_track_count(&self) -> usize {
399 self.morph_tracks.len()
400 }
401
402 pub fn has_property_track(&self) -> bool {
403 self.property_track.is_some()
404 }
405
406 pub fn frame_range(&self) -> Option<(u32, u32)> {
407 let mut range: Option<(u32, u32)> = None;
408 for binding in self.bone_tracks.iter() {
409 merge_frame_range(&mut range, binding.track.frame_range());
410 }
411 for binding in self.morph_tracks.iter() {
412 merge_frame_range(&mut range, binding.track.frame_range());
413 }
414 if let Some(property_track) = self.property_track.as_ref() {
415 merge_frame_range(&mut range, property_track.frame_range());
416 }
417 range
418 }
419
420 pub fn find_bone_track(&self, bone: BoneIndex) -> Option<&MovableBoneTrack> {
421 self.bone_tracks
422 .iter()
423 .find(|binding| binding.bone == bone)
424 .map(|binding| &binding.track)
425 }
426}
427
428fn merge_frame_range(target: &mut Option<(u32, u32)>, range: Option<(u32, u32)>) {
429 let Some((first, last)) = range else {
430 return;
431 };
432 *target = Some(match *target {
433 Some((current_first, current_last)) => (current_first.min(first), current_last.max(last)),
434 None => (first, last),
435 });
436}
437
438fn lerp(a: f32, b: f32, t: f32) -> f32 {
439 a + (b - a) * t
440}
441
442fn bezier_interpolation(x1: f32, x2: f32, y1: f32, y2: f32, x: f32) -> f32 {
443 let mut c = 0.5;
444 let mut t = c;
445 let mut s = 1.0 - t;
446
447 let mut sst3;
448 let mut stt3;
449 let mut ttt;
450
451 for _ in 0..BEZIER_ITERATIONS {
452 sst3 = 3.0 * s * s * t;
453 stt3 = 3.0 * s * t * t;
454 ttt = t * t * t;
455
456 let ft = sst3 * x1 + stt3 * x2 + ttt - x;
457 if ft == 0.0 {
458 return sst3 * y1 + stt3 * y2 + ttt;
459 }
460
461 c *= 0.5;
462 t += if ft < 0.0 { c } else { -c };
463 s = 1.0 - t;
464 }
465
466 sst3 = 3.0 * s * s * t;
467 stt3 = 3.0 * s * t * t;
468 ttt = t * t * t;
469 sst3 * y1 + stt3 * y2 + ttt
470}
471
472#[cfg(test)]
473mod tests {
474 use glam::{Quat, Vec3A};
475
476 use super::*;
477
478 fn assert_near(actual: f32, expected: f32) {
479 let delta = (actual - expected).abs();
480 assert!(
481 delta < 1.0e-4,
482 "actual={actual:?} expected={expected:?} delta={delta:?}"
483 );
484 }
485
486 fn assert_vec3a_near(actual: Vec3A, expected: Vec3A) {
487 let delta = (actual - expected).abs();
488 assert!(
489 delta.x < 1.0e-4 && delta.y < 1.0e-4 && delta.z < 1.0e-4,
490 "actual={actual:?} expected={expected:?} delta={delta:?}"
491 );
492 }
493
494 #[test]
495 fn linear_interpolation_maps_half_to_half() {
496 assert_near(InterpolationScalar::linear().evaluate(0.5), 0.5);
497 }
498
499 #[test]
500 fn mmd_camera_ease_out_matches_native_subdivision_points() {
501 let interpolation = InterpolationScalar {
502 x1: 0,
503 y1: 127,
504 x2: 127,
505 y2: 127,
506 };
507
508 assert_near(interpolation.evaluate(1.0 / 6.0), 0.5933867);
509 assert_near(interpolation.evaluate(1.0 / 3.0), 0.76974934);
510 assert_near(interpolation.evaluate(0.5), 0.875);
511 assert_near(interpolation.evaluate(2.0 / 3.0), 0.9420012);
512 assert_near(interpolation.evaluate(5.0 / 6.0), 0.9825947);
513 }
514
515 #[test]
516 fn samples_movable_bone_track() {
517 let track = MovableBoneTrack::from_keyframes(vec![
518 MovableBoneKeyframe::new(20, Vec3A::new(10.0, 0.0, 0.0), Quat::IDENTITY),
519 MovableBoneKeyframe::new(10, Vec3A::ZERO, Quat::IDENTITY),
520 ]);
521
522 let (position, rotation) = track.sample(15.0).unwrap();
523
524 assert_vec3a_near(position, Vec3A::new(5.0, 0.0, 0.0));
525 assert_near(rotation.dot(Quat::IDENTITY).abs(), 1.0);
526 }
527
528 #[test]
529 fn samples_morph_track() {
530 let track = MorphTrack::from_keyframes(vec![
531 MorphKeyframe::new(60, 1.0),
532 MorphKeyframe::new(0, 0.0),
533 ]);
534
535 assert_near(track.sample(30.0).unwrap(), 0.5);
536 }
537
538 #[test]
539 fn samples_property_track_as_step_state() {
540 let track = PropertyAnimationBinding::from_keyframes(vec![
541 PropertyKeyframe::new(30, vec![false, true]),
542 PropertyKeyframe::new(0, vec![true, true]),
543 ]);
544
545 assert_eq!(track.sample(-1.0), None);
546 assert_eq!(track.sample(29.0).unwrap(), &[1, 1]);
547 assert_eq!(track.sample(30.0).unwrap(), &[0, 1]);
548 assert_eq!(track.sample(60.0).unwrap(), &[0, 1]);
549 }
550
551 #[test]
552 fn property_track_returns_none_before_first_keyframe() {
553 let track =
554 PropertyAnimationBinding::from_keyframes(vec![PropertyKeyframe::new(30, vec![false])]);
555
556 assert_eq!(track.sample(29.0), None);
557 assert_eq!(track.sample(30.0).unwrap(), &[0]);
558 }
559
560 #[test]
561 fn clip_frame_range_spans_all_track_types() {
562 let bone_track = BoneAnimationBinding {
563 bone: BoneIndex(0),
564 track: MovableBoneTrack::from_keyframes(vec![
565 MovableBoneKeyframe::new(30, Vec3A::ZERO, Quat::IDENTITY),
566 MovableBoneKeyframe::new(10, Vec3A::ZERO, Quat::IDENTITY),
567 ]),
568 };
569 let morph_track = MorphAnimationBinding {
570 morph: MorphIndex(0),
571 track: MorphTrack::from_keyframes(vec![
572 MorphKeyframe::new(20, 0.0),
573 MorphKeyframe::new(60, 1.0),
574 ]),
575 };
576 let property_track = PropertyAnimationBinding::from_keyframes(vec![
577 PropertyKeyframe::new(5, vec![true]),
578 PropertyKeyframe::new(40, vec![false]),
579 ]);
580 let clip =
581 AnimationClip::new_full(vec![bone_track], vec![morph_track], Some(property_track));
582
583 assert_eq!(clip.frame_range(), Some((5, 60)));
584 }
585
586 #[test]
587 fn empty_clip_frame_range_is_none() {
588 assert_eq!(AnimationClip::default().frame_range(), None);
589 }
590}