<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. DSA
  3. Master level
All lessons in this course

Lesson 48 of 50 16 minMaster

Computational geometry basics

The cross product as the one tool behind most geometry code: left or right turns, segment intersection, polygon area with the shoelace formula, point in polygon, and the convex hull in O(n log n).

Site plans, CAD drawings, maps, games and robots all need geometry: do two walls cross, how big is this plot, is this point inside the boundary, what is the shortest fence around these pegs? Almost all of it is built on one operation, the cross product, and on one rule: avoid floating-point division when you can, because rounding errors make “exactly on the line” impossible to test.

Left, right or straight: the cross product

For three points O, A, B, the cross product of the vectors OA and OB is (A.X − O.X)·(B.Y − O.Y) − (A.Y − O.Y)·(B.X − O.X). Its sign says which way you turn when walking O → A → B: positive means left (counter-clockwise), negative means right (clockwise), zero means the three points are on one line. Its size is twice the area of the triangle OAB. With integer coordinates it is exact.

Orientation.cs
using Pt = (long X, long Y); Pt a = (0, 0), b = (10, 0);                          // a wall from A to BConsole.WriteLine(Turn(a, b, (5, 4)));Console.WriteLine(Turn(a, b, (5, -3)));Console.WriteLine(Turn(a, b, (20, 0))); static long Cross(Pt o, Pt a, Pt b) => (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X); static string Turn(Pt o, Pt a, Pt b) => Cross(o, a, b) switch{    > 0 => "left (counter-clockwise)",    < 0 => "right (clockwise)",    _ => "straight (collinear)"};
Output
left (counter-clockwise)
right (clockwise)
straight (collinear)

The using Pt = (long X, long Y); line (C# 12) gives the tuple type a short name. long keeps the products from overflowing for coordinates up to about a billion.

Do two segments cross?

Segments P1P2 and Q1Q2 cross properly when P1 and P2 lie on opposite sides of the line through Q1Q2, and Q1 and Q2 lie on opposite sides of the line through P1P2: four cross products with opposite signs. If a cross product is zero, a point lies on the other line, and a simple bounding-box check tells whether it is actually on the segment (touching at an end, or overlapping on one line).

Intersect.cs
using Pt = (long X, long Y); Console.WriteLine(Intersects((0, 0), (10, 10), (0, 10), (10, 0)));   // an XConsole.WriteLine(Intersects((0, 0), (10, 0), (0, 5), (10, 5)));     // parallelConsole.WriteLine(Intersects((0, 0), (10, 0), (10, 0), (10, 8)));    // touching at an endConsole.WriteLine(Intersects((0, 0), (4, 0), (6, 0), (10, 0)));      // same line, with a gap static long Cross(Pt o, Pt a, Pt b) => (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X); static bool OnSegment(Pt p, Pt q, Pt r) =>           // r is collinear with pq: is it between them?    Math.Min(p.X, q.X) <= r.X && r.X <= Math.Max(p.X, q.X) &&    Math.Min(p.Y, q.Y) <= r.Y && r.Y <= Math.Max(p.Y, q.Y); static bool Intersects(Pt p1, Pt p2, Pt q1, Pt q2){    long d1 = Cross(q1, q2, p1), d2 = Cross(q1, q2, p2);    long d3 = Cross(p1, p2, q1), d4 = Cross(p1, p2, q2);    if (Math.Sign(d1) * Math.Sign(d2) < 0 && Math.Sign(d3) * Math.Sign(d4) < 0) return true;   // proper crossing    return (d1 == 0 && OnSegment(q1, q2, p1)) || (d2 == 0 && OnSegment(q1, q2, p2))        || (d3 == 0 && OnSegment(p1, p2, q1)) || (d4 == 0 && OnSegment(p1, p2, q2));}
Output
True
False
True
False

Area of a polygon: the shoelace formula

List the corners of a polygon in order. For each edge from corner a to corner b add a.X·b.Y − b.X·a.Y (the cross product of the two position vectors). Half the absolute total is the area, for any simple polygon, convex or not. The sign of the total even tells you the direction: positive for counter-clockwise corners.

Area.cs
using Pt = (long X, long Y); Pt[] plot = [(0, 0), (40, 0), (40, 20), (20, 20), (20, 50), (0, 50)];   // an L-shaped plot, metresConsole.WriteLine($"Area: {Area(plot)} square metres"); static double Area(Pt[] poly){    long twice = 0;    for (int i = 0; i < poly.Length; i++)    {        Pt a = poly[i], b = poly[(i + 1) % poly.Length];        twice += a.X * b.Y - b.X * a.Y;              // doubled signed area of the triangle (origin, a, b)    }    return Math.Abs(twice) / 2.0;}
Output
Area: 1400 square metres

Is a point inside a polygon?

Cast a ray from the point to the right and count how many polygon edges it crosses. An odd count means inside, even means outside (every time the ray crosses the boundary it switches between inside and outside). This ray casting test is O(n) for n edges and works for concave shapes too.

PointInPolygon.cs
using Pt = (double X, double Y); Pt[] plot = [(0, 0), (40, 0), (40, 20), (20, 20), (20, 50), (0, 50)];foreach (Pt p in new Pt[] { (10, 40), (30, 40), (30, 10) })    Console.WriteLine($"{p}: {(Inside(plot, p) ? "inside" : "outside")}"); static bool Inside(Pt[] poly, Pt p){    bool inside = false;    for (int i = 0, j = poly.Length - 1; i < poly.Length; j = i++)    {        Pt a = poly[i], b = poly[j];        if ((a.Y > p.Y) != (b.Y > p.Y) &&                          // the edge spans the ray's height            p.X < (b.X - a.X) * (p.Y - a.Y) / (b.Y - a.Y) + a.X)    // and crosses it to the right of p            inside = !inside;    }    return inside;}
Output
(10, 40): inside
(30, 40): outside
(30, 10): inside

The convex hull

The convex hull of a set of points is the smallest convex polygon containing them all: the shape of a rubber band stretched around pegs. It is the shortest fence, the outline of a point cloud, and the first step of many other algorithms (farthest pair, collision shapes, minimum bounding box).

Andrew’s monotone chain sorts the points by x, then walks left to right building the lower edge. Each new point is checked against the last two points kept: if the turn is not a left turn, the last point lies inside the hull and is removed (perhaps several times). Then the same walk from right to left builds the upper edge.

Interactive

Fencing the site: convex hull

Find the shortest fence around all the survey pegs. Every step asks one question: do these three pegs turn left or right? Click the plan to add pegs.

123456789101112
Pegs 12 · step 1 of 42
Sort the 12 pegs from west to east (numbers show the order). Build the lower edge first, walking left to right.
ConvexHull.cs
using Pt = (int X, int Y); Pt[] pegs = [(30, 60), (70, 230), (120, 140), (150, 40), (160, 260), (200, 120),             (230, 210), (260, 70), (270, 170), (100, 90), (190, 190), (60, 150)];var hull = ConvexHull(pegs);Console.WriteLine(string.Join(" ", hull));Console.WriteLine($"{hull.Count} of {pegs.Length} pegs"); static long Cross(Pt o, Pt a, Pt b) => (long)(a.X - o.X) * (b.Y - o.Y) - (long)(a.Y - o.Y) * (b.X - o.X); static List<Pt> ConvexHull(Pt[] points){    var p = points.OrderBy(q => q.X).ThenBy(q => q.Y).ToArray();    if (p.Length < 3) return p.ToList();    var h = new List<Pt>();    foreach (var q in p)                                         // lower edge, west to east    {        while (h.Count >= 2 && Cross(h[^2], h[^1], q) <= 0) h.RemoveAt(h.Count - 1);        h.Add(q);    }    int lower = h.Count;    for (int i = p.Length - 2; i >= 0; i--)                      // upper edge, east to west    {        while (h.Count > lower && Cross(h[^2], h[^1], p[i]) <= 0) h.RemoveAt(h.Count - 1);        h.Add(p[i]);    }    h.RemoveAt(h.Count - 1);                                     // the first point came round again    return h;}
Output
(30, 60) (150, 40) (260, 70) (270, 170) (230, 210) (160, 260) (70, 230)
7 of 12 pegs

Sorting costs O(n log n). In the walk every point is added once and removed at most once, so it is O(n). The <= 0 removes collinear points too; use < 0 to keep them on the fence.

ProblemToolTime
Left or right turnCross product signO(1)
Do two segments cross?Four cross productsO(1)
Polygon areaShoelace formulaO(n)
Point in polygonRay castingO(n)
Convex hullMonotone chainO(n log n)
Any two of n segments cross?Sweep line (Bentley-Ottmann)O((n + k) log n)
Closest pair of pointsDivide and conquer, or a gridO(n log n)
Quick check

The cross product for O → A → B is −12. What does it tell you?

Quick check

A ray from a point crosses the polygon boundary 4 times. Where is the point?

Practice

Write IsConvex for a polygon given in order: it is convex when all turns along the boundary go the same way. The square should print True, the L-shaped plot False.

Show a hint

Compute the cross product at every corner (corner i, i + 1, i + 2, wrapping around). Convex means you never see both a positive and a negative value.

Show the solution
Solution.cs
using Pt = (long X, long Y); Pt[] square = [(0, 0), (10, 0), (10, 10), (0, 10)];Pt[] plot = [(0, 0), (40, 0), (40, 20), (20, 20), (20, 50), (0, 50)];Console.WriteLine(IsConvex(square));Console.WriteLine(IsConvex(plot)); static bool IsConvex(Pt[] poly){    bool left = false, right = false;    for (int i = 0; i < poly.Length; i++)    {        Pt a = poly[i], b = poly[(i + 1) % poly.Length], c = poly[(i + 2) % poly.Length];        long cr = (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X);        if (cr > 0) left = true;        if (cr < 0) right = true;    }    return !(left && right);                         // every turn the same way}
Output
True
False

Remember

  • The cross product’s sign gives the turn direction (left, right, straight); its size is twice a triangle’s area.
  • Segment intersection, polygon area (shoelace) and point-in-polygon (ray casting) are all built on it.
  • Andrew’s monotone chain finds the convex hull in O(n log n): sort, then pop points that do not turn left.
  • Prefer integer coordinates; with doubles, compare against a tolerance instead of zero.
0 of 50 lessons completed