bevyengine/bevy · error · MeshAttributeError

Attribute "{0}" has unexpected format {1:?}

Error message

Attribute "{0}" has unexpected format {1:?}

What it means

MeshAttributeError::UnexpectedFormat is returned when a mesh attribute exists but its VertexFormat differs from the one the API requires. The expect_attribute_* helpers in bevy_mesh::skinning accept exactly one variant: POSITION must be Float32x3, JOINT_INDEX must be Uint16x4, and JOINT_WEIGHT must be Float32x4. Any other variant for those attributes produces this error, with the offending format included in the message.

Source

Thrown at crates/bevy_mesh/src/skinning.rs:416

            if joint_weight > 0.0 {
                return Some(Influence {
                    vertex_index: self.vertex_index,
                    joint_index: JointIndex(joint_index),
                    joint_weight,
                });
            }
        }
    }
}

/// Generic error for when a mesh was expected to have a certain attribute with
/// a certain format.
#[derive(Copy, Clone, PartialEq, Debug, Error)]
pub enum MeshAttributeError {
    #[error("Missing attribute \"{0}\"")]
    MissingAttribute(&'static str),
    #[error("Attribute \"{0}\" has unexpected format {1:?}")]
    UnexpectedFormat(&'static str, VertexFormat),
}

// Implements a function that returns a mesh attribute's data or `MeshAttributeError`.
//
// ```
// impl_expect_attribute!(expect_attribute_float32x3, Float32x3, [f32; 3]);
//
// let positions: Vec<[f32; 3]> = expect_attribute_float32x3(mesh, Mesh::ATTRIBUTE_POSITION)?;
// ```
macro_rules! impl_expect_attribute {
    ($name:ident, $value_type:ident, $output_type:ty) => {
        fn $name<'a>(
            mesh: &'a Mesh,
            attribute: MeshVertexAttribute,
        ) -> Result<&'a Vec<$output_type>, MeshAttributeError> {
            match mesh.attribute(attribute) {
                Some(VertexAttributeValues::$value_type(v)) => Ok(v),

View on GitHub (pinned to 396ca72708)

Solutions

  1. Re-insert the attribute in the exact required format: convert JOINT_INDEX to Vec<[u16; 4]> and JOINT_WEIGHT to Vec<[f32; 4]> before inserting
  2. If joint indices exceed u16 range, remap/reindex joints so they fit u16, then store Uint16x4
  3. Read the error's format field to see the actual variant and write a targeted conversion

Example fix

// before: joint indices stored as u32
mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_INDEX, joint_indices_u32x4);
SkinnedMeshBounds::from_mesh(&mesh)?; // Err(UnexpectedFormat("JOINT_INDEX", Uint32x4))

// after: convert to the expected Uint16x4
let joint_indices_u16x4: Vec<[u16; 4]> = joint_indices_u32x4
    .iter()
    .map(|j| [j[0] as u16, j[1] as u16, j[2] as u16, j[3] as u16])
    .collect();
mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_INDEX, joint_indices_u16x4);
SkinnedMeshBounds::from_mesh(&mesh)?; // Ok(..)
Defensive patterns

Strategy: type-guard

Validate before calling

// Confirm the exact formats skinning requires before calling from_mesh
fn skin_attributes_well_formed(mesh: &Mesh) -> bool {
    matches!(mesh.attribute(Mesh::ATTRIBUTE_POSITION), Some(VertexAttributeValues::Float32x3(_)))
        && matches!(mesh.attribute(Mesh::ATTRIBUTE_JOINT_INDEX), Some(VertexAttributeValues::Uint16x4(_)))
        && matches!(mesh.attribute(Mesh::ATTRIBUTE_JOINT_WEIGHT), Some(VertexAttributeValues::Float32x4(_)))
}

Type guard

fn attribute_is_format(mesh: &Mesh, attr: MeshVertexAttribute, fmt: VertexFormat) -> bool {
    mesh.attribute(attr).is_some_and(|v| VertexFormat::from(v) == fmt)
}

Try / catch

match SkinnedMeshBounds::from_mesh(&mesh) {
    Ok(bounds) => { /* use */ }
    Err(SkinnedMeshBoundsError::MeshAttributeError(
        MeshAttributeError::UnexpectedFormat(name, fmt),
    )) => {
        warn!("attribute {name} has format {fmt:?}; re-export the mesh with the standard skin formats");
    }
    Err(other) => warn!("skinning bounds failed: {other:?}"),
}

Prevention

When it happens

Trigger: Calling SkinnedMeshBounds::from_mesh (or InfluenceIterator::new) on a mesh where, for example, JOINT_INDEX is stored as Uint32x4/Uint8x4 instead of Uint16x4, JOINT_WEIGHT is not Float32x4, or POSITION is not Float32x3 (e.g. half-float or Float32x2).

Common situations: Importers or packers that widen joint indices to u32 for precision; custom compressed vertex formats; hand-building a skinned mesh with the wrong Rust type for weights (e.g. [f32; 3] or u16 weights); meshes converted for vertex-compression pipelines.

Related errors


AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20). Data as JSON: /api/errors/6a4d3ec43bfb3d02. Report an issue: GitHub.