/src/com/nodename/Delaunay/Voronoi.as
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}