# darkskyapp/delaunay

Initial, incomplete commit.

0 parents commit c142f6aa51157694b86602978f1b9261395f91c9 ironwallaby committed Feb 11, 2013
Showing with 274 additions and 0 deletions.
1. +1 −0 .gitignore
2. +57 −0 examples/2d.html
3. +125 −0 lib/delaunay.js
4. +91 −0 spec/delaunay.spec.js
1 .gitignore
 @@ -0,0 +1 @@ +/node_modules
57 examples/2d.html
 @@ -0,0 +1,57 @@ + + + + 2D Delaunay Triangulation Example + + + + + + + + + + +
125 lib/delaunay.js
 @@ -0,0 +1,125 @@ +/* This is a JavaScript implementation of Delaunay triangulation. Ideally, it + * would function in any number of dimensions; the only restriction is in + * calculating the circumsphere of a simplex, and I can't seem to find the + * algorithm to do it. As such, this code currently just works in 2 or 3 + * dimensions. + * + * The theory behind Delaunay triangulation can be found here: + * + * http://paulbourke.net/papers/triangulate/ + * + * This code has not implemented the relevant optimizations to make it run in + * subquadratic time; however, such implementations should be straightforward + * to implement as necessary. */ + +var Delaunay; + +(function() { + "use strict"; + + Delaunay = { + /* Return the bounding box (two vertices) enclosing each given vertex. */ + boundingBox: function(n, matrix, margin) { + var box = new Array(n * 2), + i, j; + + /* If we weren't given at least one vertex, then just return a degenerate + * boxing box. */ + if(matrix.length < n) + for(j = 0; j !== n; ++j) { + box[0 + j] = 0; + box[n + j] = 0; + } + + /* Otherwise, find the boxing box of the given vertices and return it. */ + else { + for(j = 0; j !== n; ++j) { + box[0 + j] = Number.POSITIVE_INFINITY; + box[n + j] = Number.NEGATIVE_INFINITY; + } + + for(i = 0; i < matrix.length; i += n) + for(j = 0; j !== n; ++j) { + if(matrix[i + j] < box[0 + j]) + box[0 + j] = matrix[i + j]; + + if(matrix[i + j] > box[n + j]) + box[n + j] = matrix[i + j]; + } + + /* Add a margin around the bounding box. */ + if(margin !== undefined) + for(j = 0; j !== n; ++j) { + box[0 + j] -= margin; + box[n + j] += margin; + } + } + + return box; + }, + boundingSimplex: function(n, matrix, margin) { + var box = Delaunay.boundingBox(n, matrix, margin), + simplex = new Array((n + 1) * n), + i, j; + + /* Double the size of the bounding box. */ + for(j = 0; j !== n; ++j) + box[n + j] += box[n + j] - box[0 + j]; + + /* The first vertex is just the minimum vertex of the bounding box. */ + for(j = 0; j !== n; ++j) + simplex[j] = box[j]; + + /* Every subsequent vertex is the max along that axis. */ + for(i = 0; i !== n; ++i) + for(j = 0; j !== n; ++j) + simplex[(i + 1) * n + j] = box[(i === j) * n + j]; + + /* Return the simplex. */ + return simplex; + }, + triangulate: function(n, matrix) { + var m = n + 1, + v = Math.floor(matrix.length / n), + list, i, j; + + /* If we don't have enough vertices to even make a single simplex, then + * just bail with an empty triangle list now. */ + if(v < m) + return []; + + /* Add the bounding simplex's vertices to the end of the vertex array. + * (Duplicating the vertex array first, because we don't want to ruin + * any other code's assumptions!) */ + matrix = matrix.slice(0, v * n); + Array.prototype.push.apply( + matrix, + Delaunay.boundingSimplex(n, matrix, 1) + ); + + /* Initialize the triangle list to contain the bounding simplex. */ + list = new Array(m); + + for(j = 0; j !== m; ++j) + list[j] = v + j; + + /* FIXME */ + + /* Remove any simplices that share a vertex with the bounding simplex: + * they're not part of the Delaunay mesh! */ + for(i = 0; i !== list.length; i += m) + for(j = 0; j !== m; ++j) + if(list[i + j] >= v) { + list.splice(i, m); + i -= m; + break; + } + + return list; + } + }; + + /* If we're in Node, export our module as a Node module. */ + if(typeof module !== "undefined") + module.exports = Delaunay; +}());
91 spec/delaunay.spec.js
 @@ -0,0 +1,91 @@ +var Delaunay = require("../lib/delaunay"); + +describe("Delaunay", function() { + describe("boundingBox", function() { + it("should return two vertices at the origin given no vertices", function() { + expect(Delaunay.boundingBox(2, [ + ])).toEqual([ + 0, 0, + 0, 0 + ]); + }); + + it("should return two vertices at a vertex if given only that vertex", function() { + expect(Delaunay.boundingBox(3, [ + 1, 2, 3 + ])).toEqual([ + 1, 2, 3, + 1, 2, 3 + ]); + }); + + it("should return two vertices bounding the given vertices", function() { + expect(Delaunay.boundingBox(2, [ + -1, -1, + 1, -1, + -1, 1, + 1, 1, + 0, 0 + ])).toEqual([ + -1, -1, + 1, 1 + ]); + }); + + it("should apply a margin around the bounding box if requested", function() { + expect(Delaunay.boundingBox( + 3, + [ + 1, 2, 3, + 4, 5, 6 + ], + 20 + )).toEqual([ + -19, -18, -17, + 24, 25, 26 + ]); + }); + }); + + describe("boundingSimplex", function() { + it("should return a simplex with each vertex at the origin given no vertices", function() { + expect(Delaunay.boundingSimplex(2, [ + ])).toEqual([ + 0, 0, + 0, 0, + 0, 0 + ]); + }); + + it("should return n+1 vertices at a vertex if given only that vertex", function() { + expect(Delaunay.boundingSimplex(3, [ + 1, 2, 3 + ])).toEqual([ + 1, 2, 3, + 1, 2, 3, + 1, 2, 3, + 1, 2, 3 + ]); + }); + + it("should return a right triangle stretching along each axis bounding the box bounding the vertices", function() { + expect(Delaunay.boundingSimplex(2, [ + -1, -1, + 1, -1, + -1, 1, + 1, 1, + 0, 0 + ])).toEqual([ + -1, -1, + 3, -1, + -1, 3 + ]); + }); + + /* FIXME: Test with a bounding margin! */ + }); + + describe("triangulate", function() { + /* TODO */ + }); +});