12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706 |
- /*
- Port by Nicolas Cannasse
- Copyright (C) 2010-2013 Raymond Hill
- MIT License: See https://github.com/gorhill/Javascript-Voronoi/LICENSE.md
- Author: Raymond Hill ([email protected])
- Contributor: Jesse Morgan ([email protected])
- File: rhill-voronoi-core.js
- Version: 0.98
- Date: January 21, 2013
- Description: This is my personal Javascript implementation of
- Steven Fortune's algorithm to compute Voronoi diagrams.
- License: See https://github.com/gorhill/Javascript-Voronoi/LICENSE.md
- Credits: See https://github.com/gorhill/Javascript-Voronoi/CREDITS.md
- History: See https://github.com/gorhill/Javascript-Voronoi/CHANGELOG.md
- */
- package h2d.col;
- import hxd.Math;
- // ---------------------------------------------------------------------------
- // Red-Black tree code (based on C version of "rbtree" by Franck Bui-Huu
- // https://github.com/fbuihuu/libtree/blob/master/rb.c
- private class RBNode<T:RBNode<T>> {
- public var rbRed : Bool;
- public var rbLeft : T;
- public var rbRight : T;
- public var rbParent : T;
- public var rbNext : T;
- public var rbPrevious : T;
- }
- @:generic private class RBTree<T:RBNode<T>> {
- public var root : T;
- public function new() {
- this.root = null;
- }
- public function rbInsertSuccessor(node : T, successor : T) {
- var parent;
- if (node != null) {
- // >>> rhill 2011-05-27: Performance: cache previous/next nodes
- successor.rbPrevious = node;
- successor.rbNext = node.rbNext;
- if (node.rbNext != null ) {
- node.rbNext.rbPrevious = successor;
- }
- node.rbNext = successor;
- // <<<
- if (node.rbRight != null) {
- // in-place expansion of node.rbRight.getFirst();
- node = node.rbRight;
- while (node.rbLeft != null) {node = node.rbLeft;}
- node.rbLeft = successor;
- }
- else {
- node.rbRight = successor;
- }
- parent = node;
- }
- // rhill 2011-06-07: if node is null, successor must be inserted
- // to the left-most part of the tree
- else if (this.root != null) {
- node = this.getFirst(this.root);
- // >>> Performance: cache previous/next nodes
- successor.rbPrevious = null;
- successor.rbNext = node;
- node.rbPrevious = successor;
- // <<<
- node.rbLeft = successor;
- parent = node;
- }
- else {
- // >>> Performance: cache previous/next nodes
- successor.rbPrevious = successor.rbNext = null;
- // <<<
- this.root = successor;
- parent = null;
- }
- successor.rbLeft = successor.rbRight = null;
- successor.rbParent = parent;
- successor.rbRed = true;
- // Fixup the modified tree by recoloring nodes and performing
- // rotations (2 at most) hence the red-black tree properties are
- // preserved.
- var grandpa, uncle;
- node = successor;
- while (parent != null && parent.rbRed) {
- grandpa = parent.rbParent;
- if (parent == grandpa.rbLeft) {
- uncle = grandpa.rbRight;
- if (uncle != null && uncle.rbRed) {
- parent.rbRed = uncle.rbRed = false;
- grandpa.rbRed = true;
- node = grandpa;
- }
- else {
- if (node == parent.rbRight) {
- this.rbRotateLeft(parent);
- node = parent;
- parent = node.rbParent;
- }
- parent.rbRed = false;
- grandpa.rbRed = true;
- this.rbRotateRight(grandpa);
- }
- }
- else {
- uncle = grandpa.rbLeft;
- if (uncle != null && uncle.rbRed) {
- parent.rbRed = uncle.rbRed = false;
- grandpa.rbRed = true;
- node = grandpa;
- }
- else {
- if (node == parent.rbLeft) {
- this.rbRotateRight(parent);
- node = parent;
- parent = node.rbParent;
- }
- parent.rbRed = false;
- grandpa.rbRed = true;
- this.rbRotateLeft(grandpa);
- }
- }
- parent = node.rbParent;
- }
- this.root.rbRed = false;
- }
- public function rbRemoveNode(node:T) {
- // >>> rhill 2011-05-27: Performance: cache previous/next nodes
- if (node.rbNext != null) {
- node.rbNext.rbPrevious = node.rbPrevious;
- }
- if (node.rbPrevious != null) {
- node.rbPrevious.rbNext = node.rbNext;
- }
- node.rbNext = node.rbPrevious = null;
- // <<<
- var parent = node.rbParent,
- left = node.rbLeft,
- right = node.rbRight,
- next;
- if (left == null) {
- next = right;
- }
- else if (right == null) {
- next = left;
- }
- else {
- next = this.getFirst(right);
- }
- if (parent != null) {
- if (parent.rbLeft == node) {
- parent.rbLeft = next;
- }
- else {
- parent.rbRight = next;
- }
- }
- else {
- this.root = next;
- }
- // enforce red-black rules
- var isRed;
- if (left != null && right != null) {
- isRed = next.rbRed;
- next.rbRed = node.rbRed;
- next.rbLeft = left;
- left.rbParent = next;
- if (next != right) {
- parent = next.rbParent;
- next.rbParent = node.rbParent;
- node = next.rbRight;
- parent.rbLeft = node;
- next.rbRight = right;
- right.rbParent = next;
- }
- else {
- next.rbParent = parent;
- parent = next;
- node = next.rbRight;
- }
- }
- else {
- isRed = node.rbRed;
- node = next;
- }
- // 'node' is now the sole successor's child and 'parent' its
- // new parent (since the successor can have been moved)
- if (node != null) {
- node.rbParent = parent;
- }
- // the 'easy' cases
- if (isRed) {return;}
- if (node != null && node.rbRed) {
- node.rbRed = false;
- return;
- }
- // the other cases
- var sibling;
- do {
- if (node == this.root) {
- break;
- }
- if (node == parent.rbLeft) {
- sibling = parent.rbRight;
- if (sibling.rbRed) {
- sibling.rbRed = false;
- parent.rbRed = true;
- this.rbRotateLeft(parent);
- sibling = parent.rbRight;
- }
- if ((sibling.rbLeft != null && sibling.rbLeft.rbRed) || (sibling.rbRight != null && sibling.rbRight.rbRed)) {
- if (sibling.rbRight == null || !sibling.rbRight.rbRed) {
- sibling.rbLeft.rbRed = false;
- sibling.rbRed = true;
- this.rbRotateRight(sibling);
- sibling = parent.rbRight;
- }
- sibling.rbRed = parent.rbRed;
- parent.rbRed = sibling.rbRight.rbRed = false;
- this.rbRotateLeft(parent);
- node = this.root;
- break;
- }
- }
- else {
- sibling = parent.rbLeft;
- if (sibling.rbRed) {
- sibling.rbRed = false;
- parent.rbRed = true;
- this.rbRotateRight(parent);
- sibling = parent.rbLeft;
- }
- if ((sibling.rbLeft != null && sibling.rbLeft.rbRed) || (sibling.rbRight != null && sibling.rbRight.rbRed)) {
- if (sibling.rbLeft == null || !sibling.rbLeft.rbRed) {
- sibling.rbRight.rbRed = false;
- sibling.rbRed = true;
- this.rbRotateLeft(sibling);
- sibling = parent.rbLeft;
- }
- sibling.rbRed = parent.rbRed;
- parent.rbRed = sibling.rbLeft.rbRed = false;
- this.rbRotateRight(parent);
- node = this.root;
- break;
- }
- }
- sibling.rbRed = true;
- node = parent;
- parent = parent.rbParent;
- } while (!node.rbRed);
- if (node != null) {node.rbRed = false;}
- }
- function rbRotateLeft(node:T) {
- var p = node,
- q = node.rbRight, // can't be null
- parent = p.rbParent;
- if (parent != null) {
- if (parent.rbLeft == p) {
- parent.rbLeft = q;
- }
- else {
- parent.rbRight = q;
- }
- }
- else {
- this.root = q;
- }
- q.rbParent = parent;
- p.rbParent = q;
- p.rbRight = q.rbLeft;
- if (p.rbRight != null) {
- p.rbRight.rbParent = p;
- }
- q.rbLeft = p;
- }
- function rbRotateRight(node:T) {
- var p = node,
- q = node.rbLeft, // can't be null
- parent = p.rbParent;
- if (parent != null) {
- if (parent.rbLeft == p) {
- parent.rbLeft = q;
- }
- else {
- parent.rbRight = q;
- }
- }
- else {
- this.root = q;
- }
- q.rbParent = parent;
- p.rbParent = q;
- p.rbLeft = q.rbRight;
- if (p.rbLeft != null) {
- p.rbLeft.rbParent = p;
- }
- q.rbRight = p;
- }
- public function getFirst(node:T) {
- while(node.rbLeft != null)
- node = node.rbLeft;
- return node;
- }
- public function getLast(node:T) {
- while( node.rbRight != null )
- node = node.rbRight;
- return node;
- }
- }
- /**
- The resulting cell inside the Voronoi diagram.
- **/
- class Cell {
- /**
- The unique ID/Index of the cell.
- **/
- public var id : Int;
- /**
- The source seed point of the cell.
- **/
- public var point : Point;
- /**
- The list of the edges of the cell.
- **/
- public var halfedges : Array<Halfedge>;
- public var closeMe : Bool;
- @:dox(hide) @:noCompletion
- public function new(id, point) {
- this.id = id;
- this.point = point;
- this.halfedges = [];
- this.closeMe = false;
- }
- /**
- Returns an enclosing circle collider of the Cell.
- _Implementation note_: Not the best possible solution and may produce artifacts.
- **/
- public function getCircle() {
- // still not the best enclosing circle
- // would require implementing http://www.personal.kent.edu/~rmuhamma/Compgeometry/MyCG/CG-Applets/Center/centercli.htm for complete solution
- var p = new Point(), ec = 0;
- for( e in halfedges ) {
- var ep = e.getStartpoint();
- p.x += ep.x;
- p.y += ep.y;
- ec++;
- }
- p.x /= ec;
- p.y /= ec;
- var r = 0.;
- for( e in halfedges ) {
- var d = p.distanceSq(e.getStartpoint());
- if( d > r ) r = d;
- }
- return new Circle(p.x, p.y, Math.sqrt(r));
- }
- @:dox(hide) @:noCompletion
- public function prepare() {
- var halfedges = this.halfedges, iHalfedge = halfedges.length, edge;
- // get rid of unused halfedges
- // rhill 2011-05-27: Keep it simple, no point here in trying
- // to be fancy: dangling edges are a typically a minority.
- while (iHalfedge-- != 0) {
- edge = halfedges[iHalfedge].edge;
- if (edge.vb == null || edge.va == null) {
- halfedges.splice(iHalfedge,1);
- }
- }
- // rhill 2011-05-26: I tried to use a binary search at insertion
- // time to keep the array sorted on-the-fly (in Cell.addHalfedge()).
- // There was no real benefits in doing so, performance on
- // Firefox 3.6 was improved marginally, while performance on
- // Opera 11 was penalized marginally.
- halfedges.sort(sortByAngle);
- return halfedges.length;
- }
- static function sortByAngle(a:Halfedge, b:Halfedge) {
- return b.angle > a.angle ? 1 : (b.angle < a.angle ? -1 : 0);
- }
- /**
- Returns a list of the neighboring cells.
- **/
- public function getNeighbors() {
- var neighbors = [],
- iHalfedge = this.halfedges.length,
- edge;
- while (iHalfedge-- != 0){
- edge = this.halfedges[iHalfedge].edge;
- // NC : changes pointId check to object == check
- if (edge.lPoint != null && edge.lPoint != this.point) {
- neighbors.push(edge.lPoint);
- }
- else if (edge.rPoint != null && edge.rPoint != this.point){
- neighbors.push(edge.rPoint);
- }
- }
- return neighbors;
- }
- /**
- Returns a list of the neighbor Cell indexes.
- **/
- public function getNeighborIndexes() {
- var neighbors = [],
- iHalfedge = this.halfedges.length,
- edge;
- while (iHalfedge-- != 0){
- edge = this.halfedges[iHalfedge].edge;
- // NC : changes pointId check to object == check
- if (edge.lPoint != null && edge.lPoint != this.point) {
- neighbors.push(edge.lCell.id);
- }
- else if (edge.rPoint != null && edge.rPoint != this.point){
- neighbors.push(edge.rCell.id);
- }
- }
- return neighbors;
- }
- /**
- Returns a bounding box of the Cell.
- **/
- public function getBbox() {
- var halfedges = this.halfedges,
- iHalfedge = halfedges.length,
- xmin = Math.POSITIVE_INFINITY,
- ymin = Math.POSITIVE_INFINITY,
- xmax = Math.NEGATIVE_INFINITY,
- ymax = Math.NEGATIVE_INFINITY;
- while (iHalfedge-- != 0) {
- var v = halfedges[iHalfedge].getStartpoint();
- var vx = v.x;
- var vy = v.y;
- if (vx < xmin) {xmin = vx;}
- if (vy < ymin) {ymin = vy;}
- if (vx > xmax) {xmax = vx;}
- if (vy > ymax) {ymax = vy;}
- // we dont need to take into account end point,
- // since each end point matches a start point
- }
- return {
- x: xmin,
- y: ymin,
- width: xmax-xmin,
- height: ymax-ymin
- };
- }
- /**
- Tests if given position is inside, on, or outside of the cell.
- @returns
- * -1: point is outside the perimeter of the cell
- * 0: point is on the perimeter of the cell
- * 1: point is inside the perimeter of the cell
- **/
- public function pointIntersection(x:Float, y:Float) {
- // Check if point in polygon. Since all polygons of a Voronoi
- // diagram are convex, then:
- // http://paulbourke.net/geometry/polygonmesh/
- // Solution 3 (2D):
- // "If the polygon is convex then one can consider the polygon
- // "as a 'path' from the first vertex. A point is on the interior
- // "of this polygons if it is always on the same side of all the
- // "line segments making up the path. ...
- // "(y - y0) (x1 - x0) - (x - x0) (y1 - y0)
- // "if it is less than 0 then P is to the right of the line segment,
- // "if greater than 0 it is to the left, if equal to 0 then it lies
- // "on the line segment"
- var halfedges = this.halfedges,
- iHalfedge = halfedges.length,
- p0, p1, r;
- while (iHalfedge-- != 0) {
- var halfedge = halfedges[iHalfedge];
- p0 = halfedge.getStartpoint();
- p1 = halfedge.getEndpoint();
- r = (y-p0.y)*(p1.x-p0.x)-(x-p0.x)*(p1.y-p0.y);
- if (r == 0) {
- return 0;
- }
- if (r > 0) {
- return -1;
- }
- }
- return 1;
- }
- }
- /**
- The resulting edge inside the Voronoi diagram.
- **/
- class Edge {
- /**
- The unique ID/Index of the edge.
- **/
- public var id : Int;
- /**
- The left-hand seed point.
- **/
- public var lPoint : Point;
- /**
- The right-hand seed point.
- **/
- public var rPoint : Point;
- /**
- The left-hand cell along the edge.
- **/
- public var lCell : Null<Cell>;
- /**
- The right-hand cell along the edge.
- **/
- public var rCell : Null<Cell>;
- /**
- The first position of the edge segment.
- **/
- public var va : Null<Point>;
- /**
- The second position of the edge segment.
- **/
- public var vb : Null<Point>;
- @:dox(hide) @:noCompletion
- public function new(lPoint, rPoint) {
- this.lPoint = lPoint;
- this.rPoint = rPoint;
- this.va = this.vb = null;
- }
- }
- /**
- The edge attached to a Voronoi `Cell`.
- **/
- class Halfedge {
- /**
- The seed Point of the Cell this edge is attached to.
- **/
- public var point : Point;
- /**
- The Edge this half-edge is attached to.
- **/
- public var edge : Edge;
- /**
- The perpendicular angle to the edge segment pointing in the direction of either neighboring Cell of the border.
- **/
- public var angle : Float;
- @:dox(hide) @:noCompletion
- public function new(edge, lPoint:Point, rPoint:Point) {
- this.point = lPoint;
- this.edge = edge;
- // 'angle' is a value to be used for properly sorting the
- // halfsegments counterclockwise. By convention, we will
- // use the angle of the line defined by the 'point to the left'
- // to the 'point to the right'.
- // However, border edges have no 'point to the right': thus we
- // use the angle of line perpendicular to the halfsegment (the
- // edge should have both end points defined in such case.)
- if (rPoint != null) {
- this.angle = Math.atan2(rPoint.y-lPoint.y, rPoint.x-lPoint.x);
- } else {
- var va = edge.va,
- vb = edge.vb;
- // rhill 2011-05-31: used to call getStartpoint()/getEndpoint(),
- // but for performance purpose, these are expanded in place here.
- this.angle = edge.lPoint == lPoint
- ? Math.atan2(vb.x-va.x, va.y-vb.y)
- : Math.atan2(va.x-vb.x, vb.y-va.y);
- }
- }
- /**
- Returns the starting point of the edge segment.
- **/
- public inline function getStartpoint() {
- return this.edge.lPoint == this.point ? this.edge.va : this.edge.vb;
- }
- /**
- Returns the end point of the edge segment.
- **/
- public inline function getEndpoint() {
- return this.edge.lPoint == this.point ? this.edge.vb : this.edge.va;
- }
- /**
- Returns the neighboring Cell of this half-edge or null if it's a border edge.
- **/
- public inline function getTarget() {
- return this.edge.lCell != null && this.edge.lCell.point != point ? this.edge.lCell : this.edge.rCell;
- }
- }
- /**
- The resulting diagram of the `Voronoi.compute`.
- **/
- class Diagram {
- /**
- The list of the generated cells.
- **/
- public var cells : Array<Cell>;
- /**
- The list of the diagram seed points.
- **/
- public var points : Array<Point>;
- /**
- The list of edges between diagram cells.
- **/
- public var edges : Array<Edge>;
- /**
- The duration it took to compute this diagram.
- **/
- public var execTime : Float;
- @:dox(hide) @:noCompletion
- public function new() {
- }
- }
- private class Beachsection extends RBNode<Beachsection> {
- public var point : Point;
- public var edge : Edge;
- public var circleEvent : CircleEvent;
- public function new() {
- }
- }
- private class CircleEvent extends RBNode<CircleEvent> {
- public var point : Point;
- public var arc : Beachsection;
- public var x : Float;
- public var y : Float;
- public var ycenter : Float;
- public function new() {
- }
- }
- /**
- A Steven Fortune's algorithm to compute Voronoi diagram from given set of Points and a bounding box.
- The implementation is a port from JS library: https://github.com/gorhill/Javascript-Voronoi
- **/
- class Voronoi {
- var epsilon : Float;
- var beachline : RBTree<Beachsection>;
- var vertices : Array<Point>;
- var edges : Array<Edge>;
- var cells : Array<Cell>;
- var beachsectionJunkyard : Array<Beachsection>;
- var circleEventJunkyard : Array<CircleEvent>;
- var circleEvents : RBTree<CircleEvent>;
- var firstCircleEvent : CircleEvent;
- var pointCell : Map<Point,Cell>;
- /**
- Create a new Voronoi algorithm calculator.
- **/
- public function new( epsilon = 1e-9 ) {
- this.epsilon = epsilon;
- this.vertices = null;
- this.edges = null;
- this.cells = null;
- this.beachsectionJunkyard = [];
- this.circleEventJunkyard = [];
- }
- /**
- Clean up the calculator from previous operation, and prepare for a new one.
- Not required to be called manually, as it's invoked by `Voronoi.compute`.
- **/
- public function reset() {
- if( this.beachline == null )
- this.beachline = new RBTree<Beachsection>();
- // Move leftover beachsections to the beachsection junkyard.
- if (this.beachline.root != null) {
- var beachsection = this.beachline.getFirst(this.beachline.root);
- while (beachsection != null) {
- this.beachsectionJunkyard.push(beachsection); // mark for reuse
- beachsection = beachsection.rbNext;
- }
- }
- this.beachline.root = null;
- if (this.circleEvents == null) {
- this.circleEvents = new RBTree<CircleEvent>();
- }
- pointCell = new Map();
- this.circleEvents.root = this.firstCircleEvent = null;
- this.vertices = [];
- this.edges = [];
- this.cells = [];
- }
- static inline function abs(x:Float) return x < 0 ? -x : x;
- inline function equalWithepsilon(a:Float,b:Float) return abs(a-b)<epsilon;
- inline function greaterThanWithepsilon(a:Float,b:Float) return a-b>epsilon;
- inline function greaterThanOrEqualWithepsilon(a:Float,b:Float) return b-a<epsilon;
- inline function lessThanWithepsilon(a:Float,b:Float) return b-a>epsilon;
- inline function lessThanOrEqualWithepsilon(a:Float,b:Float) return a-b<epsilon;
- function createVertex(x, y) {
- var v = new Point(x, y);
- this.vertices.push(v);
- return v;
- }
- // this create and add an edge to internal collection, and also create
- // two halfedges which are added to each point's counterclockwise array
- // of halfedges.
- function createEdge(lPoint, rPoint, va = null, vb = null) {
- var edge = new Edge(lPoint, rPoint);
- this.edges.push(edge);
- if (va != null) {
- this.setEdgeStartpoint(edge, lPoint, rPoint, va);
- }
- if (vb != null) {
- this.setEdgeEndpoint(edge, lPoint, rPoint, vb);
- }
- pointCell.get(lPoint).halfedges.push(new Halfedge(edge, lPoint, rPoint));
- pointCell.get(rPoint).halfedges.push(new Halfedge(edge, rPoint, lPoint));
- return edge;
- }
- function createBorderEdge(lPoint, va, vb) {
- var edge = new Edge(lPoint, null);
- edge.va = va;
- edge.vb = vb;
- this.edges.push(edge);
- return edge;
- }
- function setEdgeStartpoint(edge:Edge, lPoint, rPoint, vertex) {
- if (edge.va == null && edge.vb == null) {
- edge.va = vertex;
- edge.lPoint = lPoint;
- edge.rPoint = rPoint;
- }
- else if (edge.lPoint == rPoint) {
- edge.vb = vertex;
- }
- else {
- edge.va = vertex;
- }
- }
- function setEdgeEndpoint(edge, lPoint, rPoint, vertex) {
- this.setEdgeStartpoint(edge, rPoint, lPoint, vertex);
- }
- // rhill 2011-06-02: A lot of Beachsection instanciations
- // occur during the computation of the Voronoi diagram,
- // somewhere between the number of points and twice the
- // number of points, while the number of Beachsections on the
- // beachline at any given time is comparatively low. For this
- // reason, we reuse already created Beachsections, in order
- // to avoid new memory allocation. This resulted in a measurable
- // performance gain.
- function createBeachsection(point:Point) {
- var beachsection = this.beachsectionJunkyard.pop();
- if ( beachsection == null )
- beachsection = new Beachsection();
- beachsection.point = point;
- return beachsection;
- }
- // calculate the left break point of a particular beach section,
- // given a particular sweep line
- function leftBreakPoint(arc:Beachsection, directrix:Float) {
- // http://en.wikipedia.org/wiki/Parabola
- // http://en.wikipedia.org/wiki/Quadratic_equation
- // h1 = x1,
- // k1 = (y1+directrix)/2,
- // h2 = x2,
- // k2 = (y2+directrix)/2,
- // p1 = k1-directrix,
- // a1 = 1/(4*p1),
- // b1 = -h1/(2*p1),
- // c1 = h1*h1/(4*p1)+k1,
- // p2 = k2-directrix,
- // a2 = 1/(4*p2),
- // b2 = -h2/(2*p2),
- // c2 = h2*h2/(4*p2)+k2,
- // x = (-(b2-b1) + Math.sqrt((b2-b1)*(b2-b1) - 4*(a2-a1)*(c2-c1))) / (2*(a2-a1))
- // When x1 become the x-origin:
- // h1 = 0,
- // k1 = (y1+directrix)/2,
- // h2 = x2-x1,
- // k2 = (y2+directrix)/2,
- // p1 = k1-directrix,
- // a1 = 1/(4*p1),
- // b1 = 0,
- // c1 = k1,
- // p2 = k2-directrix,
- // a2 = 1/(4*p2),
- // b2 = -h2/(2*p2),
- // c2 = h2*h2/(4*p2)+k2,
- // x = (-b2 + Math.sqrt(b2*b2 - 4*(a2-a1)*(c2-k1))) / (2*(a2-a1)) + x1
- // change code below at your own risk: care has been taken to
- // reduce errors due to computers' finite arithmetic precision.
- // Maybe can still be improved, will see if any more of this
- // kind of errors pop up again.
- var point = arc.point,
- rfocx = point.x,
- rfocy = point.y,
- pby2 = rfocy-directrix;
- // parabola in degenerate case where focus is on directrix
- if (pby2 == 0) {
- return rfocx;
- }
- var lArc = arc.rbPrevious;
- if (lArc == null) {
- return Math.NEGATIVE_INFINITY;
- }
- point = lArc.point;
- var lfocx = point.x,
- lfocy = point.y,
- plby2 = lfocy-directrix;
- // parabola in degenerate case where focus is on directrix
- if (plby2 == 0) {
- return lfocx;
- }
- var hl = lfocx-rfocx,
- aby2 = 1/pby2-1/plby2,
- b = hl/plby2;
- if (aby2 != 0) {
- return (-b+Math.sqrt(b*b-2*aby2*(hl*hl/(-2*plby2)-lfocy+plby2/2+rfocy-pby2/2)))/aby2+rfocx;
- }
- // both parabolas have same distance to directrix, thus break point is midway
- return (rfocx+lfocx)/2;
- }
- // calculate the right break point of a particular beach section,
- // given a particular directrix
- function rightBreakPoint(arc:Beachsection, directrix) {
- var rArc = arc.rbNext;
- if (rArc != null) {
- return this.leftBreakPoint(rArc, directrix);
- }
- var point = arc.point;
- return point.y == directrix ? point.x : Math.POSITIVE_INFINITY;
- }
- function detachBeachsection(beachsection) {
- this.detachCircleEvent(beachsection); // detach potentially attached circle event
- this.beachline.rbRemoveNode(beachsection); // remove from RB-tree
- this.beachsectionJunkyard.push(beachsection); // mark for reuse
- }
- function removeBeachsection(beachsection:Beachsection) {
- var circle = beachsection.circleEvent,
- x = circle.x,
- y = circle.ycenter,
- vertex = this.createVertex(x, y),
- previous = beachsection.rbPrevious,
- next = beachsection.rbNext,
- disappearingTransitions = [beachsection];
- // remove collapsed beachsection from beachline
- this.detachBeachsection(beachsection);
- // there could be more than one empty arc at the deletion point, this
- // happens when more than two edges are linked by the same vertex,
- // so we will collect all those edges by looking up both sides of
- // the deletion point.
- // by the way, there is *always* a predecessor/successor to any collapsed
- // beach section, it's just impossible to have a collapsing first/last
- // beach sections on the beachline, since they obviously are unconstrained
- // on their left/right side.
- // look left
- var lArc = previous;
- while (lArc.circleEvent != null && abs(x-lArc.circleEvent.x)<epsilon && abs(y-lArc.circleEvent.ycenter)<epsilon) {
- previous = lArc.rbPrevious;
- disappearingTransitions.unshift(lArc);
- this.detachBeachsection(lArc); // mark for reuse
- lArc = previous;
- }
- // even though it is not disappearing, I will also add the beach section
- // immediately to the left of the left-most collapsed beach section, for
- // convenience, since we need to refer to it later as this beach section
- // is the 'left' point of an edge for which a start point is set.
- disappearingTransitions.unshift(lArc);
- this.detachCircleEvent(lArc);
- // look right
- var rArc = next;
- while (rArc.circleEvent != null && abs(x-rArc.circleEvent.x)<epsilon && abs(y-rArc.circleEvent.ycenter)<epsilon) {
- next = rArc.rbNext;
- disappearingTransitions.push(rArc);
- this.detachBeachsection(rArc); // mark for reuse
- rArc = next;
- }
- // we also have to add the beach section immediately to the right of the
- // right-most collapsed beach section, since there is also a disappearing
- // transition representing an edge's start point on its left.
- disappearingTransitions.push(rArc);
- this.detachCircleEvent(rArc);
- // walk through all the disappearing transitions between beach sections and
- // set the start point of their (implied) edge.
- var nArcs = disappearingTransitions.length,
- iArc;
- for( iArc in 1...nArcs ) {
- rArc = disappearingTransitions[iArc];
- lArc = disappearingTransitions[iArc-1];
- this.setEdgeStartpoint(rArc.edge, lArc.point, rArc.point, vertex);
- }
- // create a new edge as we have now a new transition between
- // two beach sections which were previously not adjacent.
- // since this edge appears as a new vertex is defined, the vertex
- // actually define an end point of the edge (relative to the point
- // on the left)
- lArc = disappearingTransitions[0];
- rArc = disappearingTransitions[nArcs-1];
- rArc.edge = this.createEdge(lArc.point, rArc.point, null, vertex);
- // create circle events if any for beach sections left in the beachline
- // adjacent to collapsed sections
- this.attachCircleEvent(lArc);
- this.attachCircleEvent(rArc);
- }
- function addBeachsection(point:Point) {
- var x = point.x,
- directrix = point.y;
- // find the left and right beach sections which will surround the newly
- // created beach section.
- // rhill 2011-06-01: This loop is one of the most often executed,
- // hence we expand in-place the comparison-against-epsilon calls.
- var lArc = null, rArc = null,
- dxl, dxr,
- node = this.beachline.root;
- while (node != null) {
- dxl = this.leftBreakPoint(node,directrix)-x;
- // x lessThanWithepsilon xl => falls somewhere before the left edge of the beachsection
- if (dxl > epsilon) {
- // this case should never happen
- // if (!node.rbLeft) {
- // rArc = node.rbLeft;
- // break;
- // }
- node = node.rbLeft;
- }
- else {
- dxr = x-this.rightBreakPoint(node,directrix);
- // x greaterThanWithepsilon xr => falls somewhere after the right edge of the beachsection
- if (dxr > epsilon) {
- if (node.rbRight == null) {
- lArc = node;
- break;
- }
- node = node.rbRight;
- }
- else {
- // x equalWithepsilon xl => falls exactly on the left edge of the beachsection
- if (dxl > -epsilon) {
- lArc = node.rbPrevious;
- rArc = node;
- }
- // x equalWithepsilon xr => falls exactly on the right edge of the beachsection
- else if (dxr > -epsilon) {
- lArc = node;
- rArc = node.rbNext;
- }
- // falls exactly somewhere in the middle of the beachsection
- else {
- lArc = rArc = node;
- }
- break;
- }
- }
- }
- // at this point, keep in mind that lArc and/or rArc could be
- // undefined or null.
- // create a new beach section object for the point and add it to RB-tree
- var newArc = this.createBeachsection(point);
- this.beachline.rbInsertSuccessor(lArc, newArc);
- // cases:
- //
- // [null,null]
- // least likely case: new beach section is the first beach section on the
- // beachline.
- // This case means:
- // no new transition appears
- // no collapsing beach section
- // new beachsection become root of the RB-tree
- if (lArc == null && rArc == null) {
- return;
- }
- // [lArc,rArc] where lArc == rArc
- // most likely case: new beach section split an existing beach
- // section.
- // This case means:
- // one new transition appears
- // the left and right beach section might be collapsing as a result
- // two new nodes added to the RB-tree
- if (lArc == rArc) {
- // invalidate circle event of split beach section
- this.detachCircleEvent(lArc);
- // split the beach section into two separate beach sections
- rArc = this.createBeachsection(lArc.point);
- this.beachline.rbInsertSuccessor(newArc, rArc);
- // since we have a new transition between two beach sections,
- // a new edge is born
- newArc.edge = rArc.edge = this.createEdge(lArc.point, newArc.point);
- // check whether the left and right beach sections are collapsing
- // and if so create circle events, to be notified when the point of
- // collapse is reached.
- this.attachCircleEvent(lArc);
- this.attachCircleEvent(rArc);
- return;
- }
- // [lArc,null]
- // even less likely case: new beach section is the *last* beach section
- // on the beachline -- this can happen *only* if *all* the previous beach
- // sections currently on the beachline share the same y value as
- // the new beach section.
- // This case means:
- // one new transition appears
- // no collapsing beach section as a result
- // new beach section become right-most node of the RB-tree
- if (lArc != null && rArc == null) {
- newArc.edge = this.createEdge(lArc.point,newArc.point);
- return;
- }
- // [null,rArc]
- // impossible case: because points are strictly processed from top to bottom,
- // and left to right, which guarantees that there will always be a beach section
- // on the left -- except of course when there are no beach section at all on
- // the beach line, which case was handled above.
- // rhill 2011-06-02: No point testing in non-debug version
- //if (!lArc && rArc) {
- // throw "Voronoi.addBeachsection(): What is this I don't even";
- // }
- // [lArc,rArc] where lArc != rArc
- // somewhat less likely case: new beach section falls *exactly* in between two
- // existing beach sections
- // This case means:
- // one transition disappears
- // two new transitions appear
- // the left and right beach section might be collapsing as a result
- // only one new node added to the RB-tree
- if (lArc != rArc) {
- // invalidate circle events of left and right points
- this.detachCircleEvent(lArc);
- this.detachCircleEvent(rArc);
- // an existing transition disappears, meaning a vertex is defined at
- // the disappearance point.
- // since the disappearance is caused by the new beachsection, the
- // vertex is at the center of the circumscribed circle of the left,
- // new and right beachsections.
- // http://mathforum.org/library/drmath/view/55002.html
- // Except that I bring the origin at A to simplify
- // calculation
- var lPoint = lArc.point,
- ax = lPoint.x,
- ay = lPoint.y,
- bx=point.x-ax,
- by=point.y-ay,
- rPoint = rArc.point,
- cx=rPoint.x-ax,
- cy=rPoint.y-ay,
- d=2*(bx*cy-by*cx),
- hb=bx*bx+by*by,
- hc=cx*cx+cy*cy,
- vertex = this.createVertex((cy*hb-by*hc)/d+ax, (bx*hc-cx*hb)/d+ay);
- // one transition disappear
- this.setEdgeStartpoint(rArc.edge, lPoint, rPoint, vertex);
- // two new transitions appear at the new vertex location
- newArc.edge = this.createEdge(lPoint, point, null, vertex);
- rArc.edge = this.createEdge(point, rPoint, null, vertex);
- // check whether the left and right beach sections are collapsing
- // and if so create circle events, to handle the point of collapse.
- this.attachCircleEvent(lArc);
- this.attachCircleEvent(rArc);
- return;
- }
- }
- function attachCircleEvent(arc:Beachsection) {
- var lArc = arc.rbPrevious,
- rArc = arc.rbNext;
- if (lArc == null || rArc == null) {return;} // does that ever happen?
- var lPoint = lArc.point,
- cPoint = arc.point,
- rPoint = rArc.point;
- // If point of left beachsection is same as point of
- // right beachsection, there can't be convergence
- if (lPoint==rPoint) {return;}
- // Find the circumscribed circle for the three points associated
- // with the beachsection triplet.
- // rhill 2011-05-26: It is more efficient to calculate in-place
- // rather than getting the resulting circumscribed circle from an
- // object returned by calling Voronoi.circumcircle()
- // http://mathforum.org/library/drmath/view/55002.html
- // Except that I bring the origin at cPoint to simplify calculations.
- // The bottom-most part of the circumcircle is our Fortune 'circle
- // event', and its center is a vertex potentially part of the final
- // Voronoi diagram.
- var bx = cPoint.x,
- by = cPoint.y,
- ax = lPoint.x-bx,
- ay = lPoint.y-by,
- cx = rPoint.x-bx,
- cy = rPoint.y-by;
- // If points l->c->r are clockwise, then center beach section does not
- // collapse, hence it can't end up as a vertex (we reuse 'd' here, which
- // sign is reverse of the orientation, hence we reverse the test.
- // http://en.wikipedia.org/wiki/Curve_orientation#Orientation_of_a_simple_polygon
- // rhill 2011-05-21: Nasty finite precision error which caused circumcircle() to
- // return infinites: 1e-12 seems to fix the problem.
- var d = 2*(ax*cy-ay*cx);
- if (d >= -2e-12){return;}
- var ha = ax*ax+ay*ay,
- hc = cx*cx+cy*cy,
- x = (cy*ha-ay*hc)/d,
- y = (ax*hc-cx*ha)/d,
- ycenter = y+by;
- // Important: ybottom should always be under or at sweep, so no need
- // to waste CPU cycles by checking
- // recycle circle event object if possible
- var circleEvent = this.circleEventJunkyard.pop();
- if (circleEvent == null) {
- circleEvent = new CircleEvent();
- }
- circleEvent.arc = arc;
- circleEvent.point = cPoint;
- circleEvent.x = x+bx;
- circleEvent.y = ycenter+Math.sqrt(x*x+y*y); // y bottom
- circleEvent.ycenter = ycenter;
- arc.circleEvent = circleEvent;
- // find insertion point in RB-tree: circle events are ordered from
- // smallest to largest
- var predecessor = null,
- node = this.circleEvents.root;
- while (node != null) {
- if (circleEvent.y < node.y || (circleEvent.y == node.y && circleEvent.x <= node.x)) {
- if (node.rbLeft != null) {
- node = node.rbLeft;
- }
- else {
- predecessor = node.rbPrevious;
- break;
- }
- }
- else {
- if (node.rbRight != null) {
- node = node.rbRight;
- }
- else {
- predecessor = node;
- break;
- }
- }
- }
- this.circleEvents.rbInsertSuccessor(predecessor, circleEvent);
- if (predecessor == null) {
- this.firstCircleEvent = circleEvent;
- }
- }
- function detachCircleEvent(arc:Beachsection) {
- var circle = arc.circleEvent;
- if (circle != null) {
- if (circle.rbPrevious == null) {
- this.firstCircleEvent = circle.rbNext;
- }
- this.circleEvents.rbRemoveNode(circle); // remove from RB-tree
- this.circleEventJunkyard.push(circle);
- arc.circleEvent = null;
- }
- }
- // ---------------------------------------------------------------------------
- // Diagram completion methods
- // connect dangling edges (not if a cursory test tells us
- // it is not going to be visible.
- // return value:
- // false: the dangling endpoint couldn't be connected
- // true: the dangling endpoint could be connected
- function connectEdge(edge:Edge, bbox:Bounds) {
- // skip if end point already connected
- var vb = edge.vb;
- if (vb != null) {return true;}
- // make local copy for performance purpose
- var va = edge.va,
- xl = bbox.xMin,
- xr = bbox.xMax,
- yt = bbox.yMin,
- yb = bbox.yMax,
- lPoint = edge.lPoint,
- rPoint = edge.rPoint,
- lx = lPoint.x,
- ly = lPoint.y,
- rx = rPoint.x,
- ry = rPoint.y,
- fx = (lx+rx)/2,
- fy = (ly+ry)/2,
- fm = 0., fb = 0.;
- pointCell.get(lPoint).closeMe = true;
- pointCell.get(rPoint).closeMe = true;
- // get the line equation of the bisector if line is not vertical
- if (ry != ly) {
- fm = (lx-rx)/(ry-ly);
- fb = fy-fm*fx;
- }
- // remember, direction of line (relative to left point):
- // upward: left.x < right.x
- // downward: left.x > right.x
- // horizontal: left.x == right.x
- // upward: left.x < right.x
- // rightward: left.y < right.y
- // leftward: left.y > right.y
- // vertical: left.y == right.y
- // depending on the direction, find the best side of the
- // bounding box to use to determine a reasonable start point
- // special case: vertical line
- if (ry == ly) {
- // doesn't intersect with viewport
- if (fx < xl || fx >= xr) {return false;}
- // downward
- if (lx > rx) {
- if (va == null || va.y < yt) {
- va = this.createVertex(fx, yt);
- }
- else if (va.y >= yb) {
- return false;
- }
- vb = this.createVertex(fx, yb);
- }
- // upward
- else {
- if (va == null || va.y > yb) {
- va = this.createVertex(fx, yb);
- }
- else if (va.y < yt) {
- return false;
- }
- vb = this.createVertex(fx, yt);
- }
- }
- // closer to vertical than horizontal, connect start point to the
- // top or bottom side of the bounding box
- else if (fm < -1 || fm > 1) {
- // downward
- if (lx > rx) {
- if (va == null || va.y < yt) {
- va = this.createVertex((yt-fb)/fm, yt);
- }
- else if (va.y >= yb) {
- return false;
- }
- vb = this.createVertex((yb-fb)/fm, yb);
- }
- // upward
- else {
- if (va == null || va.y > yb) {
- va = this.createVertex((yb-fb)/fm, yb);
- }
- else if (va.y < yt) {
- return false;
- }
- vb = this.createVertex((yt-fb)/fm, yt);
- }
- }
- // closer to horizontal than vertical, connect start point to the
- // left or right side of the bounding box
- else {
- // rightward
- if (ly < ry) {
- if (va == null || va.x < xl) {
- va = this.createVertex(xl, fm*xl+fb);
- }
- else if (va.x >= xr) {
- return false;
- }
- vb = this.createVertex(xr, fm*xr+fb);
- }
- // leftward
- else {
- if (va == null || va.x > xr) {
- va = this.createVertex(xr, fm*xr+fb);
- }
- else if (va.x < xl) {
- return false;
- }
- vb = this.createVertex(xl, fm*xl+fb);
- }
- }
- edge.va = va;
- edge.vb = vb;
- return true;
- }
- // line-clipping code taken from:
- // Liang-Barsky function by Daniel White
- // http://www.skytopia.com/project/articles/compsci/clipping.html
- // Thanks!
- // A bit modified to minimize code paths
- function clipEdge(edge:Edge, bbox:Bounds) {
- var ax = edge.va.x,
- ay = edge.va.y,
- bx = edge.vb.x,
- by = edge.vb.y,
- t0 = 0.,
- t1 = 1.,
- dx = bx-ax,
- dy = by-ay;
- // left
- var q = ax-bbox.xMin;
- if (dx==0 && q<0) {return false;}
- var r = -q/dx;
- if (dx<0) {
- if (r<t0) {return false;}
- if (r<t1) {t1=r;}
- }
- else if (dx>0) {
- if (r>t1) {return false;}
- if (r>t0) {t0=r;}
- }
- // right
- q = bbox.xMax-ax;
- if (dx==0 && q<0) {return false;}
- r = q/dx;
- if (dx<0) {
- if (r>t1) {return false;}
- if (r>t0) {t0=r;}
- }
- else if (dx>0) {
- if (r<t0) {return false;}
- if (r<t1) {t1=r;}
- }
- // top
- q = ay-bbox.yMin;
- if (dy==0 && q<0) {return false;}
- r = -q/dy;
- if (dy<0) {
- if (r<t0) {return false;}
- if (r<t1) {t1=r;}
- }
- else if (dy>0) {
- if (r>t1) {return false;}
- if (r>t0) {t0=r;}
- }
- // bottom
- q = bbox.yMax-ay;
- if (dy==0 && q<0) {return false;}
- r = q/dy;
- if (dy<0) {
- if (r>t1) {return false;}
- if (r>t0) {t0=r;}
- }
- else if (dy>0) {
- if (r<t0) {return false;}
- if (r<t1) {t1=r;}
- }
- // if we reach this point, Voronoi edge is within bbox
- // if t0 > 0, va needs to change
- // rhill 2011-06-03: we need to create a new vertex rather
- // than modifying the existing one, since the existing
- // one is likely shared with at least another edge
- if (t0 > 0) {
- edge.va = this.createVertex(ax+t0*dx, ay+t0*dy);
- }
- // if t1 < 1, vb needs to change
- // rhill 2011-06-03: we need to create a new vertex rather
- // than modifying the existing one, since the existing
- // one is likely shared with at least another edge
- if (t1 < 1) {
- edge.vb = this.createVertex(ax+t1*dx, ay+t1*dy);
- }
- // va and/or vb were clipped, thus we will need to close
- // cells which use this edge.
- if ( t0 > 0 || t1 < 1 ) {
- pointCell.get(edge.lPoint).closeMe = true;
- pointCell.get(edge.rPoint).closeMe = true;
- }
- return true;
- }
- // Connect/cut edges at bounding box
- function clipEdges(bbox:Bounds) {
- // connect all dangling edges to bounding box
- // or get rid of them if it can't be done
- var edges = this.edges,
- iEdge = edges.length,
- edge;
- // iterate backward so we can splice safely
- while (iEdge-- != 0) {
- edge = edges[iEdge];
- // edge is removed if:
- // it is wholly outside the bounding box
- // it is actually a point rather than a line
- if (!this.connectEdge(edge, bbox) || !this.clipEdge(edge, bbox) || (abs(edge.va.x-edge.vb.x)<epsilon && abs(edge.va.y-edge.vb.y)<epsilon)) {
- edge.va = edge.vb = null;
- edges.splice(iEdge,1);
- }
- }
- }
- // Close the cells.
- // The cells are bound by the supplied bounding box.
- // Each cell refers to its associated point, and a list
- // of halfedges ordered counterclockwise.
- function closeCells(bbox:Bounds) {
- // prune, order halfedges, then add missing ones
- // required to close cells
- var xl = bbox.xMin,
- xr = bbox.xMax,
- yt = bbox.yMin,
- yb = bbox.yMax,
- cells = this.cells,
- iCell = cells.length,
- cell,
- iLeft,
- halfedges, nHalfedges,
- edge,
- lastBorderSegment,
- va, vb, vz = null;
- while (iCell-- != 0) {
- cell = cells[iCell];
- // trim non fully-defined halfedges and sort them counterclockwise
- if (cell.prepare() == 0) continue;
- if (!cell.closeMe) continue;
- // close open cells
- // step 1: find first 'unclosed' point, if any.
- // an 'unclosed' point will be the end point of a halfedge which
- // does not match the start point of the following halfedge
- halfedges = cell.halfedges;
- nHalfedges = halfedges.length;
- // special case: only one point, in which case, the viewport is the cell
- // ...
- // all other cases
- iLeft = 0;
- while (iLeft < nHalfedges) {
- va = halfedges[iLeft].getEndpoint();
- vz = halfedges[(iLeft+1) % nHalfedges].getStartpoint();
- // if end point is not equal to start point, we need to add the missing
- // halfedge(s) to close the cell
- if (abs(va.x-vz.x)>=epsilon || abs(va.y-vz.y)>=epsilon) {
- // rhill 2013-12-02:
- // "Holes" in the halfedges are not necessarily always adjacent.
- // https://github.com/gorhill/Javascript-Voronoi/issues/16
- // find entry point:
- do {
- // walk downward along left side
- if (this.equalWithepsilon(va.x,xl) && this.lessThanWithepsilon(va.y,yb)) {
- lastBorderSegment = this.equalWithepsilon(vz.x,xl);
- vb = this.createVertex(xl, lastBorderSegment ? vz.y : yb);
- edge = this.createBorderEdge(cell.point, va, vb);
- iLeft++;
- halfedges.insert(iLeft, new Halfedge(edge, cell.point, null));
- nHalfedges++;
- if ( lastBorderSegment ) { break; }
- va = vb;
- // fall through
- }
- if (this.equalWithepsilon(va.y,yb) && this.lessThanWithepsilon(va.x,xr)) {
- lastBorderSegment = this.equalWithepsilon(vz.y,yb);
- vb = this.createVertex(lastBorderSegment ? vz.x : xr, yb);
- edge = this.createBorderEdge(cell.point, va, vb);
- iLeft++;
- halfedges.insert(iLeft, new Halfedge(edge, cell.point, null));
- nHalfedges++;
- if ( lastBorderSegment ) { break; }
- va = vb;
- // fall through
- }
- if (this.equalWithepsilon(va.x,xr) && this.greaterThanWithepsilon(va.y,yt)) {
- lastBorderSegment = this.equalWithepsilon(vz.x,xr);
- vb = this.createVertex(xr, lastBorderSegment ? vz.y : yt);
- edge = this.createBorderEdge(cell.point, va, vb);
- iLeft++;
- halfedges.insert(iLeft, new Halfedge(edge, cell.point, null));
- nHalfedges++;
- if ( lastBorderSegment ) { break; }
- va = vb;
- // fall through
- }
- if (this.equalWithepsilon(va.y,yt) && this.greaterThanWithepsilon(va.x,xl)) {
- lastBorderSegment = this.equalWithepsilon(vz.y,yt);
- vb = this.createVertex(lastBorderSegment ? vz.x : xl, yt);
- edge = this.createBorderEdge(cell.point, va, vb);
- iLeft++;
- halfedges.insert(iLeft, new Halfedge(edge, cell.point, null));
- nHalfedges++;
- if ( lastBorderSegment ) { break; }
- va = vb;
- // fall through
- // walk downward along left side
- lastBorderSegment = this.equalWithepsilon(vz.x,xl);
- vb = this.createVertex(xl, lastBorderSegment ? vz.y : yb);
- edge = this.createBorderEdge(cell.point, va, vb);
- iLeft++;
- halfedges.insert(iLeft, new Halfedge(edge, cell.point, null));
- nHalfedges++;
- if ( lastBorderSegment ) { break; }
- va = vb;
- // fall through
- // walk rightward along bottom side
- lastBorderSegment = this.equalWithepsilon(vz.y,yb);
- vb = this.createVertex(lastBorderSegment ? vz.x : xr, yb);
- edge = this.createBorderEdge(cell.point, va, vb);
- iLeft++;
- halfedges.insert(iLeft, new Halfedge(edge, cell.point, null));
- nHalfedges++;
- if ( lastBorderSegment ) { break; }
- va = vb;
- // fall through
- // walk upward along right side
- lastBorderSegment = this.equalWithepsilon(vz.x,xr);
- vb = this.createVertex(xr, lastBorderSegment ? vz.y : yt);
- edge = this.createBorderEdge(cell.point, va, vb);
- iLeft++;
- halfedges.insert(iLeft, new Halfedge(edge, cell.point, null));
- nHalfedges++;
- if ( lastBorderSegment ) { break; }
- // fall through
- }
- throw "Voronoi.closeCells() > this makes no sense!";
- } while (false);
- }
- iLeft++;
- }
- cell.closeMe = false;
- }
- }
- // ---------------------------------------------------------------------------
- // Top-level Fortune loop
- static function sortByXY(a:Point, b:Point) {
- var r = b.y - a.y;
- return r < 0 ? -1 : (r > 0 ? 1 : (b.x > a.x ? 1 : b.x < a.x ? -1 : 0));
- }
- // rhill 2011-05-19:
- // Voronoi points are kept client-side now, to allow
- // user to freely modify content. At compute time,
- // *references* to points are copied locally.
- /**
- Compute the Voronoi diagram based on given list of points and bounding box.
- **/
- public function compute(points:Array<Point>, bbox:Bounds) {
- // to measure execution time
- var startTime = haxe.Timer.stamp();
- // init internal state
- this.reset();
- // Initialize point event queue
- var pointEvents = points.slice(0);
- pointEvents.sort(sortByXY);
- // process queue
- var point = pointEvents.pop(),
- pointid = 0,
- xpointx = Math.NEGATIVE_INFINITY, // to avoid duplicate points
- xpointy = Math.NEGATIVE_INFINITY,
- cells = this.cells,
- circle;
- // main loop
- while( true ) {
- // we need to figure whether we handle a point or circle event
- // for this we find out if there is a point event and it is
- // 'earlier' than the circle event
- circle = this.firstCircleEvent;
- // add beach section
- if (point != null && (circle == null || point.y < circle.y || (point.y == circle.y && point.x < circle.x))) {
- // only if point is not a duplicate
- if (point.x != xpointx || point.y != xpointy) {
- // first create cell for new point
- var c = new Cell(pointid, point);
- cells[pointid] = c;
- pointCell.set(point, c);
- pointid++;
- // then create a beachsection for that point
- this.addBeachsection(point);
- // remember last point coords to detect duplicate
- xpointy = point.y;
- xpointx = point.x;
- }
- point = pointEvents.pop();
- }
- // remove beach section
- else if (circle != null) {
- this.removeBeachsection(circle.arc);
- }
- // all done, quit
- else
- break;
- }
- // wrapping-up:
- // connect dangling edges to bounding box
- // cut edges as per bounding box
- // discard edges completely outside bounding box
- // discard edges which are point-like
- this.clipEdges(bbox);
- // add missing edges in order to close opened cells
- this.closeCells(bbox);
- var eid = 0;
- for( e in edges ) {
- e.id = eid++;
- e.lCell = e.lPoint == null ? null : pointCell.get(e.lPoint);
- e.rCell = e.rPoint == null ? null : pointCell.get(e.rPoint);
- }
- // to measure execution time
- var stopTime = haxe.Timer.stamp();
- // prepare return values
- var diagram = new Diagram();
- diagram.cells = this.cells;
- diagram.edges = this.edges;
- diagram.points = this.vertices;
- diagram.execTime = stopTime - startTime;
- // clean up
- this.reset();
- return diagram;
- }
- }
|