Skip to main content

mmd_anim_runtime/runtime/
world.rs

1use glam::Mat4;
2
3use crate::{
4    append_primitive::{AppendPrimitiveInput, solve_append_transform},
5    ik_primitive::constrain_rotation_to_axis,
6};
7
8#[cfg(test)]
9use super::WorldMatrixBoneUpdateCategory;
10
11use super::RuntimeInstance;
12
13impl RuntimeInstance {
14    pub(super) fn update_world_matrices(&mut self) {
15        self.update_world_matrices_from_eval_order_position(0);
16    }
17
18    pub(super) fn update_world_matrices_from_eval_order_position(&mut self, start_position: usize) {
19        self.update_world_matrices_from_eval_order_position_for_phase(start_position, None);
20    }
21
22    pub(super) fn update_world_matrices_from_eval_order_position_for_phase(
23        &mut self,
24        start_position: usize,
25        phase: Option<bool>,
26    ) {
27        let start_position =
28            self.expand_update_start_for_append_dependencies(start_position, phase);
29        match phase {
30            None => {
31                for bone in &self.model.eval_order()[start_position..] {
32                    self.pose.reset_append_transform(*bone);
33                }
34                for position in start_position..self.model.eval_order().len() {
35                    let bone = self.model.eval_order()[position];
36                    self.update_append_transform_for_bone(bone);
37                    self.update_world_matrix_for_bone(bone);
38                }
39            }
40            Some(after_physics) => {
41                let phase_bone_count = self.model.eval_order_for_phase(after_physics).len();
42                for phase_index in 0..phase_bone_count {
43                    let bone = self.model.eval_order_for_phase(after_physics)[phase_index];
44                    if self.model.eval_order_position(bone) < start_position {
45                        continue;
46                    }
47                    self.pose.reset_append_transform(bone);
48                }
49                for phase_index in 0..phase_bone_count {
50                    let bone = self.model.eval_order_for_phase(after_physics)[phase_index];
51                    if self.model.eval_order_position(bone) < start_position {
52                        continue;
53                    }
54                    self.update_append_transform_for_bone(bone);
55                    self.update_world_matrix_for_bone(bone);
56                }
57            }
58        }
59    }
60
61    pub(super) fn update_world_matrices_using_current_append_from_eval_order_position(
62        &mut self,
63        start_position: usize,
64    ) {
65        self.update_world_matrices_using_current_append_from_eval_order_position_for_phase(
66            start_position,
67            None,
68        );
69    }
70
71    pub(super) fn update_world_matrices_using_current_append_from_eval_order_position_for_phase(
72        &mut self,
73        start_position: usize,
74        phase: Option<bool>,
75    ) {
76        match phase {
77            None => {
78                for position in start_position..self.model.eval_order().len() {
79                    let bone = self.model.eval_order()[position];
80                    self.update_world_matrix_for_bone(bone);
81                }
82            }
83            Some(after_physics) => {
84                let phase_bone_count = self.model.eval_order_for_phase(after_physics).len();
85                for phase_index in 0..phase_bone_count {
86                    let bone = self.model.eval_order_for_phase(after_physics)[phase_index];
87                    if self.model.eval_order_position(bone) < start_position {
88                        continue;
89                    }
90                    self.update_world_matrix_for_bone(bone);
91                }
92            }
93        }
94    }
95
96    #[inline]
97    pub(super) fn bone_matches_phase(&self, bone: crate::BoneIndex, phase: Option<bool>) -> bool {
98        phase.is_none_or(|after_physics| self.model.transform_after_physics(bone) == after_physics)
99    }
100
101    pub(super) fn update_append_transform_for_bone(&mut self, bone: crate::BoneIndex) {
102        let mut visiting = Vec::new();
103        self.update_append_transform_for_bone_inner(bone, &mut visiting);
104    }
105
106    fn update_append_transform_for_bone_inner(
107        &mut self,
108        bone: crate::BoneIndex,
109        visiting: &mut Vec<crate::BoneIndex>,
110    ) {
111        let Some(append_index) = self.model.append_transform_index(bone) else {
112            return;
113        };
114        if visiting.contains(&bone) {
115            return;
116        }
117        visiting.push(bone);
118        let append = self.model.append_transform(append_index);
119        let source_bone = append.source_bone;
120        let ratio = append.ratio;
121        let affect_rotation = append.affect_rotation;
122        let affect_translation = append.affect_translation;
123        let use_source_append =
124            !append.local && self.model.append_transform_index(source_bone).is_some();
125        if use_source_append {
126            self.update_append_transform_for_bone_inner(source_bone, visiting);
127        }
128        let mut source_rotation = if use_source_append {
129            self.pose.append_rotation(source_bone)
130        } else {
131            self.pose.local_rotation(source_bone)
132        };
133        if use_source_append && self.model.is_ik_link_bone(source_bone) {
134            source_rotation = (self.pose.ik_rotation(source_bone) * source_rotation).normalize();
135        }
136        let source_position_offset = if use_source_append {
137            self.pose.append_position_offset(source_bone)
138        } else {
139            self.pose.local_position_offset(source_bone)
140        };
141        let append_output = solve_append_transform(AppendPrimitiveInput {
142            source_position_offset,
143            source_rotation,
144            ratio,
145            affect_rotation,
146            affect_translation,
147        });
148        self.pose.set_append_rotation(bone, append_output.rotation);
149        self.pose
150            .set_append_position_offset(bone, append_output.position_offset);
151        visiting.pop();
152    }
153
154    pub(super) fn update_world_matrix_for_bone(&mut self, bone: crate::BoneIndex) {
155        #[cfg(test)]
156        {
157            self.world_matrix_bone_update_count += 1;
158            match self.world_matrix_bone_update_category {
159                WorldMatrixBoneUpdateCategory::LeadingBookend => {
160                    self.world_matrix_bone_update_leading_bookend_count += 1;
161                }
162                WorldMatrixBoneUpdateCategory::PhaseLoop => {
163                    self.world_matrix_bone_update_phase_loop_count += 1;
164                }
165                WorldMatrixBoneUpdateCategory::TrailingBookend => {
166                    self.world_matrix_bone_update_trailing_bookend_count += 1;
167                }
168                WorldMatrixBoneUpdateCategory::IkLinkChange => {
169                    self.world_matrix_bone_update_ik_link_change_count += 1;
170                }
171                WorldMatrixBoneUpdateCategory::Other => {
172                    self.world_matrix_bone_update_other_count += 1;
173                }
174            }
175        }
176        let mut local_position =
177            self.model.rest_position(bone) + self.pose.local_position_offset(bone);
178        let mut local_rotation = self.pose.local_rotation(bone);
179        let local_scale = self.pose.local_scale(bone);
180
181        if let Some(append_index) = self.model.append_transform_index(bone) {
182            let append = self.model.append_transform(append_index);
183            if append.affect_rotation {
184                local_rotation = (local_rotation * self.pose.append_rotation(bone)).normalize();
185            }
186            if append.affect_translation {
187                local_position += self.pose.append_position_offset(bone);
188            }
189        }
190
191        if let Some(axis) = self.model.fixed_axis_constraint(bone) {
192            local_rotation = constrain_rotation_to_axis(local_rotation, axis);
193        }
194
195        let local_matrix = Mat4::from_scale_rotation_translation(
196            local_scale.into(),
197            local_rotation,
198            local_position.into(),
199        );
200
201        let world_matrix = match self.model.parent_index(bone) {
202            Some(parent) => self.pose.world_matrices()[parent.as_usize()] * local_matrix,
203            None => local_matrix,
204        };
205
206        self.pose.set_world_matrix(bone, world_matrix);
207        self.pose
208            .set_skinning_matrix(bone, world_matrix * self.model.inverse_bind_matrix(bone));
209    }
210
211    pub(super) fn expand_update_start_for_append_dependencies(
212        &self,
213        start_position: usize,
214        phase: Option<bool>,
215    ) -> usize {
216        let mut start = start_position;
217        loop {
218            let mut changed = false;
219            for append in self.model.append_transforms() {
220                if !self.bone_matches_phase(append.target_bone, phase) {
221                    continue;
222                }
223                let source_position = self.model.eval_order_position(append.source_bone);
224                let target_position = self.model.eval_order_position(append.target_bone);
225                if source_position >= start && target_position < start {
226                    start = target_position;
227                    changed = true;
228                }
229            }
230            if !changed {
231                return start;
232            }
233        }
234    }
235
236    #[inline]
237    pub fn world_matrices(&self) -> &[Mat4] {
238        self.pose.world_matrices()
239    }
240
241    #[cfg(test)]
242    pub(super) fn set_world_matrix_bone_update_category(
243        &mut self,
244        category: WorldMatrixBoneUpdateCategory,
245    ) {
246        self.world_matrix_bone_update_category = category;
247    }
248
249    #[cfg(test)]
250    pub(super) fn reset_world_matrix_bone_update_count(&mut self) {
251        self.world_matrix_bone_update_count = 0;
252        self.world_matrix_bone_update_leading_bookend_count = 0;
253        self.world_matrix_bone_update_phase_loop_count = 0;
254        self.world_matrix_bone_update_trailing_bookend_count = 0;
255        self.world_matrix_bone_update_ik_link_change_count = 0;
256        self.world_matrix_bone_update_other_count = 0;
257        self.world_matrix_bone_update_category = WorldMatrixBoneUpdateCategory::Other;
258    }
259
260    #[cfg(test)]
261    pub(super) fn world_matrix_bone_update_count(&self) -> usize {
262        self.world_matrix_bone_update_count
263    }
264
265    #[cfg(test)]
266    pub(super) fn world_matrix_bone_update_leading_bookend_count(&self) -> usize {
267        self.world_matrix_bone_update_leading_bookend_count
268    }
269
270    #[cfg(test)]
271    pub(super) fn world_matrix_bone_update_phase_loop_count(&self) -> usize {
272        self.world_matrix_bone_update_phase_loop_count
273    }
274
275    #[cfg(test)]
276    pub(super) fn world_matrix_bone_update_trailing_bookend_count(&self) -> usize {
277        self.world_matrix_bone_update_trailing_bookend_count
278    }
279
280    #[cfg(test)]
281    pub(super) fn world_matrix_bone_update_ik_link_change_count(&self) -> usize {
282        self.world_matrix_bone_update_ik_link_change_count
283    }
284
285    #[cfg(test)]
286    pub(super) fn world_matrix_bone_update_other_count(&self) -> usize {
287        self.world_matrix_bone_update_other_count
288    }
289
290    #[inline]
291    pub fn skinning_matrices(&self) -> &[Mat4] {
292        self.pose.skinning_matrices()
293    }
294}