bevyengine/bevy · error · SkinnedMeshBoundsError
The mesh does not contain any joints that are skinned to ver
Error message
The mesh does not contain any joints that are skinned to vertices
What it means
SkinnedMeshBoundsError::NoSkinnedJoints is returned by SkinnedMeshBounds::from_mesh, which computes per-joint AABBs so skinned meshes can be frustum-culled tightly. from_mesh iterates every vertex influence with a non-zero joint weight and adds that vertex's position to the influencing joint's AABB accumulator. The error means at least one weighted influence existed, yet no joint ever received a valid point: every accumulator finished empty. In practice the POSITION attribute does not cover the vertices that carry the weights (too short or empty), or all weighted positions are NaN (f32::min/max silently drop NaN, leaving empty AABBs).
Source
Thrown at crates/bevy_mesh/src/skinning.rs:109
// Model-space AABBs that enclose the vertices skinned to a joint. Some
// joints may not be skinned to any vertices, so not every joint has an
// AABB.
//
// `aabb_index_to_joint_index` maps from an `aabbs` index to a joint index,
// which corresponds to `Mesh::ATTRIBUTE_JOINT_INDEX` and `SkinnedMesh::joints`.
//
// These arrays could be a single `Vec<(JointAabb, JointIndex)>`, but that
// would waste two bytes due to alignment.
//
// TODO: If https://github.com/bevyengine/bevy/issues/11570 is fixed, `Vec<_>`
// can be changed to `Box<[_]>`.
pub aabbs: Vec<JointAabb>,
pub aabb_index_to_joint_index: Vec<JointIndex>,
}
#[derive(Copy, Clone, PartialEq, Debug, Error)]
pub enum SkinnedMeshBoundsError {
#[error("The mesh does not contain any joints that are skinned to vertices")]
NoSkinnedJoints,
#[error(transparent)]
MeshAttributeError(#[from] MeshAttributeError),
}
impl SkinnedMeshBounds {
/// Create a `SkinnedMeshBounds` from a [`Mesh`].
///
/// The mesh is expected to have position, joint index and joint weight
/// attributes. If any are missing then a [`MeshAttributeError`] is returned.
pub fn from_mesh(mesh: &Mesh) -> Result<SkinnedMeshBounds, SkinnedMeshBoundsError> {
let vertex_positions = expect_attribute_float32x3(mesh, Mesh::ATTRIBUTE_POSITION)?;
let vertex_influences = InfluenceIterator::new(mesh)?;
// Find the maximum joint index.
let Some(max_joint_index) = vertex_influences
.clone()
.map(|i| i.joint_index.0 as usize)View on GitHub (pinned to 396ca72708)
Solutions
- Make the POSITION attribute cover all skinned vertices: vertex count of POSITION must equal the count of JOINT_INDEX/JOINT_WEIGHT and be non-zero
- Ensure at least one vertex with a non-zero weight has a finite (non-NaN) position; fix whatever produced NaN positions (bad inverse bind matrices, broken animation math)
- Sanity-check the mesh before deriving bounds: compare attribute lengths and scan positions for NaN
- If you cannot fix the asset, treat the error as a signal and fall back to mesh.compute_aabb() for culling
Example fix
// before: weights for 100 vertices but only 10 positions mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); // 10 verts mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_INDEX, joint_indices); // 100 verts mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_WEIGHT, joint_weights); // 100 verts SkinnedMeshBounds::from_mesh(&mesh)?; // Err(NoSkinnedJoints) // after: keep all attribute arrays the same length assert_eq!(positions.len(), joint_indices.len()); assert_eq!(positions.len(), joint_weights.len()); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_INDEX, joint_indices); mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_WEIGHT, joint_weights); SkinnedMeshBounds::from_mesh(&mesh)?; // Ok(..)
Defensive patterns
Strategy: try-catch
Validate before calling
// Verify skin data actually binds joints to existing vertices before deriving bounds
fn skinning_is_sane(mesh: &Mesh) -> bool {
let Some(VertexAttributeValues::Float32x3(pos)) = mesh.attribute(Mesh::ATTRIBUTE_POSITION) else { return false };
let Some(VertexAttributeValues::Uint16x4(idx)) = mesh.attribute(Mesh::ATTRIBUTE_JOINT_INDEX) else { return false };
let Some(VertexAttributeValues::Float32x4(wgt)) = mesh.attribute(Mesh::ATTRIBUTE_JOINT_WEIGHT) else { return false };
let n = idx.len().min(wgt.len());
(0..n).any(|v| {
pos.get(v).is_some_and(|p| p.iter().all(|c| c.is_finite()))
&& (0..4).any(|i| wgt[v][i] > 0.0)
})
} Try / catch
match SkinnedMeshBounds::from_mesh(&mesh) {
Ok(bounds) => { /* insert for tight culling */ }
Err(SkinnedMeshBoundsError::NoSkinnedJoints) => {
warn!("mesh '{}' has weights but no bound joints; falling back to static AABB", name);
let aabb = mesh.compute_aabb(); // fallback path
}
Err(SkinnedMeshBoundsError::MeshAttributeError(e)) => { /* fix attributes */ }
} Prevention
- Keep POSITION, JOINT_INDEX and JOINT_WEIGHT arrays at identical, non-zero vertex counts
- Reject or repair assets whose weighted vertices have NaN positions before runtime
- Treat NoSkinnedJoints as an asset-quality error: log it and fall back to compute_aabb() rather than crashing
When it happens
Trigger: Calling SkinnedMeshBounds::from_mesh(&mesh) (or a system that derives skinned-mesh bounds) on a mesh whose JOINT_INDEX/JOINT_WEIGHT arrays contain entries with weight > 0, but where vertex_positions.get(vertex_index) is None for all of them: POSITION has fewer vertices than the joint arrays (or is empty), or every weighted vertex position is NaN. Note: all-zero weights or an empty vertex list returns Ok(default) instead, not this error.
Common situations: Hand-authored or procedurally generated skinned meshes with mismatched attribute lengths (skin data exported for more vertices than positions); glTF assets whose position stream is truncated; degenerate positions produced by bad bind-pose math; test meshes with placeholder joint weights but no real geometry.
Related errors
- Missing attribute "{0}"
- Attribute "{0}" has unexpected format {1:?}
- unsupported primitive mode
- failed to generate tangents: {0}
- failed to generate morph targets: {0}
AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20).
Data as JSON: /api/errors/6c17c1e8350a7f5a.
Report an issue: GitHub.