计算2D边界框相交的最快方法是什么?


62

假定每个Box对象都具有属性x,y,宽度,高度,并且其原点位于其中心,并且对象和边界框都不旋转。


这些是轴或对象对齐的边界框吗?
tenpn

3
当您提出这个问题时,将来肯定会需要测试其他类型的交叉点;)。因此,我建议有关对象/对象相交的列表。该表提供了静态和动态情况下所有常用对象类型(框,球体,三角形,自行车,圆锥体,...)之间的交集。
Dave O.

2
请把您的问题改成边界矩形。从我的角度来看,该框表示一个3d对象。
Dave O.

Answers:


55

(C-ish伪代码-适当地调整语言优化)

bool DoBoxesIntersect(Box a, Box b) {
  return (abs(a.x - b.x) * 2 < (a.width + b.width)) &&
         (abs(a.y - b.y) * 2 < (a.height + b.height));
}

用英语:在每个轴上,检查框的中心是否足够靠近以至于它们会相交。如果它们在两个轴上相交,则框相交。如果他们不这样做,那么他们就不这样做。

如果要将边缘触摸视为相交,可以将<更改为<=。如果需要特定的仅边接触的公式,则不能使用==-它将告诉您角是否接触,而不是边是否接触。从逻辑上讲,您想做些等效的事情return DoBoxesIntersectOrTouch(a, b) && !DoBoxesIntersect(a, b)

值得一提的是,通过存储(而不是)全宽和全高的一半宽度和一半高度,您可以得到一个很小但明显的速度提高。另一方面,二维边界框相交很少会成为性能瓶颈。


9
显然,这假定盒子是对齐的。
tenpn

1
Abs不应特别慢-至少不比有条件的慢,并且不使用Abs(我知道)的唯一方法就是额外的条件。
ZorbaTHut

4
是的,它确实假定轴对齐的框。但是,所描述的结构没有任何指示旋转的方法,因此我觉得这很安全。
ZorbaTHut

3
以下是一些加快ActionScript计算速度的好技巧(主要是整数calc):lab.polygonal.de/2007/05/10/bitwise-gems-fast-integer-math我发布了此信息,因为它还包含更快的速度Math.abs()的替代品,它确实确实会减慢Actionscript中的速度(当然,这对性能至关重要)。
bummzack

2
您缺少它们的原点在中心而不是在左边缘。从0到10的框实际上将具有“ x = 5”,而从8到12的框将具有“ x = 10”。您最终得到abs(5 - 10) * 2 < (10 + 4)=> 10 < 14。您需要进行一些简单的调整,以使其与topleft-corner-and-size一起使用。
ZorbaTHut 2015年

37

这适用于与X和Y轴对齐的两个矩形。
每个矩形都具有以下属性:
“左侧”,左侧的x坐标,
“顶部”,顶部的y坐标,
“右侧”,右侧的x坐标,
“底部”,坐标的y坐标它的底面

function IntersectRect(r1:Rectangle, r2:Rectangle):Boolean {
    return !(r2.left > r1.right
        || r2.right < r1.left
        || r2.top > r1.bottom
        || r2.bottom < r1.top);
}

请注意,这是针对+ y轴指向下方且+ x轴指向右侧(即典型的屏幕/像素坐标)的坐标系而设计的。为了使其适应于典型的笛卡尔系统,其中+ y指向上方,沿垂直轴的比较将被反转,例如:

return !(r2.left > r1.right
    || r2.right < r1.left
    || r2.top < r1.bottom
    || r2.bottom > r1.top);

想法是捕获矩形不会重叠的所有可能条件,然后取反答案以查看矩形是否重叠。无论轴的方向如何,很容易看到两个矩形在以下情况下不会重叠:

  • r2的左边缘比r1的右边缘更右

     ________     ________
    |        |   |        |
    |   r1   |   |   r2   |
    |        |   |        |
    |________|   |________|
  • r2的右边缘比r1的左边缘更左

     ________     ________
    |        |   |        |
    |   r2   |   |   r1   |
    |        |   |        |
    |________|   |________|
  • r2的上边缘低于r1的下边缘

     ________ 
    |        |
    |   r1   |
    |        |
    |________|
     ________ 
    |        |
    |   r2   |
    |        |
    |________|
  • r2的下边缘在r1的上边缘之上

     ________ 
    |        |
    |   r2   |
    |        |
    |________|
     ________ 
    |        |
    |   r1   |
    |        |
    |________|

原始功能-及其起作用原因的替代说明-可以在以下位置找到:http : //tekpool.wordpress.com/2006/10/11/rectangle-intersection-determine-if-two-given-rectangles-intersect-彼此或不/


1
出人意料的直观,再次表明,当找到答案太困难时,尝试找到相反问题的答案可能会帮助您。谢谢!
Lodewijk 2014年

1
您应该提到y轴指向下方(如图中所示)。否则,不等式r2.top> r1.bottom和r2.bottom <r1.top需要逆转。
user1443778

@ user1443778好抓住!我继续以坐标系不可知的方式松散地解释了该算法的逻辑。
Ponkadoodle

11

如果需要对象对齐的边界框,请尝试通过metanet 在分离轴定理上学习本教程:http : //www.metanetsoftware.com/technique/tutorialA.html

SAT不是最快的解决方案,但是相对来说比较简单。您试图找到一条可以分隔对象的直线(如果是3D,则为平面)。如果存在这条线,则确保与其中一个框的边缘平行,因此您遍历所有边缘测试以查看是否将框分开。

通过仅约束到x / y轴,这也适用于轴对齐的框。


我没有考虑轮换,但是谢谢,这是一个有趣的链接。
伊恩

5

上面的DoBoxesIntersect是一个很好的成对解决方案。但是,如果您有很多盒子,您仍然会遇到O(N ^ 2)问题,您可能会发现需要在上面做一些类似Kaj所指的事情。(在3D碰撞检测文献中,这被称为同时具有宽相和窄相算法。我们将非常快地执行操作以找到所有可能的重叠对,然后花费更多的钱来查看是否可能对是实际的对。)

我以前使用的宽相算法是“扫描修剪”。对于2D,您需要维护每个框的开始和结束的两个排序列表。只要框的移动不是>>框到框的缩放比例,这些列表的顺序就不会改变很多,因此您可以使用冒泡或插入排序来维护它。《实时渲染》一书很好地记载了您可以进行的优化,但是在宽泛的阶段它可以归结为O(N + K)个时间,适用于N个盒子,其中K个重叠,并且具有出色的真实世界如果您负担得起N ^ 2布尔值来跟踪哪些对盒子在帧与帧之间相交,则可以提高性能。这样一来,您的总时间为O(N + K ^ 2),如果您有很多箱子,但只有很少的重叠,则为<< O(N ^ 2)。


5

这里有一个非常简单的问题的大量数学运算,假设我们为矩形,顶部,左侧,底部,右侧确定了4个点...

在确定2个矩形是否发生碰撞的情况下,我们只需要查看所有可能防止碰撞的极端,如果没有一个满足,则2个矩形必须发生碰撞,如果要包括边界碰撞,只需替换>和<使用适当的> =和= <。

struct aRect{
  float top;
  float left;
  float bottom;
  float right;
};

bool rectCollision(rect a, rect b)
{
  return ! ( b.left > a.right || b.right < a.left || b.top < a.bottom || b.bottom > a.top);
}

老实说,我不确定为什么这不是最受好评的答案。简单,正确和有效。
3Dave

3

ZorbaTHut的答案的替代版本:

bool DoBoxesIntersect(Box a, Box b) {
     return (abs(a.x - b.x) < (a.width + b.width) / 2) &&
     (abs(a.y - b.y) < (a.height + b.height) / 2);
}

实际上,无论哪种方式,该算法都能正常工作。您可以对<的任何一侧进行任何算术运算,并且不会更改它(乘以负数表示您必须更改小于)。在那个例子中,盒子不应该碰撞。如果方框A的中心在1处,它的范围是-4到6。方框b在10处中心,并且范围是7.5到12.5,那么那里就没有碰撞了……现在,Wallacoloo发布的方法不仅是正确的,但在实现短路的语言上运行速度会更快,因为大多数检查无论如何都会返回false,因此可以在以下情况后消除短路
Deleter 2010年

是的,我今天早上醒来时才意识到这一点。克里斯(Chris)用他的<>把我弄乱了。
伊恩

1
有两个问题:首先,除法往往比乘法慢得多。其次,如果涉及的值是整数,则可能会导致一些整数截断问题(ax = 0,bx = 9,a.width = 9,b.width = 10:abs(0-9)<(9 + 10) / 2,9 <19
2,9

2

根据您尝试解决的问题,最好在移动对象时跟踪对象,例如,保留一个排序的x起始和结束位置列表以及一个用于y起始和结束位置的列表。如果您必须进行大量重叠检查并因此需要优化,则可以利用它来发挥自己的优势,因为您可以立即查找谁的结尾靠近您的左侧,每个结尾的左侧都可以修剪立即。同样适用于顶部,底部和右侧。
记账当然要花费时间,因此它更适合于移动对象少而重叠检查多的情况。
另一个选择是空间散列,您可以根据近似位置对对象进行存储(大小可能将em放入多个存储桶中),但是只有在存在大量对象的情况下,由于记账成本的缘故,每次迭代中移动的对象相对较少,因此存在这种情况。
基本上,任何避免(n * n)/ 2的操作(如果您针对b检查对象a,您显然不必针对a检查b)都比优化边界框检查更有帮助。如果边界框检查是瓶颈,那么我强烈建议您研究解决该问题的替代解决方案。


2

中心之间的距离与拐角之间的距离(例如,一个盒子在另一个盒子内)不同,因此,总的来说,这种解决方案是正确的(我认为)。

中心之间的距离(例如x):abs(x1+1/2*w1 - x2+1/2*w2)1/2 * abs(2*(x1-x2)+(w1-w2)

最小距离为1/2 w1 + 1/2 w2 or 1/2 (w1+w2)。一半抵消了..

return 
ABS(2*(x1 - x2) + (w1-w2) ) < (w1+w2)) &&
ABS(2*(y1 - y2) + (h1-h2) ) < (h1+h2));

1
那里的“ return”语句是什么?
doppelgreener 2012年

1

这是我在Java中的实现,假设采用二进制补码架构。如果您不使用二进制补码,请使用标准的Math.abs函数调用:

boolean intersects(IntAxisAlignedBox left, IntAxisAlignedBox right) {
    return
        (
            lineDeltaFactor(left.min.x, left.max.x, right.min.x, right.max.x) |
            lineDeltaFactor(left.min.y, left.max.y, right.min.y, right.max.y) |
            lineDeltaFactor(left.min.z, left.max.z, right.min.z, right.max.z)
        ) == 0;
}

int lineDeltaFactor(int leftMin, int leftMax, int rightMin, int rightMax) {
    final int
            leftWidth = leftMax - leftMin,
            rightWidth = rightMax - rightMin,

            leftMid = leftMin + ((leftMax - leftMin) >> 1),
            rightMid = rightMin + ((rightMax - rightMin) >> 1);

    return (abs(leftMid - rightMid) << 1) / (leftWidth + rightWidth + 1);
}

int abs(int value) {
    final int mask = value >> (Integer.SIZE - 1);

    value ^= mask;
    value += mask & 1;
    return value;
}

假设一个不错的编译器/ LLVM内联扩展了这些功能,以避免昂贵的堆栈处理和v表查找。对于接近32位极限(即和)的输入值,这将失败Integer.MAX_VALUEInteger.MIN_VALUE


0

最快的方法是将所有4个值组合在一个向量寄存器中。

将框存储在具有以下值的向量中[ min.x, min.y, -max.x, -max.y ]。如果存储这样的盒子,则交集测试仅需要3条CPU指令:

_mm_shuffle_ps 重新排列第二个框的最小和最大一半的顺序。

_mm_xor_ps用魔术数字_mm_set1_ps(-0.0f)翻转第二个框中所有4个值的符号。

_mm_cmple_ps 比较以下两个寄存器,将所有4个值相互比较:

[ a.min.x, a.min.y, -a.max.x, -a.max.y ] < [ b.max.x, b.max.y, -b.min.x, -b.min.y ]

最后,如果需要的话,_mm_movemask_ps可以将结果从向量单位中提取到标量寄存器中。值0表示盒子相交。或者,如果不需要两个以上的框,则将值保留在向量寄存器中,并使用按位运算来组合多个框的结果。

您尚未指定语言或平台,但是所有平台和语言都提供了对此类或非常类似的SIMD的支持。在移动设备上,ARM具有类似的NEON SIMD。.NET在System.Runtime.Intrinsics命名空间中具有Vector128,依此类推。

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.