bevyengine/bevy · error · MeshMergeError

Incompatible vertex attribute types: {} and {}

Error message

Incompatible vertex attribute types: {} and {}

What it means

MeshMergeError::IncompatibleVertexAttributes (crates/bevy_mesh/src/mesh.rs:3025), returned by Mesh::merge. When both meshes carry an attribute with the same id, merge extends self's buffer with other's; that only works when the VertexAttributeValues variants match exactly (Float32x3 with Float32x3, etc.). A type mismatch is rejected because concatenating different byte layouts would corrupt the buffer.

Source

Thrown at crates/bevy_mesh/src/mesh.rs:3025

            indices: serialized_mesh.indices.into(),
            ..Mesh::new(serialized_mesh.primitive_topology, RenderAssetUsages::default())
        }
    }
}

/// Error that can occur when calling [`Mesh::merge_duplicate_vertices`]
#[derive(Error, Debug, Clone)]
pub enum MeshMergeDuplicateVerticesError {
    #[error("Index attribute already set.")]
    IndicesAlreadySet,
    #[error("Mesh access error: {0}")]
    MeshAccessError(#[from] MeshAccessError),
}

/// Error that can occur when calling [`Mesh::merge`].
#[derive(Error, Debug, Clone)]
pub enum MeshMergeError {
    #[error("Incompatible vertex attribute types: {} and {}", self_attribute.name, other_attribute.map(|a| a.name).unwrap_or("None"))]
    IncompatibleVertexAttributes {
        self_attribute: MeshVertexAttribute,
        other_attribute: Option<MeshVertexAttribute>,
    },
    #[error(
        "Incompatible primitive topologies: {:?} and {:?}",
        self_primitive_topology,
        other_primitive_topology
    )]
    IncompatiblePrimitiveTopology {
        self_primitive_topology: PrimitiveTopology,
        other_primitive_topology: PrimitiveTopology,
    },
    #[error("Mesh access error: {0}")]
    MeshAccessError(#[from] MeshAccessError),
}

#[cfg(test)]

View on GitHub (pinned to 396ca72708)

Solutions

  1. Convert the mismatched attribute in one mesh so both use the same VertexAttributeValues variant, then merge again
  2. Remove the attribute you do not need from one mesh with mesh.remove_attribute(id) before merging
  3. Regenerate one mesh through the same builder/pipeline as the other so attribute formats line up by construction

Example fix

// before: same attribute id, different formats
let mut a = plane_mesh(); // POSITION: Float32x3
let mut b = imported_mesh(); // POSITION: Float32x2
a.merge(&b); // Err(IncompatibleVertexAttributes { self_attribute: POSITION(Float32x3), other: Float32x2 })

// after: convert b's positions to Float32x3 first
let fixed: Vec<[f32; 3]> = b.attribute(Mesh::ATTRIBUTE_POSITION).unwrap().as_float2().unwrap().iter().map(|p| [p[0], p[1], 0.0]).collect();
b.insert_attribute(Mesh::ATTRIBUTE_POSITION, fixed);
a.merge(&b).unwrap();
Defensive patterns

Strategy: try-catch

Validate before calling

use std::mem::discriminant;
use bevy_mesh::MeshVertexAttributeId;

// true when every attribute shared by both meshes uses the same values variant
fn attributes_compatible(a: &Mesh, b: &Mesh) -> bool {
    a.attributes().all(|(attr, va)| {
        b.attribute(attr.id).map_or(true, |vb| {
            discriminant(va) == discriminant(vb)
        })
    })
}

Try / catch

match a.merge(&b) {
    Ok(()) => {}
    Err(MeshMergeError::IncompatibleVertexAttributes { self_attribute, other_attribute }) => {
        bevy::log::error!(
            "cannot merge: {} vs {:?}", self_attribute.name, other_attribute.map(|a| a.name)
        );
        // convert or remove the offending attribute, then retry once
    }
    Err(e) => bevy::log::error!("merge failed: {e}"),
}

Prevention

When it happens

Trigger: Calling mesh_a.merge(&mesh_b) where an attribute with the same MeshVertexAttributeId exists in both but with different formats — e.g. ATTRIBUTE_POSITION as Float32x3 in one and Float32x2 in the other, or joint indices as Uint16x4 vs Uint32x4. Checked per attribute during the extend loop (mesh.rs:2182).

Common situations: Merging geometry from different sources (a Bevy primitive plus an imported glTF mesh); one side passed through compression/packing (Snorm8x4 normals) while the other kept plain Float32x3; custom attributes registered under the same id with inconsistent formats across code versions.

Related errors


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