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 in the same depth-first order as the former
21    /// recursive implementation, using a reusable heap scratch stack.  The
22    /// model has already rejected cycles, so the stack depth is bounded by the
23    /// morph count.
24    fn expand_group_morphs(&mut self) {
25        let spans = self.model.group_morph_spans();
26        let offsets = self.model.group_morph_offsets();
27        if spans.is_empty() || offsets.is_empty() {
28            return;
29        }
30        let mc = self.model.morph_count() as usize;
31        self.morph_scratch.expanded_weights.clear();
32        self.morph_scratch
33            .expanded_weights
34            .extend_from_slice(&self.pose.morph_weights()[..mc]);
35
36        let expanded_weights = &mut self.morph_scratch.expanded_weights;
37        let group_stack = &mut self.morph_scratch.group_stack;
38        group_stack.clear();
39        for (morph_idx, &w) in self.pose.morph_weights()[..mc].iter().enumerate() {
40            if w == 0.0 {
41                continue;
42            }
43            group_stack.push(super::GroupMorphFrame {
44                morph_idx,
45                weight: w,
46                next_offset: 0,
47            });
48            while let Some(frame) = group_stack.last_mut() {
49                let span = spans[frame.morph_idx];
50                if frame.next_offset >= span.count {
51                    group_stack.pop();
52                    continue;
53                }
54
55                let offset_index = span.start as usize + frame.next_offset as usize;
56                frame.next_offset += 1;
57                let offset = offsets[offset_index];
58                let child = offset.child_morph.as_usize();
59                let contribution = frame.weight * offset.ratio;
60                // Keep this addition in the exact order used by the
61                // recursive implementation; callers may depend on f32
62                // rounding for overlapping group paths.
63                expanded_weights[child] += contribution;
64                if spans[child].count > 0 {
65                    group_stack.push(super::GroupMorphFrame {
66                        morph_idx: child,
67                        weight: contribution,
68                        next_offset: 0,
69                    });
70                }
71            }
72        }
73        for (i, &w) in expanded_weights.iter().enumerate() {
74            self.pose.set_morph_weight(MorphIndex(i as u32), w);
75        }
76    }
77
78    /// Pass 2: apply bone morph offsets using the final (expanded) morph
79    /// weights.
80    fn apply_bone_morphs(&mut self) {
81        let spans = self.model.bone_morph_spans();
82        let offsets = self.model.bone_morph_offsets();
83        if spans.is_empty() || offsets.is_empty() {
84            return;
85        }
86        for (morph_idx, span) in spans.iter().enumerate() {
87            let weight = self.pose.morph_weight(MorphIndex(morph_idx as u32));
88            if weight == 0.0 {
89                continue;
90            }
91            for i in span.start..span.start + span.count {
92                let off = &offsets[i as usize];
93                let pos = self.pose.local_position_offset(off.target_bone);
94                self.pose
95                    .set_local_position_offset(off.target_bone, pos + off.position_offset * weight);
96                let rot = self.pose.local_rotation(off.target_bone);
97                let scaled = Quat::IDENTITY.slerp(off.rotation_offset, weight);
98                self.pose
99                    .set_local_rotation(off.target_bone, (rot * scaled).normalize());
100            }
101        }
102    }
103
104    #[inline]
105    pub fn morph_weights(&self) -> &[f32] {
106        self.pose.morph_weights()
107    }
108}