How to compute a 3D Morton number (interleave the bits of 3 ints)

后端 未结 9 2132
旧巷少年郎
旧巷少年郎 2020-11-29 18:26

I\'m looking for a fast way to compute a 3D Morton number. This site has a magic-number based trick for doing it for 2D Morton numbers, but it doesn\'t seem obvious how to e

9条回答
  •  轻奢々
    轻奢々 (楼主)
    2020-11-29 18:56

    I had a similar problem today, but instead of 3 numbers, I have to combine an arbitrary number of numbers of any bit length. I employed my own sort of bit spreading and masking algorithm and applied it to C# BigIntegers. Here is the code I wrote. As a compilation step, it figures out the magic numbers and mask for the given number of dimensions and bit depth. Then you can reuse the object for multiple conversions.

    /// 
    /// 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.
    ///   
    public class Morton
    {
        /// 
        /// Number of bits to use to represent each number being interleaved.
        /// 
        public int BitDepth { get; private set; }
    
        /// 
        /// Count of separate numbers to interleave into a Morton number.
        /// 
        public int Dimensions { get; private set; }
    
        /// 
        /// 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.
        /// 
        public BigInteger LargeMagicNumber { get; private set; }
        public BigInteger SmallMagicNumber { get; private set; }
    
        /// 
        /// The mask removes extraneous bits that were spread into positions needed by the other dimensions.
        /// 
        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.
        }
    
        /// 
        /// 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.
        /// 
        /// Integers to combine.
        /// A Morton number composed of Dimensions * BitDepth bits.
        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) + ")";
        }
    }
    

提交回复
热议问题