Float Collinearity Causes Self-Intersecting Polygons
Naive line intersection using raw float equality failed on nearly collinear vertices, causing a self-intersecting polygon destroying three prototypes.
- Orientation test: cross product of vectors (q-p) x (r-p). Positive = CCW, Negative = CW, Zero = collinear. No division — exact for integer coordinates.
- Segment intersection: segments AB and CD intersect iff {A,B} straddle CD AND {C,D} straddle AB. Collinear cases require an on-segment bounding-box check.
- Intersection point: solve parametric line equations. Denominator near zero means parallel — check with epsilon.
- Performance: O(1) per test. No allocations, no division in orientation. Batch tests dominate sweep line algorithms.
- Production insight: floating-point precision is the #1 source of geometry bugs. Use integer coordinates or epsilon comparisons — never raw float equality.
- Biggest mistake: forgetting collinear endpoint cases. Naive implementations return wrong results when an endpoint lies exactly on the other segment.
Two line segments on a plane: do they cross? This sounds simple but requires careful handling of collinear points, endpoints, and floating-point precision. The cross product orientation test answers this in O(1) — and it is the building block of convex hull, polygon clipping, and sweep line algorithms.
Line intersection is the foundational primitive of computational geometry. Collision detection, polygon clipping, sweep line algorithms, and convex hull construction all reduce to one question: do two segments intersect? The cross product orientation test answers this in O(1) with no division, making it numerically robust and fast.
Production geometry code fails not on the common case but on edge cases: collinear overlapping segments, endpoints lying exactly on segments, and floating-point precision at near-parallel intersections. A single missed collinear check can corrupt a convex hull, create self-intersecting polygons, or cause collision detection to miss contacts.
A common misconception is that line equations are equivalent to the cross product approach. They are not. Line equations require division, which introduces floating-point error. The cross product uses only multiplication and subtraction — exact for integer coordinates and significantly more robust for floats.
Orientation Test — The Core Primitive
The orientation of three points p, q, r is determined by the cross product of vectors (q-p) and (r-p): - Positive: counterclockwise (left turn) - Negative: clockwise (right turn) - Zero: collinear
This is the single operation that underlies all segment intersection tests. The cross product formula is:
val = (q.y - p.y) (r.x - q.x) - (q.x - p.x) (r.y - q.y)
No division is involved — only multiplication and subtraction. This makes the test exact for integer coordinates and significantly more robust than line-equation approaches that require dividing by a potentially-zero denominator.
- Cross product = 2 * signed area of triangle (p, q, r)
- Positive area = points are counterclockwise
- Zero area = points are collinear (triangle degenerates to a line)
- No division means no rounding error for integer inputs
Segment Intersection
Two segments AB and CD intersect if their orientations 'straddle' each other. Specifically, A and B must be on opposite sides of line CD, AND C and D must be on opposite sides of line AB. This is checked by verifying that orientation(A,B,C) != orientation(A,B,D) and orientation(C,D,A) != orientation(C,D,B).
Edge case: when one or more orientations are zero (collinear), the segments may still intersect if an endpoint lies on the other segment. The on_segment test checks this by verifying the collinear point falls within the bounding box of the segment.
- o1 == 0: p2 is collinear with p1q1 — check if p2 lies on segment p1q1
- o2 == 0: q2 is collinear with p1q1 — check if q2 lies on segment p1q1
- o3 == 0: p1 is collinear with p2q2 — check if p1 lies on segment p2q2
- o4 == 0: q1 is collinear with p2q2 — check if q1 lies on segment p2q2
Line Intersection Point
Given that two lines intersect, the intersection point is computed by solving the parametric equations of both lines simultaneously. For lines through p1-p2 and p3-p4, the denominator is the cross product of the direction vectors. If the denominator is zero, the lines are parallel. The parameter t gives the position along the first line, and the intersection point is p1 + t*(p2-p1).
This function operates on infinite lines, not segments. To find the intersection of segments, first verify they intersect using segments_intersect, then compute the point. For near-parallel lines, the denominator approaches zero and the intersection point becomes numerically unstable — handle this with an epsilon check.
- line_intersection: where do the infinite lines through these points cross?
- segments_intersect: do the finite segments between these points overlap?
- To get segment intersection point: call segments_intersect first, then line_intersection
- Calling line_intersection alone gives false positives for non-overlapping segments
Self-Intersecting Polygon in CAD Export Crashes CNC Machine
- Floating-point equality in geometry is never safe. Always use epsilon comparisons or exact arithmetic.
- Polygon simplification algorithms can produce self-intersecting output if the intersection predicate is imprecise.
- Post-condition validation (checking that output polygons are simple) catches bugs that the algorithm itself misses.
- Test with adversarial inputs: near-collinear vertices, near-parallel edges, degenerate triangles. Real CAD data is full of these.
Key takeaways
Interview Questions on This Topic
Frequently Asked Questions
That's Geometry. Mark it forged?
3 min read · try the examples if you haven't