DSA DSA In Depth
0%
lesson computational-geometrycross-productccw

Robust Computational Geometry Primitives: 2D Cross/Dot Products, CCW Orientation & Segment Intersection

7 min read Module 6.4: Computational Geometry, Number Theory, Randomized & Approximation Algorithms Updated Oct 23, 2026
📄 Cheat Sheet
Computational Geometry Primitives: Cross Products, Orientation & Segment Intersection
Errichto Algorithms Computational Geometry Primitives: Cross Products, Orientation & Segment Intersection Watch on YouTube ↗

Demonstrates why slope-based geometry fails on vertical lines and floating-point rounding, and how exact 64-bit integer cross/dot products solve orientation, segment intersection, and polygon area without division.

0:00 / --:--

1. Formal Invariant — Exact Orientation & Segment Intersection Predicates

Invariant: The CCW Orientation Determinant

Given three points $A(x_A, y_A)$, $B(x_B, y_B)$, and $C(x_C, y_C)$ in $\mathbb{Z}^2$, define the displacement vectors $\vec{AB} = B - A$ and $\vec{AC} = C - A$. The 2D cross product (twice the signed area of $\triangle ABC$) is:

$$\text{cross}(A, B, C) = \det \begin{pmatrix} x_B - x_A & y_B - y_A \\ x_C - x_A & y_C - y_A \end{pmatrix} = (x_B - x_A)(y_C - y_A) - (y_B - y_A)(x_C - x_A)$$

  • $\text{cross}(A, B, C) > 0 \iff A \to B \to C$ makes a strict Counter-Clockwise (Left) turn.
  • $\text{cross}(A, B, C) < 0 \iff A \to B \to C$ makes a strict Clockwise (Right) turn.
  • $\text{cross}(A, B, C) = 0 \iff A, B, C$ are collinear. Furthermore, $C$ lies on the closed segment $\overline{AB}$ iff $\text{cross}(A, B, C) = 0$ and $(A - C) \cdot (B - C) \le 0$.

By the Shoelace Theorem (Discrete Green's Theorem), twice the signed area of any simple polygon $P_0, P_1, \dots, P_{N-1}$ is the exact integer sum $\sum_{i=0}^{N-1} (P_i \times P_{(i+1) \bmod N})$.

2. Exact Segment-Segment Intersection Decision Tree

flowchart TD Segs["Segments AB and CD"] --> EvalCCW["Compute s1 = sgn(cross(A, B, C)), s2 = sgn(cross(A, B, D))
s3 = sgn(cross(C, D, A)), s4 = sgn(cross(C, D, B))"] EvalCCW --> Proper{"s1 * s2 < 0 AND
s3 * s4 < 0 ?"} Proper -->|"Yes"| HitProper["Proper Interior Intersection (True)"] Proper -->|"No"| Collinear{"Does any endpoint lie
on the other segment?
(e.g. s1 == 0 && on_seg(A, B, C))"} Collinear -->|"Yes"| HitDegenerate["Degenerate / Collinear Intersection (True)"] Collinear -->|"No"| Miss["Disjoint Segments (False)"]

3. Why sgn() Normalization Prevents Overflow

If coordinates satisfy $|x|, |y| \le 10^9$, then $\text{cross}(A, B, C)$ is bounded by $4 \times 10^{18}$, which fits inside a signed 64-bit integer (int64_t max $\approx 9.22 \times 10^{18}$). However, if you test opposite orientations by multiplying $\text{cross}(A, B, C) \times \text{cross}(A, B, D) < 0$ directly in int64_t, the product reaches $1.6 \times 10^{37}$ and overflows! Mapping each cross product through $\text{sgn}(v) \in \{-1, 0, +1\}$ first reduces the product to $\{-1, 0, +1\}$ with zero overflow risk.

4. Hardware & Floating-Point Realities

IEEE-754 Catastrophic Cancellation vs Exact Integer Predicates

In IEEE-754 double (53-bit significand), subtracting two nearly equal products $u_x v_y - u_y v_x$ loses all leading matching bits (catastrophic cancellation), which can flip the sign of cross(A, B, C) from positive to negative and cause Convex Hull or Delaunay Triangulation algorithms to enter infinite loops! Production CAD/GIS kernels (CGAL, Jonathan Shewchuk's Adaptive Precision Floating-Point Predicates) use a fast hardware floating-point filter first and fall back to exact multi-word expansion arithmetic only when $|u_x v_y - u_y v_x|$ is smaller than the rounding error bound.

5. Complexity Matrix

Andrew Monotone Chain Convex Hull & Sweep-Line Events
O(N log N) · CCW Cross Product

Trace 2D cross-product orientation tests cross(O, A, B) = (Ax-Ox)(By-Oy) - (Ay-Oy)(Bx-Ox) to prune clockwise interior vertices and detect sweep-line segment intersections.

Step 1 / 8
Custom Input:
Timeline

Invariant & State

cross(P0, P1, P2) < 0 (Clockwise / Right Turn): P1 lies strictly inside the lower hull and is popped from the stack.
P0 P1 (Pop!) P2 P3 P4 P5
Inspected Triple
P0 → P1 → P2
Cross Product
cross < 0 (Right Turn)
Stack Action
Pop P1, Push P2
[Step 1] Right turn detected at P1; popped P1 so lower hull edge connects P0 → P2 directly.
Geometric Predicate / Algorithm Time Complexity Space Complexity Arithmetic Requirement
2D CCW / Point-on-Segment / Segment Intersection O(1) O(1) int64_t (for coords $\le 10^9$)
Shoelace Polygon Area ($N$ vertices) O(N) O(1) int64_t ($2 \times \text{Area}$ is exact integer)
Point-in-Polygon (Winding Number / Ray Casting) O(N) O(1) int64_t cross products
Point-in-Convex-Polygon (Binary Search Wedge) O(log N) O(1) int64_t cross products

6. Production-Grade C++20 Implementation

#include <cstdint>
#include <vector>
#include <cmath>

struct Point {
    int64_t x = 0, y = 0;
    Point operator-(const Point& o) const { return {x - o.x, y - o.y}; }
    Point operator+(const Point& o) const { return {x + o.x, y + o.y}; }
};

inline int64_t dot(const Point& u, const Point& v) {
    return u.x * v.x + u.y * v.y;
}

inline int64_t cross(const Point& u, const Point& v) {
    return u.x * v.y - u.y * v.x;
}

// Twice the signed area of triangle ABC (>0: CCW, <0: CW, 0: collinear)
inline int64_t cross(const Point& a, const Point& b, const Point& c) {
    return cross(b - a, c - a);
}

inline int sgn(int64_t val) {
    return (val > 0) - (val < 0);
}

// Returns true iff point p lies on closed segment AB
inline bool on_segment(const Point& a, const Point& b, const Point& p) {
    return cross(a, b, p) == 0 && dot(a - p, b - p) <= 0;
}

// Exact closed segment intersection between AB and CD
bool segments_intersect(const Point& a, const Point& b,
                        const Point& c, const Point& d) {
    int s1 = sgn(cross(a, b, c));
    int s2 = sgn(cross(a, b, d));
    int s3 = sgn(cross(c, d, a));
    int s4 = sgn(cross(c, d, b));

    // Proper intersection: C,D strictly on opposite sides of AB and vice versa
    if (s1 * s2 < 0 && s3 * s4 < 0) return true;

    // Collinear / endpoint touching cases
    if (s1 == 0 && on_segment(a, b, c)) return true;
    if (s2 == 0 && on_segment(a, b, d)) return true;
    if (s3 == 0 && on_segment(c, d, a)) return true;
    if (s4 == 0 && on_segment(c, d, b)) return true;
    return false;
}

// Returns 2 * Area of simple polygon P (always an exact integer!)
int64_t polygon_twice_area(const std::vector<Point>& poly) {
    int64_t sum = 0;
    const std::size_t n = poly.size();
    for (std::size_t i = 0; i < n; ++i) {
        sum += cross(poly[i], poly[(i + 1) % n]);
    }
    return sum < 0 ? -sum : sum;
}

Production Pitfall — Using atan2 for Polar Angle Sorting

Sorting points or vectors around an origin by calling std::atan2(y, x) is both slow ($sim 40$ CPU cycles per transcendental evaluation) and inaccurate for integer coordinates near $10^9$ where distinct rays have identical double angles. Instead, partition vectors by half-plane (half(p) = (p.y > 0 || (p.y == 0 && p.x > 0)) ? 0 : 1) and sort within each half-plane using exact cross products cross(u, v) > 0!

Check yourself Question 1 of 4
Concept Check Core Invariant Check recall

Given three points $A, B, C$ in the 2D plane, what does the sign of the 2D cross product $\text{cross}(A, B, C) = (x_B - x_A)(y_C - y_A) - (y_B - y_A)(x_C - x_A)$ signify?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

You need to sort $N$ 2D integer vectors around the origin $(0, 0)$ by polar angle in $[0, 2\pi)$ with exact integer arithmetic ($|x|, |y| \le 10^9$). Why does `return cross(a, b) > 0;` alone fail as a `std::sort` comparator across the full $360^\circ$ circle, and how is it fixed?

Key Takeaways

The 2D cross product cross(A, B, C) = (B - A) x (C - A) computes twice the signed area of triangle ABC: positive for CCW (left turn), negative for CW (right turn), and zero for collinear points.
Point P lies on closed segment AB iff cross(A, B, P) == 0 (collinear) and dot(A - P, B - P) <= 0 (between endpoints).
Always pass cross products through sgn(x) in {-1, 0, +1} before multiplying orientation signs so int64_t never overflows.
The Shoelace Formula sums P_i x P_{i+1} around a polygon boundary to compute 2 * Area as an exact integer without floating-point arithmetic.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue