diff --git a/common/coords.go b/common/coords.go index dc57e6e..6bbc0af 100644 --- a/common/coords.go +++ b/common/coords.go @@ -5,9 +5,18 @@ package common import ( + "encoding/binary" "strconv" ) +type ( + KeyTransformer func(int64) int64 + KeyEncoder func(int64) []byte + KeyDecoder func([]byte) int64 + KeySplitter func(int64) (int16, int16, int16) + KeyJoiner func(int16, int16, int16) int64 +) + // Constructs a database key out of byte slice. func DecodePosFromBytes(key []byte) (pos int64, err error) { return strconv.ParseInt(string(key), 10, 64) @@ -17,3 +26,106 @@ func DecodePosFromBytes(key []byte) (pos int64, err error) { func EncodePosToBytes(key int64) []byte { return []byte(strconv.FormatInt(key, 10)) } + +func EncodeAsBigEndian(key int64) (enc []byte) { + enc = make([]byte, 8) + binary.BigEndian.PutUint64(enc, uint64(key)) + return +} + +func DecodeFromBigEndian(key []byte) int64 { + return int64(binary.BigEndian.Uint64(key)) +} + +const ( + numBitsPerComponent = 12 + maxPositive = 2048 + modulo = 4096 +) + +func XYZToInterleaved(x, y, z int16) (result int64) { + const end = 1 << (numBitsPerComponent + 1) + setmask := int64(1) + for mask := int16(1); mask != end; mask <<= 1 { + if x&mask != 0 { + result |= setmask + } + setmask <<= 1 + if y&mask != 0 { + result |= setmask + } + setmask <<= 1 + if z&mask != 0 { + result |= setmask + } + setmask <<= 1 + } + return +} + +func InterleavedToXYZ(c int64) (x, y, z int16) { + const end = 1 << (numBitsPerComponent + 1) + for mask := int16(1); mask != end; mask <<= 1 { + if c&1 == 1 { + x |= mask + } + c >>= 1 + if c&1 == 1 { + y |= mask + } + c >>= 1 + if c&1 == 1 { + z |= mask + } + c >>= 1 + } + if x >= 1<= 1<= 1<= 0 { + return i % modulo + } + return modulo - (-i)%modulo +} + +// Only to match the C++ code. +func PlainToXYZ(i int64) (x, y, z int16) { + x = unsignedToSigned(pythonModulo(int16(i))) + i = (i - int64(x)) / modulo + y = unsignedToSigned(pythonModulo(int16(i))) + i = (i - int64(y)) / modulo + z = unsignedToSigned(pythonModulo(int16(i))) + return +} + +func TransformPlainToInterleaved(pos int64) int64 { + x, y, z := PlainToXYZ(pos) + return XYZToInterleaved(x, y, z) +} + +func TransformInterleavedPlain(pos int64) int64 { + x, y, z := InterleavedToXYZ(pos) + return XYZToPlain(x, y, z) +} diff --git a/common/coords_test.go b/common/coords_test.go new file mode 100644 index 0000000..169206f --- /dev/null +++ b/common/coords_test.go @@ -0,0 +1,95 @@ +package common + +import "testing" + +//var data = []int16{-2} +var data = []int16{ + -2045, -1850, -1811, -1629, -1104, + -967, -725, -646, -329, -212, + -150, 88, 524, 527, 549, + 1783, 1817, 1826, 2028, 2032} + +func allData(f func(int16, int16, int16)) { + for _, z := range data { + for _, y := range data { + for _, x := range data { + f(x, y, z) + } + } + } +} + +func checkJoinSplit( + desc string, + join KeyJoiner, split KeySplitter, + x1, y1, z1 int16, t *testing.T) { + + k1 := join(x1, y1, z1) + x2, y2, z2 := split(k1) + if x1 != x2 || y1 != y2 || z1 != z2 { + t.Errorf("%s: Expected (%d, %d, %d) got (%d, %d, %d) %b\n", + desc, x1, y1, z1, x2, y2, z2, k1) + } +} + +func TestJoinSplit(t *testing.T) { + allData(func(x, y, z int16) { + checkJoinSplit( + "P2XYZ(XYZ2P(xyz))", + XYZToPlain, PlainToXYZ, + x, y, z, t) + }) + allData(func(x, y, z int16) { + checkJoinSplit( + "I2XYZ(XYZ2I(xyz))", + XYZToInterleaved, InterleavedToXYZ, + x, y, z, t) + }) +} + +func checkTransformer( + desc string, joiner KeyJoiner, + transform KeyTransformer, + x, y, z int16, t *testing.T) { + + k1 := joiner(x, y, z) + k2 := transform(k1) + if k2 != k1 { + t.Errorf("%s: Expected %v got %v for (%d, %d, %d)\n", + desc, k1, k2, x, y, z) + } +} + +func compose(transforms ...KeyTransformer) KeyTransformer { + return func(x int64) int64 { + for _, transform := range transforms { + x = transform(x) + } + return x + } +} + +func TestTransforms(t *testing.T) { + // Mainly to check the test itself. + allData(func(x, y, z int16) { + checkTransformer( + "plain", + XYZToPlain, + compose(), + x, y, z, t) + }) + allData(func(x, y, z int16) { + checkTransformer( + "I2P(P2I(plain))", + XYZToPlain, + compose(TransformPlainToInterleaved, TransformInterleavedPlain), + x, y, z, t) + }) + allData(func(x, y, z int16) { + checkTransformer( + "P2I(I2P(interleaved))", + XYZToInterleaved, + compose(TransformInterleavedPlain, TransformPlainToInterleaved), + x, y, z, t) + }) +}