{"record":{"id":"b93b2577ffe7220d","repo":"bevyengine/bevy","slug":"mesh-not-suitable-for-tangent-generation","errorCode":null,"errorMessage":"mesh not suitable for tangent generation","messagePattern":"mesh not suitable for tangent generation","errorType":"exception","errorClass":"GenerateTangentsError","httpStatus":null,"severity":"error","filePath":"crates/bevy_mesh/src/mikktspace.rs","lineNumber":73,"sourceCode":"        vert: usize,\n    ) {\n        let idx = self.index(face, vert);\n        self.tangents[idx] = tangent_space.unwrap_or_default().tangent_encoded();\n    }\n}\n\n#[derive(Error, Debug)]\n/// Failed to generate tangents for the mesh.\npub enum GenerateTangentsError {\n    #[error(\"cannot generate tangents for {0:?}\")]\n    UnsupportedTopology(PrimitiveTopology),\n    #[error(\"missing indices\")]\n    MissingIndices,\n    #[error(\"missing vertex attributes '{0}'\")]\n    MissingVertexAttribute(&'static str),\n    #[error(\"the '{0}' vertex attribute should have {1:?} format\")]\n    InvalidVertexAttributeFormat(&'static str, VertexFormat),\n    #[error(\"mesh not suitable for tangent generation\")]\n    MikktspaceError(#[from] bevy_mikktspace::GenerateTangentSpaceError),\n    #[error(\"Mesh access error: {0}\")]\n    MeshAccessError(#[from] MeshAccessError),\n}\n\npub(crate) fn generate_tangents_for_mesh(\n    mesh: &Mesh,\n) -> Result<Vec<[f32; 4]>, GenerateTangentsError> {\n    match mesh.primitive_topology() {\n        PrimitiveTopology::TriangleList => {}\n        other => return Err(GenerateTangentsError::UnsupportedTopology(other)),\n    };\n\n    let positions = mesh.try_attribute_option(Mesh::ATTRIBUTE_POSITION)?.ok_or(\n        GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),\n    )?;\n    let VertexAttributeValues::Float32x3(positions) = positions else {\n        return Err(GenerateTangentsError::InvalidVertexAttributeFormat(","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/bevyengine/bevy/blob/396ca727080776bd313bb892423b7d94e03b81b4/crates/bevy_mesh/src/mikktspace.rs#L55-L91","documentation":"GenerateTangentsError::MikktspaceError (crates/bevy_mesh/src/mikktspace.rs:73) wraps bevy_mikktspace::GenerateTangentSpaceError: the underlying MikkTSpace implementation ran over the mesh and gave up, meaning the geometry itself is unsuitable for tangent generation. Typical culprits are degenerate (zero-area) triangles, NaN/inf components in positions or UVs, or UVs collapsed so that no valid tangent frame exists.","triggerScenarios":"Calling mesh.compute_tangents() after topology, indices, and attribute presence/format checks have all passed, but the data contains degenerate triangles (repeated vertex indices, coincident positions), NaNs from bad math, or fully-collapsed UVs (all UVs identical).","commonSituations":"Procedural meshes with accidental duplicate vertices producing zero-area slivers; heightfield geometry with poles or seams where many triangles collapse to a point; imported assets with uninitialized UV channels set to (0,0) everywhere; division by zero upstream producing NaN positions.","solutions":["Sanitize the mesh: reject or drop triangles where two vertex indices coincide or positions are equal (zero area)","Check positions and UVs for non-finite values and clamp/repair them before compute_tangents()","Give UVs a non-degenerate mapping (identical UVs on every vertex cannot define a tangent frame)"],"exampleFix":"// before: degenerate soup passes format checks but fails inside mikktspace\nmesh.compute_tangents(); // Err(MikktspaceError(GenerateTangentSpaceError))\n\n// after: drop degenerate triangles first\nlet Indices::U32(idx) = mesh.indices().unwrap() else { unreachable!() };\nlet kept: Vec<[u32; 3]> = idx.as_chunks().0.iter().copied()\n    .filter(|&[a, b, c]| a != b && b != c && a != c)\n    .collect();\nmesh.insert_indices(Indices::U32(kept.into_iter().flatten().collect()));\nmesh.compute_tangents().unwrap();","handlingStrategy":"validation","validationCode":"fn geometry_sane_for_tangents(mesh: &Mesh) -> bool {\n    let ok_f32 = |x: &f32| x.is_finite();\n    let positions_ok = mesh\n        .attribute(Mesh::ATTRIBUTE_POSITION)\n        .and_then(|v| v.as_float3())\n        .is_some_and(|p| p.iter().flatten().all(ok_f32));\n    let uvs_ok = mesh\n        .attribute(Mesh::ATTRIBUTE_UV_0)\n        .and_then(|v| v.as_float2())\n        .is_some_and(|u| u.iter().flatten().all(ok_f32));\n    let no_degenerate = mesh.indices().is_some_and(|idx| match idx {\n        Indices::U16(v) => v.as_chunks().0.iter().all(|&[a, b, c]| a != b && b != c && a != c),\n        Indices::U32(v) => v.as_chunks().0.iter().all(|&[a, b, c]| a != b && b != c && a != c),\n    });\n    positions_ok && uvs_ok && no_degenerate\n}","typeGuard":null,"tryCatchPattern":"match mesh.compute_tangents() {\n    Ok(()) => {}\n    Err(GenerateTangentsError::MikktspaceError(e)) => {\n        bevy::log::error!(\"geometry unsuitable for tangents: {e}; check degenerate triangles/UVs\");\n        // fall back: skip tangents or sanitize mesh and retry once\n    }\n    Err(e) => bevy::log::error!(\"{e}\"),\n}","preventionTips":["Filter zero-area triangles (repeated indices, coincident positions) before tangent generation","Reject NaN/inf in positions and UVs at import time","Ensure UVs are not globally collapsed to a single value"],"tags":["bevy","mesh","tangents","mikktspace","degenerate-geometry","nan"],"backgroundTag":"degenerate-geometry","analyzedSha":"396ca727080776bd313bb892423b7d94e03b81b4","analyzedAt":"2026-08-20T16:12:39.808Z","contentChangedAt":"2026-08-20T16:12:39.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}