bevyengine / bevyengine/bevy

compute_smooth_normals() returns zero normals for finely subdivided unit-scale meshes

Open
#25,095 0 comments 0 reactions 0 assignees View on GitHub
A-Math A-Rendering C-Bug S-Ready-For-Implementation
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

### Bevy version

0.19.0

### What happened?

`Mesh::compute_smooth_normals()` returns zero normals for a valid indexed mesh when the mesh is finely subdivided and has a radius of approximately 1.

The same mesh works correctly if it is uniformly scaled up (for example, radius = 100).

### Expected behavior

Smooth normals should be computed regardless of the mesh's world scale, assuming the geometry is valid.

### Reproduction

I generate an indexed icosphere by subdividing an icosahedron (100 cuts per edge, approximately 102k vertices and 204k triangles).

After creating the mesh:

```rust
mesh.compute_smooth_normals();
```

every normal is `[0.0, 0.0, 0.0]`.

The same mesh:

- has no degenerate triangles,
- has consistent outward winding,
- renders correctly with flat normals,
- renders correctly with manually computed smooth normals.

If I generate the exact same mesh with radius 100 instead of radius 1, `compute_smooth_normals()` works as expected.

### Investigation

Tracing through the implementation, the angle weights are guarded by:

```rust
ab.length_squared() * ac.length_squared() > f32::EPSILON
```

For my unit-scale mesh, every edge product falls below `f32::EPSILON`, so every angle weight becomes zero.

Diagnostics:

```
------------------------------------
From planet:
Vertices: 102012
Triangles: 204020
Triangle diagnostics:
Min area*2: 0.000070035036
Max area*2: 0.00018772687
Degenerate triangles: 0
Outward faces: 204020
Inward faces: 0
Bevy angle-weight diagnostics:
f32::EPSILON: 0.00000011920929
Min edge product: 0.0000000053171676
Max edge product: 0.00000005513081
Products <= EPSILON: 612060 / 612060 (100.00%)
Normal diagnostics:
NaN normals: 0
Zero-length normals: 102012
First normals:
0: [0.0, 0.0, 0.0]
1: [0.0, 0.0, 0.0]
2: [0.0, 0.0, 0.0]
3: [0.0, 0.0, 0.0]
4: [0.0, 0.0, 0.0]
5: [0.0, 0.0, 0.0]
6: [0.0, 0.0, 0.0]
7: [0.0, 0.0, 0.0]
8: [0.0, 0.0, 0.0]
9: [0.0, 0.0, 0.0]
From mesh:
Vertices: 102012
Indices: 612060
------------------------------------
```

As a result, no face contributes to any vertex normal and every accumulated normal remains zero.

Scaling the mesh to radius 100 increases the edge products enough that normal computation succeeds.

### Additional notes

I worked around the issue by computing vertex normals manually using accumulated face normals, which produces the expected result for both mesh scales.

Contributor guide

Open the contributing guide

Research direction

Start at Mesh::compute_smooth_normals and trace the angle-weight guard using the unit-scale subdivided mesh described in the reproduction. Verify the behavior with both radius-1 and radius-100 geometry, then ensure valid finely subdivided meshes produce nonzero smooth normals regardless of scale.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
computer-graphics
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.