The idea: sweep a line and test only neighbours

You have n line segments and want every pair that crosses. The obvious way tests all pairs: n(n − 1)/2 tests, even for segments at opposite ends of the picture. The sweep line algorithm of Bentley and Ottmann (1979) moves a vertical line from left to right. At each moment it keeps the segments the line cuts, in order from bottom to top. Two segments can only cross after they sit next to each other in that order, so it only ever tests neighbours.

Five segments and a vertical sweep line. The line cuts three of them; on the right, the status lists them top to bottom, and only the two adjacent pairs are tested.
The status is the order in which the sweep line cuts the segments; only pairs next to each other in it are tested.

The page uses segments with whole-number ends on a 20 × 16 grid, chosen so that no two events share an x and no three segments meet in one point. Every number on the canvas can be checked by hand.

Do two segments cross?

This uses the orientation test from the convex hull page: the sign of

cross(a, b, c) = (bx − ax)·(cy − ay) − (by − ay)·(cx − ax)

says whether c is left of the line a→b (positive), right of it (negative) or on it (0). Segments s and t cross exactly when both of these hold:

  • t's two ends are on opposite sides of the line through s (the two cross values have opposite signs), and
  • s's two ends are on opposite sides of the line through t.
Left: two segments each straddle the other's line, both tests pass, they cross. Right: one segment straddles the other's line, but the other segment's ends are on one side, so they do not cross.
One test is not enough: the line through t may cut s while the segment t stops short.

Four multiplications-and-subtractions, no division, no square roots. Only when both tests pass is the crossing point worked out, with u = d₃ / (d₃ − d₄) along s (Demo: do two segments cross?). On the page, A and B pass both tests. E's ends are on opposite sides of line F, but F's ends are both on one side of line E, so E and F do not cross, even though they come very close.

Events and the status

The line does not move smoothly; it jumps between events, the only x values where the order can change. They wait in an event queue sorted by x (a priority queue or a balanced tree). The ordered list of cut segments is the status (a balanced search tree keyed by y at the current x).

EventWhat changesWhich pairs are tested
Left end of sInsert s into the status, by its ys with the segment just above, s with the one just below
Right end of sRemove sThe segments that were above and below s: they are neighbours now
Crossing of s and tReport it, swap s and tEach of the two with its new outer neighbour

A test that finds a crossing right of the sweep line adds a crossing event to the queue. A crossing left of the line was already handled, and one already in the queue is not added twice (Demo: sweep 6 segments).

A crossing swaps two neighbours

Just left of a crossing, A is below B; just right of it, A is above B. So at the crossing event the two trade places in the status. That gives each of them a new neighbour on its outer side, and those two new pairs are tested.

Before the crossing the status reads C, B, A, D from top to bottom; after it reads C, A, B, D, and the new pairs C with A and B with D are tested.
At a crossing the two segments swap places, and only the two new outer pairs need a test.

Why testing neighbours is enough

Take two segments that cross at point P, and suppose no other event lies between them near P. Just left of P, nothing lies between them on the sweep line, so they are neighbours in the status. They became neighbours at some earlier event (an insert, a removal or a swap), and at that event they were tested. So every crossing is put in the queue before the line reaches it.

Cost: brute force vs the sweep

SegmentsnCrossings kBrute force testsSweep tests
6 crossing segments631511
8 segments, 1 crossing812813

There are 2n + k events, each with at most two tests and a few O(log n) tree operations, so the sweep costs O((n + k) log n). Brute force is always O(n²). The sweep wins when crossings are few (Demo: 8 segments, 1 crossing). When almost every pair crosses, k is about n² and brute force is simpler and just as fast.

Where it is used

Map overlay in GIS (where do roads cross rivers?), checking that a polygon does not cross itself, design-rule checks on chip layouts, and Boolean operations on shapes in CAD and vector drawing programs. The same idea, a line sweeping across the plane with a sorted status, also builds the convex hull with Andrew's monotone chain and fills polygons scan line by scan line, as in rasterization. The cross product itself is explained in vectors, the dot product and the cross product.

The same crossing test answers whether a point is inside a polygon: see Point in Polygon.

What the page leaves out

The page leaves out the special cases: vertical segments, endpoints that lie on another segment, overlapping collinear segments and three or more segments through one point. Real code handles them with extra rules and with exact arithmetic, since floating-point crossings can fall in the wrong order. It also skips the balanced trees themselves (the status is drawn as a list) and faster algorithms such as Chazelle–Edelsbrunner, which reach O(n log n + k).