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