-
Notifications
You must be signed in to change notification settings - Fork 210
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
Touching inner rings #177
Touching inner rings #177
Conversation
This fixes cases where holes meet at a left-most point, but there still appear to be issues for valid polygons that meet at a right-most point:
It appears in these cases that Any ideas what might be needed to fix those cases? |
import {earcut, flatten} from '../src/earcut.js'; | ||
import earcut, {flatten} from '../src/earcut.js'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to make this tweak to the benchmarks to get them to run
main:
typical OSM building (15 vertices): x 2,751,261 ops/sec ±0.50% (96 runs sampled)
dude shape (94 vertices): x 76,587 ops/sec ±0.42% (100 runs sampled)
dude shape with holes (104 vertices): x 61,820 ops/sec ±0.32% (100 runs sampled)
complex OSM water (2523 vertices): x 1,024 ops/sec ±0.49% (97 runs sampled)
this branch:
typical OSM building (15 vertices): x 2,644,854 ops/sec ±0.46% (92 runs sampled)
dude shape (94 vertices): x 76,872 ops/sec ±0.31% (100 runs sampled)
dude shape with holes (104 vertices): x 61,708 ops/sec ±0.28% (100 runs sampled)
complex OSM water (2523 vertices): x 1,040 ops/sec ±0.22% (98 runs sampled)
assert.ok(err <= expectedDeviation, `deviation ${err} <= ${expectedDeviation}`); | ||
} | ||
}); | ||
for (const rotation of [0, 90, 180, 270]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this while debugging to test some adjacent cases, I can roll it it back if you don't want it.
@@ -2,125 +2,133 @@ | |||
|
|||
import earcut, {flatten, deviation} from '../src/earcut.js'; | |||
|
|||
const testPoints = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added while debugging to make it easy to switch between test fixtures, for example http://localhost:8000/viz/?fixture=touching-holes5&rotation=180, but can roll it back if you don't want it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great contribution, really happy to see improvements to make Earcut more resilient! I know how hard it is to debug issue like this (sometimes I'd just give up).
I'm wondering about that regression for the simple use case — I guess this is because of the additional equals
check? Since it is added almost everywhere where pointInTriangle
is called, should the check possibly be inside that function instead, which could theoretically help with the regression and slightly simplify the change?
Rotations for test variations are a great idea, and easier test loading for viz looks good to me too.
Thanks! By "that regression for the simple use case" do you mean the "typical OSM building (15 vertices)" benchmark? Or is it behavior on one of the test cases? |
Yep, that one, assuming it's not a fluke result. |
OK got it. I pushed the check inside pointInTriangle but it broke the tests because of the other usages of that function, so I split it into a second function that excludes the first point. Now they are closer... this branch:
and main right afterwards:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Just for the sake of experiment, what happens if you call pointInTriangle
inside pointInTriangleExceptFirst
? In theory V8 inlines things like that under the hood so I'm curious if it will affect benchmarks. Looks great otherwise.
Ah good call, performance is the same calling Also this isn't related to my change at all but I noticed there might be some improvement to be had by changing the bbox calculations to use this branch:
main:
|
@msbarry excellent! There were times when |
Great! Thanks for the help on this! For reference those benchmarks were all node 21 on a 2021 M1 Macbook Pro. |
@msbarry Merry Christmas! Looks like this change produces some weird triangulation failures in GL JS tests (seeing results from mapbox/mapbox-gl-js#13371, e.g. here https://output.circle-artifacts.com/output/job/d76e512a-1057-4c34-9a84-c32415369555/artifacts/0/test/integration/render-tests/index.html) — will need investigation. If it's not an easy thing to fix, we'll probably have to hold off on upgrading and potentially revert... |
Are you able to reproduce those failures as test cases in earcut? If so I can take a look in a few days. |
I've been noticing incorrect triangulations from earcut on real world data recently, even when the input polygons are simple and valid. It seems to happen when polygons contain inner rings that touch at a single vertex. Here is a screenshot of the issue in the wild:
I reduced the left one to this minimal reproduction case:
Since
eliminateHoles
sorts all holes by their leftmost point, when multiple holes meet at that point if they are processed in the wrong order it makes the border of the new outer polygon shape criss-cross so triangulation can't do the right thing.This change makes
eliminateHoles
process holes that meet at the same point clockwise so that each ring can be inserted after the end of the one above it.It fixes these cases: