1use std::sync::Arc;
2
3use glam::{Quat, Vec3A};
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 GroupMorphFrame {
51 morph_idx: usize,
52 weight: f32,
53 next_offset: u32,
54}
55
56#[derive(Debug)]
57struct MorphScratch {
58 expanded_weights: Vec<f32>,
59 group_stack: Vec<GroupMorphFrame>,
60}
61
62impl MorphScratch {
63 fn new(morph_count: usize) -> Self {
64 Self {
65 expanded_weights: vec![0.0; morph_count],
66 group_stack: Vec::new(),
70 }
71 }
72}
73
74#[derive(Clone, Copy, Debug, Default, PartialEq)]
75pub struct IkSolverRuntimeStats {
76 pub solver_evaluations: u64,
77 pub configured_iterations: u64,
78 pub executed_iterations: u64,
79 pub tolerance_precheck_breaks: u64,
80 pub tolerance_post_iteration_breaks: u64,
81 pub rollback_breaks: u64,
82 pub max_iteration_exhaustions: u64,
83 pub link_visits: u64,
84 pub link_steps: u64,
85 pub final_distance_sum: f64,
86 pub final_distance_max: f32,
87 pub exhausted_final_distance_sum: f64,
88 pub exhausted_final_distance_max: f32,
89}
90
91impl IkSolverRuntimeStats {
92 fn reset(&mut self) {
93 *self = Self::default();
94 }
95}
96
97#[derive(Clone, Copy, Debug, PartialEq)]
98pub struct IkSolveOptions {
99 pub tolerance: f32,
100 pub max_iterations_cap: Option<u32>,
101}
102
103pub use physics::{PhysicsMode, PhysicsStepStats, PhysicsTickConfig};
104
105impl Default for IkSolveOptions {
106 fn default() -> Self {
107 Self {
108 tolerance: 0.0,
109 max_iterations_cap: None,
110 }
111 }
112}
113
114#[cfg(test)]
115#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
116pub(super) enum WorldMatrixBoneUpdateCategory {
117 LeadingBookend,
118 PhaseLoop,
119 TrailingBookend,
120 IkLinkChange,
121 #[default]
122 Other,
123}
124
125#[derive(Clone, Copy, Debug)]
126pub struct HostPoseView<'a> {
127 pub local_position_offsets: &'a [Vec3A],
128 pub local_rotations: &'a [Quat],
129 pub local_scales: &'a [Vec3A],
130 pub morph_weights: &'a [f32],
131 pub ik_enabled: &'a [u8],
132}
133
134#[derive(Clone, Debug, PartialEq, Eq)]
135pub enum HostPoseError {
136 BoneCountMismatch { expected: usize, got: usize },
137 MorphCountMismatch { expected: usize, got: usize },
138 IkCountMismatch { expected: usize, got: usize },
139 NonFiniteValue { field: &'static str, index: usize },
140 NonNormalizedQuaternion { index: usize },
141}
142
143impl std::fmt::Display for HostPoseError {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 match self {
146 HostPoseError::BoneCountMismatch { expected, got } => {
147 write!(f, "bone count mismatch: expected {expected}, got {got}")
148 }
149 HostPoseError::MorphCountMismatch { expected, got } => {
150 write!(f, "morph count mismatch: expected {expected}, got {got}")
151 }
152 HostPoseError::IkCountMismatch { expected, got } => {
153 write!(f, "ik count mismatch: expected {expected}, got {got}")
154 }
155 HostPoseError::NonFiniteValue { field, index } => {
156 write!(f, "non-finite value in {field} at index {index}")
157 }
158 Self::NonNormalizedQuaternion { index } => {
159 write!(f, "non-normalized quaternion at local_rotations[{index}]")
160 }
161 }
162 }
163}
164
165impl std::error::Error for HostPoseError {}
166
167#[derive(Debug)]
168pub struct RuntimeInstance {
169 model: Arc<ModelArena>,
170 pose: PoseArena,
171 physics_mode: PhysicsMode,
172 physics_tick_config: PhysicsTickConfig,
173 physics_accumulator_seconds: f32,
174 ik_scratch: IkScratch,
175 morph_scratch: MorphScratch,
176 ik_stats: Vec<IkSolverRuntimeStats>,
177 ik_link_change_update_bones: Vec<Option<Vec<crate::BoneIndex>>>,
178 #[cfg(test)]
179 world_matrix_bone_update_count: usize,
180 #[cfg(test)]
181 world_matrix_bone_update_category: WorldMatrixBoneUpdateCategory,
182 #[cfg(test)]
183 world_matrix_bone_update_leading_bookend_count: usize,
184 #[cfg(test)]
185 world_matrix_bone_update_phase_loop_count: usize,
186 #[cfg(test)]
187 world_matrix_bone_update_trailing_bookend_count: usize,
188 #[cfg(test)]
189 world_matrix_bone_update_ik_link_change_count: usize,
190 #[cfg(test)]
191 world_matrix_bone_update_other_count: usize,
192}
193
194impl RuntimeInstance {
195 pub fn new(model: Arc<ModelArena>) -> Self {
196 let morph_count = model.morph_count() as usize;
197 Self::new_with_morph_count(model, morph_count)
198 }
199
200 pub fn new_with_morph_count(model: Arc<ModelArena>, morph_count: usize) -> Self {
201 let ik_count = model.ik_count();
202 Self::new_with_counts(model, morph_count, ik_count)
203 }
204
205 pub fn new_with_counts(model: Arc<ModelArena>, morph_count: usize, ik_count: usize) -> Self {
206 let morph_count = morph_count.max(model.morph_count() as usize);
207 let ik_count = ik_count.max(model.ik_count());
208 let pose = PoseArena::new_with_counts(model.bone_count(), morph_count, ik_count);
209 let ik_scratch = IkScratch::new(&model);
210 let morph_scratch = MorphScratch::new(morph_count);
211 let ik_stats = vec![IkSolverRuntimeStats::default(); model.ik_count()];
212 let ik_link_change_update_bones = vec![None; model.ik_count()];
213 Self {
214 model,
215 pose,
216 physics_mode: PhysicsMode::default(),
217 physics_tick_config: PhysicsTickConfig::default(),
218 physics_accumulator_seconds: 0.0,
219 ik_scratch,
220 morph_scratch,
221 ik_stats,
222 ik_link_change_update_bones,
223 #[cfg(test)]
224 world_matrix_bone_update_count: 0,
225 #[cfg(test)]
226 world_matrix_bone_update_category: WorldMatrixBoneUpdateCategory::default(),
227 #[cfg(test)]
228 world_matrix_bone_update_leading_bookend_count: 0,
229 #[cfg(test)]
230 world_matrix_bone_update_phase_loop_count: 0,
231 #[cfg(test)]
232 world_matrix_bone_update_trailing_bookend_count: 0,
233 #[cfg(test)]
234 world_matrix_bone_update_ik_link_change_count: 0,
235 #[cfg(test)]
236 world_matrix_bone_update_other_count: 0,
237 }
238 }
239
240 #[inline]
241 pub fn model(&self) -> &ModelArena {
242 &self.model
243 }
244
245 #[inline]
246 pub fn pose(&self) -> &PoseArena {
247 &self.pose
248 }
249
250 #[inline]
251 pub fn pose_mut(&mut self) -> &mut PoseArena {
252 &mut self.pose
253 }
254
255 pub fn evaluate_current_pose(&mut self) {
256 self.pose.reset_ik_rotations();
257 self.evaluate_current_pose_ordered(IkSolveOptions::default());
258 }
259
260 pub fn evaluate_current_pose_with_ik_options(&mut self, options: IkSolveOptions) {
261 self.pose.reset_ik_rotations();
262 self.evaluate_current_pose_ordered(options);
263 }
264
265 pub fn evaluate_current_pose_without_ik(&mut self) {
269 self.pose.reset_ik_rotations();
270 self.update_world_matrices();
271 }
272
273 fn evaluate_current_pose_ordered(&mut self, options: IkSolveOptions) {
274 self.begin_current_pose_evaluation();
275 let mut earliest_after_physics_eval_order_position = None;
276 self.evaluate_current_pose_phase(
277 false,
278 options,
279 &mut earliest_after_physics_eval_order_position,
280 );
281 self.evaluate_current_pose_phase(
282 true,
283 options,
284 &mut earliest_after_physics_eval_order_position,
285 );
286 self.finish_current_pose_evaluation(earliest_after_physics_eval_order_position);
287 }
288
289 pub fn evaluate_current_pose_before_physics(&mut self) {
290 self.evaluate_current_pose_before_physics_with_ik_options(IkSolveOptions::default());
291 }
292
293 pub fn evaluate_current_pose_before_physics_with_ik_options(
294 &mut self,
295 options: IkSolveOptions,
296 ) {
297 self.pose.reset_ik_rotations();
298 self.begin_current_pose_evaluation();
299 let mut earliest_after_physics_eval_order_position = None;
300 self.evaluate_current_pose_phase(
301 false,
302 options,
303 &mut earliest_after_physics_eval_order_position,
304 );
305 }
306
307 pub fn evaluate_current_pose_after_physics(&mut self) {
308 self.evaluate_current_pose_after_physics_with_ik_options(IkSolveOptions::default());
309 }
310
311 pub fn evaluate_current_pose_after_physics_with_ik_options(&mut self, options: IkSolveOptions) {
312 let mut earliest_after_physics_eval_order_position = None;
313 self.evaluate_current_pose_phase(
314 true,
315 options,
316 &mut earliest_after_physics_eval_order_position,
317 );
318 self.finish_current_pose_evaluation(earliest_after_physics_eval_order_position);
319 }
320
321 fn begin_current_pose_evaluation(&mut self) {
322 self.pose.reset_append_transforms();
323 #[cfg(test)]
324 self.set_world_matrix_bone_update_category(WorldMatrixBoneUpdateCategory::LeadingBookend);
325 self.update_world_matrices_using_current_append_from_eval_order_position(0);
326 }
327
328 fn evaluate_current_pose_phase(
329 &mut self,
330 after_physics: bool,
331 options: IkSolveOptions,
332 earliest_after_physics_eval_order_position: &mut Option<usize>,
333 ) {
334 let phase_bone_count = self.model.eval_order_for_phase(after_physics).len();
335 for phase_index in 0..phase_bone_count {
336 let bone = self.model.eval_order_for_phase(after_physics)[phase_index];
337 if after_physics {
338 let position = self.model.eval_order_position(bone);
339 *earliest_after_physics_eval_order_position = Some(
340 (*earliest_after_physics_eval_order_position)
341 .map_or(position, |earliest| earliest.min(position)),
342 );
343 }
344 if self.model.append_transform_index(bone).is_some() {
345 self.pose.reset_append_transform(bone);
346 self.update_append_transform_for_bone(bone);
347 }
348 #[cfg(test)]
349 self.set_world_matrix_bone_update_category(WorldMatrixBoneUpdateCategory::PhaseLoop);
350 self.update_world_matrix_for_bone(bone);
351
352 let ik_solver_count = self.model.ik_solver_count_for_bone(bone);
353 for local_index in 0..ik_solver_count {
354 let ik_index = self.model.ik_solver_index_for_bone(bone, local_index);
355 self.solve_ik_solver(ik_index, options, after_physics);
356 }
357 }
358 }
359
360 fn finish_current_pose_evaluation(
361 &mut self,
362 earliest_after_physics_eval_order_position: Option<usize>,
363 ) {
364 let mut trailing_refresh_start = earliest_after_physics_eval_order_position;
365 for append in self.model.append_transforms() {
366 let source_position = self.model.eval_order_position(append.source_bone);
367 let target_position = self.model.eval_order_position(append.target_bone);
368 if target_position < source_position {
369 trailing_refresh_start = Some(
370 trailing_refresh_start
371 .map_or(target_position, |start| start.min(target_position)),
372 );
373 }
374 }
375
376 if let Some(start_position) = trailing_refresh_start {
377 let start_position =
378 self.expand_update_start_for_append_dependencies(start_position, None);
379 #[cfg(test)]
380 self.set_world_matrix_bone_update_category(
381 WorldMatrixBoneUpdateCategory::TrailingBookend,
382 );
383 self.update_world_matrices_from_eval_order_position(start_position);
384 }
385 }
386
387 pub fn evaluate_rest_pose(&mut self) {
388 self.pose.reset_local_pose();
389 self.evaluate_current_pose();
390 }
391
392 pub fn apply_host_pose(&mut self, view: &HostPoseView) -> Result<(), HostPoseError> {
398 let bone_count = self.model.bone_count();
399 let morph_count = self.pose.morph_weights().len();
400 let ik_count = self.pose.ik_enabled().len();
401
402 if view.local_position_offsets.len() != bone_count {
403 return Err(HostPoseError::BoneCountMismatch {
404 expected: bone_count,
405 got: view.local_position_offsets.len(),
406 });
407 }
408 if view.local_rotations.len() != bone_count {
409 return Err(HostPoseError::BoneCountMismatch {
410 expected: bone_count,
411 got: view.local_rotations.len(),
412 });
413 }
414 if view.local_scales.len() != bone_count {
415 return Err(HostPoseError::BoneCountMismatch {
416 expected: bone_count,
417 got: view.local_scales.len(),
418 });
419 }
420 if view.morph_weights.len() != morph_count {
421 return Err(HostPoseError::MorphCountMismatch {
422 expected: morph_count,
423 got: view.morph_weights.len(),
424 });
425 }
426 if view.ik_enabled.len() != ik_count {
427 return Err(HostPoseError::IkCountMismatch {
428 expected: ik_count,
429 got: view.ik_enabled.len(),
430 });
431 }
432
433 for (i, v) in view.local_position_offsets.iter().enumerate() {
434 if !v.is_finite() {
435 return Err(HostPoseError::NonFiniteValue {
436 field: "local_position_offsets",
437 index: i,
438 });
439 }
440 }
441 for (i, q) in view.local_rotations.iter().enumerate() {
442 if !q.is_finite() {
443 return Err(HostPoseError::NonFiniteValue {
444 field: "local_rotations",
445 index: i,
446 });
447 }
448 if (q.length_squared() - 1.0).abs() > 1e-3 {
449 return Err(HostPoseError::NonNormalizedQuaternion { index: i });
450 }
451 }
452 for (i, v) in view.local_scales.iter().enumerate() {
453 if !v.is_finite() {
454 return Err(HostPoseError::NonFiniteValue {
455 field: "local_scales",
456 index: i,
457 });
458 }
459 }
460 for (i, w) in view.morph_weights.iter().enumerate() {
461 if !w.is_finite() {
462 return Err(HostPoseError::NonFiniteValue {
463 field: "morph_weights",
464 index: i,
465 });
466 }
467 }
468
469 self.pose
470 .set_local_position_offsets_from_slice(view.local_position_offsets);
471 self.pose
472 .set_local_rotations_from_slice(view.local_rotations);
473 self.pose.set_local_scales_from_slice(view.local_scales);
474 self.pose.set_morph_weights_from_slice(view.morph_weights);
475 self.pose.set_ik_enabled_from_slice(view.ik_enabled);
476 self.expand_morphs();
477
478 Ok(())
479 }
480
481 pub fn evaluate_clip_frame(&mut self, clip: &AnimationClip, frame: f32) {
482 clip.apply_to_pose(frame, &mut self.pose);
483 self.expand_morphs();
484 self.evaluate_current_pose();
485 }
486
487 pub fn evaluate_clip_frame_with_ik_options(
488 &mut self,
489 clip: &AnimationClip,
490 frame: f32,
491 options: IkSolveOptions,
492 ) {
493 clip.apply_to_pose(frame, &mut self.pose);
494 self.expand_morphs();
495 self.evaluate_current_pose_with_ik_options(options);
496 }
497
498 pub fn evaluate_clip_frame_before_physics(&mut self, clip: &AnimationClip, frame: f32) {
499 self.evaluate_clip_frame_before_physics_with_ik_options(
500 clip,
501 frame,
502 IkSolveOptions::default(),
503 );
504 }
505
506 pub fn evaluate_clip_frame_before_physics_with_ik_options(
507 &mut self,
508 clip: &AnimationClip,
509 frame: f32,
510 options: IkSolveOptions,
511 ) {
512 clip.apply_to_pose(frame, &mut self.pose);
513 self.expand_morphs();
514 self.evaluate_current_pose_before_physics_with_ik_options(options);
515 }
516
517 pub fn evaluate_clip_frame_without_ik(&mut self, clip: &AnimationClip, frame: f32) {
522 clip.apply_to_pose(frame, &mut self.pose);
523 self.expand_morphs();
524 self.pose.reset_ik_rotations();
525 self.update_world_matrices();
526 }
527
528 pub fn reset_ik_runtime_stats(&mut self) {
529 for stats in &mut self.ik_stats {
530 stats.reset();
531 }
532 }
533
534 pub fn ik_runtime_stats(&self) -> &[IkSolverRuntimeStats] {
535 &self.ik_stats
536 }
537
538 #[inline]
539 pub fn append_position_offset(&self, bone: crate::BoneIndex) -> glam::Vec3A {
540 self.pose.append_position_offset(bone)
541 }
542
543 #[inline]
544 pub fn append_rotation(&self, bone: crate::BoneIndex) -> glam::Quat {
545 self.pose.append_rotation(bone)
546 }
547
548 #[inline]
549 pub fn ik_enabled(&self) -> &[u8] {
550 self.pose.ik_enabled()
551 }
552}
553
554#[cfg(test)]
555mod tests;