use std::{collections::HashMap, sync::Arc};
use super::{
animation::AnimationClip,
skeleton::{Skeleton, Transform},
};
pub struct Pose {
locals: Vec<Transform>,
skeleton: Arc<Skeleton>,
worlds: Option<Vec<glam::Mat4>>, }
impl Pose {
pub(crate) fn new(locals: Vec<Transform>, skeleton: Arc<Skeleton>) -> Self {
Self { locals, skeleton, worlds: None }
}
fn ensure_worlds(&mut self) {
if self.worlds.is_none() {
self.worlds = Some(self.skeleton.world_matrices(&self.locals));
}
}
pub fn world_position(&mut self, joint: usize) -> glam::Vec3 {
self.ensure_worlds();
self.worlds.as_ref().unwrap()[joint].transform_point3(glam::Vec3::ZERO)
}
pub fn world_rotation(&mut self, joint: usize) -> glam::Quat {
self.ensure_worlds();
let (_, rot, _) = self.worlds.as_ref().unwrap()[joint].to_scale_rotation_translation();
rot
}
pub fn world_matrix(&mut self, joint: usize) -> glam::Mat4 {
self.ensure_worlds();
self.worlds.as_ref().unwrap()[joint]
}
pub fn set_world_rotation(&mut self, joint: usize, rotation: glam::Quat) {
let parent_rot = match self.skeleton.joint(joint).parent {
Some(p) => {
self.ensure_worlds();
let (_, rot, _) = self.worlds.as_ref().unwrap()[p].to_scale_rotation_translation();
rot
}
None => glam::Quat::IDENTITY,
};
self.locals[joint].rotation = parent_rot.inverse() * rotation;
self.worlds = None;
}
pub fn set_world_position(&mut self, joint: usize, position: glam::Vec3) {
let parent_world = match self.skeleton.joint(joint).parent {
Some(p) => {
self.ensure_worlds();
self.worlds.as_ref().unwrap()[p]
}
None => glam::Mat4::IDENTITY,
};
self.locals[joint].translation = parent_world.inverse().transform_point3(position);
self.worlds = None;
}
pub fn local(&self, joint: usize) -> Transform {
self.locals[joint]
}
pub fn set_local(&mut self, joint: usize, t: Transform) {
self.locals[joint] = t;
self.worlds = None;
}
pub fn skeleton(&self) -> &Skeleton {
&self.skeleton
}
pub fn skinning_matrices(&self) -> Vec<glam::Mat4> {
self.skeleton.skinning_matrices(&self.locals)
}
}
struct Transition {
from_clip: String,
from_time: f32,
elapsed: f32,
duration: f32,
}
pub struct AnimationPlayer {
skeleton: Arc<Skeleton>,
clips: Arc<HashMap<String, AnimationClip>>,
current: Option<String>,
time: f32,
speed: f32,
looping: bool,
transition: Option<Transition>,
matrices_override: Option<Vec<glam::Mat4>>,
}
impl AnimationPlayer {
pub(crate) fn new(skeleton: Arc<Skeleton>, clips: Arc<HashMap<String, AnimationClip>>) -> Self {
Self {
skeleton,
clips,
current: None,
time: 0.0,
speed: 1.0,
looping: true,
transition: None,
matrices_override: None,
}
}
pub fn play(&mut self, name: &str) {
if self.clips.contains_key(name) {
self.current = Some(name.to_string());
self.time = 0.0;
self.looping = true;
self.transition = None;
} else {
tracing::warn!("AnimationPlayer::play: clip '{name}' not found");
}
}
pub fn play_once(&mut self, name: &str) {
if self.clips.contains_key(name) {
self.current = Some(name.to_string());
self.time = 0.0;
self.looping = false;
self.transition = None;
} else {
tracing::warn!("AnimationPlayer::play_once: clip '{name}' not found");
}
}
pub fn crossfade(&mut self, name: &str, duration: f32) {
if !self.clips.contains_key(name) {
tracing::warn!("AnimationPlayer::crossfade: clip '{name}' not found");
return;
}
if let Some(current) = &self.current {
self.transition = Some(Transition {
from_clip: current.clone(),
from_time: self.time,
elapsed: 0.0,
duration: duration.max(f32::EPSILON),
});
}
self.current = Some(name.to_string());
self.time = 0.0;
self.looping = true;
}
pub fn pause(&mut self) {
self.speed = 0.0;
}
pub fn resume(&mut self) {
self.speed = 1.0;
}
pub fn set_speed(&mut self, speed: f32) {
self.speed = speed;
}
pub fn set_time(&mut self, time: f32) {
self.time = time;
}
pub fn time(&self) -> f32 {
self.time
}
pub fn speed(&self) -> f32 {
self.speed
}
pub fn joint_count(&self) -> usize {
self.skeleton.joint_count()
}
pub fn skeleton(&self) -> &Skeleton {
&self.skeleton
}
pub fn current_clip(&self) -> Option<&AnimationClip> {
self.current.as_ref().and_then(|name| self.clips.get(name))
}
pub fn clip_names(&self) -> impl Iterator<Item = &str> {
self.clips.keys().map(|s| s.as_str())
}
pub fn advance(&mut self, dt: f32) {
if let Some(t) = &mut self.transition {
t.from_time += dt * self.speed;
t.elapsed += dt;
if t.elapsed >= t.duration {
self.transition = None;
}
}
let Some(name) = &self.current else { return };
let Some(clip) = self.clips.get(name) else { return };
self.time += dt * self.speed;
if self.looping && clip.duration > 0.0 {
self.time = self.time.rem_euclid(clip.duration);
} else {
self.time = self.time.min(clip.duration);
}
}
pub fn compute_pose(&self) -> Pose {
let locals = self.sample_locals();
Pose::new(locals, Arc::clone(&self.skeleton))
}
fn sample_locals(&self) -> Vec<Transform> {
let Some(name) = &self.current else {
return self.skeleton.bind_pose();
};
let Some(clip) = self.clips.get(name) else {
return self.skeleton.bind_pose();
};
match &self.transition {
None => clip.sample(self.time, &self.skeleton),
Some(t) => {
let weight = (t.elapsed / t.duration).clamp(0.0, 1.0);
let poses_to = clip.sample(self.time, &self.skeleton);
if let Some(from) = self.clips.get(&t.from_clip) {
let poses_from = from.sample(t.from_time, &self.skeleton);
poses_from.iter().zip(&poses_to).map(|(a, b)| a.lerp(b, weight)).collect()
} else {
poses_to
}
}
}
}
pub fn set_matrices(&mut self, matrices: Vec<glam::Mat4>) {
self.matrices_override = Some(matrices);
}
pub fn clear_matrices(&mut self) {
self.matrices_override = None;
}
pub fn compute_matrices(&self) -> Vec<glam::Mat4> {
if let Some(ref m) = self.matrices_override {
return m.clone();
}
if self.current.is_none() {
return vec![glam::Mat4::IDENTITY; self.skeleton.joint_count()];
}
self.compute_pose().skinning_matrices()
}
}
impl Clone for AnimationPlayer {
fn clone(&self) -> Self {
Self {
skeleton: Arc::clone(&self.skeleton),
clips: Arc::clone(&self.clips),
current: self.current.clone(),
time: 0.0,
speed: 1.0,
looping: true,
transition: None,
matrices_override: None,
}
}
}