如何快速计算相邻体素的数量?

时间:2009-06-13 17:04:16

标签: algorithm language-agnostic 3d voxels

我有一个3D网格(体素),其中一些体素被填充,有些则没有。 3D网格稀疏地填充,因此我有一组filledVoxels,其中填充体素的坐标(x,y,z)。我想要做的是找出每个填充的体素,也填充了多少相邻的体素。

以下是一个例子:

  • filledVoxels包含体素(1,1,1),(1,2,1)和(1,3,1)。
  • 因此,邻居计数是:
    • (1,1,1)有1个邻居
    • (1,2,1)有2个邻居
    • (1,3,1)有1个邻居。

现在我有这个算法:

voxelCount = new Map<Voxel, Integer>();

for (voxel v in filledVoxels)
  count = checkAllNeighbors(v, filledVoxels);
  voxelCount[v] = count;
end

checkAllNeighbors()查找所有26个周围的体素。所以总的来说我做26 * filledVoxels.size()查找,这很慢。

有没有办法减少所需的查找次数?当您查看上面的示例时,您可以看到我多次检查相同的体素,因此可以通过一些聪明的缓存来摆脱查找。

如果这有任何帮助,体素代表一个体素化的3D表面(但它可能有洞)。我通常想得到一个包含5个或6个邻居的所有体素的列表。

8 个答案:

答案 0 :(得分:5)

您可以将体素空间转换为octree,其中每个节点都包含一个标志,指定它是否包含填充的体素。

当节点不包含填充的体素时,您不需要检查其任何后代。

答案 1 :(得分:2)

我会说如果你的每个查找都很慢(O(大小)),你应该通过有序列表中的二进制搜索来优化它(O(log(size)))。

常数26,我不会太担心。如果你更聪明地迭代,你可以缓存一些东西并且有26 - &gt;我想,10或者其他什么,但除非你对整个应用程序进行了描述,并且果断地发现它是瓶颈,否则我会专注于其他事情。

答案 2 :(得分:2)

正如ilya所说,你可以做很多事情来绕过26个邻居的观察。您必须在有效识别特定邻居是否被填满方面获得最大收益。鉴于蛮力解决方案基本上是O(N ^ 2),你可以在该区域获得很多可能的基础。由于你必须至少迭代一次所有填充的体素,我会采取类似于以下的方法:

voxelCount = new Map<Voxel, Integer>();
visitedVoxels = new EfficientSpatialDataType();

for (voxel v in filledVoxels)
  for (voxel n in neighbors(v))
    if (visitedVoxels.contains(n))
      voxelCount[v]++;
      voxelCount[n]++;
    end
  next
  visitedVoxels.add(v);
next

对于有效的空间数据类型,Zifre建议的kd树可能是个好主意。在任何情况下,您都希望通过对访问过的体素进行分级来减少搜索空间。

答案 3 :(得分:1)

如果您一次一个地沿着体素行进,则可以保留与网格对应的查找表,以便在使用IsFullVoxel()检查一次后,将值放在此网格中。对于您正在进行的每个体素,您可以检查其查找表值是否有效,并且只调用IsFullVoxel()它不是。

OTOH似乎无法避免使用IsFullVoxel()或LUT迭代所有相邻体素。如果您有更多的先验信息,它可以提供帮助。例如,如果您知道最多x个相邻的填充体素,或者您知道每个方向上最多有y个相邻的填充体素。例如,如果你知道你正在寻找有5到6个邻居的体素,你可以在找到7个完全邻居或22个空邻居后停止。

我假设存在一个函数IsFullVoxel(),如果体素已满,则返回true。

答案 4 :(得分:1)

如果您的迭代中的大部分动作都发生在邻居身上,那么您可以通过不再回顾刚刚完成步骤之前检查过的动作来减少约25%的检查。

答案 5 :(得分:1)

您可以在此处找到Z-order curve有用的概念。它允许您(带有某些条件)在您当前查询的点周围保留一个数据滑动窗口,这样当您移动到下一个点时,您不必丢弃已执行的许多查询

答案 6 :(得分:0)

嗯,你的问题不是很清楚。我假设你只有一个填充点列表。在这种情况下,这个 会非常慢,因为你必须遍历它(或者使用某种树结构,例如kd-tree,但这仍然是{{ 1}})。

如果可以(即网格不是太大),只需制作一个三维布尔阵列。在3d数组中进行26次查找不应该花费那么长时间(并且实际上没有办法减少查找次数)。

实际上,现在我想起来了,你可以把它做成一个长数组(64位)的3d数组。每个64位块将容纳64(4 x 4 x 4)个体素。当您检查块中间的体素的邻居时,您可以执行单个64位读取(这将更快)。

答案 7 :(得分:0)

有没有办法减少所需的查询次数?

您至少必须在每个体素最少 1次查找时执行。由于这是最小的,因此任何只对每个体素执行一次查找的算法都将满足您的要求。

一个简单的想法是初始化一个数组以保存每个体素的计数,然后查看每个体素并增加数组中该体素的邻居。

伪C可能看起来像这样:

#define MAXX 100
#define MAXY 100
#define MAXZ 100

int x, y, z
char countArray[MAXX][MAXY][MAXZ];

initializeCountArray(MAXX, MAXY, MAXZ);  // Set all array elements to 0

for(x=0; x<MAXX; x++)
   for(y=0;y<MAXY;y++)
      for(z=0;z<MAXZ;z++)
         if(VoxelExists(x,y,z))
            incrementNeighbors(x,y,z);

您需要编写initializeCountArray,以便将所有数组元素设置为0。

更重要的是,您还需要编写incrementNeighbors,以便它不会在数组外增加。这里的轻微速度增加仅在内部的所有体素上执行上述算法,然后在所有外边缘体素上进行单独运行,其中修改的incrementNeighbrs例程理解一侧不会有邻居。

该算法导致每个体素1次查找,每个体素最多26个字节的加法。如果您的体素空间稀疏,那么这将导致很少(相对)添加。如果您的体素空间非常密集,您可以考虑反转算法 - 将每个条目的数组初始化为值26,然后在体素不存在时递减邻居。

给定体素的结果(即,我有多少邻居?)驻留在数组中。如果您需要知道体素2,3,5有多少邻居,只需查看countArray [2] [3] [5]中的字节。

每个体素的数组消耗1个字节。您可以使用更少的空间,并可能通过打包字节来提高速度。

如果您了解有关数据的详细信息,则可以使用更好的算法。例如,非常稀疏的体素空间将从八叉树中受益匪浅,当您已经知道内部没有填充的体素时,您可以跳过大块查找。然而,大多数这些算法仍然需要每个体素至少一个查找来填充它们的矩阵,但是如果你正在执行几个操作,那么它们可能比这一个操作受益更多。