1use std::sync::Arc;
2
3use glam::Quat;
4
5use crate::ik_primitive::ChainLinkState;
6use crate::{AnimationClip, ModelArena, PoseArena};
7
8mod ik;
9mod morph;
10mod physics;
11mod world;
12
13#[cfg(test)]
14use crate::ik_primitive::{
15 LimitedAxesLinkStepInput, PlaneLinkStepInput, axis_vec, decompose_euler_xyz, euler_xyz_to_quat,
16 limit_axis_bounds, quat_to_rotation_mat3, signed_projected_angle, solve_limited_axes_link_step,
17 solve_plane_link_step,
18};
19
20#[derive(Debug)]
21struct IkScratch {
22 links: Vec<crate::IkLink>,
23 base_rotations: Vec<Quat>,
24 base_ik_rotations: Vec<Quat>,
25 ik_rotations: Vec<Quat>,
26 best_ik_rotations: Vec<Quat>,
27 chain_states: Vec<ChainLinkState>,
28}
29
30impl IkScratch {
31 fn new(model: &ModelArena) -> Self {
32 let max_links = model
33 .ik_solvers()
34 .iter()
35 .map(|s| s.links.len())
36 .max()
37 .unwrap_or(0);
38 IkScratch {
39 links: Vec::with_capacity(max_links),
40 base_rotations: Vec::with_capacity(max_links),
41 base_ik_rotations: Vec::with_capacity(max_links),
42 ik_rotations: Vec::with_capacity(max_links),
43 best_ik_rotations: Vec::with_capacity(max_links),
44 chain_states: Vec::with_capacity(max_links),
45 }
46 }
47}
48
49#[derive(Debug)]
50struct MorphScratch {
51 expanded_weights: Vec<f32>,
52}
53
54impl MorphScratch {
55 fn new(morph_count: usize) -> Self {
56 Self {
57 expanded_weights: vec![0.0; morph_count],
58 }
59 }
60}
61
62#[derive(Clone, Copy, Debug, Default, PartialEq)]
63pub struct IkSolverRuntimeStats {
64 pub solver_evaluations: u64,
65 pub configured_iterations: u64,
66 pub executed_iterations: u64,
67 pub tolerance_precheck_breaks: u64,
68 pub tolerance_post_iteration_breaks: u64,
69 pub rollback_breaks: u64,
70 pub max_iteration_exhaustions: u64,
71 pub link_visits: u64,
72 pub link_steps: u64,
73 pub final_distance_sum: f64,
74 pub final_distance_max: f32,
75 pub exhausted_final_distance_sum: f64,
76 pub exhausted_final_distance_max: f32,
77}
78
79impl IkSolverRuntimeStats {
80 fn reset(&mut self) {
81 *self = Self::default();
82 }
83}
84
85#[derive(Clone, Copy, Debug, PartialEq)]
86pub struct IkSolveOptions {
87 pub tolerance: f32,
88 pub max_iterations_cap: Option<u32>,
89}
90
91pub use physics::{PhysicsMode, PhysicsStepStats, PhysicsTickConfig};
92
93impl Default for IkSolveOptions {
94 fn default() -> Self {
95 Self {
96 tolerance: 0.0,
97 max_iterations_cap: None,
98 }
99 }
100}
101
102#[cfg(test)]
103#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
104pub(super) enum WorldMatrixBoneUpdateCategory {
105 LeadingBookend,
106 PhaseLoop,
107 TrailingBookend,
108 IkLinkChange,
109 #[default]
110 Other,
111}
112
113#[derive(Debug)]
114pub struct RuntimeInstance {
115 model: Arc<ModelArena>,
116 pose: PoseArena,
117 physics_mode: PhysicsMode,
118 physics_tick_config: PhysicsTickConfig,
119 physics_accumulator_seconds: f32,
120 ik_scratch: IkScratch,
121 morph_scratch: MorphScratch,
122 ik_stats: Vec<IkSolverRuntimeStats>,
123 ik_link_change_update_bones: Vec<Option<Vec<crate::BoneIndex>>>,
124 #[cfg(test)]
125 world_matrix_bone_update_count: usize,
126 #[cfg(test)]
127 world_matrix_bone_update_category: WorldMatrixBoneUpdateCategory,
128 #[cfg(test)]
129 world_matrix_bone_update_leading_bookend_count: usize,
130 #[cfg(test)]
131 world_matrix_bone_update_phase_loop_count: usize,
132 #[cfg(test)]
133 world_matrix_bone_update_trailing_bookend_count: usize,
134 #[cfg(test)]
135 world_matrix_bone_update_ik_link_change_count: usize,
136 #[cfg(test)]
137 world_matrix_bone_update_other_count: usize,
138}
139
140impl RuntimeInstance {
141 pub fn new(model: Arc<ModelArena>) -> Self {
142 let morph_count = model.morph_count() as usize;
143 Self::new_with_morph_count(model, morph_count)
144 }
145
146 pub fn new_with_morph_count(model: Arc<ModelArena>, morph_count: usize) -> Self {
147 let ik_count = model.ik_count();
148 Self::new_with_counts(model, morph_count, ik_count)
149 }
150
151 pub fn new_with_counts(model: Arc<ModelArena>, morph_count: usize, ik_count: usize) -> Self {
152 let morph_count = morph_count.max(model.morph_count() as usize);
153 let pose = PoseArena::new_with_counts(model.bone_count(), morph_count, ik_count);
154 let ik_scratch = IkScratch::new(&model);
155 let morph_scratch = MorphScratch::new(morph_count);
156 let ik_stats = vec![IkSolverRuntimeStats::default(); model.ik_count()];
157 let ik_link_change_update_bones = vec![None; model.ik_count()];
158 Self {
159 model,
160 pose,
161 physics_mode: PhysicsMode::default(),
162 physics_tick_config: PhysicsTickConfig::default(),
163 physics_accumulator_seconds: 0.0,
164 ik_scratch,
165 morph_scratch,
166 ik_stats,
167 ik_link_change_update_bones,
168 #[cfg(test)]
169 world_matrix_bone_update_count: 0,
170 #[cfg(test)]
171 world_matrix_bone_update_category: WorldMatrixBoneUpdateCategory::default(),
172 #[cfg(test)]
173 world_matrix_bone_update_leading_bookend_count: 0,
174 #[cfg(test)]
175 world_matrix_bone_update_phase_loop_count: 0,
176 #[cfg(test)]
177 world_matrix_bone_update_trailing_bookend_count: 0,
178 #[cfg(test)]
179 world_matrix_bone_update_ik_link_change_count: 0,
180 #[cfg(test)]
181 world_matrix_bone_update_other_count: 0,
182 }
183 }
184
185 #[inline]
186 pub fn model(&self) -> &ModelArena {
187 &self.model
188 }
189
190 #[inline]
191 pub fn pose(&self) -> &PoseArena {
192 &self.pose
193 }
194
195 #[inline]
196 pub fn pose_mut(&mut self) -> &mut PoseArena {
197 &mut self.pose
198 }
199
200 pub fn evaluate_current_pose(&mut self) {
201 self.pose.reset_ik_rotations();
202 self.evaluate_current_pose_ordered(IkSolveOptions::default());
203 }
204
205 pub fn evaluate_current_pose_with_ik_options(&mut self, options: IkSolveOptions) {
206 self.pose.reset_ik_rotations();
207 self.evaluate_current_pose_ordered(options);
208 }
209
210 pub fn evaluate_current_pose_without_ik(&mut self) {
214 self.pose.reset_ik_rotations();
215 self.update_world_matrices();
216 }
217
218 fn evaluate_current_pose_ordered(&mut self, options: IkSolveOptions) {
219 self.begin_current_pose_evaluation();
220 let mut earliest_after_physics_eval_order_position = None;
221 self.evaluate_current_pose_phase(
222 false,
223 options,
224 &mut earliest_after_physics_eval_order_position,
225 );
226 self.evaluate_current_pose_phase(
227 true,
228 options,
229 &mut earliest_after_physics_eval_order_position,
230 );
231 self.finish_current_pose_evaluation(earliest_after_physics_eval_order_position);
232 }
233
234 pub fn evaluate_current_pose_before_physics(&mut self) {
235 self.evaluate_current_pose_before_physics_with_ik_options(IkSolveOptions::default());
236 }
237
238 pub fn evaluate_current_pose_before_physics_with_ik_options(
239 &mut self,
240 options: IkSolveOptions,
241 ) {
242 self.pose.reset_ik_rotations();
243 self.begin_current_pose_evaluation();
244 let mut earliest_after_physics_eval_order_position = None;
245 self.evaluate_current_pose_phase(
246 false,
247 options,
248 &mut earliest_after_physics_eval_order_position,
249 );
250 }
251
252 pub fn evaluate_current_pose_after_physics(&mut self) {
253 self.evaluate_current_pose_after_physics_with_ik_options(IkSolveOptions::default());
254 }
255
256 pub fn evaluate_current_pose_after_physics_with_ik_options(&mut self, options: IkSolveOptions) {
257 let mut earliest_after_physics_eval_order_position = None;
258 self.evaluate_current_pose_phase(
259 true,
260 options,
261 &mut earliest_after_physics_eval_order_position,
262 );
263 self.finish_current_pose_evaluation(earliest_after_physics_eval_order_position);
264 }
265
266 fn begin_current_pose_evaluation(&mut self) {
267 self.pose.reset_append_transforms();
268 #[cfg(test)]
269 self.set_world_matrix_bone_update_category(WorldMatrixBoneUpdateCategory::LeadingBookend);
270 self.update_world_matrices_using_current_append_from_eval_order_position(0);
271 }
272
273 fn evaluate_current_pose_phase(
274 &mut self,
275 after_physics: bool,
276 options: IkSolveOptions,
277 earliest_after_physics_eval_order_position: &mut Option<usize>,
278 ) {
279 let phase_bone_count = self.model.eval_order_for_phase(after_physics).len();
280 for phase_index in 0..phase_bone_count {
281 let bone = self.model.eval_order_for_phase(after_physics)[phase_index];
282 if after_physics {
283 let position = self.model.eval_order_position(bone);
284 *earliest_after_physics_eval_order_position = Some(
285 (*earliest_after_physics_eval_order_position)
286 .map_or(position, |earliest| earliest.min(position)),
287 );
288 }
289 if self.model.append_transform_index(bone).is_some() {
290 self.pose.reset_append_transform(bone);
291 self.update_append_transform_for_bone(bone);
292 }
293 #[cfg(test)]
294 self.set_world_matrix_bone_update_category(WorldMatrixBoneUpdateCategory::PhaseLoop);
295 self.update_world_matrix_for_bone(bone);
296
297 let ik_solver_count = self.model.ik_solver_count_for_bone(bone);
298 for local_index in 0..ik_solver_count {
299 let ik_index = self.model.ik_solver_index_for_bone(bone, local_index);
300 self.solve_ik_solver(ik_index, options, after_physics);
301 }
302 }
303 }
304
305 fn finish_current_pose_evaluation(
306 &mut self,
307 earliest_after_physics_eval_order_position: Option<usize>,
308 ) {
309 let mut trailing_refresh_start = earliest_after_physics_eval_order_position;
310 for append in self.model.append_transforms() {
311 let source_position = self.model.eval_order_position(append.source_bone);
312 let target_position = self.model.eval_order_position(append.target_bone);
313 if target_position < source_position {
314 trailing_refresh_start = Some(
315 trailing_refresh_start
316 .map_or(target_position, |start| start.min(target_position)),
317 );
318 }
319 }
320
321 if let Some(start_position) = trailing_refresh_start {
322 let start_position =
323 self.expand_update_start_for_append_dependencies(start_position, None);
324 #[cfg(test)]
325 self.set_world_matrix_bone_update_category(
326 WorldMatrixBoneUpdateCategory::TrailingBookend,
327 );
328 self.update_world_matrices_from_eval_order_position(start_position);
329 }
330 }
331
332 pub fn evaluate_rest_pose(&mut self) {
333 self.pose.reset_local_pose();
334 self.evaluate_current_pose();
335 }
336
337 pub fn evaluate_clip_frame(&mut self, clip: &AnimationClip, frame: f32) {
338 clip.apply_to_pose(frame, &mut self.pose);
339 self.expand_morphs();
340 self.evaluate_current_pose();
341 }
342
343 pub fn evaluate_clip_frame_with_ik_options(
344 &mut self,
345 clip: &AnimationClip,
346 frame: f32,
347 options: IkSolveOptions,
348 ) {
349 clip.apply_to_pose(frame, &mut self.pose);
350 self.expand_morphs();
351 self.evaluate_current_pose_with_ik_options(options);
352 }
353
354 pub fn evaluate_clip_frame_before_physics(&mut self, clip: &AnimationClip, frame: f32) {
355 self.evaluate_clip_frame_before_physics_with_ik_options(
356 clip,
357 frame,
358 IkSolveOptions::default(),
359 );
360 }
361
362 pub fn evaluate_clip_frame_before_physics_with_ik_options(
363 &mut self,
364 clip: &AnimationClip,
365 frame: f32,
366 options: IkSolveOptions,
367 ) {
368 clip.apply_to_pose(frame, &mut self.pose);
369 self.expand_morphs();
370 self.evaluate_current_pose_before_physics_with_ik_options(options);
371 }
372
373 pub fn evaluate_clip_frame_without_ik(&mut self, clip: &AnimationClip, frame: f32) {
378 clip.apply_to_pose(frame, &mut self.pose);
379 self.expand_morphs();
380 self.pose.reset_ik_rotations();
381 self.update_world_matrices();
382 }
383
384 pub fn reset_ik_runtime_stats(&mut self) {
385 for stats in &mut self.ik_stats {
386 stats.reset();
387 }
388 }
389
390 pub fn ik_runtime_stats(&self) -> &[IkSolverRuntimeStats] {
391 &self.ik_stats
392 }
393
394 #[inline]
395 pub fn append_position_offset(&self, bone: crate::BoneIndex) -> glam::Vec3A {
396 self.pose.append_position_offset(bone)
397 }
398
399 #[inline]
400 pub fn append_rotation(&self, bone: crate::BoneIndex) -> glam::Quat {
401 self.pose.append_rotation(bone)
402 }
403
404 #[inline]
405 pub fn ik_enabled(&self) -> &[u8] {
406 self.pose.ik_enabled()
407 }
408}
409
410#[cfg(test)]
411mod tests;