{"record":{"id":"b1deeebbaf06cbd9","repo":"bevyengine/bevy","slug":"the-base-mesh-did-not-have-vertex-positions","errorCode":null,"errorMessage":"The base mesh did not have vertex positions","messagePattern":"The base mesh did not have vertex positions","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/bevy_mesh/src/primitives/extrusion.rs","lineNumber":243,"sourceCode":"                    _ => {\n                        panic!(\"Meshes used with Extrusions must have a primitive topology of `PrimitiveTopology::TriangleList`\");\n                    }\n                };\n            }\n            back_face\n        };\n\n        // An extrusion of depth 0 does not need a mantel\n        if self.half_depth == 0. {\n            front_face.merge(&back_face).unwrap();\n            return front_face;\n        }\n\n        let mantel = {\n            let Some(VertexAttributeValues::Float32x3(cap_verts)) =\n                front_face.attribute(Mesh::ATTRIBUTE_POSITION)\n            else {\n                panic!(\"The base mesh did not have vertex positions\");\n            };\n\n            debug_assert!(self.segments > 0);\n\n            let layers = self.segments + 1;\n            let layer_depth_delta = self.half_depth * 2.0 / self.segments as f32;\n\n            let perimeter = self.base_builder.perimeter();\n            let (vert_count, index_count) =\n                perimeter\n                    .iter()\n                    .fold((0, 0), |(verts, indices), perimeter| {\n                        (\n                            verts + layers * perimeter.vertices_per_layer() as usize,\n                            indices + self.segments * perimeter.indices_per_segment(),\n                        )\n                    });\n            let mut positions = Vec::with_capacity(vert_count);","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/bevyengine/bevy/blob/396ca727080776bd313bb892423b7d94e03b81b4/crates/bevy_mesh/src/primitives/extrusion.rs#L225-L261","documentation":"Panic thrown while bevy_mesh's Extrusion primitive builds its mesh. After constructing the front and back caps, the extrusion reads the base shape's vertex positions as VertexAttributeValues::Float32x3 to generate the mantel (side walls). If the mesh produced by the base shape builder has no Mesh::ATTRIBUTE_POSITION attribute, or stores it in a non-Float32x3 format, this panic fires. All built-in 2D primitives supply positions, so hitting it almost always means a custom or modified base shape builder that omits them.","triggerScenarios":"Calling .mesh() on an Extrusion (or spawning it as a Mesh) with a non-zero depth, where the base shape's built mesh lacks Mesh::ATTRIBUTE_POSITION or has positions in a format other than Float32x3. A depth of 0 returns early (front face merged with back face) before this check, so only non-zero depths reach the panic.","commonSituations":"Implementing a custom Primitive2d shape whose mesh construction forgets mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); converting or compressing positions to another VertexFormat before extruding; test meshes stripped of attributes.","solutions":["In the custom base shape's mesh construction, insert positions as Float32x3: mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions) where positions is Vec<[f32; 3]>","If positions were converted to another format (e.g. half-float or snorm), re-insert them as VertexAttributeValues::Float32x3 before building the Extrusion","Debug-print the base mesh's attributes to confirm what is missing: build the base shape's mesh alone and inspect mesh.attribute(Mesh::ATTRIBUTE_POSITION)","While developing the custom shape, substitute a built-in primitive (Rectangle, Circle, RegularPolygon) as the base to verify the rest of the pipeline"],"exampleFix":"// before: custom base shape builds a mesh without positions\nimpl Primitive2d for MyShape { /* ... */ }\nfn build_mesh() -> Mesh {\n    let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, default());\n    mesh.insert_indices(indices);\n    // positions never inserted -> panics inside Extrusion\n    mesh\n}\n\n// after: always insert Float32x3 positions\nfn build_mesh() -> Mesh {\n    let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, default());\n    mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); // Vec<[f32; 3]>\n    mesh.insert_indices(indices);\n    mesh\n}","handlingStrategy":"validation","validationCode":"// Validate the base shape's mesh before extruding it (e.g. in a test)\nfn base_has_positions(shape: &impl Primitive2d + Extrudable) -> bool {\n    let mesh = primitives_extrusion_build_base(shape); // whatever builds the cap mesh\n    matches!(\n        mesh.attribute(Mesh::ATTRIBUTE_POSITION),\n        Some(VertexAttributeValues::Float32x3(_))\n    )\n}","typeGuard":"fn has_float32x3_positions(mesh: &Mesh) -> bool {\n    matches!(\n        mesh.attribute(Mesh::ATTRIBUTE_POSITION),\n        Some(VertexAttributeValues::Float32x3(_))\n    )\n}","tryCatchPattern":null,"preventionTips":["Every custom Primitive2d used as an Extrusion base must insert Mesh::ATTRIBUTE_POSITION as Vec<[f32; 3]> in its mesh builder","Add a unit test per custom shape asserting the built mesh has Float32x3 positions","Never convert position attributes to compressed formats before extrusion; compress after, if at all"],"tags":["bevy","mesh","extrusion","primitive","panic","vertex-positions"],"backgroundTag":"missing-mesh-attribute","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"}