use crate::base::{Error, Result};
use crate::three::animation::{Animation, NodePose};
use crate::three::extract::MeshData;
use crate::three::math::Mat4;
use super::{Model, MAX_NODE_DEPTH};
#[derive(Debug, Clone)]
pub struct RigNode {
pub rest: NodePose,
pub matrix: Option<Mat4>,
pub children: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct SkinData {
pub joints: Vec<usize>,
pub inverse_bind: Vec<Mat4>,
}
#[derive(Debug, Clone, Default)]
pub struct Rig {
pub nodes: Vec<RigNode>,
pub roots: Vec<usize>,
pub animations: Vec<Animation>,
pub skins: Vec<SkinData>,
pub instance_skins: Vec<Option<usize>>,
}
#[derive(Debug, Clone, Default)]
pub struct Pose {
pub instance_worlds: Vec<Mat4>,
pub skin_joints: Vec<Vec<Mat4>>,
poses: Vec<NodePose>,
worlds: Vec<Mat4>,
stack: Vec<(usize, Mat4, usize)>,
}
impl Model {
pub fn animations(&self) -> &[Animation] {
self.rig
.as_ref()
.map(|r| r.animations.as_slice())
.unwrap_or(&[])
}
fn node_worlds_into(&self, anim: usize, t: f32, pose: &mut Pose) -> bool {
let Some(rig) = self.rig.as_ref() else {
return false;
};
let Some(animation) = rig.animations.get(anim) else {
return false;
};
pose.poses.clear();
pose.poses.extend(rig.nodes.iter().map(|n| n.rest));
animation.sample(t, &mut pose.poses);
pose.worlds.clear();
pose.worlds.resize(rig.nodes.len(), Mat4::IDENTITY);
pose.stack.clear();
pose.stack
.extend(rig.roots.iter().rev().map(|&r| (r, Mat4::IDENTITY, 0)));
while let Some((ni, parent, depth)) = pose.stack.pop() {
if depth > MAX_NODE_DEPTH {
return false;
}
let node = &rig.nodes[ni];
let local = node.matrix.unwrap_or_else(|| pose.poses[ni].matrix());
let world = parent.mul(&local);
pose.worlds[ni] = world;
for &c in node.children.iter().rev() {
pose.stack.push((c, world, depth + 1));
}
}
true
}
pub fn sample_pose(&self, anim: usize, t: f32, out: &mut Vec<Mat4>) -> bool {
let mut pose = Pose::default();
if !self.sample_pose_full(anim, t, &mut pose) {
return false;
}
out.clear();
out.append(&mut pose.instance_worlds);
true
}
pub fn sample_pose_full(&self, anim: usize, t: f32, pose: &mut Pose) -> bool {
if !self.node_worlds_into(anim, t, pose) {
return false;
}
let rig = self.rig.as_ref().expect("node_worlds_into implies rig");
let Pose {
instance_worlds,
skin_joints,
worlds,
..
} = pose;
instance_worlds.clear();
for inst in &self.instances {
instance_worlds.push(match inst.source_node {
Some(n) => worlds[n],
None => inst.world,
});
}
skin_joints.resize(rig.skins.len(), Vec::new());
skin_joints.truncate(rig.skins.len());
for (skin, out) in rig.skins.iter().zip(skin_joints.iter_mut()) {
out.clear();
out.extend(
skin.joints
.iter()
.zip(&skin.inverse_bind)
.map(|(&j, ibm)| worlds[j].mul(ibm)),
);
}
true
}
pub fn instance_skin(&self, i: usize) -> Option<usize> {
self.rig
.as_ref()
.and_then(|r| r.instance_skins.get(i).copied().flatten())
}
}
pub(super) fn sanitize_skin_vertices(
data: &mut MeshData,
joint_count: usize,
warnings: &mut Vec<String>,
) -> Result<()> {
let (Some(joints), Some(weights)) = (&data.joints, &mut data.weights) else {
return Ok(()); };
let mut renormalized = false;
for (vi, (j, w)) in joints.iter().zip(weights.iter_mut()).enumerate() {
for (slot, &ji) in j.iter().enumerate() {
if w[slot] != 0.0 && ji as usize >= joint_count {
return Err(Error::Parse(format!(
"gltf: vertex {vi} joint index {ji} out of range ({joint_count} joints)"
)));
}
}
if w.iter().any(|c| !c.is_finite() || *c < 0.0) {
return Err(Error::Parse(format!(
"gltf: vertex {vi} has non-finite or negative skin weights"
)));
}
let sum: f32 = w.iter().sum();
if sum <= 1e-6 {
return Err(Error::Parse(format!(
"gltf: vertex {vi} skin weights sum to zero"
)));
}
if (sum - 1.0).abs() > 0.01 {
for c in w.iter_mut() {
*c /= sum;
}
renormalized = true;
}
}
if renormalized {
warnings.push("#FALLBACK skin weights renormalized (sums drifted past 1%)".to_string());
}
Ok(())
}