用于 3D 网格的 Morton 反向编码

Posted

技术标签:

【中文标题】用于 3D 网格的 Morton 反向编码【英文标题】:Morton Reverse Encoding for a 3D grid 【发布时间】:2018-09-19 19:00:14 【问题描述】:

我有一个 3D 网格/数组,比如 u[nx+2][ny+2][nz+2]。尾随的 +2 对应于三个维度x,y,z 中的每一个中的两层晕细胞。我有另一个允许细化(使用四叉树)的网格,因此我有每个单元格的 morton 索引(或 Z 顺序)。

可以说,不加细化,这两个网格在物理现实中是相似的(除了第二个代码没有光环单元),我想要找到的是一个单元格 q 和 morton id mid 对应的索引是什么ijk 3D 网格中的索引。基本上是对mid 或Z 顺序的解码,以获得u 矩阵的对应i,j,k

寻找 C 解决方案,但任何其他编程语言的通用 cmets 也可以。

对于前向编码,我遵循如下所示的魔术位方法 Morton Encoding using different methods

【问题讨论】:

【参考方案1】:

莫顿编码只是将两个或多个组件的位交错。

如果我们按照重要性递增的顺序对二进制数字进行编号,那么无符号整数中的最低有效二进制数字是 0(并且二进制数字 i 的值为 2i),则N的组件k中的二进制数字i对应二进制数字(i i> N + k) 在莫顿代码中。

这里有两个简单的函数来编码和解码三分量莫顿码:

#include <stdlib.h>
#include <inttypes.h>

/* This source is in the public domain. */

/* Morton encoding in binary (components 21-bit: 0..2097151)
                0zyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyxzyx */
#define BITMASK_0000000001000001000001000001000001000001000001000001000001000001 UINT64_C(18300341342965825)
#define BITMASK_0000001000001000001000001000001000001000001000001000001000001000 UINT64_C(146402730743726600)
#define BITMASK_0001000000000000000000000000000000000000000000000000000000000000 UINT64_C(1152921504606846976)
/*              0000000ccc0000cc0000cc0000cc0000cc0000cc0000cc0000cc0000cc0000cc */
#define BITMASK_0000000000000011000000000011000000000011000000000011000000000011 UINT64_C(844631138906115)
#define BITMASK_0000000111000000000011000000000011000000000011000000000011000000 UINT64_C(126113986927919296)
/*              00000000000ccccc00000000cccc00000000cccc00000000cccc00000000cccc */
#define BITMASK_0000000000000000000000000000000000001111000000000000000000001111 UINT64_C(251658255)
#define BITMASK_0000000000000000000000001111000000000000000000001111000000000000 UINT64_C(1030792212480)
#define BITMASK_0000000000011111000000000000000000000000000000000000000000000000 UINT64_C(8725724278030336)
/*              000000000000000000000000000ccccccccccccc0000000000000000cccccccc */
#define BITMASK_0000000000000000000000000000000000000000000000000000000011111111 UINT64_C(255)
#define BITMASK_0000000000000000000000000001111111111111000000000000000000000000 UINT64_C(137422176256)
/*                                                         ccccccccccccccccccccc */
#define BITMASK_21BITS  UINT64_C(2097151)


static inline void morton_decode(uint64_t m, uint32_t *xto, uint32_t *yto, uint32_t *zto)

    const uint64_t  mask0 = BITMASK_0000000001000001000001000001000001000001000001000001000001000001,
                    mask1 = BITMASK_0000001000001000001000001000001000001000001000001000001000001000,
                    mask2 = BITMASK_0001000000000000000000000000000000000000000000000000000000000000,
                    mask3 = BITMASK_0000000000000011000000000011000000000011000000000011000000000011,
                    mask4 = BITMASK_0000000111000000000011000000000011000000000011000000000011000000,
                    mask5 = BITMASK_0000000000000000000000000000000000001111000000000000000000001111,
                    mask6 = BITMASK_0000000000000000000000001111000000000000000000001111000000000000,
                    mask7 = BITMASK_0000000000011111000000000000000000000000000000000000000000000000,
                    mask8 = BITMASK_0000000000000000000000000000000000000000000000000000000011111111,
                    mask9 = BITMASK_0000000000000000000000000001111111111111000000000000000000000000;
    uint64_t  x = m,
              y = m >> 1,
              z = m >> 2;

    /* 000c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c */
    x = (x & mask0) | ((x & mask1) >> 2) | ((x & mask2) >> 4);
    y = (y & mask0) | ((y & mask1) >> 2) | ((y & mask2) >> 4);
    z = (z & mask0) | ((z & mask1) >> 2) | ((z & mask2) >> 4);
    /* 0000000ccc0000cc0000cc0000cc0000cc0000cc0000cc0000cc0000cc0000cc */
    x = (x & mask3) | ((x & mask4) >> 4);
    y = (y & mask3) | ((y & mask4) >> 4);
    z = (z & mask3) | ((z & mask4) >> 4);
    /* 00000000000ccccc00000000cccc00000000cccc00000000cccc00000000cccc */
    x = (x & mask5) | ((x & mask6) >> 8) | ((x & mask7) >> 16);
    y = (y & mask5) | ((y & mask6) >> 8) | ((y & mask7) >> 16);
    z = (z & mask5) | ((z & mask6) >> 8) | ((z & mask7) >> 16);
    /* 000000000000000000000000000ccccccccccccc0000000000000000cccccccc */
    x = (x & mask8) | ((x & mask9) >> 16);
    y = (y & mask8) | ((y & mask9) >> 16);
    z = (z & mask8) | ((z & mask9) >> 16);
    /* 0000000000000000000000000000000000000000000ccccccccccccccccccccc */
    if (xto) *xto = x;
    if (yto) *yto = y;
    if (zto) *zto = z;



static inline uint64_t morton_encode(uint32_t xsrc, uint32_t ysrc, uint32_t zsrc)

    const uint64_t  mask0 = BITMASK_0000000001000001000001000001000001000001000001000001000001000001,
                    mask1 = BITMASK_0000001000001000001000001000001000001000001000001000001000001000,
                    mask2 = BITMASK_0001000000000000000000000000000000000000000000000000000000000000,
                    mask3 = BITMASK_0000000000000011000000000011000000000011000000000011000000000011,
                    mask4 = BITMASK_0000000111000000000011000000000011000000000011000000000011000000,
                    mask5 = BITMASK_0000000000000000000000000000000000001111000000000000000000001111,
                    mask6 = BITMASK_0000000000000000000000001111000000000000000000001111000000000000,
                    mask7 = BITMASK_0000000000011111000000000000000000000000000000000000000000000000,
                    mask8 = BITMASK_0000000000000000000000000000000000000000000000000000000011111111,
                    mask9 = BITMASK_0000000000000000000000000001111111111111000000000000000000000000;
    uint64_t  x = xsrc,
              y = ysrc,
              z = zsrc;
    /* 0000000000000000000000000000000000000000000ccccccccccccccccccccc */
    x = (x & mask8) | ((x << 16) & mask9);
    y = (y & mask8) | ((y << 16) & mask9);
    z = (z & mask8) | ((z << 16) & mask9);
    /* 000000000000000000000000000ccccccccccccc0000000000000000cccccccc */
    x = (x & mask5) | ((x << 8) & mask6) | ((x << 16) & mask7);
    y = (y & mask5) | ((y << 8) & mask6) | ((y << 16) & mask7);
    z = (z & mask5) | ((z << 8) & mask6) | ((z << 16) & mask7);
    /* 00000000000ccccc00000000cccc00000000cccc00000000cccc00000000cccc */
    x = (x & mask3) | ((x << 4) & mask4);
    y = (y & mask3) | ((y << 4) & mask4);
    z = (z & mask3) | ((z << 4) & mask4);
    /* 0000000ccc0000cc0000cc0000cc0000cc0000cc0000cc0000cc0000cc0000cc */
    x = (x & mask0) | ((x << 2) & mask1) | ((x << 4) & mask2);
    y = (y & mask0) | ((y << 2) & mask1) | ((y << 4) & mask2);
    z = (z & mask0) | ((z << 2) & mask1) | ((z << 4) & mask2);
    /* 000c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c00c */
    return x | (y << 1) | (z << 2);

这些功能对称地工作。为了解码,二进制数字和数字组被移动到更大的连续单元;为了编码,二进制数字组通过移位来分割和传播。检查掩码(BITMASK_ 常量以其二进制数字模式命名)和移位操作,以详细了解编码和解码是如何发生的。

虽然两个函数非常有效,但它们并未优化。

上述函数已经过验证测试,可以使用随机 21 位无符号整数分量进行数十亿次往返:解码 Morton 编码的值会产生原始的三个分量。

【讨论】:

答案看起来不错,我在其他地方也有类似的解决方案,但我不明白的是创建这些掩码背后的基本原理。好吧,我知道位掩码、位操作,但也许我才刚刚开始,因此很难形成关于如何进行操作的想法。 @datapanda:实现位交错的另一个选项是分别移动每个位。这将需要 20 个面罩和 20 个班次,每个组件。相反,每隔一个位(第一个之后的位组)同时移动,因此我们只需要移动每个组件的六个位或位组。口罩 0、3、5 和 8 留在原地;其他选择要移位的特定位组。从 0x0zyxzyxzyx...zyxzyx 开始,看看每个操作对它的影响。 你在你的回答中说“虽然两个函数非常有效,但它们没有优化。”,考虑到我必须对一些@进行编码和解码,它们感觉任何性能问题是多么不优化987654324@ 整数? @datapanda:在我的 Intel Core i5-7200U 上,一个微基准表明编码大约需要 37 个时钟周期,解码大约需要 34 个。使用 Xorshift64* 伪随机数生成器,我可以生成、编码,在不到两秒的挂钟时间内解码和验证 400×400×400 三元组。我认为这已经足够快了。无需再花时间优化它。现在,如果你有数千亿个三元组,我可能会开始考虑进一步优化它;不是以前。 这似乎很快。当然没有必要花时间在优化上。另一个问题是,我在生产代码中的i,j,k 被声明为int 而不是uint_32,这没有任何意义,因为它们永远不会是负值。将 uint_32 转换为 int 以访问数组是否会产生任何问题,因为代码也将在具有不同架构的 HPC 集群上运行。参考。 ***.com/questions/19490240/…

以上是关于用于 3D 网格的 Morton 反向编码的主要内容,如果未能解决你的问题,请参考以下文章

2D Morton 解码功能 64bits

2D Morton 解码功能 64bits

2d Morton 码 64bits 解码功能

如何计算 3D Morton 数(交错 3 个整数的位)

2D morton 代码编码/解码 64 位

Morton 代码对于更高维度是最有效的吗?