Everything Here Is Built on One Predicate
Part 1 introduced orientation(a, b, c) — the sign of a cross product — as the primitive almost every algorithm in this series reduces to. This part is the proof of that claim: segment intersection, point-to-segment distance, polygon area, and point-in-polygon tests are all short compositions of that one function, plus a handful of boundary-case policies.
Segment Intersection
Two segments $p_1p_2$ and $p_3p_4$ properly intersect (cross at a single interior point of both) exactly when $p_3$ and $p_4$ are on opposite sides of line $p_1p_2$, and $p_1$ and $p_2$ are on opposite sides of line $p_3p_4$ — four orientation calls, no coordinate solving required:
The return type matters. A Boolean answers only “do these sets share anything?” but discards whether the result is empty, one point, or an overlapping segment. Geometry libraries often return NONE, POINT(p), or OVERLAP(a, b). That richer contract becomes essential in subdivision overlay, where an overlapping edge cannot be represented honestly as a single point.
Core Insight: the "proper intersection" branch and the "degenerate endpoint-on-segment" branch are two separate policies, not one fallback for the other. Part 6's sweep-line algorithm treats a shared endpoint as its own event type precisely because collapsing it into "just another intersection" double-counts it.
Point-to-Segment Distance
Project the query point $p$ onto the line through $a, b$, clamp the projection parameter $t$ to $[0, 1]$ so the closest point stays on the segment (not the infinite line), then measure the distance to that clamped point:
$$ t = \operatorname{clamp}\!\left(\frac{(p - a) \cdot (b - a)}{(b - a)\cdot(b - a)},\; 0,\; 1\right) \qquad \text{closest point} = a + t (b - a) $$
The clamp is the entire boundary-case story here: without it, a point whose perpendicular foot falls outside the segment would report a distance to a point that isn't actually on the segment.
There are three intuitive regions: before $a$ gives $t<0$, beside the segment gives $0\le t\le1$, and beyond $b$ gives $t>1$. Clamping collapses the first and third regions onto the appropriate endpoint.
The zero-length check is not cosmetic: duplicate endpoints make the projection denominator zero. Treating the “segment” as a point gives a meaningful answer.
Polygon Signed Area and Point-in-Polygon
The shoelace formula gives a simple polygon's signed area directly from its vertices $p_0, \dots, p_{n-1}$:
$$ A = \frac{1}{2} \sum_{i=0}^{n-1} \left( x_i y_{i+1} - x_{i+1} y_i \right) $$
The sign of $A$ tells you the polygon's orientation (positive for counterclockwise, negative for clockwise) — the same orientation test from Part 1, just summed over every edge instead of one triangle. The full derivation, plus the winding number and the closely related ray-crossing point-in-polygon test, is the subject of the "Polygon Area and Winding Number" algorithm spotlight later in this series; here it's enough to state the ray-crossing rule this part's code uses: cast a ray from the query point to infinity and count how many polygon edges it crosses — odd means inside, even means outside.
Parity works because every ordinary boundary crossing toggles the state from outside to inside or back again. A ray through a vertex is awkward: counting both incident edges can toggle twice at one location. The standard half-open rule counts an edge only when one endpoint is strictly above the query's horizontal line and the other is at or below it. The unequal > tests in the code encode that convention.
Boundary Points Are a Policy Decision
Run the ray-crossing test on a point that lies exactly on one of the square's edges, e.g. (4, 2). The result depends on which edge inequalities are strict versus non-strict — this implementation does not guarantee a specific answer for boundary points, which is why every algorithm using it in this series states explicitly whether "on the boundary" counts as inside.
Circle-Line and Circle-Circle Intersection
Circle-line intersection reduces to substituting the line's parametric form into the circle equation and solving a quadratic in the parameter — two real roots (two crossing points), one repeated root (tangent), or no real roots (no intersection). Circle-circle intersection is the 2,000-year-old Problem of Apollonius: given two circles with centers $c_1, c_2$ and radii $r_1, r_2$, find their intersection points by first computing $d = |c_2 - c_1|$, then checking the triangle inequality on $r_1, r_2, d$ (no intersection if $d > r_1 + r_2$ or $d < |r_1 - r_2|$) before solving for the intersection chord.
The center distance $d$ classifies the relationship before any square root is taken. Besides the pictured cases, $d<|r_1-r_2|$ means one circle lies strictly inside the other, while $d=0$ with equal radii means the circles are coincident. Classify first, construct coordinates second: this avoids taking the square root of a tiny negative value created by rounding near tangency.
Apollonius of Perga posed and solved the general tangent-circles problem (construct a circle tangent to three given circles, points, or lines) around 200 BCE, using only compass and straightedge; the special case here — where the "circles" degenerate to the specific configuration of finding intersection points rather than a tangent circle — is the version every 2D physics engine and CAD kernel implements today, usually without realizing its lineage.
Boundary Cases This Part Commits To
- Tangent circles ($d$ exactly equal to $r_1 + r_2$ or $|r_1 - r_2|$): exactly one intersection point, not zero and not two — worth an explicit check rather than relying on the quadratic's discriminant landing on exactly zero in floating point.
- Coincident circles (same center and radius): infinitely many intersection points; this part's implementations treat this as a distinct degenerate case rather than "two intersection points."
- Point-on-boundary for point-in-polygon: as shown above, left as an explicit implementation choice rather than a guaranteed answer.
A dedicated "Polygon Area and Winding Number" algorithm spotlight, later in the series, covers the winding-number test and its relationship to ray-crossing in full depth. The next part builds the first real algorithm on top of these primitives: computing the convex hull of a point set.
Next in the Series
In Convex Hulls in the Plane, we'll explore Graham scan and Andrew's monotone chain, the sorting lower bound they meet exactly, and output-sensitive alternatives like Chan's algorithm.