Skip to main content

mmd_anim_runtime/
animation.rs

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}