Answers:
(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)
。
值得一提的是,通过存储(而不是)全宽和全高的一半宽度和一半高度,您可以得到一个很小但明显的速度提高。另一方面,二维边界框相交很少会成为性能瓶颈。
abs(5 - 10) * 2 < (10 + 4)
=> 10 < 14
。您需要进行一些简单的调整,以使其与topleft-corner-and-size一起使用。
这适用于与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-彼此或不/
如果需要对象对齐的边界框,请尝试通过metanet 在分离轴定理上学习本教程:http : //www.metanetsoftware.com/technique/tutorialA.html
SAT不是最快的解决方案,但是相对来说比较简单。您试图找到一条可以分隔对象的直线(如果是3D,则为平面)。如果存在这条线,则确保与其中一个框的边缘平行,因此您遍历所有边缘测试以查看是否将框分开。
通过仅约束到x / y轴,这也适用于轴对齐的框。
上面的DoBoxesIntersect是一个很好的成对解决方案。但是,如果您有很多盒子,您仍然会遇到O(N ^ 2)问题,您可能会发现需要在上面做一些类似Kaj所指的事情。(在3D碰撞检测文献中,这被称为同时具有宽相和窄相算法。我们将非常快地执行操作以找到所有可能的重叠对,然后花费更多的钱来查看是否可能对是实际的对。)
我以前使用的宽相算法是“扫描修剪”。对于2D,您需要维护每个框的开始和结束的两个排序列表。只要框的移动不是>>框到框的缩放比例,这些列表的顺序就不会改变很多,因此您可以使用冒泡或插入排序来维护它。《实时渲染》一书很好地记载了您可以进行的优化,但是在宽泛的阶段它可以归结为O(N + K)个时间,适用于N个盒子,其中K个重叠,并且具有出色的真实世界如果您负担得起N ^ 2布尔值来跟踪哪些对盒子在帧与帧之间相交,则可以提高性能。这样一来,您的总时间为O(N + K ^ 2),如果您有很多箱子,但只有很少的重叠,则为<< O(N ^ 2)。
这里有一个非常简单的问题的大量数学运算,假设我们为矩形,顶部,左侧,底部,右侧确定了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);
}
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);
}
根据您尝试解决的问题,最好在移动对象时跟踪对象,例如,保留一个排序的x起始和结束位置列表以及一个用于y起始和结束位置的列表。如果您必须进行大量重叠检查并因此需要优化,则可以利用它来发挥自己的优势,因为您可以立即查找谁的结尾靠近您的左侧,每个结尾的左侧都可以修剪立即。同样适用于顶部,底部和右侧。
记账当然要花费时间,因此它更适合于移动对象少而重叠检查多的情况。
另一个选择是空间散列,您可以根据近似位置对对象进行存储(大小可能将em放入多个存储桶中),但是只有在存在大量对象的情况下,由于记账成本的缘故,每次迭代中移动的对象相对较少,因此存在这种情况。
基本上,任何避免(n * n)/ 2的操作(如果您针对b检查对象a,您显然不必针对a检查b)都比优化边界框检查更有帮助。如果边界框检查是瓶颈,那么我强烈建议您研究解决该问题的替代解决方案。
中心之间的距离与拐角之间的距离(例如,一个盒子在另一个盒子内)不同,因此,总的来说,这种解决方案是正确的(我认为)。
中心之间的距离(例如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));
这是我在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_VALUE
Integer.MIN_VALUE
最快的方法是将所有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,依此类推。