diff --git a/diary.md b/diary.md index 13940c7..e4ed60f 100644 --- a/diary.md +++ b/diary.md @@ -1 +1,42 @@ the function convertPointToDistance requires 'height' to be a power of 2. Looking into ways to rewrite that, or determine if a rewrite is necessary. + +http://www.win.tue.nl/~hermanh/stack/dagstuhl08-talk.pdf + +pseudo-code: http://www.fundza.com/algorithmic/space_filling/hilbert/basics/index.html + +http://arxiv.org/abs/1109.2323 + +Axis rotation: http://www.siggraph.org/education/materials/HyperGraph/modeling/mod_tran/3drota.htm#X-Axis%20Rotation + +parity: http://en.wikipedia.org/wiki/Parity_%28mathematics%29 http://en.wikipedia.org/wiki/Parity_bit + +Want to express axis rotations internally without creating a point object. +or perhaps use one internally but return an array. + +move Point to own module for reuse in Voronoi diagram calculator and r-tree + +http://en.wikipedia.org/wiki/Rotation_matrix + +figure out how to keep track of rotations so we can unrotate. + +interesting hilbert implementation. https://github.com/ryan-williams/hilbert-js + +2D encoding algorithm: +Outline of the Method + +To encode from index to coordinates: +"Unpack" the index into a list of h D-bit ints (called "index chunks"). +h will be the number of levels of recursion (in our case the number of times to loop); from this calculate the orientation of the overall cube. +Then, for each index chunk, starting at the most- significant, +Use a modified Gray code to convert the index chunk into a D-bit "coordinate chunk" with one bit destined for each of the D coordinates; +Calculate the start and end corners for the child cube to which the indexed point belongs; +Loop to do the child cube. +Finally, +"Pack" the output coordinates by redistributing the D bits from each of h coordinate chunks into D ints, each with h bits. +Decoding is very similar, except that at each stage we Gray- decode D index bits from coordinate bits. At that point the decoder has the same information the encoder uses to calculate the orientation of the child cube for the next stage. + +N-dimensional mapping. http://www.dcs.bbk.ac.uk/~jkl/pubs/JL1_00a.pdf + +more spatial indexing info. http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves + +hilbert curve scheduling. http://en.wikipedia.org/wiki/Hilbert_curve_scheduling diff --git a/index.js b/index.js index 359d4a6..68aba6f 100644 --- a/index.js +++ b/index.js @@ -1,53 +1,172 @@ // Use Hilbert curve point generation to map 2D data to 1D space and vice-versa. // Will later expand to allow `n` dimensions. +function Point(x, y, z) { + this.rotations = { + x: 0, + y: 0, + z: 0 + } + + this.init = function (x, y, z) { + this.x = Math.round(x) || 0 + this.y = Math.round(y) || 0 + if (z != null) { + this.z = Math.round(z) || 0 + this.d = 3 + this.n = 4 * this.z + 2 * this.y + this.x + } else { + this.z = null + this.d = 2 + } + } + if (x instanceof Array) { + this.init(x[0], x[1], x[2]) + } else { + this.init(x, y, z) + } +} + +Point.prototype.rotate = function (p, n) { // :: Point -> Int -> Point + // record rotations + if (p.n == 0) return new Point(this.z, this.x, this.y) + if (p.n == 1 || p.n == 3) return new Point(this.y, this.z, this.x) + if (p.n == 2 || p.n == 6) return new Point(n - this.x, n - this.y, this.z) + return new Point(n - this.z, this.x, n - this.y) +} + +Point.prototype.rotate2d = function (n, xbit, ybit) { // : Int -> Int -> Int -> Point + return new Point(rotate2d(n, this.x, this.y, xbit, ybit)) +} + +Point.prototype.rotate3d = function (level) { // :: Int -> Point + return new Point(rotate3d(level, this.x, this.y, this.z)) +} -// Accepts the height or width of a square, and the coordinates to +Point.prototype.rotateLeft = function (n) { // :: Int -> Point + if (n % 3 == 0) return this + if (n % 3 == 1) return new Point(this.y, this.z, this.x) + return new Point(this.z, this.x, this.y) +} + +Point.prototype.rotateRight = function (n) { // :: Int -> Point + if (n % 3 == 0) return this + if (n % 3 == 1) return new Point(this.z, this.x, this.y) + return new Point(this.y, this.z, this.x) +} + +Point.prototype.toArray = function () { // :: -> [Int, Int] + if (this.d == 3) { return [this.x, this.y, this.z] } + return [this.x, this.y] +} + +Point.prototype.mod = function (n) { // :: Int -> Point + return new Point(this.x % n, this.y % n, this.z % n) +} + +Point.prototype.unrotate = function (n) { + // read this.rotations and undo +} + +// Accepts the height or width of a square/graph, and the coordinates to // convert. -function convertPointToDistance (height, x, y) { +function convert2dPointToDistance (p, height) { // :: Int -> Int -> Int -> Int var xbit, ybit, level, d = 0 + var forHeight = p.x > p.y ? p.x : p.y + // needs some tests to make sure height is compatible + // What keeps the user from putting 54 down as the height + while (forHeight >= height) { + height *=2 + } // For each Hilbert level, we want to add an amount to // `d` based on which region we are in - for (level = height / 2; level > 0; level /= 2) { + for (level = height / 2; level > 0; level = Math.floor(level / 2)) { // Determine what region we're in - xbit = (x & level) > 0 - ybit = (y & level) > 0 + xbit = (p.x & level) > 0 + ybit = (p.y & level) > 0 // increase distance based on region d += level * level * ((3 * xbit) ^ ybit) // rotate so that we'll be in sync with the next // region. - var temp = rotate (height, x, y, xbit, ybit) - x = temp[0] - y = temp[1] + p = p.rotate2d(level, xbit, ybit) + // HEAD + // p = p.rotate2d(height, xbit, ybit) } return d } -function convertDistanceToPoint (height, distance) { - var xbit, ybit, level, distance - var x = 0, y = 0 +// height and coordinates. +function convert3dPointToDistance (p, height) { // :: Int -> Int -> Int -> Int -> Int + var s = 1, level = 0 + var max = Math.max.apply(Math, p.toArray()) + for (; 2 * s <= max; s *= 2) { + level = (level + 1) % 3 + } + + // shuffle axes + // rotate based on parity +} + +// Accepts height or width of a square/graph and distance +function convertDistanceTo2dPoint (distance, height) { // :: Int -> Int -> [Int, Int] + distance = Math.floor(distance) + var xbit, ybit, level, p = new Point(0, 0) + + if (height <= Math.sqrt(distance)) { + height = 2 + while (height <= Math.sqrt(distance)) { + height *=2 + } + } for (level = 1; level < height; level *= 2) { xbit = 1 & (distance / 2) ybit = 1 & (distance ^ xbit) - var temp = rotate(height, x, y, xbit, ybit) - x = temp[0] - y = temp[1] - x += level * xbit - y += level * xbit - distance /= 4 + p = p.rotate2d(level, xbit, ybit) + p.x += level * xbit + p.y += level * ybit + distance = Math.floor(distance / 4) } - return [x, y] + return p.toArray() +} + +// height/width of a square/graph and distance +function convertDistanceTo3dPoint (distance, height) { // Int -> Int -> [Int, Int, Int] + distance = Math.floor(distance) + var xbit, ybit, zbit, level, parity + var iter = 2, log = 0, p = new Point(x, y, z) + + height = height || 2 + + for (parity = 1; parity < height; parity *= 2, log++) {} + parity = log % 3; + + for (level = 1; level < height || distance > 0; level *=2) { + xbit = distance & 1; + ybit = (ditance / 2) & 1; + zbit = (distance / 4) & 1; + + var temp = rotate3d(level - 1, xbit ^ ybit, ybit ^ zbit, zbit) + p.x = temp[0] * level + level - 1 + p.y = temp[1] * level + level - 1 + p.z = temp[2] * level + level - 1 + + distance = Math.floor(distance / 8) + level *= 2; + iter++; + } + + return p.rotateLeft(iter - parity + 1).toArray(); } // Rotate the coordinate plane and (x,y) -function rotate (n, x, y, xbit, ybit) { - if (ybit === 0 ) { - if (xbit === 1) { +function rotate2d (n, x, y, xbit, ybit) { // :: Int -> Int -> Int -> Int -> Int -> [Int, Int] + if (ybit == 0 ) { + if (xbit == 1) { x = n - 1 - x y = n - 1 - y } @@ -60,5 +179,29 @@ function rotate (n, x, y, xbit, ybit) { return [x, y] } -exports.convertPointToDistance = convertPointToDistance -exports.convertDistanceToPoint = convertDistanceToPoint +// Rotate the coordinate plane and (x,y, z) +function rotate3d(level, x, y, z) { // :: Int -> Int -> Int -> Int -> [Int, Int, Int] + index = 4 * z + 2 * y + x + if (index == 0) { + return [z, x, y] + } else if (index == 1 || index == 3) { + return [y, z, x] + } else if (index == 2 || index == 6) { + return [level - x, level - y, z] + } else if (index == 5 || index == 7) { + return [y, level - z, level - x] + } else { + return [level - z, x, level - y] + } +} + +exports.xy2d = function (x, y, height) { + height = height || 2 + return convert2dPointToDistance(new Point(x, y), height) +} +exports.xyz2d = function(x, y, z, height) { + height = height || 2 + return convert3dPointToDistance(new Point(x, y, z), height) +} +exports.d2xy = convertDistanceTo2dPoint +exports.d2xyz = convertDistanceTo3dPoint diff --git a/package.json b/package.json index 949bcf4..d3db414 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }], "devDependencies": { - "proof": "0.0.44" + "proof": "*" }, "licenses": [{ diff --git a/release.md b/release.md index e2bdcf9..1e06264 100644 --- a/release.md +++ b/release.md @@ -1,5 +1,6 @@ ### Issue by Issue + * Upgrade Proof to 0.0.47. #16. * Upgrade Proof to 0.0.44. #10. * Remove "url" from `package.json`. #9. * Update `LICENSE` for 2014. #8. diff --git a/t/xy/1dto2d.js b/t/xy/1dto2d.js deleted file mode 100644 index d9caf16..0000000 --- a/t/xy/1dto2d.js +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node - -require('proof')(1, function (equal( { - var hilbert = require('../..') - - equal(hilbert.convertDistanceToPoint(16, 2), [1, 1]) -} diff --git a/t/xy/1dto2d.t.js b/t/xy/1dto2d.t.js new file mode 100755 index 0000000..c095812 --- /dev/null +++ b/t/xy/1dto2d.t.js @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +require('proof')(9, function (equal) { + var hilbert = require('../..') + equal(hilbert.d2xy(0, 2), [0, 0]) + equal(hilbert.d2xy(0, 3), [0, 1]) + equal(hilbert.d2xy(4, 2), [2, 0]) + equal(hilbert.d2xy(15, 2), [3, 0]) + equal(hilbert.d2xy(16, 2), [0, 4]) + equal(hilbert.d2xy(63, 2), [7, 0]) + equal(hilbert.d2xy(64, 2), [0, 8]) + equal(hilbert.d2xy(1023, 2), [31, 0]) + equal(hilbert.d2xy(1024, 2), [0, 32]) + console.log("0 " , hilbert.d2xy(0, 2)) + console.log("1 " , hilbert.d2xy(1, 2)) + console.log("2 " , hilbert.d2xy(2, 2)) + console.log("3 " , hilbert.d2xy(3, 2)) + console.log("4 " , hilbert.d2xy(4, 2)) + console.log("5 " , hilbert.d2xy(5, 2)) + console.log("6 " , hilbert.d2xy(6, 2)) + console.log("7 " , hilbert.d2xy(7, 2)) + console.log("8 " , hilbert.d2xy(8, 2)) + console.log("9 " , hilbert.d2xy(9, 2)) + console.log("10 " , hilbert.d2xy(10, 2)) + console.log("11 " , hilbert.d2xy(11, 2)) + console.log("12 " ,hilbert.d2xy(12, 2)) + console.log("13 " ,hilbert.d2xy(13, 2)) + console.log("14 " ,hilbert.d2xy(14, 2)) + console.log("15 " ,hilbert.d2xy(15, 2)) + console.log("16 " ,hilbert.d2xy(16, 2)) + console.log("17 " ,hilbert.d2xy(17, 2)) + console.log("18 " ,hilbert.d2xy(18, 2)) + console.log("19 " ,hilbert.d2xy(19, 2)) + console.log("20 " ,hilbert.d2xy(20, 2)) + console.log("21 " ,hilbert.d2xy(21, 2)) + console.log("22 " ,hilbert.d2xy(22, 2)) + console.log("23 " ,hilbert.d2xy(23, 2)) + console.log("24 " ,hilbert.d2xy(24, 2)) + console.log("25 " ,hilbert.d2xy(25, 2)) + console.log("26 " ,hilbert.d2xy(26, 2)) + console.log("27 " ,hilbert.d2xy(27, 2)) + console.log("28 " ,hilbert.d2xy(28, 2)) + console.log("29 " ,hilbert.d2xy(29, 2)) +}) diff --git a/t/xy/2dto1d.js b/t/xy/1dto3d.t.js old mode 100644 new mode 100755 similarity index 62% rename from t/xy/2dto1d.js rename to t/xy/1dto3d.t.js index ee53a73..fa4e2d1 --- a/t/xy/2dto1d.js +++ b/t/xy/1dto3d.t.js @@ -3,5 +3,5 @@ require('proof')(1, function (equal) { var hilbert = require('../..') - equal(hilbert.convertPointToDistance(16, 2, 2,), 8) -} + equal(hilbert.convertPointToDistance(16, 2, 2), 8) +}) diff --git a/t/xy/2dto1d.t.js b/t/xy/2dto1d.t.js new file mode 100644 index 0000000..c13cd6a --- /dev/null +++ b/t/xy/2dto1d.t.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +require('proof')(3, function (equal) { + + var hilbert = require('../..') + equal(hilbert.xy2d(1, 0, 2), 3) + equal(hilbert.xy2d(2, 0, 4), 4) + equal(hilbert.xy2d(9, 9, 4), 10) + //equal(5, 8) + //hilbert.xy2d(0, 4, 2) + console.log("x: 0, y: 1, length: ", hilbert.xy2d(0, 1, 2)) // 1 + console.log("x: 1, y: 1, length: ", hilbert.xy2d(1, 1, 2)) // 2 + console.log("x: 1, y: 0, length: ", hilbert.xy2d(1, 0, 2)) // 3 + console.log("x: 0, y: 2, length: ", hilbert.xy2d(0, 2, 2)) // 4 + console.log("x: 0, y: 3, length: ", hilbert.xy2d(0, 3, 2)) // 5 + console.log("x: 1, y: 3, length: ", hilbert.xy2d(1, 3, 2)) // 6 + console.log("x: 1, y: 2, length: ", hilbert.xy2d(1, 2, 2)) // 7 + console.log("x: 2, y: 2, length: ", hilbert.xy2d(2, 2, 2)) // 8 + console.log("x: 2, y: 3, length: ", hilbert.xy2d(2, 3, 2)) // 9 + console.log("x: 3, y: 3, length: ", hilbert.xy2d(3, 3, 2)) // 10 + console.log("x: 3, y: 2, length: ", hilbert.xy2d(3, 2, 2)) // 11 + console.log("x: 3, y: 1, length: ", hilbert.xy2d(3, 1, 2)) // 12 + console.log("x: 2, y: 1, length: ", hilbert.xy2d(2, 1, 2)) // 13 + console.log("x: 2, y: 0, length: ", hilbert.xy2d(2, 0, 2)) // 14 + console.log("x: 3, y: 0, length: ", hilbert.xy2d(3, 0, 2)) // 15 +})