Table of Contents

  1. Points and Vectors
  2. Cross Product and Orientation
  3. Line Segment Intersection
  4. Convex Hull
  5. Point in Polygon
  6. Closest Pair of Points
  7. Sweep Line Algorithms
  8. Polygon Area and Centroid
  9. Half-Plane Intersection
  10. Rotating Calipers
  11. Voronoi Diagrams and Delaunay Triangulation
  12. Practice Problems

1. Points and Vectors

Everything in computational geometry starts with representing points in 2D space and performing vector operations on them. A point is a location (x, y). A vector is a direction + magnitude, often represented the same way but interpreted differently.

Key Insight
A vector from point A to point B is simply B - A = (B.x - A.x, B.y - A.y). This is the most fundamental operation -- it converts two points into a direction.

Point / Vector Struct

C++
struct Point {
    double x, y;
    Point(double x = 0, double y = 0) : x(x), y(y) {}

    // Vector addition
    Point operator+(const Point& p) const { return {x + p.x, y + p.y}; }
    // Vector subtraction (A - B = vector from B to A)
    Point operator-(const Point& p) const { return {x - p.x, y - p.y}; }
    // Scalar multiplication
    Point operator*(double t) const { return {x * t, y * t}; }
    // Dot product
    double dot(const Point& p) const { return x * p.x + y * p.y; }
    // Cross product (z-component of 3D cross)
    double cross(const Point& p) const { return x * p.y - y * p.x; }
    // Magnitude
    double norm() const { return sqrt(x * x + y * y); }
    // Squared magnitude (avoid sqrt when possible)
    double norm2() const { return x * x + y * y; }
};
Python
import math

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __add__(self, p):
        return Point(self.x + p.x, self.y + p.y)

    def __sub__(self, p):
        return Point(self.x - p.x, self.y - p.y)

    def __mul__(self, t):
        return Point(self.x * t, self.y * t)

    def dot(self, p):
        return self.x * p.x + self.y * p.y

    def cross(self, p):
        return self.x * p.y - self.y * p.x

    def norm(self):
        return math.sqrt(self.x ** 2 + self.y ** 2)

    def norm2(self):
        return self.x ** 2 + self.y ** 2

    def __repr__(self):
        return f"({self.x}, {self.y})"

Visual: Vector Operations


        y
        ^
    4   |         B(4,3)
        |        /
    3   |       / <-- vector AB
        |      /
    2   |     /
        |    /
    1   |   A(1,1)
        |
    0   +---+---+---+---+---> x
        0   1   2   3   4

    Vector AB = B - A = (3, 2)
    |AB| = sqrt(9 + 4) = sqrt(13) ~ 3.61

Dot Product Intuition

Dot Product
A . B = |A| * |B| * cos(theta)
A . B = A.x * B.x + A.y * B.y

Result > 0: angle < 90 degrees (vectors point roughly same direction)
Result = 0: angle = 90 degrees (perpendicular)
Result < 0: angle > 90 degrees (vectors point roughly opposite)

Angle Between Two Vectors

Python
def angle_between(a, b):
    """Returns angle in radians between vectors a and b"""
    cos_theta = a.dot(b) / (a.norm() * b.norm())
    # Clamp to avoid floating point errors with acos
    cos_theta = max(-1, min(1, cos_theta))
    return math.acos(cos_theta)
Floating Point Warning
Computational geometry is plagued by floating point issues. Whenever possible, use integer arithmetic (cross products, dot products with integer coordinates) and avoid sqrt and acos. Compare squared distances instead of distances. Use an epsilon (e.g., 1e-9) for floating point comparisons.

2. Cross Product and Orientation

The 2D cross product is the single most important tool in computational geometry. It tells you the orientation (turn direction) of three points.

2D Cross Product
For vectors A and B: A x B = A.x * B.y - A.y * B.x

This equals the signed area of the parallelogram formed by A and B.
Equivalently, for three points P, Q, R:
cross(PQ, PR) = (Q-P) x (R-P)

Orientation Test (CCW / CW / Collinear)


    Positive cross => Counter-Clockwise (left turn)

         R
        /
       /
      P --------> Q     Turn LEFT to go P->Q->R

    Negative cross => Clockwise (right turn)

      P --------> Q     Turn RIGHT to go P->Q->R
       \
        \
         R

    Zero cross => Collinear

      P --------> Q --------> R  (all on same line)
C++
// Returns: positive = CCW, negative = CW, 0 = collinear
double cross(Point O, Point A, Point B) {
    return (A - O).cross(B - O);
}

// Orientation enum for clarity
int orient(Point P, Point Q, Point R) {
    double v = cross(P, Q, R);
    if (v > 0) return 1;   // CCW (left turn)
    if (v < 0) return -1;  // CW  (right turn)
    return 0;              // collinear
}
Python
def cross3(O, A, B):
    """Cross product of vectors OA and OB"""
    return (A.x - O.x) * (B.y - O.y) - (A.y - O.y) * (B.x - O.x)

def orient(P, Q, R):
    """Returns 1=CCW, -1=CW, 0=collinear"""
    v = cross3(P, Q, R)
    if v > 0: return 1
    if v < 0: return -1
    return 0

Signed Area of a Triangle

Triangle Area from Cross Product
Area(P, Q, R) = |cross(PQ, PR)| / 2

The signed version (without absolute value) is positive when P,Q,R are in CCW order, negative when CW.
Python
def triangle_area(P, Q, R):
    return abs(cross3(P, Q, R)) / 2
When to Use
The orientation test is the building block of almost everything: convex hulls, segment intersection, point-in-polygon, and more. Master it and the rest follows naturally.

3. Line Segment Intersection

Given segments AB and CD, do they intersect? This is one of the most common geometric predicates.

Geometric Intuition


    Two segments intersect if and only if:
    1) Points C and D are on opposite sides of line AB
       AND points A and B are on opposite sides of line CD

         A-----------B
              / \
             /   \
            C     D     C and D on opposite sides of AB? YES
                        A and B on opposite sides of CD? YES
                        => They intersect!

    Special case: a point lies ON the other segment (collinear)

The Algorithm

Use orientation tests. Segments AB and CD intersect when:

C++
bool onSegment(Point p, Point a, Point b) {
    // Check if p is on segment ab, assuming p is collinear with a,b
    return min(a.x, b.x) <= p.x && p.x <= max(a.x, b.x)
        && min(a.y, b.y) <= p.y && p.y <= max(a.y, b.y);
}

bool segmentsIntersect(Point a, Point b, Point c, Point d) {
    int d1 = orient(a, b, c);
    int d2 = orient(a, b, d);
    int d3 = orient(c, d, a);
    int d4 = orient(c, d, b);

    // General case: segments straddle each other's lines
    if (d1 * d2 < 0 && d3 * d4 < 0)
        return true;

    // Special cases: collinear points lie on the other segment
    if (d1 == 0 && onSegment(c, a, b)) return true;
    if (d2 == 0 && onSegment(d, a, b)) return true;
    if (d3 == 0 && onSegment(a, c, d)) return true;
    if (d4 == 0 && onSegment(b, c, d)) return true;

    return false;
}
Python
def on_segment(p, a, b):
    """Check if p lies on segment ab (assuming collinear)"""
    return (min(a.x, b.x) <= p.x <= max(a.x, b.x) and
            min(a.y, b.y) <= p.y <= max(a.y, b.y))

def segments_intersect(a, b, c, d):
    d1 = orient(a, b, c)
    d2 = orient(a, b, d)
    d3 = orient(c, d, a)
    d4 = orient(c, d, b)

    if d1 * d2 < 0 and d3 * d4 < 0:
        return True

    if d1 == 0 and on_segment(c, a, b): return True
    if d2 == 0 and on_segment(d, a, b): return True
    if d3 == 0 and on_segment(a, c, d): return True
    if d4 == 0 and on_segment(b, c, d): return True

    return False

Finding the Intersection Point

When you need the actual intersection coordinates, use parametric line equations:

Python
def intersection_point(a, b, c, d):
    """Find intersection point of segments AB and CD.
    Returns None if they don't intersect or are parallel."""
    # Line AB: P = A + t*(B-A), Line CD: P = C + u*(D-C)
    denom = (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x)
    if abs(denom) < 1e-9:
        return None  # parallel

    t = ((c.x - a.x) * (d.y - c.y) - (c.y - a.y) * (d.x - c.x)) / denom
    u = ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)) / denom

    if 0 <= t <= 1 and 0 <= u <= 1:
        return Point(a.x + t * (b.x - a.x), a.y + t * (b.y - a.y))
    return None
Complexity
Time: O(1) per intersection test
Space: O(1)

4. Convex Hull

The convex hull of a set of points is the smallest convex polygon containing all points. Think of stretching a rubber band around nails on a board -- the shape it makes is the convex hull.


    Convex Hull Visualization:

    Input points:          Convex Hull:
                                  *
      *   *                      / \
        *                       /   \
    *       *               *  / * * \  *
      *   *                  \/       \/
        *                     *-------*

    The "rubber band" wraps around the outermost points.

Andrew's Monotone Chain (Preferred)

Sort points by x (then y), then build lower and upper hulls separately. This is generally preferred over Graham scan because it avoids angle computations and handles collinear points cleanly.

Algorithm Steps
1. Sort points by (x, y)
2. Build lower hull: process left to right, keeping only left turns
3. Build upper hull: process right to left, keeping only left turns
4. Concatenate (remove duplicated endpoints)

    Walkthrough: Building lower hull for points sorted left to right

    Step 1: Start with leftmost points
    *

    Step 2: Add next point
    *---*

    Step 3: Add point -- makes left turn, keep it
    *---*
         \
          *

    Step 4: Add point -- makes RIGHT turn! Pop the previous point
    *---*           *---*
         \    =>         \
          *               *---*     (popped the "dent")
C++
vector<Point> convexHull(vector<Point> pts) {
    int n = pts.size();
    if (n < 2) return pts;
    sort(pts.begin(), pts.end(), [](Point& a, Point& b) {
        return a.x < b.x || (a.x == b.x && a.y < b.y);
    });

    vector<Point> hull;

    // Lower hull
    for (auto& p : pts) {
        while (hull.size() >= 2 &&
               cross(hull[hull.size()-2], hull[hull.size()-1], p) <= 0)
            hull.pop_back();
        hull.push_back(p);
    }

    // Upper hull
    int lower_size = hull.size() + 1;
    for (int i = n - 2; i >= 0; i--) {
        while (hull.size() >= lower_size &&
               cross(hull[hull.size()-2], hull[hull.size()-1], pts[i]) <= 0)
            hull.pop_back();
        hull.push_back(pts[i]);
    }

    hull.pop_back(); // Remove last point (same as first)
    return hull;
}
Python
def convex_hull(points):
    """Andrew's monotone chain. Returns hull in CCW order."""
    points = sorted(points, key=lambda p: (p.x, p.y))
    if len(points) <= 1:
        return points

    # Build lower hull
    lower = []
    for p in points:
        while len(lower) >= 2 and cross3(lower[-2], lower[-1], p) <= 0:
            lower.pop()
        lower.append(p)

    # Build upper hull
    upper = []
    for p in reversed(points):
        while len(upper) >= 2 and cross3(upper[-2], upper[-1], p) <= 0:
            upper.pop()
        upper.append(p)

    # Concatenate, removing duplicate endpoints
    return lower[:-1] + upper[:-1]
Complexity
Time: O(n log n) -- dominated by sorting
Space: O(n)

Graham Scan (Alternative)

Graham scan picks the lowest point as anchor, sorts remaining points by polar angle relative to the anchor, then builds the hull by processing in angular order.

Python
def graham_scan(points):
    """Graham scan convex hull. Returns hull in CCW order."""
    # Find bottom-most point (lowest y, then leftmost x)
    anchor = min(points, key=lambda p: (p.y, p.x))

    def polar_angle(p):
        return math.atan2(p.y - anchor.y, p.x - anchor.x)

    def dist2(p):
        return (p.x - anchor.x) ** 2 + (p.y - anchor.y) ** 2

    # Sort by polar angle, break ties by distance
    pts = sorted(points, key=lambda p: (polar_angle(p), dist2(p)))

    stack = []
    for p in pts:
        while len(stack) >= 2 and cross3(stack[-2], stack[-1], p) <= 0:
            stack.pop()
        stack.append(p)

    return stack
When to Use Convex Hull
  • Farthest pair of points -- always on the convex hull (use rotating calipers)
  • Minimum enclosing rectangle/circle -- work on hull instead of all points
  • Checking if all points can be separated by a line
  • Any problem where only boundary points matter

5. Point in Polygon

Given a polygon (simple, not necessarily convex) and a query point, determine if the point is inside the polygon.

Ray Casting Algorithm

Cast a ray from the query point in any direction (typically to the right). Count how many times it crosses the polygon boundary. Odd = inside, even = outside.


    Ray Casting Visualization:

    +---+               Point P is INSIDE:
    |   |               Ray crosses boundary 1 time (odd)
    | P -------->
    |   |               Point Q is OUTSIDE:
    +---+---+           Ray crosses boundary 2 times (even)
        |   |
        | Q ---------->
        |   |
        +---+
Python
def point_in_polygon(p, polygon):
    """Ray casting: returns True if p is inside the polygon.
    polygon is a list of Point objects forming a simple polygon."""
    n = len(polygon)
    inside = False

    j = n - 1
    for i in range(n):
        pi = polygon[i]
        pj = polygon[j]

        # Check if ray from p going right crosses edge (pj, pi)
        if ((pi.y > p.y) != (pj.y > p.y)) and \
           (p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x):
            inside = not inside

        j = i

    return inside
C++
bool pointInPolygon(Point p, vector<Point>& poly) {
    int n = poly.size();
    bool inside = false;
    for (int i = 0, j = n - 1; i < n; j = i++) {
        if ((poly[i].y > p.y) != (poly[j].y > p.y) &&
            p.x < (poly[j].x - poly[i].x) * (p.y - poly[i].y) /
                   (poly[j].y - poly[i].y) + poly[i].x)
            inside = !inside;
    }
    return inside;
}
Complexity
Time: O(n) per query, where n = number of polygon vertices
Space: O(1)

Winding Number Algorithm

Count how many times the polygon winds around the point. More robust than ray casting for edge cases.

Python
def winding_number(p, polygon):
    """Returns the winding number (0 = outside, nonzero = inside)"""
    n = len(polygon)
    wn = 0

    for i in range(n):
        a = polygon[i]
        b = polygon[(i + 1) % n]

        if a.y <= p.y:
            if b.y > p.y:  # upward crossing
                if cross3(a, b, p) > 0:  # p is left of edge
                    wn += 1
        else:
            if b.y <= p.y:  # downward crossing
                if cross3(a, b, p) < 0:  # p is right of edge
                    wn -= 1

    return wn

Convex Polygon: O(log n) Binary Search

For a convex polygon, you can do point-in-polygon in O(log n) by treating the polygon as a fan of triangles from one vertex and binary searching for the right triangle.

Python
def point_in_convex_polygon(p, hull):
    """O(log n) point-in-convex-polygon. hull is in CCW order."""
    n = len(hull)
    if n < 3:
        return False

    # Check if p is on the correct side of edges from hull[0]
    if cross3(hull[0], hull[1], p) < 0:
        return False
    if cross3(hull[0], hull[n - 1], p) > 0:
        return False

    # Binary search for the triangle containing p
    lo, hi = 1, n - 1
    while hi - lo > 1:
        mid = (lo + hi) // 2
        if cross3(hull[0], hull[mid], p) >= 0:
            lo = mid
        else:
            hi = mid

    # Check if p is inside triangle (hull[0], hull[lo], hull[hi])
    return cross3(hull[lo], hull[hi], p) >= 0
Which Method to Use?
  • Ray casting -- simple polygons, easy to implement, O(n)
  • Winding number -- more robust for self-intersecting polygons, O(n)
  • Binary search -- convex polygons only, O(log n) per query after O(n) preprocessing

6. Closest Pair of Points

Given n points, find the two points with minimum distance. The brute force is O(n^2). We can do O(n log n) with divide and conquer.

Divide and Conquer Approach


    Algorithm Overview:

    1. Sort points by x-coordinate
    2. Split into left half and right half
    3. Recursively find closest pair in each half
    4. d = min(left_closest, right_closest)
    5. Check the "strip" of width 2d around the dividing line
       (points in the strip might form a closer pair across halves)
    6. For each point in the strip, only check the next ~7 points
       (sorted by y) -- this is the key insight!

    |         |         |
    |  Left   | Strip   |  Right
    |  half   | (2d)    |  half
    |    *    |  * *    |    *
    |  *      |    *    |  *
    |      *  |  *      |    *
    |         |         |
    |<-- d -->|<-- d -->|
Python
import math

def closest_pair(points):
    """Returns the minimum distance between any two points.
    O(n log n) divide and conquer."""
    pts = sorted(points, key=lambda p: (p.x, p.y))

    def dist(a, b):
        return math.sqrt((a.x - b.x)**2 + (a.y - b.y)**2)

    def rec(pts):
        n = len(pts)
        if n <= 3:
            # Brute force for small inputs
            best = float('inf')
            for i in range(n):
                for j in range(i+1, n):
                    best = min(best, dist(pts[i], pts[j]))
            return best

        mid = n // 2
        mid_x = pts[mid].x

        # Recurse on left and right halves
        d = min(rec(pts[:mid]), rec(pts[mid:]))

        # Build strip: points within distance d of the dividing line
        strip = [p for p in pts if abs(p.x - mid_x) < d]
        strip.sort(key=lambda p: p.y)

        # Check strip: for each point, only check next few points
        for i in range(len(strip)):
            j = i + 1
            while j < len(strip) and (strip[j].y - strip[i].y) < d:
                d = min(d, dist(strip[i], strip[j]))
                j += 1

        return d

    return rec(pts)
C++
double closestPair(vector<Point>& pts, int l, int r) {
    if (r - l <= 3) {
        double best = 1e18;
        for (int i = l; i < r; i++)
            for (int j = i + 1; j < r; j++)
                best = min(best, (pts[i] - pts[j]).norm());
        return best;
    }

    int mid = (l + r) / 2;
    double midX = pts[mid].x;
    double d = min(closestPair(pts, l, mid), closestPair(pts, mid, r));

    // Merge step: build strip
    vector<Point> strip;
    for (int i = l; i < r; i++)
        if (abs(pts[i].x - midX) < d)
            strip.push_back(pts[i]);

    sort(strip.begin(), strip.end(), [](const Point& a, const Point& b) {
        return a.y < b.y;
    });

    for (int i = 0; i < strip.size(); i++)
        for (int j = i + 1; j < strip.size() && strip[j].y - strip[i].y < d; j++)
            d = min(d, (strip[i] - strip[j]).norm());

    return d;
}

// Usage: sort pts by x first, then call closestPair(pts, 0, pts.size())
Complexity
Time: O(n log^2 n) as shown above (can be made O(n log n) by maintaining sorted order during merge)
Space: O(n)
The Strip Optimization -- Why Only 7 Points?
In the strip of width 2d, any d x 2d rectangle can hold at most 8 points (because each half guarantees no two points closer than d). So for each point, we check at most 7 others sorted by y-coordinate. This is what brings the strip check from O(n^2) down to O(n).

7. Sweep Line Algorithms

Sweep line is a paradigm: imagine a vertical line sweeping left-to-right across the plane, processing "events" (point appearances, segment endpoints) in order. A data structure (usually a balanced BST or set) maintains the current state.

Line Segment Intersection (Bentley-Ottmann Concept)


    Sweep line moving left to right:

    |  /       /
    | /   X   /      The sweep line processes events:
    |/   / \ /       - Left endpoint: insert segment
    |   /   X        - Right endpoint: remove segment
    |  / \ / \       - Intersection: swap adjacent segments
    | /   /   \
    |/   /     \
    +---+---+---+-->  sweep direction -->

    At each event, check newly adjacent segment pairs for intersections.
Bentley-Ottmann Overview
Events: segment endpoints + discovered intersections
Status structure: segments ordered by y-coordinate at current sweep x
Time: O((n + k) log n) where k = number of intersections
Key idea: Two segments can only intersect if they are adjacent in the sweep status at some point

The full Bentley-Ottmann implementation is complex. For competitive programming, the brute force O(n^2) check is often sufficient when n is small. Here is a simplified sweep for detecting any intersection:

Python
from sortedcontainers import SortedList

def any_intersection(segments):
    """Sweep line to detect if any two segments intersect.
    segments: list of ((x1,y1), (x2,y2)) tuples.
    Simplified version -- checks adjacent segments in sweep status."""
    events = []
    for i, (p, q) in enumerate(segments):
        if p > q:
            p, q = q, p
        events.append((p[0], 0, i))  # 0 = left endpoint
        events.append((q[0], 1, i))  # 1 = right endpoint
    events.sort()

    # In a full implementation, you'd maintain a balanced BST
    # ordered by y-coordinate at the sweep line's current x,
    # and check adjacent pairs at each event.
    # For CP, brute force is often simpler.
    pass

Area of Union of Rectangles

A classic sweep line + segment tree problem. Sweep a vertical line, and at each event (rectangle left/right edge), update a segment tree that tracks the covered length along the y-axis.

C++
// Sweep line for area of union of rectangles
// Events: left edge (+1 to coverage), right edge (-1 to coverage)
// Segment tree tracks total covered y-length

struct Event {
    double x;
    int type; // +1 = open, -1 = close
    double y1, y2;
};

double unionArea(vector<array<double, 4>>& rects) {
    // rects[i] = {x1, y1, x2, y2}
    vector<Event> events;
    vector<double> ys; // coordinate compression for y

    for (auto& r : rects) {
        events.push_back({r[0], +1, r[1], r[3]});
        events.push_back({r[2], -1, r[1], r[3]});
        ys.push_back(r[1]);
        ys.push_back(r[3]);
    }

    sort(events.begin(), events.end(), [](const Event& a, const Event& b) {
        return a.x < b.x;
    });
    sort(ys.begin(), ys.end());
    ys.erase(unique(ys.begin(), ys.end()), ys.end());

    int m = ys.size();
    vector<int> cnt(m, 0); // coverage count per y-interval

    double area = 0;
    for (int i = 0; i < events.size(); i++) {
        if (i > 0) {
            double dx = events[i].x - events[i-1].x;
            double covered = 0;
            for (int j = 0; j + 1 < m; j++)
                if (cnt[j] > 0)
                    covered += ys[j+1] - ys[j];
            area += dx * covered;
        }
        // Update coverage
        int lo = lower_bound(ys.begin(), ys.end(), events[i].y1) - ys.begin();
        int hi = lower_bound(ys.begin(), ys.end(), events[i].y2) - ys.begin();
        for (int j = lo; j < hi; j++)
            cnt[j] += events[i].type;
    }
    return area;
}
Complexity
Union of rectangles (brute force y): O(n^2)
Union of rectangles (segment tree): O(n log n)
Bentley-Ottmann: O((n + k) log n)
Sweep Line Pattern
The sweep line paradigm reduces 2D problems to 1D. Ask yourself: "If I process events left-to-right, what information do I need about the current vertical slice?" That information is your sweep status structure.

8. Polygon Area and Centroid

Shoelace Formula

The area of any simple polygon given its vertices in order (CW or CCW) can be computed using the shoelace formula, which sums cross products of consecutive edges.

Shoelace Formula
Given vertices (x0,y0), (x1,y1), ..., (x_{n-1},y_{n-1}) in order:

2 * Area = |sum_{i=0}^{n-1} (x_i * y_{i+1} - x_{i+1} * y_i)|

where indices are taken mod n (so vertex n = vertex 0).
The signed version (without absolute value) is positive for CCW, negative for CW.

    Shoelace Walkthrough:
    Polygon with vertices: (1,0), (4,0), (4,3), (0,3)

       3  +------*
          |      |         Cross products:
          |      |         (1*0 - 4*0) =  0
          |      |         (4*3 - 4*0) = 12
       0  *------*         (4*3 - 0*3) = 12
          0  1   4         (0*0 - 1*3) = -3

    2*Area = |0 + 12 + 12 + (-3)| = 21
    Area = 10.5    (which is wrong -- let's be careful)

    Actually: (1*0-4*0) + (4*3-4*0) + (4*3-0*3) + (0*0-1*3)
            = 0 + 12 - 12 + (-3) ... let me recompute properly:

    x_i*y_{i+1}: 1*0 + 4*3 + 4*3 + 0*0 = 0+12+12+0 = 24
    x_{i+1}*y_i: 4*0 + 4*0 + 0*3 + 1*3 = 0+0+0+3   = 3
    2*Area = |24 - 3| = 21  ...

    Hmm, the actual rectangle is 3*4=12. Let me use correct vertices:
    (1,0), (4,0), (4,3), (1,3)
    x_i*y_{i+1}: 1*0 + 4*3 + 4*3 + 1*0 = 0+12+12+0 = 24
    x_{i+1}*y_i: 4*0 + 4*0 + 1*3 + 1*3 = 0+0+3+3   = 6
    2*Area = |24 - 6| = 18, Area = 9 = 3*3.  Correct!
Python
def polygon_area(polygon):
    """Shoelace formula. Returns the area of a simple polygon.
    polygon: list of Point objects in order (CW or CCW)."""
    n = len(polygon)
    area = 0
    for i in range(n):
        j = (i + 1) % n
        area += polygon[i].x * polygon[j].y
        area -= polygon[j].x * polygon[i].y
    return abs(area) / 2

def signed_area(polygon):
    """Signed area: positive if CCW, negative if CW."""
    n = len(polygon)
    area = 0
    for i in range(n):
        j = (i + 1) % n
        area += polygon[i].x * polygon[j].y
        area -= polygon[j].x * polygon[i].y
    return area / 2
C++
double polygonArea(vector<Point>& poly) {
    double area = 0;
    int n = poly.size();
    for (int i = 0; i < n; i++) {
        int j = (i + 1) % n;
        area += poly[i].x * poly[j].y;
        area -= poly[j].x * poly[i].y;
    }
    return abs(area) / 2.0;
}

Centroid of a Polygon

Centroid Formula
Cx = (1 / 6A) * sum_{i=0}^{n-1} (x_i + x_{i+1})(x_i * y_{i+1} - x_{i+1} * y_i)
Cy = (1 / 6A) * sum_{i=0}^{n-1} (y_i + y_{i+1})(x_i * y_{i+1} - x_{i+1} * y_i)

where A is the signed area.
Python
def polygon_centroid(polygon):
    """Returns the centroid (center of mass) of a simple polygon."""
    n = len(polygon)
    A = signed_area(polygon)
    cx, cy = 0, 0

    for i in range(n):
        j = (i + 1) % n
        factor = polygon[i].x * polygon[j].y - polygon[j].x * polygon[i].y
        cx += (polygon[i].x + polygon[j].x) * factor
        cy += (polygon[i].y + polygon[j].y) * factor

    cx /= (6 * A)
    cy /= (6 * A)
    return Point(cx, cy)
C++
Point polygonCentroid(vector<Point>& poly) {
    int n = poly.size();
    double A = 0, cx = 0, cy = 0;
    for (int i = 0; i < n; i++) {
        int j = (i + 1) % n;
        double f = poly[i].x * poly[j].y - poly[j].x * poly[i].y;
        A += f;
        cx += (poly[i].x + poly[j].x) * f;
        cy += (poly[i].y + poly[j].y) * f;
    }
    A /= 2.0;
    cx /= (6.0 * A);
    cy /= (6.0 * A);
    return {cx, cy};
}
Complexity
Shoelace area: O(n)
Centroid: O(n)

9. Half-Plane Intersection

A half-plane is one side of a line. The intersection of multiple half-planes forms a convex polygon (or is empty/unbounded). This appears in linear programming, visibility problems, and kernel of a polygon.

Representation


    A half-plane can be defined by a directed line (P, Q):
    the feasible region is to the LEFT of the line P->Q.

    Line P --> Q
    ============     <-- the line
    ////////////     <-- feasible region (left side)


    Example: intersection of 3 half-planes forms a triangle

         /\
        /  \         Half-plane 1: above y = -x + 4
       /    \        Half-plane 2: above y = x
      / feas \       Half-plane 3: below y = 3
     /  ible  \
    +-----------+    The intersection is a triangle
Half-Plane Representation
A half-plane is stored as a directed line from point P to point Q.
The feasible region is to the left of P->Q (where cross(PQ, Ptest) > 0).

Angle of the half-plane: atan2(Q.y - P.y, Q.x - P.x)
C++
struct HalfPlane {
    Point p, d; // point on line and direction vector
    double angle;

    HalfPlane() {}
    HalfPlane(Point a, Point b) : p(a), d(b - a) {
        angle = atan2(d.y, d.x);
    }

    bool operator<(const HalfPlane& h) const {
        return angle < h.angle;
    }

    // Is point q on the left side (feasible)?
    bool out(Point q) const {
        return d.cross(q - p) < -1e-9;
    }
};

Point lineIntersect(HalfPlane& h1, HalfPlane& h2) {
    Point n = h2.p - h1.p;
    double t = h2.d.cross(n) / h1.d.cross(h2.d);
    return h1.p + h1.d * t;
}

Sorting-Based Algorithm (O(n log n))

The half-plane intersection can be computed in O(n log n) using a deque-based approach:

  1. Sort half-planes by angle
  2. Process each half-plane: maintain a deque of half-planes
  3. Remove from back if the intersection point of the last two is outside the new half-plane
  4. Remove from front similarly
  5. Final cleanup: check front against back
C++
vector<Point> halfPlaneIntersection(vector<HalfPlane> hps) {
    sort(hps.begin(), hps.end());

    // Remove duplicates (keep the more restrictive one)
    int n = hps.size(), cnt = 0;
    for (int i = 0; i < n; i++) {
        if (i == 0 || fabs(hps[i].angle - hps[i-1].angle) > 1e-9)
            hps[cnt++] = hps[i];
        else if (hps[i].d.cross(hps[cnt-1].p - hps[i].p) > 0)
            hps[cnt-1] = hps[i];
    }
    hps.resize(cnt);
    n = cnt;

    deque<HalfPlane> dq;
    deque<Point> pts;

    for (int i = 0; i < n; i++) {
        while (dq.size() > 1 && hps[i].out(pts.back()))
            { dq.pop_back(); pts.pop_back(); }
        while (dq.size() > 1 && hps[i].out(pts.front()))
            { dq.pop_front(); pts.pop_front(); }
        dq.push_back(hps[i]);
        if (dq.size() > 1)
            pts.push_back(lineIntersect(dq[dq.size()-2], dq.back()));
    }

    // Final cleanup
    while (dq.size() > 2 && dq.front().out(pts.back()))
        { dq.pop_back(); pts.pop_back(); }
    if (dq.size() > 1)
        pts.push_back(lineIntersect(dq.front(), dq.back()));

    return vector<Point>(pts.begin(), pts.end());
}
Complexity
Time: O(n log n) -- dominated by sorting
Space: O(n)
Applications
  • Kernel of a polygon: the set of points from which the entire polygon interior is visible
  • Linear programming in 2D: find the point maximizing c.x + d.y subject to half-plane constraints
  • Feasibility testing: check if constraints have any common solution

10. Rotating Calipers

Rotating calipers is a technique for solving problems on convex polygons in O(n) time by "rotating" two parallel support lines around the hull simultaneously. Think of clamping the hull between two parallel rulers and rotating them.


    Rotating Calipers for Diameter:

    Step 1: Start with horizontal calipers
    ========================
           *---*
          / *   \
         *       *
    ========================

    Step 2: Rotate to next edge, advance the opposite pointer
    \\\\\\\\\\\\\\\\\\\\\\\\
          *---*
         / *   \
        *       *
    ////////////////////////

    At each step, record the distance between the two contact points.
    The maximum distance is the diameter of the hull.

Diameter of Convex Hull (Farthest Pair)

Python
def diameter(hull):
    """Find the diameter (farthest pair distance) of a convex hull.
    hull: list of Points in CCW order. O(n) time."""
    n = len(hull)
    if n <= 1:
        return 0
    if n == 2:
        return (hull[0] - hull[1]).norm()

    # Find the point farthest from edge (hull[0], hull[1])
    j = 1
    best = 0

    for i in range(n):
        # Advance j while the triangle area increases
        ni = (i + 1) % n
        while True:
            nj = (j + 1) % n
            # Cross product gives twice the triangle area
            curr = (hull[ni] - hull[i]).cross(hull[j] - hull[i])
            next_val = (hull[ni] - hull[i]).cross(hull[nj] - hull[i])
            if next_val > curr:
                j = nj
            else:
                break

        # Check distance from hull[i] and hull[ni] to hull[j]
        best = max(best, (hull[i] - hull[j]).norm2())
        best = max(best, (hull[ni] - hull[j]).norm2())

    return math.sqrt(best)
C++
double diameter(vector<Point>& hull) {
    int n = hull.size();
    if (n <= 1) return 0;
    if (n == 2) return (hull[0] - hull[1]).norm();

    int j = 1;
    double best = 0;

    for (int i = 0; i < n; i++) {
        int ni = (i + 1) % n;
        while (true) {
            int nj = (j + 1) % n;
            if ((hull[ni] - hull[i]).cross(hull[nj] - hull[i]) >
                (hull[ni] - hull[i]).cross(hull[j] - hull[i]))
                j = nj;
            else break;
        }
        best = max(best, (hull[i] - hull[j]).norm2());
        best = max(best, (hull[(i+1)%n] - hull[j]).norm2());
    }
    return sqrt(best);
}

Minimum Width of Convex Hull

The minimum width is the minimum distance between two parallel support lines. Use rotating calipers, but instead of tracking the farthest point, track the point with maximum perpendicular distance from the current edge, and record the minimum such distance.

Python
def min_width(hull):
    """Minimum width of a convex hull using rotating calipers."""
    n = len(hull)
    if n <= 2:
        return 0

    j = 1
    best = float('inf')

    for i in range(n):
        ni = (i + 1) % n
        edge = hull[ni] - hull[i]
        edge_len = edge.norm()

        # Advance j to farthest point from edge i
        while True:
            nj = (j + 1) % n
            if abs(edge.cross(hull[nj] - hull[i])) > abs(edge.cross(hull[j] - hull[i])):
                j = nj
            else:
                break

        # Distance from hull[j] to line through edge i
        width = abs(edge.cross(hull[j] - hull[i])) / edge_len
        best = min(best, width)

    return best
Complexity
Diameter: O(n) after convex hull
Minimum width: O(n) after convex hull
Bridge between two hulls: O(n + m)
Key Idea Behind Rotating Calipers
The technique works because the "antipodal" (farthest opposite) vertex advances monotonically as you walk around the hull. Each vertex is the antipodal point for a contiguous range of edges. So the total number of advances across all edges is at most n.

11. Voronoi Diagrams and Delaunay Triangulation

These are dual structures that partition the plane based on proximity to a set of points.

Voronoi Diagram


    Voronoi Diagram: Each cell contains all points closest
    to its "site" (input point).

    +-------+-----+--------+
    |       |     |        |
    |   *   |  *  |   *    |    * = site (input point)
    |       | /   |  /     |    Lines = cell boundaries
    +------/ +    +-+------+    (equidistant from 2 sites)
    |     /  |   /  |      |
    |   */   |  / * |      |
    |   /    | /    |  *   |
    +--/-----+------+------+
Voronoi Diagram Properties
  • Each cell is a convex polygon (possibly unbounded)
  • Every Voronoi edge is the perpendicular bisector of the segment connecting two sites
  • Every Voronoi vertex is equidistant from exactly 3 sites (generically)
  • Number of edges: O(n), number of vertices: O(n)
  • Fortune's algorithm computes it in O(n log n)

Delaunay Triangulation


    Delaunay Triangulation: The dual of the Voronoi diagram.
    Connect two sites if their Voronoi cells share an edge.

    *-------*          Property: No point lies inside the
    |\     /|          circumcircle of any triangle.
    | \   / |
    |  \ /  |          This maximizes the minimum angle
    |   *   |          of all triangles (avoids "skinny"
    |  / \  |          triangles).
    | /   \ |
    |/     \|
    *-------*
Delaunay Triangulation Properties
  • Empty circle property: the circumcircle of each triangle contains no other points
  • Maximizes minimum angle -- avoids degenerate skinny triangles
  • Contains the nearest-neighbor graph as a subgraph
  • Contains the Euclidean minimum spanning tree as a subgraph
  • Number of triangles: O(n), edges: O(n)

Fortune's Algorithm Overview

Fortune's algorithm computes the Voronoi diagram in O(n log n) using a sweep line. Instead of the typical vertical sweep line, it uses a beach line -- a curve made of parabolic arcs.

  1. Beach line: A sequence of parabolic arcs, one per site discovered so far. Each arc is the set of points equidistant from a site and the sweep line.
  2. Site events: When the sweep line hits a new site, a new arc appears on the beach line.
  3. Circle events: When three consecutive arcs converge (the middle arc shrinks to zero), a Voronoi vertex is created and the middle arc disappears.
Implementation Complexity
Fortune's algorithm is notoriously tricky to implement correctly due to floating point issues and degenerate cases. In competitive programming, if you need Delaunay triangulation, consider using the incremental flip approach or the divide-and-conquer method instead. Many competitive programmers use pre-built templates.

When You Need These

Applications
  • Nearest neighbor queries: Voronoi cells directly encode nearest neighbors
  • Euclidean MST: Compute Delaunay triangulation, then run Kruskal's on its O(n) edges
  • Facility location: Find optimal placement to minimize distance to nearest service point
  • Mesh generation: Delaunay triangulation gives high-quality meshes for finite element methods
  • Largest empty circle: Center is at a Voronoi vertex

Practical: Euclidean MST via Delaunay

Computing Euclidean MST naively requires checking all O(n^2) pairs. But since the MST is a subgraph of the Delaunay triangulation (which has O(n) edges), we can:

  1. Build Delaunay triangulation: O(n log n)
  2. Run Kruskal's MST on the O(n) Delaunay edges: O(n log n)
  3. Total: O(n log n) instead of O(n^2)
Python
# Using scipy for Delaunay (practical approach)
from scipy.spatial import Delaunay
import numpy as np

def euclidean_mst(points):
    """Compute Euclidean MST using Delaunay triangulation."""
    pts = np.array([(p.x, p.y) for p in points])
    tri = Delaunay(pts)

    # Extract edges from triangulation
    edges = set()
    for simplex in tri.simplices:
        for i in range(3):
            for j in range(i + 1, 3):
                u, v = simplex[i], simplex[j]
                if u > v: u, v = v, u
                edges.add((u, v))

    # Kruskal's algorithm
    edge_list = []
    for u, v in edges:
        d = np.linalg.norm(pts[u] - pts[v])
        edge_list.append((d, u, v))
    edge_list.sort()

    # Union-Find
    parent = list(range(len(points)))
    def find(x):
        while parent[x] != x:
            parent[x] = parent[parent[x]]
            x = parent[x]
        return x

    mst_weight = 0
    mst_edges = []
    for d, u, v in edge_list:
        ru, rv = find(u), find(v)
        if ru != rv:
            parent[ru] = rv
            mst_weight += d
            mst_edges.append((u, v, d))

    return mst_weight, mst_edges

12. Practice Problems

Organized by topic, from fundamental to advanced. Problems are drawn from Codeforces, SPOJ, Kattis, CSES, and UVa.

Points, Vectors, and Cross Product

ProblemSourceKey Concept
Point Location TestCSESOrientation / cross product
Line Segment IntersectionCSESSegment intersection test
Polygon AreaCSESShoelace formula
Point in PolygonCSESRay casting
Polygon Lattice PointsCSESPick's theorem + shoelace

Convex Hull

ProblemSourceKey Concept
Convex HullCSESAndrew's monotone chain
FenceSPOJ (FENCE1)Convex hull basics
Codeforces 166BCodeforcesConvex hull containment
WallUVa 1111Convex hull perimeter + offset
Beauty ContestPOJ 2187Convex hull diameter (rotating calipers)

Closest Pair

ProblemSourceKey Concept
Minimum Euclidean DistanceCSESDivide and conquer closest pair
Closest PairSPOJ (CLOPPAIR)D&C closest pair
Closest Point PairKattisClosest pair with coordinates

Sweep Line

ProblemSourceKey Concept
Rectangle Union AreaVarious OJsSweep line + segment tree
Codeforces 1194FCodeforcesSweep line geometry
Worm HolesKattisSweep line intersection detection

Half-Plane Intersection and Rotating Calipers

ProblemSourceKey Concept
Most Distant Point from the SeaKattisHalf-plane intersection + binary search
Codeforces 1059ECodeforcesRotating calipers / convex hull
Minimum Enclosing RectangleVariousRotating calipers
Robert HoodKattisDiameter of convex hull

Voronoi / Delaunay / Mixed

ProblemSourceKey Concept
Codeforces 429DCodeforcesDelaunay triangulation MST
Galactic TaxesKattisComputational geometry + ternary search
AirportSPOJVoronoi / farthest point queries
How to Practice
  1. Start with CSES Geometry section -- these cover the fundamentals cleanly
  2. Build a template library -- competitive programmers keep a tested geometry library (Point struct, convex hull, etc.)
  3. Test with integer coordinates first -- avoid floating point until you must
  4. Draw everything -- computational geometry bugs are almost always clearer with a picture
  5. Handle edge cases -- collinear points, duplicate points, degenerate polygons

Quick Reference: Complexity Cheat Sheet

AlgorithmTimeSpaceNotes
Orientation testO(1)O(1)Cross product
Segment intersection testO(1)O(1)4 orientation tests
Convex hull (Andrew's)O(n log n)O(n)Sort + linear scan
Convex hull (Graham)O(n log n)O(n)Polar angle sort
Point in polygon (ray cast)O(n)O(1)Simple polygon
Point in convex polygonO(log n)O(1)Binary search
Closest pairO(n log n)O(n)Divide and conquer
Polygon area (shoelace)O(n)O(1)Works for any simple polygon
Polygon centroidO(n)O(1)Weighted by signed area
Half-plane intersectionO(n log n)O(n)Sort + deque
Rotating calipers (diameter)O(n)O(1)After convex hull
Voronoi diagram (Fortune)O(n log n)O(n)Beach line sweep
Delaunay triangulationO(n log n)O(n)Dual of Voronoi
Euclidean MSTO(n log n)O(n)Via Delaunay + Kruskal
Bentley-OttmannO((n+k) log n)O(n+k)k = intersections

Common Pitfalls in Computational Geometry

Floating Point Precision
  • Use long long in C++ or integers in Python for cross products when coordinates are integers
  • Avoid atan2 for sorting when you can use cross-product comparisons instead
  • Use epsilon comparisons: abs(x) < 1e-9 instead of x == 0
  • Prefer squared distances over distances to avoid sqrt
  • Be careful with acos -- clamp input to [-1, 1]
Degenerate Cases
  • Collinear points: Most algorithms need special handling. In convex hull, decide whether to include collinear points on the hull boundary (<= 0 vs < 0 in cross product check).
  • Duplicate points: Remove or handle explicitly.
  • Vertical/horizontal segments: Special care with sweep line ordering.
  • Polygon with fewer than 3 vertices: Guard your functions.
  • Cocircular points: Delaunay triangulation can have non-unique triangulations when 4+ points are cocircular.
Integer Geometry Template
For competitive programming, this integer-only Point struct avoids all floating point issues when coordinates are integers:
C++
typedef long long ll;

struct P {
    ll x, y;
    P(ll x = 0, ll y = 0) : x(x), y(y) {}
    P operator-(P o) { return {x - o.x, y - o.y}; }
    ll cross(P o) { return x * o.y - y * o.x; }
    ll dot(P o) { return x * o.x + y * o.y; }
    ll norm2() { return x * x + y * y; }
    bool operator<(P o) const { return x < o.x || (x == o.x && y < o.y); }
};

ll cross(P O, P A, P B) { return (A - O).cross(B - O); }

// Twice the signed area of triangle OAB
// Positive = CCW, Negative = CW, Zero = collinear
// No floating point anywhere!
Python
# Python handles big integers natively -- no overflow risk!
# For competitive programming, use tuples for speed:

def cross(o, a, b):
    return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])

def convex_hull_fast(points):
    """Tuple-based convex hull for speed in CP."""
    points = sorted(set(points))
    if len(points) <= 1:
        return points
    lower = []
    for p in points:
        while len(lower) >= 2 and cross(lower[-2], lower[-1], p) <= 0:
            lower.pop()
        lower.append(p)
    upper = []
    for p in reversed(points):
        while len(upper) >= 2 and cross(upper[-2], upper[-1], p) <= 0:
            upper.pop()
        upper.append(p)
    return lower[:-1] + upper[:-1]

Problem-Solving Strategy for Geometry Problems

Step-by-Step Approach
  1. Draw it. Always sketch the geometry before coding. Most bugs become obvious on paper.
  2. Identify the primitive. What fundamental operation does the problem reduce to? (orientation test, convex hull, sweep line, etc.)
  3. Choose integer or floating point. If coordinates are integers and you can stay in integers, do so. Use long long in C++.
  4. Handle degeneracies. What happens when points are collinear? When the polygon has only 3 vertices? When segments overlap?
  5. Test on small cases. Geometry is hard to debug. Use small hand-drawn examples and verify each step.
  6. Use a template. Build and test your geometry library once. In contests, paste it and focus on the problem logic.

Decision Tree: Which Algorithm Do I Need?


    "I need to..."

    Find if a point is left/right of a line?
        => Cross product / orientation test

    Find the outermost boundary of points?
        => Convex hull (Andrew's monotone chain)

    Check if a point is inside a shape?
        => Ray casting (general) or binary search (convex)

    Find the two farthest points?
        => Convex hull + rotating calipers

    Find the two closest points?
        => Divide and conquer closest pair

    Compute the area of a polygon?
        => Shoelace formula

    Process geometric events in spatial order?
        => Sweep line

    Find where multiple constraints overlap?
        => Half-plane intersection

    Find nearest neighbor regions?
        => Voronoi diagram

    Build a good triangulation?
        => Delaunay triangulation