Zylann / godot_voxel

Voxel module for Godot Engine
MIT License
2.59k stars 244 forks source link

Tiny hole in the mesh of VoxelLodTerrain #641

Open Piratux opened 3 months ago

Piratux commented 3 months ago

Describe the bug As title says, I found a case where the meshed SDF of VoxelLodTerrain contains tiny hole.

To Reproduce Download project and run it. Upon launch you should see this which contains triangle shaped hole:

image

Expected behavior There should be no holes in the mesh.

Environment

Test Mesh issue.zip

Zylann commented 3 months ago

It appears to be caused by this: https://github.com/Zylann/godot_voxel/blob/4187c63df62496e7e1a1d08fe05c516b36a49050/meshers/transvoxel/transvoxel.cpp#L712-L749

The mesher eliminates triangles that are too small or degenerate, because that makes Jolt Physics spam errors. Maybe there can be a way to turn this off, but then if you use Jolt you'll end up with the problem again.

image

Piratux commented 3 months ago

I see. This is not very noticeable so it's not a big problem in my case. Just wanted to let you know.

Feel free to close the issue.

Zylann commented 3 months ago

Another option would be to snap/clamp edge interpolation factors so that vertices cannot be generated too close to corners of marching cube cells, which then tends to produce less thin/small triangles, at the cost of slightly biasing the resulting mesh. This post mentions a similar idea: https://stackoverflow.com/questions/35112181/marching-cubes-very-small-triangles (unfortunately without any code example so it's unclear what they mean by "snapping iso values")

Piratux commented 3 months ago

From the description and provided images in the post of your link, it sounds like if mesh vertices are very close to integer coordinates (which are corners of the cubes), then vertices get set to those integer coordinates.

Something like this I'd assume:

float epsilon = 0.05;
float rounded_pos_x = round(pos.x);
float rounded_pos_y = round(pos.y);
float rounded_pos_z = round(pos.z);
float diff_x = abs(pos.x - rounded_pos_x);
float diff_y = abs(pos.y - rounded_pos_y);
float diff_z = abs(pos.z - rounded_pos_z);
if (diff_x * diff_x + diff_y * diff_y + diff_z * diff_z < epsilon * epsilon){
    pos.x = rounded_pos_x;
    pos.y = rounded_pos_y;
    pos.z = rounded_pos_z;
}
Zylann commented 3 months ago

No, you're only describing the result of it. Vertices are generated on edges of marching cube cells, and that depends on an interpolation factor between pairs of corners. That factor can be clamped/snapped, but the SDF values can also be clamped/snapped. I'm not sure which one they are referring to. One downside of doing it on the SDF is that it requires to know how gradients progress, which in turn depends on them being good gradients. On the other hand, the interpolation factor has no unit and is normalized, so is easier to clamp (however it is known after case selection so triangle count won't change)

Piratux commented 3 months ago

Considering the author of post there mentioned percentages, I can only imagine this working with the interpolation factor since it can be a fixed range of [0, 1], where 0 is starting point of the edge, and 1 is ending point.

If SDF can be "infinitely large", then I don't see how percentages can be used to "snap" the SDF.

Zylann commented 3 months ago

I removed the code that was removing small triangles, replaced it with a margin property that clamps edge interpolation.

Piratux commented 3 months ago

Nice, I've tested it and it works.

Just small suggestion: If you add silent clamping for the value (in this case edge_clamp_margin) please mention it in the docs.

image

image