我有一个3D网格(体素),其中一些体素被填充,有些则没有。 3D网格稀疏地填充,因此我有一组filledVoxels
,其中填充体素的坐标(x,y,z)。我想要做的是找出每个填充的体素,也填充了多少相邻的体素。
以下是一个例子:
现在我有这个算法:
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个邻居的所有体素的列表。
答案 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)
如果可以(即网格不是太大),只需制作一个三维布尔阵列。在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个字节。您可以使用更少的空间,并可能通过打包字节来提高速度。
如果您了解有关数据的详细信息,则可以使用更好的算法。例如,非常稀疏的体素空间将从八叉树中受益匪浅,当您已经知道内部没有填充的体素时,您可以跳过大块查找。然而,大多数这些算法仍然需要每个体素至少一个查找来填充它们的矩阵,但是如果你正在执行几个操作,那么它们可能比这一个操作受益更多。