【问题标题】:How to find a octree node's neighbors when the tree is ordered by Morton code当树按莫顿代码排序时如何找到八叉树节点的邻居
【发布时间】:2016-11-02 20:16:54
【问题描述】:

我有一个八叉树,我想用它在 3D 空间中执行寻路。我正在使用 Morton 代码对节点进行排序,以便能够轻松找到任何给定节点附近的节点。它似乎工作得很好;我抓取的任何节点子集都相对靠近地聚集在一起。我正在努力寻找一种有效的方法来获取节点的直接邻居。

树的早期版本让它细分树,直到叶子都是我想要的大小相等的节点。这使得查找邻居变得容易,因为每个节点之间存在一致的偏移量,因此我可以在我希望找到节点的位置重新计算 Morton 代码,然后根据代码检索节点。但是我试图通过在没有那么多几何图形的区域(例如,我会得到 8 个空孩子的地方)不把它分解得那么多,从而提高树的效率。结果,我不再知道给定节点可能有多少邻居,也不知道任何邻居的大小(除了它是 2 的幂)。

我最初的想法是在数组中的任一方向搜索附近的节点,这会起作用,但我必须撒网太宽才能保证得到所有邻居,而且我会花时间评估一个大量我不需要的节点。快速测试表明,即使阵列中任一方向的 50 个节点也不能保证获得所有邻居。

树是静态的,所以我也许可以使用上述蛮力方法一次性生成节点的邻居并存储邻居;但我担心这可能会使程序的初始加载时间超出可接受的水平。有没有可以实时找到邻居的算法?

编辑:改写问题以阐明我在寻找什么以及我尝试过什么。

【问题讨论】:

    标签: 3d nodes path-finding octree


    【解决方案1】:

    是的。

    不要蛮力,直接去你想要的图块。

    只要你有:

    • 一个Tile toMorton(Point3D location) 函数,它根据实际位置提供一个 Tile
    • 一个 Point3D fromMorton(Tile t) 函数,它提供了一个 Tile 的真实世界坐标(我相信两者都可以从 Wikipaedia 获得)

    这些都是简单的实时操作。

    天真的方法

    只需转换为真实世界,在那里偏移,然后回到莫顿空间:

    Tile originalTile = ...
    Point3D originalLocation = fromMorton(original);
    Point3D offsetLocation = new Point3D(offsetLocation.x + 1, offsetLocation.y, offsetLocation.y);
    Tile offsetTile = toMorton(offsetLocation)
    

    这只需要一些算术运算,因此应该已经适合实时(比暴力破解更适用)。

    优化方法

    在 Google 上快速搜索“morton 代码社区”后,我找到了来自 volumeseoffun 的一篇写得很好的博客文章。

    我将在这里提供最好的部分:

    对于具有莫顿位置 p 的给定 (x,y,z);如果我们想查看 (x+1,y,z),那么 p 必须增加的量 Δp 仅取决于 x,并且与 y 和 z 无关。同样的逻辑也适用于查看 y 和 z。

    这意味着我们不需要toMorton / fromMorton 所有三个坐标

    此外(尽管博客和它的source 都没有支持它)似乎所说的偏移量可以通过查找表预先计算。这为您正在寻找的实时功能提供了额外的加速。鉴于 Morton 算法定义明确且可重复,对我来说这是有道理的,所以我相信这个假设。

    只需预先计算偏移表,然后从一个起始图块开始,使用该表将你的图块偏移到它的真实邻居:

    int[] offsetX = [ ... compute once ... ]
    int[] offsetY = [ ... compute once ... ]
    int[] offsetZ = [ ... compute once ... ]
    Tile[] allTiles = ... // Init 3D world
    public Tile neighbour(Tile original, Direction dir){
         switch(dir){
         case DIR_X_PLUS: return allTiles[original.chunkID + offsetX[original.chunkID];
         case DIR_X_MINUS: return allTiles[original.chunkID - offsetX[original.chunkID];
         case DIR_Y_PLUS: return allTiles[original.chunkID + offsetY[original.chunkID];
         [... etc...]
         default: return original;
         }
    }
    

    希望这会有所帮助。

    【讨论】:

    • 您所描述的是我目前的工作方式。我在说的是如果邻居的大小不同。例如,目前每个节点都是 2x2x2,所以如果我正在寻找上面的邻居,我会得到 9 个邻居(正上方、北、东、南、西和对角线)。我只需要在适当的方向偏移 2。但是,如果不是所有 2x2x2 邻居,而是其中一个邻居是 8x8x8 怎么办,因为树在该区域没有细分那么多。我怎么知道我需要调整偏移量,或者调整多少?除非我错过了有关此解释的某些内容。
    • 呃,如果我完全错过了,我一定是在那儿放了个屁。答案不成立,但您仍然可以这样做,尽管在每个级别上。为此,我们需要详细说明每一层的存储方式。是否有一个独立应用的 Morton 函数每层
    • 我想我明白了你的“网”隐喻:你在 Morton 阵列中采样,在原始图块的前面和后面?那是行不通的,邻居可以任意远(好吧,除了大小有限的树,但那么限制可能会接近整个数组。)
    • Morton 代码应用于每个节点,甚至包含其他节点的节点。但是,节点当前不具备它的细分级别。虽然很容易添加。您是否建议对可能是邻居的每个细分级别执行偏移,并将结果的组合作为邻居?
    • 细分仍然不是 100% 清晰。不同层次级别的节点可能共享相同的现实世界位置,但又是不同的。就像一级块{(0,0)} 会有一个莫顿地址0,但二级块{(0,0),(0,1),(1,0),(1,1)} 等也是如此。这意味着当你细分块时,每一层都必须有自己的莫顿数组来支持它。那是你打算做的吗?如果是这样,那么是的,这就是我的建议,也许是一个新的答案。
    猜你喜欢
    • 1970-01-01
    • 2015-12-01
    • 1970-01-01
    • 2020-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-29
    • 1970-01-01
    相关资源
    最近更新 更多