bevyengine/bevy · error · GenerateTangentsError

missing indices

Error message

missing indices

What it means

GenerateTangentsError::MissingIndices (crates/bevy_mesh/src/mikktspace.rs:67), returned when Mesh::compute_tangents is called on a mesh without an index buffer. Mikktspace needs face connectivity (which vertices form each triangle) to compute per-triangle tangents; raw vertex soups carry no such information.

Source

Thrown at crates/bevy_mesh/src/mikktspace.rs:67

    }

    fn set_tangent(
        &mut self,
        tangent_space: Option<bevy_mikktspace::TangentSpace>,
        face: usize,
        vert: usize,
    ) {
        let idx = self.index(face, vert);
        self.tangents[idx] = tangent_space.unwrap_or_default().tangent_encoded();
    }
}

#[derive(Error, Debug)]
/// Failed to generate tangents for the mesh.
pub enum GenerateTangentsError {
    #[error("cannot generate tangents for {0:?}")]
    UnsupportedTopology(PrimitiveTopology),
    #[error("missing indices")]
    MissingIndices,
    #[error("missing vertex attributes '{0}'")]
    MissingVertexAttribute(&'static str),
    #[error("the '{0}' vertex attribute should have {1:?} format")]
    InvalidVertexAttributeFormat(&'static str, VertexFormat),
    #[error("mesh not suitable for tangent generation")]
    MikktspaceError(#[from] bevy_mikktspace::GenerateTangentSpaceError),
    #[error("Mesh access error: {0}")]
    MeshAccessError(#[from] MeshAccessError),
}

pub(crate) fn generate_tangents_for_mesh(
    mesh: &Mesh,
) -> Result<Vec<[f32; 4]>, GenerateTangentsError> {
    match mesh.primitive_topology() {
        PrimitiveTopology::TriangleList => {}
        other => return Err(GenerateTangentsError::UnsupportedTopology(other)),
    };

View on GitHub (pinned to 396ca72708)

Solutions

  1. Generate an index buffer with mesh.merge_duplicate_vertices() (it welds the soup and produces Indices::U32)
  2. Or supply explicit triangle indices with mesh.insert_indices(Indices::U32(vec)) / Indices::U16
  3. Re-check ordering: compute tangents after indexing, not before

Example fix

// before: attribute-only mesh
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh.compute_tangents(); // Err(MissingIndices)

// after: weld into an indexed mesh first
mesh.merge_duplicate_vertices().unwrap();
mesh.compute_tangents().unwrap();
Defensive patterns

Strategy: try-catch

Validate before calling

if mesh.indices().is_none() {
    mesh.merge_duplicate_vertices()?; // produces Indices::U32
}
mesh.compute_tangents()?;

Type guard

fn is_indexed(mesh: &Mesh) -> bool {
    mesh.indices().is_some()
}

Try / catch

match mesh.compute_tangents() {
    Ok(()) => {}
    Err(GenerateTangentsError::MissingIndices) => {
        mesh.merge_duplicate_vertices().expect("weld failed");
        mesh.compute_tangents()?; // retry once, now indexed
    }
    Err(e) => bevy::log::error!("tangent generation failed: {e}"),
}

Prevention

When it happens

Trigger: Calling compute_tangents() on a mesh built with only insert_attribute calls and no insert_indices / with_inserted_indices — e.g. a manually assembled triangle soup, or a mesh processed by duplicate_vertices() (which strips indices).

Common situations: Following the flat-normals recipe (duplicate_vertices -> compute_flat_normals) and then adding normal mapping; hand-written quad/triangle builders that rely on implicit ordering; importer paths that drop empty index buffers.

Related errors


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