我正在寻找一种算法,用于检测两个矩形是否相交(一个在任意角度,另一个只有垂直/水平线)。
测试一个矩形的角落是否在另一个矩形内几乎可行。如果矩形呈十字型,则该方法会失败。
避免使用线段斜率似乎是个好主意,因为这需要针对垂直线进行特殊处理。
我正在寻找一种算法,用于检测两个矩形是否相交(一个在任意角度,另一个只有垂直/水平线)。
测试一个矩形的角落是否在另一个矩形内几乎可行。如果矩形呈十字型,则该方法会失败。
避免使用线段斜率似乎是个好主意,因为这需要针对垂直线进行特殊处理。
通常的方法是进行分离轴测试(在谷歌上搜索此内容)。
简而言之:
有趣的是,只需检查两个矩形的所有边缘即可。如果矩形不重叠,则其中一个边缘将成为分离轴。
在二维情况下,可以在不使用斜率的情况下完成这项工作。边缘仅定义为两个顶点之间的差异,例如:
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);
现在,对于矩形A的所有点和矩形B的边界进行测试,反之亦然。如果您发现一个分离的边缘,这些对象不会相交(假设B中的所有其他点都在正在测试的边缘的另一侧-请参阅下面的图示)。如果您找不到分离的边缘,则矩形相交或一个矩形包含另一个矩形。
顺带一提,该测试适用于任何凸多边形。
更正:为了确定一个分离的边缘,仅测试一个矩形的所有点是否与另一个矩形的每条边相交是不够的。候选边缘E(如下图所示)将被识别为分离的边缘,因为所有A中的点都在E的同一半平面中。但是,它不是一个分离的边缘,因为B的顶点Vb1和Vb2也在该半平面中。只有在那种情况下,它才是一个分离的边缘。 http://www.iassess.com/collision.png
基本上看一下以下图片:
如果这两个矩形重叠,线段A和B将会重叠。
需要注意的是,在X轴和Y轴上都需要进行重叠判断,只有当两个矩形在X轴和Y轴上都有重叠时才算碰撞。
gamasutra.com 上有一篇很好的文章回答了这个问题(该图片来自该文章)。 5年前我做过类似的算法,我必须找到我的代码片段以后在这里发布。
修正:分离轴定理表明,如果存在一条分离轴(即它们的投影不重叠),则两个凸形状不会重叠。 因此,“存在分离轴”=>“没有重叠”。 这不是一个双向蕴含,因此您不能得出逆命题。
- (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;
}
有关分离轴测试的被接受的答案很有启发性,但我仍然觉得它不容易应用。我将分享我想到的伪代码,首先使用包围圆测试(请参见此其他答案)进行“优化”,以防可能有助于其他人。 我考虑了大小相同的两个矩形A和B(但是考虑一般情况很简单)。
function isRectangleACollidingWithRectangleB:
if d > 2 * R:
return False
...
function isRectangleACollidingWithRectangleB:
...
#Consider first rectangle A:
Si-1 = Vertex_A[4] - Vertex_A[1]
for i in Vertex_A:
Si+1 = Vertex_A[i+1] - Vertex_A[i]
Ni = [- Si+1.y, Si+1.x ]
sgn_i = sign( dot_product(Si-1, Ni) ) #sgn_i is the sign of rectangle A with respect the separating axis
for j in Vertex_B:
sij = Vertex_B[j] - Vertex_A[i]
sgn_j = sign( dot_product(sij, Ni) ) #sgnj is the sign of vertex j of square B with respect the separating axis
if sgn_i * sgn_j > 0: #i.e., we have the same sign
break #Vertex i does not define separating axis
else:
if j == 4: #we have reached the last vertex so vertex i defines the separating axis
return False
Si-1 = - Si+1
#Repeat for rectangle B
...
#If we do not find any separating axis
return True
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
}
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
}
区别在于宽度'现在有些不同:
rectA的宽度A':Math.sqrt(rectA.width*rectA.width + rectA.height*rectA.height) * Math.cos(rectA.angle)
rectB的宽度B':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(Game Development Conference 2007)PPT。您可以通过www.realtimecollisiondetection.net/pubs/GDC07_Ericson_Physics_Tutorial_SAT.ppt链接获取。
检查一个矩形的任意一条线段是否与另一个矩形的任意一条线段相交。朴素的线段相交算法易于编写。
如果需要更快的速度,可以使用高级算法进行线段相交(扫描线)。请参阅http://en.wikipedia.org/wiki/Line_segment_intersection
我认为以下方法可以处理所有可能的情况。执行以下测试。
如果以上两个测试都返回false,则这两个矩形不重叠。
我有自己更简单的方法,如果我们有两个矩形:
R1 =(min_x1,max_x1,min_y1,max_y1)
R2 =(min_x2,max_x2,min_y2,max_y2)
它们重叠当且仅当:
Overlap =(max_x1 > min_x2)and(max_x2 > min_x1)and(max_y1 > min_y2)and(max_y2 > min_y1)
你也可以用这种方法来判断3D盒子,实际上它适用于任意维度。
另一种比使用分离轴测试稍微快一些的测试方法是在每个矩形(任意选择)的每个顶点上使用绕数算法(仅限象限 - 不是角度总和,后者速度极慢)。如果任何一个顶点具有非零绕数,则两个矩形重叠。
这种算法比分离轴测试略显冗长,但更快,因为它只需要半平面测试,如果边缘穿过两个象限(而不是使用分离轴方法最多需要32次测试)。
该算法进一步的优势是可以用于测试任何多边形(凸多边形或凹多边形)的重叠。据我所知,该算法仅适用于2D空间。