From numbers on a grid to a line on the screen
A weather map's isobars, a topographic map's elevation contours, and a video game's soft, blobby "metaball" creatures are all the same underlying problem: you have a scalar field — one number at every point of a grid — and you want to draw the curve where that field crosses a chosen threshold. Marching squares is the standard algorithm for it, and it works one small 2x2 cell of the grid at a time.
Four corners, sixteen cases
Each cell of the grid has four corners, and each corner is either above the threshold ("inside", bit = 1) or below it ("outside", bit = 0). Four bits give exactly 16 possible configurations, and each one has a fixed, precomputed line pattern showing how the contour crosses the cell — a plain lookup table, no branching logic needed beyond building the 4-bit index.
index = (topLeft >= threshold ? 8 : 0)
| (topRight >= threshold ? 4 : 0)
| (bottomRight >= threshold ? 2 : 0)
| (bottomLeft >= threshold ? 1 : 0)
// index 0 and 15 → no line (all corners on the same side)
// index 1, 2, 4, 8 and their complements → one corner cut off by a line
// index 3, 6, 9, 12 → two adjacent corners cut off, one line straight across
// index 5, 10 → the two SADDLE cases, see below
To make the resulting line smooth rather than always crossing the exact midpoint of an edge, the crossing point is placed by linear interpolation between the two corner values: if one corner is far above the threshold and its neighbour is barely above it, the crossing sits close to the second corner, not halfway between them. This single refinement is what turns a blocky staircase into a contour that looks like it was traced by hand.
The saddle ambiguity
Two of the sixteen cases are genuinely ambiguous: index 5 and index 10, where the two corners diagonally opposite each other are both "inside" and the other diagonal pair is both "outside". The four edge crossings are unambiguous by themselves, but there are two different, equally valid ways to connect them into lines — one interpretation draws two separate small arcs cutting off each inside corner, the other draws two long arcs that pass near each other without ever touching. Both are locally consistent with the four corner values; the grid alone cannot say which is correct.
saddle case (index 5 or 10):
center = average of the four corner values // or bilinear sample
if center >= threshold:
connect the crossings as ONE diagonal pass (inside corners joined)
else:
connect the crossings as TWO separate cut corners
The standard fix samples an extra value at the cell's center — the average of the four corners is a cheap approximation, a proper bilinear interpolation is more accurate — and uses it to decide which of the two topologies is consistent with the field's actual behaviour in the middle of the cell. It is a small patch on top of an otherwise trivial lookup table, and it is the detail that separates a correct marching-squares implementation from one that occasionally produces a contour with an impossible crossing.
Beyond flat lines: marching cubes and dual contouring
The natural extension to three dimensions is marching cubes (Lorensen and Cline, 1987), which extracts a triangulated isosurface from a 3D scalar field the same cell-by-cell way, using 8 corners instead of 4 and 256 raw configurations that reduce to 15 unique cases by symmetry. It inherits an even trickier version of the saddle ambiguity, since a single cube face can itself be a saddle case, and getting every combination watertight took the graphics community years to fully resolve. A newer alternative, dual contouring, places one vertex inside each active cell rather than on its edges and can represent sharp corners that marching cubes smooths away — useful for extracting surfaces from CSG models and voxel terrain where hard edges matter.
Frequently asked questions
Why do marching squares contours look jagged on a coarse grid?
Because each contour segment is a straight line inside one grid cell, and the grid resolution is the only source of curvature information. A curve that bends sharply within one cell can only be drawn as a straight chord across it. Raising the grid resolution, or interpolating the field itself with a smoother function before sampling, is the only fix — the algorithm has no way to add curvature it was not given.
What exactly is the saddle-point ambiguity?
It happens in cases 5 and 10, where two diagonally opposite corners are above the threshold and the other two are below. The four-corner pattern alone cannot tell you whether the two "inside" corners belong to the same blob or two separate blobs passing near each other, because both interpretations connect the same four edge crossings differently. Sampling the cell's center value and comparing it to the threshold resolves which interpretation is consistent with the field in between.
How is marching cubes different from marching squares?
Marching cubes is the 3D generalisation, extracting a triangulated isosurface from a 3D scalar field instead of a 2D contour line from a 2D field. Each cube has 8 corners instead of 4, giving 256 raw configurations that reduce to 15 unique cases by symmetry — and marching cubes has its own, more severe version of the same ambiguity problem, since a cube face can itself contain a saddle.
Try it live
Everything above runs in your browser — open Marching Squares and change the parameters while it is running. Nothing is installed, nothing is uploaded, the whole model lives in one tab.
▶ Open Marching Squares simulation