ianmackenzie / elm-geometry

2D/3D geometry package for Elm
Mozilla Public License 2.0
183 stars 26 forks source link

Add self intersection for polygon and polyline #15

Open mpizenberg opened 7 years ago

mpizenberg commented 7 years ago

Computing self intersection for a polygon or a polyline is not obvious, even if an intersection function for line segments is provided. I recently had to do this so I wonder if that's something that would be interesting for OpenSolid. I have implemented a function with this interface:

selfIntersection : Polygon2d -> Maybe Point2d

It "stops" as soon as one intersection point is found. This was because I only wanted to know if the polygon was simple or not. I used (almost) the Shamos-Hoey algorithm which is a simplification of Bentley-Ottmann intersection algorithm stopping at first intersection.

Let me know if that's something that you'd like to add to OpenSolid.

ianmackenzie commented 7 years ago

It would certainly be a useful addition, although I'd like to take some time to understand the implementation a bit (especially stuff like numerical stability). Can you share your current code and/or send me a link to a description of the algorithm?

Signature-wise, I think

selfIntersection : Polygon2d -> Maybe Point2d

could be slightly misleading since it kind of implies the result is "the" self-intersection if it exists; perhaps

findSelfIntersection : Polygon2d -> Maybe Point2d

since I think there's a fair bit of precedent for 'find' functions returning only the first result found. Or, for maximum flexibility (find whatever you want, without paying for anything you don't care about):

hasSelfIntersection : Polygon2d -> Bool
findSelfIntersection : Polygon2d -> Maybe Point2d
allSelfIntersections : Polygon2d -> List Point2d
mpizenberg commented 7 years ago

Yes findSelfIntersection is way better ;)

The Bentley - Ottmann algorithm is also called the sweep line algorithm. You can find two interesting explanations at these references: http://www.webcitation.org/6ahkPQIsN http://jeffe.cs.illinois.edu/teaching/373/notes/x06-sweepline.pdf

My current implementation is accessible in file Polygon2d.elm. Please be aware of the following remarks:

To see a working use of the intersection algorithm, you can try running the examples/protoFeedback.elm file.

ianmackenzie commented 7 years ago

What are your thoughts on the various numerical issues described at https://en.wikipedia.org/wiki/Bentley%E2%80%93Ottmann_algorithm#Special_position_and_numerical_precision_issues? Polylines/polygons will certainly often have vertical segments and by definition will have segments with coincident endpoints. I think that findSelfIntersection would be a useful function, but I'd like its behaviour to be consistent with a brute-force 'iterate over every pair of segments and check that pair using LineSegment2d.intersection' (that is, you should never have a case where one of those methods finds an intersection point and the other one doesn't). Fortunately, fuzz testing with elm-test makes that sort of thing pretty easy to check =)

An alternative would be to build an R-tree of line segments and then query that R-tree with each individual line segment to look for possibly-intersecting other segments. This wouldn't be quite as fast as a specialized algorithm like Shamos-Hoey but should be reasonably efficient. I'm planning on building an OpenSolid R-tree package anyways at some point since it's a very useful data structure for a lot of cases.

mpizenberg commented 7 years ago

What are your thoughts on the various numerical issues described at https://en.wikipedia.org/wiki/Bentley%E2%80%93Ottmann_algorithm#Special_position_and_numerical_precision_issues? Polylines/polygons will certainly often have vertical segments and by definition will have segments with coincident endpoints.

The listed restrictions related to numerical issues that my happen are:

  1. No two line segment endpoints or crossings have the same x-coordinate
  2. No line segment endpoint lies upon another line segment
  3. No three line segments intersect at a single point

The findSelfIntersection function only cares about finding at least one intersection point. Therefore, it uses the Shamos–Hoey algorithm and so issue 3 is not an issue anymore.

Issue 1 is basically a problem when we have coincident endpoints. However, the implementation I chose ignores successive segments in the polygon by keeping their index in the polygon. So it will only detect "true" intersection, not successive segments intersections.

I don't exactly know what the issue with issue 2 is (except for pure numerical instability). However, I feel that it will not be an issue with this implementation for the following reasons:

I'd like its behaviour to be consistent with a brute-force 'iterate over every pair of segments and check that pair using LineSegment2d.intersection'

Segments are not modified by the algorithm, and internally, each intersection computation is done with that very function. So the only reason for not having the same result would be if some pair comparison is not done. In this algorithm, pair comparisons are done only between "adjacent" segments in the sweep line data structure. Therefore a difference between those two algorithms would happen only if all "brute" intersection points are never appear as adjacent segments in the data structure. However the construction of the algorithm "should" avoid such an issue. This may also deserve some fuzzy testing.

If you can prepare some tests (I might be biased by my implementation), I can implement this and make sure it has a valid behavior.

I'm planning on building an OpenSolid R-tree package anyways at some point since it's a very useful data structure for a lot of cases.

Sounds cool :)