Parcourir la source

ported from https://github.com/gorhill/Javascript-Voronoi

ncannasse il y a 12 ans
Parent
commit
a1c3e08462
1 fichiers modifiés avec 1486 ajouts et 0 suppressions
  1. 1486 0
      h3d/col/Voronoi.hx

+ 1486 - 0
h3d/col/Voronoi.hx

@@ -0,0 +1,1486 @@
+/*
+	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 h3d.col;
+
+// ---------------------------------------------------------------------------
+// Red-Black tree code (based on C version of "rbtree" by Franck Bui-Huu
+// https://github.com/fbuihuu/libtree/blob/master/rb.c
+
+class Bounds {
+	public var xl : Float;
+	public var xr : Float;
+	public var yt : Float;
+	public var yb : Float;
+	public function new(xl, xr, yt, yb) {
+		this.xl = xl;
+		this.xr = xr;
+		this.yt = yt;
+		this.yb = yb;
+	}
+}
+
+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 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;
+    }
+}
+
+class Site {
+	public var voronoiId : Int;
+	public var x : Float;
+	public var y : Float;
+	public function new(x, y) {
+		this.x = x;
+		this.y = y;
+	}
+}
+
+class Cell {
+	
+	public var site : Site;
+	public var halfedges : Array<Halfedge>;
+	
+	public function new(site) {
+		this.site = site;
+		this.halfedges = [];
+    }
+
+	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);
+	}
+
+	// Return a list of the neighbor Ids
+	public function getNeighborIds() {
+		var neighbors = [],
+			iHalfedge = this.halfedges.length,
+			edge;
+		while (iHalfedge-- != 0){
+			edge = this.halfedges[iHalfedge].edge;
+			if (edge.lSite != null && edge.lSite.voronoiId != this.site.voronoiId) {
+				neighbors.push(edge.lSite.voronoiId);
+				}
+			else if (edge.rSite != null && edge.rSite.voronoiId != this.site.voronoiId){
+				neighbors.push(edge.rSite.voronoiId);
+				}
+			}
+		return neighbors;
+    }
+
+	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
+		};
+    }
+
+	// Return whether a point is inside, on, or outside the cell:
+	//   -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, y) {
+		// 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;
+	}
+	
+}
+
+class Vertex {
+
+	public var x : Float;
+	public var y : Float;
+	
+	public function new(x, y) {
+		this.x = x;
+		this.y = y;
+    }
+	
+}
+
+class Edge {
+	
+	public var lSite : Site;
+	public var rSite : Site;
+	public var va : Null<Vertex>;
+	public var vb : Null<Vertex>;
+	
+	public function new(lSite, rSite) {
+		this.lSite = lSite;
+		this.rSite = rSite;
+		this.va = this.vb = null;
+    }
+}
+
+
+class Halfedge {
+	
+	public var site : Site;
+	public var edge : Edge;
+	public var angle : Float;
+	
+	public function new(edge, lSite:Site, rSite:Site) {
+		this.site = lSite;
+		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 'site to the left'
+		// to the 'site to the right'.
+		// However, border edges have no 'site 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 (rSite != null) {
+			this.angle = Math.atan2(rSite.y-lSite.y, rSite.x-lSite.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.lSite == lSite
+				? Math.atan2(vb.x-va.x, va.y-vb.y)
+				: Math.atan2(va.x-vb.x, vb.y-va.y);
+		}
+	}
+		
+	public function getStartpoint() {
+		return this.edge.lSite == this.site ? this.edge.va : this.edge.vb;
+    }
+
+	public function getEndpoint() {
+		return this.edge.lSite == this.site ? this.edge.vb : this.edge.va;
+    }
+
+}
+
+class Beachsection extends RBNode<Beachsection> {
+	public var site : Site;
+	public var edge : Edge;
+	public var circleEvent : CircleEvent;
+	public function new() {
+	}
+}
+
+class CircleEvent extends RBNode<CircleEvent> {
+	public var site : Site;
+	public var arc : Beachsection;
+	public var x : Float;
+	public var y : Float;
+	public var ycenter : Float;
+	public function new() {
+	}
+}
+
+class Voronoi {
+	
+	var beachline : RBTree<Beachsection>;
+	var vertices : Array<Vertex>;
+	var edges : Array<Edge>;
+	var cells : Array<Cell>;
+	var beachsectionJunkyard : Array<Beachsection>;
+	var circleEventJunkyard : Array<CircleEvent>;
+	var circleEvents : RBTree<CircleEvent>;
+	var firstCircleEvent : CircleEvent;
+
+	public function new() {
+		this.vertices = null;
+		this.edges = null;
+		this.cells = null;
+		this.beachsectionJunkyard = [];
+		this.circleEventJunkyard = [];
+    }
+
+	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>();
+			}
+		this.circleEvents.root = this.firstCircleEvent = null;
+		this.vertices = [];
+		this.edges = [];
+		this.cells = [];
+	}
+
+	static inline var EPSILON = 1e-9;
+	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 Vertex(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 site's counterclockwise array
+	// of halfedges.
+
+	function createEdge(lSite, rSite, va = null, vb = null) {
+		var edge = new Edge(lSite, rSite);
+		this.edges.push(edge);
+		if (va != null) {
+			this.setEdgeStartpoint(edge, lSite, rSite, va);
+			}
+		if (vb != null) {
+			this.setEdgeEndpoint(edge, lSite, rSite, vb);
+			}
+		this.cells[lSite.voronoiId].halfedges.push(new Halfedge(edge, lSite, rSite));
+		this.cells[rSite.voronoiId].halfedges.push(new Halfedge(edge, rSite, lSite));
+		return edge;
+	}
+
+	function createBorderEdge(lSite, va, vb) {
+		var edge = new Edge(lSite, null);
+		edge.va = va;
+		edge.vb = vb;
+		this.edges.push(edge);
+		return edge;
+    }
+
+	function setEdgeStartpoint(edge:Edge, lSite, rSite, vertex) {
+		if (edge.va == null && edge.vb == null) {
+			edge.va = vertex;
+			edge.lSite = lSite;
+			edge.rSite = rSite;
+			}
+		else if (edge.lSite == rSite) {
+			edge.vb = vertex;
+			}
+		else {
+			edge.va = vertex;
+			}
+    }
+
+	function setEdgeEndpoint(edge, lSite, rSite, vertex) {
+		this.setEdgeStartpoint(edge, rSite, lSite, vertex);
+    }
+	
+
+	// rhill 2011-06-02: A lot of Beachsection instanciations
+	// occur during the computation of the Voronoi diagram,
+	// somewhere between the number of sites and twice the
+	// number of sites, 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(site:Site) {
+		var beachsection = this.beachsectionJunkyard.pop();
+		if ( beachsection == null )
+			beachsection = new Beachsection();
+		beachsection.site = site;
+		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 site = arc.site,
+			rfocx = site.x,
+			rfocy = site.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;
+			}
+		site = lArc.site;
+		var lfocx = site.x,
+			lfocy = site.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 site = arc.site;
+		return site.y == directrix ? site.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)<1e-9 && abs(y-lArc.circleEvent.ycenter)<1e-9) {
+			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' site 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)<1e-9 && abs(y-rArc.circleEvent.ycenter)<1e-9) {
+			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.site, rArc.site, 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 site
+		// on the left)
+		lArc = disappearingTransitions[0];
+		rArc = disappearingTransitions[nArcs-1];
+		rArc.edge = this.createEdge(lArc.site, rArc.site, 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(site:Site) {
+		var x = site.x,
+			directrix = site.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 > 1e-9) {
+				// 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 > 1e-9) {
+					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 > -1e-9) {
+						lArc = node.rbPrevious;
+						rArc = node;
+						}
+					// x equalWithEpsilon xr => falls exactly on the right edge of the beachsection
+					else if (dxr > -1e-9) {
+						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 site and add it to RB-tree
+		var newArc = this.createBeachsection(site);
+		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.site);
+			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.site, newArc.site);
+
+			// 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.site,newArc.site);
+			return;
+			}
+
+		// [null,rArc]
+		// impossible case: because sites 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 sites
+			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 lSite = lArc.site,
+				ax = lSite.x,
+				ay = lSite.y,
+				bx=site.x-ax,
+				by=site.y-ay,
+				rSite = rArc.site,
+				cx=rSite.x-ax,
+				cy=rSite.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, lSite, rSite, vertex);
+
+			// two new transitions appear at the new vertex location
+			newArc.edge = this.createEdge(lSite, site, null, vertex);
+			rArc.edge = this.createEdge(site, rSite, 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 lSite = lArc.site,
+			cSite = arc.site,
+			rSite = rArc.site;
+
+		// If site of left beachsection is same as site of
+		// right beachsection, there can't be convergence
+		if (lSite==rSite) {return;}
+
+		// Find the circumscribed circle for the three sites 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 cSite 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 = cSite.x,
+			by = cSite.y,
+			ax = lSite.x-bx,
+			ay = lSite.y-by,
+			cx = rSite.x-bx,
+			cy = rSite.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.site = cSite;
+		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) {
+		// 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.xl,
+			xr = bbox.xr,
+			yt = bbox.yt,
+			yb = bbox.yb,
+			lSite = edge.lSite,
+			rSite = edge.rSite,
+			lx = lSite.x,
+			ly = lSite.y,
+			rx = rSite.x,
+			ry = rSite.y,
+			fx = (lx+rx)/2,
+			fy = (ly+ry)/2,
+			fm = 0., fb = 0.;
+
+		// 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 site):
+		// 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 = this.createVertex(fx, yt);
+					}
+				else if (va.y >= yb) {
+					return false;
+					}
+				vb = this.createVertex(fx, yb);
+				}
+			// upward
+			else {
+				if (va == null) {
+					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 = 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 = 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 = 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 = 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.xl;
+		if (dx==0 && q<0) {return false;}
+		var r = -q/dx;
+		if (dx<0) {
+			if (r<t0) {return false;}
+			else if (r<t1) {t1=r;}
+			}
+		else if (dx>0) {
+			if (r>t1) {return false;}
+			else if (r>t0) {t0=r;}
+			}
+		// right
+		q = bbox.xr-ax;
+		if (dx==0 && q<0) {return false;}
+		r = q/dx;
+		if (dx<0) {
+			if (r>t1) {return false;}
+			else if (r>t0) {t0=r;}
+			}
+		else if (dx>0) {
+			if (r<t0) {return false;}
+			else if (r<t1) {t1=r;}
+			}
+		// top
+		q = ay-bbox.yt;
+		if (dy==0 && q<0) {return false;}
+		r = -q/dy;
+		if (dy<0) {
+			if (r<t0) {return false;}
+			else if (r<t1) {t1=r;}
+			}
+		else if (dy>0) {
+			if (r>t1) {return false;}
+			else if (r>t0) {t0=r;}
+			}
+		// bottom
+		q = bbox.yb-ay;
+		if (dy==0 && q<0) {return false;}
+		r = q/dy;
+		if (dy<0) {
+			if (r>t1) {return false;}
+			else if (r>t0) {t0=r;}
+			}
+		else if (dy>0) {
+			if (r<t0) {return false;}
+			else 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);
+			}
+
+		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)<1e-9 && abs(edge.va.y-edge.vb.y)<1e-9)) {
+				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 site, and a list
+	// of halfedges ordered counterclockwise.
+	function closeCells(bbox) {
+		// prune, order halfedges, then add missing ones
+		// required to close cells
+		var xl = bbox.xl,
+			xr = bbox.xr,
+			yt = bbox.yt,
+			yb = bbox.yb,
+			cells = this.cells,
+			iCell = cells.length,
+			cell,
+			iLeft, iRight,
+			halfedges, nHalfedges,
+			edge,
+			startpoint, endpoint,
+			va, vb = null;
+
+		while (iCell-- != 0) {
+			cell = cells[iCell];
+			// trim non fully-defined halfedges and sort them counterclockwise
+			if (cell.prepare() == 0)
+				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 site, in which case, the viewport is the cell
+			// ...
+			// all other cases
+			iLeft = 0;
+			while (iLeft < nHalfedges) {
+				iRight = (iLeft+1) % nHalfedges;
+				endpoint = halfedges[iLeft].getEndpoint();
+				startpoint = halfedges[iRight].getStartpoint();
+				// if end point is not equal to start point, we need to add the missing
+				// halfedge(s) to close the cell
+				if (abs(endpoint.x-startpoint.x)>=1e-9 || abs(endpoint.y-startpoint.y)>=1e-9) {
+					// if we reach this point, cell needs to be closed by walking
+					// counterclockwise along the bounding box until it connects
+					// to next halfedge in the list
+					va = endpoint;
+					// walk downward along left side
+					if (this.equalWithEpsilon(endpoint.x,xl) && this.lessThanWithEpsilon(endpoint.y,yb)) {
+						vb = this.createVertex(xl, this.equalWithEpsilon(startpoint.x,xl) ? startpoint.y : yb);
+					}
+					// walk rightward along bottom side
+					else if (this.equalWithEpsilon(endpoint.y,yb) && this.lessThanWithEpsilon(endpoint.x,xr)) {
+						vb = this.createVertex(this.equalWithEpsilon(startpoint.y,yb) ? startpoint.x : xr, yb);
+					}
+					// walk upward along right side
+					else if (this.equalWithEpsilon(endpoint.x,xr) && this.greaterThanWithEpsilon(endpoint.y,yt)) {
+						vb = this.createVertex(xr, this.equalWithEpsilon(startpoint.x,xr) ? startpoint.y : yt);
+					}
+					// walk leftward along top side
+					else if (this.equalWithEpsilon(endpoint.y,yt) && this.greaterThanWithEpsilon(endpoint.x,xl)) {
+						vb = this.createVertex(this.equalWithEpsilon(startpoint.y,yt) ? startpoint.x : xl, yt);
+					}
+					edge = this.createBorderEdge(cell.site, va, vb);
+					halfedges.insert(iLeft+1, new Halfedge(edge, cell.site, null));
+					nHalfedges = halfedges.length;
+				}
+				iLeft++;
+			}
+		}
+	}
+
+	// ---------------------------------------------------------------------------
+	// Top-level Fortune loop
+
+	static function sortByXY(a:Site, b:Site) {
+		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 sites are kept client-side now, to allow
+	//   user to freely modify content. At compute time,
+	//   *references* to sites are copied locally.
+	public function compute(sites:Array<Site>, bbox:Bounds) {
+		// to measure execution time
+		var startTime = haxe.Timer.stamp();
+
+		// init internal state
+		this.reset();
+
+		// Initialize site event queue
+		var siteEvents = sites.slice(0);
+		siteEvents.sort(sortByXY);
+
+		// process queue
+		var site = siteEvents.pop(),
+			siteid = 0,
+			xsitex = Math.NEGATIVE_INFINITY, // to avoid duplicate sites
+			xsitey = Math.NEGATIVE_INFINITY,
+			cells = this.cells,
+			circle;
+
+		// main loop
+		while( true ) {
+			// we need to figure whether we handle a site or circle event
+			// for this we find out if there is a site event and it is
+			// 'earlier' than the circle event
+			circle = this.firstCircleEvent;
+
+			// add beach section
+			if (site != null && (circle == null || site.y < circle.y || (site.y == circle.y && site.x < circle.x))) {
+				// only if site is not a duplicate
+				if (site.x != xsitex || site.y != xsitey) {
+					// first create cell for new site
+					cells[siteid] = new Cell(site);
+					site.voronoiId = siteid++;
+					// then create a beachsection for that site
+					this.addBeachsection(site);
+					// remember last site coords to detect duplicate
+					xsitey = site.y;
+					xsitex = site.x;
+					}
+				site = siteEvents.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);
+
+		// to measure execution time
+		var stopTime = haxe.Timer.stamp();
+
+		// prepare return values
+		var diagram = {
+			cells : this.cells,
+			edges : this.edges,
+			vertices : this.vertices,
+			execTime : stopTime - startTime,
+		};
+
+		// clean up
+		this.reset();
+
+		return diagram;
+	}
+
+}