use crate::scene::SceneNode3d;
use glamx::{Quat, Vec3};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Interpolation {
Linear,
Step,
CubicSpline,
}
enum ChannelOutput {
Translation(Vec<Vec3>),
Rotation(Vec<Quat>),
Scale(Vec<Vec3>),
MorphWeights {
values: Vec<f32>,
num_targets: usize,
},
}
pub struct AnimationChannel {
target: SceneNode3d,
times: Vec<f32>,
interpolation: Interpolation,
output: ChannelOutput,
}
impl AnimationChannel {
pub fn translation(
target: SceneNode3d,
times: Vec<f32>,
values: Vec<Vec3>,
interpolation: Interpolation,
) -> Self {
Self {
target,
times,
interpolation,
output: ChannelOutput::Translation(values),
}
}
pub fn rotation(
target: SceneNode3d,
times: Vec<f32>,
values: Vec<Quat>,
interpolation: Interpolation,
) -> Self {
Self {
target,
times,
interpolation,
output: ChannelOutput::Rotation(values),
}
}
pub fn scale(
target: SceneNode3d,
times: Vec<f32>,
values: Vec<Vec3>,
interpolation: Interpolation,
) -> Self {
Self {
target,
times,
interpolation,
output: ChannelOutput::Scale(values),
}
}
pub fn morph_weights(
target: SceneNode3d,
times: Vec<f32>,
values: Vec<f32>,
num_targets: usize,
interpolation: Interpolation,
) -> Self {
Self {
target,
times,
interpolation,
output: ChannelOutput::MorphWeights {
values,
num_targets,
},
}
}
fn end_time(&self) -> f32 {
self.times.last().copied().unwrap_or(0.0)
}
fn segment(&self, t: f32) -> Option<(usize, usize, f32)> {
let n = self.times.len();
if n == 0 {
return None;
}
if t <= self.times[0] || n == 1 {
return Some((0, 0, 0.0));
}
if t >= self.times[n - 1] {
return Some((n - 1, n - 1, 0.0));
}
let i1 = self.times.partition_point(|&k| k <= t);
let i0 = i1 - 1;
let dt = self.times[i1] - self.times[i0];
let u = if dt > 0.0 {
(t - self.times[i0]) / dt
} else {
0.0
};
Some((i0, i1, u))
}
fn apply(&mut self, t: f32) {
let Some((i0, i1, u)) = self.segment(t) else {
return;
};
let cubic = self.interpolation == Interpolation::CubicSpline;
let dt = if i0 != i1 {
self.times[i1] - self.times[i0]
} else {
0.0
};
match &self.output {
ChannelOutput::Translation(v) => {
let value = sample_vec3(v, self.interpolation, i0, i1, u, dt);
self.target.set_position(value);
}
ChannelOutput::Scale(v) => {
let value = sample_vec3(v, self.interpolation, i0, i1, u, dt);
self.target.set_local_scale(value.x, value.y, value.z);
}
ChannelOutput::Rotation(v) => {
let value = if cubic {
sample_quat_cubic(v, i0, i1, u, dt)
} else if self.interpolation == Interpolation::Step || i0 == i1 {
v[i0]
} else {
v[i0].slerp(v[i1], u)
};
self.target.set_rotation(value.normalize());
}
ChannelOutput::MorphWeights {
values,
num_targets,
} => {
let weights =
sample_weights(values, *num_targets, self.interpolation, i0, i1, u, dt);
self.target.set_morph_weights(&weights);
}
}
}
}
fn sample_weights(
v: &[f32],
num_targets: usize,
interp: Interpolation,
i0: usize,
i1: usize,
u: f32,
dt: f32,
) -> Vec<f32> {
let mut out = vec![0.0; num_targets];
match interp {
Interpolation::Step => {
let base = i0 * num_targets;
out.copy_from_slice(&v[base..base + num_targets]);
}
Interpolation::Linear => {
let (a, b) = (i0 * num_targets, i1 * num_targets);
for k in 0..num_targets {
out[k] = if i0 == i1 {
v[a + k]
} else {
v[a + k] + (v[b + k] - v[a + k]) * u
};
}
}
Interpolation::CubicSpline => {
let (h00, h10, h01, h11) = hermite_basis(u);
for k in 0..num_targets {
let p0 = v[(3 * i0 + 1) * num_targets + k];
let m0 = v[(3 * i0 + 2) * num_targets + k] * dt;
let p1 = v[(3 * i1 + 1) * num_targets + k];
let m1 = v[(3 * i1) * num_targets + k] * dt;
out[k] = p0 * h00 + m0 * h10 + p1 * h01 + m1 * h11;
}
}
}
out
}
fn sample_vec3(v: &[Vec3], interp: Interpolation, i0: usize, i1: usize, u: f32, dt: f32) -> Vec3 {
match interp {
Interpolation::CubicSpline => {
let p0 = v[3 * i0 + 1];
let m0 = v[3 * i0 + 2] * dt; let p1 = v[3 * i1 + 1];
let m1 = v[3 * i1] * dt; hermite_vec3(p0, m0, p1, m1, u)
}
Interpolation::Step => v[i0],
Interpolation::Linear => {
if i0 == i1 {
v[i0]
} else {
v[i0].lerp(v[i1], u)
}
}
}
}
fn sample_quat_cubic(v: &[Quat], i0: usize, i1: usize, u: f32, dt: f32) -> Quat {
let p0 = v[3 * i0 + 1];
let m0 = quat_scale(v[3 * i0 + 2], dt);
let p1 = v[3 * i1 + 1];
let m1 = quat_scale(v[3 * i1], dt);
let (h00, h10, h01, h11) = hermite_basis(u);
let q = quat_add(
quat_add(quat_scale(p0, h00), quat_scale(m0, h10)),
quat_add(quat_scale(p1, h01), quat_scale(m1, h11)),
);
q.normalize()
}
fn hermite_vec3(p0: Vec3, m0: Vec3, p1: Vec3, m1: Vec3, u: f32) -> Vec3 {
let (h00, h10, h01, h11) = hermite_basis(u);
p0 * h00 + m0 * h10 + p1 * h01 + m1 * h11
}
fn hermite_basis(u: f32) -> (f32, f32, f32, f32) {
let u2 = u * u;
let u3 = u2 * u;
(
2.0 * u3 - 3.0 * u2 + 1.0, u3 - 2.0 * u2 + u, -2.0 * u3 + 3.0 * u2, u3 - u2, )
}
fn quat_scale(q: Quat, s: f32) -> Quat {
Quat::from_xyzw(q.x * s, q.y * s, q.z * s, q.w * s)
}
fn quat_add(a: Quat, b: Quat) -> Quat {
Quat::from_xyzw(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w)
}
pub struct AnimationClip {
pub name: String,
channels: Vec<AnimationChannel>,
duration: f32,
}
impl AnimationClip {
pub fn new(name: String, channels: Vec<AnimationChannel>) -> Self {
let duration = channels
.iter()
.map(|c| c.end_time())
.fold(0.0_f32, f32::max);
Self {
name,
channels,
duration,
}
}
pub fn duration(&self) -> f32 {
self.duration
}
fn apply(&mut self, t: f32) {
for ch in &mut self.channels {
ch.apply(t);
}
}
}
pub struct AnimationPlayer {
clips: Vec<AnimationClip>,
current: Option<usize>,
time: f32,
looping: bool,
speed: f32,
playing: bool,
}
impl AnimationPlayer {
pub fn new(clips: Vec<AnimationClip>) -> Self {
Self {
clips,
current: None,
time: 0.0,
looping: true,
speed: 1.0,
playing: false,
}
}
pub fn clip_count(&self) -> usize {
self.clips.len()
}
pub fn clip_names(&self) -> impl Iterator<Item = &str> {
self.clips.iter().map(|c| c.name.as_str())
}
pub fn play(&mut self, name: &str) -> bool {
match self.clips.iter().position(|c| c.name == name) {
Some(i) => {
self.play_index(i);
true
}
None => false,
}
}
pub fn play_index(&mut self, index: usize) {
if index < self.clips.len() {
self.current = Some(index);
self.time = 0.0;
self.playing = true;
}
}
pub fn stop(&mut self) {
self.playing = false;
}
pub fn is_playing(&self) -> bool {
self.playing
}
pub fn set_looping(&mut self, looping: bool) {
self.looping = looping;
}
pub fn set_speed(&mut self, speed: f32) {
self.speed = speed;
}
pub fn time(&self) -> f32 {
self.time
}
pub fn seek(&mut self, time: f32) {
self.time = time;
self.apply_current();
}
pub fn update(&mut self, dt: f32) {
if !self.playing {
return;
}
let Some(i) = self.current else {
return;
};
let duration = self.clips[i].duration;
self.time += dt * self.speed;
if duration > 0.0 {
if self.looping {
self.time = self.time.rem_euclid(duration);
} else if self.time >= duration {
self.time = duration;
self.playing = false;
} else if self.time < 0.0 {
self.time = 0.0;
self.playing = false;
}
} else {
self.time = 0.0;
}
self.apply_current();
}
fn apply_current(&mut self) {
if let Some(i) = self.current {
let t = self.time;
self.clips[i].apply(t);
}
}
}