用于2D碰撞检测的四叉树的高效(并且得到充分解释)实现

时间:2017-01-30 21:50:31

标签: data-structures collision rectangles quadtree

我一直在努力将Quadtree添加到我正在撰写的程序中,我无法提醒,但我注意到很少有解释/执行的实施教程我和# 39;我正在寻找。

具体来说,我在四季中常常使用的方法和伪代码列表(或者只是对其进程的描述)(检索,插入,删除等)是我的意思。寻找,以及可能提高性能的一些技巧。这是用于碰撞检测的,因此最好用2d矩形来解释,因为它们是将要存储的对象。

6 个答案:

答案 0 :(得分:15)

<强> 2。基本面

对于这个答案(对不起,我再次用完了角色限制),我将更多地关注针对这些结构新手的基础知识。

好吧,所以我们假设我们在太空中有很多这样的元素:

enter image description here

我们想要找出鼠标光标下面的元素,或者哪些元素相互交叉/碰撞,或者最近的元素与另一个元素的对应关系,或者这类。

在这种情况下,如果我们拥有的唯一数据是空间中的一堆元素位置和大小/半径,我们必须遍历所有内容以找出给定搜索区域内的元素。对于碰撞检测,我们必须循环遍历每个元素,然后,对于每个元素,循环遍历所有其他元素,使其成为爆炸性二次复杂度算法。这并不能阻止非平凡的输入大小。

<强>细分

那么我们可以对这个问题做些什么呢?一种直接的方法是将搜索空间(例如屏幕)细分为固定数量的单元格,如下所示:

enter image description here

现在让我们假设您要在位置(cx, cy)处找到鼠标光标下的元素。在这种情况下,我们所要做的就是检查鼠标光标下单元格中的元素:

grid_x = floor(cx / cell_size);
grid_y = floor(cy / cell_size);
for each element in cell(grid_x, grid_y):
{
     if element is under cx,cy:
         do something with element (hover highlight it, e.g)
}

碰撞检测的类似之处。如果我们想看看哪些元素与给定元素相交(碰撞):

grid_x1 = floor(element.x1 / cell_size);
grid_y1 = floor(element.y1 / cell_size);
grid_x2 = floor(element.x2 / cell_size);
grid_y2 = floor(element.y2 / cell_size);

for grid_y = grid_y1, grid_y2:
{
     for grid_x = grid_x1, grid_x2:
     {
         for each other_element in cell(grid_x, grid_y):
         {
             if element != other_element and collide(element, other_element):
             {
                 // The two elements intersect. Do something in response
                 // to the collision.
             }
         }
     }
}

我建议人们以这种方式开始,将空间/屏幕划分为固定数量的网格单元格,如10x10,或100x100,甚至1000x1000。有些人可能认为1000x1000在内存使用方面会有爆炸性,但你可以让每个单元只需要4位字节和32位整数,如下所示:

enter image description here

......此时甚至一百万个小区也不到4兆字节。

固定分辨率网格的下行

如果您问我(我个人最喜欢的碰撞检测),固定分辨率网格对于这个问题是一个很棒的数据结构,但它确实有一些缺点。

想象一下,你有一个指环王的视频游戏。让我们说你的许多单位都是人类,兽人和精灵在地图上的小单位。但是,你也有一些 gigantic 单位,比如龙和ents。

网格固定分辨率的一个问题是,虽然你的单元格大小可能是最适合存储像人类和精灵这样的小单位以及大多数时间只占用1个单元格的兽人,但巨大的家伙如龙和蚂蚁可能想要占用很多细胞,比如400个细胞(20x20)。因此,我们必须将这些大家伙插入许多单元格并存储大量冗余数据。

另外,让我们假设您要搜索地图的大矩形区域以查找感兴趣的单位。在这种情况下,您可能需要检查更多的单元格而不是理论上的最佳单元格。

这是固定分辨率网格*的主要缺点。我们最终可能需要插入大量的东西并将它们存储到比理想情况下要存储的更多的单元格中,对于大型搜索区域,我们可能需要检查比理想情况下要搜索的单元格更多的单元格。

  
      
  • 也就是说,抛开理论,通常你可以用类似于图像处理的缓存方式来处理网格。   结果,虽然它在实践中具有这些理论上的缺点   实现缓存友好遍历的简单性和易用性   模式可以使网格比听起来好很多。
  •   

<强>四叉树

因此四叉树是解决这个问题的唯一方法。可以说,它们不是使用固定分辨率网格,而是根据某些标准调整分辨率,同时细分/分割成4个子单元格以提高分辨率。例如,我们可能会说如果给定单元格中有超过2个子节点,则单元格应该拆分。在这种情况下,这个:

enter image description here

结束这个:

enter image description here

现在我们有一个相当不错的表示,其中没有单元存储超过2个元素。与此同时,让我们考虑如果我们插入一条巨龙会发生什么:

enter image description here

这里,与固定分辨率网格不同,龙只能插入一个单元格,因为它占据的单元格中只有一个元素。同样,如果我们搜索地图的一个大区域,除非占用单元格的元素很多,否则我们不必检查这么多单元格。

<强>实施

那么我们如何实现这些东西呢?嗯,它是一天结束时的一棵树,还有一棵四棵树。因此,我们从具有4个子节点的根节点的概念开始,但它们当前为null / nil,而root目前也是一个叶子:

enter image description here

<强>插入

让我们开始插入一些元素,并且为了简单起见,我们假设一个节点在有超过2个元素时应该分割。所以我们将插入一个元素,当我们插入一个元素时,我们应该将它插入到它所属的叶子(单元格)中。在这种情况下,我们只有一个,根节点/单元格,所以我们将其插入那里:

enter image description here

...让我们插入另一个:

enter image description here

......还有另一个:

enter image description here

现在我们在叶子节点中有两个以上的元素。它现在应该拆分。此时,我们为叶节点(在本例中为根)创建了4个子节点,然后根据每个元素在空间中占据的区域/单元格将元素从被分割的叶子(根)转移到适当的象限中: p>

enter image description here

让我们插入另一个元素,并再次插入它所属的适当叶子:

enter image description here

......和另一个:

enter image description here

现在我们又在叶子中有两个以上的元素,所以我们应该把它分成4个象限(孩子们):

enter image description here

这就是基本想法。您可能注意到的一件事是,当我们插入不是无限小点的元素时,它们很容易与多个单元/节点重叠。

因此,如果我们有许多元素重叠细胞之间的许多边界,他们最终可能想要细分一大堆,可能是无限的。为了缓解这个问题,有些人选择拆分元素。如果你所有与元素相关联的都是一个矩形,那么将矩形切块相当简单。其他人可能只对树可以拆分的数量设置深度/递归限制。我倾向于选择后两种解决方案用于这两者之间的碰撞检测方案,因为我发现它至少更容易实现更有效。但是,还有另一种选择:松散的表示形式,并且将在不同的部分中介绍。

此外,如果你的元素彼此重叠,那么即使你存储了无限小的点,你的树也可能想要无限分裂。例如,如果你在空间中有25个点在彼此的顶部(我经常在VFX中遇到的场景),那么无论如何,你的树都会想要无限期地分裂而没有递归/深度限制。因此,要处理病态情况,即使您对元素进行切块,也可能需要深度限制。

删除元素

删除元素包含在第一个答案中,同时删除节点以清理树并删除空叶。但基本上我们所做的就是使用我提出的方法删除元素只是从树下降到存储元素的叶子/叶子(例如,可以使用其矩形确定),并从那些叶子中移除它。

然后,为了开始删除空叶节点,我们使用原始答案中涵盖的延迟清理方法。

<强>结论

我的时间不足,但会尝试回到这个并继续改进答案。如果人们想要练习,我建议实现一个普通的旧固定分辨率网格,看看你是否可以将它降低到每个单元格只是一个32位整数的位置。在考虑四叉树之前首先要理解网格及其固有问题,你可能对网格很好。它甚至可以为您提供最佳解决方案,具体取决于您实现网格与四叉树的效率。

答案 1 :(得分:12)

4。松散的四叉树

好吧,我想花一些时间来实现和解释松散的四叉树,因为我发现它们非常有趣,甚至可能是最广泛的涉及非常动态场景的用例的平衡。

所以我昨晚最终实现了一个,并花了一些时间调整和调整并分析它。这里有25万动态代理人的预告片,每一步都会相互移动和反弹:

enter image description here

当我缩小以查看所有25万个代理以及松散四叉树的所有边界矩形时,帧速率开始受到影响,但这主要是由于我的绘图功能中的瓶颈。如果我缩小一次在屏幕上绘制所有内容,它们就会开始变成热点,而我根本不打算优化它们。以下是基本级别如何使用极少数代理的方式:

enter image description here

松散的四叉树

好吧,那么松散的四叉树是什么?它们基本上是四叉树,其节点没有完全从中心分割成四个偶数象限。相反,他们的AABB(边界矩形)可以重叠,并且可以更大,甚至更小,如果你将一个节点完全从中心分割成4个象限,那么它甚至可以小于你所得到的。

因此,在这种情况下,我们绝对必须存储每个节点的边界框,因此我将其表示为:

struct LooseQuadNode
{
    // Stores the AABB of the node.
    float rect[4];

    // Stores the negative index to the first child for branches or the 
    // positive index to the element list for leaves.
    int children;
};
  

这一次,我尝试使用单精度浮点来查看它的表现如何,并且它做得非常不错。

Point是什么?

好的,那么重点是什么?使用松散四叉树可以利用的主要内容是,为了插入和移除,您可以将插入到四叉树中的每个元素视为单个点。因此,元素永远不会插入到整个树中的多个叶节点中,因为它被视为无限小的点。

然而,当我们插入这些&#34;元素点&#34;在树中,我们扩展我们插入的每个节点的边界框,以包含元素的边界(例如,元素的矩形)。这使我们可以在进行搜索查询时可靠地找到这些元素(例如:搜索与矩形或圆形区域相交的所有元素)。

<强>优点:

  • 即使是最庞大的代理也只需要插入一个叶子节点,并且不会占用比最小的更多的内存。因此,它非常适合于元素大小各不相同的场景,以及我在上面的250k代理演示中的压力测试。
  • 每个元素使用较少的内存,尤其是巨大元素。

<强>缺点:

  • 虽然这会加快插入和移除速度,但它不可避免地会减慢对树的搜索速度。先前对节点的中心点进行的一些基本比较,以确定哪些象限下降成为一个循环,必须检查每个子节点的每个矩形,以确定哪些与搜索区域相交。
  • 每个节点使用更多内存(在我的情况下多5倍)。

昂贵的查询

对于静态元素,这第一个con会非常可怕,因为我们所做的就是构建树并在这些情况下搜索它。我发现这个松散的四叉树,虽然花了几个小时调整和调整它,但是查询它有一个巨大的热点:

enter image description here

那就是说,这实际上是我个人最好的&#34;到目前为止,动态场景的四叉树的实现(虽然请记住,为了这个目的,我喜欢使用分层网格,并且没有那么多使用四边形来动态场景的经验),尽管有这种明显的看法。这是因为至少对于动态场景,我们必须每隔一个步骤不断移动元素,因此与树有很多关系,而不仅仅是查询它。它必须一直在更新,这实际上做得相当不错。

我喜欢松散的四叉树,即使你拥有一大堆巨大的元素,除了最神奇的元素之外,你也能感觉到安全。大量的元素不会比小的元素占用更多的内存。因此,如果我正在编写一个具有大规模世界的视频游戏,并希望将所有内容都放入一个中心空间索引中以加速所有内容而不必像往常那样打扰数据结构的层次结构,那么松散的四叉树和松散的八叉树可能是完美的如果我们只是在整个动态世界中使用一个数据结构,那么它就会成为一个中心通用数据结构。

内存使用

在内存使用方面,虽然元素占用的内存较少(特别是大量内存),但与节点甚至不需要存储AABB的实现相比,节点需要更多。我总体上发现了各种测试用例,包括那些具有许多巨大元素的测试用例,松散的四叉树往往会因其强壮的节点而占用更多的内存(粗略估计约为33%)。也就是说,它在我原来的答案中表现得比四叉树更好。

从好的方面来说,内存使用更多是稳定(这往往会转化为更稳定和平滑的帧速率)。在内存使用完全稳定之前,我原来的答案的四叉树花了大约5秒多。这个在开始之后往往会变得稳定一两秒,并且很可能因为元素永远不必被插入多次(即使小元素可以在我原来的四叉树中插入两次,如果它们重叠两个或者在边界处有更多节点)。因此,数据结构可以快速发现针对所有情况分配所需的内存量,可以这么说。

<强>理论

所以让我们来介绍一下基本理论。我建议首先实现一个常规的四叉树并在转向松散版本之前理解它,因为它们实现起来有点困难。当我们从空树开始时,你可以想象它也有一个空的矩形。

让我们插入一个元素:

enter image description here

由于我们目前只有一个根节点也是一个叶子,我们只需将其插入即可。执行此操作时,根节点的先前空矩形现在包含我们插入的元素(以红色虚线显示)。让我们插入另一个:

enter image description here

当我们通过我们正在插入的元素的AABB插入(这次只是根)时,我们扩展了我们遍历的节点的AABB。让我们插入另一个,让我们说节点在包含2个以上元素时应该拆分:

enter image description here

在这种情况下,我们在叶子节点(我们的根)中有两个以上的元素,所以我们应该将它分成4个象限。这与分割常规的基于点的四叉树非常相似,除了我们再次扩展边界框时我们转移子节点。我们首先考虑被拆分节点的中心位置:

enter image description here

现在我们有4个孩子到我们的根节点,每个孩子也存储它的紧身边界框(以绿色显示)。让我们插入另一个元素:

enter image description here

在这里您可以看到插入此元素不仅扩展了左下角子项的矩形,还扩展了根(我们沿着我们要插入的路径展开所有AABB)。让我们插入另一个:

enter image description here

在这种情况下,我们在叶节点中再次有3个元素,因此我们应该拆分:

enter image description here

就像这样。那左下角的圆圈怎么样?它似乎与两个象限交叉。但是,我们只考虑元素的一个点(例如:它的中心)来确定它所属的象限。因此,圆圈实际上只会插入到左下象限。

然而,左下象限的边界框被扩展到包含其范围(以青色显示,希望你们不要介意但我改变了BG的颜色,因为它很难看到颜色),所以2级节点的AABB(以青色显示)实际上会溢出到彼此的象限中。

每个象限存储自己的矩形,总是保证包含其元素的事实允许我们将元素插入到一个叶节点,即使它的区域与多个节点相交。相反,我们扩展叶节点的边界框,而不是将元素插入多个节点。

更新AABB

因此,这可能会导致问题,AABB何时更新?如果我们只在插入元素时扩展AABB,它们往往会变得越来越大。当元素被删除时,我们如何缩小它们?有很多方法可以解决这个问题,但是我通过更新整个层次结构的边界框来实现这一点,并且#34;清理&#34;在我的原始答案中描述的方法。这似乎足够快(甚至不会显示为热点)。

与网格比较

我似乎仍然无法像我的分层网格实现一样有效地实现碰撞检测,但同样可能只是比数据结构更重要。我发现树结构的主要困难是可以轻松控制内存中的所有内容以及访问方式。例如,通过网格,您可以确保一行中的所有列都是连续的并按顺序排列,并确保以连续的方式访问它们以及连续存储在该行中的元素。对于一棵树,内存访问本质上往往有点零星,并且随着节点被分割成多个子节点,树更想要更频繁地传递元素,因此往往会迅速降级。也就是说,如果我想使用一个树的空间索引,我到目前为止真的正在挖掘这些松散的变体,并且想到了实现一个松散的网格&#34;。 / p>

<强>结论

简而言之,这是松散的四叉树,它基本上具有常规四叉树的插入/移除逻辑,只存储点,除了它在路上扩展/更新AABB。为了搜索,我们最终遍历所有矩形与搜索区域相交的子节点。

  

我希望人们不要介意我发布这么多冗长的答案。我真的很喜欢写它们,这对我来说是一个很有用的练习,可以重新审视四叉树,试图写出所有这些答案。我也会在某个时候考虑一​​本关于这些主题的书(尽管它会用日语),并在这里写一些答案,而草率和英语,帮助我将所有东西放在我的大脑中。现在我只需要有人要求解释如何编写有效的八叉树或网格以进行碰撞检测,以便为我在这些主题上做同样的事情提供借口。

答案 2 :(得分:10)

5。具有500k代理的松散/紧密双网格

enter image description here

上面是一个小GIF,显示每个时间段使用新的&#34;松散/紧密的网格&#34;数据结构在写完关于松散四叉树的答案后,我受到启发。我会试着了解它是如何运作的。

到目前为止,这是我所展示的所有已实施的数据结构(尽管它可能只是我),处理了50万个代理比处理的初始四叉树更好100k,并且比松散的四叉树处理250k更好。它还需要最少的内存,并且在这三者中使用最稳定的内存。这仍然只在一个线程中工作,没有SIMD代码,没有花哨的微优化,因为我通常申请生产代码 - 只需几个小时的工作就可以直接实现。

我还改进了绘图瓶颈而没有改进我的光栅化代码。这是因为网格让我可以轻松地以对图像处理缓存友好的方式遍历它(在网格的单元格中逐个绘制元素恰好会导致非常缓存 - 栅格化时友好的图像处理模式。)

有趣的是,我花了最短的时间来实现(在松散的四叉树上花了5或6个小时只用了2个小时),而且它还需要最少量的代码(可以说是最简单的代码)。虽然这可能只是因为我积累了很多实施网格的经验。

松散/紧密双网格

所以我介绍了网格在基础部分的工作原理(参见第2部分),但这是一个松散的网格&#34;。每个网格单元存储其自己的边界框,当元素被移除时允许收缩,并随着元素的添加而增长。因此,每个元素只需要根据其中心位置落入哪个单元格插入网格中,如下所示:

// Ideally use multiplication here with inv_cell_w or inv_cell_h.
int cell_x = clamp(floor(elt_x / cell_w), 0, num_cols-1);
int cell_y = clamp(floor(ely_y / cell_h), 0, num_rows-1);
int cell_idx = cell_y*num_rows + cell_x;
// Insert element to cell at 'cell_idx' and expand the loose cell's AABB.

单元格存储元素和AABB,如下所示:

struct LGridLooseCell
{
    // Stores the index to the first element using an indexed SLL.
    int head;

    // Stores the extents of the grid cell relative to the upper-left corner
    // of the grid which expands and shrinks with the elements inserted and 
    // removed.
    float l, t, r, b;
};

然而,松散的细胞构成一个概念性问题。如果我们插入一个巨大的元素,他们有这些可以变大的边界框,当我们想要找出哪个松散的单元格和元素与搜索矩形相交时,我们如何避免检查网格的每个单个格子单元,例如? / p>

实际上这是一个&#34;双松散网格&#34;。松散的网格单元本身被分隔成紧密的网格。当我们进行上面的模拟搜索时,我们首先要仔细观察紧密网格:

tx1 = clamp(floor(search_x1 / cell_w), 0, num_cols-1);
tx2 = clamp(floor(search_x2 / cell_w), 0, num_cols-1);
ty1 = clamp(floor(search_y1 / cell_h), 0, num_rows-1);
ty2 = clamp(floor(search_y2 / cell_h), 0, num_rows-1);

for ty = ty1, ty2:
{
    trow = ty * num_cols
    for tx = tx1, tx2:
    {
        tight_cell = tight_cells[trow + tx];
        for each loose_cell in tight_cell:
        {
            if loose_cell intersects search area:
            {
                for each element in loose_cell:
                {
                    if element intersects search area:
                        add element to query results
                }
            }
        }
    }
}

紧密细胞存储松散细胞的单链接索引列表,如下所示:

struct LGridLooseCellNode
{
    // Points to the next loose cell node in the tight cell.
    int next;

    // Stores an index to the loose cell.
    int cell_idx;
};

struct LGridTightCell
{
    // Stores the index to the first loose cell node in the tight cell using 
    // an indexed SLL.
    int head;
};

瞧,这是&#34;松散的双网格&#34;的基本思想。当我们插入一个元素时,我们就像扩展四叉树一样扩展松散单元格的AABB。但是,我们还会根据矩形将松散的单元格插入到紧密网格中(并且可以将其插入多个单元格中)。

这两者的组合(紧密网格以快速找到松散的细胞,松散的细胞以快速找到元素)提供了一个非常可爱的数据结构,其中每个元素被插入到具有恒定时间搜索,插入和移除的单个单元格中。

我看到的唯一重大缺点是我们必须存储所有这些单元格,并且可能仍然需要搜索超出我们需要的更多单元格,但它们仍然相当便宜(在我的情况下每个单元格20个字节)和在一个非常缓存友好的访问模式中,搜索单元格很容易。

我建议给出这个&#34;松散网格的想法&#34;一试。它实际上比四叉树和松散四叉树更容易实现,更重要的是,优化,因为它立即适用于缓存友好的内存布局。作为一个超级酷的奖励,如果你可以提前预测你的世界中的代理人数量,那么它在内存使用方面是100%完全稳定的,因为一个元素总是占据一个单元格,并且单元格总数是固定(因为他们不进行细分/拆分)。结果,内存使用非常稳定。对于某些硬件和软件来说,这可能是一个巨大的好处,你需要事先预先分配所有内存并知道内存使用永远不会超过这一点。

使用SIMD同时使用矢量化代码(除多线程之外)同时执行多个连贯查询也非常容易,因为如果我们甚至可以称之为遍历,则遍历是平的(&# 39; s只是一个常数时间查找单元格索引,涉及一些算术)。因此,应用与英特尔应用于其光线跟踪内核/ BVH(Embree)的射线数据包相似的优化策略相当容易同时测试多个相干光线(在我们的例子中,它们将是&#34;代理数据包& #34;用于碰撞),除了没有这样的花哨/复杂代码,因为网格&#34;遍历&#34;非常简单。

关于记忆的使用和效率

我在高效四叉树的第1部分中略微介绍了这一点,但减少内存使用通常是加速这些天的关键,因为一旦你将数据输入L1或寄存器,我们的处理器速度如此之快,但DRAM访问相对如此,如此缓慢。即使我们有一个疯狂的缓慢记忆,我们仍然有如此珍贵的快速记忆。

我认为我有点幸运,因为我们必须非常节俭地使用内存(尽管没有我之前的人那么多),甚至一兆兆字节的DRAM也被认为是惊人的。当时我学到的一些东西和我所接受的习惯(即使我远离专家)恰巧与表现保持一致。

因此,我提供的关于效率的一般建议,不仅仅是用于碰撞检测的空间索引,而是关注内存的使用。如果它具有爆炸性,那么解决方案很可能不会非常有效。当然,有一个灰色区域,为数据结构使用更多的内存可以大大减少处理到只考虑速度有利的程度,但很多时候减少了数据所需的内存量结构,特别是热记忆&#34;反复访问,可以与速度改进成比例地转换。我职业生涯中遇到的所有效率最低的空间索引都是内存使用中最具爆炸性的。

查看您需要存储和计算的数据量是有帮助的,至少大致是,理想情况下需要多少内存。然后将其与实际需求量进行比较。如果这两个世界分开,那么你可能会得到适当的提升来减少内存使用,因为这通常转化为更少的时间从更慢的形式加载内存块内存层次中的内存。

答案 3 :(得分:10)

脏伎俩:统一尺寸

对于这个答案,我将介绍一个不道德的伎俩,如果数据合适(例如,在许多视频游戏中通常会出现这种情况),可以让你的模拟运行速度提高一个数量级。它可以让您从数万到数十万个代理,或数十万代理到数百万代理。到目前为止,我还没有在我的答案中显示的任何演示中应用它,因为它有点作弊,但我已经在生产中使用它,它可以创造一个与众不同的世界。有趣的是,我不经常看到它经常讨论。实际上,我从来没有见过它讨论哪种奇怪。

让我们回到指环王的例子。我们有很多人性化的&#34;像人类,精灵,矮人,兽人和霍比特人这样的单位,我们也有像龙和鸟类这样的巨大单位。

&#34;人性化的&#34;单位不会变化那么大。霍比特人可能有四英尺高,有点矮胖,一个兽人可能是6英尺4英寸。虽然存在一些差异,但它不是史诗差异。它不是一个数量级的差异。

那么如果我们在一个霍比特人周围放置一个边界球体/盒子会发生什么呢?这个球体的大小就是一个兽人的边界球体/盒子,只是为了粗略的交叉点查询(在我们深入研究检查更多真实之前)颗粒/精细水平的碰撞)?有一些浪费的负空间,但真正有趣的事情发生了。

如果我们可以预见普通案例单位的上限,我们可以将它们存储在一个数据结构中,该数据结构假设所有事物都具有统一的上限大小。在这种情况下会发生几件非常有趣的事情:

  1. 我们不必为每个元素存储大小。数据结构可以假设插入其中的所有元素具有相同的统一大小(仅用于粗略交叉查询)。在许多情况下,这几乎可以减少元素的内存使用量,当我们每个元素访问的内存/数据更少时,它自然会加速遍历。
  2. 我们可以将元素存储在 one 单元格/节点中,即使对于没有存储在单元格/节点中的可变大小AABB的严格表示也是如此。
  3. 只存储一个点

    这第二部分很棘手,但想象我们有这样一个案例:

    enter image description here

    好吧,如果我们查看绿色圆圈并搜索其半径,如果它只作为我们空间索引中的单个点存储,我们最终会错过蓝色圆圈的中心点。但是如果我们搜索一个两倍于我们圆圈半径的区域呢?

    enter image description here

    在这种情况下,即使蓝色圆圈仅存储在我们的空间索引中的单个点(橙色中心点),我们也能找到交叉点。只是为了直观地表明这是有效的:

    enter image description here

    在这种情况下,圆圈不相交,我们可以看到中心点甚至在扩展的双倍搜索半径之外。因此,只要我们在空间索引中搜索两倍半径,假设所有元素都具有统一的上限大小,如果我们搜索上限半度两倍的区域,我们就可以保证在粗略查询中找到它们。 (或AABB的矩形半尺寸的两倍)。

    现在看起来似乎很浪费,因为它会检查我们的搜索查询中所需的单元/节点数量,但这只是因为我为了说明目的而绘制了图表。如果您使用此策略,则将其用于大小通常只是单个叶节点/单元大小的一小部分的元素。

    巨大优化

    因此,您可以应用的巨大优化是将您的内容分为3种不同的类型:

    1. 一个动态集(不断移动和动画),具有像人类,兽人,精灵和霍比特人一样的常见情况。我们基本上在所有这些代理周围放置相同大小的边界框/球体。在这里,您可以使用紧密的四叉树或紧密网格等紧密表示,并且它仅为每个元素存储单个点。你也可以使用这个相同结构的另一个实例,用于超级小元素,如仙女和缕缕,具有不同的常见上限大小。
    2. 比任何可预见的常见情况上限更大的动态集合,如龙和非常不寻常的大小。在这里,您可以使用松散的四叉树或我的&#34;松散/紧密的双网格&#34 ;.
    3. 一个静态集合,您可以负担得起构建时间较长或更新效率非常低的结构,例如静态数据的四叉树,以完美连续的方式存储所有内容。在这种情况下,数据结构的更新效率并不重要,只要它提供最快的搜索查询,因为您永远不会更新它。你可以将它用于你的世界中的元素,如城堡,路障和巨石。
    4. 因此,如果您可以应用它,那么使用统一的上限范围(边界球体或框)分隔常见元素的想法可能是非常有用的优化策略。这也是我没有看到过的问题。我经常看到开发人员在谈论分离动态内容和静态内容,但是你可以通过进一步对常见大小相似的动态元素进行分组并将它们视为具有统一的上限大小的动态元素来获得同样多的改进(如果不是更多)粗碰撞测试,它具有允许它们像无限小点一样存储的效果,该点只插入紧密数据结构中的一个叶节点。

      关于&#34;作弊&#34;

      的好处

      因此,这个解决方案并不是特别聪明或有趣,但我认为值得一提的是,至少对于那些像我一样的人来说,这是一种值得一提的心态。我浪费了很多职业生涯来寻找&#34; uber&#34;解决方案:一刀切的所有数据结构和算法,可以精美地处理任何用例,希望能够花费一些额外的时间来做到正确,然后重新使用它就像疯了一样远在未来并且完全不同用例,更不用说与寻求相同的同事合作了。

      在性能不能过分牺牲生产力的情况下,热心寻求这样的解决方案既不会导致性能也不会导致生产力。因此,有时候停下来看一下软件特定数据要求的性质,看看我们是否可以“欺骗”#34;并在这个例子中创建一些针对这些特殊要求的定制的&#34;更为狭隘的解决方案。有时,这是一种最有效的方法,可以在不受太多利益影响另一方的情况下,将性能和生产力完美结合。

答案 4 :(得分:9)

3。便携式C实现

我希望人们不要介意另一个答案,但我已经超出了30k的限制。我今天在想我的第一个答案是如何与语言无关的。我在谈论内存分配策略,类模板等,并非所有语言都允许这样的事情。

所以我花了一些时间考虑一种几乎普遍适用的有效实现(一种例外是函数式语言)。所以我最终将我的四叉树移植到C语言,以便它只需要int的数组来存储所有内容。

结果不是很好,但应该可以非常有效地使用任何允许您存储int连续数组的语言。对于Python,ndarray中有numpy等库。对于JS,有typed arrays。对于Java和C#,我们可以使用int数组(不是Integer,这些数组不能保证连续存储,并且它们比普通的旧int使用更多的内存。)

C IntList

因此,我在int数组上使用一个辅助结构来构建整个四叉树,以便尽可能轻松地移植到其他语言。它结合了堆栈/空闲列表。这就是我们需要以有效的方式实现在另一个答案中谈论的所有内容。

#ifndef INT_LIST_H
#define INT_LIST_H

#ifdef __cplusplus
    #define IL_FUNC extern "C"
#else
    #define IL_FUNC
#endif

typedef struct IntList IntList;
enum {il_fixed_cap = 128};

struct IntList
{
    // Stores a fixed-size buffer in advance to avoid requiring
    // a heap allocation until we run out of space.
    int fixed[il_fixed_cap];

    // Points to the buffer used by the list. Initially this will
    // point to 'fixed'.
    int* data;

    // Stores how many integer fields each element has.
    int num_fields;

    // Stores the number of elements in the list.
    int num;

    // Stores the capacity of the array.
    int cap;

    // Stores an index to the free element or -1 if the free list
    // is empty.
    int free_element;
};

// ---------------------------------------------------------------------------------
// List Interface
// ---------------------------------------------------------------------------------
// Creates a new list of elements which each consist of integer fields.
// 'num_fields' specifies the number of integer fields each element has.
IL_FUNC void il_create(IntList* il, int num_fields);

// Destroys the specified list.
IL_FUNC void il_destroy(IntList* il);

// Returns the number of elements in the list.
IL_FUNC int il_size(const IntList* il);

// Returns the value of the specified field for the nth element.
IL_FUNC int il_get(const IntList* il, int n, int field);

// Sets the value of the specified field for the nth element.
IL_FUNC void il_set(IntList* il, int n, int field, int val);

// Clears the specified list, making it empty.
IL_FUNC void il_clear(IntList* il);

// ---------------------------------------------------------------------------------
// Stack Interface (do not mix with free list usage; use one or the other)
// ---------------------------------------------------------------------------------
// Inserts an element to the back of the list and returns an index to it.
IL_FUNC int il_push_back(IntList* il);

// Removes the element at the back of the list.
IL_FUNC void il_pop_back(IntList* il);

// ---------------------------------------------------------------------------------
// Free List Interface (do not mix with stack usage; use one or the other)
// ---------------------------------------------------------------------------------
// Inserts an element to a vacant position in the list and returns an index to it.
IL_FUNC int il_insert(IntList* il);

// Removes the nth element in the list.
IL_FUNC void il_erase(IntList* il, int n);

#endif

#include "IntList.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>

void il_create(IntList* il, int num_fields)
{
    il->data = il->fixed;
    il->num = 0;
    il->cap = il_fixed_cap;
    il->num_fields = num_fields;
    il->free_element = -1;
}

void il_destroy(IntList* il)
{
    // Free the buffer only if it was heap allocated.
    if (il->data != il->fixed)
        free(il->data);
}

void il_clear(IntList* il)
{
    il->num = 0;
    il->free_element = -1;
}

int il_size(const IntList* il)
{
    return il->num;
}

int il_get(const IntList* il, int n, int field)
{
    assert(n >= 0 && n < il->num);
    return il->data[n*il->num_fields + field];
}

void il_set(IntList* il, int n, int field, int val)
{
    assert(n >= 0 && n < il->num);
    il->data[n*il->num_fields + field] = val;
}

int il_push_back(IntList* il)
{
    const int new_pos = (il->num+1) * il->num_fields;

    // If the list is full, we need to reallocate the buffer to make room
    // for the new element.
    if (new_pos > il->cap)
    {
        // Use double the size for the new capacity.
        const int new_cap = new_pos * 2;

        // If we're pointing to the fixed buffer, allocate a new array on the
        // heap and copy the fixed buffer contents to it.
        if (il->cap == il_fixed_cap)
        {
            il->data = malloc(new_cap * sizeof(*il->data));
            memcpy(il->data, il->fixed, sizeof(il->fixed));
        }
        else
        {
            // Otherwise reallocate the heap buffer to the new size.
            il->data = realloc(il->data, new_cap * sizeof(*il->data));
        }
        // Set the old capacity to the new capacity.
        il->cap = new_cap;
    }
    return il->num++;
}

void il_pop_back(IntList* il)
{
    // Just decrement the list size.
    assert(il->num > 0);
    --il->num;
}

int il_insert(IntList* il)
{
    // If there's a free index in the free list, pop that and use it.
    if (il->free_element != -1)
    {
        const int index = il->free_element;
        const int pos = index * il->num_fields;

        // Set the free index to the next free index.
        il->free_element = il->data[pos];

        // Return the free index.
        return index;
    }
    // Otherwise insert to the back of the array.
    return il_push_back(il);
}

void il_erase(IntList* il, int n)
{
    // Push the element to the free list.
    const int pos = n * il->num_fields;
    il->data[pos] = il->free_element;
    il->free_element = n;
}

使用IntList

使用此数据结构来实现所有内容并不会产生最漂亮的代码。而不是像这样访问元素和字段:

elements[n].field = elements[n].field + 1;

......我们最终这样做:

il_set(&elements, n, idx_field, il_get(&elements, n, idx_field) + 1);

......我知道这很恶心,但是这段代码的目的是尽可能高效和便携,而不是尽可能容易维护。希望人们只需将这个四叉树用于他们的项目而无需更改或维护它。

哦,随意使用这个代码我发布你想要的,即使是商业项目。如果人们让我知道他们是否认为有用,但我愿意这样做,我真的很喜欢它。

C四叉树

好的,所以使用上面的数据结构,这里是C:中的四叉树:

#ifndef QUADTREE_H
#define QUADTREE_H

#include "IntList.h"

#ifdef __cplusplus
    #define QTREE_FUNC extern "C"
#else
    #define QTREE_FUNC
#endif

typedef struct Quadtree Quadtree;

struct Quadtree
{
    // Stores all the nodes in the quadtree. The first node in this
    // sequence is always the root.
    IntList nodes;

    // Stores all the elements in the quadtree.
    IntList elts;

    // Stores all the element nodes in the quadtree.
    IntList enodes;

    // Stores the quadtree extents.
    int root_mx, root_my, root_sx, root_sy;

    // Maximum allowed elements in a leaf before the leaf is subdivided/split unless
    // the leaf is at the maximum allowed tree depth.
    int max_elements;

    // Stores the maximum depth allowed for the quadtree.
    int max_depth;

    // Temporary buffer used for queries.
    char* temp;

    // Stores the size of the temporary buffer.
    int temp_size;
};

// Function signature used for traversing a tree node.
typedef void QtNodeFunc(Quadtree* qt, void* user_data, int node, int depth, int mx, int my, int sx, int sy);

// Creates a quadtree with the requested extents, maximum elements per leaf, and maximum tree depth.
QTREE_FUNC void qt_create(Quadtree* qt, int width, int height, int max_elements, int max_depth);

// Destroys the quadtree.
QTREE_FUNC void qt_destroy(Quadtree* qt);

// Inserts a new element to the tree.
// Returns an index to the new element.
QTREE_FUNC int qt_insert(Quadtree* qt, int id, float x1, float y1, float x2, float y2);

// Removes the specified element from the tree.
QTREE_FUNC void qt_remove(Quadtree* qt, int element);

// Cleans up the tree, removing empty leaves.
QTREE_FUNC void qt_cleanup(Quadtree* qt);

// Outputs a list of elements found in the specified rectangle.
QTREE_FUNC void qt_query(Quadtree* qt, IntList* out, float x1, float y1, float x2, float y2, int omit_element);

// Traverses all the nodes in the tree, calling 'branch' for branch nodes and 'leaf' 
// for leaf nodes.
QTREE_FUNC void qt_traverse(Quadtree* qt, void* user_data, QtNodeFunc* branch, QtNodeFunc* leaf);

#endif

#include "Quadtree.h"
#include <stdlib.h>

enum
{
    // ----------------------------------------------------------------------------------------
    // Element node fields:
    // ----------------------------------------------------------------------------------------
    enode_num = 2,

    // Points to the next element in the leaf node. A value of -1 
    // indicates the end of the list.
    enode_idx_next = 0,

    // Stores the element index.
    enode_idx_elt = 1,

    // ----------------------------------------------------------------------------------------
    // Element fields:
    // ----------------------------------------------------------------------------------------
    elt_num = 5,

    // Stores the rectangle encompassing the element.
    elt_idx_lft = 0, elt_idx_top = 1, elt_idx_rgt = 2, elt_idx_btm = 3,

    // Stores the ID of the element.
    elt_idx_id = 4,

    // ----------------------------------------------------------------------------------------
    // Node fields:
    // ----------------------------------------------------------------------------------------
    node_num = 2,

    // Points to the first child if this node is a branch or the first element
    // if this node is a leaf.
    node_idx_fc = 0,

    // Stores the number of elements in the node or -1 if it is not a leaf.
    node_idx_num = 1,

    // ----------------------------------------------------------------------------------------
    // Node data fields:
    // ----------------------------------------------------------------------------------------
    nd_num = 6,

    // Stores the extents of the node using a centered rectangle and half-size.
    nd_idx_mx = 0, nd_idx_my = 1, nd_idx_sx = 2, nd_idx_sy = 3,

    // Stores the index of the node.
    nd_idx_index = 4,

    // Stores the depth of the node.
    nd_idx_depth = 5,
};

static void node_insert(Quadtree* qt, int index, int depth, int mx, int my, int sx, int sy, int element);

static int floor_int(float val)
{
    return (int)val;
}

static int intersect(int l1, int t1, int r1, int b1,
                     int l2, int t2, int r2, int b2)
{
    return l2 <= r1 && r2 >= l1 && t2 <= b1 && b2 >= t1;
}

void leaf_insert(Quadtree* qt, int node, int depth, int mx, int my, int sx, int sy, int element)
{
    // Insert the element node to the leaf.
    const int nd_fc = il_get(&qt->nodes, node, node_idx_fc);
    il_set(&qt->nodes, node, node_idx_fc, il_insert(&qt->enodes));
    il_set(&qt->enodes, il_get(&qt->nodes, node, node_idx_fc), enode_idx_next, nd_fc);
    il_set(&qt->enodes, il_get(&qt->nodes, node, node_idx_fc), enode_idx_elt, element);

    // If the leaf is full, split it.
    if (il_get(&qt->nodes, node, node_idx_num) == qt->max_elements && depth < qt->max_depth)
    {
        int fc = 0, j = 0;
        IntList elts = {0};
        il_create(&elts, 1);

        // Transfer elements from the leaf node to a list of elements.
        while (il_get(&qt->nodes, node, node_idx_fc) != -1)
        {
            const int index = il_get(&qt->nodes, node, node_idx_fc);
            const int next_index = il_get(&qt->enodes, index, enode_idx_next);
            const int elt = il_get(&qt->enodes, index, enode_idx_elt);

            // Pop off the element node from the leaf and remove it from the qt.
            il_set(&qt->nodes, node, node_idx_fc, next_index);
            il_erase(&qt->enodes, index);

            // Insert element to the list.
            il_set(&elts, il_push_back(&elts), 0, elt);
        }

        // Start by allocating 4 child nodes.
        fc = il_insert(&qt->nodes);
        il_insert(&qt->nodes);
        il_insert(&qt->nodes);
        il_insert(&qt->nodes);
        il_set(&qt->nodes, node, node_idx_fc, fc);

        // Initialize the new child nodes.
        for (j=0; j < 4; ++j)
        {
            il_set(&qt->nodes, fc+j, node_idx_fc, -1);
            il_set(&qt->nodes, fc+j, node_idx_num, 0);
        }

        // Transfer the elements in the former leaf node to its new children.
        il_set(&qt->nodes, node, node_idx_num, -1);
        for (j=0; j < il_size(&elts); ++j)
            node_insert(qt, node, depth, mx, my, sx, sy, il_get(&elts, j, 0));
        il_destroy(&elts);
    }
    else
    {
        // Increment the leaf element count.
        il_set(&qt->nodes, node, node_idx_num, il_get(&qt->nodes, node, node_idx_num) + 1);
    }
}

static void push_node(IntList* nodes, int nd_index, int nd_depth, int nd_mx, int nd_my, int nd_sx, int nd_sy)
{
    const int back_idx = il_push_back(nodes);
    il_set(nodes, back_idx, nd_idx_mx, nd_mx);
    il_set(nodes, back_idx, nd_idx_my, nd_my);
    il_set(nodes, back_idx, nd_idx_sx, nd_sx);
    il_set(nodes, back_idx, nd_idx_sy, nd_sy);
    il_set(nodes, back_idx, nd_idx_index, nd_index);
    il_set(nodes, back_idx, nd_idx_depth, nd_depth);
}

static void find_leaves(IntList* out, const Quadtree* qt, int node, int depth, 
                        int mx, int my, int sx, int sy, 
                        int lft, int top, int rgt, int btm)
{
    IntList to_process = {0};
    il_create(&to_process, nd_num);
    push_node(&to_process, node, depth, mx, my, sx, sy);

    while (il_size(&to_process) > 0)
    {
        const int back_idx = il_size(&to_process) - 1;
        const int nd_mx = il_get(&to_process, back_idx, nd_idx_mx);
        const int nd_my = il_get(&to_process, back_idx, nd_idx_my);
        const int nd_sx = il_get(&to_process, back_idx, nd_idx_sx);
        const int nd_sy = il_get(&to_process, back_idx, nd_idx_sy);
        const int nd_index = il_get(&to_process, back_idx, nd_idx_index);
        const int nd_depth = il_get(&to_process, back_idx, nd_idx_depth);
        il_pop_back(&to_process);

        // If this node is a leaf, insert it to the list.
        if (il_get(&qt->nodes, nd_index, node_idx_num) != -1)
            push_node(out, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy);
        else
        {
            // Otherwise push the children that intersect the rectangle.
            const int fc = il_get(&qt->nodes, nd_index, node_idx_fc);
            const int hx = nd_sx >> 1, hy = nd_sy >> 1;
            const int l = nd_mx-hx, t = nd_my-hx, r = nd_mx+hx, b = nd_my+hy;

            if (top <= nd_my)
            {
                if (lft <= nd_mx)
                    push_node(&to_process, fc+0, nd_depth+1, l,t,hx,hy);
                if (rgt > nd_mx)
                    push_node(&to_process, fc+1, nd_depth+1, r,t,hx,hy);
            }
            if (btm > nd_my)
            {
                if (lft <= nd_mx)
                    push_node(&to_process, fc+2, nd_depth+1, l,b,hx,hy);
                if (rgt > nd_mx)
                    push_node(&to_process, fc+3, nd_depth+1, r,b,hx,hy);
            }
        }
    }
    il_destroy(&to_process);
}

static void node_insert(Quadtree* qt, int index, int depth, int mx, int my, int sx, int sy, int element)
{
    // Find the leaves and insert the element to all the leaves found.
    int j = 0;
    IntList leaves = {0};

    const int lft = il_get(&qt->elts, element, elt_idx_lft);
    const int top = il_get(&qt->elts, element, elt_idx_top);
    const int rgt = il_get(&qt->elts, element, elt_idx_rgt);
    const int btm = il_get(&qt->elts, element, elt_idx_btm);

    il_create(&leaves, nd_num);
    find_leaves(&leaves, qt, index, depth, mx, my, sx, sy, lft, top, rgt, btm);
    for (j=0; j < il_size(&leaves); ++j)
    {
        const int nd_mx = il_get(&leaves, j, nd_idx_mx);
        const int nd_my = il_get(&leaves, j, nd_idx_my);
        const int nd_sx = il_get(&leaves, j, nd_idx_sx);
        const int nd_sy = il_get(&leaves, j, nd_idx_sy);
        const int nd_index = il_get(&leaves, j, nd_idx_index);
        const int nd_depth = il_get(&leaves, j, nd_idx_depth);
        leaf_insert(qt, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy, element);
    }
    il_destroy(&leaves);
}

void qt_create(Quadtree* qt, int width, int height, int max_elements, int max_depth)
{
    qt->max_elements = max_elements;
    qt->max_depth = max_depth;
    qt->temp = 0;
    qt->temp_size = 0;
    il_create(&qt->nodes, node_num);
    il_create(&qt->elts, elt_num);
    il_create(&qt->enodes, enode_num);

    // Insert the root node to the qt.
    il_insert(&qt->nodes);
    il_set(&qt->nodes, 0, node_idx_fc, -1);
    il_set(&qt->nodes, 0, node_idx_num, 0);

    // Set the extents of the root node.
    qt->root_mx = width >> 1;
    qt->root_my = height >> 1;
    qt->root_sx = qt->root_mx;
    qt->root_sy = qt->root_my;
}

void qt_destroy(Quadtree* qt)
{
    il_destroy(&qt->nodes);
    il_destroy(&qt->elts);
    il_destroy(&qt->enodes);
    free(qt->temp);
}

int qt_insert(Quadtree* qt, int id, float x1, float y1, float x2, float y2)
{
    // Insert a new element.
    const int new_element = il_insert(&qt->elts);

    // Set the fields of the new element.
    il_set(&qt->elts, new_element, elt_idx_lft, floor_int(x1));
    il_set(&qt->elts, new_element, elt_idx_top, floor_int(y1));
    il_set(&qt->elts, new_element, elt_idx_rgt, floor_int(x2));
    il_set(&qt->elts, new_element, elt_idx_btm, floor_int(y2));
    il_set(&qt->elts, new_element, elt_idx_id, id);

    // Insert the element to the appropriate leaf node(s).
    node_insert(qt, 0, 0, qt->root_mx, qt->root_my, qt->root_sx, qt->root_sy, new_element);
    return new_element;
}

void qt_remove(Quadtree* qt, int element)
{
    // Find the leaves.
    int j = 0;
    IntList leaves = {0};

    const int lft = il_get(&qt->elts, element, elt_idx_lft);
    const int top = il_get(&qt->elts, element, elt_idx_top);
    const int rgt = il_get(&qt->elts, element, elt_idx_rgt);
    const int btm = il_get(&qt->elts, element, elt_idx_btm);

    il_create(&leaves, nd_num);
    find_leaves(&leaves, qt, 0, 0, qt->root_mx, qt->root_my, qt->root_sx, qt->root_sy, lft, top, rgt, btm);

    // For each leaf node, remove the element node.
    for (j=0; j < il_size(&leaves); ++j)
    {
        const int nd_index = il_get(&leaves, j, nd_idx_index);

        // Walk the list until we find the element node.
        int node_index = il_get(&qt->nodes, nd_index, node_idx_fc);
        int prev_index = -1;
        while (node_index != -1 && il_get(&qt->enodes, node_index, enode_idx_elt) != element)
        {
            prev_index = node_index;
            node_index = il_get(&qt->enodes, node_index, enode_idx_next);
        }

        if (node_index != -1)
        {
            // Remove the element node.
            const int next_index = il_get(&qt->enodes, node_index, enode_idx_next);
            if (prev_index == -1)
                il_set(&qt->nodes, nd_index, node_idx_fc, next_index);
            else
                il_set(&qt->enodes, prev_index, enode_idx_next, next_index);
            il_erase(&qt->enodes, node_index);

            // Decrement the leaf element count.
            il_set(&qt->nodes, nd_index, node_idx_num, il_get(&qt->nodes, nd_index, node_idx_num)-1);
        }
    }
    il_destroy(&leaves);

    // Remove the element.
    il_erase(&qt->elts, element);
}

void qt_query(Quadtree* qt, IntList* out, float x1, float y1, float x2, float y2, int omit_element)
{
    // Find the leaves that intersect the specified query rectangle.
    int j = 0;
    IntList leaves = {0};
    const int elt_cap = il_size(&qt->elts);

    const int qlft = floor_int(x1);
    const int qtop = floor_int(y1);
    const int qrgt = floor_int(x2);
    const int qbtm = floor_int(y2);

    if (qt->temp_size < elt_cap)
    {
        qt->temp_size = elt_cap;
        qt->temp = realloc(qt->temp, qt->temp_size * sizeof(*qt->temp));
        memset(qt->temp, 0, qt->temp_size * sizeof(*qt->temp));
    }

    // For each leaf node, look for elements that intersect.
    il_create(&leaves, nd_num);
    find_leaves(&leaves, qt, 0, 0, qt->root_mx, qt->root_my, qt->root_sx, qt->root_sy, qlft, qtop, qrgt, qbtm);

    il_clear(out);
    for (j=0; j < il_size(&leaves); ++j)
    {
        const int nd_index = il_get(&leaves, j, nd_idx_index);

        // Walk the list and add elements that intersect.
        int elt_node_index = il_get(&qt->nodes, nd_index, node_idx_fc);
        while (elt_node_index != -1)
        {
            const int element = il_get(&qt->enodes, elt_node_index, enode_idx_elt);
            const int lft = il_get(&qt->elts, element, elt_idx_lft);
            const int top = il_get(&qt->elts, element, elt_idx_top);
            const int rgt = il_get(&qt->elts, element, elt_idx_rgt);
            const int btm = il_get(&qt->elts, element, elt_idx_btm);
            if (!qt->temp[element] && element != omit_element && intersect(qlft,qtop,qrgt,qbtm, lft,top,rgt,btm))
            {
                il_set(out, il_push_back(out), 0, element);
                qt->temp[element] = 1;
            }
            elt_node_index = il_get(&qt->enodes, elt_node_index, enode_idx_next);
        }
    }
    il_destroy(&leaves);

    // Unmark the elements that were inserted.
    for (j=0; j < il_size(out); ++j)
        qt->temp[il_get(out, j, 0)] = 0;
}

void qt_cleanup(Quadtree* qt)
{
    IntList to_process = {0};
    il_create(&to_process, 1);

    // Only process the root if it's not a leaf.
    if (il_get(&qt->nodes, 0, node_idx_num) == -1)
    {
        // Push the root index to the stack.
        il_set(&to_process, il_push_back(&to_process), 0, 0);
    }

    while (il_size(&to_process) > 0)
    {
        // Pop a node from the stack.
        const int node = il_get(&to_process, il_size(&to_process)-1, 0);
        const int fc = il_get(&qt->nodes, node, node_idx_fc);
        int num_empty_leaves = 0;
        int j = 0;
        il_pop_back(&to_process);

        // Loop through the children.
        for (j=0; j < 4; ++j)
        {
            const int child = fc + j;

            // Increment empty leaf count if the child is an empty 
            // leaf. Otherwise if the child is a branch, add it to
            // the stack to be processed in the next iteration.
            if (il_get(&qt->nodes, child, node_idx_num) == 0)
                ++num_empty_leaves;
            else if (il_get(&qt->nodes, child, node_idx_num) == -1)
            {
                // Push the child index to the stack.
                il_set(&to_process, il_push_back(&to_process), 0, child);
            }
        }

        // If all the children were empty leaves, remove them and 
        // make this node the new empty leaf.
        if (num_empty_leaves == 4)
        {
            // Remove all 4 children in reverse order so that they 
            // can be reclaimed on subsequent insertions in proper
            // order.
            il_erase(&qt->nodes, fc + 3);
            il_erase(&qt->nodes, fc + 2);
            il_erase(&qt->nodes, fc + 1);
            il_erase(&qt->nodes, fc + 0);

            // Make this node the new empty leaf.
            il_set(&qt->nodes, node, node_idx_fc, -1);
            il_set(&qt->nodes, node, node_idx_num, 0);
        }
    }
    il_destroy(&to_process);
}

void qt_traverse(Quadtree* qt, void* user_data, QtNodeFunc* branch, QtNodeFunc* leaf)
{
    IntList to_process = {0};
    il_create(&to_process, nd_num);
    push_node(&to_process, 0, 0, qt->root_mx, qt->root_my, qt->root_sx, qt->root_sy);

    while (il_size(&to_process) > 0)
    {
        const int back_idx = il_size(&to_process) - 1;
        const int nd_mx = il_get(&to_process, back_idx, nd_idx_mx);
        const int nd_my = il_get(&to_process, back_idx, nd_idx_my);
        const int nd_sx = il_get(&to_process, back_idx, nd_idx_sx);
        const int nd_sy = il_get(&to_process, back_idx, nd_idx_sy);
        const int nd_index = il_get(&to_process, back_idx, nd_idx_index);
        const int nd_depth = il_get(&to_process, back_idx, nd_idx_depth);
        const int fc = il_get(&qt->nodes, nd_index, node_idx_fc);
        il_pop_back(&to_process);

        if (il_get(&qt->nodes, nd_index, node_idx_num) == -1)
        {
            // Push the children of the branch to the stack.
            const int hx = nd_sx >> 1, hy = nd_sy >> 1;
            const int l = nd_mx-hx, t = nd_my-hx, r = nd_mx+hx, b = nd_my+hy;
            push_node(&to_process, fc+0, nd_depth+1, l,t, hx,hy);
            push_node(&to_process, fc+1, nd_depth+1, r,t, hx,hy);
            push_node(&to_process, fc+2, nd_depth+1, l,b, hx,hy);
            push_node(&to_process, fc+3, nd_depth+1, r,b, hx,hy);
            if (branch)
                branch(qt, user_data, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy);
        }
        else if (leaf)
            leaf(qt, user_data, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy);
    }
    il_destroy(&to_process);
}

临时结论

这不是一个很好的答案,但我会尝试回来继续编辑它。但是,上面的代码应该非常有效,几乎任何允许连续的普通旧整数数组的语言。只要我们有这种连续性保证,我们就可以提供一个非常易于缓存的四叉树,它使用非常小的存储空间。

有关整体方法的详细信息,请参阅原始答案。

答案 5 :(得分:7)

4。 Java IntList

我希望人们不介意我发布第三个答案,但我再次跑出了角色限制。我结束了在Java的第二个答案中移植C代码。对于移植到面向对象语言的人来说,Java端口可能更容易引用。

class IntList
{
    private int data[] = new int[128];
    private int num_fields = 0;
    private int num = 0;
    private int cap = 128;
    private int free_element = -1;

    // Creates a new list of elements which each consist of integer fields.
    // 'start_num_fields' specifies the number of integer fields each element has.
    public IntList(int start_num_fields)
    {
        num_fields = start_num_fields;
    }

    // Returns the number of elements in the list.
    int size()
    {
        return num;
    }

    // Returns the value of the specified field for the nth element.
    int get(int n, int field)
    {
        assert n >= 0 && n < num && field >= 0 && field < num_fields;
        return data[n*num_fields + field];
    }

    // Sets the value of the specified field for the nth element.
    void set(int n, int field, int val)
    {
        assert n >= 0 && n < num && field >= 0 && field < num_fields;
        data[n*num_fields + field] = val;
    }

    // Clears the list, making it empty.
    void clear()
    {
        num = 0;
        free_element = -1;
    }

    // Inserts an element to the back of the list and returns an index to it.
    int pushBack()
    {
        final int new_pos = (num+1) * num_fields;

        // If the list is full, we need to reallocate the buffer to make room
        // for the new element.
        if (new_pos > cap)
        {
            // Use double the size for the new capacity.
            final int new_cap = new_pos * 2;

            // Allocate new array and copy former contents.
            int new_array[] = new int[new_cap];
            System.arraycopy(data, 0, new_array, 0, cap);
            data = new_array;

            // Set the old capacity to the new capacity.
            cap = new_cap;
        }
        return num++;
    }

    // Removes the element at the back of the list.
    void popBack()
    {
        // Just decrement the list size.
        assert num > 0;
        --num;
    }

    // Inserts an element to a vacant position in the list and returns an index to it.
    int insert()
    {
        // If there's a free index in the free list, pop that and use it.
        if (free_element != -1)
        {
            final int index = free_element;
            final int pos = index * num_fields;

            // Set the free index to the next free index.
            free_element = data[pos];

            // Return the free index.
            return index;
        }
        // Otherwise insert to the back of the array.
        return pushBack();
    }

    // Removes the nth element in the list.
    void erase(int n)
    {
        // Push the element to the free list.
        final int pos = n * num_fields;
        data[pos] = free_element;
        free_element = n;
    }
}

Java四叉树

这是Java中的四叉树(对不起,如果它不是非常惯用的;我在大约十年左右的时间里没有编写Java并忘记了很多东西):

interface IQtVisitor
{
    // Called when traversing a branch node.
    // (mx, my) indicate the center of the node's AABB.
    // (sx, sy) indicate the half-size of the node's AABB.
    void branch(Quadtree qt, int node, int depth, int mx, int my, int sx, int sy);

    // Called when traversing a leaf node.
    // (mx, my) indicate the center of the node's AABB.
    // (sx, sy) indicate the half-size of the node's AABB.
    void leaf(Quadtree qt, int node, int depth, int mx, int my, int sx, int sy);
}

class Quadtree
{
    // Creates a quadtree with the requested extents, maximum elements per leaf, and maximum tree depth.
    Quadtree(int width, int height, int start_max_elements, int start_max_depth)
    {
        max_elements = start_max_elements;
        max_depth = start_max_depth;

        // Insert the root node to the qt.
        nodes.insert();
        nodes.set(0, node_idx_fc, -1);
        nodes.set(0, node_idx_num, 0);

        // Set the extents of the root node.
        root_mx = width / 2;
        root_my = height / 2;
        root_sx = root_mx;
        root_sy = root_my;
    }

    // Outputs a list of elements found in the specified rectangle.
    public int insert(int id, float x1, float y1, float x2, float y2)
    {
        // Insert a new element.
        final int new_element = elts.insert();

        // Set the fields of the new element.
        elts.set(new_element, elt_idx_lft, floor_int(x1));
        elts.set(new_element, elt_idx_top, floor_int(y1));
        elts.set(new_element, elt_idx_rgt, floor_int(x2));
        elts.set(new_element, elt_idx_btm, floor_int(y2));
        elts.set(new_element, elt_idx_id, id);

        // Insert the element to the appropriate leaf node(s).
        node_insert(0, 0, root_mx, root_my, root_sx, root_sy, new_element);
        return new_element;
    }

    // Removes the specified element from the tree.
    public void remove(int element)
    {
        // Find the leaves.
        final int lft = elts.get(element, elt_idx_lft);
        final int top = elts.get(element, elt_idx_top);
        final int rgt = elts.get(element, elt_idx_rgt);
        final int btm = elts.get(element, elt_idx_btm);
        IntList leaves = find_leaves(0, 0, root_mx, root_my, root_sx, root_sy, lft, top, rgt, btm);

        // For each leaf node, remove the element node.
        for (int j=0; j < leaves.size(); ++j)
        {
            final int nd_index = leaves.get(j, nd_idx_index);

            // Walk the list until we find the element node.
            int node_index = nodes.get(nd_index, node_idx_fc);
            int prev_index = -1;
            while (node_index != -1 && enodes.get(node_index, enode_idx_elt) != element)
            {
                prev_index = node_index;
                node_index = enodes.get(node_index, enode_idx_next);
            }

            if (node_index != -1)
            {
                // Remove the element node.
                final int next_index = enodes.get(node_index, enode_idx_next);
                if (prev_index == -1)
                    nodes.set(nd_index, node_idx_fc, next_index);
                else
                    enodes.set(prev_index, enode_idx_next, next_index);
                enodes.erase(node_index);

                // Decrement the leaf element count.
                nodes.set(nd_index, node_idx_num, nodes.get(nd_index, node_idx_num)-1);
            }
        }

        // Remove the element.
        elts.erase(element);
    }

    // Cleans up the tree, removing empty leaves.
    public void cleanup()
    {
        IntList to_process = new IntList(1);

        // Only process the root if it's not a leaf.
        if (nodes.get(0, node_idx_num) == -1)
        {
            // Push the root index to the stack.
            to_process.set(to_process.pushBack(), 0, 0);
        }

        while (to_process.size() > 0)
        {
            // Pop a node from the stack.
            final int node = to_process.get(to_process.size()-1, 0);
            final int fc = nodes.get(node, node_idx_fc);
            int num_empty_leaves = 0;
            to_process.popBack();

            // Loop through the children.
            for (int j=0; j < 4; ++j)
            {
                final int child = fc + j;

                // Increment empty leaf count if the child is an empty 
                // leaf. Otherwise if the child is a branch, add it to
                // the stack to be processed in the next iteration.
                if (nodes.get(child, node_idx_num) == 0)
                    ++num_empty_leaves;
                else if (nodes.get(child, node_idx_num) == -1)
                {
                    // Push the child index to the stack.
                    to_process.set(to_process.pushBack(), 0, child);
                }
            }

            // If all the children were empty leaves, remove them and 
            // make this node the new empty leaf.
            if (num_empty_leaves == 4)
            {
                // Remove all 4 children in reverse order so that they 
                // can be reclaimed on subsequent insertions in proper
                // order.
                nodes.erase(fc + 3);
                nodes.erase(fc + 2);
                nodes.erase(fc + 1);
                nodes.erase(fc + 0);

                // Make this node the new empty leaf.
                nodes.set(node, node_idx_fc, -1);
                nodes.set(node, node_idx_num, 0);
            }
        }
    }

    // Returns a list of elements found in the specified rectangle.
    public IntList query(float x1, float y1, float x2, float y2)
    {
        return query(x1, y1, x2, y2, -1);
    }

    // Returns a list of elements found in the specified rectangle excluding the
    // specified element to omit.
    public IntList query(float x1, float y1, float x2, float y2, int omit_element)
    {
        IntList out = new IntList(1);

        // Find the leaves that intersect the specified query rectangle.
        final int qlft = floor_int(x1);
        final int qtop = floor_int(y1);
        final int qrgt = floor_int(x2);
        final int qbtm = floor_int(y2);
        IntList leaves = find_leaves(0, 0, root_mx, root_my, root_sx, root_sy, qlft, qtop, qrgt, qbtm);

        if (temp_size < elts.size())
        {
            temp_size = elts.size();
            temp = new boolean[temp_size];;
        }

        // For each leaf node, look for elements that intersect.
        for (int j=0; j < leaves.size(); ++j)
        {
            final int nd_index = leaves.get(j, nd_idx_index);

            // Walk the list and add elements that intersect.
            int elt_node_index = nodes.get(nd_index, node_idx_fc);
            while (elt_node_index != -1)
            {
                final int element = enodes.get(elt_node_index, enode_idx_elt);
                final int lft = elts.get(element, elt_idx_lft);
                final int top = elts.get(element, elt_idx_top);
                final int rgt = elts.get(element, elt_idx_rgt);
                final int btm = elts.get(element, elt_idx_btm);
                if (!temp[element] && element != omit_element && intersect(qlft,qtop,qrgt,qbtm, lft,top,rgt,btm))
                {
                    out.set(out.pushBack(), 0, element);
                    temp[element] = true;
                }
                elt_node_index = enodes.get(elt_node_index, enode_idx_next);
            }
        }

        // Unmark the elements that were inserted.
        for (int j=0; j < out.size(); ++j)
            temp[out.get(j, 0)] = false;
        return out;
    }

    // Traverses all the nodes in the tree, calling 'branch' for branch nodes and 'leaf' 
    // for leaf nodes.
    public void traverse(IQtVisitor visitor)
    {
        IntList to_process = new IntList(nd_num);
        pushNode(to_process, 0, 0, root_mx, root_my, root_sx, root_sy);

        while (to_process.size() > 0)
        {
            final int back_idx = to_process.size() - 1;
            final int nd_mx = to_process.get(back_idx, nd_idx_mx);
            final int nd_my = to_process.get(back_idx, nd_idx_my);
            final int nd_sx = to_process.get(back_idx, nd_idx_sx);
            final int nd_sy = to_process.get(back_idx, nd_idx_sy);
            final int nd_index = to_process.get(back_idx, nd_idx_index);
            final int nd_depth = to_process.get(back_idx, nd_idx_depth);
            final int fc = nodes.get(nd_index, node_idx_fc);
            to_process.popBack();

            if (nodes.get(nd_index, node_idx_num) == -1)
            {
                // Push the children of the branch to the stack.
                final int hx = nd_sx >> 1, hy = nd_sy >> 1;
                final int l = nd_mx-hx, t = nd_my-hx, r = nd_mx+hx, b = nd_my+hy;
                pushNode(to_process, fc+0, nd_depth+1, l,t, hx,hy);
                pushNode(to_process, fc+1, nd_depth+1, r,t, hx,hy);
                pushNode(to_process, fc+2, nd_depth+1, l,b, hx,hy);
                pushNode(to_process, fc+3, nd_depth+1, r,b, hx,hy);
                visitor.branch(this, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy);
            }
            else
                visitor.leaf(this, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy);
        }
    }

    private static int floor_int(float val)
    {
        return (int)val;
    }

    private static boolean intersect(int l1, int t1, int r1, int b1,
                                     int l2, int t2, int r2, int b2)
    {
        return l2 <= r1 && r2 >= l1 && t2 <= b1 && b2 >= t1;
    }

    private static void pushNode(IntList nodes, int nd_index, int nd_depth, int nd_mx, int nd_my, int nd_sx, int nd_sy)
    {
        final int back_idx = nodes.pushBack();
        nodes.set(back_idx, nd_idx_mx, nd_mx);
        nodes.set(back_idx, nd_idx_my, nd_my);
        nodes.set(back_idx, nd_idx_sx, nd_sx);
        nodes.set(back_idx, nd_idx_sy, nd_sy);
        nodes.set(back_idx, nd_idx_index, nd_index);
        nodes.set(back_idx, nd_idx_depth, nd_depth);
    }

    private IntList find_leaves(int node, int depth, 
                                int mx, int my, int sx, int sy, 
                                int lft, int top, int rgt, int btm)
    {
        IntList leaves = new IntList(nd_num);
        IntList to_process = new IntList(nd_num);
        pushNode(to_process, node, depth, mx, my, sx, sy);

        while (to_process.size() > 0)
        {
            final int back_idx = to_process.size() - 1;
            final int nd_mx = to_process.get(back_idx, nd_idx_mx);
            final int nd_my = to_process.get(back_idx, nd_idx_my);
            final int nd_sx = to_process.get(back_idx, nd_idx_sx);
            final int nd_sy = to_process.get(back_idx, nd_idx_sy);
            final int nd_index = to_process.get(back_idx, nd_idx_index);
            final int nd_depth = to_process.get(back_idx, nd_idx_depth);
            to_process.popBack();

            // If this node is a leaf, insert it to the list.
            if (nodes.get(nd_index, node_idx_num) != -1)
                pushNode(leaves, nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy);
            else
            {
                // Otherwise push the children that intersect the rectangle.
                final int fc = nodes.get(nd_index, node_idx_fc);
                final int hx = nd_sx / 2, hy = nd_sy / 2;
                final int l = nd_mx-hx, t = nd_my-hx, r = nd_mx+hx, b = nd_my+hy;

                if (top <= nd_my)
                {
                    if (lft <= nd_mx)
                        pushNode(to_process, fc+0, nd_depth+1, l,t,hx,hy);
                    if (rgt > nd_mx)
                        pushNode(to_process, fc+1, nd_depth+1, r,t,hx,hy);
                }
                if (btm > nd_my)
                {
                    if (lft <= nd_mx)
                        pushNode(to_process, fc+2, nd_depth+1, l,b,hx,hy);
                    if (rgt > nd_mx)
                        pushNode(to_process, fc+3, nd_depth+1, r,b,hx,hy);
                }
            }
        }
        return leaves;
    }

    private void node_insert(int index, int depth, int mx, int my, int sx, int sy, int element)
    {
        // Find the leaves and insert the element to all the leaves found.
        final int lft = elts.get(element, elt_idx_lft);
        final int top = elts.get(element, elt_idx_top);
        final int rgt = elts.get(element, elt_idx_rgt);
        final int btm = elts.get(element, elt_idx_btm);
        IntList leaves = find_leaves(index, depth, mx, my, sx, sy, lft, top, rgt, btm);

        for (int j=0; j < leaves.size(); ++j)
        {
            final int nd_mx = leaves.get(j, nd_idx_mx);
            final int nd_my = leaves.get(j, nd_idx_my);
            final int nd_sx = leaves.get(j, nd_idx_sx);
            final int nd_sy = leaves.get(j, nd_idx_sy);
            final int nd_index = leaves.get(j, nd_idx_index);
            final int nd_depth = leaves.get(j, nd_idx_depth);
            leaf_insert(nd_index, nd_depth, nd_mx, nd_my, nd_sx, nd_sy, element);
        }
    }

    private void leaf_insert(int node, int depth, int mx, int my, int sx, int sy, int element)
    {
        // Insert the element node to the leaf.
        final int nd_fc = nodes.get(node, node_idx_fc);
        nodes.set(node, node_idx_fc, enodes.insert());
        enodes.set(nodes.get(node, node_idx_fc), enode_idx_next, nd_fc);
        enodes.set(nodes.get(node, node_idx_fc), enode_idx_elt, element);

        // If the leaf is full, split it.
        if (nodes.get(node, node_idx_num) == max_elements && depth < max_depth)
        {
            // Transfer elements from the leaf node to a list of elements.
            IntList elts = new IntList(1);
            while (nodes.get(node, node_idx_fc) != -1)
            {
                final int index = nodes.get(node, node_idx_fc);
                final int next_index = enodes.get(index, enode_idx_next);
                final int elt = enodes.get(index, enode_idx_elt);

                // Pop off the element node from the leaf and remove it from the qt.
                nodes.set(node, node_idx_fc, next_index);
                enodes.erase(index);

                // Insert element to the list.
                elts.set(elts.pushBack(), 0, elt);
            }

            // Start by allocating 4 child nodes.
            final int fc = nodes.insert();
            nodes.insert();
            nodes.insert();
            nodes.insert();
            nodes.set(node, node_idx_fc, fc);

            // Initialize the new child nodes.
            for (int j=0; j < 4; ++j)
            {
                nodes.set(fc+j, node_idx_fc, -1);
                nodes.set(fc+j, node_idx_num, 0);
            }

            // Transfer the elements in the former leaf node to its new children.
            nodes.set(node, node_idx_num, -1);
            for (int j=0; j < elts.size(); ++j)
                node_insert(node, depth, mx, my, sx, sy, elts.get(j, 0));
        }
        else
        {
            // Increment the leaf element count.
            nodes.set(node, node_idx_num, nodes.get(node, node_idx_num) + 1);
        }
    }


    // ----------------------------------------------------------------------------------------
    // Element node fields:
    // ----------------------------------------------------------------------------------------
    // Points to the next element in the leaf node. A value of -1 
    // indicates the end of the list.
    static final int enode_idx_next = 0;

    // Stores the element index.
    static final int enode_idx_elt = 1;

    // Stores all the element nodes in the quadtree.
    private IntList enodes = new IntList(2);

    // ----------------------------------------------------------------------------------------
    // Element fields:
    // ----------------------------------------------------------------------------------------
    // Stores the rectangle encompassing the element.
    static final int elt_idx_lft = 0, elt_idx_top = 1, elt_idx_rgt = 2, elt_idx_btm = 3;

    // Stores the ID of the element.
    static final int elt_idx_id = 4;

    // Stores all the elements in the quadtree.
    private IntList elts = new IntList(5);

    // ----------------------------------------------------------------------------------------
    // Node fields:
    // ----------------------------------------------------------------------------------------
    // Points to the first child if this node is a branch or the first element
    // if this node is a leaf.
    static final int node_idx_fc = 0;

    // Stores the number of elements in the node or -1 if it is not a leaf.
    static final int node_idx_num = 1;

    // Stores all the nodes in the quadtree. The first node in this
    // sequence is always the root.
    private IntList nodes = new IntList(2);

    // ----------------------------------------------------------------------------------------
    // Node data fields:
    // ----------------------------------------------------------------------------------------
    static final int nd_num = 6;

    // Stores the extents of the node using a centered rectangle and half-size.
    static final int nd_idx_mx = 0, nd_idx_my = 1, nd_idx_sx = 2, nd_idx_sy = 3;

    // Stores the index of the node.
    static final int nd_idx_index = 4;

    // Stores the depth of the node.
    static final int nd_idx_depth = 5;

    // ----------------------------------------------------------------------------------------
    // Data Members
    // ----------------------------------------------------------------------------------------
    // Temporary buffer used for queries.
    private boolean temp[];

    // Stores the size of the temporary buffer.
    private int temp_size = 0;

    // Stores the quadtree extents.
    private int root_mx, root_my, root_sx, root_sy;

    // Maximum allowed elements in a leaf before the leaf is subdivided/split unless
    // the leaf is at the maximum allowed tree depth.
    private int max_elements;

    // Stores the maximum depth allowed for the quadtree.
    private int max_depth;
}

临时结论

再次抱歉,这是一个代码转储答案。我会回来编辑它并尝试解释越来越多的东西。

有关整体方法的详细信息,请参阅原始答案。