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
8use super::RuntimeInstance;
9
10impl RuntimeInstance {
11    pub(super) fn update_world_matrices(&mut self) {
12        self.update_world_matrices_from_eval_order_position(0);
13    }
14
15    pub(super) fn update_world_matrices_from_eval_order_position(&mut self, start_position: usize) {
16        self.update_world_matrices_from_eval_order_position_for_phase(start_position, None);
17    }
18
19    pub(super) fn update_world_matrices_from_eval_order_position_for_phase(
20        &mut self,
21        start_position: usize,
22        phase: Option<bool>,
23    ) {
24        let start_position =
25            self.expand_update_start_for_append_dependencies(start_position, phase);
26        for bone in &self.model.eval_order()[start_position..] {
27            if !self.bone_matches_phase(*bone, phase) {
28                continue;
29            }
30            self.pose.reset_append_transform(*bone);
31        }
32        for position in start_position..self.model.eval_order().len() {
33            let bone = self.model.eval_order()[position];
34            if !self.bone_matches_phase(bone, phase) {
35                continue;
36            }
37            self.update_append_transform_for_bone(bone);
38            self.update_world_matrix_for_bone(bone);
39        }
40    }
41
42    pub(super) fn update_world_matrices_using_current_append_from_eval_order_position(
43        &mut self,
44        start_position: usize,
45    ) {
46        self.update_world_matrices_using_current_append_from_eval_order_position_for_phase(
47            start_position,
48            None,
49        );
50    }
51
52    pub(super) fn update_world_matrices_using_current_append_from_eval_order_position_for_phase(
53        &mut self,
54        start_position: usize,
55        phase: Option<bool>,
56    ) {
57        for position in start_position..self.model.eval_order().len() {
58            let bone = self.model.eval_order()[position];
59            if !self.bone_matches_phase(bone, phase) {
60                continue;
61            }
62            self.update_world_matrix_for_bone(bone);
63        }
64    }
65
66    #[inline]
67    pub(super) fn bone_matches_phase(&self, bone: crate::BoneIndex, phase: Option<bool>) -> bool {
68        phase.is_none_or(|after_physics| self.model.transform_after_physics(bone) == after_physics)
69    }
70
71    pub(super) fn update_append_transform_for_bone(&mut self, bone: crate::BoneIndex) {
72        let Some(append_index) = self.model.append_transform_index(bone) else {
73            return;
74        };
75        let append = self.model.append_transform(append_index);
76        let use_source_append = !append.local
77            && self
78                .model
79                .append_transform_index(append.source_bone)
80                .is_some();
81        let mut source_rotation = if use_source_append {
82            self.pose.append_rotation(append.source_bone)
83        } else {
84            self.pose.local_rotation(append.source_bone)
85        };
86        if use_source_append && self.model.is_ik_link_bone(append.source_bone) {
87            source_rotation =
88                (self.pose.ik_rotation(append.source_bone) * source_rotation).normalize();
89        }
90        let source_position_offset = if use_source_append {
91            self.pose.append_position_offset(append.source_bone)
92        } else {
93            self.pose.local_position_offset(append.source_bone)
94        };
95        let append_output = solve_append_transform(AppendPrimitiveInput {
96            source_position_offset,
97            source_rotation,
98            ratio: append.ratio,
99            affect_rotation: append.affect_rotation,
100            affect_translation: append.affect_translation,
101        });
102        self.pose.set_append_rotation(bone, append_output.rotation);
103        self.pose
104            .set_append_position_offset(bone, append_output.position_offset);
105    }
106
107    pub(super) fn update_world_matrix_for_bone(&mut self, bone: crate::BoneIndex) {
108        #[cfg(test)]
109        {
110            self.world_matrix_bone_update_count += 1;
111        }
112        let mut local_position =
113            self.model.rest_position(bone) + self.pose.local_position_offset(bone);
114        let mut local_rotation = self.pose.local_rotation(bone);
115        let local_scale = self.pose.local_scale(bone);
116
117        if let Some(append_index) = self.model.append_transform_index(bone) {
118            let append = self.model.append_transform(append_index);
119            if append.affect_rotation {
120                local_rotation = (local_rotation * self.pose.append_rotation(bone)).normalize();
121            }
122            if append.affect_translation {
123                local_position += self.pose.append_position_offset(bone);
124            }
125        }
126
127        if let Some(axis) = self.model.fixed_axis_constraint(bone) {
128            local_rotation = constrain_rotation_to_axis(local_rotation, axis);
129        }
130
131        let local_matrix = Mat4::from_scale_rotation_translation(
132            local_scale.into(),
133            local_rotation,
134            local_position.into(),
135        );
136
137        let world_matrix = match self.model.parent_index(bone) {
138            Some(parent) => self.pose.world_matrices()[parent.as_usize()] * local_matrix,
139            None => local_matrix,
140        };
141
142        self.pose.set_world_matrix(bone, world_matrix);
143        self.pose
144            .set_skinning_matrix(bone, world_matrix * self.model.inverse_bind_matrix(bone));
145    }
146
147    pub(super) fn expand_update_start_for_append_dependencies(
148        &self,
149        start_position: usize,
150        phase: Option<bool>,
151    ) -> usize {
152        let mut start = start_position;
153        loop {
154            let mut changed = false;
155            for append in self.model.append_transforms() {
156                if !self.bone_matches_phase(append.target_bone, phase) {
157                    continue;
158                }
159                let source_position = self.model.eval_order_position(append.source_bone);
160                let target_position = self.model.eval_order_position(append.target_bone);
161                if source_position >= start && target_position < start {
162                    start = target_position;
163                    changed = true;
164                }
165            }
166            if !changed {
167                return start;
168            }
169        }
170    }
171
172    #[inline]
173    pub fn world_matrices(&self) -> &[Mat4] {
174        self.pose.world_matrices()
175    }
176
177    #[cfg(test)]
178    pub(super) fn reset_world_matrix_bone_update_count(&mut self) {
179        self.world_matrix_bone_update_count = 0;
180    }
181
182    #[cfg(test)]
183    pub(super) fn world_matrix_bone_update_count(&self) -> usize {
184        self.world_matrix_bone_update_count
185    }
186
187    #[inline]
188    pub fn skinning_matrices(&self) -> &[Mat4] {
189        self.pose.skinning_matrices()
190    }
191}