bevyengine/bevy · error · GenerateTangentsError

Mesh access error: {0}

Error message

Mesh access error: {0}

What it means

GenerateTangentsError::MeshAccessError (crates/bevy_mesh/src/mikktspace.rs:75) propagates from the try_attribute_option calls at the start of generate_tangents_for_mesh. It is the render-world-extraction case: the mesh was created without MAIN_WORLD in its RenderAssetUsages, the renderer moved its data away, and tangent generation can no longer read POSITION/NORMAL/UV_0.

Source

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

        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)),
    };

    let positions = mesh.try_attribute_option(Mesh::ATTRIBUTE_POSITION)?.ok_or(
        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
    )?;
    let VertexAttributeValues::Float32x3(positions) = positions else {
        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(
            Mesh::ATTRIBUTE_POSITION.name,
            VertexFormat::Float32x3,

View on GitHub (pinned to 396ca72708)

Solutions

  1. Create the mesh with RenderAssetUsages::default() so CPU data survives extraction
  2. Run compute_tangents() during asset preparation, before the mesh is handed to the renderer
  3. Regenerate the mesh from source data if extraction already happened

Example fix

// before
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::RENDER_WORLD);
// ...extracted...
mesh.compute_tangents(); // Err(MeshAccessError::ExtractedToRenderWorld)

// after
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
mesh.compute_tangents().unwrap();
Defensive patterns

Strategy: validation

Validate before calling

if mesh.asset_usage().contains(RenderAssetUsages::MAIN_WORLD) {
    mesh.compute_tangents()?;
} else {
    // recreate mesh with RenderAssetUsages::default() first
}

Try / catch

if let Err(GenerateTangentsError::MeshAccessError(MeshAccessError::ExtractedToRenderWorld)) =
    mesh.compute_tangents()
{
    // re-run generation on a fresh CPU-resident mesh
}

Prevention

When it happens

Trigger: Calling mesh.compute_tangents() on a Mesh created with RenderAssetUsages::RENDER_WORLD after it has been prepared by the render pipeline at least once.

Common situations: Memory-tight projects marking generated meshes render-world-only; systems that add normal maps and re-tangent meshes at runtime, running after the first frame has already extracted the asset.

Related errors


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