ソースを参照

completed Voronoi

ncannasse 12 年 前
コミット
aa99aed908
4 ファイル変更197 行追加93 行削除
  1. 111 0
      h3d/col/Bounds2d.hx
  2. 13 0
      h3d/col/Point2d.hx
  3. 4 4
      h3d/col/Seg2d.hx
  4. 69 89
      h3d/col/Voronoi.hx

+ 111 - 0
h3d/col/Bounds2d.hx

@@ -0,0 +1,111 @@
+package h3d.col;
+
+class Bounds2d {
+	
+	public var xMin : Float;
+	public var yMin : Float;
+
+	public var xMax : Float;
+	public var yMax : Float;
+	
+	public inline function new() {
+		empty();
+	}
+	
+	public inline function collide( b : Bounds2d ) {
+		return !(xMin > b.xMax || yMin > b.yMax || xMax < b.xMin || yMax < b.yMin);
+	}
+	
+	public inline function add( b : Bounds2d ) {
+		if( b.xMin < xMin ) xMin = b.xMin;
+		if( b.xMax > xMax ) xMax = b.xMax;
+		if( b.yMin < yMin ) yMin = b.yMin;
+		if( b.yMax > yMax ) yMax = b.yMax;
+	}
+
+	public inline function addPoint( p : Point2d ) {
+		if( p.x < xMin ) xMin = p.x;
+		if( p.x > xMax ) xMax = p.x;
+		if( p.y < yMin ) yMin = p.y;
+		if( p.y > yMax ) yMax = p.y;
+	}
+	
+	public inline function setMin( p : Point2d ) {
+		xMin = p.x;
+		yMin = p.y;
+	}
+
+	public inline function setMax( p : Point2d ) {
+		xMax = p.x;
+		yMax = p.y;
+	}
+	
+	public function load( b : Bounds2d ) {
+		xMin = b.xMin;
+		yMin = b.yMin;
+		xMax = b.xMax;
+		yMax = b.yMax;
+	}
+	
+	public function scaleCenter( v : Float ) {
+		var dx = (xMax - xMin) * 0.5 * v;
+		var dy = (yMax - yMin) * 0.5 * v;
+		var mx = (xMax + xMin) * 0.5;
+		var my = (yMax + yMin) * 0.5;
+		xMin = mx - dx * v;
+		yMin = my - dy * v;
+		xMax = mx + dx * v;
+		yMax = my + dy * v;
+	}
+	
+	public inline function getMin() {
+		return new Point2d(xMin, yMin);
+	}
+	
+	public inline function getCenter() {
+		return new Point2d((xMin + xMax) * 0.5, (yMin + yMax) * 0.5);
+	}
+
+	public inline function getSize() {
+		return new Point2d(xMax - xMin, yMax - yMin);
+	}
+	
+	public inline function getMax() {
+		return new Point2d(xMax, yMax);
+	}
+	
+	public inline function empty() {
+		xMin = 1e20;
+		yMin = 1e20;
+		xMax = -1e20;
+		yMax = -1e20;
+	}
+
+	public inline function all() {
+		xMin = -1e20;
+		yMin = -1e20;
+		xMax = 1e20;
+		yMax = 1e20;
+	}
+	
+	public inline function clone() {
+		var b = new Bounds2d();
+		b.xMin = xMin;
+		b.yMin = yMin;
+		b.xMax = xMax;
+		b.yMax = yMax;
+		return b;
+	}
+	
+	public function toString() {
+		return "{" + getMin() + "," + getMax() + "}";
+	}
+	
+	public static inline function fromPoints( min : Point2d, max : Point2d ) {
+		var b = new Bounds2d();
+		b.setMin(min);
+		b.setMax(max);
+		return b;
+	}
+	
+}

+ 13 - 0
h3d/col/Point2d.hx

@@ -0,0 +1,13 @@
+package h3d.col;
+
+class Point2d {
+	
+	public var x : Float;
+	public var y : Float;
+	
+	public inline function new(x = 0., y = 0.) {
+		this.x = x;
+		this.y = y;
+	}
+	
+}

+ 4 - 4
h3d/col/Seg2d.hx

@@ -9,7 +9,7 @@ class Seg2d {
 	public var lenSq : Float;
 	public var invLenSq : Float;
 	
-	public inline function new( p1 : Vector, p2 : Vector ) {
+	public inline function new( p1 : Point2d, p2 : Point2d ) {
 		x = p1.x;
 		y = p1.y;
 		dx = p2.x - x;
@@ -18,11 +18,11 @@ class Seg2d {
 		invLenSq = 1 / lenSq;
 	}
 	
-	public inline function side( p : h3d.Vector ) {
+	public inline function side( p : Point2d ) {
 		return dx * (p.y - y) - dy * (p.x - x);
 	}
 	
-	public inline function distanceSq( p : h3d.Vector ) {
+	public inline function distanceSq( p : Point2d ) {
 		var px = p.x - x;
 		var py = p.y - y;
 		var t = px * dx + py * dy;
@@ -40,7 +40,7 @@ class Seg2d {
 		}
 	}
 
-	public inline function distance( p : h3d.Vector ) {
+	public inline function distance( p : Point2d ) {
 		return FMath.sqrt(distanceSq(p));
 	}
 

+ 69 - 89
h3d/col/Voronoi.hx

@@ -21,20 +21,7 @@ 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>> {
+private class RBNode<T:RBNode<T>> {
 	public var rbRed : Bool;
 	public var rbLeft : T;
 	public var rbRight : T;
@@ -43,7 +30,7 @@ class RBNode<T:RBNode<T>> {
 	public var rbPrevious : T;
 }
 
-@:generic class RBTree<T:RBNode<T>> {
+@:generic private class RBTree<T:RBNode<T>> {
 	
 	public var root : T;
 	
@@ -331,22 +318,14 @@ class RBNode<T:RBNode<T>> {
     }
 }
 
-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 id : Int;
+	public var site : Point2d;
 	public var halfedges : Array<Halfedge>;
 	
-	public function new(site) {
+	public function new(id, site) {
+		this.id = id;
 		this.site = site;
 		this.halfedges = [];
     }
@@ -383,11 +362,12 @@ class Cell {
 			edge;
 		while (iHalfedge-- != 0){
 			edge = this.halfedges[iHalfedge].edge;
-			if (edge.lSite != null && edge.lSite.voronoiId != this.site.voronoiId) {
-				neighbors.push(edge.lSite.voronoiId);
+			// NC : changes siteId check to object == check
+			if (edge.lSite != null && edge.lSite != this.site) {
+				neighbors.push(edge.lSite);
 				}
-			else if (edge.rSite != null && edge.rSite.voronoiId != this.site.voronoiId){
-				neighbors.push(edge.rSite.voronoiId);
+			else if (edge.rSite != null && edge.rSite != this.site){
+				neighbors.push(edge.rSite);
 				}
 			}
 		return neighbors;
@@ -424,7 +404,7 @@ class Cell {
 	//    0: point is on the perimeter of the cell
 	//    1: point is inside the perimeter of the cell
 	//
-	public function pointIntersection(x, y) {
+	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/
@@ -457,24 +437,12 @@ class Cell {
 	
 }
 
-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 var lSite : Point2d;
+	public var rSite : Point2d;
+	public var va : Null<Point2d>;
+	public var vb : Null<Point2d>;
 	
 	public function new(lSite, rSite) {
 		this.lSite = lSite;
@@ -486,11 +454,11 @@ class Edge {
 
 class Halfedge {
 	
-	public var site : Site;
+	public var site : Point2d;
 	public var edge : Edge;
 	public var angle : Float;
 	
-	public function new(edge, lSite:Site, rSite:Site) {
+	public function new(edge, lSite:Point2d, rSite:Point2d) {
 		this.site = lSite;
 		this.edge = edge;
 		// 'angle' is a value to be used for properly sorting the
@@ -512,27 +480,36 @@ class Halfedge {
 				: Math.atan2(va.x-vb.x, vb.y-va.y);
 		}
 	}
-		
-	public function getStartpoint() {
+	
+	public inline function getStartpoint() {
 		return this.edge.lSite == this.site ? this.edge.va : this.edge.vb;
     }
 
-	public function getEndpoint() {
+	public inline function getEndpoint() {
 		return this.edge.lSite == this.site ? this.edge.vb : this.edge.va;
     }
 
 }
 
-class Beachsection extends RBNode<Beachsection> {
-	public var site : Site;
+class Diagram {
+	public var cells : Array<Cell>;
+	public var points : Array<Point2d>;
+	public var edges : Array<Edge>;
+	public var execTime : Float;
+	public function new() {
+	}
+}
+
+private class Beachsection extends RBNode<Beachsection> {
+	public var site : Point2d;
 	public var edge : Edge;
 	public var circleEvent : CircleEvent;
 	public function new() {
 	}
 }
 
-class CircleEvent extends RBNode<CircleEvent> {
-	public var site : Site;
+private class CircleEvent extends RBNode<CircleEvent> {
+	public var site : Point2d;
 	public var arc : Beachsection;
 	public var x : Float;
 	public var y : Float;
@@ -544,13 +521,14 @@ class CircleEvent extends RBNode<CircleEvent> {
 class Voronoi {
 	
 	var beachline : RBTree<Beachsection>;
-	var vertices : Array<Vertex>;
+	var vertices : Array<Point2d>;
 	var edges : Array<Edge>;
 	var cells : Array<Cell>;
 	var beachsectionJunkyard : Array<Beachsection>;
 	var circleEventJunkyard : Array<CircleEvent>;
 	var circleEvents : RBTree<CircleEvent>;
 	var firstCircleEvent : CircleEvent;
+	var siteCell : Map<Point2d,Cell>;
 
 	public function new() {
 		this.vertices = null;
@@ -575,6 +553,7 @@ class Voronoi {
 		if (this.circleEvents == null) {
 			this.circleEvents = new RBTree<CircleEvent>();
 			}
+		siteCell = new Map();
 		this.circleEvents.root = this.firstCircleEvent = null;
 		this.vertices = [];
 		this.edges = [];
@@ -590,7 +569,7 @@ class Voronoi {
 	inline function lessThanOrEqualWithEpsilon(a:Float,b:Float) return a-b<EPSILON;
 
 	function createVertex(x, y) {
-		var v = new Vertex(x, y);
+		var v = new Point2d(x, y);
 		this.vertices.push(v);
 		return v;
     }
@@ -608,8 +587,8 @@ class Voronoi {
 		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));
+		siteCell.get(lSite).halfedges.push(new Halfedge(edge, lSite, rSite));
+		siteCell.get(rSite).halfedges.push(new Halfedge(edge, rSite, lSite));
 		return edge;
 	}
 
@@ -649,7 +628,7 @@ class Voronoi {
 	// to avoid new memory allocation. This resulted in a measurable
 	// performance gain.
 
-	function createBeachsection(site:Site) {
+	function createBeachsection(site:Point2d) {
 		var beachsection = this.beachsectionJunkyard.pop();
 		if ( beachsection == null )
 			beachsection = new Beachsection();
@@ -816,7 +795,7 @@ class Voronoi {
 		this.attachCircleEvent(rArc);
     }
 
-	function addBeachsection(site:Site) {
+	function addBeachsection(site:Point2d) {
 		var x = site.x,
 			directrix = site.y;
 
@@ -1098,17 +1077,17 @@ class Voronoi {
 	// return value:
 	//   false: the dangling endpoint couldn't be connected
 	//   true: the dangling endpoint could be connected
-	function connectEdge(edge:Edge, bbox) {
+	function connectEdge(edge:Edge, bbox:Bounds2d) {
 		// 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,
+			xl = bbox.xMin,
+			xr = bbox.xMax,
+			yt = bbox.yMin,
+			yb = bbox.yMax,
 			lSite = edge.lSite,
 			rSite = edge.rSite,
 			lx = lSite.x,
@@ -1220,7 +1199,7 @@ class Voronoi {
 	//   http://www.skytopia.com/project/articles/compsci/clipping.html
 	// Thanks!
 	// A bit modified to minimize code paths
-	function clipEdge(edge:Edge, bbox:Bounds) {
+	function clipEdge(edge:Edge, bbox:Bounds2d) {
 		var ax = edge.va.x,
 			ay = edge.va.y,
 			bx = edge.vb.x,
@@ -1230,7 +1209,7 @@ class Voronoi {
 			dx = bx-ax,
 			dy = by-ay;
 		// left
-		var q = ax-bbox.xl;
+		var q = ax-bbox.xMin;
 		if (dx==0 && q<0) {return false;}
 		var r = -q/dx;
 		if (dx<0) {
@@ -1242,7 +1221,7 @@ class Voronoi {
 			else if (r>t0) {t0=r;}
 			}
 		// right
-		q = bbox.xr-ax;
+		q = bbox.xMax-ax;
 		if (dx==0 && q<0) {return false;}
 		r = q/dx;
 		if (dx<0) {
@@ -1254,7 +1233,7 @@ class Voronoi {
 			else if (r<t1) {t1=r;}
 			}
 		// top
-		q = ay-bbox.yt;
+		q = ay-bbox.yMin;
 		if (dy==0 && q<0) {return false;}
 		r = -q/dy;
 		if (dy<0) {
@@ -1266,7 +1245,7 @@ class Voronoi {
 			else if (r>t0) {t0=r;}
 			}
 		// bottom
-		q = bbox.yb-ay;
+		q = bbox.yMax-ay;
 		if (dy==0 && q<0) {return false;}
 		r = q/dy;
 		if (dy<0) {
@@ -1300,7 +1279,7 @@ class Voronoi {
 	}
 
 	// Connect/cut edges at bounding box
-	function clipEdges(bbox:Bounds) {
+	function clipEdges(bbox:Bounds2d) {
 		// connect all dangling edges to bounding box
 		// or get rid of them if it can't be done
 		var edges = this.edges,
@@ -1324,13 +1303,13 @@ class Voronoi {
 	// 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) {
+	function closeCells(bbox:Bounds2d) {
 		// prune, order halfedges, then add missing ones
 		// required to close cells
-		var xl = bbox.xl,
-			xr = bbox.xr,
-			yt = bbox.yt,
-			yb = bbox.yb,
+		var xl = bbox.xMin,
+			xr = bbox.xMax,
+			yt = bbox.yMin,
+			yb = bbox.yMax,
 			cells = this.cells,
 			iCell = cells.length,
 			cell,
@@ -1394,7 +1373,7 @@ class Voronoi {
 	// ---------------------------------------------------------------------------
 	// Top-level Fortune loop
 
-	static function sortByXY(a:Site, b:Site) {
+	static function sortByXY(a:Point2d, b:Point2d) {
 		var r = b.y - a.y;
 		return r < 0 ? -1 : (r > 0 ? 1 : (b.x > a.x ? 1 : b.x < a.x ? -1 : 0));
 	}
@@ -1403,7 +1382,7 @@ class Voronoi {
 	//   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) {
+	public function compute(sites:Array<Point2d>, bbox:Bounds2d) {
 		// to measure execution time
 		var startTime = haxe.Timer.stamp();
 
@@ -1434,8 +1413,10 @@ class Voronoi {
 				// 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++;
+					var c = new Cell(siteid, site);
+					cells[siteid] = c;
+					siteCell.set(site, c);
+					siteid++;
 					// then create a beachsection for that site
 					this.addBeachsection(site);
 					// remember last site coords to detect duplicate
@@ -1470,12 +1451,11 @@ class Voronoi {
 		var stopTime = haxe.Timer.stamp();
 
 		// prepare return values
-		var diagram = {
-			cells : this.cells,
-			edges : this.edges,
-			vertices : this.vertices,
-			execTime : stopTime - startTime,
-		};
+		var diagram = new Diagram();
+		diagram.cells = this.cells;
+		diagram.edges = this.edges;
+		diagram.points = this.vertices;
+		diagram.execTime = stopTime - startTime;
 
 		// clean up
 		this.reset();