http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm
The Winding Number
On the other hand, the winding number accurately determines if a point is inside a nonsimple closed polygon. It does this by computing how many times the polygon winds around the point. A point is outside only when the polygon doesn't wind around the point at all which is when the winding number wn= 0. More generally, one can define the winding number wn around a point for any closed continuous curve in the 2D plane. Let C(u)=(x(u),y(u)), for 0<=u<=1 with C(0)=C(1), be a continuous 2D curve, and let P be a point not on C(u). Then, define the vector CP(u)=C(u)-P from P to C(u), and its unit direction vector is W(u)=CP(u)/|CP(u)| which gives a continuous mapping from the curve C to the unit circle S1, and so can be represented in coordinates as W(u)=(cos q(u), sin q(u)) where q(u) is a positive counterclockwise angle. The winding number wn is then equal to the integer number of times W(u) wraps around S1. This corresponds to a homotopy class of S1, and can be computed by the integral:
When the curve C is a polygon with vertices V0,V1,...,Vn=V0, this integral reduces to the sum of the (signed) angles that each edge ViVi+1 subtends with the point P. So, if qi=angle(PVi,PVi+1), we have:
![]() | ![]() | ![]() |
This formula is clearly not very efficient since it uses an expensive arccos() trig function. But, a simple observation lets us replace this formula by a more efficient one. Pick any point Q on S1. Then, as the curve W(u) wraps around S1, it passes Q a certain number of times. If we count (+1) when it passes Q counterclockwise, and (-1) when it passes clockwise, then the accumulated sum is exactly the total number of times that W wraps around S1, and is equal to the winding number wn. Further, if we take an infinite ray R starting at P and extending in the direction of the vector Q, then intersections where R crosses the curve C correspond to the points where W passes Q. To do the math, we have to distinguish between positive and negative crossings where C crosses R from right-to-left or left-to-right. This can be determined by the sign of the dot product between a normal vector to C and the direction vector Q [Foley et al, 1996, p-965], and when the curve C is a polygon, one just needs to make this determination once for each edge. For a horizontal ray R from P, testing whether an edge's endpoints are above and below the ray suffices. If the edge crosses the positive ray from below to above, the crossing is positive (+1); but if it crosses from above to below, the crossing is negative (-1). One then simply adds all crossing values to get wn. For example:
Additionally, one can avoid computing the actual edge-ray intersection point by using the isLeft() attribute; however, it needs to be applied differently for ascending and descending edges. If an upward edge crosses the ray to the right of P, then P is on the left side of the edge since the triangle ViVi+1P is oriented counterclockwise. On the other hand, a downward edge crossing the positive ray would have P on the right side since the triangle ViVi+1P would then be oriented clockwise.
Pseudo-Code: Winding # Inclusion
This results in the following wn algorithm which is an adaptation of the cn algorithm and uses the same edge crossing rules as before to handle special cases.
typedef struct {int x, y;} Point; wn_PnPoly( Point P, Point V[], int n ) { int wn = 0; // the winding number counter // loop through all edges of the polygon for (each edge E[i]:V[i]V[i+1] of the polygon) { if (E[i] crosses upward ala Rule #1) { if (P is strictly left of E[i]) // Rule #4 ++wn; // a valid up intersect right of P.x } else if (E[i] crosses downward ala Rule #2) { if (P is strictly right of E[i]) // Rule #4 --wn; // a valid down intersect right of P.x } } return wn; // =0 <=> P is outside V[] } |
Clearly, this winding number algorithm has the same efficiency as the analogous crossing number algorithm. Thus, since it is more accurate in general, the winding number algorithm should be the preferred method to determine inclusion of a point in an arbitrary polygon.
The wn algorithm's efficiency can be improved further by rearranging the crossing comparison tests. This is shown in the detailed implementation of wn_PnPoly() given below. In that code, all edges that are totally above or totally below P get rejected after only two (2) inequality tests. However, currently popular implementations of the cn algorithm ([Franklin, 2000], [ Haines, 1994], [O'Rourke, 1998]) use at least three (3) inequality tests for each rejected edge. Since most of the edges in a large polygon get rejected in practical applications, there is about a 33% (or more) reduction in the number of comparisons done. In runtime tests using very large (1,000,000 edge) random polygons (with edge length < 1/10 the polygon diameter) and 1000 random test points (inside the polygon's bounding box), we measured a 20% increase in efficiency.