On the Triangulation of Simple Polygons by Ear Clipping

This document explains the ear-clipping triangulation algorithm in full detail, with particular attention to the mathematical foundations of each geometric test and the manner in which the algorithm is guaranteed to terminate.

1. The Problem

Given a simple polygon with n vertices — no holes, no self-intersections — partition its interior into non-overlapping triangles that cover it exactly. The result always contains exactly n − 2 triangles. Ear-clipping achieves this by repeatedly identifying and removing a single vertex whose local triangle is entirely interior to the polygon, until only three vertices remain.

2. One Tool: The 2D Cross Product

Every geometric decision in the algorithm reduces to a single scalar. Begin with the 3D cross product. Embed two 2D vectors in 3D by setting z = 0:

u = (u.x, u.y, 0)
v = (v.x, v.y, 0)
         | i    j    k |
u x v =  | u.x  u.y  0 |
         | v.x  v.y  0 |
= i(u.y*0 - 0*v.y) - j(u.x*0 - 0*v.x) + k(u.x*v.y - u.y*v.x)
= (0,  0,  u.x*v.y - u.y*v.x)

The x and y components vanish. Only the z-component survives:

cross(u, v) = u.x * v.y - u.y * v.x

The cross product magnitude is

    |u x v| = |u| |v| sin(θ)
where θ is the angle from u to v measured counter-clockwise. sin(θ) is what carries the sign:
    θ ∈ (0, 180)    -->   sin(θ) > 0   -->   cross > 0   -->   left turn
    θ ∈ (180, 360)  -->   sin(θ) < 0   -->   cross < 0   -->   right turn
So the sign of the cross product is the sign of sin(θ), which is positive for angles in the upper half and negative for angles in the lower half. A left turn means v is in the upper half-plane relative to u, a right turn means it is in the lower half.

parallelogram

The blue arrow is u, the red arrow is v. The shaded area equals |cross(u,v)|. The box on the right sketches the formula: the blue bar represents u.x*v.y, the red bar represents u.y*v.x, their difference is the signed area.

For three points A, B, C, form vectors from B:

cross(A, B, C) = (A.x - B.x)(C.y - B.y) - (A.y - B.y)(C.x - B.x)
SignMeaning
> 0A → B → C is a counter-clockwise (left) turn
< 0A → B → C is a clockwise (right) turn
= 0A, B, C are collinear

cross product sign

Left: the turn from u (black) to v (blue) is CCW, cross > 0, marked with +. Right: the turn to v (red) is CW, cross < 0, marked with −. The shaded wedge shows the angular region between the vectors.

3. Winding Order

Before any cross product sign can be interpreted, the winding order of the polygon vertices must be established. The Shoelace formula gives the signed area:

2A = ∑( x_i * y_{i+1} - x_{i+1} * y_i ) for i = 0..n-1, indices mod n

If 2A > 0, the vertices are in counter-clockwise order.

If 2A < 0, the vertices are in clockwise order.

Every cross-product sign test in the remainder of the algorithm depends on this convention being consistent. If the polygon arrives in clockwise order, reverse the vertex list before proceeding.

4. Convex and Reflex Vertices

For three consecutive polygon vertices V_{i-1}, V_i, V_{i+1}, form edge vectors from the middle vertex:

u = V_{i-1} - V_i
v = V_{i+1} - V_i
D = cross(u, v)

For a CCW polygon the interior is always to the left of each directed edge. At a convex vertex the path bends leftward, agreeing with the winding, so D > 0 and the interior angle is less than 180 degrees. At a reflex vertex the path bends rightward against the winding, so D < 0 and the interior angle exceeds 180 degrees. The cross product is the signed measure of that bending.

convex and reflex

Left: a convex vertex (blue dot). The cross product is positive; the green arrow points with the CCW winding; interior angle < 180. Right: a reflex vertex (red dot). The cross product is negative; the red arrow points against the winding; interior angle > 180.

A reflex vertex cannot be an ear tip. The triangle formed at a reflex vertex would extend outside the polygon. Only convex vertices are candidates.

5. The Point-in-Triangle Test

To validate a candidate ear, no other polygon vertex may fall inside the proposed triangle. The test follows from the same left-of-edge logic. A CCW triangle (A, B, C) has its interior on the left of every directed edge. A point P is inside if and only if it is to the left of A→B, left of B→C, and left of C→A simultaneously:

d1 = cross(A, B, P) = (B - A) x (P - A)
d2 = cross(B, C, P) = (C - B) x (P - B)
d3 = cross(C, A, P) = (A - C) x (P - C)
P is strictly inside iff d1, d2, d3 all share the same sign

If even one value flips sign, P lies outside that edge and therefore outside the triangle.

point in triangle

Green dot (P): inside the triangle, all three d values positive. Red dot (Q): outside, it fails the test for at least one edge. Blue arrows show the inward normals of each edge.

6. What Makes an Ear

A vertex V_i is an ear tip when both of the following conditions hold:

1. V_i is convex (cross product at V_i is positive, Section 4).

2. The triangle (V_{i-1}, V_i, V_{i+1}) contains no other polygon vertex in its interior (Section 5).

Condition 1 ensures the triangle lies on the correct side of the boundary. Condition 2 ensures it does not intrude on a concave indent. A reflex vertex inside the candidate triangle would mean the triangle cuts across a region not belonging to it.

reflex blocks ear

Orange shading: a candidate ear blocked because a reflex vertex (red X) falls inside it. Green shading: a valid ear on the bottom-left corner, no other vertex inside. Blue dots are convex vertices; the orange and green dots mark the respective ear tips.

7. The Two-Ears Theorem

The algorithm terminates because of a result due to Gary Meisters (1975): every simple polygon with four or more vertices has at least two non-overlapping ears. This guarantees that the search will always find an ear. Each successful clip removes one vertex. Starting from n vertices and ending at 3, the loop runs n − 3 times, yielding n − 3 clipped triangles plus the one final triangle, for a total of n − 2 triangles.

8. The Complete Procedure

Initialize:

Compute winding order via Shoelace formula.
If CW, reverse the vertex list.

While n > 3:

    For i = 0 to n-1:

        prev = V[(i-1) mod n]
        curr = V[i]
        next = V[(i+1) mod n]

        If cross(prev, curr, next) <= 0: continue

        blocked = false
        For each V[j] not in {prev, curr, next}:
            If point_in_triangle(V[j], prev, curr, next):
                blocked = true; break

        If blocked: continue

        Emit triangle(prev, curr, next)
        Remove curr from polygon
        n = n - 1
        Break and restart scan

Emit triangle(V[0], V[1], V[2])

Output: n-2 triangles. Time complexity: O(n^2).

9. Step-by-Step on a Real Polygon

The image below shows four frames. Each frame is the polygon after the previous ear has been removed. The highlighted triangle is the ear being clipped in that step and the coloured dot marks the ear tip.

step by step ear clipping

Frame 1: original 6-vertex L-shape, red ear at V1. Frame 2: after removing V1, green ear at the new V1. Frame 3: after that removal, blue ear. Frame 4: final triangle, yellow.

10. C Implementation with canvas.h

The following triangulates a polygon and renders each clipped triangle into a pixel buffer using canvas.h. Compile with cc sketch.c -o sketch -lm.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define CANVAS_NO_IMAGE_RENDERING
#define CANVAS_IMPLEMENTATION
#include "canvas.h"

static float cross2(float ax, float ay,
                    float bx, float by,
                    float cx, float cy)
{
    return (ax-bx)*(cy-by) - (ay-by)*(cx-bx);
}

static int in_tri(float px, float py,
                  float ax, float ay,
                  float bx, float by,
                  float cx, float cy)
{
    float d1 = cross2(ax,ay, bx,by, px,py);
    float d2 = cross2(bx,by, cx,cy, px,py);
    float d3 = cross2(cx,cy, ax,ay, px,py);
    int neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
    int pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
    return !(neg && pos);
}

void triangulate(canvas_t c, float *vx, float *vy, int n)
{
    int *idx = malloc(n * sizeof(int));
    for (int i = 0; i < n; i++) idx[i] = i;
    int rem = n, ci = 0;

    /* colors: 0xAARRGGBB */
    uint32_t colors[] = {
        0xFFCC2222, 0xFF22AA44, 0xFF2255CC,
        0xFFEE8800, 0xFFAA44AA, 0xFF009999
    };

    while (rem > 3) {
        for (int i = 0; i < rem; i++) {
            int pi = idx[(i-1+rem) % rem];
            int ci2 = idx[i];
            int ni = idx[(i+1) % rem];

            float px = vx[pi], py = vy[pi];
            float cx2= vx[ci2],cy2= vy[ci2];
            float nx = vx[ni], ny = vy[ni];

            if (cross2(px,py, cx2,cy2, nx,ny) <= 0) continue;

            int blocked = 0;
            for (int j = 0; j < rem && !blocked; j++) {
                int jj = idx[j];
                if (jj==pi || jj==ci2 || jj==ni) continue;
                if (in_tri(vx[jj],vy[jj], px,py, cx2,cy2, nx,ny))
                    blocked = 1;
            }
            if (blocked) continue;

            canvas_fill_tri(c,
                (int)px,(int)py,
                (int)cx2,(int)cy2,
                (int)nx,(int)ny,
                colors[ci % 6]);
            canvas_draw_line(c,(int)px,(int)py,
                (int)cx2,(int)cy2, 0xFF000000);
            canvas_draw_line(c,(int)cx2,(int)cy2,
                (int)nx,(int)ny, 0xFF000000);
            canvas_draw_line(c,(int)nx,(int)ny,
                (int)px,(int)py, 0xFF000000);
            ci++;

            for (int j = i; j < rem-1; j++) idx[j] = idx[j+1];
            rem--;
            break;
        }
    }

    canvas_fill_tri(c,
        (int)vx[idx[0]], (int)vy[idx[0]],
        (int)vx[idx[1]], (int)vy[idx[1]],
        (int)vx[idx[2]], (int)vy[idx[2]],
        colors[ci % 6]);
    free(idx);
}

int main(void)
{
    canvas_t c = canvas_make(400, 400, 0);
    canvas_fill_bg(c, 0xFFFAFAF8);

    /* L-shaped polygon, CCW */
    float vx[] = { 60, 60, 160, 160, 320, 320 };
    float vy[] = { 340, 60, 60, 200, 200, 340 };

    triangulate(c, vx, vy, 6);
    canvas_save_ppm(c, "out_triangulated.ppm");
    return 0;
}

11. Summary

ConceptMath behind it
Winding directionSign of Shoelace formula (signed area)
Convex vs reflex vertexSign of cross product at that vertex
Is P left of edge A→B?Sign of (B−A) × (P−A)
Is P inside a triangle?All three edge cross products share the same sign
Does an ear always exist?Two-Ears Theorem (Meisters, 1975)
Triangle countn − 2
Time complexityO(n^2) naive