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

Posted

技术标签:

【中文标题】如何计算 3D Morton 数(交错 3 个整数的位)【英文标题】:How to compute a 3D Morton number (interleave the bits of 3 ints) 【发布时间】:2010-11-04 17:10:53 【问题描述】:

我正在寻找一种快速计算 3D 莫顿数的方法。 This site 有一个基于幻数的技巧来处理 2D 莫顿数,但如何将其扩展到 3D 似乎并不明显。

所以基本上我有 3 个 10 位数,我想用最少的操作将它们交织成一个 30 位数。

【问题讨论】:

链接应该是graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN,但只有一个'.'编辑是不够的...... @mrblewog 已修复 :) 【参考方案1】:

您可以使用相同的技术。我假设变量包含 32 位整数,最高 22 位设置为0(这比必要的限制要多一些)。对于包含三个 10 位整数之一的每个变量 x,我们执行以下操作:

x = (x | (x << 16)) & 0x030000FF;
x = (x | (x <<  8)) & 0x0300F00F;
x = (x | (x <<  4)) & 0x030C30C3;
x = (x | (x <<  2)) & 0x09249249;

然后,使用xyz 这三个经过处理的 10 位整数,我们通过以下方式得到结果:

x | (y << 1) | (z << 2)

这种技术的工作方式如下。上面的每一行x = ... 都将位组“拆分”成两半,这样其他整数的位之间就有足够的空间。例如,如果我们考虑三个 4 位整数,我们将一个具有 1234 位的整数拆分为 000012000034,其中零为其他整数保留。在下一步中,我们以相同的方式拆分 12 和 34,得到 001002003004。尽管 10 位不能很好地重复划分为两组,但您可以将其视为 16 位,最终您会丢失最高的位.

从第一行可以看出,实际上您只需要为每个输入整数 x 它保留 x &amp; 0x03000000 == 0

【讨论】:

该死的!当你们都比我早 5 分钟到达那里时,我正要发布类似的解决方案! :)【参考方案2】:

这是我使用 python 脚本的解决方案:

我从他的评论中得到了暗示:F*** “ryg” Giesen 阅读下面的长评论!我们需要跟踪哪些位需要走多远! 然后在每一步中,我们选择这些位并移动它们并应用位掩码(参见最后几行的注释)来屏蔽它们!

Bit Distances: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Bit Distances (binary): ['0', '10', '100', '110', '1000', '1010', '1100', '1110', '10000', '10010']
Shifting bits by 1   for bits idx: []
Shifting bits by 2   for bits idx: [1, 3, 5, 7, 9]
Shifting bits by 4   for bits idx: [2, 3, 6, 7]
Shifting bits by 8   for bits idx: [4, 5, 6, 7]
Shifting bits by 16  for bits idx: [8, 9]
BitPositions: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Shifted bef.:   0000 0000 0000 0000 0000 0011 0000 0000 hex: 0x300
Shifted:        0000 0011 0000 0000 0000 0000 0000 0000 hex: 0x3000000
NonShifted:     0000 0000 0000 0000 0000 0000 1111 1111 hex: 0xff
Bitmask is now: 0000 0011 0000 0000 0000 0000 1111 1111 hex: 0x30000ff

Shifted bef.:   0000 0000 0000 0000 0000 0000 1111 0000 hex: 0xf0
Shifted:        0000 0000 0000 0000 1111 0000 0000 0000 hex: 0xf000
NonShifted:     0000 0011 0000 0000 0000 0000 0000 1111 hex: 0x300000f
Bitmask is now: 0000 0011 0000 0000 1111 0000 0000 1111 hex: 0x300f00f

Shifted bef.:   0000 0000 0000 0000 1100 0000 0000 1100 hex: 0xc00c
Shifted:        0000 0000 0000 1100 0000 0000 1100 0000 hex: 0xc00c0
NonShifted:     0000 0011 0000 0000 0011 0000 0000 0011 hex: 0x3003003
Bitmask is now: 0000 0011 0000 1100 0011 0000 1100 0011 hex: 0x30c30c3

Shifted bef.:   0000 0010 0000 1000 0010 0000 1000 0010 hex: 0x2082082
Shifted:        0000 1000 0010 0000 1000 0010 0000 1000 hex: 0x8208208
NonShifted:     0000 0001 0000 0100 0001 0000 0100 0001 hex: 0x1041041
Bitmask is now: 0000 1001 0010 0100 1001 0010 0100 1001 hex: 0x9249249

x &= 0x3ff
x = (x | x << 16) & 0x30000ff   <<< THIS IS THE MASK for shifting 16 (for bit 8 and 9)
x = (x | x << 8) & 0x300f00f
x = (x | x << 4) & 0x30c30c3
x = (x | x << 2) & 0x9249249

所以对于一个 10 位数字和 2 个交错位(对于 32 位),您需要执行以下操作!:

x &= 0x3ff
x = (x | x << 16) & 0x30000ff   #<<< THIS IS THE MASK for shifting 16 (for bit 8 and 9)
x = (x | x << 8) & 0x300f00f
x = (x | x << 4) & 0x30c30c3
x = (x | x << 2) & 0x9249249

对于 21 位数字和 2 个交织位(对于 64 位),您需要执行以下操作!:

x &= 0x1fffff
x = (x | x << 32) & 0x1f00000000ffff
x = (x | x << 16) & 0x1f0000ff0000ff
x = (x | x << 8) & 0x100f00f00f00f00f
x = (x | x << 4) & 0x10c30c30c30c30c3
x = (x | x << 2) & 0x1249249249249249

对于 42 位数字和 2 个交织位(对于 128 位),您需要执行以下操作(以防万一;-)):

x &= 0x3ffffffffff
x = (x | x << 64) & 0x3ff0000000000000000ffffffffL
x = (x | x << 32) & 0x3ff00000000ffff00000000ffffL
x = (x | x << 16) & 0x30000ff0000ff0000ff0000ff0000ffL
x = (x | x << 8) & 0x300f00f00f00f00f00f00f00f00f00fL
x = (x | x << 4) & 0x30c30c30c30c30c30c30c30c30c30c3L
x = (x | x << 2) & 0x9249249249249249249249249249249L

生成和检查交错模式的 Python 脚本!!!

def prettyBinString(x,d=32,steps=4,sep=".",emptyChar="0"):
    b = bin(x)[2:]
    zeros = d - len(b)
    
    
    if zeros <= 0: 
        zeros = 0
        k = steps - (len(b) % steps)
    else:
        k = steps - (d % steps)
        
    s = ""
    #print("zeros" , zeros)
    #print("k" , k)
    for i in range(zeros): 
        #print("k:",k)
        if(k%steps==0 and i!= 0):
            s+=sep
        s += emptyChar
        k+=1
     
    for i in range(len(b)):
        if( (k%steps==0 and i!=0 and zeros == 0) or  (k%steps==0 and zeros != 0) ):
            s+=sep
        s += b[i]
        k+=1
    return s    

def binStr(x): return prettyBinString(x,32,4," ","0")


def computeBitMaskPatternAndCode(numberOfBits, numberOfEmptyBits):
    bitDistances=[ i*numberOfEmptyBits for i in range(numberOfBits) ]
    print("Bit Distances: " + str(bitDistances))
    bitDistancesB = [bin(dist)[2:] for dist in  bitDistances]
    print("Bit Distances (binary): " + str(bitDistancesB))
    moveBits=[] #Liste mit allen Bits welche aufsteigend um 2, 4,8,16,32,64,128 stellen geschoben werden müssen

    maxLength = len(max(bitDistancesB, key=len))
    abort = False
    for i in range(maxLength):
        moveBits.append([])
        for idx,bits in enumerate(bitDistancesB):
            if not len(bits) - 1 < i:
                if(bits[len(bits)-i-1] == "1"):
                    moveBits[i].append(idx)
                    
    for i in range(len(moveBits)):
        print("Shifting bits by " + str(2**i) + "\t for bits idx: " + str(moveBits[i]))
    
    bitPositions = range(numberOfBits);
    print("BitPositions: " + str(bitPositions))
    maskOld = (1 << numberOfBits) -1
    
    codeString = "x &= " + hex(maskOld) + "\n"
    for idx in xrange(len(moveBits)-1, -1, -1):
        if len(moveBits[idx]):
            
           
           shifted = 0
           for bitIdxToMove in moveBits[idx]:
                shifted |= 1<<bitPositions[bitIdxToMove];
                bitPositions[bitIdxToMove] += 2**idx; # keep track where the actual bit stands! might get moved several times
           
           # Get the non shifted part!     
           nonshifted = ~shifted & maskOld
           
           print("Shifted bef.:\t" + binStr(shifted) + " hex: " + hex(shifted))
           shifted = shifted << 2**idx
           print("Shifted:\t" + binStr(shifted)+ " hex: " + hex(shifted))
                
           print("NonShifted:\t" + binStr(nonshifted) + " hex: " + hex(nonshifted))
           maskNew =  shifted | nonshifted
           print("Bitmask is now:\t" + binStr(maskNew) + " hex: " + hex(maskNew) +"\n")
           #print("Code: " + "x = x | x << " +str(2**idx)+ " & " +hex(maskNew))
                
           codeString += "x = (x | x << " +str(2**idx)+ ") & " +hex(maskNew) + "\n"
           maskOld = maskNew
    return codeString


numberOfBits = 10;
numberOfEmptyBits = 2;
codeString = computeBitMaskPatternAndCode(numberOfBits,numberOfEmptyBits);
print(codeString)

def partitionBy2(x):
    exec(codeString)
    return x

def checkPartition(x):
    print("Check partition for: \t" + binStr(x))
    part = partitionBy2(x);
    print("Partition is : \t\t" + binStr(part))
    #make the pattern manualy
    partC = long(0);
    for bitIdx in range(numberOfBits):
        partC  = partC | (x & (1<<bitIdx)) << numberOfEmptyBits*bitIdx
    print("Partition check is :\t" + binStr(partC))
    if(partC == part):
        return True
    else:
        return False

checkError = False        
for i in range(20):
    x = random.getrandbits(numberOfBits);
    if(checkPartition(x) == False):
        checkError = True
        break
if not checkError:
    print("CHECK PARTITION SUCCESSFUL!!!!!!!!!!!!!!!!...")
else:
    print("checkPartition has ERROR!!!!")

【讨论】:

【参考方案3】:

如果您有 4K 可用空间,最简单的可能是查找表:

static uint32_t t [ 1024 ] =  0, 0x1, 0x8, ... ;

uint32_t m ( int a, int b, int c )

    return t[a] | ( t[b] << 1 ) | ( t[c] << 2 );

bit hack 使用移位和掩码将位展开,因此每次它移位值并对其进行运算,将一些位复制到空白空间中,然后屏蔽组合以便只保留原始位。

例如:

x = 0xabcd;
  = 0000_0000_0000_0000_1010_1011_1100_1101    

x = (x | (x << S[3])) & B[3]; 

  = ( 0x00abcd00 | 0x0000abcd ) & 0xff00ff 
  = 0x00ab__cd & 0xff00ff 
  = 0x00ab00cd
  = 0000_0000_1010_1011_0000_0000_1100_1101
x = (x | (x << S[2])) & B[2]; 
  = ( 0x0ab00cd0 | 0x00ab00cd) & 0x0f0f0f0f 
  =   0x0a_b_c_d & 0x0f0f0f0f 
  = 0x0a0b0c0d 
  = 0000_1010_0000_1011_0000_1100_0000_1101
x = (x | (x << S[1])) & B[1]; 
  = ( 0000_1010_0000_1011_0000_1100_0000_1101 | 
      0010_1000_0010_1100_0011_0000_0011_0100 ) &
      0011_0011_0011_0011_0011_0011_0011_0011
  =   0010_0010_0010_0011_0011_0000_0011_0001
x = (x | (x << S[0])) & B[0]; 
  = ( 0010_0010_0010_0011_0011_0000_0011_0001 | 
      0100_0100_0100_0110_0110_0000_0110_0010 ) &
      0101_0101_0101_0101_0101_0101_0101_0101
  =   0100_0010_0100_0101_0101_0000_0101_0001

在每次迭代中,每个块都被分成两部分,块的最左半部分的最右边位移动到其最终位置,并应用了掩码以便只保留所需的位。

一旦你将输入分开,移动它们以使一个的值落入另一个的零是很容易的。

要将该技术扩展到最终结果中的值之间的两位以上,您必须增加位结束位置之间的移位。它变得有点棘手,因为起始块大小不是 2 的幂,因此您可以将其从中间拆分,也可以在 2 的幂边界上。

所以这样的演变可能会奏效:

0000_0000_0000_0000_0000_0011_1111_1111    
0000_0011_0000_0000_0000_0000_1111_1111    
0000_0011_0000_0000_1111_0000_0000_1111    
0000_0011_0000_1100_0011_0000_1100_0011    
0000_1001_0010_0100_1001_0010_0100_1001    

// 0000_0000_0000_0000_0000_0011_1111_1111    
x = ( x | ( x << 16 ) ) & 0x030000ff;
// 0000_0011_0000_0000_0000_0000_1111_1111    
x = ( x | ( x << 8 ) ) & 0x0300f00f;
// 0000_0011_0000_0000_1111_0000_0000_1111    
x = ( x | ( x << 4 ) ) & 0x030c30c3;
// 0000_0011_0000_1100_0011_0000_1100_0011    
x = ( x | ( x << 2 ) ) & 0x09249249;
// 0000_1001_0010_0100_1001_0010_0100_1001    

对输入执行相同的转换,一个一个移动,另一个移动两个,或者它们一起,你就完成了。

【讨论】:

【参考方案4】:

好时机,我上个月刚做的!

关键是要实现两个功能。一个将位分散到每三分之一位。 然后我们可以将其中三个组合在一起(最后两个移位)以获得最终的 Morton 交错值。

此代码从高位开始交错(这对于定点值更符合逻辑。)如果您的应用程序每个组件只有 10 位,只需将每个值左移 22 以使其从高位开始。

/* Takes a value and "spreads" the HIGH bits to lower slots to seperate them.
   ie, bit 31 stays at bit 31, bit 30 goes to bit 28, bit 29 goes to bit 25, etc.
   Anything below bit 21 just disappears. Useful for interleaving values
   for Morton codes. */
inline unsigned long spread3(unsigned long x)

  x=(0xF0000000&x) | ((0x0F000000&x)>>8) | (x>>16); // spread top 3 nibbles
  x=(0xC00C00C0&x) | ((0x30030030&x)>>4);
  x=(0x82082082&x) | ((0x41041041&x)>>2);
  return x;


inline unsigned long morton(unsigned long x, unsigned long y, unsigned long z)

  return spread3(x) | (spread3(y)>>1) | (spread3(z)>>2);

【讨论】:

【参考方案5】:

以下代码查找三个 10 位输入数字的莫顿数。它使用链接中的idee并在步骤5-5、3-2-3-2、2-1-1-1-2-1-1-1和1-1-1-中执行位扩展1-1-1-1-1-1-1,因为 10 不是 2 的幂。

......................9876543210
............98765..........43210
........987....56......432....10
......98..7..5..6....43..2..1..0
....9..8..7..5..6..4..3..2..1..0

您可以在上面看到每个位在四个步骤之前和之后的位置。

public static Int32 GetMortonNumber(Int32 x, Int32 y, Int32 z)

    return SpreadBits(x, 0) | SpreadBits(y, 1) | SpreadBits(z, 2);


public static Int32 SpreadBits(Int32 x, Int32 offset)

    if ((x < 0) || (x > 1023))
    
        throw new ArgumentOutOfRangeException();
    

    if ((offset < 0) || (offset > 2))
    
        throw new ArgumentOutOfRangeException();
    

    x = (x | (x << 10)) & 0x000F801F;
    x = (x | (x <<  4)) & 0x00E181C3;
    x = (x | (x <<  2)) & 0x03248649;
    x = (x | (x <<  2)) & 0x09249249;

    return x << offset;

【讨论】:

【参考方案6】:

我采用上述方法并将其修改为将 3 个 16 位数字组合成一个 48 位(实际上是 64 位)数字。也许它会节省一些人到达那里的想法。

#include <inttypes.h>
#include <assert.h>
uint64_t zorder3d(uint64_t x, uint64_t y, uint64_t z)
     static const uint64_t B[] = 0x00000000FF0000FF, 0x000000F00F00F00F,
                                    0x00000C30C30C30C3, 0X0000249249249249;           
     static const int S[] =  16, 8, 4, 2; 
     static const uint64_t MAXINPUT = 65536;

     assert( ( (x < MAXINPUT) ) && 
      ( (y < MAXINPUT) ) && 
      ( (z < MAXINPUT) )
     );

     x = (x | (x << S[0])) & B[0];
     x = (x | (x << S[1])) & B[1];
     x = (x | (x << S[2])) & B[2];
     x = (x | (x << S[3])) & B[3];

     y = (y | (y << S[0])) & B[0];
     y = (y | (y << S[1])) & B[1];
     y = (y | (y << S[2])) & B[2];
     y = (y | (y << S[3])) & B[3];

     z = (z | (z <<  S[0])) & B[0];
     z = (z | (z <<  S[1])) & B[1];
     z = (z | (z <<  S[2])) & B[2];
     z = (z | (z <<  S[3])) & B[3];

     return ( x | (y << 1) | (z << 2) );
    

【讨论】:

【参考方案7】:

以下是代码 sn-p 为 3-D 点生成大小为 64 位的 Morton 密钥。

using namespace std;

unsigned long long spreadBits(unsigned long long x)

    x=(x|(x<<20))&0x000001FFC00003FF;
    x=(x|(x<<10))&0x0007E007C00F801F;
    x=(x|(x<<4))&0x00786070C0E181C3;
    x=(x|(x<<2))&0x0199219243248649;
    x=(x|(x<<2))&0x0649249249249249;
    x=(x|(x<<2))&0x1249249249249249;
    return x;


int main()

    unsigned long long x,y,z,con=1;
    con=con<<63;
    printf("%#llx\n",(spreadBits(x)|(spreadBits(y)<<1)|(spreadBits(z)<<2))|con);    

【讨论】:

你能在 FORTRAN 中做到这一点吗?如何解码编码值?【参考方案8】:

我今天遇到了类似的问题,但我必须组合任意数量的任意位长度的数字,而不是 3 个数字。我采用了我自己的位扩展和掩码算法,并将其应用于 C# BigIntegers。这是我写的代码。作为编译步骤,它计算出给定维数和位深度的幻数和掩码。然后您可以重复使用该对象进行多次转换。

/// <summary>
/// Convert an array of integers into a Morton code by interleaving the bits.
/// Create one Morton object for a given pair of Dimension and BitDepth and reuse if when encoding multiple 
/// Morton numbers.
/// </summary>  
public class Morton

    /// <summary>
    /// Number of bits to use to represent each number being interleaved.
    /// </summary>
    public int BitDepth  get; private set; 

    /// <summary>
    /// Count of separate numbers to interleave into a Morton number.
    /// </summary>
    public int Dimensions  get; private set; 

    /// <summary>
    /// The MagicNumbers spread the bits out to the right position.
    /// Each must must be applied and masked, because the bits would overlap if we only used one magic number.
    /// </summary>
    public BigInteger LargeMagicNumber  get; private set; 
    public BigInteger SmallMagicNumber  get; private set; 

    /// <summary>
    /// The mask removes extraneous bits that were spread into positions needed by the other dimensions.
    /// </summary>
    public BigInteger Mask  get; private set; 

    public Morton(int dimensions, int bitDepth)
    
        BitDepth = bitDepth;
        Dimensions = dimensions;
        BigInteger magicNumberUnit = new BigInteger(1UL << (int)(Dimensions - 1));
        LargeMagicNumber = magicNumberUnit;
        BigInteger maskUnit = new BigInteger(1UL << (int)(Dimensions - 1));
        Mask = maskUnit;
        for (var i = 0; i < bitDepth - 1; i++)
        
            LargeMagicNumber = (LargeMagicNumber << (Dimensions - 1)) | (i % 2 == 1 ? magicNumberUnit : BigInteger.Zero);
            Mask = (Mask << Dimensions) | maskUnit;       
        
        SmallMagicNumber = (LargeMagicNumber >> BitDepth) << 1; // Need to trim off pesky ones place bit.
    

    /// <summary>
    /// Interleave the bits from several integers into a single BigInteger.
    /// The high-order bit from the first number becomes the high-order bit of the Morton number.
    /// The high-order bit of the second number becomes the second highest-ordered bit in the Morton number.
    /// 
    /// How it works.
    /// 
    /// When you multupliy by the magic numbers you make multiple copies of the the number they are multplying, 
    /// each shifted by a different amount.
    /// As it turns out, the high order bit of the highest order copy of a number is N bits to the left of the 
    /// second bit of the second copy, and so forth. 
    /// This is because each copy is shifted one bit less than N times the copy number.
    /// After that, you apply the AND-mask to unset all bits that are not in position.
    /// 
    /// Two magic numbers are needed because since each copy is shifted one less than the bitDepth, consecutive
    /// copies would overlap and ruin the algorithm. Thus one magic number (LargeMagicNumber) handles copies 1, 3, 5, etc, while the 
    /// second (SmallMagicNumber) handles copies 2, 4, 6, etc.
    /// </summary>
    /// <param name="vector">Integers to combine.</param>
    /// <returns>A Morton number composed of Dimensions * BitDepth bits.</returns>
    public BigInteger Interleave(int[] vector)
    
        if (vector == null || vector.Length != Dimensions)
            throw new ArgumentException("Interleave expects an array of length " + Dimensions, "vector");
        var morton = BigInteger.Zero;
        for (var i = 0; i < Dimensions; i++)
        
            morton |= (((LargeMagicNumber * vector[i]) & Mask) | ((SmallMagicNumber * vector[i]) & Mask)) >> i;
        
        return morton;
    


    public override string ToString()
    
        return "Morton(Dimension: " + Dimensions + ", BitDepth: " + BitDepth 
            + ", MagicNumbers: " + Convert.ToString((long)LargeMagicNumber, 2) + ", " + Convert.ToString((long)SmallMagicNumber, 2)
            + ", Mask: " + Convert.ToString((long)Mask, 2) + ")";
    

【讨论】:

我不明白为什么分成两个幻数对所有情况都足够了。如果您的输入坐标 > 2^2*(Dimensions+1),这不会因为重叠而失败吗?【参考方案9】:

这是我用 Ruby 制作的一个生成器,用于生成任意长度的编码方法:

def morton_code_for(bits)
  method = ''

  limit_mask = (1 << (bits * 3)) - 1
  split = (2 ** ((Math.log(bits) / Math.log(2)).to_i + 1)).to_i
  level = 1

  puts "// Coding for 3 #bits-bit values"

  loop do
    shift = split
    split /= 2
    level *= 2

    mask = ([ '1' * split ] * level).join('0' * split * 2).to_i(2) & limit_mask

    expression = "v = (v | (v << %2d)) & 0x%016x;" % [ shift, mask ]

    method << expression

    puts "%s // 0b%064b" % [ expression, mask ]

    break if (split <= 1)
  end

  puts
  print "// Test of method results: "
  v = (1 << bits) - 1
  puts eval(method).to_s(2)
end

morton_code_for(21)

输出具有适当的通用性,可以根据需要进行调整。示例输出:

// Coding for 3 21-bit values
v = (v | (v << 32)) & 0x7fff00000000ffff; // 0b0111111111111111000000000000000000000000000000001111111111111111
v = (v | (v << 16)) & 0x00ff0000ff0000ff; // 0b0000000011111111000000000000000011111111000000000000000011111111
v = (v | (v <<  8)) & 0x700f00f00f00f00f; // 0b0111000000001111000000001111000000001111000000001111000000001111
v = (v | (v <<  4)) & 0x30c30c30c30c30c3; // 0b0011000011000011000011000011000011000011000011000011000011000011
v = (v | (v <<  2)) & 0x1249249249249249; // 0b0001001001001001001001001001001001001001001001001001001001001001

// Test of method results: 1001001001001001001001001001001001001001001001001001001001001

【讨论】:

以上是关于如何计算 3D Morton 数(交错 3 个整数的位)的主要内容,如果未能解决你的问题,请参考以下文章

2D Morton 解码功能 64bits

2D Morton 解码功能 64bits

2d Morton 码 64bits 解码功能

为 32 位、64 位和 128 位生成交错位模式(morton 密钥)

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

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