检测两个矩形相交的算法?


143

我正在寻找一种算法来检测两个矩形是否相交(一个以任意角度相交,另一个仅以垂直/水平线相交)。

测试一个角是否在另一个ALMOST中是可行的。如果矩形形成十字形,则失败。

避免使用直线的斜率似乎是个好主意,因为直线的斜率需要特殊情况。


如果添加到角检查中,该检查会检查第二个矩形是否在成角度的矩形的边界(矩形)之内,该怎么办?
Wes P

你要用什么语言来做?因为在Java中,有内置的类可以让您执行此操作。
马丁

我认为图形api和大多数GUI库(例如swing)都已实现此功能。
l_39217_l

可能会漏掉它们重叠但矩形内没有角的情况
FlorianBösch08年

1
这个问题几乎与:stackoverflow.com/questions/306316/…相同。虽然,这是在寻找专门针对C ++的解决方案。接受的答案也非常简单明了。
冈萨雷斯银奖2013年

Answers:


162

标准方法是进行分离轴测试(对此进行Google搜索)。

简而言之:

  • 如果可以找到将两个对象分开的线,则两个对象不会相交。例如,对象/对象的所有点都在直线的不同侧。

有趣的是,仅检查两个矩形的所有边缘就足够了。如果矩形不重叠,则边缘之一将成为分隔轴。

在2D模式下,您无需使用坡度即可执行此操作。边简单定义为两个顶点之间的差,例如

  edge = v(n) - v(n-1)

您可以通过将其旋转90°来获得垂直于此的角度。在2D中,这很容易,因为:

  rotated.x = -unrotated.y
  rotated.y =  unrotated.x

因此,不涉及三角函数或斜率。也不需要将向量归一化为单位长度。

如果要测试点是否在直线的一侧或另一侧,则可以使用点积。标牌会告诉您您在哪一边:

  // rotated: your rotated edge
  // v(n-1) any point from the edge.
  // testpoint: the point you want to find out which side it's on.

  side = sign (rotated.x * (testpoint.x - v(n-1).x) + 
               rotated.y * (testpoint.y - v(n-1).y);

现在,针对矩形B的边缘测试矩形A的所有点,反之亦然。如果找到分离的边缘,则对象不相交(假设B中的所有其他点都在要测试的边缘的另一侧-请参见下图)。如果找不到分隔边,则两个矩形相交,或者另一个矩形包含一个矩形。

该测试适用于所有凸多边形btw ..

修正:要确定一个分离的边缘,仅将一个矩形的所有点都与另一个边缘的每个边缘进行测试是不够的。由于A中的所有点都在E的同一半平面中,因此候选边缘E(如下)将被标识为分离边缘。但是,它不是分离边缘,因为B的顶点Vb1和Vb2也在那个半平面内 如果不是这种情况,那只会是一个分离的边缘 。http://www.iassess.com/collision.png


2
此算法不适用于所有情况。可以将第二个矩形旋转到第一个矩形45度并沿对角线偏移,以使其满足上述相交测试但不相交。
Skizz

6
Skizz,检查所有八个边缘。如果对象不相交,则八个边缘之一分隔它们。您为什么不张贴显示案件情况的图像?我可以告诉你的轴..
尼尔斯Pipenbrinck

2
我的错,确实可以解决这种情况。
Skizz

2
图像已消失(2012
John Dvorak

2
我在可视化该图像时遇到了很多麻烦,因此我重新创建了我认为参考图像的外观。imgur.com/bNwrzsv
Rjdlee 2015年

16

基本上看下图:


如果两个框碰撞,则线A和B将重叠。

请注意,这必须同时在X和Y轴上进行,并且都需要重叠以使矩形发生碰撞。

gamasutra.com上有一篇很好的文章可以回答这个问题(图片来自该文章)。我在5年前做过类似的算法,所以我必须找到我的代码段才能稍后在此处发布

修正:分离轴定理指出,如果存在分离轴,则两个凸形重叠(即,图中所示的投影重叠的凸形)。因此,“存在分离轴” =>“无重叠”。这不是双向含义,因此您不能得出相反的结论。


1
显然,因为两个正方形(0,0,1,1)和(0,3,1,4)不重叠,但它们在x轴上的投影完全重叠。两项测试都是必要的,组合就足够了。
MSalters

18
x和y投影重叠是不够的:例如使用矩形[[0,0),(0,3),(3,3),(3,0)]和[(2,5), (5,2),(7,4),(4,7)]。
乔尔(Joel)在2009年

4
我同意Gö中的@Joel。此方法错过了矩形不重叠,但投影半径在x和y上都重叠的大量情况。
Scottie T

5
这个答案没有错,但却是一种误导。的确是这样:如果两个框碰撞,则线A和B将重叠。但也确实如此:如果A和B线重叠,则两个盒子可能会碰撞,也可能不会碰撞
暗淡会

7
@floater:我想说这不仅是错误的,而且还会引起误解,甚至更糟。
BlueRaja-Danny Pflughoeft,2010年

4

m_pGladiator的答案是正确的,我更喜欢它。 分离轴测试是检测矩形重叠的最简单和标准的方法。投影间隔不重叠的线称为分离轴。Nils Pipenbrinck的解决方案过于笼统。它使用点积检查一种形状是否完全位于另一种形状的边缘的一侧。该解决方案实际上可能会诱发n边凸多边形。但是,两个矩形均未优化。

m_pGladiator回答的关键点是,我们应该检查两个矩形(x和y)上两个矩形的投影。如果两个投影重叠,那么我们可以说这两个矩形重叠。因此,以上对m_pGladiator答案的评论是错误的。

对于简单的情况,如果不旋转两个矩形,我们将提供一个结构如下的矩形:

struct Rect {
    x, // the center in x axis
    y, // the center in y axis
    width,
    height
}

我们将矩形A,B分别命名为rectA,rectB。

    if Math.abs(rectA.x - rectB.x) < (Math.abs(rectA.width + rectB.width) / 2) 
&& (Math.abs(rectA.y - rectB.y) < (Math.abs(rectA.height + rectB.height) / 2))
    then
        // A and B collide
    end if

如果两个矩形中的任何一个旋转,则可能需要一些努力才能确定它们在x和y轴上的投影。定义结构RotatedRect如下:

struct RotatedRect : Rect {
    double angle; // the rotating angle oriented to its center
}

区别是width'现在有一点不同:rectA为widthA Math.sqrt(rectA.width*rectA.width + rectA.height*rectA.height) * Math.cos(rectA.angle) '; rectB 为widthB ':Math.sqrt(rectB.width*rectB.width + rectB.height*rectB.height) * Math.cos(rectB.angle)

    if Math.abs(rectA.x - rectB.x) < (Math.abs(widthA' + widthB') / 2) 
&& (Math.abs(rectA.y - rectB.y) < (Math.abs(heightA' + heightB') / 2))
    then
        // A and B collide
    end if

可以参考GDC(2007年游戏开发大会)PPT www.realtimecollisiondetection.net/pubs/GDC07_Ericson_Physics_Tutorial_SAT.ppt


为什么需要“ Math.abs(rectA.width + rectB.width)”中的Math.abs()来处理负宽度?
AlexWien

分隔轴不一定是罗盘方向,它可以有任何角度。
Ben Voigt 2014年

非旋转矩形rectA(x = 0,y = 0,width = 1,height = 1)和rectB(x = 2,y = 0,width = 100,height = 1)不相交,但是您的方法说它们相交。难道我做错了什么?
Kagami Sascha Rosylight

4

在可可中,您可以轻松地检测出selectedArea rect是否与旋转的NSView的帧rect相交。您甚至不需要计算多边形,法线就是这样。只需将这些方法添加到您的NSView子类中即可。例如,用户在NSView的超级视图上选择一个区域,然后通过传递selectedArea rect调用方法DidsThisRectSelectMe。API convertRect:将完成这项工作。单击NSView将其选中时,同样的技巧也起作用。在这种情况下,只需重写hitTest方法,如下所示。API convertPoint:将完成该工作;-)

- (BOOL)DoesThisRectSelectMe:(NSRect)selectedArea
{
    NSRect localArea = [self convertRect:selectedArea fromView:self.superview];

    return NSIntersectsRect(localArea, self.bounds);
}


- (NSView *)hitTest:(NSPoint)aPoint
{
    NSPoint localPoint = [self convertPoint:aPoint fromView:self.superview];
    return NSPointInRect(localPoint, self.bounds) ? self : nil;
}

2
该代码仅适用于与屏幕成正方形的矩形。那是个小事。假设我们正在处理的矩形与屏幕或彼此之间不成90度角。
邓肯C

正如我在应用程序中检查和使用的那样,该代码可在任何旋转的矩形上工作。不管旋转度。
莱昂纳多·

但这并没有描述该算法,只是提到了一个已经在使用它的库。
Ben Voigt 2014年


2

一种解决方案是使用一种称为“不适合多边形”的方法。从两个多边形计算出此多边形(从概念上讲,一个在另一个周围滑动),并根据给定的相对偏移量定义了多边形重叠的区域。一旦有了该NFP,则只需对包含两个多边形的相对偏移量的点进行包含性测试。此包含测试快速简便,但是您必须首先创建NFP。

在网上搜索“不适合多边形”,看看是否可以找到凸多边形的算法(如果您拥有凹多边形,则算法会变得更加复杂)。如果找不到任何内容,请给我发送电子邮件,地址为霍华德点J点可gmail点com


1

我认为这将解决所有可能的情况。做以下测试。

  1. 检查矩形1的任何顶点是否位于矩形2内,反之亦然。只要找到位于另一个矩形内的顶点,您就可以得出结论,它们相交并停止搜索。这将照顾一个完全位于另一个矩形内部的矩形。
  2. 如果上述测试尚无定论,请找到1个矩形的每条线与另一个矩形的每条线的相交点。找到交点后,检查其是否位于由相应4个点创建的虚矩形内。只要找到这样的点,就可以得出结论,它们相交并停止了搜索。

如果以上两个测试返回false,则这两个矩形不重叠。


0

如果您使用的是Java,则Shape接口的所有实现都有一个采用矩形的相交方法。


不幸的是,我正在使用C#。Rectangle类具有Contains()方法,但仅适用于非旋转矩形。
user20493

intersects()方法几乎没有用,因为它返回布尔值而不是我猜到的交集。
ZZ 2015年

0

好吧,蛮力方法是沿着水平矩形的边缘走动,并检查沿该边缘的每个点以查看其是否落在另一个矩形上或在另一个矩形中。

数学答案是形成描述两个矩形的每个边缘的方程式。现在,您可以简单地找到矩形A的四条线中的任何一条是否与矩形B的任何线相交,这应该是一个简单的(快速)线性方程求解器。

-亚当


2
方程的问题是当您有一条垂直线时,它具有无限的斜率。
user20493

每个解决方案都有一些极端情况。
亚当·戴维斯

2
一个正方形完全包围了另一个正方形。
奥利弗·哈拉姆

0

您可以找到成角度的矩形的每一侧与轴对齐的矩形的每一侧的交集。通过找到每条边所在的无穷线的等式(即v1 + t(v2-v1)和v'1 + t'(v'2-v'1)来确定),找到当这两个方程相等时,通过求解t来满足线的交汇(如果它们是平行的,则可以进行测试),然后测试该点是否位于两个顶点之间的线段上,即0 <= t <= 1和0 <= t'<= 1。

但是,当一个矩形完全覆盖另一个矩形时,这并不适用。您可以通过测试一个矩形的所有四个点是否都位于另一个矩形内来进行覆盖。


0

对于这个问题的3D版本,这就是我要做的事情:

将两个矩形建模为方程P1和P2所描述的平面,然后写P1 = P2并从中得出相交线方程,如果平面平行(无相交)或在同一平面中,则该相交线将不存在,在这种情况下,您将获得0 = 0。在这种情况下,您将需要使用2D矩形相交算法。

然后,我将查看位于两个矩形平面中的那条线是否穿过两个矩形。如果是这样,则您有两个矩形的交集,否则就没有(或者不应该,我可能会错过一个脑袋)。

为了确定一条线是否穿过同一平面中的一个矩形,我会找到该线和矩形边的2个交点(使用线方程对它们进行建模),然后确保交点与in范围。

那是数学上的描述,不幸的是我没有上面的代码来做。


您错过了找到平面相交线的部分,因此必须确保它的一部分存在于两个矩形中。
Lee Louviere 2013年

0

进行测试的另一种方法比使用分离轴测试要快一些,该方法是在任意矩形(任意选择)的每个顶点上使用绕组数算法(仅在象限上,而不是在角度上缓慢的角度求和)。如果任何一个顶点的绕组数不为零,则两个矩形重叠。

该算法比分离轴测试耗时更长,但速度更快,因为如果边线跨越两个象限,则仅需要半平面测试(与之相比,使用分离轴方法最多进行32个测试)

该算法的另一个优点是可以用于测试任何多边形(凸面或凹面)的重叠。据我所知,该算法仅适用于2D空间。


3
我可能是错的,但是那不只是检查一个矩形的顶点是否在另一个矩形内吗?如果是,这还不够,因为矩形可能重叠而内部没有任何顶点。
sinelaw 2011年

他们可以用矩形吗?怎么样?在我看来,为了使2个矩形相交,至少一个矩形的一个顶点必须位于另一个矩形上。
邓肯C

@DuncanC:是的,他们可以。反例是一个十字架,它甚至在原始问题中列出。
Ben Voigt 2014年

@BenVoigt这是一个非常旧的线程,但是您绝对正确。
邓肯C

0

我是否想念其他东西,为什么使它变得如此复杂?

如果(x1,y1)和(X1,Y1)是矩形的角,则要找到交点,请执行以下操作:

    xIntersect = false;
    yIntersect = false;
    if (!(Math.min(x1, x2, x3, x4) > Math.max(X1, X2, X3, X4) || Math.max(x1, x2, x3, x4) < Math.min(X1, X2, X3, X4))) xIntersect = true;
    if (!(Math.min(y1, y2, y3, y4) > Math.max(Y1, Y2, Y3, Y4) || Math.max(y1, y2, y3, y4) < Math.min(Y1, Y2, Y3, Y4))) yIntersect = true;
    if (xIntersect && yIntersect) {alert("Intersect");}

3
您不知道他想要一个人旋转任意角度。
Robotbugs 2013年

0

我是这样实现的:

bool rectCollision(const CGRect &boundsA, const Matrix3x3 &mB, const CGRect &boundsB)
{
    float Axmin = boundsA.origin.x;
    float Axmax = Axmin + boundsA.size.width;
    float Aymin = boundsA.origin.y;
    float Aymax = Aymin + boundsA.size.height;

    float Bxmin = boundsB.origin.x;
    float Bxmax = Bxmin + boundsB.size.width;
    float Bymin = boundsB.origin.y;
    float Bymax = Bymin + boundsB.size.height;

    // find location of B corners in A space
    float B0x = mB(0,0) * Bxmin + mB(0,1) * Bymin + mB(0,2);
    float B0y = mB(1,0) * Bxmin + mB(1,1) * Bymin + mB(1,2);

    float B1x = mB(0,0) * Bxmax + mB(0,1) * Bymin + mB(0,2);
    float B1y = mB(1,0) * Bxmax + mB(1,1) * Bymin + mB(1,2);

    float B2x = mB(0,0) * Bxmin + mB(0,1) * Bymax + mB(0,2);
    float B2y = mB(1,0) * Bxmin + mB(1,1) * Bymax + mB(1,2);

    float B3x = mB(0,0) * Bxmax + mB(0,1) * Bymax + mB(0,2);
    float B3y = mB(1,0) * Bxmax + mB(1,1) * Bymax + mB(1,2);

    if(B0x<Axmin && B1x<Axmin && B2x<Axmin && B3x<Axmin)
        return false;
    if(B0x>Axmax && B1x>Axmax && B2x>Axmax && B3x>Axmax)
        return false;
    if(B0y<Aymin && B1y<Aymin && B2y<Aymin && B3y<Aymin)
        return false;
    if(B0y>Aymax && B1y>Aymax && B2y>Aymax && B3y>Aymax)
        return false;

    float det = mB(0,0)*mB(1,1) - mB(0,1)*mB(1,0);
    float dx = mB(1,2)*mB(0,1) - mB(0,2)*mB(1,1);
    float dy = mB(0,2)*mB(1,0) - mB(1,2)*mB(0,0);

    // find location of A corners in B space
    float A0x = (mB(1,1) * Axmin - mB(0,1) * Aymin + dx)/det;
    float A0y = (-mB(1,0) * Axmin + mB(0,0) * Aymin + dy)/det;

    float A1x = (mB(1,1) * Axmax - mB(0,1) * Aymin + dx)/det;
    float A1y = (-mB(1,0) * Axmax + mB(0,0) * Aymin + dy)/det;

    float A2x = (mB(1,1) * Axmin - mB(0,1) * Aymax + dx)/det;
    float A2y = (-mB(1,0) * Axmin + mB(0,0) * Aymax + dy)/det;

    float A3x = (mB(1,1) * Axmax - mB(0,1) * Aymax + dx)/det;
    float A3y = (-mB(1,0) * Axmax + mB(0,0) * Aymax + dy)/det;

    if(A0x<Bxmin && A1x<Bxmin && A2x<Bxmin && A3x<Bxmin)
        return false;
    if(A0x>Bxmax && A1x>Bxmax && A2x>Bxmax && A3x>Bxmax)
        return false;
    if(A0y<Bymin && A1y<Bymin && A2y<Bymin && A3y<Bymin)
        return false;
    if(A0y>Bymax && A1y>Bymax && A2y>Bymax && A3y>Bymax)
        return false;

    return true;
}

矩阵mB是将B空间中的点转换为A空间中的点的任何仿射变换矩阵。这包括简单的旋转和平移,旋转加缩放以及完全仿射扭曲,但不包括透视扭曲。

它可能不是最佳的。速度并不是一个大问题。但是,这似乎对我来说还可以。


0

这是公认答案的matlab实现:

function olap_flag = ol(A,B,sub)

%A and B should be 4 x 2 matrices containing the xy coordinates of the corners in clockwise order

if nargin == 2
  olap_flag = ol(A,B,1) && ol(B,A,1);
  return;
end

urdl = diff(A([1:4 1],:));
s = sum(urdl .* A, 2);
sdiff = B * urdl' - repmat(s,[1 4]);

olap_flag = ~any(max(sdiff)<0);

0

这是常规方法,逐行检查行是否相交。这是MATLAB中的代码。

C1 = [0, 0];    % Centre of rectangle 1 (x,y)
C2 = [1, 1];    % Centre of rectangle 2 (x,y)
W1 = 5; W2 = 3; % Widths of rectangles 1 and 2
H1 = 2; H2 = 3; % Heights of rectangles 1 and 2
% Define the corner points of the rectangles using the above
R1 = [C1(1) + [W1; W1; -W1; -W1]/2, C1(2) + [H1; -H1; -H1; H1]/2];
R2 = [C2(1) + [W2; W2; -W2; -W2]/2, C2(2) + [H2; -H2; -H2; H2]/2];

R1 = [R1 ; R1(1,:)] ;
R2 = [R2 ; R2(1,:)] ;

plot(R1(:,1),R1(:,2),'r')
hold on
plot(R2(:,1),R2(:,2),'b')


%% lines of Rectangles 
L1 = [R1(1:end-1,:) R1(2:end,:)] ;
L2 = [R2(1:end-1,:) R2(2:end,:)] ;
%% GEt intersection points
P = zeros(2,[]) ;
count = 0 ;
for i = 1:4
    line1 = reshape(L1(i,:),2,2) ;
    for j = 1:4
        line2 = reshape(L2(j,:),2,2) ;
        point = InterX(line1,line2) ;
        if ~isempty(point)
            count = count+1 ;
            P(:,count) = point ;
        end
    end
end
%%
if ~isempty(P)
    fprintf('Given rectangles intersect at %d points:\n',size(P,2))
    plot(P(1,:),P(2,:),'*k')
end

可以从以下网址下载InterX的功能:https ://in.mathworks.com/matlabcentral/fileexchange/22441-curve-intersections?focused=5165138&tab = function


0

如果我们有2个矩形,则我有一个更简单的方法:

R1 =(min_x1,max_x1,min_y1,max_y1)

R2 =(min_x2,max_x2,min_y2,max_y2)

当且仅当:

重叠=(max_x1> min_x2)和(max_x2> min_x1)和(max_y1> min_y2)和(max_y2> min_y1)

您也可以为3D盒子做,实际上它可以用于任意数量的尺寸。


0

在其他答案中已经说够了,所以我只添加伪代码单行代码:

!(a.left > b.right || b.left > a.right || a.top > b.bottom || b.top > a.bottom);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.