碰撞检测是否总是O(n ^ 2)?


14

物理引擎是否能够例如通过将彼此靠近的对象分组并检查该组内部而不是与所有对象的碰撞来降低复杂性?(例如,可以通过查看远处物体的速度和与其他物体的距离来将其从一个组中移除)。

如果不是,那么这对于球体(在3d中)或圆盘(在2d中)是否会使碰撞变得微不足道?我应该做一个双循环,还是创建一个成对的数组?

编辑:对于子弹和box2d之类的物理引擎,碰撞检测是否仍为O(N ^ 2)?


12
两个字:空间划分
MichaelHouse


1
你打赌 我相信两者都有SAP(Sweep和Prune)的实现(以及其他实现),这是一种O(n log(n))算法。搜索“宽相碰撞检测”以了解更多信息。
MichaelHouse

2
@ Byte56 Sweep and Prune仅在每次测试都需要排序时才具有复杂度O(n log(n))。您想保留一个排序的对象列表,并且每次添加一个对象时,只需将其排序到正确的位置O(log(n))即可,因此得到O(log(n)+ n)= O(n)。当物体开始移动时,它变得非常复杂!
MartinTeeVarga 2013年

1
@ sm4,如果移动受到限制,则可以通过几次冒泡排序来解决此问题(只需标记已移动的对象,然后将它们在数组中向前或向后移动,直到它们被排序为止。请小心其他移动的对象
棘轮怪胎

Answers:


14

在最坏的情况下,空间划分始终为O(N ^ 2),这就是信息学的复杂性所在。

但是,有些算法可以在线性时间O(N)中工作。它们全部基于某种扫掠线。

基本上,您需要按一个坐标对对象进行排序。假设X。如果您在冲突检测之前每次都执行排序,那么复杂度将为O(N * logN)。技巧是仅在将对象添加到场景中时进行排序,然后在场景中的某些内容发生更改时进行排序。移动后进行排序并非无关紧要。请参阅下面的链接文章,以了解可以移动并且仍在线性时间内工作的算法。

然后您从左向右扫动。每次扫掠线穿过对象的起点时,都将其放入临时列表中。每次扫掠线退出对象时,都将其从列表中取出。您仅在此临时列表内考虑冲突。

在最坏的情况下,朴素的扫掠线也为O(N ^ 2)(使所有对象从左到右跨整个地图),但是可以通过使其更智能来使其变为O(N)(请参见下面的链接)。一个真正好的算法将非常复杂。

这是简单的示意图,扫掠线是如何工作的:

扫线算法

该线从左向右扫动。对象按X坐标排序。

  • 情况一:检查前两个对象。别的都无所谓。
  • 情况二:第一个对象已检查并从列表中消失。检查两个和三个。
  • 情况三:即使该对象发生碰撞,我们也不会检查。
  • 案例四:因为我们在这种情况下检查!

这样的算法的复杂度为O(C * N)= O(N)。

资料来源:两年的计算几何课程。

在冲突检测中,这通常称为Sweep和Prune,但是算法的扫描线系列在许多其他领域中很有用。

我认为还建议阅读的书超出了这个问题的范围,但仍然很有趣:具有AABB插入和移除功能的高效大规模扫掠和修剪方法 -本文提出了一种增强的扫掠和修剪算法,该算法使用了轴对齐的边界框(AABB) ),并考虑到运动。本文提出的算法是线性时间。


现在注意,这是理论上最好的算法。这并不意味着它已被使用。在实践中,在典型情况下(接近O(N)),具有空间划分的O(N ^ 2)算法将具有更好的性能速度,并且对内存有一些额外的要求。这是因为O(C * N)中的常数C可能非常高!由于我们通常有足够的内存并且典型情况下对象在空间中均匀分布-这种算法将执行得更好。但是O(N)是原始问题的答案。


box2d / bullet使用这个吗?
jokoon 2013年

3
“扫除”是物理学通常所说的。不错的是,随着模拟的进行,您可以保持排序更新。同样,图形中的扫掠线在实现方面略有偏离(尽管对理论有好处)-您只需要在框的开始/结束处进行迭代,因此只检查实际的潜在冲突。可见,此方法用于生成功能更强大的空间分区树,而不是直接使用。
肖恩·米德迪奇

3
由于从技术上讲实际上可能存在O(N ^ 2)个成对冲突,因此说扫后修剪始终为O(N)并不是完全正确的。相反,该算法的核心复杂度为O(N + c),其中c是该算法发现的冲突数-它是输出敏感型的,与许多凸包算法一样。(参考:en.wikipedia.org/wiki/Output-sensitive_algorithm
Steven Stadnicki

1
您应该使用某些出版物或至少算法名称来支持自己的主张。
sam hocevar

1
@SamHocevar我已经添加了一个指向真正高级的Sweep and Prune算法的链接,该算法可以在线性时间内对常量进行详细分解。该算法被称为“ Sweep and Prune”的事实对我来说是新的,因为我从未使用过它。我在地图选择中使用了这些算法(这是与其他对象的1点碰撞),因此我只是应用了这些知识。
MartinTeeVarga 2013年

8

不会。碰撞检测并不总是O(N ^ 2)。

例如,假设我们有一个100x100的空间,其中对象的尺寸为10x10。我们可以用网格将这个空间划分为10x10的单元格。

每个对象最多可以位于4个网格单元中(它可以恰好位于一个块中或在“单元”之间)。我们可以在每个单元格中保留一个对象列表。

我们只需要检查这些单元中的冲突。如果每个网格单元中有最大数量的对象(例如,同一块中的对象永远不超过4个),则每个对象的碰撞检测为O(1),所有对象的碰撞检测为O(N)。

这不是避免O(N ^ 2)复杂度的唯一方法。还有其他方法,更适合其他用例-通常使用基于树的数据结构。

我所描述的算法是一种类型的空间分割,但也有其他空间分割算法。有关更多避免O(N ^ 2)时间复杂度的算法,请参见空间分区数据结构的类型

Box2D和Bullet支持机制均可以减少检查对的数量。

根据手册第4.15节:

物理步骤中的碰撞处理可以分为窄相和宽相。在狭窄阶段,我们计算形状对之间的接触点。假设我们有N个形状。使用蛮力,我们需要对N * N / 2对执行窄相。

b2BroadPhase类通过使用动态树进行配对管理来减少此负载。这大大减少了窄相调用的次数。

通常,您不会直接与广相互动。相反,Box2D在内部创建和管理广泛阶段。同样,b2BroadPhase在设计时考虑了Box2D的仿真循环,因此它可能不适合其他用例。

Bullet Wiki

有多种广相算法可以改进仅返回完整对对的朴素O(n ^ 2)算法。这些优化的广义阶段有时会引入更多的非冲突对,但这被它们通常缩短的执行时间所抵消。它们具有不同的性能特征,并且在所有情况下都无法胜过其他产品。

动态AABB树

这由Bullet中的btDbvtBroadphase实现。

顾名思义,这是一个动态的AABB树。这一广泛阶段的一个有用特征是,该结构可以动态适应世界的维度及其内容。它经过很好的优化,并且具有很好的通用性。它可以处理许多物体在运动的动态世界,并且物体的添加和删除比SAP快。

扫描和修剪(SAP)

在Bullet中,这是AxisSweep的类范围。这也是一个很好的通用广义阶段,但有一个局限,即它需要一个预先确定的固定世界尺寸。对于大多数物体几乎没有运动或完全没有运动的典型动力学世界,此宽相阶段具有最佳性能。btAxisSweep3和bt32AxisSweep3都将每个轴的起点和终点量化为整数而不是浮点数,以提高性能。

以下链接是对broadphase的一般介绍,也是对Sweep and Prune算法的描述(尽管它称为“ Sort and Sweep”):

http://www.ziggyware.com/readarticle.php?article_id=128

另外,请查看维基百科页面:

http://en.wikipedia.org/wiki/Sweep_and_prune


与类似问题和外部资源的某些链接将使这成为一个很好的答案。
MichaelHouse

3
错了 您仍然得到O(N ^ 2)。它会快得多,大约是N ^ 2/100,但仍然是N ^ 2。作为证明,只需考虑所有对象都在一个单元格中。
MartinTeeVarga 2013年

4
@ sm4这是最坏的情况O(N ^ 2),如果所有对象都在一个单元格中,则确实会发生这种情况。但是,在物理引擎中,对象通常不会在一个单元中。在我的示例中,没有任何对象可以与3个以上其他对象共享同一单元格。这将是物理引擎中“正常”对象发生的情况(“正常”是指“不仅仅是传感器”)。
luiscubal 2013年

我认为您的算法将需要检查周围的8个单元,而不仅仅是4个单元。
jokoon 2013年

6
@luiscubal复杂度始终是“最坏的情况”。理论上,您正在寻找“保证的”复杂性。快速排序(O(N ^ 2))和合并排序(O(N * logN))相同。Quicksort在真实数据上表现更好,并且对空间的要求更低。但是mergesort保证了更好的复杂性。如果需要证明某些内容,请使用mergesort。如果需要排序,请使用quicksort。
MartinTeeVarga 2013年

2

O(N ^ 2)表示这样的事实:如果您有N个对象,则找出什么与最坏情况下的 N ^ 2碰撞计算发生冲突。假设您有3个对象。要找到“谁在打谁”,您必须找到:

o1 hitting o2?  o1 hitting o3?
o2 hitting o1?  o2 hitting o3?
o3 hitting o1?  o3 hitting o2?

那是6个冲突检查,或N *(N-1)个检查。在渐近分析中,我们将展开多项式并将其近似为O(N ^ 2)。如果您有100个对象,那将是100 * 99,足够接近100 * 100。

因此,例如,如果使用八叉树对空间进行分区,则实体之间比较的平均次数会减少。如果所有对象都可能聚集在一个很小的区域中(例如,如果您正在执行某种粒子流模拟,其中粒子可以聚集在同一区域中),则O(N ^ 2)仍可能出现在模拟中的点(在那点上您会看到速度变慢)。

因此,存在O(N ^ 2)的整个要点是因为每个身体检查场景中每个其他身体的性质。那只是计算的本质。很多事情可以使它变得更便宜。即使是场景图(例如仅检测同一房间中的对象之间的场景图),也会显着减少要执行的碰撞​​计算的次数,但仍然是O(M ^ 2)(其中M是要计算的房间中对象的数量)被碰撞检测到)。球面边界体积使初始检查非常快(if( distance( myCenter, hisCenter ) > (myRadius+hisRadius) ) then MISS),因此即使碰撞检测为O(N ^ 2),边界球计算也可能会非常快速地发生。


无需将蛮力检查作为参考:无论使用哪种聪明的算法,N个对象都可以与其他所有对象碰撞,从而产生O(N ^ 2)个碰撞,需要处理O(N ^ 2)个工作。好的算法只有在冲突较少的情况下才能做得更好。
洛伦佐·加蒂
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.