use crate::animation::{Animation, AnimationProperty, SampledValue};
use crate::mesh::Mesh;
use crate::scene::{mat4_mul, NodeId, Scene3D, Transform};
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Pose {
pub translations: Vec<Option<[f32; 3]>>,
pub rotations: Vec<Option<[f32; 4]>>,
pub scales: Vec<Option<[f32; 3]>>,
pub morph_weights: Vec<Option<Vec<f32>>>,
}
impl Pose {
pub fn new(node_count: usize) -> Self {
Self {
translations: vec![None; node_count],
rotations: vec![None; node_count],
scales: vec![None; node_count],
morph_weights: vec![None; node_count],
}
}
pub fn node_count(&self) -> usize {
self.translations.len()
}
pub fn is_empty(&self) -> bool {
self.translations.iter().all(Option::is_none)
&& self.rotations.iter().all(Option::is_none)
&& self.scales.iter().all(Option::is_none)
&& self.morph_weights.iter().all(Option::is_none)
}
pub fn local_transform(&self, node: NodeId, base: &Transform) -> Transform {
let i = node.0 as usize;
let t = self.translations.get(i).copied().flatten();
let r = self.rotations.get(i).copied().flatten();
let s = self.scales.get(i).copied().flatten();
if t.is_none() && r.is_none() && s.is_none() {
return *base;
}
let (bt, br, bs) = match *base {
Transform::Trs {
translation,
rotation,
scale,
} => (translation, rotation, scale),
Transform::Matrix(m) => match Transform::from_matrix(m) {
Transform::Trs {
translation,
rotation,
scale,
} => (translation, rotation, scale),
Transform::Matrix(_) => ([0.0; 3], [0.0, 0.0, 0.0, 1.0], [1.0; 3]),
},
};
Transform::Trs {
translation: t.unwrap_or(bt),
rotation: r.unwrap_or(br),
scale: s.unwrap_or(bs),
}
}
}
impl Animation {
pub fn duration(&self) -> f32 {
self.channels
.iter()
.filter_map(|ch| ch.sampler.keyframes.last().copied())
.fold(0.0, f32::max)
}
pub fn sample_pose(&self, t: f32, node_count: usize) -> Pose {
let mut pose = Pose::new(node_count);
for ch in &self.channels {
let idx = ch.target.node.0 as usize;
if idx >= node_count {
continue;
}
let Some(value) = ch.sampler.sample(t) else {
continue;
};
match (ch.target.property, value) {
(AnimationProperty::Translation, SampledValue::Vec3(v)) => {
pose.translations[idx] = Some(v);
}
(AnimationProperty::Scale, SampledValue::Vec3(v)) => {
pose.scales[idx] = Some(v);
}
(AnimationProperty::Rotation, SampledValue::Quat(q)) => {
let n = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt();
if n.is_finite() && n > 0.0 {
pose.rotations[idx] = Some([q[0] / n, q[1] / n, q[2] / n, q[3] / n]);
}
}
(AnimationProperty::MorphWeights, SampledValue::Scalar(w)) => {
pose.morph_weights[idx] = Some(w);
}
_ => {}
}
}
pose
}
}
impl Scene3D {
pub fn posed_node_transforms(&self, pose: &Pose) -> Vec<Option<[[f32; 4]; 4]>> {
let n_nodes = self.nodes.len();
let mut out: Vec<Option<[[f32; 4]; 4]>> = vec![None; n_nodes];
if n_nodes == 0 {
return out;
}
let identity: [[f32; 4]; 4] = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
let mut stack: Vec<(NodeId, [[f32; 4]; 4])> =
self.roots.iter().rev().map(|r| (*r, identity)).collect();
while let Some((nid, parent)) = stack.pop() {
let idx = nid.0 as usize;
if idx >= n_nodes || out[idx].is_some() {
continue;
}
let node = &self.nodes[idx];
let local = pose.local_transform(nid, &node.transform);
let world = mat4_mul(parent, local.to_matrix());
out[idx] = Some(world);
for child in node.children.iter().rev() {
stack.push((*child, world));
}
}
out
}
pub fn world_mesh_at(&self, animation: &Animation, t: f32, node: NodeId) -> Option<Mesh> {
let pose = animation.sample_pose(t, self.nodes.len());
let worlds = self.posed_node_transforms(&pose);
let weight_override = pose
.morph_weights
.get(node.0 as usize)
.and_then(|w| w.as_deref());
self.world_mesh_impl(node, &worlds, weight_override)
}
pub fn posed(&self, animation: &Animation, t: f32) -> Scene3D {
let pose = animation.sample_pose(t, self.nodes.len());
let mut out = self.clone();
for (i, node) in out.nodes.iter_mut().enumerate() {
node.transform = pose.local_transform(NodeId(i as u32), &node.transform);
}
for (i, weights) in pose.morph_weights.iter().enumerate() {
let Some(w) = weights else {
continue;
};
let Some(node) = out.nodes.get_mut(i) else {
continue;
};
if !node
.mesh
.is_some_and(|m| (m.0 as usize) < self.meshes.len())
{
continue;
}
node.weights = w.clone();
}
out
}
}