确定两个矩形是不是相互重叠?
Posted
技术标签:
【中文标题】确定两个矩形是不是相互重叠?【英文标题】:Determine if two rectangles overlap each other?确定两个矩形是否相互重叠? 【发布时间】:2010-09-23 07:23:39 【问题描述】:我正在尝试编写一个 C++ 程序,它接受用户的以下输入来构造矩形(2 到 5 之间):高度、宽度、x-pos、y-pos。所有这些矩形都将平行于 x 轴和 y 轴存在,也就是说,它们的所有边的斜率为 0 或无穷大。
我已尝试实现this 问题中提到的内容,但运气不佳。
我当前的实现如下:
// Gets all the vertices for Rectangle 1 and stores them in an array -> arrRect1
// point 1 x: arrRect1[0], point 1 y: arrRect1[1] and so on...
// Gets all the vertices for Rectangle 2 and stores them in an array -> arrRect2
// rotated edge of point a, rect 1
int rot_x, rot_y;
rot_x = -arrRect1[3];
rot_y = arrRect1[2];
// point on rotated edge
int pnt_x, pnt_y;
pnt_x = arrRect1[2];
pnt_y = arrRect1[3];
// test point, a from rect 2
int tst_x, tst_y;
tst_x = arrRect2[0];
tst_y = arrRect2[1];
int value;
value = (rot_x * (tst_x - pnt_x)) + (rot_y * (tst_y - pnt_y));
cout << "Value: " << value;
但是我不太确定 (a) 我是否已经正确地实现了我链接到的算法,或者我是否做了正确的解释?
有什么建议吗?
【问题讨论】:
我认为您的问题的解决方案不涉及任何乘法。 如果您需要旋转矩形的答案,我会创建一个包含所有步骤的答案:***.com/questions/62028169/…(它在 javascript 中,但可以在 C++ 中轻松复制) 【参考方案1】:if (RectA.Left < RectB.Right && RectA.Right > RectB.Left &&
RectA.Top > RectB.Bottom && RectA.Bottom < RectB.Top )
或者,使用笛卡尔坐标
(X1为左坐标,X2为右坐标,从左到右递增,Y1为上坐标,Y2为下坐标,从下到上递增 > -- 如果这不是您的坐标系[例如大多数计算机的 Y 方向反转],交换下面的比较) ...
if (RectA.X1 < RectB.X2 && RectA.X2 > RectB.X1 &&
RectA.Y1 > RectB.Y2 && RectA.Y2 < RectB.Y1)
假设你有 Rect A 和 Rect B。 证明是矛盾的。四个条件中的任何一个都保证不存在重叠:
条件 1。如果 A 的左边缘在 B 的右边缘的右侧, - 那么 A 完全在 B 的右边 条件 2。如果 A 的右边缘在 B 的左边缘的左边, - 那么 A 完全在 B 的左边 条件 3。如果 A 的上边缘低于 B 的下边缘, - 那么 A 完全低于 B 条件 4。如果 A 的底边高于 B 的顶边, - 那么 A 完全高于 B所以非重叠的条件是
NON-Overlap => Cond1 or Cond2 or Cond3 or Cond4
因此,Overlap 的充分条件是相反的。
重叠 => NOT (Cond1 or Cond2 or Cond3 or Cond4)
德摩根定律说Not (A or B or C or D)
与Not A And Not B And Not C And Not D
相同
所以使用德摩根,我们有
不是 Cond1 也不是 Cond2 不是 Cond3 而不是 Cond4
这相当于:
A 的左边缘到 B 的右边缘的左侧,[RectA.Left < RectB.Right
],以及
A 的右边缘到 B 的左边缘的右侧,[RectA.Right > RectB.Left
],以及
A 的顶部高于 B 的底部,[RectA.Top > RectB.Bottom
],以及
A 的底部低于 B 的顶部 [RectA.Bottom < RectB.Top
]
注 1:很明显,同样的原理可以扩展到任意数量的维度。注 2:计数也应该很明显只有一个像素的重叠,将边界上的<
和/或>
更改为<=
或>=
。注意3:这个答案,当使用笛卡尔坐标 (X, Y) 基于标准代数笛卡尔坐标(x 从左到右增加,Y 从下到上增加)。显然,如果计算机系统可能以不同的方式处理屏幕坐标(例如,从上到下增加 Y,或从右到左增加 X),则需要相应地调整语法/
【讨论】:
如果您很难想象它的工作原理,我在silentmatt.com/intersection.html 制作了一个示例页面,您可以在其中拖动矩形并查看比较。 你不认为你在使用硬约束吗?如果两个矩形正好在边缘重叠怎么办?你不应该考虑 = ?? @MatthewCrumley for A.Y1 B.Y1 在您的链接上,gt & lt 标志不应该颠倒吗? 我必须在最后两次比较中交换 才能使其正常工作 不,答案是正确的。它基于使用标准笛卡尔坐标。如果您使用不同的系统(Y 从上到下递增),请进行适当的调整。【参考方案2】:struct rect
int x;
int y;
int width;
int height;
;
bool valueInRange(int value, int min, int max)
return (value >= min) && (value <= max);
bool rectOverlap(rect A, rect B)
bool xOverlap = valueInRange(A.x, B.x, B.x + B.width) ||
valueInRange(B.x, A.x, A.x + A.width);
bool yOverlap = valueInRange(A.y, B.y, B.y + B.height) ||
valueInRange(B.y, A.y, A.y + A.height);
return xOverlap && yOverlap;
【讨论】:
@e.James 我猜最后一个B.height
应该是A.height
'min' 和 'max' 是 #undef min
和 #undef max
或使用不同的参数名称来修复它。#define BETWEEN(value,min,max) \ (\ value > max ? max : ( value < min ? min : value )\ )
@Nemo 实际上,检查xOverlap
是一维的; rectOverlap
是二维的。它可以使用循环扩展到 N 维。
我不是 100% 确定,但它看起来是错误的。我的情况是:(3, 0, 2, 3) 和 (3, 3, 2, 2)。它们不重叠,但这个函数“说”它们是重叠的。第一个接受的答案适用于这种情况。 (我使用基于网格的 int rects)【参考方案3】:
struct Rect
Rect(int x1, int x2, int y1, int y2)
: x1(x1), x2(x2), y1(y1), y2(y2)
assert(x1 < x2);
assert(y1 < y2);
int x1, x2, y1, y2;
;
bool
overlap(const Rect &r1, const Rect &r2)
// The rectangles don't overlap if
// one rectangle's minimum in some dimension
// is greater than the other's maximum in
// that dimension.
bool noOverlap = r1.x1 > r2.x2 ||
r2.x1 > r1.x2 ||
r1.y1 > r2.y2 ||
r2.y1 > r1.y2;
return !noOverlap;
【讨论】:
不错的一个!应用德摩根定律得到:r1.x1 【参考方案4】:检查一个矩形是否完全在另一个矩形之外更容易,所以如果它是任何一个
在左边……
(r1.x + r1.width < r2.x)
或者在右边……
(r1.x > r2.x + r2.width)
或在顶部...
(r1.y + r1.height < r2.y)
或者在底部...
(r1.y > r2.y + r2.height)
第二个矩形,它不可能与它发生碰撞。因此,为了有一个返回布尔值的函数来表示矩形碰撞的天气,我们只需通过逻辑 OR 组合条件并否定结果:
function checkOverlap(r1, r2) : Boolean
return !(r1.x + r1.width < r2.x || r1.y + r1.height < r2.y || r1.x > r2.x + r2.width || r1.y > r2.y + r2.height);
如果只在触摸时已经收到阳性结果,我们可以将“”更改为“=”。
【讨论】:
并应用德摩根定律。【参考方案5】:假设您已经像这样定义了矩形的位置和大小:
我的 C++ 实现是这样的:
class Vector2D
public:
Vector2D(int x, int y) : x(x), y(y)
~Vector2D()
int x, y;
;
bool DoRectanglesOverlap( const Vector2D & Pos1,
const Vector2D & Size1,
const Vector2D & Pos2,
const Vector2D & Size2)
if ((Pos1.x < Pos2.x + Size2.x) &&
(Pos1.y < Pos2.y + Size2.y) &&
(Pos2.x < Pos1.x + Size1.x) &&
(Pos2.y < Pos1.y + Size1.y))
return true;
return false;
根据上图的示例函数调用:
DoRectanglesOverlap(Vector2D(3, 7),
Vector2D(8, 5),
Vector2D(6, 4),
Vector2D(9, 4));
if
块内的比较如下所示:
if ((Pos1.x < Pos2.x + Size2.x) &&
(Pos1.y < Pos2.y + Size2.y) &&
(Pos2.x < Pos1.x + Size1.x) &&
(Pos2.y < Pos1.y + Size1.y))
↓
if (( 3 < 6 + 9 ) &&
( 7 < 4 + 4 ) &&
( 6 < 3 + 8 ) &&
( 4 < 7 + 5 ))
【讨论】:
Quick Check 用于那些条件工作。如果要将触摸矩形计为重叠,请将所有 【参考方案6】:问自己一个相反的问题:如何确定两个矩形是否完全不相交?显然,完全在矩形 B 左侧的矩形 A 不相交。此外,如果 A 完全在右侧。同样,如果 A 完全高于 B 或完全低于 B。在任何其他情况下,A 和 B 相交。
以下可能有错误,但我对算法很有信心:
struct Rectangle int x; int y; int width; int height; ;
bool is_left_of(Rectangle const & a, Rectangle const & b)
if (a.x + a.width <= b.x) return true;
return false;
bool is_right_of(Rectangle const & a, Rectangle const & b)
return is_left_of(b, a);
bool not_intersect( Rectangle const & a, Rectangle const & b)
if (is_left_of(a, b)) return true;
if (is_right_of(a, b)) return true;
// Do the same for top/bottom...
bool intersect(Rectangle const & a, Rectangle const & b)
return !not_intersect(a, b);
【讨论】:
【参考方案7】:这是用 C++ 检查两个矩形是否重叠的一种非常快速的方法:
return std::max(rectA.left, rectB.left) < std::min(rectA.right, rectB.right)
&& std::max(rectA.top, rectB.top) < std::min(rectA.bottom, rectB.bottom);
它的工作原理是计算相交矩形的左右边框,然后比较它们:如果右边框等于或小于左边框,则表示相交为空,因此矩形不重叠;否则,它会再次尝试使用顶部和底部边框。
与传统的 4 次比较替代方法相比,这种方法有什么优势?这是关于现代处理器的设计方式。他们有一种叫做分支预测的东西,当比较的结果总是相同的时候效果很好,但否则会有巨大的性能损失。但是,在没有分支指令的情况下,CPU 的性能相当不错。通过计算交叉点的边界而不是对每个轴进行两次单独检查,我们节省了两个分支,每对一个。
如果第一次比较很可能是错误的,那么四种比较方法可能会优于这种方法。不过,这种情况非常罕见,因为这意味着第二个矩形通常位于第一个矩形的左侧,而不是在右侧或与其重叠;大多数情况下,您需要检查第一个矩形两侧的矩形,这通常会抵消分支预测的优势。
此方法可以进一步改进,具体取决于矩形的预期分布:
如果您希望选中的矩形主要位于彼此的左侧或右侧,则上述方法效果最佳。例如,当您使用矩形交集来检查游戏的碰撞时,可能就是这种情况,其中游戏对象主要水平分布(例如,类似 SuperMarioBros 的游戏)。 如果您希望选中的矩形主要位于彼此的顶部或底部,例如在冰塔类型的游戏中,首先检查顶部/底部和最后检查左/右可能会更快:return std::max(rectA.top, rectB.top) < std::min(rectA.bottom, rectB.bottom)
&& std::max(rectA.left, rectB.left) < std::min(rectA.right, rectB.right);
如果相交的概率接近于不相交的概率,那么最好有一个完全无分支的替代方案:
return std::max(rectA.left, rectB.left) < std::min(rectA.right, rectB.right)
& std::max(rectA.top, rectB.top) < std::min(rectA.bottom, rectB.bottom);
(注意&&
改为单个&
)
【讨论】:
【参考方案8】:在问题中,您链接到矩形何时处于任意旋转角度的数学。但是,如果我理解问题中关于角度的一点,我会解释为所有矩形都相互垂直。
知道重叠面积的一般公式是:
使用示例:
1 2 3 4 5 6 1 +---+---+ | | 2 + A +---+---+ | |乙| 3 + + +---+---+ | | | | | 4 +---+---+---+---+ + | | 5 + C + | | 6 +---+---+
1)将所有x坐标(左右)收集到一个列表中,然后对其进行排序并删除重复项
1 3 4 5 6
2) 将所有 y 坐标(顶部和底部)收集到一个列表中,然后对其进行排序并删除重复项
1 2 3 4 6
3) 根据唯一 x 坐标之间的间隙数 * 唯一 y 坐标之间的间隙数创建一个二维数组。
4 * 4
4) 将所有矩形绘制到这个网格中,增加它出现的每个单元格的计数:
1 3 4 5 6 1 +---+ | 1 | 0 0 0 2 +---+---+---+ | 1 | 1 | 1 | 0 3 +---+---+---+---+ | 1 | 1 | 2 | 1 | 4 +---+---+---+---+ 0 0 | 1 | 1 | 6 +---+---+5) 绘制矩形时,很容易截取重叠部分。
【讨论】:
【参考方案9】:在 Java API 中是这样完成的:
public boolean intersects(Rectangle r)
int tw = this.width;
int th = this.height;
int rw = r.width;
int rh = r.height;
if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0)
return false;
int tx = this.x;
int ty = this.y;
int rx = r.x;
int ry = r.y;
rw += rx;
rh += ry;
tw += tx;
th += ty;
// overflow || intersect
return ((rw < rx || rw > tx) &&
(rh < ry || rh > ty) &&
(tw < tx || tw > rx) &&
(th < ty || th > ry));
【讨论】:
请注意,在 C++ 中,这些溢出测试将不起作用,因为有符号整数溢出是未定义的。【参考方案10】:struct Rect
Rect(int x1, int x2, int y1, int y2)
: x1(x1), x2(x2), y1(y1), y2(y2)
assert(x1 < x2);
assert(y1 < y2);
int x1, x2, y1, y2;
;
//some area of the r1 overlaps r2
bool overlap(const Rect &r1, const Rect &r2)
return r1.x1 < r2.x2 && r2.x1 < r1.x2 &&
r1.y1 < r2.y2 && r2.x1 < r1.y2;
//either the rectangles overlap or the edges touch
bool touch(const Rect &r1, const Rect &r2)
return r1.x1 <= r2.x2 && r2.x1 <= r1.x2 &&
r1.y1 <= r2.y2 && r2.x1 <= r1.y2;
【讨论】:
【参考方案11】:不要将坐标视为指示像素所在的位置。把它们想象成像素之间。这样一来,2x2 矩形的面积应该是 4,而不是 9。
bool bOverlap = !((A.Left >= B.Right || B.Left >= A.Right)
&& (A.Bottom >= B.Top || B.Bottom >= A.Top));
【讨论】:
【参考方案12】:最简单的方法是
/**
* Check if two rectangles collide
* x_1, y_1, width_1, and height_1 define the boundaries of the first rectangle
* x_2, y_2, width_2, and height_2 define the boundaries of the second rectangle
*/
boolean rectangle_collision(float x_1, float y_1, float width_1, float height_1, float x_2, float y_2, float width_2, float height_2)
return !(x_1 > x_2+width_2 || x_1+width_1 < x_2 || y_1 > y_2+height_2 || y_1+height_1 < y_2);
首先请记住,在计算机中坐标系是颠倒的。 x轴与数学相同,但y轴向下增加,向上减少.. 如果矩形是从中心绘制的。 如果 x1 坐标大于 x2 加上它的一半宽度。那么就意味着走了一半他们会互相接触。并以同样的方式向下 + 其高度的一半。它会碰撞..
【讨论】:
【参考方案13】:假设这两个矩形是矩形A和矩形B。让它们的中心是A1和B1(A1和B1的坐标很容易找到),让高度是Ha和Hb,宽度是Wa和Wb,让dx 是 A1 和 B1 之间的宽度(x)距离,dy 是 A1 和 B1 之间的高度(y)距离。
现在我们可以说我们可以说 A 和 B 重叠:当
if(!(dx > Wa+Wb)||!(dy > Ha+Hb)) returns true
【讨论】:
【参考方案14】:如果矩形重叠,则重叠区域将大于零。现在让我们找到重叠区域:
如果它们重叠,则重叠矩形的左边缘将是max(r1.x1, r2.x1)
,右边缘将是min(r1.x2, r2.x2)
。所以重叠的长度将是min(r1.x2, r2.x2) - max(r1.x1, r2.x1)
所以该区域将是:
area = (max(r1.x1, r2.x1) - min(r1.x2, r2.x2)) * (max(r1.y1, r2.y1) - min(r1.y2, r2.y2))
如果area = 0
则它们不重叠。
是不是很简单?
【讨论】:
这适用于重叠(这是问题),但不适用于交叉,因为如果它们恰好在一个角落相交,它将不起作用。 我试过这段代码,但它根本不起作用。即使它们根本不重叠,我也只是得到正数。 @Brett:是的,因为两个负数的乘积是正数。 @BenVoigt,问题是当没有重叠时函数没有返回 0。我的评论很不清楚,但是是的,我只从这个函数中收到了 area > 0。 如果您使用浮点数,在任何数字比较之前使用减法和其他算术运算通常是一个非常糟糕的主意。特别是如果您需要与精确值进行比较 - 在这种情况下为零。它在理论上有效,但在实践中无效。【参考方案15】:我已经实现了一个 C# 版本,它很容易转换为 C++。
public bool Intersects ( Rectangle rect )
float ulx = Math.Max ( x, rect.x );
float uly = Math.Max ( y, rect.y );
float lrx = Math.Min ( x + width, rect.x + rect.width );
float lry = Math.Min ( y + height, rect.y + rect.height );
return ulx <= lrx && uly <= lry;
【讨论】:
对于受过训练的人来说,很明显您的意思是这是 Rectangle 的扩展类,但您没有提供任何边界或代码来实际执行此操作。如果您这样做或解释这就是您的方法的使用方式,那将是很好的,如果您的变量实际上具有足够的描述性名称,以便任何人理解他们的目的/意图,则可以加分。【参考方案16】:我有一个非常简单的解决方案
设x1,y1 x2,y2,l1,b1,l2,分别为坐标和长宽
考虑条件 ((x2
现在,这些矩形重叠的唯一方法是,如果与 x1,y1 对角线的点位于另一个矩形内,或者类似地,与 x2,y2 对角线的点将位于另一个矩形内。这正是上述条件所暗示的。
【讨论】:
【参考方案17】:A 和 B 是两个矩形。 C 是它们的覆盖矩形。
four points of A be (xAleft,yAtop),(xAleft,yAbottom),(xAright,yAtop),(xAright,yAbottom)
four points of A be (xBleft,yBtop),(xBleft,yBbottom),(xBright,yBtop),(xBright,yBbottom)
A.width = abs(xAleft-xAright);
A.height = abs(yAleft-yAright);
B.width = abs(xBleft-xBright);
B.height = abs(yBleft-yBright);
C.width = max(xAleft,xAright,xBleft,xBright)-min(xAleft,xAright,xBleft,xBright);
C.height = max(yAtop,yAbottom,yBtop,yBbottom)-min(yAtop,yAbottom,yBtop,yBbottom);
A and B does not overlap if
(C.width >= A.width + B.width )
OR
(C.height >= A.height + B.height)
它会处理所有可能的情况。
【讨论】:
【参考方案18】:这是来自《Java 编程简介-综合版》一书的练习 3.28。代码测试两个矩形是否有齿,一个是否在另一个内部,一个是否在另一个外部。如果这些条件都不满足,则两者重叠。
**3.28(几何:两个矩形)编写一个程序,提示用户输入 将两个矩形的 x、y 坐标、宽度和高度居中,并确定 第二个矩形是在第一个矩形内还是与第一个矩形重叠,如图所示 在图 3.9 中。测试您的程序以涵盖所有情况。 以下是示例运行:
输入 r1 的中心 x、y 坐标、宽度和高度:2.5 4 2.5 43 输入 r2 的中心 x、y 坐标、宽度和高度:1.5 5 0.5 3 r2 在 r1 内
输入 r1 的中心 x、y 坐标、宽度和高度:1 2 3 5.5 输入 r2 的中心 x、y 坐标、宽度和高度:3 4 4.5 5 r2 与 r1 重叠
输入 r1 的中心 x、y 坐标、宽度和高度:1 2 3 3 输入 r2 的中心 x、y 坐标、宽度和高度:40 45 3 2 r2 不与 r1 重叠
import java.util.Scanner;
public class ProgrammingEx3_28
public static void main(String[] args)
Scanner input = new Scanner(System.in);
System.out
.print("Enter r1's center x-, y-coordinates, width, and height:");
double x1 = input.nextDouble();
double y1 = input.nextDouble();
double w1 = input.nextDouble();
double h1 = input.nextDouble();
w1 = w1 / 2;
h1 = h1 / 2;
System.out
.print("Enter r2's center x-, y-coordinates, width, and height:");
double x2 = input.nextDouble();
double y2 = input.nextDouble();
double w2 = input.nextDouble();
double h2 = input.nextDouble();
w2 = w2 / 2;
h2 = h2 / 2;
// Calculating range of r1 and r2
double x1max = x1 + w1;
double y1max = y1 + h1;
double x1min = x1 - w1;
double y1min = y1 - h1;
double x2max = x2 + w2;
double y2max = y2 + h2;
double x2min = x2 - w2;
double y2min = y2 - h2;
if (x1max == x2max && x1min == x2min && y1max == y2max
&& y1min == y2min)
// Check if the two are identicle
System.out.print("r1 and r2 are indentical");
else if (x1max <= x2max && x1min >= x2min && y1max <= y2max
&& y1min >= y2min)
// Check if r1 is in r2
System.out.print("r1 is inside r2");
else if (x2max <= x1max && x2min >= x1min && y2max <= y1max
&& y2min >= y1min)
// Check if r2 is in r1
System.out.print("r2 is inside r1");
else if (x1max < x2min || x1min > x2max || y1max < y2min
|| y2min > y1max)
// Check if the two overlap
System.out.print("r2 does not overlaps r1");
else
System.out.print("r2 overlaps r1");
【讨论】:
【参考方案19】:bool Square::IsOverlappig(Square &other)
bool result1 = other.x >= x && other.y >= y && other.x <= (x + width) && other.y <= (y + height); // other's top left falls within this area
bool result2 = other.x >= x && other.y <= y && other.x <= (x + width) && (other.y + other.height) <= (y + height); // other's bottom left falls within this area
bool result3 = other.x <= x && other.y >= y && (other.x + other.width) <= (x + width) && other.y <= (y + height); // other's top right falls within this area
bool result4 = other.x <= x && other.y <= y && (other.x + other.width) >= x && (other.y + other.height) >= y; // other's bottom right falls within this area
return result1 | result2 | result3 | result4;
【讨论】:
【参考方案20】:对于那些在矩形数据中使用中心点和一半大小的人,而不是典型的 x、y、w、h 或 x0、y0、x1、x1,您可以这样做:
#include <cmath> // for fabsf(float)
struct Rectangle
float centerX, centerY, halfWidth, halfHeight;
;
bool isRectangleOverlapping(const Rectangle &a, const Rectangle &b)
return (fabsf(a.centerX - b.centerX) <= (a.halfWidth + b.halfWidth)) &&
(fabsf(a.centerY - b.centerY) <= (a.halfHeight + b.halfHeight));
【讨论】:
【参考方案21】:struct point int x, y; ;
struct rect point tl, br; ; // top left and bottom right points
// return true if rectangles overlap
bool overlap(const rect &a, const rect &b)
return a.tl.x <= b.br.x && a.br.x >= b.tl.x &&
a.tl.y >= b.br.y && a.br.y <= b.tl.y;
【讨论】:
以上是关于确定两个矩形是不是相互重叠?的主要内容,如果未能解决你的问题,请参考以下文章