use std::sync::{
Arc,
atomic::{AtomicBool, AtomicU64, Ordering},
};
use slotmap::new_key_type;
use crate::diagnostics::AnimationError;
use crate::scene::{NodeKey, Quat, Vec3};
mod sampling;
use self::sampling::{sample_quat, sample_vec3, sample_weights};
new_key_type! {
pub struct AnimationMixerKey;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AnimationClipKey(u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimationPlaybackState {
Playing,
Paused,
Stopped,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimationLoopMode {
Once,
Repeat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimationTarget {
Translation,
Rotation,
Scale,
Weights,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimationInterpolation {
Linear,
Step,
CubicSpline,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AnimationClip {
key: AnimationClipKey,
name: Option<String>,
channels: Vec<AnimationChannel>,
duration_seconds: f32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AnimationSourceClip {
name: Option<String>,
channels: Vec<AnimationSourceChannel>,
duration_seconds: f32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AnimationChannel {
target_node: NodeKey,
target: AnimationTarget,
input_seconds: Vec<f32>,
output: AnimationOutput,
interpolation: AnimationInterpolation,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AnimationSourceChannel {
source_node: usize,
target: AnimationTarget,
input_seconds: Vec<f32>,
output: AnimationOutput,
interpolation: AnimationInterpolation,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AnimationOutput {
Vec3(Vec<Vec3>),
Quat(Vec<Quat>),
Weights(Vec<Vec<f32>>),
}
#[derive(Debug, Clone)]
pub struct AnimationMixer {
clip: AnimationClip,
state: AnimationPlaybackState,
time_seconds: f32,
speed: f32,
loop_mode: AnimationLoopMode,
import_live: Arc<AtomicBool>,
}
impl AnimationClipKey {
pub(crate) fn fresh() -> Self {
static NEXT: AtomicU64 = AtomicU64::new(1);
Self(NEXT.fetch_add(1, Ordering::Relaxed))
}
pub const fn as_u64(self) -> u64 {
self.0
}
}
impl AnimationClip {
pub fn authored(
name: Option<String>,
channels: Vec<AnimationChannel>,
duration_seconds: f32,
) -> Result<Self, AnimationError> {
Self::new(AnimationClipKey::fresh(), name, channels, duration_seconds)
}
pub fn new(
key: AnimationClipKey,
name: Option<String>,
channels: Vec<AnimationChannel>,
duration_seconds: f32,
) -> Result<Self, AnimationError> {
validate_clip(&channels, duration_seconds)?;
Ok(Self {
key,
name,
channels,
duration_seconds,
})
}
pub(crate) fn new_unchecked(
key: AnimationClipKey,
name: Option<String>,
channels: Vec<AnimationChannel>,
duration_seconds: f32,
) -> Self {
Self {
key,
name,
channels,
duration_seconds,
}
}
pub const fn key(&self) -> AnimationClipKey {
self.key
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn channels(&self) -> &[AnimationChannel] {
&self.channels
}
pub const fn duration_seconds(&self) -> f32 {
self.duration_seconds
}
}
impl AnimationSourceClip {
pub fn new(
name: Option<String>,
channels: Vec<AnimationSourceChannel>,
duration_seconds: f32,
) -> Self {
Self {
name,
channels,
duration_seconds,
}
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn channels(&self) -> &[AnimationSourceChannel] {
&self.channels
}
pub const fn duration_seconds(&self) -> f32 {
self.duration_seconds
}
pub fn rebind<F, G>(
&self,
key: AnimationClipKey,
mut map_node: F,
mut map_vec3: G,
) -> AnimationClip
where
F: FnMut(usize) -> Option<NodeKey>,
G: FnMut(AnimationTarget, Vec3) -> Vec3,
{
let channels = self
.channels
.iter()
.filter_map(|channel| channel.rebind(&mut map_node, &mut map_vec3))
.collect();
AnimationClip::new_unchecked(key, self.name.clone(), channels, self.duration_seconds)
}
}
fn validate_clip(
channels: &[AnimationChannel],
duration_seconds: f32,
) -> Result<(), AnimationError> {
if !duration_seconds.is_finite() || duration_seconds <= 0.0 {
return Err(AnimationError::InvalidClip {
reason: "duration_seconds must be finite and positive".to_owned(),
});
}
if channels.is_empty() {
return Err(AnimationError::InvalidClip {
reason: "clip must contain at least one channel".to_owned(),
});
}
for (channel_index, channel) in channels.iter().enumerate() {
validate_channel(channel_index, channel, duration_seconds)?;
}
Ok(())
}
fn validate_channel(
channel_index: usize,
channel: &AnimationChannel,
duration_seconds: f32,
) -> Result<(), AnimationError> {
if channel.input_seconds.is_empty() {
return Err(invalid_channel(channel_index, "times must not be empty"));
}
let mut previous = None;
for (time_index, time) in channel.input_seconds.iter().copied().enumerate() {
if !time.is_finite() || time < 0.0 {
return Err(invalid_channel(
channel_index,
format!("time[{time_index}] must be finite and non-negative"),
));
}
if time > duration_seconds {
return Err(invalid_channel(
channel_index,
format!("time[{time_index}] exceeds clip duration"),
));
}
if previous.is_some_and(|previous| time <= previous) {
return Err(invalid_channel(
channel_index,
format!("time[{time_index}] must be strictly increasing"),
));
}
previous = Some(time);
}
let expected_values = if channel.interpolation == AnimationInterpolation::CubicSpline {
channel.input_seconds.len().saturating_mul(3)
} else {
channel.input_seconds.len()
};
match &channel.output {
AnimationOutput::Vec3(values) => {
if values.len() != expected_values {
return Err(invalid_channel(
channel_index,
format!("Vec3 output length must be {expected_values}"),
));
}
if let Some(index) = values.iter().position(|value| !vec3_is_finite(*value)) {
return Err(invalid_channel(
channel_index,
format!("Vec3 output[{index}] must be finite"),
));
}
}
AnimationOutput::Quat(values) => {
if values.len() != expected_values {
return Err(invalid_channel(
channel_index,
format!("Quat output length must be {expected_values}"),
));
}
if let Some(index) = values.iter().position(|value| !quat_is_finite(*value)) {
return Err(invalid_channel(
channel_index,
format!("Quat output[{index}] must be finite"),
));
}
}
AnimationOutput::Weights(values) => {
if values.len() != expected_values {
return Err(invalid_channel(
channel_index,
format!("Weights output length must be {expected_values}"),
));
}
let Some(width) = values.first().map(Vec::len).filter(|width| *width > 0) else {
return Err(invalid_channel(
channel_index,
"weights output must contain at least one morph target",
));
};
for (index, value) in values.iter().enumerate() {
if value.len() != width {
return Err(invalid_channel(
channel_index,
format!("weights output[{index}] has inconsistent width"),
));
}
if value.iter().any(|component| !component.is_finite()) {
return Err(invalid_channel(
channel_index,
format!("weights output[{index}] must be finite"),
));
}
}
}
}
Ok(())
}
fn invalid_channel(channel_index: usize, reason: impl Into<String>) -> AnimationError {
AnimationError::InvalidClip {
reason: format!("channel {channel_index}: {}", reason.into()),
}
}
fn quat_is_finite(value: Quat) -> bool {
value.x.is_finite() && value.y.is_finite() && value.z.is_finite() && value.w.is_finite()
}
fn vec3_is_finite(value: Vec3) -> bool {
value.x.is_finite() && value.y.is_finite() && value.z.is_finite()
}
impl AnimationChannel {
pub fn new(
target_node: NodeKey,
target: AnimationTarget,
input_seconds: Vec<f32>,
output: AnimationOutput,
interpolation: AnimationInterpolation,
) -> Self {
Self {
target_node,
target,
input_seconds,
output,
interpolation,
}
}
pub const fn target_node(&self) -> NodeKey {
self.target_node
}
pub const fn target(&self) -> AnimationTarget {
self.target
}
pub fn sample_vec3(&self, time_seconds: f32) -> Option<Vec3> {
let AnimationOutput::Vec3(values) = &self.output else {
return None;
};
sample_vec3(
&self.input_seconds,
values,
self.interpolation,
time_seconds,
)
}
pub fn sample_quat(&self, time_seconds: f32) -> Option<Quat> {
let AnimationOutput::Quat(values) = &self.output else {
return None;
};
sample_quat(
&self.input_seconds,
values,
self.interpolation,
time_seconds,
)
}
pub fn sample_weights(&self, time_seconds: f32) -> Option<Vec<f32>> {
let AnimationOutput::Weights(values) = &self.output else {
return None;
};
sample_weights(
&self.input_seconds,
values,
self.interpolation,
time_seconds,
)
}
}
impl AnimationSourceChannel {
pub fn new(
source_node: usize,
target: AnimationTarget,
input_seconds: Vec<f32>,
output: AnimationOutput,
interpolation: AnimationInterpolation,
) -> Self {
Self {
source_node,
target,
input_seconds,
output,
interpolation,
}
}
pub const fn source_node(&self) -> usize {
self.source_node
}
pub fn input_seconds(&self) -> &[f32] {
&self.input_seconds
}
fn rebind<F, G>(&self, map_node: &mut F, map_vec3: &mut G) -> Option<AnimationChannel>
where
F: FnMut(usize) -> Option<NodeKey>,
G: FnMut(AnimationTarget, Vec3) -> Vec3,
{
let output = match &self.output {
AnimationOutput::Vec3(values) => AnimationOutput::Vec3(
values
.iter()
.copied()
.map(|value| map_vec3(self.target, value))
.collect(),
),
AnimationOutput::Quat(values) => AnimationOutput::Quat(values.clone()),
AnimationOutput::Weights(values) => AnimationOutput::Weights(values.clone()),
};
Some(AnimationChannel::new(
map_node(self.source_node)?,
self.target,
self.input_seconds.clone(),
output,
self.interpolation,
))
}
}
impl AnimationMixer {
pub fn new(clip: AnimationClip, import_live: Arc<AtomicBool>) -> Self {
Self {
clip,
state: AnimationPlaybackState::Stopped,
time_seconds: 0.0,
speed: 1.0,
loop_mode: AnimationLoopMode::Once,
import_live,
}
}
pub const fn state(&self) -> AnimationPlaybackState {
self.state
}
pub const fn time_seconds(&self) -> f32 {
self.time_seconds
}
pub const fn speed(&self) -> f32 {
self.speed
}
pub const fn loop_mode(&self) -> AnimationLoopMode {
self.loop_mode
}
pub fn clip(&self) -> &AnimationClip {
&self.clip
}
pub(crate) fn is_stale(&self) -> bool {
!self.import_live.load(Ordering::Acquire)
}
pub(crate) fn play(&mut self) {
self.state = AnimationPlaybackState::Playing;
}
pub(crate) fn pause(&mut self) {
self.state = AnimationPlaybackState::Paused;
}
pub(crate) fn stop(&mut self) {
self.state = AnimationPlaybackState::Stopped;
self.time_seconds = 0.0;
}
pub(crate) fn seek(&mut self, time_seconds: f32) {
self.time_seconds = self.clamp_or_wrap_time(time_seconds.max(0.0));
}
pub(crate) fn set_speed(&mut self, speed: f32) {
self.speed = if speed.is_finite() { speed } else { 1.0 };
}
pub(crate) fn set_loop_mode(&mut self, loop_mode: AnimationLoopMode) {
self.loop_mode = loop_mode;
self.time_seconds = self.clamp_or_wrap_time(self.time_seconds);
}
pub(crate) fn advance(&mut self, delta_seconds: f32) {
if self.state != AnimationPlaybackState::Playing {
return;
}
let delta = if delta_seconds.is_finite() {
delta_seconds.max(0.0)
} else {
0.0
};
self.time_seconds = self.clamp_or_wrap_time(self.time_seconds + delta * self.speed);
}
fn clamp_or_wrap_time(&self, time_seconds: f32) -> f32 {
let duration = self.clip.duration_seconds.max(0.0);
if duration <= f32::EPSILON {
return 0.0;
}
match self.loop_mode {
AnimationLoopMode::Once => time_seconds.clamp(0.0, duration),
AnimationLoopMode::Repeat => time_seconds.rem_euclid(duration),
}
}
}