如何有效地连续找到半径内的所有实体?


14

我有大量的实体(单位)。在每个步骤中,每个单元都需要知道其附近所有单元的位置(距离小于给定常数R)。所有单元连续移动。这是3D模式。

平均而言,在具有给定约束的任何其他给定单位附近,将有总单位计数的1%。

我如何能有效地做到这一点,而无需强加于人?


7
您需要某种空间分区系统:en.wikipedia.org/wiki/Space_partitioning
Tetrad 2012年

Answers:


15

使用一种常见的空间分区算法,例如四叉树,八叉树,BSP树,甚至是简单的网格系统。每个人在每种情况下都有自己的优点和缺点。您可以在这些书中阅读有关它们的更多信息。

通常(或者,我已经听说过,我不太了解其背后的原因),四叉树或八叉树更适合室外环境,而BSP树则更适合室内场景。使用四叉树还是八叉树的选择取决于您的世界有多平坦。如果Y轴上的变化很小,则使用八进制将是浪费的。Octree本质上是具有附加维度的四叉树。

最后,不要忽略Grid解决方案的简单性。许多人忽略了一个简单的网格有时足以解决他们的问题,甚至直接使用一个更复杂的解决方案。

使用网格只是将世界划分为均匀分布的区域,然后将实体存储在世界的适当区域中。然后,在给定位置的情况下,找到相邻实体将是遍历与搜索半径相交的区域的问题。

假设您的世界在XZ平面上的范围从(-1000,-1000)到(1000,1000)。例如,您可以将其划分为10x10的网格,如下所示:

var grid = new List<Entity>[10, 10];

然后,您可以将实体放入网格中其适当的单元格中。例如,具有XZ(-1000,-1000)的实体将落在像元(0,0)上,而具有XZ(1000,1000)的实体将落在像元(9,9)上。然后给定一个在世界上的位置和半径,您可以确定该“圆”与哪些单元格相交,并仅在这些单元格上进行迭代,并使用简单的double作为。

无论如何,请研究所有替代方案并选择似乎更适合您的游戏的替代方案。我承认我对这一主题仍然不了解,无法确定哪种算法最适合您。

编辑另一个论坛上找到它,它可能会帮助您做出决定:

当绝大多数对象都位于网格正方形内并且分布相当均匀时,网格工作效果最佳。相反,当对象具有可变大小或聚集在较小区域时,四叉树起作用。

考虑到您对问题的模糊描述,我也倾向于使用网格解决方案(即假设单位较小且分布相当均匀)。


感谢您提供详细答案。是的,看来简单的Grid解决方案对我来说已经足够了。
OCyril 2012年

0

我前一段时间写了这个。它现在在商业站点上,但是您可以免费获取个人使用源。它可能是用Java编写的,但是用的很好,但是有据可查,因此修剪和重写另一种语言应该不太困难。它基本上使用了Octree,并进行了调整以处理非常大的对象和多线程。

我发现Octree提供了灵活性和效率的最佳组合。我从网格开始,但是不可能适当调整正方形的大小,大块的空白正方形会无用地占用空间和计算能力。(这只是二维的。)我的代码处理来自多个线程的查询,这增加了很多复杂性,但是文档可以帮助您解决不必要的问题。


0

为了提高效率,请尝试使用非常便宜的包围盒检查法来舍弃不靠近目标单元的99%的“单元”。我希望您可以做到这一点,而无需在空间上构建数据。因此,如果所有单位都存储在平面数据结构中,则可以尝试从头到尾进行竞争,首先检查当前单位是否在感兴趣单位的边界框之外。

为感兴趣的单位定义一个超大的边界框,以便它可以安全地拒绝没有机会被视为“靠近”它的项目。排除边界框的检查可能比半径检查便宜。但是,在某些经过测试的系统上,情况并非如此。两者的表现几乎相同。经过大量辩论之后,对此进行了编辑。

首先:2D边界框剪辑。

// returns true if the circle supplied is completely OUTSIDE the bounding box, rectClip
bool canTrivialRejectCircle(Vertex2D& vCentre, WorldUnit radius, Rect& rectClip) {
  if (vCentre.x + radius < rectClip.l ||
    vCentre.x - radius > rectClip.r ||
    vCentre.y + radius < rectClip.b ||
    vCentre.y - radius > rectClip.t)
    return true;
  else
    return false;
}

与类似的东西(在3D中)相比:

BOOL bSphereTest(CObject3D* obj1, CObject3D* obj2 )
{
  D3DVECTOR relPos = obj1->prPosition - obj2->prPosition;
  float dist = relPos.x * relPos.x + relPos.y * relPos.y + relPos.z * relPos.z;
  float minDist = obj1->fRadius + obj2->fRadius;
  return dist <= minDist * minDist;
}.

如果不轻易拒绝对象,则执行更昂贵,更准确的碰撞测试。但是,您只是在寻找接近度,因此球形测试适用于此,但仅适用于在微不足道的情况下幸存的1%的物体。

本文支持微不足道的拒绝框。 http://www.h3xed.com/programming/bounding-box-vs-bounding-circle-collision-detection-performance-as3

如果这种线性方法不能提供所需的性能,则可能需要分层数据结构,例如其他发布者一直在谈论的结构。R树值得考虑。它们支持动态变化。它们是空间世界的B树。

如果您可以避免的话,我只是不想让您麻烦地介绍这种复杂性。再加上随着对象每秒移动数次而使这种复杂数据结构保持最新状态的成本如何?

请记住,网格是一层深的空间数据结构。此限制意味着它不是真正可扩展的。随着世界规模的扩大,您需要覆盖的细胞数量也在增加。最终,该单元数本身成为性能问题。但是,对于特定大小的世界,它将在没有空间分区的情况下为您带来巨大的性能提升。


1
OP明确表示,他希望避免采用暴力手段,这正是您在第一段中所描述的。另外,您如何看待边界框检查比边界球检查便宜?!那是错误的。
notlesh 2012年

是的,我知道他想避免蛮力,通过将分层数据结构引入他的应用程序可以避免这种蛮力。但这可能是很大的努力。如果他还不想这样做,那么他可以尝试线性方法,这种方法是蛮力的,但是如果他的清单不是很大,效果可能不会那么差。我将尝试编辑上面的代码,以将其放入2D边界框平凡的拒绝函数中。我不认为我错了。
Ciaran

到GDnet的链接已断开,但是规范球测试非常简单,非常便宜且不会分支:inside = (dot(p-p0, p-p0) <= r*r)
Lars Viklund 2012年

我将代码粘贴在上面。与边框相比,它看起来并不便宜。
Ciaran

1
@Ciaran说实话,那篇文章看起来真的很糟糕。首先,它不会对真实数据进行测试,而是一遍又一遍地使用相同的值。在实际场景中不会遇到任何事情。不,根据这篇文章,BB仅在没有冲突时才更快(例如,在第一条if语句中检查失败)。也不太现实。但老实说,如果您开始优化这样的事情,那么您肯定是从错误的地方开始的。
bummzack,2012年

0

我必须回答这个问题,因为我没有评论或支持的观点。正如Ciaran所描述的,对于99%提出此问题的人来说,边界框是解决方案。用编译的语言,它会在眨眼间拒绝100,000个无关单位。非蛮力解决方案涉及很多开销;如果使用较小的数字(例如少于1000个),则在处理时间方面将比蛮力检查更昂贵。而且他们将花费更多的编程时间。

我不确定问题中的“非常大”是什么意思,或者其他在这里寻找答案的人是什么意思。我怀疑上面的数字是保守的,可以乘以10。我个人对蛮力技术颇有偏见,并对它们的工作效果感到非常恼火。但是我不希望有人,例如拥有10,000个单位的人花一些时间来花哨的解决方案,而只需几行快速的代码就能解决问题。如果需要的话,他们以后总是可以花哨的。

另外,我要指出,边界球检查需要乘法,而边界框则不需要。就其本质而言,乘法的时间是加法和比较的几倍。语言,操作系统和硬件之间必定存在某种组合,其中球形检查比框形检查要快,但是在大多数地方和时间,框形检查都必须更快,即使球形确实拒绝了一些无关的单元也是如此。盒子接受。(在球形速度更快的地方,新版本的编译器/解释器/优化器很可能会改变这一点。)


尽管您的答案没有错,但您没有在回答问题。特别要求提供“非暴力”方法。同样,您似乎重复了Ciaran已经写过的内容,并且我们就AABB与圈子测试进行了冗长的讨论。性能差异根本无关紧要。最好选择适合大多数碰撞候选对象的边界体积,因为这将减少实际的窄相测试的数量。这将对总体性能产生更大的影响。
bummzack 2012年
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.