Circle vs Polygon Collision Detection in C#

Here is an efficient 2D algorithm in C# for circle/polygon intersection useful for collision detection in games.

A lot of explanations start of by introducing the idea of SAT (separating axis theorem), however, it's not necessary to understand SAT for efficient circle/polygon intersection. My approach uses SAT, but the explanation will be obvious without in depth knowledge of SAT (which is more complicated for polygon/polygon collisions).

The algorithm presented here only needs to iterate through each vertex in the polygon once to decide if a circle has collided with a polygon. Giving an algorithm of \(O(n)\) complexity. Like most vector game algorithms, it applies only to concave polygons and assumes a counter clockwise winding order.

Concepts Needed for Efficient Circle vs Polygon Intersection

The concepts you will need to understand this circle/polygon intersection algorithm for collision detection are as follows:

  • Voroni regions
  • Minimum distance of a point to a line by taking the perpendicular from the point to the line.
  • Knowing which side of a line a perpendicular protrudes from if they are both centred around the origin.

Voroni Regions for Circle vs Polygon Intersection

The following diagram shows a convex polygon and its corresponding Voroni regions. To create the Voroni regions, lines are drawn perpendicular (at right angles) from the end of each edge of length\(r\) (where \(r\) is the radius of the circle we want to test for intersection (they are not normally limited to \(r\) in this way, but it's how we're going to think of them).

We can define the following two types of region:

Edge region - the centre of the circle lies within distance \(r\) of a single edge (depicted by the boxes adjacent to each edge in diagram).

Vertex region - the centre of the circle lies outside an edge region but is distance \(r\) from a vertex (depicted by the corner segments at each vertex in diagram). Another way of describing a vertex region is to say that the circle lies within distance \(r\) of two edges.

Circle vs Polygon Intersection Algorithm

If we loop through each vertex once we can check the following things:

  • If the distance from the vertex to the centre of the circle less than the radius \(r\) of the circle, the circle and polygon intersect.
  • If the circle is within an edge region and a line perpendicular from the edge to the centre of the circle is less than the radius \(r\) of the circle, the circle and polygon intersect.
  • If there exists a perpendicular from the circle's centre to a single edge, then if the circle does not intersect the edge and the circle's centre is to the right of the edge, no more checks need to be performed to conclude that the circle does not intersect (and is outside the polygon).
  • The circle is inside the polygon if a perpendicular can be drawn from the circle to either edge joining it's closest vertex and the circle's centre is not to the right of either of these two edges.

If we do these checks carefully, it is possible to stop before we have looped through all the vertices of the polygon. We can also perform these checks from only a very few shared calculations.</p>

Circle vs Polygon Collision Testing in C#

The following algorithm first checks to see if the circle is within radius \(r\) of each vertex (the square of the distances is used to avoid unnecessary square roots). If that test fails, it then checks if the circle's centre is within the Voroni edge region defined earlier. If it is then it checks the perpendicular distance from the edge to the circle's centre is less than \(r\). If this check fails, it determines if the circle lies within the edge's perpendiculars to the left or right of the edge. If to the right, the point is outside the polygon and the algorithm is done. Along the way, the algorithm keeps track of the closest vertex so that at the end of the checks, it can determine if the circle is in fact inside the polygon by making use of the fact that the circle will be to the left of at least one of the two edges of the nearest vertex if it is inside the polygon.

public class Polygon
{
    private Vector[] _points;

    public Polygon(Vector[] points)
    {
      _points = points;
    }

    public bool Intesects(Circle circle)
    {
      float radiusSquared = circle.Radius * circle.Radius;

    Vector vertex = _points[_points.Length - 1];

    Vector circleCenter = circle.Position;

    float nearestDistance = Single.MaxValue;
    bool nearestIsInside = false;
    int nearestVertex = -1;
    bool lastIsInside = false;

    for (int i = 0; i < _points.Length; i++)
    {
      Vector nextVertex =_points[i];

      Vector axis = circleCenter - vertex;

      float distance = axis.LengthSquared() - radiusSquared;

      if (distance <= 0)
      {
        return true;
      }

      bool isInside = false;

      Vector edge = nextVertex - vertex;

      float edgeLengthSquared = edge.LengthSquared();

      if (edgeLengthSquared != 0)
      {
        float dot = edge.Dot(axis);

        if (dot >= 0 && dot <= edgeLengthSquared)
        {
          Vector projection = vertex + (dot / edgeLengthSquared) * edge;

          axis = projection - circleCenter;

          if (axis.LengthSquared() <= radiusSquared)
          {
            return true;
          }
          else
          {
            if (edge.X > 0)
            {
              if (axis.Y > 0)
              {
                return false;
              }
          }
          else if (edge.X < 0)
          {
            if (axis.Y < 0)
            {
              return false;
            }
          }
          else if (edge.Y > 0)
          {
            if (axis.X < 0)
            {
              return false;
            }
          }
          else
          {
            if (axis.X > 0)
            {
              return false;
            }
          }

          isInside = true;
        }
      }
    }

    if (distance < nearestDistance)
    {
      nearestDistance = distance;
      nearestIsInside = isInside || lastIsInside;
      nearestVertex = i;
    }

      vertex = nextVertex;
      lastIsInside = isInside;
    }

    if (nearestVertex == 0)
    {
      return nearestIsInside || lastIsInside;
    }
    else
    {
      return nearestIsInside;
    }
  }
}
The circle representation:

public class Circle
{
  public Vector Position { get; set; }

  public float Radius { get; set; }
}
The 2D vector:

public class Vector
{
  public float X { get; set; }
  public float Y { get; set; }

  public float LengthSquared()
  {
    return X * X + Y * Y;
  }

  public static Vector operator +(Vector a, Vector b)
  {
    return new Vector { X = a.X + b.X, Y = a.Y + b.Y};
  }

  public static Vector operator -(Vector a, Vector b)
  {
    return new Vector { X = a.X - b.X, Y = a.Y - b.Y};
  }

  public static Vector operator *(float s, Vector v)
  {
    return new Vector { X = v.X * s, Y = v.Y * s };
  }

  public float Dot(Vector v)
  {
    return X * v.X + Y * v.Y;
  }
}

Alternative Method Using Closest Vertex The intersection can also be derived by first finding the closest vertex and then checking if the edge either side of the vertex is outside (the code is very similar, just rearranged slightly). This yields a better worst case timing and a slower best case timing.

public class Polygon
{
  private Vector[] _points;

  public Polygon(Vector[] points)
  {
    _points = points;
  }

  public bool Intesects(Circle circle)
  {
    float radiusSquared = circle.Radius * circle.Radius;

    Vector vertex = _points[_points.Length - 1];

    Vector circleCenter = circle.Position;

    float nearestDistance = Single.MaxValue;
    int nearestVertex = -1;

    for (var i = 0; i < _points.Length; i++)
    {
      Vector axis = circleCenter - _points[i];

      var distance = axis.LengthSquared() - radiusSquared;

      if (distance <= 0)
      {
        return true;
      }
      else if (distance < nearestDistance)
      {
        nearestVertex = i;
        nearestDistance = distance;
      }
    }

    vertex = this[nearestVertex - 1];

    for (var i = 0; i < 2; i++)
    {
      var nextVertex = this[nearestVertex + i];

      var edge = nextVertex - vertex;

      var edgeLengthSquared = edge.LengthSquared();

      if (edgeLengthSquared != 0)
      {
        Vector axis = circleCenter - vertex;

        var dot = edge.Dot(axis);

        if (dot >= 0 && dot <= edgeLengthSquared)
        {
          Vector projection = vertex + (dot / edgeLengthSquared) * edge;

          axis = projection - circleCenter;

          if (axis.LengthSquared() <= radiusSquared)
          {
            return true;
          }
          else
          {
            if (edge.X > 0)
            {
              if (axis.Y > 0)
              {
                return false;
              }
            }
            else if (edge.X < 0)
            {
              if (axis.Y < 0)
              {
                return false;
              }
            }
            else if (edge.Y > 0)
            {
              if (axis.X < 0)
              {
                return false;
              }
            }
            else
            {
              if (axis.X > 0)
              {
                return false;
              }
            }
          }
        }
      }

      vertex = nextVertex;
    }

    return true;
  }

  private Vector this[int index]
  {
    get
    {
      if (index < 0)
      {
        index += _points.Length;
      }
      else if (index >= _points.Length)
      {
        index -= _points.Length;
      }

      return _points[index];
    }
  }
}

This is a better arrangement if you you want to get the incident face and distance of least penetration. Rather than return immediately in the special case that the circle overlaps a vertex, just find the closest. Then in the second loop, store the closest face (if there is one), then before exiting right at the end, if an edge was found, the incident face is the edge (the distance of least penetration would be the length of the $axis$ minus $r$ in both cases), otherwise, the incident face is perpendicular to the axis from the vertex to the circle's centre.