Sticky Coding – Android Developer Redefining Awesome

3Jul/100

Point in a (convex) polygon

So, I may as-well start this new (spam-free!) blog up with something which I implemented into a game I'm working on last night. I make no promises that it's optimized, there's probably quicker ways.

Thanks to evancharlton of #android-dev for the guidance, he requested I write this up, so it's for you!

As with all things I'll be putting in this section, it can easily be applied to other languages. But I'm writing in Java (Android-specific is my platform of choice, at the minute), and also because it's fairly easy to understand.

I already have my polygons vertices stored, and have written an algorithm to find their position when this polygon is translated/rotated. This was all set up when I wrote my separating axis theorem collision detection algorithm.

All I needed was an algorithm to check whether a polygon on screen was touched, that is, determine whether a specific point is inside the polygon. This, for some reason, I couldn't think up myself ...

A point is inside a (convex) polygon when the condition below is true for all edges.

All points (that is, your test point and all polygon vertices) are wholly on one side of an edge.

In a convex polygon, we can simplify things, because we know that for every edge - the vertices must all be on the same side of the edge anyway. So we need only check one vertex and the test point.

This meant coming up with a simple algorithm to test it, which is easy enough:

  1. We begin with a set of vertices, and a test point.
  2. Calculate the edge, and hence the normal axis.
  3. Calculate (vertexDot) the dot product of the axis, and a vertex which does not form part of the edge.
  4. Calculate (pointDot) the dot product of the axis, and the test point.
  5. If vertexDot and pointDot are on opposite sides to the edge, the point cannot be inside the polygon, return false.
  6. Repeat steps 2 to 5 for all edges.
  7. If still alive, the point must be inside the polygon, return true.

It isn't as difficult as it seemed before, so let's write some code :)

boolean isPointInPolygon(int vertexCount, Vector vertex[], Vector testPoint) {
 
	/* Loop over all edges, the number of edges == number of vertices */
	for(int i = 0; i < vertexCount; i++) {
 
		/* Start the edge at i, go to i + 1. Be sure to check that we are looping back to the start */
		int edgeStartIndex = i;
		int edgeEndIndex = i < vertexCount - 1 ? i + 1 : 0;
 
		/* We choose the next vertex in the polygon for testing, making sure we loop if necessary */
		int testIndex = edgeEndIndex < vertexCount - 1 ? edgeEndIndex + 1 : 0;
 
		/* Fetch our vertex objects, each contains an X and Y value */
		Vector edgeStartVertex = vertex[edgeStartIndex];
		Vector edgeEndVertex = vertex[edgeEndIndex];
		Vector testVertex = vertex[testIndex];
 
		/* Calculate the edge, simply (dx, dy) */
		float edgeX = edgeEndVertex.X - edgeStartVertex.X;
		float edgeY = edgeEndVertex.Y - edgeStartVertex.Y;
 
		/* Find the normal. For speed, we do not need to normalize this vector (note, normal does not mean normalised) Note that my vertices are ordered clockwise, you would have to flip the signs here if anticlockwise */
		float axisX = edgeY;
		float axisY = -edgeX;
 
		/* Note, calculating dot products is simple: 
			dot([x1, y1], [x2, y2]) = (x1 * x2) + (y1 * 2) */
 
		/* Because our normal axis does not necessarily have its origin at the edge, we must find the dot product of one of the edges vertices */
		float axisDot = dot(axisX, axisY, edgeStartVertex.X, edgeStartVertex.Y);
 
		/* Calculate the dot product of the third vertex */
		float vertexDot = dot(axisX, axisY, testVertex.X, testVertex.Y);
 
		/* Calculate the dot product of our test point */
		float pointDot = dot(axisX, axisY, testPoint.X, testPoint.Y)
 
		/* Determine whether our vertexDot and pointDot lay on opposing sides of axisDot, if so, exit now. 
			Note the use of >=, <=, > and <. These are used in such a way that the testVertex is 'allowed' to exist in line with the axis, but our testPoint is not. This way, if testPoint were to lay on the axis, it would be considered as being inside the polygon. */	   
		if( (nextDot >= axisDot && testDot < axisDot) || (nextDot <= axisDot && testDot > axisDot) ) {
			return false;
		}
 
	}
 
	/* If we have reached this far, the point must be inside the polygon, so let the world know! */
	return true;
 
}