Skip to main content

mmd_anim_runtime/runtime/
morph.rs

1use glam::Quat;
2
3use crate::MorphIndex;
4
5use super::RuntimeInstance;
6
7impl RuntimeInstance {
8    /// Expand group morphs and apply bone morph offsets.
9    ///
10    /// Called automatically from [`Self::evaluate_clip_frame`]. Exposed publicly so
11    /// that hosts manually driving [`crate::PoseArena`] can trigger morph expansion
12    /// before calling [`Self::evaluate_current_pose`].
13    pub fn expand_morphs(&mut self) {
14        self.expand_group_morphs();
15        self.apply_bone_morphs();
16    }
17
18    /// Pass 1: expand all group morph weights (updates morph_weights in-place).
19    /// Group morph children may appear before or after their parents in PMX, so
20    /// expansion follows the graph recursively using the model-validated
21    /// cycle-free group morph spans.
22    fn expand_group_morphs(&mut self) {
23        let spans = self.model.group_morph_spans();
24        let offsets = self.model.group_morph_offsets();
25        if spans.is_empty() || offsets.is_empty() {
26            return;
27        }
28        let mc = self.model.morph_count() as usize;
29        self.morph_scratch.expanded_weights.clear();
30        self.morph_scratch
31            .expanded_weights
32            .extend_from_slice(&self.pose.morph_weights()[..mc]);
33
34        for (morph_idx, &w) in self.pose.morph_weights()[..mc].iter().enumerate() {
35            if w == 0.0 {
36                continue;
37            }
38            expand_group_morph_weight(
39                morph_idx,
40                w,
41                spans,
42                offsets,
43                &mut self.morph_scratch.expanded_weights,
44            );
45        }
46        for (i, &w) in self.morph_scratch.expanded_weights.iter().enumerate() {
47            self.pose.set_morph_weight(MorphIndex(i as u32), w);
48        }
49    }
50
51    /// Pass 2: apply bone morph offsets using the final (expanded) morph
52    /// weights.
53    fn apply_bone_morphs(&mut self) {
54        let spans = self.model.bone_morph_spans();
55        let offsets = self.model.bone_morph_offsets();
56        if spans.is_empty() || offsets.is_empty() {
57            return;
58        }
59        for (morph_idx, span) in spans.iter().enumerate() {
60            let weight = self.pose.morph_weight(MorphIndex(morph_idx as u32));
61            if weight == 0.0 {
62                continue;
63            }
64            for i in span.start..span.start + span.count {
65                let off = &offsets[i as usize];
66                let pos = self.pose.local_position_offset(off.target_bone);
67                self.pose
68                    .set_local_position_offset(off.target_bone, pos + off.position_offset * weight);
69                let rot = self.pose.local_rotation(off.target_bone);
70                let scaled = Quat::IDENTITY.slerp(off.rotation_offset, weight);
71                self.pose
72                    .set_local_rotation(off.target_bone, (rot * scaled).normalize());
73            }
74        }
75    }
76
77    #[inline]
78    pub fn morph_weights(&self) -> &[f32] {
79        self.pose.morph_weights()
80    }
81}
82
83fn expand_group_morph_weight(
84    morph_idx: usize,
85    weight: f32,
86    spans: &[crate::MorphOffsetSpan],
87    offsets: &[crate::GroupMorphOffset],
88    expanded_weights: &mut [f32],
89) {
90    let span = spans[morph_idx];
91    for i in span.start..span.start + span.count {
92        let off = &offsets[i as usize];
93        let child = off.child_morph.as_usize();
94        let contribution = weight * off.ratio;
95        expanded_weights[child] += contribution;
96        if spans[child].count > 0 {
97            expand_group_morph_weight(child, contribution, spans, offsets, expanded_weights);
98        }
99    }
100}