圆 - 矩形碰撞检测(交叉)

aib*_*aib 181 geometry collision-detection

如何判断圆形和矩形在2D欧几里德空间中是否相交?(即经典2D几何)

e.J*_*mes 276

我将如何做到这一点:

bool intersects(CircleType circle, RectType rect)
{
    circleDistance.x = abs(circle.x - rect.x);
    circleDistance.y = abs(circle.y - rect.y);

    if (circleDistance.x > (rect.width/2 + circle.r)) { return false; }
    if (circleDistance.y > (rect.height/2 + circle.r)) { return false; }

    if (circleDistance.x <= (rect.width/2)) { return true; } 
    if (circleDistance.y <= (rect.height/2)) { return true; }

    cornerDistance_sq = (circleDistance.x - rect.width/2)^2 +
                         (circleDistance.y - rect.height/2)^2;

    return (cornerDistance_sq <= (circle.r^2));
}
Run Code Online (Sandbox Code Playgroud)

以下是它的工作原理:

illusration

  1. 第一对线计算圆心和矩形中心之间的x和y差的绝对值.这将四个象限折叠成一个,因此计算不必进行四次.图像显示圆圈中心现在必须位于的区域.请注意,仅显示单个象限.矩形是灰色区域,红色边框勾勒出临界区域,该区域与矩形边缘恰好相差一个半径.圆的中心必须在此红色边界内,以便发生交叉.

  2. 第二对线消除了圆形远离矩形(在任一方向上)足够远而不可能交叉的简单情况.这对应于图像中的绿色区域.

  3. 第三对线处理容易的情况,其中圆圈足够接近矩形(在任一方向上),保证交叉点.这对应于图像中的橙色和灰色部分.请注意,必须在步骤2之后执行此步骤才能使逻辑有意义.

  4. 剩下的线计算圆可能与矩形的角相交的困难情况.要求解,请计算距圆心和拐角的距离,然后验证距离是否不大于圆的半径.对于中心位于红色阴影区域内的所有圆,此计算返回false,对于中心位于白色阴影区域内的所有圆,该计算返回true.

  • 只是为了澄清 - 这个答案仅适用于轴对齐的矩形.从阅读其他答案的评论中可以清楚地看到这一点,但仅从这个答案+评论中看不出来.(对齐轴对齐的很好的答案!) (11认同)
  • 非常好!注意:显然在这里,rect.x/y位于矩形的右上角.您也可以通过与半径的平方进行比较来消除昂贵的平方根. (4认同)
  • 大!重要的是要让读者知道,在这里我相信rect的定义是rect.x和rect.y是rect的_center_。在我的世界中,rect的xy在rect的顶部/左侧,而0,0在屏幕的顶部/左侧,所以我使用了:`circleDistance_x = abs(circle.x-(rect.x-rect.w / 2)) ; circleDistance_y = abs(circle.y-(rect.y-rect.h / 2));` (3认同)
  • 哦不,我的坏.rect.x/y位于矩形的左下角.我会写:circleDistance.x = abs(circle.x - (rect.x + rect.width/2)); (2认同)
  • 在我进一步阅读之前,我认为您的逻辑有错误。你的第三步不应该说“这对应于橙色部分......”,因为那不是真的。第三步仅保证如果中心位于灰色区域内,则函数将立即以 true 结束。一旦你通过了步骤 2,橙色区域和灰色区域就是“安全的”。步骤 3 只是让你绕过昂贵的步骤 4。这捕获了罕见的极端情况。但谢谢你,这很有帮助。 (2认同)
  • @Tanner:我们走了.Hooray for backup and OCD`;)` (2认同)
  • 这就是我想要的。没有复杂的循环,没有手工操作,没有“在几何引擎中检查X很简单”,只是简单的伪代码和很好的解释。 (2认同)

Shr*_*saR 176

当圆与矩形相交时,只有两种情况:

  • 圆的中心位于矩形内部,或者
  • 矩形的一个边缘在圆圈中有一个点.

请注意,这不要求矩形是轴平行的.

圆和矩形可以交叉的一些不同方式

(一种观察方式:如果没有边缘在圆中有一个点(如果所有边都完全"在圆"之外),那么圆仍然可以与多边形相交的唯一方法是它是否完全位于圆内多边形.)

有了这种洞察力,像下面的工作,其中圆的中心P和半径R,以及矩形的顶点A,B,C,D的顺序(不完整的代码):

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)))
Run Code Online (Sandbox Code Playgroud)

如果您正在编写任何几何图形,则可能已在库中具有上述功能.否则,pointInRectangle()可以通过多种方式实施; 多边形方法中的任何一般都可以使用,但是对于一个矩形,您可以检查它是否有效:

0 ? AP·AB ? AB·AB and 0 ? AP·AD ? AD·AD
Run Code Online (Sandbox Code Playgroud)

并且intersectCircle()易于实现:一种方法是检查从P线到线的垂直脚是否足够接近并且在端点之间,否则检查端点.

很酷的是,同样的想法不仅适用于矩形,而且适用于圆与任何简单多边形的交叉- 甚至不必是凸起的!

  • 对于它的价值,我真的认为这个答案比我的好.主要有两个原因:1:如果矩形不是轴平行的,则不需要旋转; 2:概念很容易扩展到*all*多边形. (21认同)
  • 那个矩形完全在圆圈里面的情况怎么样,但是圆圈的中心不在矩形内? (7认同)
  • 我发现这个答案令人反感地被高估了。当然,它看起来有花哨的图表和代码示例。但这只是解释一些明显的东西的烟雾和镜子,然后最终将实现留给读者作为练习。如果我们有神奇的“lineIntersectsCircle”或“pointInRectangle”库函数,那么我们可能已经在那个库中拥有“rectangleIntersectsCircle”函数了! (4认同)
  • @PaulK 你一定比我聪明。:-) 这对我来说不是“显而易见的东西”;我必须弄清楚检查这些条件就足够了。同样,如何实现`pointInRectangle`和`intersectCircle`也不是很明显;这就是为什么我解释了实现每个方法的一种可能方法,即使每个方法都有很多方法(可能在其他问题上回答)。(顺便说一句,所有这些东西对我来说*仍然*不明显;这就是添加证明的原因。答案是在 2008 年写的;我只在 2017 年添加了图片。)我只是分享我的理解,并不打算引起你的任何反感。:-) (3认同)
  • @paniq:嗯,两者都是常数.:-)但是,是的,这作为一般解决方案更有用,涵盖任何方向的矩形,实际上任何简单的多边形. (2认同)
  • @ericsoco:很好的观察.:-)我想我应该在"矩形的一个边缘与圆相交"中说"与圆盘相交",因为我的意思是它与圆圈本身共享一个点,不一定是圆的边界.无论如何,上面的描述"检查从P [圆心的中心]到线的垂线的足部是否足够接近并且在端点之间,否则检查端点"将仍然有效 - 例如端点位于圆内(盘). (2认同)
  • @ DexD.Hunter如果圆的中心在矩形之外,但是它的一部分在矩形内,那么矩形的一条边必然与圆相交. (2认同)

Cyg*_*gon 119

这是另一个实现起来非常简单的解决方案(也非常快).它将捕获所有交叉点,包括球体完全进入矩形时.

// clamp(value, min, max) - limits value to the range min..max

// Find the closest point to the circle within the rectangle
float closestX = clamp(circle.X, rectangle.Left, rectangle.Right);
float closestY = clamp(circle.Y, rectangle.Top, rectangle.Bottom);

// Calculate the distance between the circle's center and this closest point
float distanceX = circle.X - closestX;
float distanceY = circle.Y - closestY;

// If the distance is less than the circle's radius, an intersection occurs
float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
return distanceSquared < (circle.Radius * circle.Radius);
Run Code Online (Sandbox Code Playgroud)

任何体面的数学库,可以缩短为3或4行.

  • 我最喜欢这个答案.它简短,易于理解,速度快. (7认同)
  • 你有一个错误,你用Left和Right搜索nearestY,而不是Top和Bottom,否则就是可爱的解决方案. (3认同)
  • @Leo我认为修改这个算法以适应这种情况并不难,应该简单地应用一个坐标变换,其中原点位于矩形中心,矩形不再倾斜.您只需将转换应用于圆心. (3认同)
  • 我认为,如果矩形相对于x轴和y轴倾斜,则您的解决方案将失败。 (2认同)

use*_*676 10

你的球体和矩形与IIF相交
,圆心与矩形的一个顶点之间的距离小于球体的半径,
或者
圆心与矩形的一个边缘之间的距离小于球体的半径( [ 点线距离 ])

圆心位于矩形

点点距离内:

P1 = [x1,y1]
P2 = [x2,y2]
Distance = sqrt(abs(x1 - x2)+abs(y1-y2))

点线距离:

L1 = [x1,y1],L2 = [x2,y2] (two points of your line, ie the vertex points)
P1 = [px,py] some point

Distance d =  abs( (x2-x1)(y1-py)-(x1-px)(y2-y1) ) / Distance(L1,L2)


在rect内部的圆心:
采取一个分离轴的方法:如果在一条线上有一个投影,将该矩形与该点分开,它们就不会相交

将点投影在平行于矩形边的线上,然后可以轻松确定它们是否相交.如果它们不与所有4个投影相交,则它们(点和矩形)不能相交.

你只需要内积(x = [x1,x2],y = [y1,y2],x*y = x1*y1 + x2*y2)

你的测试看起来像那样:

//rectangle edges: TL (top left), TR (top right), BL (bottom left), BR (bottom right)
//point to test: POI

seperated = false
for egde in { {TL,TR}, {BL,BR}, {TL,BL},{TR-BR} }:  // the edges
    D = edge[0] - edge[1]
    innerProd =  D * POI
    Interval_min = min(D*edge[0],D*edge[1])
    Interval_max = max(D*edge[0],D*edge[1])
    if not (  Interval_min ≤ innerProd ≤  Interval_max ) 
           seperated = true
           break  // end for loop 
    end if
end for
if (seperated is true)    
      return "no intersection"
else 
      return "intersection"
end if

这并不假设轴对齐的矩形,并且可以很容易地扩展以测试凸集之间的交叉点.

  • 不知道为什么这是低估的.逻辑是合理的.+1 (3认同)

Cli*_*key 9

我想出的最简单的解决方案非常简单。

它的工作原理是找到矩形中最接近圆的点,然后比较距离。

您可以通过一些操作完成所有这些,甚至可以避免使用 sqrt 函数。

public boolean intersects(float cx, float cy, float radius, float left, float top, float right, float bottom)
{
   float closestX = (cx < left ? left : (cx > right ? right : cx));
   float closestY = (cy < top ? top : (cy > bottom ? bottom : cy));
   float dx = closestX - cx;
   float dy = closestY - cy;

   return ( dx * dx + dy * dy ) <= radius * radius;
}
Run Code Online (Sandbox Code Playgroud)

就是这样!上述解决方案假设原点位于世界的左上角,x 轴向下。

如果您想要处理移动圆和矩形之间碰撞的解决方案,它会复杂得多,并且在我的另一个答案中有所介绍。

  • 你能提供使这个失败的实际输入吗?当圆圈在里面时,测试的左边部分是 0.0。除非半径为零,否则测试的右侧部分应 &gt; 0.0 (2认同)

int*_*dis 6

这是最快的解决方案:

public static boolean intersect(Rectangle r, Circle c)
{
    float cx = Math.abs(c.x - r.x - r.halfWidth);
    float xDist = r.halfWidth + c.radius;
    if (cx > xDist)
        return false;
    float cy = Math.abs(c.y - r.y - r.halfHeight);
    float yDist = r.halfHeight + c.radius;
    if (cy > yDist)
        return false;
    if (cx <= r.halfWidth || cy <= r.halfHeight)
        return true;
    float xCornerDist = cx - r.halfWidth;
    float yCornerDist = cy - r.halfHeight;
    float xCornerDistSq = xCornerDist * xCornerDist;
    float yCornerDistSq = yCornerDist * yCornerDist;
    float maxCornerDistSq = c.radius * c.radius;
    return xCornerDistSq + yCornerDistSq <= maxCornerDistSq;
}
Run Code Online (Sandbox Code Playgroud)

请注意执行顺序,并预先计算宽度/高度的一半.平方也是"手动"完成的,以节省一些时钟周期.

  • 我认为你不能声称在最昂贵的代码路径中进行五次测试/比较是没有一些证据的"最快的解决方案". (3认同)
  • 请参阅https://en.wikipedia.org/wiki/Argumentum_ad_populum和https://en.wikipedia.org/wiki/Argument_from_ignorance (2认同)