Search code examples
c++geometrycollision-detection

Circle collision with compound object


I would like to do a collision detection between circle and section of a circular ring. The circle is defined by it's position position and it's radius. The other object is defined by inner and outer radius and then a startPoint and endPoint both [x, y] points.

In the examples below, this is the circle and other is the ring section.

First I just check if it's colliding with the full ring. This works without a problem.

float mag = this.position.Magnitude();
if (mag < other.InnerRadius() - this.radius ||
    mag > other.OuterRadius() + this.radius) {
    return false;
}

But then I need to check if the circle is inside or outside of the section defined by the two points. Closest I was able to get was to check if it isn't colliding with the start and end vectors, but this returns wrong results when the circle is fully inside the ring section.

auto dot1 = Vector::Dot(position, other.StartPoint());
auto projected1 = dot1 / Vector::Dot(other.StartPoint(), other.StartPoint()) * other.StartPoint();
auto distance1 = Vector::Distance(position, projected1);

auto dot2 = Vector::Dot(position, other.EndPoint());
auto projected2 = dot2 / Vector::Dot(other.EndPoint(), other.EndPoint()) * other.EndPoint();
auto distance2 = Vector::Distance(position, projected2);

return distance1 < radius || distance2 < radius;

What is the easiest way to check if a circle is colliding with a object defined by these two vectors?

enter image description here

Edit: all the point objects I'm using here are my custom Vector class that has implemented all the vector operations.

Edit2: just to clarify, the ring object has it's origin in [0, 0]


Solution

  • Here is a simple algorithm.

    First, let's agree on variable names:

    meow

    Here r1 ≤ r2, -π/2 ≤ a1 ≤ a2 ≤ π/2.

    (As I was reminded in comments, you have start and end points rather than angles, but I'm going to use angles as they seem more convenient. You can easily obtain angles from points via atan2(y-ry, x-rx), just make sure that a1 ≤ a2. Or you can rewrite the algorithm to not use angles at all.)

    We need to consider 3 different cases. The case depends on where the circle center is located relative to the ring segment:

    meow

    In the 1st case, as you already figured, collision occurs if length of vector (cx-rx, cy-ry) is greater than r1-rc and less than r2+rc.

    In the 2nd case collision occurs if the distane between the circle center and the closest straight edge is less than rc.

    In the 3rd case collision occurs if the distance between the circle center and the closest of 4 corners is less than rc.

    Here's some pseudocode:

    rpos = vec2(rx,ry); // Ring segment center coordinates
    cpos = vec2(cx,cy); // Circle coordinates
    
    a = atan2(cy-ry, cx-rx); // Relative angle
    r = length(cpos - rpos); // Distance between centers
    
    if (a > a1 && a < a2) // Case 1
    {
        does_collide = (r+rc > a1 && r-rc < a2);
    }
    else
    {
        // Ring segment corners:
        p11 = vec2(cos(a1), sin(a1)) * r1;
        p12 = vec2(cos(a1), sin(a1)) * r2;
        p21 = vec2(cos(a2), sin(a2)) * r1;
        p22 = vec2(cos(a2), sin(a2)) * r2;
    
        if (((cpos-p11) · (p12-p11) > 0 && (cpos-p12) · (p11-p12) > 0) ||
            ((cpos-p21) · (p22-p21) > 0 && (cpos-p22) · (p21-p22) > 0)) // Case 2
        {
            // Normals of straight edges:
            n1 = normalize(vec2(p12.y - p11.y, p11.x - p12.x));
            n2 = normalize(vec2(p21.y - p22.y, p22.x - p21.x));
    
            // Distances to edges:
            d1 = n1 · (cpos - p11);
            d2 = n2 · (cpos - p21);
    
            does_collide = (min(d1, d2) < rc);
        }
        else // Case 3
        {
            // Squared distances to corners
            c1 = length_sqr(cpos-p11);
            c2 = length_sqr(cpos-p12);
            c3 = length_sqr(cpos-p21);
            c4 = length_sqr(cpos-p22);
    
            does_collide = (sqrt(min(c1, c2, c3, c4)) < rc);
        }
    }