bevyengine/bevy · error

`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of ty

Error message

`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`

What it means

Raised by Mesh::compute_flat_normals (crates/bevy_mesh/src/mesh.rs:1683). The function reads Mesh::ATTRIBUTE_POSITION through VertexAttributeValues::as_float3(), which returns Some only for the Float32x3 variant; any other storage format triggers this expect panic. Flat normals are computed per triangle, so positions must be exact 3-component floats.

Source

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

    /// attributes.
    ///
    /// FIXME: This should handle more cases since this is called as a part of gltf
    /// mesh loading where we can't really blame users for loading meshes that might
    /// not conform to the limitations here!
    pub fn try_compute_flat_normals(&mut self) -> Result<(), MeshAccessError> {
        assert!(
            self.try_indices_option()?.is_none(),
            "`compute_flat_normals` can't work on indexed geometry. Consider calling either `Mesh::compute_smooth_normals` or `Mesh::duplicate_vertices` followed by `Mesh::compute_flat_normals`."
        );
        assert!(
            matches!(self.primitive_topology, PrimitiveTopology::TriangleList),
            "`compute_flat_normals` can only work on `TriangleList`s"
        );

        let positions = self
            .try_attribute(Mesh::ATTRIBUTE_POSITION)?
            .as_float3()
            .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`");

        let normals: Vec<_> = positions
            .as_chunks()
            .0
            .iter()
            .flat_map(|&[a, b, c]| [triangle_normal(a, b, c); 3])
            .collect();

        self.try_insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
    }

    /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared
    /// vertices.
    ///
    /// This method weights normals by the angles of the corners of connected triangles, thus
    /// eliminating triangle area and count as factors in the final normal. This does make it
    /// somewhat slower than [`Mesh::compute_area_weighted_normals`] which does not need to
    /// greedily normalize each triangle's normal or calculate corner angles.

View on GitHub (pinned to 396ca72708)

Solutions

  1. Insert positions as Vec<[f32; 3]> / Float32x3 from the start
  2. Rewrite the existing attribute in place: take the old values, convert each to [f32; 3], and re-insert with mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, converted)
  3. Build geometry through Bevy's MeshBuilder/primitive helpers, which always emit Float32x3 positions

Example fix

// before: positions stored as 2-component floats
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vec![[0.0f32, 0.0]; 30]);
mesh.compute_flat_normals(); // panic: expects float3

// after: store positions as Float32x3
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vec![[0.0f32, 0.0, 0.0]; 30]);
mesh.compute_flat_normals();
Defensive patterns

Strategy: validation

Validate before calling

fn positions_are_float3(mesh: &Mesh) -> bool {
    mesh.attribute(Mesh::ATTRIBUTE_POSITION)
        .is_some_and(|v| v.as_float3().is_some())
}

if positions_are_float3(&mesh) {
    mesh.compute_flat_normals()?;
}

Type guard

fn float3_position_mesh(mesh: &Mesh) -> Option<&Vec<[f32; 3]>> {
    mesh.attribute(Mesh::ATTRIBUTE_POSITION)?.as_float3()
}

Prevention

When it happens

Trigger: Calling compute_flat_normals() on a non-indexed TriangleList mesh whose ATTRIBUTE_POSITION was inserted with a format other than Float32x3 (e.g. Vec<[f32; 2]> -> Float32x2, Float32x4, or a packed integer format). The indexed/topology asserts earlier in the function fire first, so reaching this panic also implies those checks passed.

Common situations: Hand-built meshes where positions come from 2D data (heightmaps, Vec2 outlines) or from a pipeline that stores positions as vec4 (w=1) for math convenience; converting a raw GPU buffer with an unexpected stride into a Bevy Mesh.

Related errors


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