Data structures 用于2D碰撞检测的四叉树的高效(并且解释得很好)实现
我一直在为我正在编写的程序添加一个四叉树,我不禁注意到,对于我正在寻找的实现,很少有解释良好/性能良好的教程 具体地说,我要寻找的是一系列方法和伪代码,这些方法和伪代码用于如何实现它们(或者只是对它们的过程的描述),这些方法和伪代码通常在四叉树中使用(检索、插入、删除等),以及一些提高性能的技巧。这是用于碰撞检测的,因此最好用2d矩形来解释,因为它们是将要存储的对象Data structures 用于2D碰撞检测的四叉树的高效(并且解释得很好)实现,data-structures,collision,rectangles,quadtree,Data Structures,Collision,Rectangles,Quadtree,我一直在为我正在编写的程序添加一个四叉树,我不禁注意到,对于我正在寻找的实现,很少有解释良好/性能良好的教程 具体地说,我要寻找的是一系列方法和伪代码,这些方法和伪代码用于如何实现它们(或者只是对它们的过程的描述),这些方法和伪代码通常在四叉树中使用(检索、插入、删除等),以及一些提高性能的技巧。这是用于碰撞检测的,因此最好用2d矩形来解释,因为它们是将要存储的对象 有效四叉树 好吧,我来试试这个。首先是一个小游戏,展示我提议的涉及20000名代理的结果(这正是我为这个特定问题快速准备的):
好吧,我来试试这个。首先是一个小游戏,展示我提议的涉及20000名代理的结果(这正是我为这个特定问题快速准备的): GIF极大地降低了帧速率,并显著降低了分辨率,以适应此站点的最大2MB。这里有一个视频,如果你想以接近全速观看的话: 还有一个100k的GIF,尽管我不得不花很多时间来摆弄它,不得不关闭四叉树行(似乎不想在打开它们的情况下压缩那么多),并且改变代理的方式,使其适合2兆字节(我希望制作GIF像编写四叉树一样简单): 使用20k代理进行模拟需要约3 MB的RAM。我还可以在不牺牲帧速率的情况下轻松处理100k较小的代理,尽管这会导致屏幕上出现一点混乱,以至于你几乎看不到上面的GIF所显示的情况。在我的i7上,这一切都只在一个线程中运行,根据VTune,我花费了几乎一半的时间在屏幕上绘制这些东西(只需使用一些基本的标量指令,在CPU中一次一个像素地绘制东西) 虽然很难看出发生了什么。这是一个很大的视频,我找不到任何合适的方法来压缩它,而不让整个视频变成一团乱麻(可能需要先下载或缓存它,才能以合理的FPS观看它的流)。对于100k代理,模拟需要大约4.5 MB的RAM,在运行模拟大约5秒后,内存使用非常稳定(停止上升或下降,因为它停止堆分配) 用于碰撞检测的高效四叉树 好的,实际上四叉树并不是我最喜欢的数据结构。我倾向于使用网格层次结构,如世界的粗网格、区域的细网格和子区域的更细网格(3个固定级别的密集网格,不涉及树),以及基于行的优化,以便释放其中没有实体的行并将其转换为空指针,同样,完全空的区域或子区域也变成了空。虽然这个在一个线程中运行的四叉树的简单实现可以在我的i7上以60+FPS的速度处理100k个代理,但我已经实现了网格,可以在旧硬件(i3)上每帧处理数百万个相互反弹的代理。此外,我一直喜欢网格使预测它们需要多少内存变得非常容易,因为它们不细分单元。但我将尝试介绍如何实现一个合理有效的四叉树 请注意,我不会详细介绍数据结构的全部理论。我假设您已经知道这一点,并且对改进性能感兴趣。我还将探讨我个人解决这个问题的方法,这似乎比我在网上找到的大多数案例解决方案都要好,但有很多不错的方法,这些解决方案都适合我的使用案例(非常大的输入,电影和电视中每一帧的视觉效果都在移动)。其他人可能会针对不同的用例进行优化。特别是在空间索引结构方面,我认为解决方案的效率比数据结构更能说明实现者。同样,我将提出的加速策略也适用于八叉树的三维空间 节点表示法 首先,让我们介绍一下节点表示:
//表示四叉树中的节点。
结构四节点
{
//如果此节点是分支或第一个子节点,则指向第一个子节点
//元素,如果此节点是叶。
第一个孩子;
//存储叶中的元素数,如果此节点不可用,则存储-1
//一片叶子也没有。
int32_t计数;
};
总共是8个字节,这非常重要,因为它是速度的关键部分。我实际上使用了一个较小的(每个节点6字节),但我将把它作为练习留给读者
您可能不需要计数
。我将其包括在病理病例中,以避免线性遍历元素,并在每次叶节点分裂时对元素进行计数。在大多数情况下,节点不应该存储那么多元素。然而,我在VisualFX工作,病理病例并不一定罕见。你可能会遇到艺术家用一大堆重合点、横跨整个场景的巨大多边形等来创建内容,因此我最终存储了一个计数
AABBs在哪里?
因此,人们可能想知道的第一件事是节点的边界框(矩形)在哪里。我不储存它们。我在飞行中计算它们。我有点惊讶大多数人在我看到的代码中没有这样做。对我来说,它们只存储在树结构中(基本上根只有一个AABB)
这看起来可能需要更高的成本来计算这些,但是减少节点的内存使用可以在遍历树时成比例地减少缓存未命中率,并且这些缓存未命中率的减少往往比遍历期间必须进行几次位移位和一些加/减操作更为显著。
first_child+0 = index to 1st child (TL)
first_child+1 = index to 2nd child (TR)
first_child+2 = index to 3nd child (BL)
first_child+3 = index to 4th child (BR)
for each element in scene:
use quad tree to check for collision against other elements
traversed = {}
gather quadtree leaves
for each leaf in leaves:
{
for each element in leaf:
{
if not traversed[element]:
{
use quad tree to check for collision against other elements
traversed[element] = true
}
}
}
elements[n].field = elements[n].field + 1;
il_set(&elements, n, idx_field, il_get(&elements, n, idx_field) + 1);
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.
}
}
}
}
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;
};
// 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.
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;
};
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;
};