PageRenderTime 53ms CodeModel.GetById 23ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 1ms

/src/com/nodename/Delaunay/Voronoi.as

http://github.com/m0ose/ambient-pixel-action-script
ActionScript | 418 lines | 323 code | 51 blank | 44 comment | 42 complexity | 1560012374d5b85e19c46df404fd7801 MD5 | raw file
  1/*
  2 * The author of this software is Steven Fortune.  Copyright (c) 1994 by AT&T
  3 * Bell Laboratories.
  4 * Permission to use, copy, modify, and distribute this software for any
  5 * purpose without fee is hereby granted, provided that this entire notice
  6 * is included in all copies of any software which is or includes a copy
  7 * or modification of this software and in all copies of the supporting
  8 * documentation for such software.
  9 * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
 10 * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
 11 * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
 12 * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
 13 */
 14
 15
 16package com.nodename.Delaunay
 17{
 18	import com.nodename.geom.Circle;
 19	import com.nodename.geom.LineSegment;
 20	
 21	import flash.display.BitmapData;
 22	import flash.geom.Point;
 23	import flash.geom.Rectangle;
 24	import flash.utils.Dictionary;
 25	
 26	public final class Voronoi
 27	{
 28		private var _sites:SiteList;
 29		private var _sitesIndexedByLocation:Dictionary;
 30		public var _triangles:Vector.<Triangle>;
 31		private var _edges:Vector.<Edge>;
 32
 33		
 34		// TODO generalize this so it doesn't have to be a rectangle;
 35		// then we can make the fractal voronois-within-voronois
 36		private var _plotBounds:Rectangle;
 37		public function get plotBounds():Rectangle
 38		{
 39			return _plotBounds;
 40		}
 41		
 42		public function dispose():void
 43		{
 44			var i:int, n:int;
 45			if (_sites)
 46			{
 47				_sites.dispose();
 48				_sites = null;
 49			}
 50			if (_triangles)
 51			{
 52				n = _triangles.length;
 53				for (i = 0; i < n; ++i)
 54				{
 55					_triangles[i].dispose();
 56				}
 57				_triangles.length = 0;
 58				_triangles = null;
 59			}
 60			if (_edges)
 61			{
 62				n = _edges.length;
 63				for (i = 0; i < n; ++i)
 64				{
 65					_edges[i].dispose();
 66				}
 67				_edges.length = 0;
 68				_edges = null;
 69			}
 70			_plotBounds = null;
 71			_sitesIndexedByLocation = null;
 72		}
 73		
 74		public function Voronoi(points:Vector.<Point>, colors:Vector.<uint>, plotBounds:Rectangle)
 75		{
 76			_sites = new SiteList();
 77			_sitesIndexedByLocation = new Dictionary(true);
 78			addSites(points, colors);
 79			_plotBounds = plotBounds;
 80			_triangles = new Vector.<Triangle>();
 81			_edges = new Vector.<Edge>();
 82			fortunesAlgorithm();
 83		}
 84		
 85		private function addSites(points:Vector.<Point>, colors:Vector.<uint>):void
 86		{
 87			var length:uint = points.length;
 88			for (var i:uint = 0; i < length; ++i)
 89			{
 90				addSite(points[i], colors ? colors[i] : 0, i);
 91			}
 92		}
 93		
 94		private function addSite(p:Point, color:uint, index:int):void
 95		{
 96			var weight:Number = Math.random() * 100;
 97			var site:Site = Site.create(p, index, weight, color);
 98			_sites.push(site);
 99			_sitesIndexedByLocation[p] = site;
100		}
101		
102		public function region(p:Point):Vector.<Point>
103		{
104			var site:Site = _sitesIndexedByLocation[p];
105			if (!site)
106			{
107				return new Vector.<Point>();
108			}
109			return site.region(_plotBounds);
110		}
111		
112		public function neighborSitesForSite(coord:Point):Vector.<Point>
113		{
114			var points:Vector.<Point> = new Vector.<Point>();
115			var site:Site = _sitesIndexedByLocation[coord];
116			if (!site)
117			{
118				return points;
119			}
120			var sites:Vector.<Site> = site.neighborSites();
121			var neighbor:Site;
122			for each (neighbor in sites)
123			{
124				points.push(neighbor.coord);
125			}
126			return points;
127		}
128
129		public function circles():Vector.<Circle>
130		{
131			return _sites.circles();
132		}
133		
134		public function voronoiBoundaryForSite(coord:Point):Vector.<LineSegment>
135		{
136			return visibleLineSegments(selectEdgesForSitePoint(coord, _edges));
137		}
138
139		public function delaunayLinesForSite(coord:Point):Vector.<LineSegment>
140		{
141			return delaunayLinesForEdges(selectEdgesForSitePoint(coord, _edges));
142		}
143		
144		public function voronoiDiagram():Vector.<LineSegment>
145		{
146			return visibleLineSegments(_edges);
147		}
148		
149		public function delaunayTriangulation(keepOutMask:BitmapData = null):Vector.<LineSegment>
150		{
151			return delaunayLinesForEdges(selectNonIntersectingEdges(keepOutMask, _edges));
152		}
153		
154		public function hull():Vector.<LineSegment>
155		{
156			return delaunayLinesForEdges(hullEdges());
157		}
158		
159		private function hullEdges():Vector.<Edge>
160		{
161			return _edges.filter(myTest);
162		
163			function myTest(edge:Edge, index:int, vector:Vector.<Edge>):Boolean
164			{
165				return (edge.isPartOfConvexHull());
166			}
167		}
168
169		public function hullPointsInOrder():Vector.<Point>
170		{
171			var hullEdges:Vector.<Edge> = hullEdges();
172			
173			var points:Vector.<Point> = new Vector.<Point>();
174			if (hullEdges.length == 0)
175			{
176				return points;
177			}
178			
179			var reorderer:EdgeReorderer = new EdgeReorderer(hullEdges, Site);
180			hullEdges = reorderer.edges;
181			var orientations:Vector.<LR> = reorderer.edgeOrientations;
182			reorderer.dispose();
183			
184			var orientation:LR;
185
186			var n:int = hullEdges.length;
187			for (var i:int = 0; i < n; ++i)
188			{
189				var edge:Edge = hullEdges[i];
190				orientation = orientations[i];
191				points.push(edge.site(orientation).coord);
192			}
193			return points;
194		}
195		
196		public function spanningTree(type:String = "minimum", keepOutMask:BitmapData = null):Vector.<LineSegment>
197		{
198			var edges:Vector.<Edge> = selectNonIntersectingEdges(keepOutMask, _edges);
199			var segments:Vector.<LineSegment> = delaunayLinesForEdges(edges);
200			return kruskal(segments, type);
201		}
202
203		public function regions():Vector.<Vector.<Point>>
204		{
205			return _sites.regions(_plotBounds);
206		}
207		
208		public function siteColors(referenceImage:BitmapData = null):Vector.<uint>
209		{
210			return _sites.siteColors(referenceImage);
211		}
212		
213		/**
214		 * 
215		 * @param proximityMap a BitmapData whose regions are filled with the site index values; see PlanePointsCanvas::fillRegions()
216		 * @param x
217		 * @param y
218		 * @return coordinates of nearest Site to (x, y)
219		 * 
220		 */
221		public function nearestSitePoint(proximityMap:BitmapData, x:Number, y:Number):Point
222		{
223			return _sites.nearestSitePoint(proximityMap, x, y);
224		}
225		
226		public function siteCoords():Vector.<Point>
227		{
228			return _sites.siteCoords();
229		}
230
231		private function fortunesAlgorithm():void
232		{
233			var newSite:Site, bottomSite:Site, topSite:Site, tempSite:Site;
234			var v:Vertex, vertex:Vertex;
235			var newintstar:Point;
236			var leftRight:LR;
237			var lbnd:Halfedge, rbnd:Halfedge, llbnd:Halfedge, rrbnd:Halfedge, bisector:Halfedge;
238			var edge:Edge;
239			
240			var dataBounds:Rectangle = _sites.getSitesBounds();
241			
242			var sqrt_nsites:int = int(Math.sqrt(_sites.length + 4));
243			var heap:HalfedgePriorityQueue = new HalfedgePriorityQueue(dataBounds.y, dataBounds.height, sqrt_nsites);
244			var edgeList:EdgeList = new EdgeList(dataBounds.x, dataBounds.width, sqrt_nsites);
245			var halfEdges:Vector.<Halfedge> = new Vector.<Halfedge>();
246			var vertices:Vector.<Vertex> = new Vector.<Vertex>();
247			
248			var bottomMostSite:Site = _sites.next();
249			newSite = _sites.next();
250			
251			for (;;)
252			{
253				if (heap.empty() == false)
254				{
255					newintstar = heap.min();
256				}
257			
258				if (newSite != null 
259				&&  (heap.empty() || compareByYThenX(newSite, newintstar) < 0))
260				{
261					/* new site is smallest */
262					//trace("smallest: new site " + newSite);
263					
264					// Step 8:
265					lbnd = edgeList.edgeListLeftNeighbor(newSite.coord);	// the Halfedge just to the left of newSite
266					//trace("lbnd: " + lbnd);
267					rbnd = lbnd.edgeListRightNeighbor;		// the Halfedge just to the right
268					//trace("rbnd: " + rbnd);
269					bottomSite = rightRegion(lbnd);		// this is the same as leftRegion(rbnd)
270					// this Site determines the region containing the new site
271					//trace("new Site is in region of existing site: " + bottomSite);
272					
273					// Step 9:
274					edge = Edge.createBisectingEdge(bottomSite, newSite);
275					//trace("new edge: " + edge);
276					_edges.push(edge);
277					
278					bisector = Halfedge.create(edge, LR.LEFT);
279					halfEdges.push(bisector);
280					// inserting two Halfedges into edgeList constitutes Step 10:
281					// insert bisector to the right of lbnd:
282					edgeList.insert(lbnd, bisector);
283					
284					// first half of Step 11:
285					if ((vertex = Vertex.intersect(lbnd, bisector)) != null) 
286					{
287						vertices.push(vertex);
288						heap.remove(lbnd);
289						lbnd.vertex = vertex;
290						lbnd.ystar = vertex.y + newSite.dist(vertex);
291						heap.insert(lbnd);
292					}
293					
294					lbnd = bisector;
295					bisector = Halfedge.create(edge, LR.RIGHT);
296					halfEdges.push(bisector);
297					// second Halfedge for Step 10:
298					// insert bisector to the right of lbnd:
299					edgeList.insert(lbnd, bisector);
300					
301					// second half of Step 11:
302					if ((vertex = Vertex.intersect(bisector, rbnd)) != null)
303					{
304						vertices.push(vertex);
305						bisector.vertex = vertex;
306						bisector.ystar = vertex.y + newSite.dist(vertex);
307						heap.insert(bisector);	
308					}
309					
310					newSite = _sites.next();	
311				}
312				else if (heap.empty() == false) 
313				{
314					/* intersection is smallest */
315					lbnd = heap.extractMin();
316					llbnd = lbnd.edgeListLeftNeighbor;
317					rbnd = lbnd.edgeListRightNeighbor;
318					rrbnd = rbnd.edgeListRightNeighbor;
319					bottomSite = leftRegion(lbnd);
320					topSite = rightRegion(rbnd);
321					// these three sites define a Delaunay triangle
322					// (not actually using these for anything...)
323					_triangles.push(new Triangle(bottomSite, topSite, rightRegion(lbnd)));
324					
325					v = lbnd.vertex;
326					v.setIndex();
327					lbnd.edge.setVertex(lbnd.leftRight, v);
328					rbnd.edge.setVertex(rbnd.leftRight, v);
329					edgeList.remove(lbnd); 
330					heap.remove(rbnd);
331					edgeList.remove(rbnd); 
332					leftRight = LR.LEFT;
333					if (bottomSite.y > topSite.y)
334					{
335						tempSite = bottomSite; bottomSite = topSite; topSite = tempSite; leftRight = LR.RIGHT;
336					}
337					edge = Edge.createBisectingEdge(bottomSite, topSite);
338					_edges.push(edge);
339					bisector = Halfedge.create(edge, leftRight);
340					halfEdges.push(bisector);
341					edgeList.insert(llbnd, bisector);
342					edge.setVertex(LR.other(leftRight), v);
343					if ((vertex = Vertex.intersect(llbnd, bisector)) != null)
344					{
345						vertices.push(vertex);
346						heap.remove(llbnd);
347						llbnd.vertex = vertex;
348						llbnd.ystar = vertex.y + bottomSite.dist(vertex);
349						heap.insert(llbnd);
350					}
351					if ((vertex = Vertex.intersect(bisector, rrbnd)) != null)
352					{
353						vertices.push(vertex);
354						bisector.vertex = vertex;
355						bisector.ystar = vertex.y + bottomSite.dist(vertex);
356						heap.insert(bisector);
357					}
358				}
359				else
360				{
361					break;
362				}
363			}
364			
365			// heap should be empty now
366			heap.dispose();
367			edgeList.dispose();
368			
369			for each (var halfEdge:Halfedge in halfEdges)
370			{
371				halfEdge.reallyDispose();
372			}
373			halfEdges.length = 0;
374			
375			// we need the vertices to clip the edges
376			for each (edge in _edges)
377			{
378				edge.clipVertices(_plotBounds);
379			}
380			// but we don't actually ever use them again!
381			for each (vertex in vertices)
382			{
383				vertex.dispose();
384			}
385			vertices.length = 0;
386			
387			function leftRegion(he:Halfedge):Site
388			{
389				var edge:Edge = he.edge;
390				if (edge == null)
391				{
392					return bottomMostSite;
393				}
394				return edge.site(he.leftRight);
395			}
396			
397			function rightRegion(he:Halfedge):Site
398			{
399				var edge:Edge = he.edge;
400				if (edge == null)
401				{
402					return bottomMostSite;
403				}
404				return edge.site(LR.other(he.leftRight));
405			}
406		}
407
408		internal static function compareByYThenX(s1:Site, s2:*):Number
409		{
410			if (s1.y < s2.y) return -1;
411			if (s1.y > s2.y) return 1;
412			if (s1.x < s2.x) return -1;
413			if (s1.x > s2.x) return 1;
414			return 0;
415		}
416
417	}
418}