├── LICENSE ├── README.md ├── go.mod └── spacecurves ├── doc.go ├── hilbert.go ├── hilbert_test.go ├── morton.go └── morton_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2014-2019, Samuel Stauffer 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Space-filling Curves for Go 2 | =========================== 3 | 4 | [![Documentation](https://godoc.org/github.com/samuel/go-spacecurves/spacecurves?status.svg)](https://godoc.org/github.com/samuel/go-spacecurves/spacecurves) 5 | 6 | Package spacecurves implements [space-filling curves](https://en.wikipedia.org/wiki/Space-filling_curve) 7 | to encode/decode multi-dimensional points into single values along a curve. Different 8 | curves maintain locality of points to various degrees. 9 | 10 | Implemented curves: 11 | 12 | - [Hilbert](https://en.wikipedia.org/wiki/Hilbert_curve) 13 | - [Morton (Z-Order)](https://en.wikipedia.org/wiki/Z-order_curve) 14 | 15 | License 16 | ------- 17 | 18 | 3-clause BSD. See LICENSE file. 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/samuel/go-spacecurves 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /spacecurves/doc.go: -------------------------------------------------------------------------------- 1 | // Package spacecurves implements space-filling curves to encode/decode 2 | // multi-dimensional points into single values along a curve. Different 3 | // curves maintain locality of points to various degrees. 4 | package spacecurves 5 | -------------------------------------------------------------------------------- /spacecurves/hilbert.go: -------------------------------------------------------------------------------- 1 | package spacecurves 2 | 3 | // Hilbert2DEncode encodes a 2D x,y pair into a single value along 4 | // a Hilbert space-filling curve of the specified number of bits. 5 | func Hilbert2DEncode(bits, x, y uint) uint { 6 | d := uint(0) 7 | for s := uint(1< 0; s /= 2 { 8 | var rx, ry uint 9 | if x&s > 0 { 10 | rx = 1 11 | } 12 | if y&s > 0 { 13 | ry = 1 14 | } 15 | d += s * s * ((3 * rx) ^ ry) 16 | x, y = hilbertRot(s, x, y, rx, ry) 17 | } 18 | return d 19 | } 20 | 21 | // Hilbert2DDecode decodes a value along a Hilbert space-filling curve 22 | // of the specified number of bits into a 2D x,y pair. 23 | func Hilbert2DDecode(bits, d uint) (uint, uint) { 24 | var x, y uint 25 | n := uint(1 << bits) 26 | for s := uint(1); s < n; s *= 2 { 27 | rx := 1 & (d / 2) 28 | ry := 1 & (d ^ rx) 29 | x, y = hilbertRot(s, x, y, rx, ry) 30 | x += s * rx 31 | y += s * ry 32 | d /= 4 33 | } 34 | return x, y 35 | } 36 | 37 | func hilbertRot(n, x, y, rx, ry uint) (uint, uint) { 38 | if ry == 0 { 39 | if rx == 1 { 40 | x = n - 1 - x 41 | y = n - 1 - y 42 | } 43 | x, y = y, x 44 | } 45 | return x, y 46 | } 47 | -------------------------------------------------------------------------------- /spacecurves/hilbert_test.go: -------------------------------------------------------------------------------- 1 | package spacecurves 2 | 3 | import "testing" 4 | 5 | func TestHilbert2D(t *testing.T) { 6 | const n = 8 7 | for y := uint(0); y < 1< x { 34 | out[o+1] = '-' 35 | } else if lastY > y { 36 | out[o+n*2] = '|' 37 | } 38 | lastX, lastY = x, y 39 | } 40 | for y := 0; y < n*2; y++ { 41 | t.Logf("%s\n", string(out[y*n*2:y*n*2+n*2])) 42 | } 43 | } 44 | 45 | func BenchmarkHilbert2DEncode_5(b *testing.B) { 46 | const n = 5 47 | for i := 0; i < b.N; i++ { 48 | for y := uint(0); y < 1<> i) & s 37 | y |= (d >> (i + 1)) & s 38 | s <<= 1 39 | } 40 | return x, y 41 | } 42 | 43 | // Morton3DDecode decodes a value along a Z-order curve 44 | // of the specified number of bits into a 3D x,y,z point. 45 | func Morton3DDecode(n, d uint) (uint, uint, uint) { 46 | var x, y, z uint 47 | s := uint(1) 48 | for i := uint(0); i < n; i++ { 49 | x |= (d >> (i * 2)) & s 50 | y |= (d >> (i*2 + 1)) & s 51 | z |= (d >> (i*2 + 2)) & s 52 | s <<= 1 53 | } 54 | return x, y, z 55 | } 56 | 57 | // MortonToHilbert2D transforms a 2D point along a Morton Z-order 58 | // curve to a point along a Hilbert space-filling curve. 59 | func MortonToHilbert2D(morton, bits uint) uint { 60 | hilbert := uint(0) 61 | remap := uint(0xb4) 62 | block := bits << 1 63 | for block != 0 { 64 | block -= 2 65 | mcode := (morton >> block) & 3 66 | hcode := (remap >> (mcode << 1)) & 3 67 | remap ^= 0x82000028 >> (hcode << 3) 68 | hilbert = (hilbert << 2) + hcode 69 | } 70 | return hilbert 71 | } 72 | 73 | // HilbertToMorton2D transforms a 2D point along a Hilbert space-filling 74 | // curve to a point along a Morton Z-order curve. 75 | func HilbertToMorton2D(hilbert, bits uint) uint { 76 | morton := uint(0) 77 | remap := uint(0xb4) 78 | block := bits << 1 79 | for block != 0 { 80 | block -= 2 81 | hcode := (hilbert >> block) & 3 82 | mcode := (remap >> (hcode << 1)) & 3 83 | remap ^= 0x330000cc >> (hcode << 3) 84 | morton = (morton << 2) + mcode 85 | } 86 | return morton 87 | } 88 | 89 | // MortonToHilbert3D transforms a 3D point along a Morton Z-order 90 | // curve to a point along a Hilbert space-filling curve. 91 | func MortonToHilbert3D(morton, bits uint) uint { 92 | hilbert := morton 93 | if bits > 1 { 94 | block := (bits * 3) - 3 95 | hcode := (hilbert >> block) & 7 96 | shift := uint(0) 97 | signs := uint(0) 98 | for block != 0 { 99 | block -= 3 100 | hcode <<= 2 101 | mcode := (uint(0x20212021) >> hcode) & 3 102 | shift = (0x48 >> (7 - shift - mcode)) & 3 103 | signs = (signs | (signs << 3)) >> mcode 104 | signs = (signs ^ (0x53560300 >> hcode)) & 7 105 | mcode = (hilbert >> block) & 7 106 | hcode = mcode 107 | hcode = ((hcode | (hcode << 3)) >> shift) & 7 108 | hcode ^= signs 109 | hilbert ^= (mcode ^ hcode) << block 110 | } 111 | } 112 | hilbert ^= (hilbert >> 1) & 0x92492492 113 | hilbert ^= (hilbert & 0x92492492) >> 1 114 | return hilbert 115 | } 116 | 117 | // HilbertToMorton3D transforms a 3D point along a Hilbert space-filling 118 | // curve to a point along a Morton Z-order curve. 119 | func HilbertToMorton3D(hilbert, bits uint) uint { 120 | morton := hilbert 121 | morton ^= (morton & 0x92492492) >> 1 122 | morton ^= (morton >> 1) & 0x92492492 123 | if bits > 1 { 124 | block := ((bits * 3) - 3) 125 | hcode := ((morton >> block) & 7) 126 | shift := uint(0) 127 | signs := uint(0) 128 | for block != 0 { 129 | block -= 3 130 | hcode <<= 2 131 | mcode := (uint(0x20212021) >> hcode) & 3 132 | shift = (0x48 >> (4 - shift + mcode)) & 3 133 | signs = (signs | (signs << 3)) >> mcode 134 | signs = (signs ^ (0x53560300 >> hcode)) & 7 135 | hcode = (morton >> block) & 7 136 | mcode = hcode 137 | mcode ^= signs 138 | mcode = ((mcode | (mcode << 3)) >> shift) & 7 139 | morton ^= (hcode ^ mcode) << block 140 | } 141 | } 142 | return morton 143 | } 144 | 145 | // Morton2DEncode5bit transforms a 2D point into a value along 146 | // a 5-bit Morton space-filling curve. It is more optimal than 147 | // the generic Morton2DEncode. 148 | func Morton2DEncode5bit(x, y uint) uint { 149 | x &= 0x0000001f 150 | y &= 0x0000001f 151 | x *= 0x01041041 152 | y *= 0x01041041 153 | x &= 0x10204081 154 | y &= 0x10204081 155 | x *= 0x00108421 156 | y *= 0x00108421 157 | x &= 0x15500000 158 | y &= 0x15500000 159 | return (x >> 20) | (y >> 19) 160 | } 161 | 162 | // Morton2DDecode5bit transforms a point along a 5-bit Morton 163 | // space-filling curve into a 2D point. It is more efficient than 164 | // the generic Morton2DDecode. 165 | func Morton2DDecode5bit(morton uint) (uint, uint) { 166 | value1 := morton 167 | value2 := value1 >> 1 168 | value1 &= 0x00000155 169 | value2 &= 0x00000155 170 | value1 |= value1 >> 1 171 | value2 |= value2 >> 1 172 | value1 &= 0x00000133 173 | value2 &= 0x00000133 174 | value1 |= value1 >> 2 175 | value2 |= value2 >> 2 176 | value1 &= 0x0000010f 177 | value2 &= 0x0000010f 178 | value1 |= value1 >> 4 179 | value2 |= value2 >> 4 180 | value1 &= 0x0000001f 181 | value2 &= 0x0000001f 182 | return value1, value2 183 | } 184 | 185 | // Morton2DEncode16bit transforms a 2D point into a value along 186 | // a 16-bit Morton space-filling curve. It is more efficient than 187 | // the generic Morton2DEncode. 188 | func Morton2DEncode16bit(x, y uint) uint { 189 | x &= 0x0000ffff 190 | y &= 0x0000ffff 191 | x |= x << 8 192 | y |= y << 8 193 | x &= 0x00ff00ff 194 | y &= 0x00ff00ff 195 | x |= x << 4 196 | y |= y << 4 197 | x &= 0x0f0f0f0f 198 | y &= 0x0f0f0f0f 199 | x |= x << 2 200 | y |= y << 2 201 | x &= 0x33333333 202 | y &= 0x33333333 203 | x |= x << 1 204 | y |= y << 1 205 | x &= 0x55555555 206 | y &= 0x55555555 207 | return x | (y << 1) 208 | } 209 | 210 | // Morton2DDecode16bit transforms a point along a 16-bit Morton 211 | // space-filling curve into a 2D point. It is more efficient than 212 | // the generic Morton2DDecode. 213 | func Morton2DDecode16bit(morton uint) (uint, uint) { 214 | value1 := morton 215 | value2 := value1 >> 1 216 | value1 &= 0x55555555 217 | value2 &= 0x55555555 218 | value1 |= value1 >> 1 219 | value2 |= value2 >> 1 220 | value1 &= 0x33333333 221 | value2 &= 0x33333333 222 | value1 |= value1 >> 2 223 | value2 |= value2 >> 2 224 | value1 &= 0x0f0f0f0f 225 | value2 &= 0x0f0f0f0f 226 | value1 |= value1 >> 4 227 | value2 |= value2 >> 4 228 | value1 &= 0x00ff00ff 229 | value2 &= 0x00ff00ff 230 | value1 |= value1 >> 8 231 | value2 |= value2 >> 8 232 | value1 &= 0x0000ffff 233 | value2 &= 0x0000ffff 234 | return value1, value2 235 | } 236 | 237 | // Morton3DEncode5bit transforms a 3D point into a value along 238 | // a 5-bit Morton space-filling curve. It is more optimal than 239 | // the generic Morton2DEncode. 240 | func Morton3DEncode5bit(x, y, z uint) uint { 241 | x &= 0x0000001f 242 | y &= 0x0000001f 243 | z &= 0x0000001f 244 | x *= 0x01041041 245 | y *= 0x01041041 246 | z *= 0x01041041 247 | x &= 0x10204081 248 | y &= 0x10204081 249 | z &= 0x10204081 250 | x *= 0x00011111 251 | y *= 0x00011111 252 | z *= 0x00011111 253 | x &= 0x12490000 254 | y &= 0x12490000 255 | z &= 0x12490000 256 | return (x >> 16) | (y >> 15) | (z >> 14) 257 | } 258 | 259 | // Morton3DDecode5bit transforms a point along a 5-bit Morton 260 | // space-filling curve into a 3D point. It is more efficient than 261 | // the generic Morton2DDecode. 262 | func Morton3DDecode5bit(morton uint) (uint, uint, uint) { 263 | value1 := morton 264 | value2 := value1 >> 1 265 | value3 := value1 >> 2 266 | value1 &= 0x00001249 267 | value2 &= 0x00001249 268 | value3 &= 0x00001249 269 | value1 |= value1 >> 2 270 | value2 |= value2 >> 2 271 | value3 |= value3 >> 2 272 | value1 &= 0x000010c3 273 | value2 &= 0x000010c3 274 | value3 &= 0x000010c3 275 | value1 |= value1 >> 4 276 | value2 |= value2 >> 4 277 | value3 |= value3 >> 4 278 | value1 &= 0x0000100f 279 | value2 &= 0x0000100f 280 | value3 &= 0x0000100f 281 | value1 |= value1 >> 8 282 | value2 |= value2 >> 8 283 | value3 |= value3 >> 8 284 | value1 &= 0x0000001f 285 | value2 &= 0x0000001f 286 | value3 &= 0x0000001f 287 | return value1, value2, value3 288 | } 289 | 290 | // Morton3DEncode10bit transforms a 3D point into a value along 291 | // a 10-bit Morton space-filling curve. It is more optimal than 292 | // the generic Morton2DEncode. 293 | func Morton3DEncode10bit(x, y, z uint) uint { 294 | x &= 0x000003ff 295 | y &= 0x000003ff 296 | z &= 0x000003ff 297 | x |= x << 16 298 | y |= y << 16 299 | z |= z << 16 300 | x &= 0x030000ff 301 | y &= 0x030000ff 302 | z &= 0x030000ff 303 | x |= x << 8 304 | y |= y << 8 305 | z |= z << 8 306 | x &= 0x0300f00f 307 | y &= 0x0300f00f 308 | z &= 0x0300f00f 309 | x |= x << 4 310 | y |= y << 4 311 | z |= z << 4 312 | x &= 0x030c30c3 313 | y &= 0x030c30c3 314 | z &= 0x030c30c3 315 | x |= x << 2 316 | y |= y << 2 317 | z |= z << 2 318 | x &= 0x09249249 319 | y &= 0x09249249 320 | z &= 0x09249249 321 | return x | (y << 1) | (z << 2) 322 | } 323 | 324 | // Morton3DDecode10bit transforms a point along a 10-bit Morton 325 | // space-filling curve into a 3D point. It is more efficient than 326 | // the generic Morton2DDecode. 327 | func Morton3DDecode10bit(morton uint) (uint, uint, uint) { 328 | value1 := morton 329 | value2 := value1 >> 1 330 | value3 := value1 >> 2 331 | value1 &= 0x09249249 332 | value2 &= 0x09249249 333 | value3 &= 0x09249249 334 | value1 |= value1 >> 2 335 | value2 |= value2 >> 2 336 | value3 |= value3 >> 2 337 | value1 &= 0x030c30c3 338 | value2 &= 0x030c30c3 339 | value3 &= 0x030c30c3 340 | value1 |= value1 >> 4 341 | value2 |= value2 >> 4 342 | value3 |= value3 >> 4 343 | value1 &= 0x0300f00f 344 | value2 &= 0x0300f00f 345 | value3 &= 0x0300f00f 346 | value1 |= value1 >> 8 347 | value2 |= value2 >> 8 348 | value3 |= value3 >> 8 349 | value1 &= 0x030000ff 350 | value2 &= 0x030000ff 351 | value3 &= 0x030000ff 352 | value1 |= value1 >> 16 353 | value2 |= value2 >> 16 354 | value3 |= value3 >> 16 355 | value1 &= 0x000003ff 356 | value2 &= 0x000003ff 357 | value3 &= 0x000003ff 358 | return value1, value2, value3 359 | } 360 | -------------------------------------------------------------------------------- /spacecurves/morton_test.go: -------------------------------------------------------------------------------- 1 | package spacecurves 2 | 3 | import "testing" 4 | 5 | func TestMorton2D(t *testing.T) { 6 | const n = 1 << 5 7 | for y := uint(0); y < n; y++ { 8 | for x := uint(0); x < n; x++ { 9 | morton := Morton2DEncode(n, x, y) 10 | if x2, y2 := Morton2DDecode(n, morton); x != x2 || y != y2 { 11 | t.Fatalf("Got (%d, %d) expected (%d, %d)", x2, y2, x, y) 12 | } 13 | } 14 | } 15 | } 16 | 17 | func TestMorton3D(t *testing.T) { 18 | const n = 1 << 5 19 | for y := uint(0); y < n; y++ { 20 | for x := uint(0); x < n; x++ { 21 | for z := uint(0); z < n; z++ { 22 | morton := Morton3DEncode(5, x, y, z) 23 | if x2, y2, z2 := Morton3DDecode(n, morton); x != x2 || y != y2 || z != z2 { 24 | t.Fatalf("Got (%d, %d, %d) expected (%d, %d, %d)", x2, y2, z2, x, y, z) 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | func TestMorton2D5bit(t *testing.T) { 32 | const n = 1 << 5 33 | for y := uint(0); y < n; y++ { 34 | for x := uint(0); x < n; x++ { 35 | morton := Morton2DEncode5bit(x, y) 36 | if x2, y2 := Morton2DDecode5bit(morton); x != x2 || y != y2 { 37 | t.Fatalf("Got (%d, %d) expected (%d, %d)", x2, y2, x, y) 38 | } 39 | } 40 | } 41 | } 42 | 43 | func TestMorton3D5bit(t *testing.T) { 44 | const n = 1 << 5 45 | for y := uint(0); y < n; y++ { 46 | for x := uint(0); x < n; x++ { 47 | for z := uint(0); z < n; z++ { 48 | morton := Morton3DEncode5bit(x, y, z) 49 | if x2, y2, z2 := Morton3DDecode5bit(morton); x != x2 || y != y2 || z != z2 { 50 | t.Fatalf("Got (%d, %d, %d) expected (%d, %d, %d)", x2, y2, z2, x, y, z) 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | func TestMortonToHilbert2D(t *testing.T) { 58 | const n = 1 << 5 59 | for y := uint(0); y < n; y++ { 60 | for x := uint(0); x < n; x++ { 61 | morton := Morton2DEncode5bit(x, y) 62 | hilbert := MortonToHilbert2D(morton, 10) 63 | if x2, y2 := Morton2DDecode5bit(HilbertToMorton2D(hilbert, 10)); x != x2 || y != y2 { 64 | t.Fatalf("Got (%d, %d) expected (%d, %d)", x2, y2, x, y) 65 | } 66 | } 67 | } 68 | } 69 | 70 | func TestMortonToHilbert3D(t *testing.T) { 71 | const n = 1 << 5 72 | for y := uint(0); y < n; y++ { 73 | for x := uint(0); x < n; x++ { 74 | for z := uint(0); z < n; z++ { 75 | morton := Morton3DEncode5bit(x, y, z) 76 | hilbert := MortonToHilbert2D(morton, 15) 77 | if x2, y2, z2 := Morton3DDecode5bit(HilbertToMorton2D(hilbert, 15)); x != x2 || y != y2 || z != z2 { 78 | t.Fatalf("Got (%d, %d, %d) expected (%d, %d, %d)", x2, y2, z2, x, y, z) 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | func TestDisplayMorton2D(t *testing.T) { 86 | const bits = 5 87 | const n = 1 << bits 88 | var out [(2 * n) * (2 * n)]byte 89 | for i := 0; i < len(out); i++ { 90 | out[i] = ' ' 91 | } 92 | out[0] = '+' 93 | lastX, lastY := Morton2DDecode(bits, 0) 94 | for d := uint(1); d < n*n; d++ { 95 | x, y := Morton2DDecode(bits, d) 96 | o := y*n*4 + x*2 97 | out[o] = '+' 98 | switch { 99 | case lastX < x && lastY < y: 100 | out[o-1-n*2] = '\\' 101 | case lastX < x && lastY > y: 102 | out[o-1+n*2] = '/' 103 | case lastX > x && lastY < y: 104 | out[o+1-n*2] = '/' 105 | case lastX > x && lastY > x: 106 | out[o+1+n*2] = '\\' 107 | case lastX < x: 108 | out[o-1] = '-' 109 | case lastX > x: 110 | out[o+1] = '-' 111 | case lastY < y: 112 | out[o-n*2] = '|' 113 | case lastY > y: 114 | out[o+n*2] = '|' 115 | } 116 | lastX, lastY = x, y 117 | } 118 | for y := 0; y < n*2; y++ { 119 | t.Logf("%s\n", string(out[y*n*2:y*n*2+n*2])) 120 | } 121 | } 122 | 123 | func BenchmarkMorton2DEncode_5(b *testing.B) { 124 | const n = 5 125 | for i := 0; i < b.N; i++ { 126 | for y := uint(0); y < 1<