use core::mem::discriminant;
use crate::math::{Quat, Vec3};
use crate::mesh::Local;
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct Animation {
tracks: Vec<Track>,
}
impl Animation {
pub(crate) fn new(tracks: Vec<Track>) -> Self {
Self { tracks }
}
pub(crate) fn tracks(&self) -> &[Track] {
&self.tracks
}
pub(crate) fn bytes(&self) -> usize {
self.tracks().iter().map(Track::bytes).sum()
}
pub(crate) fn timeline(&self) -> Timeline {
self.tracks()
.iter()
.filter_map(|track| track.moves.span())
.reduce(Timeline::joined)
.unwrap_or_default()
}
pub(crate) fn key_times(&self) -> Vec<f32> {
let mut times: Vec<f32> = self
.tracks()
.iter()
.flat_map(|track| track.moves.times())
.collect();
times.sort_by(f32::total_cmp);
times.dedup();
times
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(crate) struct Timeline {
start: f32,
end: f32,
}
impl Timeline {
pub(crate) fn span(self) -> f32 {
self.end - self.start
}
pub(crate) fn at(self, fraction: f32) -> f32 {
self.start + self.span() * fraction
}
fn joined(self, other: Self) -> Self {
Self {
start: self.start.min(other.start),
end: self.end.max(other.end),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Track {
pub(crate) joint: u32,
pub(crate) moves: Moves,
}
impl Track {
pub(crate) fn same_path(&self, other: &Self) -> bool {
self.joint == other.joint && discriminant(&self.moves) == discriminant(&other.moves)
}
pub(crate) fn same_curve(&self, other: &Self) -> bool {
self.moves == other.moves
}
fn bytes(&self) -> usize {
self.moves.bytes()
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Moves {
Position(Keys<Vec3>),
Turn(Keys<Quat>),
Scale(Keys<Vec3>),
}
impl Moves {
pub(crate) fn moved(&self, local: Local, at: f32) -> Local {
match self {
Self::Position(keys) => Local {
position: keys.at(at).unwrap_or(local.position),
..local
},
Self::Turn(keys) => Local {
turn: keys.at(at).unwrap_or(local.turn),
..local
},
Self::Scale(keys) => Local {
scale: keys.at(at).unwrap_or(local.scale),
..local
},
}
}
fn span(&self) -> Option<Timeline> {
match self {
Self::Position(keys) | Self::Scale(keys) => keys.span(),
Self::Turn(keys) => keys.span(),
}
}
fn times(&self) -> Vec<f32> {
match self {
Self::Position(keys) | Self::Scale(keys) => keys.times(),
Self::Turn(keys) => keys.times(),
}
}
fn bytes(&self) -> usize {
match self {
Self::Position(keys) | Self::Scale(keys) => keys.bytes(),
Self::Turn(keys) => keys.bytes(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Keys<T> {
Step(Vec<(f32, T)>),
Linear(Vec<(f32, T)>),
Cubic(Vec<(f32, [T; 3])>),
}
impl<T> Keys<T> {
fn bytes(&self) -> usize {
match self {
Self::Step(keys) | Self::Linear(keys) => size_of_val(keys.as_slice()),
Self::Cubic(keys) => size_of_val(keys.as_slice()),
}
}
fn span(&self) -> Option<Timeline> {
let (first, last) = match self {
Self::Step(keys) | Self::Linear(keys) => (keys.first()?.0, keys.last()?.0),
Self::Cubic(keys) => (keys.first()?.0, keys.last()?.0),
};
Some(Timeline {
start: first,
end: last,
})
}
fn times(&self) -> Vec<f32> {
match self {
Self::Step(keys) | Self::Linear(keys) => keys.iter().map(|&(at, _)| at).collect(),
Self::Cubic(keys) => keys.iter().map(|&(at, _)| at).collect(),
}
}
}
impl<T: Mixed> Keys<T> {
fn at(&self, at: f32) -> Option<T> {
match self {
Self::Step(keys) => Some(keys.get(Between::of(keys, at).earlier)?.1),
Self::Linear(keys) => {
let between = Between::of(keys, at);
let (_, earlier) = *keys.get(between.earlier)?;
let (_, later) = *keys.get(between.later)?;
Some(earlier.mixed(later, between.amount))
}
Self::Cubic(keys) => {
let between = Between::of(keys, at);
let (earlier_at, [_, earlier, out]) = *keys.get(between.earlier)?;
let (later_at, [into, later, _]) = *keys.get(between.later)?;
let span = later_at - earlier_at;
Some(T::combined([
(earlier, hermite(between.amount, Curve::Earlier)),
(out, hermite(between.amount, Curve::OutOf) * span),
(later, hermite(between.amount, Curve::Later)),
(into, hermite(between.amount, Curve::Into) * span),
]))
}
}
}
}
struct Between {
earlier: usize,
later: usize,
amount: f32,
}
impl Between {
fn of<T>(keys: &[(f32, T)], at: f32) -> Self {
let after = keys.partition_point(|&(time, _)| time <= at);
if after == 0 {
return Self {
earlier: 0,
later: 0,
amount: 0.0,
};
}
let earlier = after - 1;
let Some(&(later_at, _)) = keys.get(after) else {
return Self {
earlier,
later: earlier,
amount: 0.0,
};
};
let earlier_at = keys[earlier].0;
let span = later_at - earlier_at;
Self {
earlier,
later: after,
amount: match span > 0.0 {
true => (at - earlier_at) / span,
false => 1.0,
},
}
}
}
enum Curve {
Earlier,
OutOf,
Later,
Into,
}
fn hermite(amount: f32, curve: Curve) -> f32 {
let squared = amount * amount;
let cubed = squared * amount;
match curve {
Curve::Earlier => 2.0 * cubed - 3.0 * squared + 1.0,
Curve::OutOf => cubed - 2.0 * squared + amount,
Curve::Later => 3.0 * squared - 2.0 * cubed,
Curve::Into => cubed - squared,
}
}
pub(crate) trait Mixed: Copy {
fn mixed(self, other: Self, amount: f32) -> Self;
fn combined(weighted: [(Self, f32); 4]) -> Self;
}
impl Mixed for Vec3 {
fn mixed(self, other: Self, amount: f32) -> Self {
self.lerp(other, amount)
}
fn combined(weighted: [(Self, f32); 4]) -> Self {
weighted
.into_iter()
.map(|(value, amount)| value * amount)
.sum()
}
}
impl Mixed for Quat {
fn mixed(self, other: Self, amount: f32) -> Self {
self.lerp(other, amount)
}
fn combined(weighted: [(Self, f32); 4]) -> Self {
let summed = weighted.into_iter().fold(
Quat::from_xyzw(0.0, 0.0, 0.0, 0.0),
|sum, (turn, amount)| sum + turn * amount,
);
normalized(summed)
}
}
fn normalized(turn: Quat) -> Quat {
match turn.length_squared() > 0.0 {
true => turn.normalize(),
false => Quat::IDENTITY,
}
}