C#
point in polygon
computational geometry
programming
algorithms

C Point in polygon

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The point-in-polygon problem asks whether a 2D point lies inside, outside, or exactly on the boundary of a polygon. In C#, the most practical general-purpose solution is usually the ray-casting test, with an explicit boundary check added so edge cases do not get misclassified.

The Core Idea Behind Ray Casting

Imagine shooting a horizontal ray to the right from the test point. Count how many times that ray crosses polygon edges.

  • an odd number of crossings means the point is inside
  • an even number means the point is outside

This works for simple polygons because every time the ray crosses the boundary, it toggles between inside and outside.

A Runnable C# Implementation

The following example uses a small Point2D struct and a ray-casting function. It also checks whether the point lies exactly on an edge.

csharp
1using System;
2
3public readonly record struct Point2D(double X, double Y);
4
5public static class Geometry
6{
7    public static bool IsPointInPolygon(Point2D point, Point2D[] polygon)
8    {
9        bool inside = false;
10
11        for (int i = 0, j = polygon.Length - 1; i < polygon.Length; j = i++)
12        {
13            Point2D a = polygon[i];
14            Point2D b = polygon[j];
15
16            if (IsPointOnSegment(point, a, b))
17            {
18                return true;
19            }
20
21            bool intersects = ((a.Y > point.Y) != (b.Y > point.Y)) &&
22                              (point.X < (b.X - a.X) * (point.Y - a.Y) / (b.Y - a.Y) + a.X);
23
24            if (intersects)
25            {
26                inside = !inside;
27            }
28        }
29
30        return inside;
31    }
32
33    private static bool IsPointOnSegment(Point2D p, Point2D a, Point2D b)
34    {
35        double cross = (p.Y - a.Y) * (b.X - a.X) - (p.X - a.X) * (b.Y - a.Y);
36        if (Math.Abs(cross) > 1e-9)
37        {
38            return false;
39        }
40
41        double dot = (p.X - a.X) * (b.X - a.X) + (p.Y - a.Y) * (b.Y - a.Y);
42        if (dot < 0)
43        {
44            return false;
45        }
46
47        double squaredLength = (b.X - a.X) * (b.X - a.X) + (b.Y - a.Y) * (b.Y - a.Y);
48        return dot <= squaredLength;
49    }
50}
51
52public class Program
53{
54    public static void Main()
55    {
56        Point2D[] polygon =
57        {
58            new(0, 0),
59            new(5, 0),
60            new(5, 5),
61            new(0, 5)
62        };
63
64        Console.WriteLine(Geometry.IsPointInPolygon(new Point2D(2, 2), polygon));
65        Console.WriteLine(Geometry.IsPointInPolygon(new Point2D(6, 2), polygon));
66        Console.WriteLine(Geometry.IsPointInPolygon(new Point2D(5, 3), polygon));
67    }
68}

This prints True, False, and True. The last result counts a boundary point as inside because the code explicitly checks edges first.

Why The Boundary Check Matters

A plain ray-casting implementation often produces inconsistent answers for points that sit exactly on an edge or vertex. That is not a flaw in the idea of ray casting; it is a sign that the implementation skipped a policy decision.

In real programs, choose one rule and document it:

  • boundary counts as inside
  • boundary counts as outside
  • boundary is a separate result

The example above treats boundary as inside because that is often convenient in UI and GIS-style applications.

Handling Vertices Carefully

Vertices are where many buggy implementations fail. If the ray passes exactly through a polygon vertex, naïve code may count the crossing twice.

The condition used in the example:

csharp
((a.Y > point.Y) != (b.Y > point.Y))

helps avoid double counting by only considering edges that straddle the point's horizontal line in a controlled way. This is a standard trick in robust ray-casting implementations.

Concave Polygons Are Fine

Ray casting works for both convex and concave simple polygons. It does not require the polygon to be a rectangle or any other easy shape.

For example, this polygon is concave:

csharp
1Point2D[] polygon =
2{
3    new(0, 0),
4    new(4, 0),
5    new(4, 4),
6    new(2, 2),
7    new(0, 4)
8};

The same algorithm still works as long as the polygon is simple, meaning its edges do not cross each other.

When Winding Number Is Better

Another well-known approach is the winding-number algorithm. It tracks how many times the polygon winds around the point instead of just counting parity.

For ordinary inside-versus-outside checks, ray casting is simpler and usually enough. Winding number becomes attractive when:

  • you want a more geometric orientation-based formulation
  • you are working with polygons whose orientation matters
  • you want finer control over complex shape semantics

For many business applications, ray casting plus a clear boundary policy is the best balance of simplicity and correctness.

Common Pitfalls

  • Ignoring points that fall exactly on an edge or vertex.
  • Double counting intersections at polygon vertices.
  • Assuming the algorithm only works for convex polygons.
  • Feeding in self-intersecting polygons and expecting simple inside-outside semantics.
  • Comparing floating-point values with exact equality instead of using a tolerance.

Summary

  • In C#, ray casting is a practical solution to the point-in-polygon problem.
  • Count horizontal-ray crossings and add an explicit boundary check.
  • Concave polygons are fine as long as the polygon is simple.
  • Vertex handling is where many implementations go wrong.
  • Decide whether boundary points count as inside, outside, or separate, and code that policy explicitly.

Course illustration
Course illustration

All Rights Reserved.