tags:

views:

162

answers:

2

I have an algorithm that can find if a point is inside a polygon.

 int CGlEngineFunctions::PointInPoly(int npts, float *xp, float *yp, float x, float y)
 {
     int i, j, c = 0;
     for (i = 0, j = npts-1; i < npts; j = i++) {
         if ((((yp[i] <= y) && (y < yp[j])) ||
             ((yp[j] <= y) && (y < yp[i]))) &&
             (x < (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i]))
             c = !c;
     }
     return c;
 }

My only issue with it is it assumes an odd winding rule. What I mean by this is that if the polygon is self intersecting, certain parts that it would considered to be 'empty' will return as false. What I'd need in even if it self intersects, anything inside the polygon will return true.

Thanks

+5  A: 

Beware: this answer is wrong. I have no time to fix it right now, but see the comments.

This casts a ray from the point to infinity, and checks for intersections with each of the polygon's edges. Each time an intersection is found, the flag c is toggled:

c = !c;

So an even number of intersections means an even number of toggles, so c will be 0 at the end. An odd number of intersections means an odd number of toggles, so c will be 1.

What you want instead is to set the c flag if any intersection occurs:

c = 1;

And for good measure, you can then eliminate c entirely, and terminate early:

 int CGlEngineFunctions::PointInPoly(int npts, float *xp, float *yp, float x, float y)
 {
     int i, j;
     for (i = 0, j = npts-1; i < npts; j = i++) {
         if ((((yp[i] <= y) && (y < yp[j])) ||
             ((yp[j] <= y) && (y < yp[i]))) &&
             (x < (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i]))
             return 1;
     }
     return 0;
 }
Thomas
Awesome thanks!
Milo
And fix the design by returning `true`/`false` and change the return type to `bool`. Also, `npts` (and therefore `i` and `j`) should be unsigned, since a signed value doesn't make sense. Change it to `size_t`. Lastly, a more C++-type interface would be a template function that takes iterators to the first and last of the points (or even better, just a collection of points wrapped in a class). But I suppose that's a larger re-design.
GMan
Thanks for the tips GMan, I have a more templatized and C++ version of it, I just wanted to show this to make it easier to understand
Milo
Oh, from the style of coding, I just assumed that this was C... I didn't look to hard at the first line, I guess.
Thomas
@Jex, I believe this algorithm fails if the point is to the left of the entire polygon.
Mark Ransom
@Mark: Better is to add/subtract 1, depending whether the line between (x,y) and infinity crosses the edge from left to right (looking from start to end) or right to left. Then the Odd rule is calculated by (c % 2), while the Zero Winding rule is (c != 0). (Almost the) same calculation for both rules!
Sjoerd
Sjoerd
@Mark Ransom: Dang, you're right! I'll add a note to the answer, pointing to Sjoerd's excellent suggestion.
Thomas
+1  A: 

To translate your original algorithm to english: You're determining if the number of polygon segments to the right of your point are even or odd. If it's even (including zero) your point is outside, if it's odd your point is inside. This means if there are two segments to the right and also two segments to the left, the point is not considered inside the polygon.

What you need to do is change the algorithm so that it checks for segments on both sides; if there's a segment on both sides of the point, then the point is within the polygon.

 int CGlEngineFunctions::PointInPoly(int npts, float *xp, float *yp, float x, float y) 
 { 
     int i, j;
     bool hasSegmentLeft = false;
     bool hasSegmentRight = false;
     for (i = 0, j = npts-1; i < npts; j = i++) { 
         if ((((yp[i] <= y) && (y < yp[j])) || 
             ((yp[j] <= y) && (y < yp[i]))))
         {
             if (x < (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i])
             {
                 hasSegmentRight = true;
                 if (hasSegmentLeft)  // short circuit early return
                     return true;
             }
             else
             {
                 hasSegmentLeft = true;
                 if (hasSegmentRight)  // short circuit early return
                     return true;
             }
     } 
     return hasSegmentLeft && hasSegmentRight; 
 }

P.S. that for statement construct is a very clever way of dealing with a circular list that wraps around to the beginning; I'd never seen it before.

Mark Ransom
What about a U-shaped polygon where the point is in the bowl of the U (outside the polygon, but inside its convex hull)? You'll find intersections on both sides, but the point is not inside the polygon.
Thomas