物理引擎是否能够例如通过将彼此靠近的对象分组并检查该组内部而不是与所有对象的碰撞来降低复杂性?(例如,可以通过查看远处物体的速度和与其他物体的距离来将其从一个组中移除)。
如果不是,那么这对于球体(在3d中)或圆盘(在2d中)是否会使碰撞变得微不足道?我应该做一个双循环,还是创建一个成对的数组?
编辑:对于子弹和box2d之类的物理引擎,碰撞检测是否仍为O(N ^ 2)?
物理引擎是否能够例如通过将彼此靠近的对象分组并检查该组内部而不是与所有对象的碰撞来降低复杂性?(例如,可以通过查看远处物体的速度和与其他物体的距离来将其从一个组中移除)。
如果不是,那么这对于球体(在3d中)或圆盘(在2d中)是否会使碰撞变得微不足道?我应该做一个双循环,还是创建一个成对的数组?
编辑:对于子弹和box2d之类的物理引擎,碰撞检测是否仍为O(N ^ 2)?
Answers:
在最坏的情况下,空间划分始终为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)是原始问题的答案。
不会。碰撞检测并不总是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的仿真循环,因此它可能不适合其他用例。
有多种广相算法可以改进仅返回完整对对的朴素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
另外,请查看维基百科页面:
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),边界球计算也可能会非常快速地发生。