Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExtrudeGeometry: Fix triangulation #30698

Closed

Conversation

mlasala45
Copy link
Contributor

@mlasala45 mlasala45 commented Mar 9, 2025

Related issue: #20317

Description

Fixes triangulation errors for various shapes that were caused by the Earcut algorithm. Changes ExtrudeGeometry to use cdt2d (Constrained Delaunay Triangulation) instead, which is added as a bundled file. Geometry data is now cleaned prior to triangulation, welding adjacent vertices that are within a certain distance threshold (currently 0.0002; this should probably be a config option).

Triangulation is now consistently accurate. It can still experience errors for geometries with holes, but only if the hole is wound backwards, which is easily fixable.

Also improved documentation/commenting for ExtrudeGeometry and ShapeUtil.

The E2E test webgl_custom_attributes_lines-diff runs correctly on my local machine but not on remote; I suspect its timing or its colors are not sufficiently deterministic.

@mlasala45 mlasala45 marked this pull request as draft March 9, 2025 19:36
@mlasala45 mlasala45 force-pushed the feature/20317-neg-bevel-offset branch 2 times, most recently from 19cad39 to 5753c10 Compare March 9, 2025 20:42
Copy link

github-actions bot commented Mar 9, 2025

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 336.02
78.26
336.02
78.26
+0 B
+0 B
WebGPU 524.99
146.23
524.99
146.23
+0 B
+0 B
WebGPU Nodes 524.46
146.12
524.46
146.12
+0 B
+0 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 465.06
112.14
465.06
112.14
+0 B
+0 B
WebGPU 597.77
162.49
597.77
162.49
+0 B
+0 B
WebGPU Nodes 552.89
151.97
552.89
151.97
+0 B
+0 B

@mlasala45 mlasala45 force-pushed the feature/20317-neg-bevel-offset branch from 5753c10 to 116494a Compare March 9, 2025 21:33
@mlasala45 mlasala45 marked this pull request as ready for review March 9, 2025 22:04
@gkjohnson
Copy link
Collaborator

gkjohnson commented Mar 10, 2025

Thanks for the contribution! Nice to see there may be a good CDT algorithm available in Javascript. A few questions:

Fixes triangulation errors for various shapes that were caused by the Earcut algorithm. Changes ExtrudeGeometry to use cdt2d (Constrained Delaunay Triangulation) instead.

Can you provide a before and after example demonstrating the issues that this fixes? And why?

From the screenshot changes it looks like CDT will provide a significantly more dense triangulation (or perhaps the triangles are just concentrated in different places). Do you have a rough idea of how much this increases the complexity of the resulting geometry?

Bundle size after tree-shaking...

+26.22 kB
+6.61 kB

If I'm reading this right it looks like the cdt2d package isn't being treeshaken - likely because the cdt2d file can't be determined to be side-effect-free. I'm wondering if there's a way to auto-convert the file to a more well-formed es6 module style file to clean this up using something like Rollup.

Also for anyone else the repo for cdt2d is available here.

@mlasala45
Copy link
Contributor Author

From the screenshot changes it looks like CDT will provide a significantly more dense triangulation (or perhaps the triangles are just concentrated in different places).

Ah, that makes sense that the FX in that example uses the mesh triangles. That explains the example failure, though not why it doesn't work on remote.

webgl_custom_attributes_lines-diff
For reference, this is the diff for that example. The lines are the same, but something about the shading ends up different.

Do you have a rough idea of how much this increases the complexity of the resulting geometry?

I haven't compared it between the two methods in detail. The resultant triangulation is more accurate to the original contour, whereas Earcut literally cuts corners, so I'd expect there to be a higher triangle count. I can look into it for solid numbers.

Can you provide a before and after example demonstrating the issues that this fixes? And why?

Sure, I'll put something together. The inciting incident is #20317, which shows that certain contours will produce large noticeable triangulation artifacts, such as triangles crossing what should be empty space or truncating corners. From my investigation, this seemed to be independent of bevelOffset, though certain values for the offset would trigger such a result due to the changes made to the contour.

If I'm reading this right it looks like the cdt2d package isn't being treeshaken

I agree. I've got a fork open to generate the bundle, it should be simple to experiment with other bundling options. I'm not sure if there's a good way to test the results short of pushing to the PR and letting it run its CI. I'll fiddle.

@gkjohnson
Copy link
Collaborator

For reference, this is the diff for that example. The lines are the same, but something about the shading ends up different.

There may be differences in blending, antialiasing, etc between your local and host machines. We can deal with this once the rest of the implementation and questions are settled.

whereas Earcut literally cuts corners, so I'd expect there to be a higher triangle count. I can look into it for solid numbers.

This is not an intrinsic quality of the earcut algorithm. A completely robust version of earcut should not have the artifacts shown and should follow the contour exactly but it can be difficult track down all the corner cases. There are number of filed issues in the original earcut repo that three.js' is based on (see mapbox/earcut#174, mapbox/earcut#163, mapbox/earcut#162, and others). I'm not completely convinced that using something like this cdt library won't just move these errors and corner cases somewhere else but if it's working in a number of examples and fixing known issues it should be considered. We should still understand the practical impact on triangulation complexity and performance for projects already using the ExtrudeGeometry class.

I'm not sure if there's a good way to test the results short of pushing to the PR and letting it run its CI

Perhaps @Mugen87 or @marcofugaro knows how file size and treeshaking impact can be tested locally.

We should also hear Mugen's or @mrdoob's thoughts on including a bundled library in core before spending too much time on the treeshaking bit - external library inclusion is typically relegated to the "examples" folder. Though the earcut algorithm was ported from mapbox to be included in the core project.

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 10, 2025

In general, I do not vote to have two triangulation libs in the core. We should either use Earcut or this one.

TBH, I prefer a non-bundled code for clarity reasons. The current Earcut implementation is something that we can actually enhance if necessary which is not true for the bundle file of this PR.

Besides, would upgrading Earcut to the latest version resolve some of the issue mentioned in this PR?

I do not vote to go for "best triangulation quality" if the required lib adds a lot of code to the engine. We should have a good compromise between size, speed and quality and I'm not convinced yet that cdt2d is a good pick here.

@gkjohnson
Copy link
Collaborator

gkjohnson commented Mar 14, 2025

I've done some benchmarking and unfortunately the CDT version of ExtrudeGeometry seems to more than 4x slower than the earcut version, probably from some of the other features CDT affords over earcut. The triangle count is about the same, though. While I prefer the triangulation and robust of the CDT implementation the performance loss is not ideal.

TBH, I prefer a non-bundled code for clarity reasons. The current Earcut implementation is something that we can actually enhance if necessary which is not true for the bundle file of this PR.

We should not be modifying library code, though - there's no reason to do that. Any fixes should be made upstream. The fact that we've copied Mapbox's earcut code and modified function names makes it more difficult to maintain. There have been updates to the earcut project (unclear whether they'll fix the issue in question here) but if the file were imported as intended then we could just update a version and benefit from any fixes.

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 14, 2025

@mlasala45 It should be possible to monkey-patch Earcut.triangulate() with cdt2d so even if this PR is closed you can still integrate a custom solution on app level.

We should not be modifying library code, though - there's no reason to do that.

three.js has no dependencies and at the time earcut was integrated there was no willingness to change that. The policy at that time was three.js should be self-contained as much as possible.

@mlasala45
Copy link
Contributor Author

mlasala45 commented Mar 15, 2025

I do not vote to go for "best triangulation quality" if the required lib adds a lot of code to the engine. We should have a good compromise between size, speed and quality and I'm not convinced yet that cdt2d is a good pick here.

Personally, as a 3D app developer, I'd seriously value the option to choose higher quality at the expense of performance. Rather than replacing the current version, maybe there's a way to make that option available without compromising engine size; eg. an optional module that has the three.js rubberstamp.

It should be possible to monkey-patch Earcut.triangulate() with cdt2d so even if this PR is closed you can still integrate a custom solution on app level.

Good point. Definitely doable, though ideally there'd be an officially supported option.

This is not an intrinsic quality of the earcut algorithm. A completely robust version of earcut should not have the artifacts shown and should follow the contour exactly but it can be difficult track down all the corner cases.

If #30737 goes through, I'll see if the newer earcut version fixes the triangulation problem.

Also reverts test screenshot changes
@mlasala45
Copy link
Contributor Author

Upon investigation, I realized that the changes I made to clean up the geometry data before extrusion actually fix the problem on their own, still using earcut. I did those after adding cdt2d, so I guess it masked that fact.

The current version of the PR fixes the triangulation issues, and does not add or modify triangulation libraries.

Original version (prominent triangulation errors):
corner-artifacts

With fix (bevelOffset is negative, no errors):
with-fix

Triangulation still breaks if you use physically invalid parameters (bevelOffset negative and larger than the width of the contour shape):
invalid-offsets

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 16, 2025

Upon investigation, I realized that the changes I made to clean up the geometry data before extrusion actually fix the problem on their own, still using earcut.

That sounds good! In this case, I think we can close the PR and continue the usage of earcut. #30737 improved the integration so it's easier for us to update the library in the future. If you encounter triangulation issues, it's indeed better to follow the suggestions in #30698 (comment) and report the issue at the earcut repository. When fixed there, we can simply upgrade three's dependency.

@Mugen87 Mugen87 closed this Mar 16, 2025
@Mugen87 Mugen87 added this to the r175 milestone Mar 16, 2025
@mlasala45
Copy link
Contributor Author

@Mugen87 I think you misunderstand. The modified version of this PR still fixes triangulation issues present in the library. It's just that those issues were caused by uncleaned input geometry data, not by a faulty earcut implementation. The PR as it stands improves readability and documentation of the affected files, and adds data cleaning (specifically point welding) to the ExtrudeGeometry process, which fixes the issue as shown above.

@Mugen87 Mugen87 reopened this Mar 16, 2025
@gkjohnson
Copy link
Collaborator

The PR as it stands improves readability and documentation of the affected files, and adds data cleaning (specifically point welding) to the ExtrudeGeometry process, which fixes the issue as shown above.

I personally would prefer to see the fix made in a separate PR from so many cleanup changes so it's more clear what the meaningful changes are.

@mlasala45
Copy link
Contributor Author

I personally would prefer to see the fix made in a separate PR from so many cleanup changes so it's more clear what the meaningful changes are.

@gkjohnson New PR at #30750

@gkjohnson
Copy link
Collaborator

@gkjohnson New PR at #30750

Sorry, to be clear I would like to see the "welding fixes" only without any variable renaming and extraneous comment changes. Please only include the changes necessary to fix the triangulation.

Also improved variable names and commenting in the affected files.

These extra changes make the PR more difficult to follow and understand.

@mlasala45
Copy link
Contributor Author

mlasala45 commented Mar 18, 2025 via email

@gkjohnson
Copy link
Collaborator

I don't have a strong preference on whether it's a new PR or in this one.

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 18, 2025

Closing in favor of #30750.

@Mugen87 Mugen87 closed this Mar 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants