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

VecSet may cause panics #1104

Closed
andriyDev opened this issue Oct 29, 2023 · 8 comments
Closed

VecSet may cause panics #1104

andriyDev opened this issue Oct 29, 2023 · 8 comments

Comments

@andriyDev
Copy link
Contributor

I tried writing my own implementation of Martinez-Rueda polygon clipping, and also used my own VecSet. This can be problematic since floating point issues can make the position in the VecSet somewhat not ordered (binary search can fail because floating point errors tell it to search in the wrong half).

The original algorithm from the paper cheats. Instead of using a VecSet, they use C++s std::set, which is a binary search tree with pointer stability. In other words, when you insert a value, it returns an iterator to the node in the binary search tree. This iterator is not invalidated unless the value is removed, so they just store this iterator with the edge. Once the edge has to be removed from the active set, they go into the edge, find the iterator, and remove that iterator. No lookup, so no way for floating point errors to make this fail.

This won't solve all the panics, but it could solve a good chunk of them.

@andriyDev
Copy link
Contributor Author

#976 may be an example of this.

@lnicola
Copy link
Member

lnicola commented Nov 4, 2023

That's an intriguing possibility. If I get this right, you're saying that you can insert x into VecSet, then search for it and not find it because it's not sorted properly?

I'm not familiar with Martinez-Rueda, can you share your sample implementation that exhibits the problem? My impression is that for non-NaN floats, equality and ordering tests should work. So if we recompute x somehow, that might cause problems, but if we use the same x, it should be the same as keeping track of an iterator.

@andriyDev
Copy link
Contributor Author

Yes, that's my hypothesis. Here's my implementation: https://github.com/andriyDev/polygon_clipping_rs and this branch being the most up-to-date (but broken) https://github.com/andriyDev/polygon_clipping_rs/tree/new-impl-2

The line that would regularly "trip" is https://github.com/andriyDev/polygon_clipping_rs/blob/new-impl-2/src/lib.rs#L984, which is the search for the "right" event, which seems to be the same error as in #976. I think the reason this happens is that computing the intersection of two lines can break ordering. For example, one of the ordering criteria in Martinez-Rueda is whether a line is vertical or not. A previously non-vertical line can be split into a non-vertical line and vertical line due to floating point errors if the intersection point is close enough to an endpoint.

I believe you are correct in that equality and ordering tests are fine for the same x, but since we insert computed xs from line intersections they can actually subtly break the ordering. I imagine these subtle breakages don't impact that algorithm significantly, since they problem come from "small" lines which will be resolved quickly, but they do break the ordering enough that removing the corresponding right point may fail. I am very not certain of this though, and I might be totally wrong :P

@andriyDev
Copy link
Contributor Author

To be clear, my version still uses a VecSet (of my own). I got the idea to use a node set through the original implementation here http://www4.ujaen.es/~fmartin/bool_op.html (or at least this looks like the original author).

@urschrei
Copy link
Member

urschrei commented Nov 4, 2023

I think the reason this happens is that computing the intersection of two lines can break ordering. For example, one of the ordering criteria in Martinez-Rueda is whether a line is vertical or not. A previously non-vertical line can be split into a non-vertical line and vertical line due to floating point errors if the intersection point is close enough to an endpoint.

This is awful / great job figuring out that this could be an issue

@andriyDev
Copy link
Contributor Author

andriyDev commented Dec 7, 2023

I created https://github.com/andriyDev/stable_node_set_rs just for this (just coming back to this after a break). Not sure of its performance implications exactly, but it could be a fine starting point for seeing if it works at all.

I did an initial attempt at using this in place of the VecSet, but it got stuck on an infinite loop somewhere I believe. I haven't dug in very deep though.

@frehberg
Copy link

frehberg commented Jan 3, 2024

anyone having an idea why the RegionAssembly is crashing?
Please see the regression test in #1126

mikemoraned added a commit to mikemoraned/spectrum that referenced this issue Aug 5, 2024
michaelkirk added a commit that referenced this issue Oct 28, 2024
We've had several reports of crashes in our BooleanOps implementation
over the years. Some of them have been addressed, but there remains a
long tail of crashes.

FIXES #913, #948, #976, #1053, #1064, #1103, #1104, #1127, #1174, #1189, 1193

The root of the issue (as I understand it) is that floating point
rounding errors break the invariants of our sweep line algorithm. After
a couple years, it seems like a "simple" resolution is not in sight.

In the meanwhile, the i_overlay crate appeared. It uses a strategy that
maps floating point geometries to a scaled fixed point grid, which
nicely avoids the kind of problems we were having.

Related changes included:

Included are some tests that cover all reports in the issue tracker of the existing BoolOps panic'ing

JTS supports Bops between all geometry types. We support a more limited
set:

1. Two 2-Dimensional geometries `boolean_op`.
2. A 1-Dimenstinoal geometry with a 2-D geometry, which we call `clip`.

So this maps JTS's Line x Poly intersection tests to our Clip method.

- rm unused sweep code now that old boolops is gone

  I marked a couple fields as `allow(unused)` because they are used for
  printing debug repr in tests.

Speed up benches by only benching local boolop impl by default
@michaelkirk
Copy link
Member

I just merged #1234 which replaces our BoolOps implementation with one backed by the i_overlay crate. This should resolve the issue you're seeing. Let us know if it recurs. You can use it now if you use the unreleased geo crate, otherwise we expect it to be part of the upcoming v0.29.0 release.

urschrei pushed a commit to urschrei/geo that referenced this issue Nov 6, 2024
We've had several reports of crashes in our BooleanOps implementation
over the years. Some of them have been addressed, but there remains a
long tail of crashes.

FIXES georust#913, georust#948, georust#976, georust#1053, georust#1064, georust#1103, georust#1104, georust#1127, georust#1174, georust#1189, 1193

The root of the issue (as I understand it) is that floating point
rounding errors break the invariants of our sweep line algorithm. After
a couple years, it seems like a "simple" resolution is not in sight.

In the meanwhile, the i_overlay crate appeared. It uses a strategy that
maps floating point geometries to a scaled fixed point grid, which
nicely avoids the kind of problems we were having.

Related changes included:

Included are some tests that cover all reports in the issue tracker of the existing BoolOps panic'ing

JTS supports Bops between all geometry types. We support a more limited
set:

1. Two 2-Dimensional geometries `boolean_op`.
2. A 1-Dimenstinoal geometry with a 2-D geometry, which we call `clip`.

So this maps JTS's Line x Poly intersection tests to our Clip method.

- rm unused sweep code now that old boolops is gone

  I marked a couple fields as `allow(unused)` because they are used for
  printing debug repr in tests.

Speed up benches by only benching local boolop impl by default
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

No branches or pull requests

5 participants