bevyengine/bevy · error · MeshMergeDuplicateVerticesError

Index attribute already set.

Error message

Index attribute already set.

What it means

MeshMergeDuplicateVerticesError::IndicesAlreadySet (crates/bevy_mesh/src/mesh.rs:3016), returned by Mesh::merge_duplicate_vertices when self.try_indices() finds an existing index buffer. The function's contract is to CREATE indices from a non-indexed vertex soup; an already-indexed mesh violates that precondition, so it refuses instead of overwriting valid topology.

Solutions

  1. If you really want to rebuild indices, drop the existing buffer first: mesh.remove_indices(), then merge_duplicate_vertices()
  2. For flat shading of indexed geometry use the documented recipe: mesh.duplicate_vertices() then mesh.compute_flat_normals()
  3. For smooth shading keep the existing indices and call compute_smooth_normals() instead

Example fix

// before: Sphere builder already inserted indices
let mut mesh = Sphere::new(1.0).mesh().build();
mesh.merge_duplicate_vertices(); // Err(IndicesAlreadySet)

// after: drop old indices, then dedupe
let mut mesh = Sphere::new(1.0).mesh().build();
mesh.remove_indices();
mesh.merge_duplicate_vertices().unwrap();
Defensive patterns

Strategy: try-catch

Validate before calling

if mesh.indices().is_none() {
    mesh.merge_duplicate_vertices()?;
} else {
    // already indexed: nothing to dedupe, or remove_indices() first if rebuilding
}

Type guard

fn is_unindexed(mesh: &Mesh) -> bool {
    mesh.indices().is_none()
}

Try / catch

match mesh.merge_duplicate_vertices() {
    Ok(()) => {}
    Err(MeshMergeDuplicateVerticesError::IndicesAlreadySet) => {
        // decide: keep existing topology, or mesh.remove_indices() and retry
    }
    Err(MeshMergeDuplicateVerticesError::MeshAccessError(e)) => {
        bevy::log::error!("mesh data unavailable: {e}");
    }
}

Prevention

When it happens

Trigger: Calling merge_duplicate_vertices() on any mesh that already has indices set: meshes from Bevy primitive builders (Sphere, Torus, ... all insert Indices::U32/U16), imported glTF geometry (indexed by nature), or a mesh where you previously called insert_indices/with_inserted_indices or an earlier successful merge_duplicate_vertices.

Common situations: Developer welds an imported indexed mesh to smooth seams and calls merge_duplicate_vertices directly; mixing up the documented normal-generation recipes (compute_smooth_normals wants indexed geometry, compute_flat_normals wants non-indexed) and reaching for the wrong helper.

Related errors


AI-assisted analysis of bevyengine/bevy@8d743eb7dc (2026-08-20). Data as JSON: /api/errors/26fd61a39108fc65. Report an issue: GitHub.

Appendix: source

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

                .into_iter()
                .map(|(id, data)| {
                    (
                        id,
                        SerializedMeshAttributeData::from_mesh_attribute_data(data),
                    )
                })
                .collect(),
            indices: mesh.indices.replace(None).expect(MESH_EXTRACTED_ERROR),
        }
    }

    /// Create a [`Mesh`] from a [`SerializedMesh`]. See the documentation for [`SerializedMesh`] for caveats.
    ///
    /// Use [`MeshDeserializer`] if you need to pass extra options to the deserialization process, such as specifying custom vertex attributes.
    pub fn into_mesh(self) -> Mesh {
        MeshDeserializer::default().deserialize(self)
    }
}

/// Use to specify extra options when deserializing a [`SerializedMesh`] into a [`Mesh`].
#[cfg(feature = "serialize")]
pub struct MeshDeserializer {
    custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
}

#[cfg(feature = "serialize")]
impl Default for MeshDeserializer {
    fn default() -> Self {
        // Written like this so that the compiler can validate that we use all the built-in attributes.
        // If you just added a new attribute and got a compile error, please add it to this list :)
        const BUILTINS: [MeshVertexAttribute; Mesh::FIRST_AVAILABLE_CUSTOM_ATTRIBUTE as usize] = [
            Mesh::ATTRIBUTE_POSITION,
            Mesh::ATTRIBUTE_NORMAL,
            Mesh::ATTRIBUTE_UV_0,
            Mesh::ATTRIBUTE_UV_1,
            Mesh::ATTRIBUTE_TANGENT,

View on GitHub (pinned to 8d743eb7dc)