当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《圆-矩形碰撞检测(交叉)》经验,为你挑选了5个好方法。

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



1> e.James..:

我将如何做到这一点:

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));
}

以下是它的工作原理:

illusration

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

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

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

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


只是为了澄清 - 这个答案仅适用于轴对齐的矩形.从阅读其他答案的评论中可以清楚地看到这一点,但仅从这个答案+评论中看不出来.(对齐轴对齐的很好的答案!)
非常好!注意:显然在这里,rect.x/y位于矩形的右上角.您也可以通过与半径的平方进行比较来消除昂贵的平方根.
大!重要的是要让读者知道,在这里我相信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));`
哦不,我的坏.rect.x/y位于矩形的左下角.我会写:circleDistance.x = abs(circle.x - (rect.x + rect.width/2));
@Tanner:我们走了.Hooray for backup and OCD`;)`
这就是我想要的。没有复杂的循环,没有手工操作,没有“在几何引擎中检查X很简单”,只是简单的伪代码和很好的解释。

2> ShreevatsaR..:

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

圆的中心位于矩形内部,或者

矩形的一个边缘在圆圈中有一个点.

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

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

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

有了这种洞察力,像下面的工作,其中圆的中心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)))

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

0 ? AP·AB ? AB·AB and 0 ? AP·AD ? AD·AD

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

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


对于它的价值,我真的认为这个答案比我的好.主要有两个原因:1:如果矩形不是轴平行的,则不需要旋转; 2:概念很容易扩展到*all*多边形.
那个矩形完全在圆圈里面的情况怎么样,但是圆圈的中心不在矩形内?
@paniq:嗯,两者都是常数.:-)但是,是的,这作为一般解决方案更有用,涵盖任何方向的矩形,实际上任何简单的多边形.
@ericsoco:很好的观察.:-)我想我应该在"矩形的一个边缘与圆相交"中说"与圆盘相交",因为我的意思是它与圆圈本身共享一个点,不一定是圆的边界.无论如何,上面的描述"检查从P [圆心的中心]到线的垂线的足部是否足够接近并且在端点之间,否则检查端点"将仍然有效 - 例如端点位于圆内(盘).
@ DexD.Hunter如果圆的中心在矩形之外,但是它的一部分在矩形内,那么矩形的一条边必然与圆相交.

3> Cygon..:

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

// 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);

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


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

4> user104676..:

你的球体和矩形与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

5> intrepidis..:

这是最快的解决方案:

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;
}

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


我认为你不能声称在最昂贵的代码路径中进行五次测试/比较是没有一些证据的"最快的解决方案".
请参阅https://en.wikipedia.org/wiki/Argumentum_ad_populum和https://en.wikipedia.org/wiki/Argument_from_ignorance
推荐阅读
郑小蒜9299_941611_G
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有