use std::collections::HashMap;
use itertools::Itertools;
use rend3::{
types::{
glam::{Mat4, Quat, Vec3},
SkeletonHandle,
},
util::typedefs::{FastHashMap, FastHashSet},
Renderer,
};
use rend3_gltf::{AnimationChannel, GltfSceneInstance, LoadedGltfScene};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct AnimationIndex(pub usize);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct SkinIndex(pub usize);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct NodeIndex(pub usize);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct JointIndex(pub usize);
pub struct PerSkinData {
pub node_to_joint_idx: FastHashMap<NodeIndex, JointIndex>,
pub joint_nodes_topological_order: Vec<NodeIndex>,
pub skeletons: Vec<SkeletonHandle>,
}
pub struct AnimationData {
pub skin_data: FastHashMap<SkinIndex, PerSkinData>,
pub animation_skin_usage: FastHashMap<AnimationIndex, Vec<SkinIndex>>,
}
impl AnimationData {
pub fn from_gltf_scene(scene: &LoadedGltfScene, instance: &GltfSceneInstance) -> Self {
let animation_to_joint_nodes: HashMap<AnimationIndex, FastHashSet<NodeIndex>> = scene
.animations
.iter()
.enumerate()
.flat_map(|(anim_idx, anim)| {
anim.inner
.channels
.keys()
.map(move |node_idx| (AnimationIndex(anim_idx), NodeIndex(*node_idx)))
})
.into_grouping_map()
.collect::<FastHashSet<_>>();
let mut animation_skin_usage = FastHashMap::<AnimationIndex, Vec<SkinIndex>>::default();
for animation_idx in 0..scene.animations.len() {
let animation_idx = AnimationIndex(animation_idx);
for (skin_index, skin) in scene.skins.iter().enumerate() {
let skin_index = SkinIndex(skin_index);
let anim_affected_nodes = &animation_to_joint_nodes[&animation_idx];
if skin
.inner
.joints
.iter()
.any(|j| anim_affected_nodes.contains(&NodeIndex(j.inner.node_idx)))
{
let entry = animation_skin_usage
.entry(animation_idx)
.or_insert_with(Default::default);
entry.push(skin_index);
}
}
}
let mut skin_data = FastHashMap::default();
for (skin_index, skin) in scene.skins.iter().enumerate() {
let skin_index = SkinIndex(skin_index);
let node_to_joint_idx = skin
.inner
.joints
.iter()
.enumerate()
.map(|(idx, joint)| (NodeIndex(joint.inner.node_idx), JointIndex(idx)))
.collect();
let skin_nodes: Vec<NodeIndex> = skin.inner.joints.iter().map(|j| NodeIndex(j.inner.node_idx)).collect();
let joint_nodes_topological_order: Vec<NodeIndex> = instance
.topological_order
.iter()
.map(|node_idx| NodeIndex(*node_idx))
.filter(|node_idx| skin_nodes.contains(node_idx))
.collect();
let skeletons: Vec<SkeletonHandle> = instance
.nodes
.iter()
.flat_map(|node| &node.inner.object)
.flat_map(|object| &object.inner.armature)
.filter(|armature| armature.skin_index == skin_index.0)
.flat_map(|armature| &armature.skeletons)
.cloned()
.collect();
skin_data.insert(
skin_index,
PerSkinData {
node_to_joint_idx,
joint_nodes_topological_order,
skeletons,
},
);
}
AnimationData {
skin_data,
animation_skin_usage,
}
}
}
pub trait Lerp {
fn lerp(self, other: Self, t: f32) -> Self;
}
impl Lerp for Vec3 {
fn lerp(self, other: Self, t: f32) -> Self {
self.lerp(other, t)
}
}
impl Lerp for Quat {
fn lerp(self, other: Self, t: f32) -> Self {
self.lerp(other, t).normalize()
}
}
fn sample_at_time<T: Lerp + Copy>(channel: &AnimationChannel<T>, current_time: f32) -> T {
let next_idx = channel
.times
.iter()
.position(|time| *time > current_time)
.unwrap_or(channel.times.len() - 1);
let prev_idx = next_idx.saturating_sub(1);
let interp_factor = f32::clamp(
(current_time - channel.times[prev_idx]) / (channel.times[next_idx] - channel.times[prev_idx]),
0.0,
1.0,
);
channel.values[prev_idx].lerp(channel.values[next_idx], interp_factor)
}
pub fn pose_animation_frame(
renderer: &Renderer,
scene: &LoadedGltfScene,
instance: &GltfSceneInstance,
animation_data: &AnimationData,
animation_index: usize,
time: f32,
) {
let animation = &scene.animations[animation_index];
let time = time.clamp(0.0, animation.inner.duration);
for (skin_index, per_skin_data) in &animation_data.skin_data {
let skin = &scene.skins[skin_index.0];
let inv_bind_mats = &skin.inner.inverse_bind_matrices;
let mut joint_local_matrices = vec![Mat4::IDENTITY; inv_bind_mats.len()];
let node_to_joint_idx = &per_skin_data.node_to_joint_idx;
for (&node_idx, channels) in &animation.inner.channels {
let local_transform = instance.nodes[node_idx].inner.local_transform;
let (bind_scale, bind_rotation, bind_translation) = local_transform.to_scale_rotation_translation();
let translation = channels
.translation
.as_ref()
.map(|tra| sample_at_time(tra, time))
.unwrap_or(bind_translation);
let rotation = channels
.rotation
.as_ref()
.map(|rot| sample_at_time(rot, time))
.unwrap_or(bind_rotation);
let scale = channels
.scale
.as_ref()
.map(|sca| sample_at_time(sca, time))
.unwrap_or(bind_scale);
let matrix = Mat4::from_scale_rotation_translation(scale, rotation, translation);
let joint_idx = node_to_joint_idx[&NodeIndex(node_idx)];
joint_local_matrices[joint_idx.0] = matrix;
}
let mut global_joint_transforms = vec![Mat4::IDENTITY; inv_bind_mats.len()];
for node_idx in &per_skin_data.joint_nodes_topological_order {
let node = &instance.nodes[node_idx.0].inner;
let joint_idx = node_to_joint_idx[node_idx];
if let Some(parent_joint_idx) = node.parent.map(|pi| node_to_joint_idx.get(&NodeIndex(pi))) {
let parent_transform = parent_joint_idx
.map(|p| global_joint_transforms[p.0])
.unwrap_or(Mat4::IDENTITY);
let current_transform = joint_local_matrices[joint_idx.0];
global_joint_transforms[joint_idx.0] = parent_transform * current_transform;
} else {
global_joint_transforms[joint_idx.0] = joint_local_matrices[joint_idx.0];
}
}
for skeleton in &per_skin_data.skeletons {
renderer.set_skeleton_joint_transforms(skeleton, &global_joint_transforms, inv_bind_mats);
}
}
}