use std::ops::{Add, Mul};
use glam::{FloatExt, Quat, Vec3};
use crate::{NodeId, Scene};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Interpolation {
Step,
Linear,
CubicSpline,
}
#[derive(Debug, Clone)]
pub enum Keyframes {
Translation(Vec<Vec3>),
Rotation(Vec<Quat>),
Scale(Vec<Vec3>),
Weights {
targets: usize,
values: Vec<f32>,
},
}
#[derive(Debug, Clone)]
pub struct Channel {
pub node: NodeId,
pub interpolation: Interpolation,
pub times: Vec<f32>,
pub keyframes: Keyframes,
}
#[derive(Debug, Clone)]
pub struct AnimationClip {
pub name: String,
pub duration: f32,
pub channels: Vec<Channel>,
}
impl AnimationClip {
pub fn apply(&self, scene: &mut Scene, offset: u32, time: f32, weight: f32) {
let time = time.clamp(0.0, self.duration.max(0.0));
for channel in &self.channels {
let id = NodeId(channel.node.0 + offset);
let Some(key) = Key::locate(&channel.times, time, channel.interpolation) else {
continue;
};
if let Keyframes::Weights { targets, values } = &channel.keyframes {
let weights: Vec<Option<f32>> = (0..*targets)
.map(|target| {
key.sample(|i| values.get(i * targets + target).copied(), f32::lerp)
})
.collect();
set_weights(scene, id, &weights, weight);
continue;
}
let Some(node) = scene.get_mut(id) else {
continue;
};
match &channel.keyframes {
Keyframes::Translation(values) => {
if let Some(v) = key.sample(|i| values.get(i).copied(), Vec3::lerp) {
node.position = node.position.lerp(v, weight);
}
}
Keyframes::Scale(values) => {
if let Some(v) = key.sample(|i| values.get(i).copied(), Vec3::lerp) {
node.scale = node.scale.lerp(v, weight);
}
}
Keyframes::Rotation(values) => {
if let Some(q) = key.sample(|i| values.get(i).copied(), Quat::slerp) {
node.rotation = node.rotation.slerp(q.normalize(), weight).normalize();
}
}
Keyframes::Weights { .. } => {}
}
}
}
pub fn apply_looped(&self, scene: &mut Scene, offset: u32, time: f32, weight: f32) {
let time = if self.duration > 0.0 {
time.rem_euclid(self.duration)
} else {
0.0
};
self.apply(scene, offset, time, weight);
}
}
fn set_weights(scene: &mut Scene, id: NodeId, weights: &[Option<f32>], weight: f32) {
let Some(node) = scene.get(id) else {
return;
};
let meshes: Vec<NodeId> = if node.as_mesh().is_some() {
vec![id]
} else {
(node.children().iter().copied())
.filter(|&child| scene[child].as_mesh().is_some())
.collect()
};
for mesh in meshes {
let Some(mesh) = scene[mesh].mesh_mut() else {
continue;
};
mesh.morph_weights.resize(weights.len(), 0.0);
for (out, value) in mesh.morph_weights.iter_mut().zip(weights) {
if let Some(value) = value {
*out += (value - *out) * weight;
}
}
}
}
struct Key {
interpolation: Interpolation,
index: usize,
t: f32,
dt: f32,
}
impl Key {
fn locate(times: &[f32], time: f32, interpolation: Interpolation) -> Option<Self> {
let last = times.len().checked_sub(1)?;
let next = times.partition_point(|&t| t <= time);
let (index, t, dt) = if next == 0 || next > last {
(next.min(last), 0.0, 0.0)
} else {
let dt = times[next] - times[next - 1];
let t = if dt > 0.0 {
(time - times[next - 1]) / dt
} else {
0.0
};
(next - 1, t, dt)
};
Some(Self {
interpolation,
index,
t,
dt,
})
}
fn sample<T>(&self, get: impl Fn(usize) -> Option<T>, mix: fn(T, T, f32) -> T) -> Option<T>
where
T: Copy + Mul<f32, Output = T> + Add<Output = T>,
{
let (k, t, dt) = (self.index, self.t, self.dt);
match self.interpolation {
Interpolation::Step => get(k),
Interpolation::Linear => {
let a = get(k)?;
Some(mix(a, get(k + 1).unwrap_or(a), t))
}
Interpolation::CubicSpline => {
let v0 = get(k * 3 + 1)?;
match (get(k * 3 + 2), get(k * 3 + 4), get(k * 3 + 3)) {
(Some(out0), Some(v1), Some(in1)) => {
Some(hermite(v0, out0 * dt, v1, in1 * dt, t))
}
_ => Some(v0),
}
}
}
}
}
fn hermite<T>(v0: T, m0: T, v1: T, m1: T, t: f32) -> T
where
T: Mul<f32, Output = T> + Add<Output = T>,
{
let t2 = t * t;
let t3 = t2 * t;
v0 * (2.0 * t3 - 3.0 * t2 + 1.0)
+ m0 * (t3 - 2.0 * t2 + t)
+ v1 * (-2.0 * t3 + 3.0 * t2)
+ m1 * (t3 - t2)
}