我正在寻找一种算法来检测两个矩形是否相交(一个以任意角度相交,另一个仅以垂直/水平线相交)。
测试一个角是否在另一个ALMOST中是可行的。如果矩形形成十字形,则失败。
避免使用直线的斜率似乎是个好主意,因为直线的斜率需要特殊情况。
我正在寻找一种算法来检测两个矩形是否相交(一个以任意角度相交,另一个仅以垂直/水平线相交)。
测试一个角是否在另一个ALMOST中是可行的。如果矩形形成十字形,则失败。
避免使用直线的斜率似乎是个好主意,因为直线的斜率需要特殊情况。
Answers:
标准方法是进行分离轴测试(对此进行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
基本上看下图:
如果两个框碰撞,则线A和B将重叠。
请注意,这必须同时在X和Y轴上进行,并且都需要重叠以使矩形发生碰撞。
gamasutra.com上有一篇很好的文章可以回答这个问题(图片来自该文章)。我在5年前做过类似的算法,所以我必须找到我的代码段才能稍后在此处发布
修正:分离轴定理指出,如果存在分离轴,则两个凸形不重叠(即,图中所示的投影不重叠的凸形)。因此,“存在分离轴” =>“无重叠”。这不是双向含义,因此您不能得出相反的结论。
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
在可可中,您可以轻松地检测出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;
}
检查一个矩形中的任何线是否与另一矩形中的任何线相交。朴素的线段交点很容易编码。
如果需要更高的速度,则可以使用高级算法来处理线段交点(扫线)。参见http://en.wikipedia.org/wiki/Line_segment_intersection
对于这个问题的3D版本,这就是我要做的事情:
将两个矩形建模为方程P1和P2所描述的平面,然后写P1 = P2并从中得出相交线方程,如果平面平行(无相交)或在同一平面中,则该相交线将不存在,在这种情况下,您将获得0 = 0。在这种情况下,您将需要使用2D矩形相交算法。
然后,我将查看位于两个矩形平面中的那条线是否穿过两个矩形。如果是这样,则您有两个矩形的交集,否则就没有(或者不应该,我可能会错过一个脑袋)。
为了确定一条线是否穿过同一平面中的一个矩形,我会找到该线和矩形边的2个交点(使用线方程对它们进行建模),然后确保交点与in范围。
那是数学上的描述,不幸的是我没有上面的代码来做。
进行测试的另一种方法比使用分离轴测试要快一些,该方法是在任意矩形(任意选择)的每个顶点上使用绕组数算法(仅在象限上,而不是在角度上缓慢的角度求和)。如果任何一个顶点的绕组数不为零,则两个矩形重叠。
该算法比分离轴测试耗时更长,但速度更快,因为如果边线跨越两个象限,则仅需要半平面测试(与之相比,使用分离轴方法最多进行32个测试)
该算法的另一个优点是可以用于测试任何多边形(凸面或凹面)的重叠。据我所知,该算法仅适用于2D空间。
我是否想念其他东西,为什么使它变得如此复杂?
如果(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");}
我是这样实现的:
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空间中的点的任何仿射变换矩阵。这包括简单的旋转和平移,旋转加缩放以及完全仿射扭曲,但不包括透视扭曲。
它可能不是最佳的。速度并不是一个大问题。但是,这似乎对我来说还可以。
这是公认答案的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);
这是常规方法,逐行检查行是否相交。这是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