use crate::scene::NodeId;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AnimationProperty {
Translation,
Rotation,
Scale,
MorphWeights,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AnimationTarget {
pub node: NodeId,
pub property: AnimationProperty,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Interpolation {
Step,
Linear,
CubicSpline,
}
#[derive(Clone, Debug)]
pub struct AnimationSampler {
pub keyframes: Vec<f32>,
pub values: AnimationValues,
pub interpolation: Interpolation,
}
#[derive(Clone, Debug, PartialEq)]
pub enum SampledValue {
Vec3([f32; 3]),
Quat([f32; 4]),
Scalar(Vec<f32>),
}
impl AnimationSampler {
pub fn sample(&self, t: f32) -> Option<SampledValue> {
let n = self.keyframes.len();
if n == 0 {
return None;
}
let factor: usize = match self.interpolation {
Interpolation::CubicSpline => 3,
_ => 1,
};
let total = self.values.len();
if total == 0 || total % (n * factor) != 0 {
return None;
}
let per_frame_scalars = total / (n * factor);
if per_frame_scalars == 0 {
return None;
}
if t <= self.keyframes[0] {
return Some(self.value_at(0, factor, per_frame_scalars));
}
if t >= self.keyframes[n - 1] {
return Some(self.value_at(n - 1, factor, per_frame_scalars));
}
let k = match self
.keyframes
.binary_search_by(|kt| kt.partial_cmp(&t).unwrap_or(std::cmp::Ordering::Equal))
{
Ok(exact) => return Some(self.value_at(exact, factor, per_frame_scalars)),
Err(insert_at) => insert_at - 1,
};
let t_k = self.keyframes[k];
let t_k1 = self.keyframes[k + 1];
let t_d = t_k1 - t_k;
if t_d <= 0.0 {
return Some(self.value_at(k, factor, per_frame_scalars));
}
let u = (t - t_k) / t_d;
match self.interpolation {
Interpolation::Step => Some(self.value_at(k, factor, per_frame_scalars)),
Interpolation::Linear => self.lerp(k, u, per_frame_scalars),
Interpolation::CubicSpline => self.cubic(k, u, t_d, per_frame_scalars),
}
}
fn value_at(&self, k: usize, factor: usize, stride: usize) -> SampledValue {
let centre_off = if factor == 3 { 1 } else { 0 };
let base = (k * factor + centre_off) * stride;
match &self.values {
AnimationValues::Vec3(v) => SampledValue::Vec3(v[k * factor + centre_off]),
AnimationValues::Quat(v) => SampledValue::Quat(v[k * factor + centre_off]),
AnimationValues::Scalar(v) => SampledValue::Scalar(v[base..base + stride].to_vec()),
}
}
fn lerp(&self, k: usize, u: f32, stride: usize) -> Option<SampledValue> {
match &self.values {
AnimationValues::Vec3(v) => {
let a = v[k];
let b = v[k + 1];
Some(SampledValue::Vec3([
(1.0 - u) * a[0] + u * b[0],
(1.0 - u) * a[1] + u * b[1],
(1.0 - u) * a[2] + u * b[2],
]))
}
AnimationValues::Quat(v) => {
let q0 = v[k];
let q1 = v[k + 1];
Some(SampledValue::Quat(slerp(q0, q1, u)))
}
AnimationValues::Scalar(v) => {
let a_off = k * stride;
let b_off = (k + 1) * stride;
let mut out = Vec::with_capacity(stride);
for i in 0..stride {
out.push((1.0 - u) * v[a_off + i] + u * v[b_off + i]);
}
Some(SampledValue::Scalar(out))
}
}
}
fn cubic(&self, k: usize, u: f32, t_d: f32, stride: usize) -> Option<SampledValue> {
let uu = u * u;
let uuu = uu * u;
let c_v_k = 2.0 * uuu - 3.0 * uu + 1.0;
let c_b_k = t_d * (uuu - 2.0 * uu + u);
let c_v_k1 = -2.0 * uuu + 3.0 * uu;
let c_a_k1 = t_d * (uuu - uu);
let off_b_k = k * 3 + 2; let off_a_k1 = (k + 1) * 3; let off_v_k = k * 3 + 1; let off_v_k1 = (k + 1) * 3 + 1; match &self.values {
AnimationValues::Vec3(v) => {
let v_k = v[off_v_k];
let v_k1 = v[off_v_k1];
let b_k = v[off_b_k];
let a_k1 = v[off_a_k1];
Some(SampledValue::Vec3([
c_v_k * v_k[0] + c_b_k * b_k[0] + c_v_k1 * v_k1[0] + c_a_k1 * a_k1[0],
c_v_k * v_k[1] + c_b_k * b_k[1] + c_v_k1 * v_k1[1] + c_a_k1 * a_k1[1],
c_v_k * v_k[2] + c_b_k * b_k[2] + c_v_k1 * v_k1[2] + c_a_k1 * a_k1[2],
]))
}
AnimationValues::Quat(v) => {
let v_k = v[off_v_k];
let v_k1 = v[off_v_k1];
let b_k = v[off_b_k];
let a_k1 = v[off_a_k1];
Some(SampledValue::Quat([
c_v_k * v_k[0] + c_b_k * b_k[0] + c_v_k1 * v_k1[0] + c_a_k1 * a_k1[0],
c_v_k * v_k[1] + c_b_k * b_k[1] + c_v_k1 * v_k1[1] + c_a_k1 * a_k1[1],
c_v_k * v_k[2] + c_b_k * b_k[2] + c_v_k1 * v_k1[2] + c_a_k1 * a_k1[2],
c_v_k * v_k[3] + c_b_k * b_k[3] + c_v_k1 * v_k1[3] + c_a_k1 * a_k1[3],
]))
}
AnimationValues::Scalar(v) => {
let base_b_k = off_b_k * stride;
let base_a_k1 = off_a_k1 * stride;
let base_v_k = off_v_k * stride;
let base_v_k1 = off_v_k1 * stride;
let mut out = Vec::with_capacity(stride);
for i in 0..stride {
out.push(
c_v_k * v[base_v_k + i]
+ c_b_k * v[base_b_k + i]
+ c_v_k1 * v[base_v_k1 + i]
+ c_a_k1 * v[base_a_k1 + i],
);
}
Some(SampledValue::Scalar(out))
}
}
}
}
impl AnimationSampler {
pub fn morph_weights(
keyframes: Vec<f32>,
frames: Vec<Vec<f32>>,
interpolation: Interpolation,
) -> Option<Self> {
if matches!(interpolation, Interpolation::CubicSpline) {
return None;
}
let stride = uniform_stride(&keyframes, &[&frames])?;
let mut values = Vec::with_capacity(frames.len() * stride);
for f in &frames {
values.extend_from_slice(f);
}
Some(Self {
keyframes,
values: AnimationValues::Scalar(values),
interpolation,
})
}
pub fn morph_weights_cubic(
keyframes: Vec<f32>,
in_tangents: Vec<Vec<f32>>,
values: Vec<Vec<f32>>,
out_tangents: Vec<Vec<f32>>,
) -> Option<Self> {
let stride = uniform_stride(&keyframes, &[&in_tangents, &values, &out_tangents])?;
let mut flat = Vec::with_capacity(keyframes.len() * 3 * stride);
for k in 0..keyframes.len() {
flat.extend_from_slice(&in_tangents[k]);
flat.extend_from_slice(&values[k]);
flat.extend_from_slice(&out_tangents[k]);
}
Some(Self {
keyframes,
values: AnimationValues::Scalar(flat),
interpolation: Interpolation::CubicSpline,
})
}
pub fn morph_weight_stride(&self) -> Option<usize> {
let AnimationValues::Scalar(v) = &self.values else {
return None;
};
let n = self.keyframes.len();
if n == 0 {
return None;
}
let factor: usize = match self.interpolation {
Interpolation::CubicSpline => 3,
_ => 1,
};
let total = v.len();
if total == 0 || total % (n * factor) != 0 {
return None;
}
Some(total / (n * factor))
}
pub fn morph_weight_frame(&self, k: usize) -> Option<&[f32]> {
let stride = self.morph_weight_stride()?;
if k >= self.keyframes.len() {
return None;
}
let AnimationValues::Scalar(v) = &self.values else {
return None;
};
let (factor, centre) = match self.interpolation {
Interpolation::CubicSpline => (3, 1),
_ => (1, 0),
};
let base = (k * factor + centre) * stride;
v.get(base..base + stride)
}
pub fn morph_weight_frames(&self) -> Option<Vec<&[f32]>> {
self.morph_weight_stride()?;
(0..self.keyframes.len())
.map(|k| self.morph_weight_frame(k))
.collect()
}
pub fn morph_weight_cubic_frame(&self, k: usize) -> Option<(&[f32], &[f32], &[f32])> {
if !matches!(self.interpolation, Interpolation::CubicSpline) {
return None;
}
let stride = self.morph_weight_stride()?;
if k >= self.keyframes.len() {
return None;
}
let AnimationValues::Scalar(v) = &self.values else {
return None;
};
let base = k * 3 * stride;
Some((
v.get(base..base + stride)?,
v.get(base + stride..base + 2 * stride)?,
v.get(base + 2 * stride..base + 3 * stride)?,
))
}
}
fn uniform_stride(keyframes: &[f32], tables: &[&Vec<Vec<f32>>]) -> Option<usize> {
if keyframes.is_empty() {
return None;
}
let mut prev = f32::NEG_INFINITY;
for &t in keyframes {
if !t.is_finite() || t <= prev {
return None;
}
prev = t;
}
let stride = tables.first()?.first()?.len();
if stride == 0 {
return None;
}
for table in tables {
if table.len() != keyframes.len() || table.iter().any(|row| row.len() != stride) {
return None;
}
}
Some(stride)
}
fn slerp(q0: [f32; 4], q1: [f32; 4], t: f32) -> [f32; 4] {
let mut dot = q0[0] * q1[0] + q0[1] * q1[1] + q0[2] * q1[2] + q0[3] * q1[3];
let q1 = if dot < 0.0 {
dot = -dot;
[-q1[0], -q1[1], -q1[2], -q1[3]]
} else {
q1
};
let dot = dot.clamp(-1.0, 1.0);
let a = dot.acos();
let sin_a = a.sin();
if sin_a.abs() < 1.0e-6 {
let r = [
(1.0 - t) * q0[0] + t * q1[0],
(1.0 - t) * q0[1] + t * q1[1],
(1.0 - t) * q0[2] + t * q1[2],
(1.0 - t) * q0[3] + t * q1[3],
];
let n = (r[0] * r[0] + r[1] * r[1] + r[2] * r[2] + r[3] * r[3]).sqrt();
if n > 0.0 {
[r[0] / n, r[1] / n, r[2] / n, r[3] / n]
} else {
r
}
} else {
let w0 = ((1.0 - t) * a).sin() / sin_a;
let w1 = (t * a).sin() / sin_a;
[
w0 * q0[0] + w1 * q1[0],
w0 * q0[1] + w1 * q1[1],
w0 * q0[2] + w1 * q1[2],
w0 * q0[3] + w1 * q1[3],
]
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum AnimationValues {
Vec3(Vec<[f32; 3]>),
Quat(Vec<[f32; 4]>),
Scalar(Vec<f32>),
}
impl AnimationValues {
pub fn len(&self) -> usize {
match self {
Self::Vec3(v) => v.len(),
Self::Quat(v) => v.len(),
Self::Scalar(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Clone, Debug)]
pub struct AnimationChannel {
pub target: AnimationTarget,
pub sampler: AnimationSampler,
}
impl AnimationChannel {
pub fn new(node: NodeId, property: AnimationProperty, sampler: AnimationSampler) -> Self {
Self {
target: AnimationTarget { node, property },
sampler,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Animation {
pub name: Option<String>,
pub channels: Vec<AnimationChannel>,
}
impl Animation {
pub fn new(name: impl Into<Option<String>>) -> Self {
Self {
name: name.into(),
channels: Vec::new(),
}
}
pub fn with_channel(
mut self,
node: NodeId,
property: AnimationProperty,
sampler: AnimationSampler,
) -> Self {
self.channels
.push(AnimationChannel::new(node, property, sampler));
self
}
pub fn channel_for(
&self,
node: NodeId,
property: AnimationProperty,
) -> Option<&AnimationChannel> {
self.channels
.iter()
.rev()
.find(|ch| ch.target.node == node && ch.target.property == property)
}
}