bevyengine/bevy · error · MeshMergeError

Incompatible primitive topologies: {:?} and {:?}

Error message

Incompatible primitive topologies: {:?} and {:?}

What it means

MeshMergeError::IncompatiblePrimitiveTopology (crates/bevy_mesh/src/mesh.rs:3030), returned by Mesh::merge as its very first check (mesh.rs:2135). Merging concatenates index buffers with an offset, which is only meaningful when both meshes interpret those indices under the same primitive topology; mixing e.g. TriangleList with TriangleStrip would produce garbage geometry, so it is rejected up front.

Source

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

/// 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)]
mod tests {
    use super::Mesh;
    #[cfg(feature = "serialize")]
    use super::SerializedMesh;
    use crate::mesh::{Indices, MeshWindingInvertError, VertexAttributeValues};

View on GitHub (pinned to 396ca72708)

Solutions

  1. Rebuild one mesh with the same PrimitiveTopology you pass to Mesh::new for the other
  2. Convert the strip mesh's triangle list (re-expand strip indices into triangles) before merging
  3. Batch by topology: group meshes into merge sets that share a topology instead of merging across groups

Example fix

// before
let mut a = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
let b = Mesh::new(PrimitiveTopology::TriangleStrip, RenderAssetUsages::default());
a.merge(&b); // Err(IncompatiblePrimitiveTopology { self: TriangleList, other: TriangleStrip })

// after: build both with the same topology
let b = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
a.merge(&b).unwrap();
Defensive patterns

Strategy: validation

Validate before calling

fn same_topology(a: &Mesh, b: &Mesh) -> bool {
    a.primitive_topology() == b.primitive_topology()
}

if same_topology(&mesh_a, &mesh_b) {
    mesh_a.merge(&mesh_b)?;
}

Type guard

fn mergeable_pair<'a>(a: &'a mut Mesh, b: &'a Mesh) -> Option<&'a mut Mesh> {
    (a.primitive_topology() == b.primitive_topology()).then_some(a)
}

Try / catch

if let Err(MeshMergeError::IncompatiblePrimitiveTopology {
    self_primitive_topology,
    other_primitive_topology,
}) = mesh_a.merge(&mesh_b)
{
    bevy::log::error!("topology mismatch: {self_primitive_topology:?} vs {other_primitive_topology:?}");
}

Prevention

When it happens

Trigger: Calling mesh_a.merge(&mesh_b) where mesh_a was created with PrimitiveTopology::TriangleList and mesh_b with PrimitiveTopology::TriangleStrip (or any other differing variant such as LineList vs LineStrip).

Common situations: Merging a Bevy primitive (always TriangleList) with a custom mesh built as TriangleStrip to save indices; mixing 2D line geometry (LineList) with triangle meshes in a batching pass; topology defaults changing between asset importer versions.

Related errors


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