compute_smooth_normals() returns zero normals for finely subdivided unit-scale meshes
- 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
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