normalize joint weights (#10539)

# Objective

allow automatic fixing of bad joint weights.
fix #10447 

## Solution

- remove automatic normalization of vertexes with all zero joint
weights.
- add `Mesh::normalize_joint_weights` which fixes zero joint weights,
and also ensures that all weights sum to 1. this is a manual call as it
may be slow to apply to large skinned meshes, and is unnecessary if you
have control over the source assets.

note: this became a more significant problem with 0.12, as weights that
are close to, but not exactly 1 now seem to use `Vec3::ZERO` for the
unspecified weight, where previously they used the entity translation.
This commit is contained in:
robtfm 2024-01-27 16:13:38 +00:00 committed by GitHub
parent a955d65ffa
commit b35b9e5005
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -213,7 +213,7 @@ impl Mesh {
attribute: MeshVertexAttribute,
values: impl Into<VertexAttributeValues>,
) {
let mut values = values.into();
let values = values.into();
let values_format = VertexFormat::from(&values);
if values_format != attribute.format {
panic!(
@ -222,17 +222,6 @@ impl Mesh {
);
}
// validate attributes
if attribute.id == Self::ATTRIBUTE_JOINT_WEIGHT.id {
let VertexAttributeValues::Float32x4(ref mut values) = values else {
unreachable!() // we confirmed the format above
};
for value in values.iter_mut().filter(|v| *v == &[0.0, 0.0, 0.0, 0.0]) {
// zero weights are invalid
value[0] = 1.0;
}
}
self.attributes
.insert(attribute.id, MeshAttributeData { attribute, values });
}
@ -630,6 +619,31 @@ impl Mesh {
pub fn morph_target_names(&self) -> Option<&[String]> {
self.morph_target_names.as_deref()
}
/// Normalize joint weights so they sum to 1.
pub fn normalize_joint_weights(&mut self) {
if let Some(joints) = self.attribute_mut(Self::ATTRIBUTE_JOINT_WEIGHT) {
let VertexAttributeValues::Float32x4(ref mut joints) = joints else {
panic!("unexpected joint weight format");
};
for weights in joints.iter_mut() {
// force negative weights to zero
weights.iter_mut().for_each(|w| *w = w.max(0.0));
let sum: f32 = weights.iter().sum();
if sum == 0.0 {
// all-zero weights are invalid
weights[0] = 1.0;
} else {
let recip = sum.recip();
for weight in weights.iter_mut() {
*weight *= recip;
}
}
}
}
}
}
#[derive(Debug, Clone)]