Circle-Rectangle collision detection (intersection)

前端 未结 24 1403
无人共我
无人共我 2020-11-22 02:55

How can I tell whether a circle and a rectangle intersect in 2D Euclidean space? (i.e. classic 2D geometry)

相关标签:
24条回答
  • 2020-11-22 03:10
    def colision(rect, circle):
    dx = rect.x - circle.x
    dy = rect.y - circle.y
    distance = (dy**2 + dx**2)**0.5
    angle_to = (rect.angle + math.atan2(dx, dy)/3.1415*180.0) % 360
    if((angle_to>135 and angle_to<225) or (angle_to>0 and angle_to<45) or (angle_to>315 and angle_to<360)):
        if distance <= circle.rad/2.+((rect.height/2.0)*(1.+0.5*abs(math.sin(angle_to*math.pi/180.)))):
            return True
    else:
        if distance <= circle.rad/2.+((rect.width/2.0)*(1.+0.5*abs(math.cos(angle_to*math.pi/180.)))):
            return True
    return False
    
    0 讨论(0)
  • 2020-11-22 03:11

    For those have to calculate Circle/Rectangle collision in Geographic Coordinates with SQL,
    this is my implementation in oracle 11 of e.James suggested algorithm.

    In input it requires circle coordinates, circle radius in km and two vertices coordinates of the rectangle:

    CREATE OR REPLACE FUNCTION "DETECT_CIRC_RECT_COLLISION"
    (
        circleCenterLat     IN NUMBER,      -- circle Center Latitude
        circleCenterLon     IN NUMBER,      -- circle Center Longitude
        circleRadius        IN NUMBER,      -- circle Radius in KM
        rectSWLat           IN NUMBER,      -- rectangle South West Latitude
        rectSWLon           IN NUMBER,      -- rectangle South West Longitude
        rectNELat           IN NUMBER,      -- rectangle North Est Latitude
        rectNELon           IN NUMBER       -- rectangle North Est Longitude
    )
    RETURN NUMBER
    AS
        -- converts km to degrees (use 69 if miles)
        kmToDegreeConst     NUMBER := 111.045;
    
        -- Remaining rectangle vertices 
        rectNWLat   NUMBER;
        rectNWLon   NUMBER;
        rectSELat   NUMBER;
        rectSELon   NUMBER;
    
        rectHeight  NUMBER;
        rectWIdth   NUMBER;
    
        circleDistanceLat   NUMBER;
        circleDistanceLon   NUMBER;
        cornerDistanceSQ    NUMBER;
    
    BEGIN
        -- Initialization of remaining rectangle vertices  
        rectNWLat := rectNELat;
        rectNWLon := rectSWLon;
        rectSELat := rectSWLat;
        rectSELon := rectNELon;
    
        -- Rectangle sides length calculation
        rectHeight := calc_distance(rectSWLat, rectSWLon, rectNWLat, rectNWLon);
        rectWidth := calc_distance(rectSWLat, rectSWLon, rectSELat, rectSELon);
    
        circleDistanceLat := abs( (circleCenterLat * kmToDegreeConst) - ((rectSWLat * kmToDegreeConst) + (rectHeight/2)) );
        circleDistanceLon := abs( (circleCenterLon * kmToDegreeConst) - ((rectSWLon * kmToDegreeConst) + (rectWidth/2)) );
    
        IF circleDistanceLon > ((rectWidth/2) + circleRadius) THEN
            RETURN -1;   --  -1 => NO Collision ; 0 => Collision Detected
        END IF;
    
        IF circleDistanceLat > ((rectHeight/2) + circleRadius) THEN
            RETURN -1;   --  -1 => NO Collision ; 0 => Collision Detected
        END IF;
    
        IF circleDistanceLon <= (rectWidth/2) THEN
            RETURN 0;   --  -1 => NO Collision ; 0 => Collision Detected
        END IF;
    
        IF circleDistanceLat <= (rectHeight/2) THEN
            RETURN 0;   --  -1 => NO Collision ; 0 => Collision Detected
        END IF;
    
    
        cornerDistanceSQ := POWER(circleDistanceLon - (rectWidth/2), 2) + POWER(circleDistanceLat - (rectHeight/2), 2);
    
        IF cornerDistanceSQ <=  POWER(circleRadius, 2) THEN
            RETURN 0;  --  -1 => NO Collision ; 0 => Collision Detected
        ELSE
            RETURN -1;  --  -1 => NO Collision ; 0 => Collision Detected
        END IF;
    
        RETURN -1;  --  -1 => NO Collision ; 0 => Collision Detected
    END;    
    
    0 讨论(0)
  • 2020-11-22 03:11

    There is an incredibly simple way to do this, you have to clamp a point in x and y, but inside the square, while the center of the circle is between the two square border points in one of the perpendicular axis you need to clamp those coordinates to the parallel axis, just make sure the clamped coordinates do not exeed the limits of the square. Then just get the distance between the center of the circle and the clamped coordinates and check if the distance is less than the radius of the circle.

    Here is how I did it (First 4 points are the square coordinates, the rest are circle points):

    bool DoesCircleImpactBox(float x, float y, float x1, float y1, float xc, float yc, float radius){
        float ClampedX=0;
        float ClampedY=0;
        
        if(xc>=x and xc<=x1){
        ClampedX=xc;
        }
        
        if(yc>=y and yc<=y1){
        ClampedY=yc;
        }
        
        radius = radius+1;
        
        if(xc<x) ClampedX=x;
        if(xc>x1) ClampedX=x1-1;
        if(yc<y) ClampedY=y;
        if(yc>y1) ClampedY=y1-1;
        
        float XDif=ClampedX-xc;
        XDif=XDif*XDif;
        float YDif=ClampedY-yc;
        YDif=YDif*YDif;
        
        if(XDif+YDif<=radius*radius) return true;
        
        return false;
    }
    
    0 讨论(0)
  • 2020-11-22 03:13

    Here's my C code for resolving a collision between a sphere and a non-axis aligned box. It relies on a couple of my own library routines, but it may prove useful to some. I'm using it in a game and it works perfectly.

    float physicsProcessCollisionBetweenSelfAndActorRect(SPhysics *self, SPhysics *actor)
    {
        float diff = 99999;
    
        SVector relative_position_of_circle = getDifference2DBetweenVectors(&self->worldPosition, &actor->worldPosition);
        rotateVector2DBy(&relative_position_of_circle, -actor->axis.angleZ); // This aligns the coord system so the rect becomes an AABB
    
        float x_clamped_within_rectangle = relative_position_of_circle.x;
        float y_clamped_within_rectangle = relative_position_of_circle.y;
        LIMIT(x_clamped_within_rectangle, actor->physicsRect.l, actor->physicsRect.r);
        LIMIT(y_clamped_within_rectangle, actor->physicsRect.b, actor->physicsRect.t);
    
        // Calculate the distance between the circle's center and this closest point
        float distance_to_nearest_edge_x = relative_position_of_circle.x - x_clamped_within_rectangle;
        float distance_to_nearest_edge_y = relative_position_of_circle.y - y_clamped_within_rectangle;
    
        // If the distance is less than the circle's radius, an intersection occurs
        float distance_sq_x = SQUARE(distance_to_nearest_edge_x);
        float distance_sq_y = SQUARE(distance_to_nearest_edge_y);
        float radius_sq = SQUARE(self->physicsRadius);
        if(distance_sq_x + distance_sq_y < radius_sq)   
        {
            float half_rect_w = (actor->physicsRect.r - actor->physicsRect.l) * 0.5f;
            float half_rect_h = (actor->physicsRect.t - actor->physicsRect.b) * 0.5f;
    
            CREATE_VECTOR(push_vector);         
    
            // If we're at one of the corners of this object, treat this as a circular/circular collision
            if(fabs(relative_position_of_circle.x) > half_rect_w && fabs(relative_position_of_circle.y) > half_rect_h)
            {
                SVector edges;
                if(relative_position_of_circle.x > 0) edges.x = half_rect_w; else edges.x = -half_rect_w;
                if(relative_position_of_circle.y > 0) edges.y = half_rect_h; else edges.y = -half_rect_h;   
    
                push_vector = relative_position_of_circle;
                moveVectorByInverseVector2D(&push_vector, &edges);
    
                // We now have the vector from the corner of the rect to the point.
                float delta_length = getVector2DMagnitude(&push_vector);
                float diff = self->physicsRadius - delta_length; // Find out how far away we are from our ideal distance
    
                // Normalise the vector
                push_vector.x /= delta_length;
                push_vector.y /= delta_length;
                scaleVector2DBy(&push_vector, diff); // Now multiply it by the difference
                push_vector.z = 0;
            }
            else // Nope - just bouncing against one of the edges
            {
                if(relative_position_of_circle.x > 0) // Ball is to the right
                    push_vector.x = (half_rect_w + self->physicsRadius) - relative_position_of_circle.x;
                else
                    push_vector.x = -((half_rect_w + self->physicsRadius) + relative_position_of_circle.x);
    
                if(relative_position_of_circle.y > 0) // Ball is above
                    push_vector.y = (half_rect_h + self->physicsRadius) - relative_position_of_circle.y;
                else
                    push_vector.y = -((half_rect_h + self->physicsRadius) + relative_position_of_circle.y);
    
                if(fabs(push_vector.x) < fabs(push_vector.y))
                    push_vector.y = 0;
                else
                    push_vector.x = 0;
            }
    
            diff = 0; // Cheat, since we don't do anything with the value anyway
            rotateVector2DBy(&push_vector, actor->axis.angleZ);
            SVector *from = &self->worldPosition;       
            moveVectorBy2D(from, push_vector.x, push_vector.y);
        }   
        return diff;
    }
    
    0 讨论(0)
  • 2020-11-22 03:14

    To visualise, take your keyboard's numpad. If the key '5' represents your rectangle, then all the keys 1-9 represent the 9 quadrants of space divided by the lines that make up your rectangle (with 5 being the inside.)

    1) If the circle's center is in quadrant 5 (i.e. inside the rectangle) then the two shapes intersect.

    With that out of the way, there are two possible cases: a) The circle intersects with two or more neighboring edges of the rectangle. b) The circle intersects with one edge of the rectangle.

    The first case is simple. If the circle intersects with two neighboring edges of the rectangle, it must contain the corner connecting those two edges. (That, or its center lies in quadrant 5, which we have already covered. Also note that the case where the circle intersects with only two opposing edges of the rectangle is covered as well.)

    2) If any of the corners A, B, C, D of the rectangle lie inside the circle, then the two shapes intersect.

    The second case is trickier. We should make note of that it may only happen when the circle's center lies in one of the quadrants 2, 4, 6 or 8. (In fact, if the center is on any of the quadrants 1, 3, 7, 8, the corresponding corner will be the closest point to it.)

    Now we have the case that the circle's center is in one of the 'edge' quadrants, and it only intersects with the corresponding edge. Then, the point on the edge that is closest to the circle's center, must lie inside the circle.

    3) For each line AB, BC, CD, DA, construct perpendicular lines p(AB,P), p(BC,P), p(CD,P), p(DA,P) through the circle's center P. For each perpendicular line, if the intersection with the original edge lies inside the circle, then the two shapes intersect.

    There is a shortcut for this last step. If the circle's center is in quadrant 8 and the edge AB is the top edge, the point of intersection will have the y-coordinate of A and B, and the x-coordinate of center P.

    You can construct the four line intersections and check if they lie on their corresponding edges, or find out which quadrant P is in and check the corresponding intersection. Both should simplify to the same boolean equation. Be wary of that the step 2 above did not rule out P being in one of the 'corner' quadrants; it just looked for an intersection.

    Edit: As it turns out, I have overlooked the simple fact that #2 is a subcase of #3 above. After all, corners too are points on the edges. See @ShreevatsaR's answer below for a great explanation. And in the meanwhile, forget #2 above unless you want a quick but redundant check.

    0 讨论(0)
  • 2020-11-22 03:15

    There are only two cases when the circle intersects with the rectangle:

    • Either the circle's centre lies inside the rectangle, or
    • One of the edges of the rectangle has a point in the circle.

    Note that this does not require the rectangle to be axis-parallel.

    (One way to see this: if none of the edges has a point in the circle (if all the edges are completely "outside" the circle), then the only way the circle can still intersect the polygon is if it lies completely inside the polygon.)

    With that insight, something like the following will work, where the circle has centre P and radius R, and the rectangle has vertices A, B, C, D in that order (not complete code):

    def intersect(Circle(P, R), Rectangle(A, B, C, D)):
        S = Circle(P, R)
        return (pointInRectangle(P, Rectangle(A, B, C, D)) or
                intersectCircle(S, (A, B)) or
                intersectCircle(S, (B, C)) or
                intersectCircle(S, (C, D)) or
                intersectCircle(S, (D, A)))
    

    If you're writing any geometry you probably have the above functions in your library already. Otherwise, pointInRectangle() can be implemented in several ways; any of the general point in polygon methods will work, but for a rectangle you can just check whether this works:

    0 ≤ AP·AB ≤ AB·AB and 0 ≤ AP·AD ≤ AD·AD
    

    And intersectCircle() is easy to implement too: one way would be to check if the foot of the perpendicular from P to the line is close enough and between the endpoints, and check the endpoints otherwise.

    The cool thing is that the same idea works not just for rectangles but for the intersection of a circle with any simple polygon — doesn't even have to be convex!

    0 讨论(0)
提交回复
热议问题