You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
18 KiB
117 lines
18 KiB
/* 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=i+1;continue}if(this.lessThanWithEpsilon(x,b)){r=i;continue}return i}};Voronoi.prototype.createEdge=function(a,b,c,d){var e=new this.Edge(a,b);this.edges.push(e);if(c!==undefined){this.setEdgeStartpoint(e,a,b,c)}if(d!==undefined){this.setEdgeEndpoint(e,a,b,d)}this.cells[a.id].halfedges.push(new this.Halfedge(a,e));this.cells[b.id].halfedges.push(new this.Halfedge(b,e));return e};Voronoi.prototype.createBorderEdge=function(a,b,c){var d=new this.Edge(a,null);d.va=b;d.vb=c;this.edges.push(d);return d};Voronoi.prototype.destroyEdge=function(a){a.id=0};Voronoi.prototype.setEdgeStartpoint=function(a,b,c,d){if(a.va===undefined&&a.vb===undefined){a.va=d;a.lSite=b;a.rSite=c}else if(a.lSite.id==c.id){a.vb=d}else{a.va=d}};Voronoi.prototype.setEdgeEndpoint=function(a,b,c,d){this.setEdgeStartpoint(a,c,b,d)};Voronoi.prototype.removeArc=function(a){var x=a.center.x;var y=a.center.y;var b=a.y;var c=this.findDeletionPoint(x,b);var d=c;while(d-1>0&&this.equalWithEpsilon(x,this.leftBreakPoint(d-1,b))){d--}var e=c;while(e+1<this.arcs.length&&this.equalWithEpsilon(x,this.rightBreakPoint(e+1,b))){e++}var f,rArc;for(var g=d;g<=e+1;g++){f=this.arcs[g-1];rArc=this.arcs[g];this.setEdgeStartpoint(rArc.edge,f.site,rArc.site,new this.Vertex(x,y))}this.voidCircleEvents(d-1,e+1);this.arcs.splice(d,e-d+1);f=this.arcs[d-1];rArc=this.arcs[d];rArc.edge=this.createEdge(f.site,rArc.site,undefined,new this.Vertex(x,y));this.addCircleEvents(d-1,b);this.addCircleEvents(d,b)};Voronoi.prototype.addArc=function(a){var b=new this.Beachsection(a);var c=this.findInsertionPoint(a.x,a.y);if(c==this.arcs.length){this.arcs.push(b);if(c===0){return}b.edge=this.createEdge(this.arcs[c-1].site,b.site);return}var d,rArc;if(c>0&&this.equalWithEpsilon(a.x,this.rightBreakPoint(c-1,a.y))&&this.equalWithEpsilon(a.x,this.leftBreakPoint(c,a.y))){d=this.arcs[c-1];rArc=this.arcs[c];this.voidCircleEvents(c-1,c);var e=this.circumcircle(d.site,a,rArc.site);this.setEdgeStartpoint(rArc.edge,d.site,rArc.site,new this.Vertex(e.x,e.y));b.edge=this.createEdge(d.site,b.site,undefined,new this.Vertex(e.x,e.y));rArc.edge=this.createEdge(b.site,rArc.site,undefined,new this.Vertex(e.x,e.y));this.arcs.splice(c,0,b);this.addCircleEvents(c-1,a.y);this.addCircleEvents(c+1,a.y);return}this.voidCircleEvents(c);d=this.arcs[c];rArc=new this.Beachsection(d.site);this.arcs.splice(c+1,0,b,rArc);b.edge=rArc.edge=this.createEdge(d.site,b.site);this.addCircleEvents(c,a.y);this.addCircleEvents(c+2,a.y)};Voronoi.prototype.circumcircle=function(a,b,c){var e=a.x;var f=a.y;var g=b.x-e;var h=b.y-f;var i=c.x-e;var j=c.y-f;var d=2*(g*j-h*i);var k=g*g+h*h;var l=i*i+j*j;var x=(j*k-h*l)/d;var y=(g*l-i*k)/d;return{x:x+e,y:y+f,radius:this.sqrt(x*x+y*y)}};Voronoi.prototype.addCircleEvents=function(a,b){if(a<=0||a>=this.arcs.length-1){return}var c=this.arcs[a];var d=this.arcs[a-1].site;var e=this.arcs[a].site;var f=this.arcs[a+1].site;if(d.id==f.id||d.id==e.id||e.id==f.id){return}if((d.y-e.y)*(f.x-e.x)<=(d.x-e.x)*(f.y-e.y)){return}var g=this.circumcircle(d,e,f);var h=g.y+g.radius;if(!this.greaterThanOrEqualWithEpsilon(h,b)){return}var i={type:this.CIRCLE_EVENT,site:e,x:g.x,y:h,center:{x:g.x,y:g.y}};c.circleEvent=i;this.queuePushCircle(i)};Voronoi.prototype.voidCircleEvents=function(a,b){if(b===undefined){b=a}a=this.max(a,0);b=this.min(b,this.arcs.length-1);while(a<=b){var c=this.arcs[a];if(c.circleEvent!==undefined){c.circleEvent.type=this.VOID_EVENT;c.circleEvent=undefined}a++}};Voronoi.prototype.queueSanitize=function(){var q=this.circEvents;var a=q.length;if(!a){return}var b=a;while(b&&q[b-1].type===this.VOID_EVENT){b--}var c=a-b;if(c){q.splice(b,c)}var d=this.arcs.length;if(q.length<d*2){return}while(true){a=b-1;while(a>0&&q[a-1].type!==this.VOID_EVENT){a--}if(a<=0){break}b=a-1;while(b>0&&q[b-1].type===this.VOID_EVENT){b--}c=a-b;q.splice(b,c);if(q.length<d){return}}};Voronoi.prototype.queuePop=function(){var a=this.siteEvents.length>0?this.siteEvents[this.siteEvents.length-1]:null;var b=this.circEvents.length>0?this.circEvents[this.circEvents.length-1]:null;if(Boolean(a)!==Boolean(b)){return a?this.siteEvents.pop():this.circEvents.pop()}if(!a){return null}if(a.y<b.y||(a.y==b.y&&a.x<b.x)){return this.siteEvents.pop()}return this.circEvents.pop()};Voronoi.prototype.queuePushSite=function(o){var q=this.siteEvents;var r=q.length;if(r){var l=0;var i,c;while(l<r){i=(l+r)>>1;c=o.y-q[i].y;if(!c){c=o.x-q[i].x}if(c>0){r=i}else if(c<0){l=i+1}else{return}}q.splice(l,0,o)}else{q.push(o)}};Voronoi.prototype.queuePushCircle=function(o){var q=this.circEvents;var r=q.length;if(r){var l=0;var i,c;while(l<r){i=(l+r)>>1;c=o.y-q[i].y;if(!c){c=o.x-q[i].x}if(c>0){r=i}else{l=i+1}}q.splice(l,0,o)}else{q.push(o)}};Voronoi.prototype.getBisector=function(a,b){var r={x:(a.x+b.x)/2,y:(a.y+b.y)/2};if(b.y==a.y){return r}r.m=(a.x-b.x)/(b.y-a.y);r.b=r.y-r.m*r.x;return r};Voronoi.prototype.connectEdge=function(a,b){var c=a.vb;if(!!c){return true}var d=a.va;var e=b.xl;var g=b.xr;var h=b.yt;var i=b.yb;var j=a.lSite;var k=a.rSite;var f=this.getBisector(j,k);if(f.m===undefined){if(f.x<e||f.x>=g){return false}if(j.x>k.x){if(d===undefined){d=new this.Vertex(f.x,h)}else if(d.y>=i){return false}c=new this.Vertex(f.x,i)}else{if(d===undefined){d=new this.Vertex(f.x,i)}else if(d.y<h){return false}c=new this.Vertex(f.x,h)}}else if(f.m<1){if(j.y<k.y){if(d===undefined){d=new this.Vertex(e,f.m*e+f.b)}else if(d.x>=g){return false}c=new this.Vertex(g,f.m*g+f.b)}else{if(d===undefined){d=new this.Vertex(g,f.m*g+f.b)}else if(d.x<e){return false}c=new this.Vertex(e,f.m*e+f.b)}}else{if(j.x>k.x){if(d===undefined){d=new this.Vertex((h-f.b)/f.m,h)}else if(d.y>=i){return false}c=new this.Vertex((i-f.b)/f.m,i)}else{if(d===undefined){d=new this.Vertex((i-f.b)/f.m,i)}else if(d.y<h){return false}c=new this.Vertex((h-f.b)/f.m,h)}}a.va=d;a.vb=c;return true};Voronoi.prototype.clipEdge=function(a,b){var c=a.va.x;var d=a.va.y;var e=a.vb.x;var f=a.vb.y;var g=0;var h=1;var i=e-c;var j=f-d;var q=c-b.xl;if(i===0&&q<0){return false}var r=-q/i;if(i<0){if(r<g){return false}else if(r<h){h=r}}else if(i>0){if(r>h){return false}else if(r>g){g=r}}q=b.xr-c;if(i===0&&q<0){return false}r=q/i;if(i<0){if(r>h){return false}else if(r>g){g=r}}else if(i>0){if(r<g){return false}else if(r<h){h=r}}q=d-b.yt;if(j===0&&q<0){return false}r=-q/j;if(j<0){if(r<g){return false}else if(r<h){h=r}}else if(j>0){if(r>h){return false}else if(r>g){g=r}}q=b.yb-d;if(j===0&&q<0){return false}r=q/j;if(j<0){if(r>h){return false}else if(r>g){g=r}}else if(j>0){if(r<g){return false}else if(r<h){h=r}}a.va.x=c+g*i;a.va.y=d+g*j;a.vb.x=c+h*i;a.vb.y=d+h*j;return true};Voronoi.prototype.clipEdges=function(a){var b=this.edges;var c=b.length;var d;for(var e=c-1;e>=0;e-=1){d=b[e];if(!this.connectEdge(d,a)||!this.clipEdge(d,a)||this.verticesAreEqual(d.va,d.vb)){this.destroyEdge(d);b.splice(e,1)}}};Voronoi.prototype.verticesAreEqual=function(a,b){return this.equalWithEpsilon(a.x,b.x)&&this.equalWithEpsilon(a.y,b.y)};Voronoi.prototype.sortHalfedgesCallback=function(a,b){var c=a.getStartpoint();var d=a.getEndpoint();var e=b.getStartpoint();var f=b.getEndpoint();return Math.atan2(f.y-e.y,f.x-e.x)-Math.atan2(d.y-c.y,d.x-c.x)};Voronoi.prototype.closeCells=function(a){var b=a.xl;var c=a.xr;var d=a.yt;var e=a.yb;this.clipEdges(a);var f=this.cells;var g;var h,iRight;var i,nHalfedges;var j;var k,endpoint;var l,vb;for(var m in f){g=f[m];if(!(g instanceof this.Cell)){continue}i=g.halfedges;h=i.length;while(h){iRight=h;while(iRight>0&&i[iRight-1].isLineSegment()){iRight--}h=iRight;while(h>0&&!i[h-1].isLineSegment()){h--}if(h===iRight){break}i.splice(h,iRight-h)}if(i.length===0){f.removeCell(g);continue}i.sort(this.sortHalfedgesCallback);nHalfedges=i.length;h=0;while(h<nHalfedges){iRight=(h+1)%nHalfedges;endpoint=i[h].getEndpoint();k=i[iRight].getStartpoint();if(!this.verticesAreEqual(endpoint,k)){l=new this.Vertex(endpoint.x,endpoint.y);if(this.equalWithEpsilon(endpoint.x,b)&&this.lessThanWithEpsilon(endpoint.y,e)){vb=new this.Vertex(b,this.equalWithEpsilon(k.x,b)?k.y:e)}else if(this.equalWithEpsilon(endpoint.y,e)&&this.lessThanWithEpsilon(endpoint.x,c)){vb=new this.Vertex(this.equalWithEpsilon(k.y,e)?k.x:c,e)}else if(this.equalWithEpsilon(endpoint.x,c)&&this.greaterThanWithEpsilon(endpoint.y,d)){vb=new this.Vertex(c,this.equalWithEpsilon(k.x,c)?k.y:d)}else if(this.equalWithEpsilon(endpoint.y,d)&&this.greaterThanWithEpsilon(endpoint.x,b)){vb=new this.Vertex(this.equalWithEpsilon(k.y,d)?k.x:b,d)}j=this.createBorderEdge(g.site,l,vb);i.splice(h+1,0,new this.Halfedge(g.site,j));nHalfedges=i.length}h++}}};Voronoi.prototype.addSites=function(a){var b=a.length;var v;for(var c=0;c<b;c++){v=a[c];this.sites.push(new this.Site(v.x,v.y))}};Voronoi.prototype.setSites=function(a){this.sites=[];this.addSites(a)};Voronoi.prototype.getSites=function(){return this.sites};Voronoi.prototype.compute=function(a){var b=new Date();this.siteEvents=[];this.circEvents=[];var c=this.sites.length;var d;for(var e=c-1;e>=0;e--){d=this.sites[e];if(!d.id){this.sites.splice(e,1)}else{this.queuePushSite({type:this.SITE_EVENT,x:d.x,y:d.y,site:d})}}this.arcs=[];this.edges=[];this.cells=new this.Cells();var f=this.queuePop();while(f){if(f.type===this.SITE_EVENT){this.cells.addCell(new this.Cell(f.site));this.addArc(f.site)}else if(f.type===this.CIRCLE_EVENT){this.removeArc(f)}else{this.queueSanitize()}f=this.queuePop()}this.closeCells(a);var g=new Date();var h={sites:this.sites,cells:this.cells,edges:this.edges,execTime:g.getTime()-b.getTime()};this.arcs=[];this.edges=[];this.cells=new this.Cells();return h};
|
|
|
|
module.exports = function () {
|
|
return new Voronoi()
|
|
}
|
|
|