From b35b9e5005c90ccd95c7099ae72e9040fcc1a7c2 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Sat, 27 Jan 2024 16:13:38 +0000 Subject: [PATCH] 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. --- crates/bevy_render/src/mesh/mesh/mod.rs | 38 +++++++++++++++++-------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index d7fa777a9f..88a99b7973 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -213,7 +213,7 @@ impl Mesh { attribute: MeshVertexAttribute, values: impl Into, ) { - 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)]