|
|
|
/* eslint-disable */
|
|
|
|
/*!
|
|
|
|
A custom Javascript implementation of Steven J. Fortune's algorithm to
|
|
|
|
compute Voronoi diagrams.
|
|
|
|
Copyright (C) 2010 Raymond Hill
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
*****
|
|
|
|
|
|
|
|
Author: Raymond Hill (rhill@raymondhill.net)
|
|
|
|
File: rhill-voronoi-core-min.js
|
|
|
|
Version: 0.9
|
|
|
|
Date: Sep. 21, 2010
|
|
|
|
Description: This is my personal Javascript implementation of
|
|
|
|
Steven Fortune's algorithm to generate Voronoi diagrams.
|
|
|
|
|
|
|
|
Portions of this software use, or depend on the work of:
|
|
|
|
|
|
|
|
"Fortune's algorithm" by Steven J. Fortune: For his clever
|
|
|
|
algorithm to compute Voronoi diagrams.
|
|
|
|
http://ect.bell-labs.com/who/sjf/
|
|
|
|
|
|
|
|
"The Liang-Barsky line clipping algorithm in a nutshell!" by Daniel White,
|
|
|
|
to efficiently clip a line within a rectangle.
|
|
|
|
http://www.skytopia.com/project/articles/compsci/clipping.html
|
|
|
|
|
|
|
|
*****
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
|
|
|
var vertices = [{x:300,y:300}, {x:100,y:100}, {x:200,y:500}, {x:250,y:450}, {x:600,y:150}];
|
|
|
|
// xl, xr means x left, x right
|
|
|
|
// yt, yb means y top, y bottom
|
|
|
|
var bbox = {xl:0, xr:800, yt:0, yb:600};
|
|
|
|
var voronoi = new Voronoi();
|
|
|
|
// pass an array of objects, each of which exhibits x and y properties
|
|
|
|
voronoi.setSites(vertices);
|
|
|
|
// pass an object which exhibits xl, xr, yt, yb properties. The bounding
|
|
|
|
// box will be used to connect unbound edges, and to close open cells
|
|
|
|
result = voronoi.compute(bbox);
|
|
|
|
// render, further analyze, etc.
|
|
|
|
|
|
|
|
Return value:
|
|
|
|
An object with the following properties:
|
|
|
|
|
|
|
|
result.sites = an array of unordered, unique Voronoi.Site objects underlying the Voronoi diagram.
|
|
|
|
result.edges = an array of unordered, unique Voronoi.Edge objects making up the Voronoi diagram.
|
|
|
|
result.cells = a dictionary of Voronoi.Cell object making up the Voronoi diagram. The Voronoi.Cell
|
|
|
|
in the dictionary are keyed on their associated Voronoi.Site's unique id.
|
|
|
|
result.execTime = the time it took to compute the Voronoi diagram, in milliseconds.
|
|
|
|
|
|
|
|
Voronoi.Site object:
|
|
|
|
id: a unique id identifying this Voronoi site.
|
|
|
|
x: the x position of this Voronoi site.
|
|
|
|
y: the y position of this Voronoi site.
|
|
|
|
destroy(): mark this Voronoi site object as destroyed, it will be removed from the
|
|
|
|
internal collection and won't be part of the next Voronoi diagram computation.
|
|
|
|
|
|
|
|
When adding vertices to the Voronoi object, through Voronoi.setSites() or
|
|
|
|
Voronoi.addSites(), an internal collection of matching Voronoi.Site object is maintained,
|
|
|
|
which is read accessible at all time through Voronoi.getSites(). You are allowed to
|
|
|
|
change the x and/or y properties of any Voronoi.Site object in the array, before
|
|
|
|
launching the computation of the Voronoi diagram. However, do *not* change the id
|
|
|
|
of any Voronoi.Site object, this could break the computation of the Voronoi diagram.
|
|
|
|
|
|
|
|
Voronoi.Edge object:
|
|
|
|
id: a unique id identifying this Voronoi edge.
|
|
|
|
lSite: the Voronoi.Site object at the left of this Voronoi.Edge object.
|
|
|
|
rSite: the Voronoi.Site object at the right of this Voronoi.Edge object (can be null).
|
|
|
|
va: the Voronoi.Vertex object defining the start point (relative to the Voronoi.Site
|
|
|
|
on the left) of this Voronoi.Edge object.
|
|
|
|
vb: the Voronoi.Vertex object defining the end point (relative to Voronoi.Site on
|
|
|
|
the left) of this Voronoi.Edge object.
|
|
|
|
|
|
|
|
For edges which are used to close open cells (using the supplied bounding box), the
|
|
|
|
rSite property will be null.
|
|
|
|
|
|
|
|
Voronoi.Cells object:
|
|
|
|
A collection of Voronoi.Cell objects, keyed on the id of the associated Voronoi.Site
|
|
|
|
object.
|
|
|
|
numCells: the number of Voronoi.Cell objects in the collection.
|
|
|
|
|
|
|
|
Voronoi.Cell object:
|
|
|
|
site: the Voronoi.Site object associated with the Voronoi cell.
|
|
|
|
halfedges: an array of Voronoi.Halfedge objects, ordered counterclockwise, defining the
|
|
|
|
polygon for this Voronoi cell.
|
|
|
|
|
|
|
|
Voronoi.Halfedge object:
|
|
|
|
site: the Voronoi.Site object owning this Voronoi.Halfedge object.
|
|
|
|
edge: a reference to the unique Voronoi.Edge object underlying this Voronoi.Halfedge object.
|
|
|
|
getStartpoint(): a method returning a Voronoi.Vertex for the start point of this
|
|
|
|
halfedge. Keep in mind halfedges are always countercockwise.
|
|
|
|
getEndpoint(): a method returning a Voronoi.Vertex for the end point of this
|
|
|
|
halfedge. Keep in mind halfedges are always countercockwise.
|
|
|
|
|
|
|
|
Voronoi.Vertex object:
|
|
|
|
x: the x coordinate.
|
|
|
|
y: the y coordinate.
|
|
|
|
|
|
|
|
*/
|
|
|
|
function Voronoi(){this.sites=[];this.siteEvents=[];this.circEvents=[];this.arcs=[];this.edges=[];this.cells=new this.Cells()}Voronoi.prototype.SITE_EVENT=0;Voronoi.prototype.CIRCLE_EVENT=1;Voronoi.prototype.VOID_EVENT=-1;Voronoi.prototype.sqrt=Math.sqrt;Voronoi.prototype.abs=Math.abs;Voronoi.prototype.floor=Math.floor;Voronoi.prototype.random=Math.random;Voronoi.prototype.round=Math.round;Voronoi.prototype.min=Math.min;Voronoi.prototype.max=Math.max;Voronoi.prototype.pow=Math.pow;Voronoi.prototype.isNaN=isNaN;Voronoi.prototype.PI=Math.PI;Voronoi.prototype.EPSILON=1e-5;Voronoi.prototype.equalWithEpsilon=function(a,b){return this.abs(a-b)<1e-5};Voronoi.prototype.greaterThanWithEpsilon=function(a,b){return(a-b)>1e-5};Voronoi.prototype.greaterThanOrEqualWithEpsilon=function(a,b){return(b-a)<1e-5};Voronoi.prototype.lessThanWithEpsilon=function(a,b){return(b-a)>1e-5};Voronoi.prototype.lessThanOrEqualWithEpsilon=function(a,b){return(a-b)<1e-5};Voronoi.prototype.Beachsection=function(a){this.site=a;this.edge=null;this.sweep=-Infinity;this.lid=0;this.circleEvent=undefined};Voronoi.prototype.Beachsection.prototype.sqrt=Math.sqrt;Voronoi.prototype.Beachsection.prototype._leftParabolicCut=function(a,c,d){var e=a.x;var f=a.y;if(f==d){return e}var g=c.x;var h=c.y;if(h==d){return g}if(f==h){return(e+g)/2}var i=f-d;var j=h-d;var k=g-e;var l=1/i-1/j;var b=k/j;return(-b+this.sqrt(b*b-2*l*(k*k/(-2*j)-h+j/2+f-i/2)))/l+e};Voronoi.prototype.Beachsection.prototype.leftParabolicCut=function(a,b){if(this.sweep!==b||this.lid!==a.id){this.sweep=b;this.lid=a.id;this.lBreak=this._leftParabolicCut(this.site,a,b)}return this.lBreak};Voronoi.prototype.Beachsection.prototype.isCollapsing=function(){return this.circleEvent!==undefined&&this.circleEvent.type===Voronoi.prototype.CIRCLE_EVENT};Voronoi.prototype.Site=function(x,y){this.id=this.constructor.prototype.idgenerator++;this.x=x;this.y=y};Voronoi.prototype.Site.prototype.destroy=function(){this.id=0};Voronoi.prototype.Vertex=function(x,y){this.x=x;this.y=y};Voronoi.prototype.Edge=function(a,b){this.id=this.constructor.prototype.idgenerator++;this.lSite=a;this.rSite=b;this.va=this.vb=undefined};Voronoi.prototype.Halfedge=function(a,b){this.site=a;this.edge=b};Voronoi.prototype.Cell=function(a){this.site=a;this.halfedges=[]};Voronoi.prototype.Cells=function(){this.numCells=0};Voronoi.prototype.Cells.prototype.addCell=function(a){this[a.site.id]=a;this.numCells++};Voronoi.prototype.Cells.prototype.removeCell=function(a){delete this[a.site.id];this.numCells--};Voronoi.prototype.Site.prototype.idgenerator=1;Voronoi.prototype.Edge.prototype.isLineSegment=function(){return this.id!==0&&Boolean(this.va)&&Boolean(this.vb)};Voronoi.prototype.Edge.prototype.idgenerator=1;Voronoi.prototype.Halfedge.prototype.isLineSegment=function(){return this.edge.id!==0&&Boolean(this.edge.va)&&Boolean(this.edge.vb)};Voronoi.prototype.Halfedge.prototype.getStartpoint=function(){return this.edge.lSite.id==this.site.id?this.edge.va:this.edge.vb};Voronoi.prototype.Halfedge.prototype.getEndpoint=function(){return this.edge.lSite.id==this.site.id?this.edge.vb:this.edge.va};Voronoi.prototype.leftBreakPoint=function(a,b){var c=this.arcs[a];var d=c.site;if(d.y==b){return d.x}if(a===0){return-Infinity}return c.leftParabolicCut(this.arcs[a-1].site,b)};Voronoi.prototype.rightBreakPoint=function(a,b){if(a<this.arcs.length-1){return this.leftBreakPoint(a+1,b)}var c=this.arcs[a].site;return c.y==b?c.x:Infinity};Voronoi.prototype.findInsertionPoint=function(x,a){var n=this.arcs.length;if(!n){return 0}var l=0;var r=n;var i;while(l<r){i=(l+r)>>1;if(this.lessThanWithEpsilon(x,this.leftBreakPoint(i,a))){r=i;continue}if(this.greaterThanOrEqualWithEpsilon(x,this.rightBreakPoint(i,a))){l=i+1;continue}return i}return l};Voronoi.prototype.findDeletionPoint=function(x,a){var n=this.arcs.length;if(!n){return 0}var l=0;var r=n;var i;var b;while(l<r){i=(l+r)>>1;b=this.leftBreakPoint(i,a);if(this.lessThanWithEpsilon(x,b)){r=i;continue}if(this.greaterThanWithEpsilon(x,b)){l=i+1;continue}b=this.rightBreakPoint(i,a);if(this.greaterThanWithEpsilon(x,b)){l=
|
|
|
|
|
|
|
|
module.exports = function () {
|
|
|
|
return new Voronoi()
|
|
|
|
}
|