Triangles are pretty cool. Our goal is to take dumb polygons and make them cool by cutting them into triangles.
In particular, given some lattice polygon
The motivation for the algorithm comes from the idea that between three adjacent vertices, we can connect the unconnected vertices to form a new triangle, so long as
So we can reduce our problem to the following pseudocode:
# polygon is a list of vertices ordered so that element i is adjacent to element i+1
def triangulate(polygon):
if (polygon.length < 4):
return polygon
for each adjacent v1, v2, v3 in polygon:
l := line connecting v1 and v3;
i := interior of triangle with vertices v1, v2, v3
if (l is in interior && i does not contain any vertices of polygon):
draw l;
polygon.remove(v2);
return triangulate(polygon) // recursive call on original
// polygon minus the v2 that was cut out
How can we check whether the new edge of three points forms a free triangle? We can use orientation! From now on
we'll consider our input 'polygon' as really a list of vertices ordered counterclockwise such that element
We can calculate the orientation of three points
So recall that we need to solve two problems:
As stated before, the interior of the new triangle is in the interior of the whole polygon if and only if the points, when ordered properly, are counterclockwise oriented. This is best communicated by illustration:
So we want to make sure the new line we create doesn't intersect existing edges. For example in the polygon above, the triangle 1-2-3 is oriented counterclockwise, but if we drew the line 1-3, we would intersect another edge. So we should try to avoid that. But we can also just exploit the fact that we're working only with polygons, and notice that we would cross an edge if and only if some other point exists in the interior of the new triangle (in the example, 12 is that point). This is true because the polygons we are working with only have straight, non-intersecting edges.
There are many ways to do this, but we've already built up on a framework of orientation, so let's just use
that! Take a triangle formed by
// Input: poly_points, a list of vertices of the polygon ordered
// in ccw rotation starting from an arbitrary point
function triangulate(poly_points) {
var p1, p2, p3;
if (poly_points.length < 4) {return} // base case: triangle
for (var i = 0; i < poly_points.length; i++) {
// for each set of 3 adjacent vertices
p1 = poly_points[i]
p2 = poly_points[(i + 1) % poly_points.length]
p3 = poly_points[(i + 2) % poly_points.length]
if (ccw(p1, p2, p3) > 0 && clearShot(p1, p2, p3)) {
// draw new line between p1 and p3
line(p1[0], p1[1], p3[0], p3[1])
// kill p2 and bury the body
poly_points.splice(i + 1, 1)
// recursively call on polygon with p2 removed
return triangulate(poly_points)
}
}
}
/////////////// Helper Functions ///////////////
// Given 3 points, returns whether those points are
// oriented ccw (+), collinear (0), or cw(-)
function ccw(p1, p2, p3) {
return (p2[0] - p1[0]) * (p3[1] - p1[1]) -
(p2[1] - p1[1]) * (p3[0] - p1[0])
}
// Given 3 numbers, returns whether they have the
// same sign
function sameSign(a, b, c) {
return (a * b > 0 && b * c > 0)
}
// Given a point and a triangle, returns whether the point
// is in the interior of the triangle
function inTriangle(s, p1, p2, p3) {
return sameSign(ccw(p1, s, p2), ccw(p2, s, p3), ccw(p3, s, p1))
}
// Checks whether the line between p1 and p3 can be drawn
// by checking if any points exist in the interior of
// the triangle formed by p1 p2 and p3
function clearShot(p1, p2, p3) {
for (var j = 0; j < points.length; j++) {
s = points[j]
if (inTriangle(s, p1, p2, p3)) {
return false
}
}
return true
}