(&mut self, handle: f64, anim_index: usize, time: f32)
| 423 | } |
| 424 | |
| 425 | pub fn update_model_animation(&mut self, handle: f64, anim_index: usize, time: f32) { |
| 426 | if let Some(model_anim) = self.animations.get_mut(handle) { |
| 427 | let skeleton = match &model_anim.skeleton { |
| 428 | Some(s) => s, |
| 429 | None => return, |
| 430 | }; |
| 431 | if anim_index >= model_anim.animations.len() { return; } |
| 432 | |
| 433 | let joint_count = skeleton.joints.len(); |
| 434 | if model_anim.joint_matrices.len() != joint_count { |
| 435 | model_anim.joint_matrices = vec![mat4_identity(); joint_count]; |
| 436 | } |
| 437 | |
| 438 | // Initialize from rest-pose transforms (fallback for non-animated joints) |
| 439 | let mut local_translations: Vec<[f32; 3]> = skeleton.joints.iter() |
| 440 | .map(|j| j.rest_translation).collect(); |
| 441 | let mut local_rotations: Vec<[f32; 4]> = skeleton.joints.iter() |
| 442 | .map(|j| j.rest_rotation).collect(); |
| 443 | let mut local_scales: Vec<[f32; 3]> = skeleton.joints.iter() |
| 444 | .map(|j| j.rest_scale).collect(); |
| 445 | |
| 446 | let anim = &model_anim.animations[anim_index]; |
| 447 | let t = if anim.duration > 0.0 { time % anim.duration } else { 0.0 }; |
| 448 | |
| 449 | #[cfg(debug_assertions)] |
| 450 | let mut channels_applied = 0usize; |
| 451 | for channel in &anim.channels { |
| 452 | let ji = channel.joint_index; |
| 453 | if ji >= joint_count { continue; } |
| 454 | #[cfg(debug_assertions)] |
| 455 | { channels_applied += 1; } |
| 456 | |
| 457 | if !channel.translations.is_empty() && !channel.timestamps.is_empty() { |
| 458 | local_translations[ji] = sample_vec3(&channel.timestamps, &channel.translations, t); |
| 459 | } |
| 460 | if !channel.rotations.is_empty() { |
| 461 | let rot_ts = if !channel.rotation_timestamps.is_empty() { &channel.rotation_timestamps } else { &channel.timestamps }; |
| 462 | if !rot_ts.is_empty() { |
| 463 | local_rotations[ji] = sample_quat(rot_ts, &channel.rotations, t); |
| 464 | } |
| 465 | } |
| 466 | if !channel.scales.is_empty() { |
| 467 | let scale_ts = if !channel.scale_timestamps.is_empty() { &channel.scale_timestamps } else { &channel.timestamps }; |
| 468 | if !scale_ts.is_empty() { |
| 469 | local_scales[ji] = sample_vec3(scale_ts, &channel.scales, t); |
| 470 | } |
| 471 | } |
| 472 | } |
| 473 | |
| 474 | // Lock root translation to rest pose (strip all root motion) |
| 475 | local_translations[0] = skeleton.joints[0].rest_translation; |
| 476 | |
| 477 | // Build world transforms by walking the hierarchy from roots |
| 478 | let mut world_transforms = vec![mat4_identity(); joint_count]; |
| 479 | |
| 480 | let root_joints = skeleton.root_joints.clone(); |
| 481 | for &root in &root_joints { |
| 482 | compute_joint_transforms( |
no test coverage detected