├── Delaunay.meta ├── Delaunay ├── Edge.cs ├── Edge.cs.meta ├── EdgeList.cs ├── EdgeList.cs.meta ├── EdgeReorderer.cs ├── EdgeReorderer.cs.meta ├── Halfedge.cs ├── Halfedge.cs.meta ├── HalfedgePriorityQueue.cs ├── HalfedgePriorityQueue.cs.meta ├── ICoord.cs ├── ICoord.cs.meta ├── Site.cs ├── Site.cs.meta ├── Triangle.cs ├── Triangle.cs.meta ├── Vertex.cs ├── Vertex.cs.meta ├── Voronoi.cs └── Voronoi.cs.meta ├── Geom.meta ├── Geom ├── Circle.cs ├── Circle.cs.meta ├── LineSegment.cs ├── LineSegment.cs.meta ├── Polygon.cs ├── Polygon.cs.meta ├── Rectf.cs ├── Rectf.cs.meta ├── Vector2f.cs ├── Vector2f.cs.meta ├── Winding.cs └── Winding.cs.meta ├── README.md ├── README.md.meta ├── Tests.meta ├── Tests ├── CapacityTest.cs ├── CapacityTest.cs.meta ├── EdgeReordererTest.cs ├── EdgeReordererTest.cs.meta ├── GCAllocsTest.cs ├── GCAllocsTest.cs.meta ├── VoronoiTest.cs ├── VoronoiTest.cs.meta ├── csDelaunay.Tests.asmdef └── csDelaunay.Tests.asmdef.meta ├── csDelaunay.asmdef └── csDelaunay.asmdef.meta /Delaunay.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f286d6cd16d254f44bb5183828c665f9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Delaunay/Edge.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine.Profiling; 5 | 6 | namespace csDelaunay 7 | { 8 | 9 | /* 10 | * The line segment connecting the two Sites is part of the Delaunay triangulation 11 | * The line segment connecting the two Vertices is part of the Voronoi diagram 12 | */ 13 | public class Edge 14 | { 15 | static List all = new List(); 16 | 17 | #region Pool 18 | private static Queue unusedPool = new Queue(); 19 | public static int PoolCapacity { get => all.Count; } 20 | public bool disposed { get; private set; } 21 | 22 | public static void PoolDummies(int num) 23 | { 24 | all.Capacity = num; 25 | 26 | var dummies = new Edge[num]; 27 | for (int i = 0; i < num; i++) 28 | { 29 | dummies[i] = Create(); 30 | } 31 | 32 | for (int i = 0; i < num; i++) 33 | { 34 | dummies[i].Dispose(); 35 | } 36 | } 37 | 38 | /// 39 | /// Use only for testing 40 | /// 41 | public static void FlushUnused() 42 | { 43 | all = new List(); 44 | unusedPool = new Queue(); 45 | } 46 | 47 | public bool Clipped { get; private set; } 48 | 49 | private static int nEdges = 0; 50 | 51 | public static void DisposeAll() 52 | { 53 | for (int i = 0; i < all.Count; i++) 54 | { 55 | if (!all[i].disposed) 56 | { 57 | all[i].Dispose(); 58 | //UnityEngine.Debug.Log("Found undisposed Edge"); 59 | } 60 | } 61 | } 62 | 63 | /* 64 | * This is the only way to create a new Edge 65 | * @param site0 66 | * @param site1 67 | * @return 68 | */ 69 | public static Edge CreateBisectingEdge(Site s0, Site s1) 70 | { 71 | float dx, dy; 72 | float absdx, absdy; 73 | float a, b, c; 74 | 75 | dx = s1.x - s0.x; 76 | dy = s1.y - s0.y; 77 | absdx = dx > 0 ? dx : -dx; 78 | absdy = dy > 0 ? dy : -dy; 79 | c = s0.x * dx + s0.y * dy + (dx * dx + dy * dy) * 0.5f; 80 | 81 | if (absdx > absdy) 82 | { 83 | a = 1; 84 | b = dy / dx; 85 | c /= dx; 86 | } 87 | else 88 | { 89 | b = 1; 90 | a = dx / dy; 91 | c /= dy; 92 | } 93 | 94 | Profiler.BeginSample("Edge create"); 95 | Edge edge = Create(); // alloc 96 | Profiler.EndSample(); 97 | 98 | edge.LeftSite = s0; 99 | edge.RightSite = s1; 100 | Profiler.BeginSample("AddEdge"); 101 | s0.AddEdge(edge); // alloc 102 | s1.AddEdge(edge); // alloc 103 | Profiler.EndSample(); 104 | 105 | edge.a = a; 106 | edge.b = b; 107 | edge.c = c; 108 | 109 | return edge; 110 | } 111 | 112 | private static Edge Create() 113 | { 114 | //UnityEngine.Debug.Log("Pool size: " + pool.Count); 115 | 116 | Edge edge; 117 | if (unusedPool.Count > 0) 118 | { 119 | edge = unusedPool.Dequeue(); 120 | edge.disposed = false; 121 | edge.Init(); 122 | } 123 | else 124 | { 125 | edge = new Edge(); 126 | all.Add(edge); 127 | } 128 | 129 | return edge; 130 | } 131 | #endregion 132 | 133 | public static List SelectEdgesForSitePoint(Vector2f coord, List edgesToTest) 134 | { 135 | return edgesToTest.FindAll( 136 | delegate (Edge e) 137 | { 138 | if (e.LeftSite != null) 139 | { 140 | if (e.LeftSite.Coord == coord) return true; 141 | } 142 | if (e.RightSite != null) 143 | { 144 | if (e.RightSite.Coord == coord) return true; 145 | } 146 | return false; 147 | }); 148 | } 149 | 150 | public static readonly Edge DELETED = new Edge(); 151 | 152 | #region Object 153 | // The equation of the edge: ax + by = c 154 | public float a, b, c; 155 | 156 | // The two Voronoi vertices that the edge connects (if one of them is null, the edge extends to infinity) 157 | public Vertex LeftVertex { get; private set; } 158 | public Vertex RightVertex { get; private set; } 159 | 160 | public Vertex Vertex(bool leftRight) 161 | { 162 | return leftRight == false ? LeftVertex : RightVertex; 163 | } 164 | 165 | public void SetVertex(bool right, Vertex v) 166 | { 167 | if (right == false) 168 | { 169 | LeftVertex = v; 170 | } 171 | else 172 | { 173 | RightVertex = v; 174 | } 175 | } 176 | 177 | public bool IsPartOfConvexHull() 178 | { 179 | return LeftVertex == null || RightVertex == null; 180 | } 181 | 182 | public float SitesDistance() 183 | { 184 | return (LeftSite.Coord - RightSite.Coord).magnitude; 185 | } 186 | 187 | public static int CompareSitesDistances_MAX(Edge edge0, Edge edge1) 188 | { 189 | float length0 = edge0.SitesDistance(); 190 | float length1 = edge1.SitesDistance(); 191 | if (length0 < length1) 192 | { 193 | return 1; 194 | } 195 | if (length0 > length1) 196 | { 197 | return -1; 198 | } 199 | return 0; 200 | } 201 | 202 | public static int CompareSitesDistances(Edge edge0, Edge edge1) 203 | { 204 | return -CompareSitesDistances_MAX(edge0, edge1); 205 | } 206 | 207 | // Once clipVertices() is called, this array will hold two Points 208 | // representing the clipped coordinates of the left and the right ends... 209 | public Vector2f[] ClippedEnds { get; private set; } 210 | 211 | // The two input Sites for which this Edge is a bisector: 212 | private Site[] sites; 213 | public Site LeftSite { get { return sites[0]; } set { sites[0] = value; } } 214 | public Site RightSite { get { return sites[1]; } set { sites[1] = value; } } 215 | 216 | public Site Site(bool leftRight) 217 | { 218 | return sites[leftRight ? 1 : 0]; 219 | } 220 | 221 | private int edgeIndex; 222 | public int EdgeIndex { get { return edgeIndex; } } 223 | 224 | public void Dispose() 225 | { 226 | LeftVertex = null; 227 | RightVertex = null; 228 | 229 | unusedPool.Enqueue(this); 230 | disposed = true; 231 | } 232 | 233 | public Edge() 234 | { 235 | edgeIndex = nEdges++; 236 | 237 | sites = new Site[2]; 238 | ClippedEnds = new Vector2f[2]; 239 | 240 | Init(); 241 | } 242 | 243 | void Init() 244 | { 245 | sites[0] = null; 246 | sites[1] = null; 247 | 248 | Clipped = false; 249 | } 250 | 251 | public override string ToString() 252 | { 253 | return "Edge " + edgeIndex + "; sites " + sites[0] + ", " + sites[1] + 254 | "; endVertices " + (LeftVertex != null ? LeftVertex.VertexIndex.ToString() : "null") + ", " + 255 | (RightVertex != null ? RightVertex.VertexIndex.ToString() : "null") + "::"; 256 | } 257 | 258 | /* 259 | * Set clippedVertices to contain the two ends of the portion of the Voronoi edge that is visible 260 | * within the bounds. If no part of the Edge falls within the bounds, leave clippedVertices null 261 | * @param bounds 262 | */ 263 | public void ClipVertices(Rectf bounds, bool cullIfIntersectsBounds = false) 264 | { 265 | float xmin = bounds.x; 266 | float ymin = bounds.y; 267 | float xmax = bounds.right; 268 | float ymax = bounds.bottom; 269 | 270 | 271 | 272 | Vertex vertex0, vertex1; 273 | float x0, x1, y0, y1; 274 | 275 | if (a == 1 && b >= 0) 276 | { 277 | vertex0 = RightVertex; 278 | vertex1 = LeftVertex; 279 | } 280 | else 281 | { 282 | vertex0 = LeftVertex; 283 | vertex1 = RightVertex; 284 | } 285 | 286 | if (a == 1) 287 | { 288 | y0 = ymin; 289 | if (vertex0 != null && vertex0.y > ymin) 290 | { 291 | y0 = vertex0.y; 292 | } 293 | if (y0 > ymax) 294 | { 295 | return; 296 | } 297 | x0 = c - b * y0; 298 | 299 | y1 = ymax; 300 | if (vertex1 != null && vertex1.y < ymax) 301 | { 302 | y1 = vertex1.y; 303 | } 304 | if (y1 < ymin) 305 | { 306 | return; 307 | } 308 | x1 = c - b * y1; 309 | 310 | if ((x0 > xmax && x1 > xmax) || (x0 < xmin && x1 < xmin)) 311 | { 312 | return; 313 | } 314 | 315 | if (x0 > xmax) 316 | { 317 | x0 = xmax; 318 | y0 = (c - x0) / b; 319 | } 320 | else if (x0 < xmin) 321 | { 322 | x0 = xmin; 323 | y0 = (c - x0) / b; 324 | } 325 | 326 | if (x1 > xmax) 327 | { 328 | x1 = xmax; 329 | y1 = (c - x1) / b; 330 | } 331 | else if (x1 < xmin) 332 | { 333 | x1 = xmin; 334 | y1 = (c - x1) / b; 335 | } 336 | } 337 | else 338 | { 339 | x0 = xmin; 340 | if (vertex0 != null && vertex0.x > xmin) 341 | { 342 | x0 = vertex0.x; 343 | } 344 | if (x0 > xmax) 345 | { 346 | return; 347 | } 348 | y0 = c - a * x0; 349 | 350 | x1 = xmax; 351 | if (vertex1 != null && vertex1.x < xmax) 352 | { 353 | x1 = vertex1.x; 354 | } 355 | if (x1 < xmin) 356 | { 357 | return; 358 | } 359 | y1 = c - a * x1; 360 | 361 | if ((y0 > ymax && y1 > ymax) || (y0 < ymin && y1 < ymin)) 362 | { 363 | return; 364 | } 365 | 366 | if (y0 > ymax) 367 | { 368 | y0 = ymax; 369 | x0 = (c - y0) / a; 370 | } 371 | else if (y0 < ymin) 372 | { 373 | y0 = ymin; 374 | x0 = (c - y0) / a; 375 | } 376 | 377 | if (y1 > ymax) 378 | { 379 | y1 = ymax; 380 | x1 = (c - y1) / a; 381 | } 382 | else if (y1 < ymin) 383 | { 384 | y1 = ymin; 385 | x1 = (c - y1) / a; 386 | } 387 | } 388 | 389 | if (cullIfIntersectsBounds) 390 | { 391 | if (x0 <= xmin || x0 >= xmax) return; 392 | if (y0 <= ymin || y0 >= ymax) return; 393 | 394 | if (x1 <= xmin || x1 >= xmax) return; 395 | if (y1 <= ymin || y1 >= ymax) return; 396 | } 397 | 398 | Clipped = true; 399 | 400 | if (vertex0 == LeftVertex) 401 | { 402 | ClippedEnds[0] = new Vector2f(x0, y0); 403 | ClippedEnds[1] = new Vector2f(x1, y1); 404 | } 405 | else 406 | { 407 | ClippedEnds[1] = new Vector2f(x0, y0); 408 | ClippedEnds[0] = new Vector2f(x1, y1); 409 | } 410 | } 411 | 412 | public void ResetClippedPositionsFromVertices() 413 | { 414 | if (!Clipped) return; 415 | 416 | // Prevents non existant vertices, but unsure why, the edge is clipped? 417 | if (LeftVertex == null || RightVertex == null) return; 418 | 419 | ClippedEnds[0] = LeftVertex.Coord; 420 | ClippedEnds[1] = RightVertex.Coord; 421 | } 422 | #endregion 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /Delaunay/Edge.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 15c73b4f710b24e4d8d87a434b21315b 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Delaunay/EdgeList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace csDelaunay 5 | { 6 | 7 | public class EdgeList 8 | { 9 | 10 | private float deltaX; 11 | private float xmin; 12 | 13 | private int hashSize; 14 | private Halfedge[] hash; 15 | private Halfedge leftEnd; 16 | public Halfedge LeftEnd { get { return leftEnd; } } 17 | private Halfedge rightEnd; 18 | public Halfedge RightEnd { get { return rightEnd; } } 19 | 20 | public void Dispose() 21 | { 22 | Halfedge halfedge = leftEnd; 23 | Halfedge prevHe; 24 | while (halfedge != rightEnd) 25 | { 26 | prevHe = halfedge; 27 | halfedge = halfedge.edgeListRightNeighbor; 28 | prevHe.Dispose(); 29 | } 30 | leftEnd = null; 31 | rightEnd.Dispose(); 32 | rightEnd = null; 33 | 34 | //UnityEngine.Debug.Log(hash.Length); 35 | 36 | // cleanup the hash 37 | for (int i = 0; i < hash.Length; i++) 38 | { 39 | hash[i] = null; 40 | } 41 | 42 | //hash = null; // causes alloc 43 | } 44 | 45 | public EdgeList(float xmin, float deltaX, int sqrtSitesNb) 46 | { 47 | this.xmin = xmin; 48 | this.deltaX = deltaX; 49 | 50 | hashSize = 2 * sqrtSitesNb; 51 | hash = new Halfedge[hashSize]; 52 | 53 | // Two dummy Halfedges: 54 | leftEnd = Halfedge.CreateDummy(); 55 | rightEnd = Halfedge.CreateDummy(); 56 | leftEnd.edgeListLeftNeighbor = null; 57 | leftEnd.edgeListRightNeighbor = rightEnd; 58 | rightEnd.edgeListLeftNeighbor = leftEnd; 59 | rightEnd.edgeListRightNeighbor = null; 60 | hash[0] = leftEnd; 61 | hash[hashSize - 1] = rightEnd; 62 | } 63 | 64 | public void ClearNoResize(float xmin, float deltaX) 65 | { 66 | this.xmin = xmin; 67 | this.deltaX = deltaX; 68 | 69 | // Two dummy Halfedges: 70 | leftEnd = Halfedge.CreateDummy(); // nonalloc 71 | rightEnd = Halfedge.CreateDummy(); // nonalloc 72 | 73 | leftEnd.edgeListLeftNeighbor = null; 74 | leftEnd.edgeListRightNeighbor = rightEnd; 75 | rightEnd.edgeListLeftNeighbor = leftEnd; 76 | rightEnd.edgeListRightNeighbor = null; 77 | hash[0] = leftEnd; 78 | hash[hashSize - 1] = rightEnd; 79 | } 80 | 81 | /* 82 | * Insert newHalfedge to the right of lb 83 | * @param lb 84 | * @param newHalfedge 85 | */ 86 | public void Insert(Halfedge lb, Halfedge newHalfedge) 87 | { 88 | newHalfedge.edgeListLeftNeighbor = lb; 89 | newHalfedge.edgeListRightNeighbor = lb.edgeListRightNeighbor; 90 | lb.edgeListRightNeighbor.edgeListLeftNeighbor = newHalfedge; 91 | lb.edgeListRightNeighbor = newHalfedge; 92 | } 93 | 94 | /* 95 | * This function only removes the Halfedge from the left-right list. 96 | * We cannot dispose it yet because we are still using it. 97 | * @param halfEdge 98 | */ 99 | public void Remove(Halfedge halfedge) 100 | { 101 | halfedge.edgeListLeftNeighbor.edgeListRightNeighbor = halfedge.edgeListRightNeighbor; 102 | halfedge.edgeListRightNeighbor.edgeListLeftNeighbor = halfedge.edgeListLeftNeighbor; 103 | halfedge.edge = Edge.DELETED; 104 | halfedge.edgeListLeftNeighbor = halfedge.edgeListRightNeighbor = null; 105 | } 106 | 107 | /* 108 | * Find the rightmost Halfedge that is still elft of p 109 | * @param p 110 | * @return 111 | */ 112 | public Halfedge EdgeListLeftNeighbor(Vector2f p) 113 | { 114 | int bucket; 115 | Halfedge halfedge; 116 | 117 | // Use hash table to get close to desired halfedge 118 | bucket = (int)((p.x - xmin) / deltaX * hashSize); 119 | if (bucket < 0) 120 | { 121 | bucket = 0; 122 | } 123 | if (bucket >= hashSize) 124 | { 125 | bucket = hashSize - 1; 126 | } 127 | halfedge = GetHash(bucket); 128 | if (halfedge == null) 129 | { 130 | for (int i = 0; true; i++) 131 | { 132 | if ((halfedge = GetHash(bucket - i)) != null) break; 133 | if ((halfedge = GetHash(bucket + i)) != null) break; 134 | } 135 | } 136 | // Now search linear list of haledges for the correct one 137 | if (halfedge == leftEnd || (halfedge != rightEnd && halfedge.IsLeftOf(p))) 138 | { 139 | do 140 | { 141 | halfedge = halfedge.edgeListRightNeighbor; 142 | } while (halfedge != rightEnd && halfedge.IsLeftOf(p)); 143 | halfedge = halfedge.edgeListLeftNeighbor; 144 | 145 | } 146 | else 147 | { 148 | do 149 | { 150 | halfedge = halfedge.edgeListLeftNeighbor; 151 | } while (halfedge != leftEnd && !halfedge.IsLeftOf(p)); 152 | } 153 | 154 | // Update hash table and reference counts 155 | if (bucket > 0 && bucket < hashSize - 1) 156 | { 157 | hash[bucket] = halfedge; 158 | } 159 | return halfedge; 160 | } 161 | 162 | // Get entry from the has table, pruning any deleted nodes 163 | private Halfedge GetHash(int b) 164 | { 165 | Halfedge halfedge; 166 | 167 | if (b < 0 || b >= hashSize) 168 | { 169 | return null; 170 | } 171 | halfedge = hash[b]; 172 | if (halfedge != null && halfedge.edge == Edge.DELETED) 173 | { 174 | // Hash table points to deleted halfedge. Patch as necessary 175 | hash[b] = null; 176 | // Still can't dispose halfedge yet! 177 | return null; 178 | } 179 | else 180 | { 181 | return halfedge; 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Delaunay/EdgeList.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87a738a12f68dcc40920f9c2b980cb9c 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Delaunay/EdgeReorderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine.Profiling; 5 | 6 | namespace csDelaunay 7 | { 8 | 9 | public class EdgeReorderer 10 | { 11 | public List EdgeOrientations { get; private set; } 12 | 13 | static EdgeReorderer instance; 14 | const int BUFFER_CAPACITY = 64; 15 | 16 | List newEdgesBuffer; 17 | List doneBuffer; 18 | 19 | public static void CreateInstance() 20 | { 21 | if (instance == null) 22 | instance = new EdgeReorderer(); 23 | else 24 | { 25 | instance.Clear(); 26 | } 27 | } 28 | 29 | EdgeReorderer() 30 | { 31 | EdgeOrientations = new List(BUFFER_CAPACITY); 32 | 33 | newEdgesBuffer = new List(BUFFER_CAPACITY); 34 | doneBuffer = new List(BUFFER_CAPACITY); 35 | } 36 | 37 | public static void Reorder(ref List origEdges, ref List edgeOrientations, Type criterion) 38 | { 39 | CreateInstance(); 40 | 41 | if (origEdges == null || origEdges.Count == 0) 42 | return; 43 | 44 | instance.ReorderEdges(origEdges, criterion); 45 | 46 | origEdges.Clear(); 47 | for (int i = 0; i < instance.newEdgesBuffer.Count; i++) 48 | origEdges.Add(instance.newEdgesBuffer[i]); 49 | 50 | // Copy EdgeOrientations to edgeOrientations 51 | edgeOrientations.Clear(); 52 | for (int i = 0; i < instance.EdgeOrientations.Count; i++) 53 | edgeOrientations.Add(instance.EdgeOrientations[i]); 54 | } 55 | 56 | public void Clear() 57 | { 58 | EdgeOrientations.Clear(); 59 | 60 | newEdgesBuffer.Clear(); 61 | doneBuffer.Clear(); 62 | } 63 | 64 | public static string GetCapacities() 65 | { 66 | return $"EdgeOrientations: {instance.EdgeOrientations.Capacity}, newEdgesBuffer: {instance.newEdgesBuffer.Capacity}, doneBuffer: {instance.doneBuffer.Capacity}"; 67 | } 68 | 69 | void ReorderEdges(List origEdges, Type criterion) 70 | { 71 | Profiler.BeginSample("Reorder start"); 72 | int i; 73 | int n = origEdges.Count; 74 | Edge edge; 75 | // We're going to reorder the edges in order of traversal 76 | //List done = new List(); // alloc 77 | int nDone = 0; 78 | for (int b = 0; b < n; b++) doneBuffer.Add(false); 79 | //List newEdges = new List(); // alloc 80 | 81 | i = 0; 82 | edge = origEdges[i]; 83 | newEdgesBuffer.Add(edge); // extend alloc 84 | EdgeOrientations.Add(false); // extend alloc 85 | ICoord firstPoint; 86 | ICoord lastPoint; 87 | Profiler.EndSample(); 88 | 89 | Profiler.BeginSample("Criterion search"); 90 | if (criterion == typeof(Vertex)) 91 | { 92 | firstPoint = edge.LeftVertex; 93 | lastPoint = edge.RightVertex; 94 | } 95 | else 96 | { 97 | firstPoint = edge.LeftSite; 98 | lastPoint = edge.RightSite; 99 | } 100 | Profiler.EndSample(); 101 | 102 | if (firstPoint == Vertex.VERTEX_AT_INFINITY || lastPoint == Vertex.VERTEX_AT_INFINITY) 103 | { 104 | UnityEngine.Debug.LogError("Puk"); 105 | return; 106 | } 107 | 108 | doneBuffer[i] = true; 109 | nDone++; 110 | 111 | Profiler.BeginSample("While"); 112 | while (nDone < n) 113 | { 114 | for (i = 1; i < n; i++) 115 | { 116 | if (doneBuffer[i]) 117 | { 118 | continue; 119 | } 120 | edge = origEdges[i]; 121 | ICoord leftPoint; 122 | ICoord rightPoint; 123 | if (criterion == typeof(Vertex)) 124 | { 125 | leftPoint = edge.LeftVertex; 126 | rightPoint = edge.RightVertex; 127 | } 128 | else 129 | { 130 | leftPoint = edge.LeftSite; 131 | rightPoint = edge.RightSite; 132 | } 133 | if (leftPoint == Vertex.VERTEX_AT_INFINITY || rightPoint == Vertex.VERTEX_AT_INFINITY) 134 | { 135 | UnityEngine.Debug.LogError("Puk2"); 136 | return; 137 | } 138 | if (leftPoint == lastPoint) 139 | { 140 | lastPoint = rightPoint; 141 | EdgeOrientations.Add(false); // extend alloc 142 | newEdgesBuffer.Add(edge); // extend alloc 143 | doneBuffer[i] = true; 144 | } 145 | else if (rightPoint == firstPoint) 146 | { 147 | firstPoint = leftPoint; 148 | EdgeOrientations.Insert(0, false); // extend alloc 149 | newEdgesBuffer.Insert(0, edge); // extend alloc 150 | doneBuffer[i] = true; 151 | } 152 | else if (leftPoint == firstPoint) 153 | { 154 | firstPoint = rightPoint; 155 | EdgeOrientations.Insert(0, true); // extend alloc 156 | newEdgesBuffer.Insert(0, edge); // extend alloc 157 | doneBuffer[i] = true; 158 | } 159 | else if (rightPoint == lastPoint) 160 | { 161 | lastPoint = leftPoint; 162 | EdgeOrientations.Add(true); // extend alloc 163 | newEdgesBuffer.Add(edge); // extend alloc 164 | doneBuffer[i] = true; 165 | } 166 | if (doneBuffer[i]) 167 | { 168 | nDone++; 169 | } 170 | } 171 | } 172 | Profiler.EndSample(); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Delaunay/EdgeReorderer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9bcca6669d918d64cb2364afb8ac174e 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Delaunay/Halfedge.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine.Profiling; 4 | 5 | namespace csDelaunay 6 | { 7 | 8 | public class Halfedge 9 | { 10 | public static List all = new List(); 11 | 12 | #region Pool 13 | private static Queue unusedPool = new Queue(); 14 | public static int PoolCapacity { get => all.Count; } 15 | bool disposed; 16 | 17 | public static void PoolDummies(int num) 18 | { 19 | var dummies = new Halfedge[num]; 20 | for (int i = 0; i < num; i++) 21 | { 22 | dummies[i] = CreateDummy(); 23 | } 24 | 25 | for (int i = 0; i < num; i++) 26 | { 27 | dummies[i].Dispose(); 28 | } 29 | } 30 | 31 | public static void DisposeAll() 32 | { 33 | for (int i = 0; i < all.Count; i++) 34 | { 35 | if (!all[i].disposed) 36 | { 37 | all[i].ReallyDispose(); 38 | //UnityEngine.Debug.Log("Found undisposed HE"); 39 | } 40 | } 41 | } 42 | 43 | /// 44 | /// Use only for testing 45 | /// 46 | public static void FlushUnused() 47 | { 48 | all = new List(); 49 | unusedPool = new Queue(); 50 | } 51 | 52 | public static Halfedge Create(Edge edge, bool lr) 53 | { 54 | if (unusedPool.Count > 0) 55 | { 56 | return unusedPool.Dequeue().Init(edge, lr); 57 | } 58 | else 59 | { 60 | Profiler.BeginSample("Making new halfege"); 61 | Halfedge halfedge = new Halfedge(edge, lr); 62 | all.Add(halfedge); 63 | Profiler.EndSample(); 64 | return halfedge; 65 | } 66 | } 67 | public static Halfedge CreateDummy() 68 | { 69 | return Create(null, false); 70 | } 71 | #endregion 72 | 73 | #region Object 74 | public Halfedge edgeListLeftNeighbor; 75 | public Halfedge edgeListRightNeighbor; 76 | public Halfedge nextInPriorityQueue; 77 | 78 | public Edge edge; 79 | public bool leftRight; 80 | public Vertex vertex; 81 | 82 | // The vertex's y-coordinate in the transformed Voronoi space V 83 | public float ystar; 84 | 85 | public Halfedge(Edge edge, bool lr) 86 | { 87 | Init(edge, lr); 88 | } 89 | 90 | private Halfedge Init(Edge edge, bool lr) 91 | { 92 | this.edge = edge; 93 | leftRight = lr; 94 | nextInPriorityQueue = null; 95 | vertex = null; 96 | 97 | disposed = false; 98 | 99 | return this; 100 | } 101 | 102 | public override string ToString() 103 | { 104 | return "Halfedge (LeftRight: " + leftRight + "; vertex: " + vertex + ")"; 105 | } 106 | 107 | public void Dispose() 108 | { 109 | if (edgeListLeftNeighbor != null || edgeListRightNeighbor != null) 110 | { 111 | // still in EdgeList 112 | return; 113 | } 114 | if (nextInPriorityQueue != null) 115 | { 116 | // still in PriorityQueue 117 | return; 118 | } 119 | edge = null; 120 | leftRight = false; 121 | vertex = null; 122 | unusedPool.Enqueue(this); 123 | disposed = true; 124 | } 125 | 126 | public void ReallyDispose() 127 | { 128 | edgeListLeftNeighbor = null; 129 | edgeListRightNeighbor = null; 130 | nextInPriorityQueue = null; 131 | edge = null; 132 | leftRight = false; 133 | vertex = null; 134 | unusedPool.Enqueue(this); 135 | disposed = true; 136 | } 137 | 138 | public bool IsLeftOf(Vector2f p) 139 | { 140 | Site topSite; 141 | bool rightOfSite, above, fast; 142 | float dxp, dyp, dxs, t1, t2, t3, y1; 143 | 144 | topSite = edge.RightSite; 145 | rightOfSite = p.x > topSite.x; 146 | if (rightOfSite && this.leftRight == false) 147 | { 148 | return true; 149 | } 150 | if (!rightOfSite && this.leftRight == true) 151 | { 152 | return false; 153 | } 154 | 155 | if (edge.a == 1) 156 | { 157 | dyp = p.y - topSite.y; 158 | dxp = p.x - topSite.x; 159 | fast = false; 160 | if ((!rightOfSite && edge.b < 0) || (rightOfSite && edge.b >= 0)) 161 | { 162 | above = dyp >= edge.b * dxp; 163 | fast = above; 164 | } 165 | else 166 | { 167 | above = p.x + p.y * edge.b > edge.c; 168 | if (edge.b < 0) 169 | { 170 | above = !above; 171 | } 172 | if (!above) 173 | { 174 | fast = true; 175 | } 176 | } 177 | if (!fast) 178 | { 179 | dxs = topSite.x - edge.LeftSite.x; 180 | above = edge.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1 + 2 * dxp / dxs + edge.b * edge.b); 181 | if (edge.b < 0) 182 | { 183 | above = !above; 184 | } 185 | } 186 | } 187 | else 188 | { 189 | y1 = edge.c - edge.a * p.x; 190 | t1 = p.y - y1; 191 | t2 = p.x - topSite.x; 192 | t3 = y1 - topSite.y; 193 | above = t1 * t1 > t2 * t2 + t3 * t3; 194 | } 195 | return this.leftRight == false ? above : !above; 196 | } 197 | #endregion 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Delaunay/Halfedge.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fa36a6db0ad8bf24abce955367456fe1 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Delaunay/HalfedgePriorityQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace csDelaunay 5 | { 6 | 7 | // Also know as heap 8 | public class HalfedgePriorityQueue 9 | { 10 | 11 | private Halfedge[] hash; 12 | public int count; 13 | private int minBucked; 14 | private int hashSize; 15 | 16 | private float ymin; 17 | private float deltaY; 18 | 19 | public HalfedgePriorityQueue(float ymin, float deltaY, int sqrtSitesNb) 20 | { 21 | this.ymin = ymin; 22 | this.deltaY = deltaY; 23 | hashSize = 4 * sqrtSitesNb; 24 | Init(); 25 | } 26 | 27 | public void Reinit(float ymin, float deltaY, int sqrtSitesNb) 28 | { 29 | this.ymin = ymin; 30 | this.deltaY = deltaY; 31 | hashSize = 4 * sqrtSitesNb; 32 | Init(); 33 | } 34 | 35 | public void ReinitNoSizeChange(float ymin, float deltaY) 36 | { 37 | this.ymin = ymin; 38 | this.deltaY = deltaY; 39 | 40 | // from Init() 41 | count = 0; 42 | minBucked = 0; 43 | 44 | // Dummy Halfedge at the top of each hash 45 | for (int i = 0; i < hashSize; i++) 46 | { 47 | hash[i] = Halfedge.CreateDummy(); 48 | hash[i].nextInPriorityQueue = null; 49 | } 50 | } 51 | 52 | public void Dispose() 53 | { 54 | // Get rid of dummies 55 | for (int i = 0; i < hashSize; i++) 56 | { 57 | hash[i].Dispose(); 58 | } 59 | //hash = null; // causes allocs in the next run 60 | } 61 | 62 | public void Init() 63 | { 64 | count = 0; 65 | minBucked = 0; 66 | hash = new Halfedge[hashSize]; 67 | // Dummy Halfedge at the top of each hash 68 | for (int i = 0; i < hashSize; i++) 69 | { 70 | hash[i] = Halfedge.CreateDummy(); 71 | hash[i].nextInPriorityQueue = null; 72 | } 73 | } 74 | 75 | public void Insert(Halfedge halfedge) 76 | { 77 | Halfedge previous, next; 78 | 79 | int insertionBucket = Bucket(halfedge); 80 | if (insertionBucket < minBucked) 81 | { 82 | minBucked = insertionBucket; 83 | } 84 | previous = hash[insertionBucket]; 85 | while ((next = previous.nextInPriorityQueue) != null && 86 | (halfedge.ystar > next.ystar || (halfedge.ystar == next.ystar && halfedge.vertex.x > next.vertex.x))) 87 | { 88 | previous = next; 89 | } 90 | halfedge.nextInPriorityQueue = previous.nextInPriorityQueue; 91 | previous.nextInPriorityQueue = halfedge; 92 | count++; 93 | } 94 | 95 | public void Remove(Halfedge halfedge) 96 | { 97 | Halfedge previous; 98 | int removalBucket = Bucket(halfedge); 99 | 100 | if (halfedge.vertex != null) 101 | { 102 | previous = hash[removalBucket]; 103 | while (previous.nextInPriorityQueue != halfedge) // NRE!?!? 104 | { 105 | previous = previous.nextInPriorityQueue; 106 | } 107 | previous.nextInPriorityQueue = halfedge.nextInPriorityQueue; 108 | count--; 109 | halfedge.vertex = null; 110 | halfedge.nextInPriorityQueue = null; 111 | halfedge.Dispose(); 112 | } 113 | } 114 | 115 | private int Bucket(Halfedge halfedge) 116 | { 117 | int theBucket = (int)((halfedge.ystar - ymin) / deltaY * hashSize); 118 | if (theBucket < 0) theBucket = 0; 119 | if (theBucket >= hashSize) theBucket = hashSize - 1; 120 | return theBucket; 121 | } 122 | 123 | private bool IsEmpty(int bucket) 124 | { 125 | return (hash[bucket].nextInPriorityQueue == null); 126 | } 127 | 128 | /* 129 | * move minBucket until it contains an actual Halfedge (not just the dummy at the top); 130 | */ 131 | private void AdjustMinBucket() 132 | { 133 | while (minBucked < hashSize - 1 && IsEmpty(minBucked)) 134 | { 135 | minBucked++; 136 | } 137 | } 138 | 139 | /* 140 | * @return coordinates of the Halfedge's vertex in V*, the transformed Voronoi diagram 141 | */ 142 | public Vector2f Min() 143 | { 144 | AdjustMinBucket(); 145 | Halfedge answer = hash[minBucked].nextInPriorityQueue; 146 | return new Vector2f(answer.vertex.x, answer.ystar); 147 | } 148 | 149 | /* 150 | * Remove and return the min Halfedge 151 | */ 152 | public Halfedge ExtractMin() 153 | { 154 | Halfedge answer; 155 | 156 | // Get the first real Halfedge in minBucket 157 | answer = hash[minBucked].nextInPriorityQueue; 158 | 159 | hash[minBucked].nextInPriorityQueue = answer.nextInPriorityQueue; 160 | count--; 161 | answer.nextInPriorityQueue = null; 162 | 163 | return answer; 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /Delaunay/HalfedgePriorityQueue.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 71a615039a729984ca98548117462785 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Delaunay/ICoord.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace csDelaunay { 4 | 5 | public interface ICoord { 6 | 7 | Vector2f Coord {get;set;} 8 | } 9 | } -------------------------------------------------------------------------------- /Delaunay/ICoord.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c43853d42fd54204fa46fb95c8538f27 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Delaunay/Site.cs: -------------------------------------------------------------------------------- 1 | //#define CAPACITY_WARNING 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using UnityEngine.Profiling; 7 | 8 | namespace csDelaunay 9 | { 10 | 11 | public class Site : ICoord 12 | { 13 | public static int edgesCapacity = 10; 14 | 15 | private static Queue unusedPool = new Queue(); 16 | public static int PoolCapacity { get => unusedPool.Count; } 17 | 18 | /// 19 | /// Use only for testing 20 | /// 21 | public static void FlushUnused() 22 | { 23 | unusedPool = new Queue(); 24 | } 25 | 26 | public static int GetMaxEdgeCapacity(List sites) 27 | { 28 | int max = 0; 29 | foreach (var site in sites) 30 | { 31 | int c = site.edges.Capacity; 32 | if (c > max) max = c; 33 | } 34 | 35 | return max; 36 | } 37 | 38 | public static Rectf GetSitesBounds(List sites) 39 | { 40 | /*if (!sorted) 41 | { 42 | Site.SortSites(sites); 43 | //SortList(); 44 | //ResetListIndex(); 45 | //currentIndex = 0; 46 | }*/ 47 | 48 | SortSites(sites); // always sort 49 | 50 | float xmin, xmax, ymin, ymax; 51 | 52 | if (sites.Count == 0) 53 | { 54 | return Rectf.zero; 55 | } 56 | 57 | xmin = float.MaxValue; 58 | xmax = float.MinValue; 59 | 60 | foreach (Site site in sites) 61 | { 62 | if (site.x < xmin) xmin = site.x; 63 | if (site.x > xmax) xmax = site.x; 64 | } 65 | 66 | // here's where we assume that the sites have been sorted on y: 67 | ymin = sites[0].y; 68 | ymax = sites[sites.Count - 1].y; 69 | 70 | return new Rectf(xmin, ymin, xmax - xmin, ymax - ymin); 71 | } 72 | 73 | public static Site Create(Vector2f p, int index, float weigth) 74 | { 75 | //UnityEngine.Debug.Log("Queuesize: " + unusedPool.Count); 76 | 77 | if (unusedPool.Count > 0) 78 | { 79 | Site site = unusedPool.Dequeue(); 80 | site.Init(p, index, weigth); 81 | return site; 82 | } 83 | else 84 | { 85 | Site site = new Site(p, index, weigth); 86 | return site; 87 | } 88 | } 89 | 90 | public static void SortSites(List sites) 91 | { 92 | sites.Sort(delegate (Site s0, Site s1) 93 | { 94 | int returnValue = Voronoi.CompareByYThenX(s0, s1); 95 | 96 | int tempIndex; 97 | 98 | if (returnValue == -1) 99 | { 100 | if (s0.siteIndex > s1.SiteIndex) 101 | { 102 | tempIndex = s0.SiteIndex; 103 | s0.SiteIndex = s1.SiteIndex; 104 | s1.SiteIndex = tempIndex; 105 | } 106 | } 107 | else if (returnValue == 1) 108 | { 109 | if (s1.SiteIndex > s0.SiteIndex) 110 | { 111 | tempIndex = s1.SiteIndex; 112 | s1.SiteIndex = s0.SiteIndex; 113 | s0.SiteIndex = tempIndex; 114 | } 115 | } 116 | 117 | return returnValue; 118 | }); 119 | } 120 | 121 | public int Compare(Site s1, Site s2) 122 | { 123 | return s1.CompareTo(s2); 124 | } 125 | 126 | public int CompareTo(Site s1) 127 | { 128 | int returnValue = Voronoi.CompareByYThenX(this, s1); 129 | 130 | int tempIndex; 131 | 132 | if (returnValue == -1) 133 | { 134 | if (this.siteIndex > s1.SiteIndex) 135 | { 136 | tempIndex = this.SiteIndex; 137 | this.SiteIndex = s1.SiteIndex; 138 | s1.SiteIndex = tempIndex; 139 | } 140 | } 141 | else if (returnValue == 1) 142 | { 143 | if (s1.SiteIndex > this.SiteIndex) 144 | { 145 | tempIndex = s1.SiteIndex; 146 | s1.SiteIndex = this.SiteIndex; 147 | this.SiteIndex = tempIndex; 148 | } 149 | } 150 | 151 | return returnValue; 152 | } 153 | 154 | private const float EPSILON = 0.005f; 155 | private static bool CloseEnough(Vector2f p0, Vector2f p1) 156 | { 157 | return (p0 - p1).magnitude < EPSILON; 158 | } 159 | 160 | private int siteIndex; 161 | public int SiteIndex { get { return siteIndex; } set { siteIndex = value; } } 162 | 163 | private Vector2f coord; 164 | public Vector2f Coord { get { return coord; } set { coord = value; } } 165 | 166 | public float x { get { return coord.x; } } 167 | public float y { get { return coord.y; } } 168 | 169 | private float weigth; 170 | public float Weigth { get { return weigth; } } 171 | 172 | // The edges that define this Site's Voronoi region: 173 | private List edges; 174 | public List Edges { get { return edges; } } 175 | // which end of each edge hooks up with the previous edge in edges: 176 | private List edgeOrientations; 177 | // ordered list of points that define the region clipped to bounds: 178 | private List region; 179 | 180 | public Site(Vector2f p, int index, float weigth) 181 | { 182 | Init(p, index, weigth); 183 | } 184 | 185 | private Site Init(Vector2f p, int index, float weigth) 186 | { 187 | coord = p; 188 | siteIndex = index; 189 | this.weigth = weigth; 190 | 191 | if (edges == null) 192 | edges = new List(edgesCapacity); 193 | else 194 | edges.Clear(); 195 | 196 | // used for regions: 197 | if (edgeOrientations == null) 198 | edgeOrientations = new List(edgesCapacity); 199 | else 200 | edges.Clear(); 201 | 202 | if (region == null) 203 | region = new List(edgesCapacity * 2); 204 | else 205 | region.Clear(); 206 | 207 | return this; 208 | } 209 | 210 | public override string ToString() 211 | { 212 | return "Site " + siteIndex + ": " + coord; 213 | } 214 | 215 | private void Move(Vector2f p) 216 | { 217 | Clear(); 218 | coord = p; 219 | } 220 | 221 | public void Dispose() 222 | { 223 | Clear(); 224 | unusedPool.Enqueue(this); 225 | } 226 | 227 | private void Clear() 228 | { 229 | if (edges != null) 230 | { 231 | int disposedct = 0; 232 | for (int i = 0; i < edges.Count; i++) 233 | { 234 | if (!edges[i].disposed) 235 | { 236 | disposedct++; 237 | edges[i].Dispose(); 238 | } 239 | } 240 | 241 | if (disposedct > 0) 242 | UnityEngine.Debug.Log("There were " + disposedct + " undisposed edges"); 243 | 244 | edges.Clear(); 245 | //edges = null; 246 | } 247 | if (edgeOrientations != null) 248 | { 249 | edgeOrientations.Clear(); 250 | //edgeOrientations = null; 251 | } 252 | if (region != null) 253 | { 254 | region.Clear(); 255 | //region = null; 256 | } 257 | } 258 | 259 | #if CAPACITY_WARNING 260 | int prevCapacity; 261 | #endif 262 | 263 | public void AddEdge(Edge edge) 264 | { 265 | edges.Add(edge); 266 | 267 | #if CAPACITY_WARNING 268 | if (edges.Capacity != prevCapacity) 269 | UnityEngine.Debug.Log($"Capacity got extended from {prevCapacity} to {edges.Capacity}"); 270 | 271 | prevCapacity = edges.Capacity; 272 | #endif 273 | } 274 | 275 | public void RemoveEdge(Edge edge) 276 | { 277 | if (!edges.Contains(edge)) 278 | throw new Exception("Edge not part of the site"); 279 | 280 | edges.Remove(edge); 281 | } 282 | 283 | public Edge NearestEdge() 284 | { 285 | edges.Sort(Edge.CompareSitesDistances); 286 | return edges[0]; 287 | } 288 | 289 | // Not correct! 290 | public List NeighborSites() 291 | { 292 | if (edges == null || edges.Count == 0) 293 | { 294 | return new List(); 295 | } 296 | if (edgeOrientations == null) 297 | { 298 | ReorderEdges(); // alloc 299 | } 300 | List list = new List(); 301 | foreach (Edge edge in edges) 302 | { 303 | list.Add(NeighborSite(edge)); 304 | } 305 | return list; 306 | } 307 | 308 | private Site NeighborSite(Edge edge) 309 | { 310 | if (this == edge.LeftSite) 311 | { 312 | return edge.RightSite; 313 | } 314 | if (this == edge.RightSite) 315 | { 316 | return edge.LeftSite; 317 | } 318 | return null; 319 | } 320 | 321 | public List Region(Rectf clippingBounds) 322 | { 323 | if (edges == null || edges.Count == 0) 324 | return null; 325 | 326 | Profiler.BeginSample("Reordering"); 327 | ReorderEdges(); 328 | Profiler.EndSample(); 329 | 330 | Profiler.BeginSample("Building region"); 331 | BuildRegionNoClip(); 332 | Profiler.EndSample(); 333 | 334 | //region = ClipToBounds(clippingBounds); // alloc 335 | 336 | //if ((new Polygon(region)).PolyWinding() == Winding.CLOCKWISE) // alloc 337 | //region.Reverse(); 338 | 339 | return region; 340 | } 341 | 342 | private void ReorderEdges() 343 | { 344 | EdgeReorderer.Reorder(ref edges, ref edgeOrientations, typeof(Vertex)); 345 | } 346 | 347 | void BuildRegionNoClip() 348 | { 349 | region.Clear(); 350 | 351 | if (edges.Count == 0) 352 | { 353 | UnityEngine.Debug.Log("Site has no edges"); 354 | return; 355 | } 356 | 357 | for (int e = 0; e < edges.Count; e++) 358 | { 359 | if (!edges[e].Clipped) 360 | { 361 | //UnityEngine.Debug.Log("Site region is not clipped"); 362 | return; 363 | } 364 | } 365 | 366 | UnityEngine.Debug.Assert(edgeOrientations != null, "Edge orientations is null"); 367 | 368 | for (int i = 0; i < edges.Count; i++) 369 | { 370 | Edge edge = edges[i]; 371 | bool orientation = edgeOrientations[i]; 372 | 373 | if (!edge.Clipped) 374 | UnityEngine.Debug.LogError("Edge is not clipped!"); 375 | 376 | Vector2f end1 = edge.ClippedEnds[orientation ? 1 : 0]; 377 | Vector2f end2 = edge.ClippedEnds[!orientation ? 1 : 0]; 378 | 379 | Profiler.BeginSample("Adding ends"); 380 | region.Add(end1); 381 | region.Add(end2); 382 | Profiler.EndSample(); 383 | } 384 | 385 | //UnityEngine.Debug.Log($"{region.Count}, {region.Capacity}"); 386 | } 387 | 388 | // alloc 389 | private List ClipToBounds(Rectf bounds) 390 | { 391 | List points = new List(); 392 | int n = edges.Count; 393 | int i = 0; 394 | Edge edge; 395 | 396 | while (i < n && !edges[i].Clipped) 397 | { 398 | i++; 399 | } 400 | 401 | if (i == n) 402 | { 403 | // No edges visible 404 | return new List(); 405 | } 406 | edge = edges[i]; 407 | 408 | UnityEngine.Debug.Assert(edgeOrientations != null, "Edge orientations is null"); 409 | 410 | bool orientation = edgeOrientations[i]; 411 | 412 | if (!edge.Clipped) 413 | UnityEngine.Debug.LogError("Edge is not clipped!"); 414 | 415 | points.Add(edge.ClippedEnds[orientation ? 1 : 0]); 416 | points.Add(edge.ClippedEnds[!orientation ? 1 : 0]); 417 | 418 | for (int j = i + 1; j < n; j++) 419 | { 420 | edge = edges[j]; 421 | if (!edge.Clipped) 422 | { 423 | continue; 424 | } 425 | Connect(ref points, j, bounds); 426 | } 427 | // Close up the polygon by adding another corner point of the bounds if needed: 428 | Connect(ref points, i, bounds, true); 429 | 430 | return points; 431 | } 432 | 433 | private void Connect(ref List points, int j, Rectf bounds, bool closingUp = false) 434 | { 435 | Vector2f rightPoint = points[points.Count - 1]; 436 | Edge newEdge = edges[j]; 437 | bool newOrientation = edgeOrientations[j]; 438 | 439 | // The point that must be conected to rightPoint: 440 | Vector2f newPoint = newEdge.ClippedEnds[newOrientation ? 1 : 0]; 441 | 442 | if (!CloseEnough(rightPoint, newPoint)) 443 | { 444 | // The points do not coincide, so they must have been clipped at the bounds; 445 | // see if they are on the same border of the bounds: 446 | if (rightPoint.x != newPoint.x && rightPoint.y != newPoint.y) 447 | { 448 | // They are on different borders of the bounds; 449 | // insert one or two corners of bounds as needed to hook them up: 450 | // (NOTE this will not be correct if the region should take up more than 451 | // half of the bounds rect, for then we will have gone the wrong way 452 | // around the bounds and included the smaller part rather than the larger) 453 | int rightCheck = BoundsCheck.Check(rightPoint, bounds); 454 | int newCheck = BoundsCheck.Check(newPoint, bounds); 455 | float px, py; 456 | if ((rightCheck & BoundsCheck.RIGHT) != 0) 457 | { 458 | px = bounds.right; 459 | 460 | if ((newCheck & BoundsCheck.BOTTOM) != 0) 461 | { 462 | py = bounds.bottom; 463 | points.Add(new Vector2f(px, py)); 464 | 465 | } 466 | else if ((newCheck & BoundsCheck.TOP) != 0) 467 | { 468 | py = bounds.top; 469 | points.Add(new Vector2f(px, py)); 470 | 471 | } 472 | else if ((newCheck & BoundsCheck.LEFT) != 0) 473 | { 474 | if (rightPoint.y - bounds.y + newPoint.y - bounds.y < bounds.height) 475 | { 476 | py = bounds.top; 477 | } 478 | else 479 | { 480 | py = bounds.bottom; 481 | } 482 | points.Add(new Vector2f(px, py)); 483 | points.Add(new Vector2f(bounds.left, py)); 484 | } 485 | } 486 | else if ((rightCheck & BoundsCheck.LEFT) != 0) 487 | { 488 | px = bounds.left; 489 | 490 | if ((newCheck & BoundsCheck.BOTTOM) != 0) 491 | { 492 | py = bounds.bottom; 493 | points.Add(new Vector2f(px, py)); 494 | 495 | } 496 | else if ((newCheck & BoundsCheck.TOP) != 0) 497 | { 498 | py = bounds.top; 499 | points.Add(new Vector2f(px, py)); 500 | 501 | } 502 | else if ((newCheck & BoundsCheck.RIGHT) != 0) 503 | { 504 | if (rightPoint.y - bounds.y + newPoint.y - bounds.y < bounds.height) 505 | { 506 | py = bounds.top; 507 | } 508 | else 509 | { 510 | py = bounds.bottom; 511 | } 512 | points.Add(new Vector2f(px, py)); 513 | points.Add(new Vector2f(bounds.right, py)); 514 | } 515 | } 516 | else if ((rightCheck & BoundsCheck.TOP) != 0) 517 | { 518 | py = bounds.top; 519 | 520 | if ((newCheck & BoundsCheck.RIGHT) != 0) 521 | { 522 | px = bounds.right; 523 | points.Add(new Vector2f(px, py)); 524 | 525 | } 526 | else if ((newCheck & BoundsCheck.LEFT) != 0) 527 | { 528 | px = bounds.left; 529 | points.Add(new Vector2f(px, py)); 530 | 531 | } 532 | else if ((newCheck & BoundsCheck.BOTTOM) != 0) 533 | { 534 | if (rightPoint.x - bounds.x + newPoint.x - bounds.x < bounds.width) 535 | { 536 | px = bounds.left; 537 | } 538 | else 539 | { 540 | px = bounds.right; 541 | } 542 | points.Add(new Vector2f(px, py)); 543 | points.Add(new Vector2f(px, bounds.bottom)); 544 | } 545 | } 546 | else if ((rightCheck & BoundsCheck.BOTTOM) != 0) 547 | { 548 | py = bounds.bottom; 549 | 550 | if ((newCheck & BoundsCheck.RIGHT) != 0) 551 | { 552 | px = bounds.right; 553 | points.Add(new Vector2f(px, py)); 554 | 555 | } 556 | else if ((newCheck & BoundsCheck.LEFT) != 0) 557 | { 558 | px = bounds.left; 559 | points.Add(new Vector2f(px, py)); 560 | 561 | } 562 | else if ((newCheck & BoundsCheck.TOP) != 0) 563 | { 564 | if (rightPoint.x - bounds.x + newPoint.x - bounds.x < bounds.width) 565 | { 566 | px = bounds.left; 567 | } 568 | else 569 | { 570 | px = bounds.right; 571 | } 572 | points.Add(new Vector2f(px, py)); 573 | points.Add(new Vector2f(px, bounds.top)); 574 | } 575 | } 576 | } 577 | if (closingUp) 578 | { 579 | // newEdge's ends have already been added 580 | return; 581 | } 582 | points.Add(newPoint); 583 | } 584 | Vector2f newRightPoint = newEdge.ClippedEnds[!newOrientation ? 0 : 1]; 585 | if (!CloseEnough(points[0], newRightPoint)) 586 | { 587 | points.Add(newRightPoint); 588 | } 589 | } 590 | 591 | public float Dist(ICoord p) 592 | { 593 | return (this.Coord - p.Coord).magnitude; 594 | } 595 | } 596 | 597 | public class BoundsCheck 598 | { 599 | public const int TOP = 1; 600 | public const int BOTTOM = 2; 601 | public const int LEFT = 4; 602 | public const int RIGHT = 8; 603 | 604 | /* 605 | * 606 | * @param point 607 | * @param bounds 608 | * @return an int with the appropriate bits set if the Point lies on the corresponding bounds lines 609 | */ 610 | public static int Check(Vector2f point, Rectf bounds) 611 | { 612 | int value = 0; 613 | if (point.x == bounds.left) 614 | { 615 | value |= LEFT; 616 | } 617 | if (point.x == bounds.right) 618 | { 619 | value |= RIGHT; 620 | } 621 | if (point.y == bounds.top) 622 | { 623 | value |= TOP; 624 | } 625 | if (point.y == bounds.bottom) 626 | { 627 | value |= BOTTOM; 628 | } 629 | 630 | return value; 631 | } 632 | } 633 | } 634 | -------------------------------------------------------------------------------- /Delaunay/Site.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8eec8ae5caa523a42bae9758510531cd 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Delaunay/Triangle.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace csDelaunay { 5 | 6 | public class Triangle { 7 | public List Sites { get; } 8 | 9 | public Triangle(Site a, Site b, Site c) { 10 | Sites = new List(); 11 | Sites.Add(a); 12 | Sites.Add(b); 13 | Sites.Add(c); 14 | } 15 | 16 | public void Dispose() { 17 | Sites.Clear(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Delaunay/Triangle.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f127700883d5ca44fa4e471d6cf8336a 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Delaunay/Vertex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace csDelaunay 6 | { 7 | 8 | public class Vertex : ICoord 9 | { 10 | // variables 11 | public Vector2f Coord { get; set; } 12 | public int VertexIndex { get; set; } 13 | 14 | // Properties 15 | public float x { get { return Coord.x; } } 16 | public float y { get { return Coord.y; } } 17 | 18 | // Static 19 | public static readonly Vertex VERTEX_AT_INFINITY = new Vertex(float.NaN, float.NaN); 20 | 21 | #region Pool 22 | private static Queue unusedPool = new Queue(); 23 | public static int PoolCapacity { get => unusedPool.Count; } 24 | 25 | public static void PoolDummies(int num) 26 | { 27 | var dummies = new Vertex[num]; 28 | for (int i = 0; i < num; i++) 29 | { 30 | dummies[i] = Create(0, 0); 31 | } 32 | 33 | for (int i = 0; i < num; i++) 34 | { 35 | dummies[i].Dispose(); 36 | } 37 | } 38 | 39 | public static void FlushUnused() 40 | { 41 | unusedPool = new Queue(); 42 | } 43 | 44 | private static Vertex Create(float x, float y) 45 | { 46 | if (float.IsNaN(x) || float.IsNaN(y)) 47 | { 48 | return VERTEX_AT_INFINITY; 49 | } 50 | if (unusedPool.Count > 0) 51 | { 52 | return unusedPool.Dequeue().Init(x, y); 53 | } 54 | else 55 | { 56 | return new Vertex(x, y); 57 | } 58 | } 59 | #endregion 60 | 61 | #region Object 62 | 63 | public Vertex(float x, float y) 64 | { 65 | Init(x, y); 66 | } 67 | 68 | private Vertex Init(float x, float y) 69 | { 70 | Coord = new Vector2f(x, y); 71 | 72 | return this; 73 | } 74 | 75 | public void Dispose() 76 | { 77 | Coord = Vector2f.zero; 78 | unusedPool.Enqueue(this); 79 | } 80 | 81 | public override string ToString() 82 | { 83 | return "Vertex (" + VertexIndex + ")"; 84 | } 85 | 86 | /* 87 | * This is the only way to make a Vertex 88 | * 89 | * @param halfedge0 90 | * @param halfedge1 91 | * @return 92 | * 93 | */ 94 | public static Vertex Intersect(Halfedge halfedge0, Halfedge halfedge1) 95 | { 96 | Edge edge, edge0, edge1; 97 | Halfedge halfedge; 98 | float determinant, intersectionX, intersectionY; 99 | bool rightOfSite; 100 | 101 | edge0 = halfedge0.edge; 102 | edge1 = halfedge1.edge; 103 | if (edge0 == null || edge1 == null) 104 | { 105 | return null; 106 | } 107 | if (edge0.RightSite == edge1.RightSite) 108 | { 109 | return null; 110 | } 111 | 112 | determinant = edge0.a * edge1.b - edge0.b * edge1.a; 113 | if (Math.Abs(determinant) < 1E-10) 114 | { 115 | // The edges are parallel 116 | return null; 117 | } 118 | 119 | intersectionX = (edge0.c * edge1.b - edge1.c * edge0.b) / determinant; 120 | intersectionY = (edge1.c * edge0.a - edge0.c * edge1.a) / determinant; 121 | 122 | if (Voronoi.CompareByYThenX(edge0.RightSite, edge1.RightSite) < 0) 123 | { 124 | halfedge = halfedge0; 125 | edge = edge0; 126 | } 127 | else 128 | { 129 | halfedge = halfedge1; 130 | edge = edge1; 131 | } 132 | rightOfSite = intersectionX >= edge.RightSite.x; 133 | if ((rightOfSite && halfedge.leftRight == false) || 134 | (!rightOfSite && halfedge.leftRight == true)) 135 | { 136 | return null; 137 | } 138 | 139 | return Create(intersectionX, intersectionY); 140 | } 141 | #endregion 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Delaunay/Vertex.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e56e91916729bf448b201fb464f4b5f0 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Delaunay/Voronoi.cs: -------------------------------------------------------------------------------- 1 | //#define TRIANGLES 2 | 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using UnityEngine.Profiling; 7 | 8 | namespace csDelaunay 9 | { 10 | 11 | public class Voronoi 12 | { 13 | 14 | public List sites; 15 | #if TRIANGLES 16 | private List triangles; 17 | #endif 18 | 19 | public List Edges { get; private set; } 20 | public Rectf PlotBounds { get; private set; } 21 | public Dictionary SitesIndexedByLocation { get; private set; } 22 | 23 | public bool disposeVerticesManually = false; 24 | 25 | private Random weightDistributor; 26 | 27 | public void Clear() 28 | { 29 | for (int i = 0; i < Edges.Count; i++) 30 | { 31 | if (!Edges[i].disposed) 32 | Edges[i].Dispose(); 33 | } 34 | 35 | 36 | for (int i = 0; i < sites.Count; i++) 37 | { 38 | sites[i].Dispose(); 39 | } 40 | 41 | 42 | sites.Clear(); 43 | #if TRIANGLES 44 | triangles.Clear(); 45 | #endif 46 | 47 | 48 | Edges.Clear(); 49 | PlotBounds = Rectf.zero; 50 | SitesIndexedByLocation.Clear(); 51 | } 52 | 53 | [Obsolete] 54 | public void Dispose() 55 | { 56 | sites.Clear(); 57 | sites = null; 58 | 59 | #if TRIANGLES 60 | foreach (Triangle t in triangles) 61 | { 62 | t.Dispose(); 63 | } 64 | triangles.Clear(); 65 | #endif 66 | 67 | foreach (Edge e in Edges) 68 | { 69 | e.Dispose(); 70 | } 71 | Edges.Clear(); 72 | 73 | PlotBounds = Rectf.zero; 74 | SitesIndexedByLocation.Clear(); 75 | //SitesIndexedByLocation = null; 76 | } 77 | 78 | /// 79 | /// Call this after running Redo if disposeVerticesManually is set to true 80 | /// 81 | public void DisposeVertices() 82 | { 83 | for (int i = 0; i < vertices.Count; i++) 84 | { 85 | vertices[i].Dispose(); 86 | } 87 | vertices.Clear(); 88 | } 89 | 90 | public static void InitPools(int halfEdgePoolCapacity, int edgePoolCapacity, int edgesPerSiteCapacity, int vertexPoolCapacity) 91 | { 92 | Halfedge.PoolDummies(halfEdgePoolCapacity); 93 | Edge.PoolDummies(edgePoolCapacity); 94 | Site.edgesCapacity = edgesPerSiteCapacity; 95 | Vertex.PoolDummies(vertexPoolCapacity); 96 | } 97 | 98 | public static void FlushPools() 99 | { 100 | Halfedge.FlushUnused(); 101 | Edge.FlushUnused(); 102 | Site.FlushUnused(); 103 | Vertex.FlushUnused(); 104 | } 105 | 106 | public Voronoi(List points, Rectf plotBounds, bool _disposeVerticesManually = false) 107 | { 108 | if (weightDistributor == null) 109 | weightDistributor = new Random(); 110 | 111 | disposeVerticesManually = _disposeVerticesManually; 112 | 113 | Init(points, plotBounds); 114 | Clear(); 115 | Init(points, plotBounds); 116 | } 117 | 118 | public void Redo(List points, Rectf plotBounds) 119 | { 120 | Clear(); 121 | Init(points, plotBounds); 122 | } 123 | 124 | public Voronoi(List points, Rectf plotBounds, int lloydIterations) 125 | { 126 | weightDistributor = new Random(); 127 | Init(points, plotBounds); 128 | LloydRelaxation(lloydIterations); 129 | } 130 | 131 | public string DebugCapacities() 132 | { 133 | int edgePerSite = Site.GetMaxEdgeCapacity(sites); 134 | return $"Sites: {sites.Count}, Halfedges: {Halfedge.PoolCapacity}, Edges: {Edge.PoolCapacity}, EdgesPerSite: {edgePerSite}, Vertices: {Vertex.PoolCapacity}"; 135 | } 136 | 137 | private void Init(List points, Rectf plotBounds) 138 | { 139 | Profiler.BeginSample("Create sites and dict"); 140 | 141 | if (sites == null) 142 | sites = new List(points.Count); 143 | 144 | if (SitesIndexedByLocation == null) 145 | SitesIndexedByLocation = new Dictionary(points.Count); 146 | 147 | Profiler.EndSample(); 148 | 149 | Profiler.BeginSample("Add points to sites"); 150 | for (int i = 0; i < points.Count; i++) 151 | { 152 | Vector2f p = points[i]; 153 | 154 | float weight = (float)weightDistributor.NextDouble() * 100; 155 | Site site = Site.Create(p, i, weight); 156 | sites.Add(site); 157 | SitesIndexedByLocation[p] = site; 158 | } 159 | Profiler.EndSample(); 160 | 161 | PlotBounds = plotBounds; 162 | 163 | Profiler.BeginSample("Create edges and triangles"); 164 | #if TRIANGLES 165 | if (triangles == null) triangles = new List(); 166 | #endif 167 | if (Edges == null) 168 | Edges = new List(); 169 | Profiler.EndSample(); 170 | 171 | FortunesAlgorithm(); 172 | } 173 | 174 | public void GetAllClippedLines(List lines) 175 | { 176 | lines.Clear(); 177 | 178 | foreach (var edge in Edges) 179 | { 180 | if (!edge.Clipped) continue; 181 | 182 | var p1 = edge.ClippedEnds[0]; 183 | var p2 = edge.ClippedEnds[1]; 184 | 185 | lines.Add(p1); 186 | lines.Add(p2); 187 | } 188 | } 189 | 190 | #region Unused Publics 191 | 192 | /* 193 | public List Region(Vector2f p) 194 | { 195 | Site site; 196 | if (SitesIndexedByLocation.TryGetValue(p, out site)) 197 | { 198 | return site.Region(PlotBounds); 199 | } 200 | else 201 | { 202 | return new List(); 203 | } 204 | }*/ 205 | 206 | /* 207 | public List NeighborSitesForSite(Vector2f coord) 208 | { 209 | List points = new List(); 210 | Site site; 211 | if (SitesIndexedByLocation.TryGetValue(coord, out site)) 212 | { 213 | List sites = site.NeighborSites(); 214 | foreach (Site neighbor in sites) 215 | { 216 | points.Add(neighbor.Coord); 217 | } 218 | } 219 | 220 | return points; 221 | }*/ 222 | 223 | /* 224 | public List Circles() 225 | { 226 | return sites.Circles(); 227 | }*/ 228 | 229 | /* 230 | public List VoronoiBoundarayForSite(Vector2f coord) 231 | { 232 | return LineSegment.VisibleLineSegments(Edge.SelectEdgesForSitePoint(coord, Edges)); 233 | }*/ 234 | 235 | /* 236 | public List DelaunayLinesForSite(Vector2f coord) { 237 | return DelaunayLinesForEdges(Edge.SelectEdgesForSitePoint(coord, edges)); 238 | }*/ 239 | 240 | /* 241 | public List VoronoiDiagram() 242 | { 243 | return LineSegment.VisibleLineSegments(Edges); 244 | }*/ 245 | 246 | /* 247 | public List Hull() { 248 | return DelaunayLinesForEdges(HullEdges()); 249 | }*/ 250 | 251 | /* 252 | public List HullEdges() 253 | { 254 | return Edges.FindAll(edge => edge.IsPartOfConvexHull()); 255 | }*/ 256 | 257 | /* 258 | public List HullPointsInOrder() 259 | { 260 | List hullEdges = HullEdges(); 261 | 262 | List points = new List(); 263 | if (hullEdges.Count == 0) 264 | { 265 | return points; 266 | } 267 | 268 | EdgeReorderer reorderer = new EdgeReorderer(hullEdges, typeof(Site)); 269 | hullEdges = reorderer.Edges; 270 | List orientations = reorderer.EdgeOrientations; 271 | reorderer.Dispose(); 272 | 273 | bool orientation; 274 | for (int i = 0; i < hullEdges.Count; i++) 275 | { 276 | Edge edge = hullEdges[i]; 277 | orientation = orientations[i]; 278 | points.Add(edge.Site(orientation).Coord); 279 | } 280 | return points; 281 | }*/ 282 | 283 | /* 284 | public List> Regions() 285 | { 286 | return sites.Regions(PlotBounds); 287 | }*/ 288 | 289 | /* 290 | public List SiteCoords() 291 | { 292 | return sites.SiteCoords(); 293 | }*/ 294 | 295 | #endregion 296 | 297 | int currentSiteIndex; 298 | int nVertices; 299 | HalfedgePriorityQueue heap; 300 | 301 | EdgeList edgeList; 302 | List halfEdges; 303 | List vertices; 304 | 305 | private void FortunesAlgorithm() 306 | { 307 | Profiler.BeginSample("DAll HEs"); 308 | Halfedge.DisposeAll(); 309 | Edge.DisposeAll(); 310 | Profiler.EndSample(); 311 | 312 | //UnityEngine.Debug.Log("HE pool: " + Halfedge.unusedPool.Count); 313 | 314 | currentSiteIndex = 0; 315 | nVertices = 0; 316 | 317 | // vars 318 | 319 | Profiler.BeginSample("Fortunes: initing"); 320 | Site newSite, bottomSite, topSite, tempSite; 321 | Vertex v, vertex; 322 | Vector2f newIntStar = Vector2f.zero; 323 | bool leftRight; 324 | 325 | Halfedge lbnd = null; 326 | Halfedge rbnd = null; 327 | Halfedge llbnd = null; 328 | Halfedge rrbnd = null; 329 | Halfedge bisector = null; 330 | 331 | Edge edge; 332 | Profiler.EndSample(); 333 | 334 | // Data bounds 335 | Profiler.BeginSample("Fortunes: Getting data bounds"); 336 | Rectf dataBounds = Site.GetSitesBounds(sites); 337 | Profiler.EndSample(); 338 | 339 | int sqrtSitesNb = (int)Math.Sqrt(sites.Count + 4); // WTF 340 | 341 | Profiler.BeginSample("Fortunes: Init heap"); 342 | 343 | if (heap == null) 344 | { 345 | heap = new HalfedgePriorityQueue(dataBounds.y, dataBounds.height, sqrtSitesNb); 346 | } 347 | else 348 | heap.ReinitNoSizeChange(dataBounds.y, dataBounds.height); 349 | 350 | Profiler.EndSample(); 351 | 352 | Profiler.BeginSample("Fortunes: Init EdgeList"); 353 | if (edgeList == null) 354 | { 355 | edgeList = new EdgeList(dataBounds.x, dataBounds.width, sqrtSitesNb); 356 | } 357 | else 358 | edgeList.ClearNoResize(dataBounds.x, dataBounds.width); 359 | 360 | //edgeList = new EdgeList(dataBounds.x, dataBounds.width, sqrtSitesNb); 361 | Profiler.EndSample(); 362 | 363 | Profiler.BeginSample("Fortunes: Init HEs and vertices"); 364 | if (halfEdges == null) // TODO: Move to init 365 | { 366 | halfEdges = new List(); 367 | vertices = new List(); 368 | } 369 | else 370 | { 371 | halfEdges.Clear(); 372 | vertices.Clear(); 373 | } 374 | Profiler.EndSample(); 375 | 376 | Site bottomMostSite = GetNextSite(); 377 | newSite = GetNextSite(); 378 | 379 | Profiler.BeginSample("Fortunes: Main Loop"); 380 | while (true) 381 | { 382 | if (heap.count > 0) 383 | { 384 | newIntStar = heap.Min(); 385 | } 386 | 387 | if (newSite != null && 388 | (heap.count == 0 || CompareByYThenX(newSite, newIntStar) < 0)) 389 | { 390 | // New site is smallest 391 | //Debug.Log("smallest: new site " + newSite); 392 | 393 | // Step 8: 394 | // The halfedge just to the left of newSite 395 | //UnityEngine.Debug.Log("lbnd: " + lbnd); 396 | lbnd = edgeList.EdgeListLeftNeighbor(newSite.Coord); 397 | // The halfedge just to the right 398 | rbnd = lbnd.edgeListRightNeighbor; 399 | //UnityEngine.Debug.Log("rbnd: " + rbnd); 400 | 401 | // This is the same as leftRegion(rbnd) 402 | // This Site determines the region containing the new site 403 | bottomSite = RightRegion(lbnd, bottomMostSite); 404 | //UnityEngine.Debug.Log("new Site is in region of existing site: " + bottomSite); 405 | 406 | // Step 9 407 | Profiler.BeginSample("CreateBisectingEdge"); 408 | edge = Edge.CreateBisectingEdge(bottomSite, newSite); 409 | Profiler.EndSample(); 410 | //UnityEngine.Debug.Log("new edge: " + edge); 411 | Edges.Add(edge); 412 | 413 | bisector = Halfedge.Create(edge, false); 414 | halfEdges.Add(bisector); 415 | // Inserting two halfedges into edgelist constitutes Step 10: 416 | // Insert bisector to the right of lbnd: 417 | edgeList.Insert(lbnd, bisector); 418 | 419 | // First half of Step 11: 420 | if ((vertex = Vertex.Intersect(lbnd, bisector)) != null) 421 | { 422 | vertices.Add(vertex); 423 | heap.Remove(lbnd); 424 | lbnd.vertex = vertex; 425 | lbnd.ystar = vertex.y + newSite.Dist(vertex); 426 | heap.Insert(lbnd); 427 | } 428 | 429 | lbnd = bisector; 430 | bisector = Halfedge.Create(edge, true); 431 | halfEdges.Add(bisector); 432 | // Second halfedge for Step 10:: 433 | // Insert bisector to the right of lbnd: 434 | edgeList.Insert(lbnd, bisector); 435 | 436 | // Second half of Step 11: 437 | if ((vertex = Vertex.Intersect(bisector, rbnd)) != null) 438 | { 439 | vertices.Add(vertex); 440 | bisector.vertex = vertex; 441 | bisector.ystar = vertex.y + newSite.Dist(vertex); 442 | heap.Insert(bisector); 443 | } 444 | 445 | newSite = GetNextSite(); 446 | } 447 | else if (heap.count > 0) 448 | { 449 | // Intersection is smallest 450 | lbnd = heap.ExtractMin(); 451 | llbnd = lbnd.edgeListLeftNeighbor; 452 | rbnd = lbnd.edgeListRightNeighbor; 453 | rrbnd = rbnd.edgeListRightNeighbor; 454 | bottomSite = LeftRegion(lbnd, bottomMostSite); 455 | topSite = RightRegion(rbnd, bottomMostSite); 456 | // These three sites define a Delaunay triangle 457 | // (not actually using these for anything...) 458 | #if TRIANGLES 459 | triangles.Add(new Triangle(bottomSite, topSite, RightRegion(lbnd, bottomMostSite))); 460 | #endif 461 | 462 | v = lbnd.vertex; 463 | v.VertexIndex = nVertices++; 464 | lbnd.edge.SetVertex(lbnd.leftRight, v); 465 | rbnd.edge.SetVertex(rbnd.leftRight, v); 466 | edgeList.Remove(lbnd); 467 | heap.Remove(rbnd); 468 | edgeList.Remove(rbnd); 469 | leftRight = false; 470 | 471 | if (bottomSite.y > topSite.y) 472 | { 473 | tempSite = bottomSite; 474 | bottomSite = topSite; 475 | topSite = tempSite; 476 | leftRight = true; 477 | } 478 | 479 | edge = Edge.CreateBisectingEdge(bottomSite, topSite); 480 | 481 | Profiler.BeginSample("addedge"); 482 | Edges.Add(edge); 483 | Profiler.EndSample(); 484 | 485 | bisector = Halfedge.Create(edge, leftRight); 486 | 487 | halfEdges.Add(bisector); 488 | 489 | edgeList.Insert(llbnd, bisector); 490 | 491 | edge.SetVertex(!leftRight, v); 492 | 493 | if ((vertex = Vertex.Intersect(llbnd, bisector)) != null) 494 | { 495 | vertices.Add(vertex); 496 | heap.Remove(llbnd); 497 | llbnd.vertex = vertex; 498 | llbnd.ystar = vertex.y + bottomSite.Dist(vertex); 499 | heap.Insert(llbnd); 500 | } 501 | if ((vertex = Vertex.Intersect(bisector, rrbnd)) != null) 502 | { 503 | vertices.Add(vertex); 504 | bisector.vertex = vertex; 505 | bisector.ystar = vertex.y + bottomSite.Dist(vertex); 506 | heap.Insert(bisector); 507 | } 508 | } 509 | else 510 | { 511 | break; 512 | } 513 | } 514 | Profiler.EndSample(); 515 | 516 | // DISPOSE 517 | 518 | // Heap should be empty now 519 | Profiler.BeginSample("Fortunes: Heap dispose"); 520 | heap.Dispose(); 521 | Profiler.EndSample(); 522 | Profiler.BeginSample("Fortunes: Edgelist dispose"); 523 | edgeList.Dispose(); 524 | Profiler.EndSample(); 525 | 526 | Profiler.BeginSample("Fortunes: Halfedges REALLY dispose"); 527 | for (int i = 0; i < halfEdges.Count; i++) 528 | { 529 | halfEdges[i].ReallyDispose(); 530 | } 531 | halfEdges.Clear(); 532 | Profiler.EndSample(); 533 | 534 | Profiler.BeginSample("Fortunes: ClipVertices"); 535 | // we need the vertices to clip the edges 536 | for (int i = 0; i < Edges.Count; i++) 537 | { 538 | Edges[i].ClipVertices(PlotBounds, true); 539 | } 540 | Profiler.EndSample(); 541 | 542 | // But we don't actually ever use them again! 543 | if (!disposeVerticesManually) 544 | { 545 | Profiler.BeginSample("Vertices dispose"); 546 | DisposeVertices(); 547 | Profiler.EndSample(); 548 | UnityEngine.Debug.Log("Disposing vertices!"); 549 | } 550 | 551 | /* 552 | UnityEngine.Debug.Assert(Halfedge.unusedPool.Contains(lbnd), "lbnd"); 553 | UnityEngine.Debug.Assert(Halfedge.unusedPool.Contains(rbnd), "rbnd"); 554 | UnityEngine.Debug.Assert(Halfedge.unusedPool.Contains(llbnd), "llbnd"); 555 | UnityEngine.Debug.Assert(Halfedge.unusedPool.Contains(rrbnd), "rrbnd"); 556 | UnityEngine.Debug.Assert(Halfedge.unusedPool.Contains(bisector), "bisector"); 557 | */ 558 | } 559 | 560 | public void LloydRelaxation(int nbIterations) 561 | { 562 | // Reapeat the whole process for the number of iterations asked 563 | for (int i = 0; i < nbIterations; i++) 564 | { 565 | List newPoints = new List(); 566 | // Go thourgh all sites 567 | currentSiteIndex = 0; // sites.ResetListIndex(); 568 | Site site = GetNextSite(); 569 | 570 | while (site != null) 571 | { 572 | // Loop all corners of the site to calculate the centroid 573 | List region = site.Region(PlotBounds); 574 | if (region.Count < 1) 575 | { 576 | site = GetNextSite(); 577 | continue; 578 | } 579 | 580 | Vector2f centroid = Vector2f.zero; 581 | float signedArea = 0; 582 | float x0 = 0; 583 | float y0 = 0; 584 | float x1 = 0; 585 | float y1 = 0; 586 | float a = 0; 587 | // For all vertices except last 588 | for (int j = 0; j < region.Count - 1; j++) 589 | { 590 | x0 = region[j].x; 591 | y0 = region[j].y; 592 | x1 = region[j + 1].x; 593 | y1 = region[j + 1].y; 594 | a = x0 * y1 - x1 * y0; 595 | signedArea += a; 596 | centroid.x += (x0 + x1) * a; 597 | centroid.y += (y0 + y1) * a; 598 | } 599 | // Do last vertex 600 | x0 = region[region.Count - 1].x; 601 | y0 = region[region.Count - 1].y; 602 | x1 = region[0].x; 603 | y1 = region[0].y; 604 | a = x0 * y1 - x1 * y0; 605 | signedArea += a; 606 | centroid.x += (x0 + x1) * a; 607 | centroid.y += (y0 + y1) * a; 608 | 609 | signedArea *= 0.5f; 610 | centroid.x /= (6 * signedArea); 611 | centroid.y /= (6 * signedArea); 612 | // Move site to the centroid of its Voronoi cell 613 | newPoints.Add(centroid); 614 | site = GetNextSite(); 615 | } 616 | 617 | // Between each replacement of the cendroid of the cell, 618 | // we need to recompute Voronoi diagram: 619 | Rectf origPlotBounds = this.PlotBounds; 620 | //Dispose(); 621 | Clear(); 622 | Init(newPoints, origPlotBounds); 623 | } 624 | } 625 | 626 | private Site GetNextSite() 627 | { 628 | if (currentSiteIndex < sites.Count) 629 | return sites[currentSiteIndex++]; 630 | else 631 | return null; 632 | } 633 | 634 | private Site LeftRegion(Halfedge he, Site bottomMostSite) 635 | { 636 | Edge edge = he.edge; 637 | if (edge == null) 638 | { 639 | return bottomMostSite; 640 | } 641 | return edge.Site(he.leftRight); 642 | } 643 | 644 | private Site RightRegion(Halfedge he, Site bottomMostSite) 645 | { 646 | Edge edge = he.edge; 647 | if (edge == null) 648 | { 649 | return bottomMostSite; 650 | } 651 | return edge.Site(!he.leftRight); 652 | } 653 | 654 | public static int CompareByYThenX(Site s1, Site s2) 655 | { 656 | if (s1.y < s2.y) return -1; 657 | if (s1.y > s2.y) return 1; 658 | if (s1.x < s2.x) return -1; 659 | if (s1.x > s2.x) return 1; 660 | return 0; 661 | } 662 | 663 | public static int CompareByYThenX(Site s1, Vector2f s2) 664 | { 665 | if (s1.y < s2.y) return -1; 666 | if (s1.y > s2.y) return 1; 667 | if (s1.x < s2.x) return -1; 668 | if (s1.x > s2.x) return 1; 669 | return 0; 670 | } 671 | } 672 | } 673 | -------------------------------------------------------------------------------- /Delaunay/Voronoi.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a2a070e24e4455843a05b80fa7026324 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Geom.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 304f54b8b2afcca4090dde6f8625e3f6 3 | folderAsset: yes 4 | timeCreated: 1471574678 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Geom/Circle.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace csDelaunay { 4 | public class Circle { 5 | 6 | public Vector2f center; 7 | public float radius; 8 | 9 | public Circle(float centerX, float centerY, float radius) { 10 | this.center = new Vector2f(centerX, centerY); 11 | this.radius = radius; 12 | } 13 | 14 | public override string ToString () { 15 | return "Circle (center: " + center + "; radius: " + radius + ")"; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Geom/Circle.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e6cd446e3e286864dac796e11ab27a4d 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Geom/LineSegment.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace csDelaunay 5 | { 6 | 7 | public class LineSegment 8 | { 9 | 10 | public static List VisibleLineSegments(List edges) 11 | { 12 | List segments = new List(); 13 | 14 | foreach (Edge edge in edges) 15 | { 16 | if (edge.Clipped) 17 | { 18 | Vector2f p1 = edge.ClippedEnds[0]; 19 | Vector2f p2 = edge.ClippedEnds[1]; 20 | segments.Add(new LineSegment(p1, p2)); 21 | } 22 | } 23 | 24 | return segments; 25 | } 26 | 27 | public static float CompareLengths_MAX(LineSegment segment0, LineSegment segment1) 28 | { 29 | float length0 = (segment0.p0 - segment0.p1).magnitude; 30 | float length1 = (segment1.p0 - segment1.p1).magnitude; 31 | if (length0 < length1) 32 | { 33 | return 1; 34 | } 35 | if (length0 > length1) 36 | { 37 | return -1; 38 | } 39 | return 0; 40 | } 41 | 42 | public static float CompareLengths(LineSegment edge0, LineSegment edge1) 43 | { 44 | return -CompareLengths_MAX(edge0, edge1); 45 | } 46 | 47 | public Vector2f p0; 48 | public Vector2f p1; 49 | 50 | public LineSegment(Vector2f p0, Vector2f p1) 51 | { 52 | this.p0 = p0; 53 | this.p1 = p1; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Geom/LineSegment.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fc68b58b0bdde004fb9ce932e00ea706 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Geom/Polygon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace csDelaunay { 6 | public class Polygon { 7 | 8 | private List vertices; 9 | 10 | public Polygon(List vertices) { 11 | this.vertices = vertices; 12 | } 13 | 14 | public float Area() { 15 | return Math.Abs(SignedDoubleArea() * 0.5f); 16 | } 17 | 18 | public Winding PolyWinding() { 19 | float signedDoubleArea = SignedDoubleArea(); 20 | if (signedDoubleArea < 0) { 21 | return Winding.CLOCKWISE; 22 | } 23 | if (signedDoubleArea > 0) { 24 | return Winding.COUNTERCLOCKWISE; 25 | } 26 | return Winding.NONE; 27 | } 28 | 29 | private float SignedDoubleArea() { 30 | int index, nextIndex; 31 | int n = vertices.Count; 32 | Vector2f point, next; 33 | float signedDoubleArea = 0; 34 | 35 | for (index = 0; index < n; index++) { 36 | nextIndex = (index+1) % n; 37 | point = vertices[index]; 38 | next = vertices[nextIndex]; 39 | signedDoubleArea += point.x * next.y - next.x * point.y; 40 | } 41 | 42 | return signedDoubleArea; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Geom/Polygon.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 79392f9de9dbd4845b9a4642a684bb35 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Geom/Rectf.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public struct Rectf { 5 | 6 | public static readonly Rectf zero = new Rectf(0,0,0,0); 7 | public static readonly Rectf one = new Rectf(1,1,1,1); 8 | 9 | public float x,y,width,height; 10 | 11 | public Rectf(float x, float y, float width, float height) { 12 | this.x = x; 13 | this.y = y; 14 | this.width = width; 15 | this.height = height; 16 | } 17 | 18 | public float left { 19 | get { 20 | return x;} 21 | } 22 | 23 | public float right { 24 | get { 25 | return x+width; 26 | } 27 | } 28 | 29 | public float top { 30 | get { 31 | return y; 32 | } 33 | } 34 | 35 | public float bottom { 36 | get { 37 | return y+height; 38 | } 39 | } 40 | 41 | public Vector2f topLeft { 42 | get { 43 | return new Vector2f(left, top); 44 | } 45 | } 46 | 47 | public Vector2f bottomRight { 48 | get { 49 | return new Vector2f(right, bottom); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Geom/Rectf.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7cede398f4bee9743b2dc0ebe14a1cd6 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Geom/Vector2f.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // Recreation of the UnityEngine.Vector3, so it can be used in other thread 4 | public struct Vector2f : IEquatable 5 | { 6 | 7 | public float x, y; 8 | 9 | public static readonly Vector2f zero = new Vector2f(0, 0); 10 | public static readonly Vector2f one = new Vector2f(1, 1); 11 | 12 | public static readonly Vector2f right = new Vector2f(1, 0); 13 | public static readonly Vector2f left = new Vector2f(-1, 0); 14 | 15 | public static readonly Vector2f up = new Vector2f(0, 1); 16 | public static readonly Vector2f down = new Vector2f(0, -1); 17 | 18 | public Vector2f(float x, float y) { 19 | this.x = x; 20 | this.y = y; 21 | } 22 | public Vector2f(double x, double y) { 23 | this.x = (float)x; 24 | this.y = (float)y; 25 | } 26 | 27 | public float magnitude { 28 | get { 29 | return (float)Math.Sqrt(x * x + y * y); 30 | } 31 | } 32 | 33 | public void Normalize() { 34 | float magnitude = this.magnitude; 35 | x /= magnitude; 36 | y /= magnitude; 37 | } 38 | 39 | public static Vector2f Normalize(Vector2f a) { 40 | float magnitude = a.magnitude; 41 | return new Vector2f(a.x / magnitude, a.y / magnitude); 42 | } 43 | 44 | public override bool Equals(object other) { 45 | if (!(other is Vector2f)) { 46 | return false; 47 | } 48 | Vector2f v = (Vector2f)other; 49 | return x == v.x && 50 | y == v.y; 51 | } 52 | 53 | public override string ToString() { 54 | return string.Format("[Vector2f]" + x + "," + y); 55 | } 56 | 57 | public override int GetHashCode() { 58 | return x.GetHashCode() ^ y.GetHashCode() << 2; 59 | } 60 | 61 | public float DistanceSquare(Vector2f v) { 62 | return Vector2f.DistanceSquare(this, v); 63 | } 64 | public static float DistanceSquare(Vector2f a, Vector2f b) { 65 | float cx = b.x - a.x; 66 | float cy = b.y - a.y; 67 | return cx * cx + cy * cy; 68 | } 69 | 70 | public static bool operator ==(Vector2f a, Vector2f b) { 71 | return a.x == b.x && 72 | a.y == b.y; 73 | } 74 | 75 | public static bool operator !=(Vector2f a, Vector2f b) { 76 | return a.x != b.x || 77 | a.y != b.y; 78 | } 79 | 80 | public static Vector2f operator -(Vector2f a, Vector2f b) { 81 | return new Vector2f(a.x - b.x, a.y - b.y); 82 | } 83 | 84 | public static Vector2f operator +(Vector2f a, Vector2f b) { 85 | return new Vector2f(a.x + b.x, a.y + b.y); 86 | } 87 | 88 | public static Vector2f operator *(Vector2f a, int i) { 89 | return new Vector2f(a.x * i, a.y * i); 90 | } 91 | 92 | public static Vector2f operator *(Vector2f a, float scalar) { 93 | return new Vector2f(a.x * scalar, a.y * scalar); 94 | } 95 | 96 | public static Vector2f operator /(Vector2f a, float scalar) { 97 | return new Vector2f(a.x / scalar, a.y / scalar); 98 | } 99 | 100 | public static Vector2f Min(Vector2f a, Vector2f b) { 101 | return new Vector2f(Math.Min(a.x, b.x), Math.Min(a.y, b.y)); 102 | } 103 | public static Vector2f Max(Vector2f a, Vector2f b) { 104 | return new Vector2f(Math.Max(a.x, b.x), Math.Max(a.y, b.y)); 105 | } 106 | 107 | public bool Equals(Vector2f other) 108 | { 109 | return x == other.x && 110 | y == other.y; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Geom/Vector2f.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 27fcd631d79e88446875babcfd859977 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Geom/Winding.cs: -------------------------------------------------------------------------------- 1 | namespace csDelaunay { 2 | 3 | public enum Winding { 4 | CLOCKWISE, COUNTERCLOCKWISE, NONE 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Geom/Winding.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0af82637c84ba8e488ff7c5bc07ed6c4 3 | timeCreated: 1471574678 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | A GC alloc free version of [csDelaunay by PouletFrit](https://github.com/PouletFrit/csDelaunay) optimised for runtime generation 6 | 7 | This version has breaking changes between the original, so it is not necesarily a "fork". Since this is used for a personal project where only constructing voronoi diagrams and retrieving clipped edges was needed, all other features are not tested nor guaranteed to be GC alloc free (like LloydRelaxation), and some methods may be removed or commented out. 8 | 9 | ## How to use alloc free 10 | To make sure the code runs alloc-free you must initialize element pools with capacities suited to your [maximum] number of sites. This process is not required, but every time the lists and queues get extended, you will have a GC alloc, which is recommended to avoid. 11 | 12 | In the current version, Voronoi.Redo() is only guarnteed to be alloc free with a fixed number of sites 13 | #### 1. Analyse capacities 14 | You can find out the required capacities by creating a test voronoi diagram and rebuilding it many times. At the end call `voronoi.DebugCapacities()`, for example: 15 | To make the code run alloc-free you must initialize element pools with capacities suited to your [maximum] number of sites. You can find out the required capacities by creating a test voronoi diagram and rebuild it many times, at the end call `voronoi.DebugCapacities()` like so: 16 | 17 | ``` 18 | void AnalyseCapacities() 19 | { 20 | voronoi = new Voronoi(points, new Rectf(0, 0, 2, 2)); 21 | 22 | for (int i = 0; i < 1000; i++) 23 | { 24 | CreateRandomPoints(points); 25 | voronoi.Redo(points, new Rectf(0, 0, 2, 2)); 26 | } 27 | 28 | Debug.Log(voronoi.DebugCapacities()); 29 | } 30 | ``` 31 | You might get a result like this: 32 | ``` 33 | Sites: 50, Halfedges: 217, Edges: 138, EdgesPerSite: 20, Vertices: 153 34 | ``` 35 | (also, alternatively, see Test/CapacityTest examples, you can use the TestRunner to run and view the results) 36 | #### 2. Init pools with capacities 37 | These are your maximum capacities. Now, take the last 3 numbers (and add a with a few more as a margin, just in case) and call `InitPools` before you create your Voronoi: 38 | ``` 39 | Voronoi.InitPools(225, 150, 25, 160); 40 | voronoi = new Voronoi(points, new Rectf(0, 0, 2, 2)); 41 | ``` 42 | #### 3. Profit 43 | Now, next time you call: 44 | ``` 45 | voronoi.Redo(points, new Rectf(0,0,2,2)); 46 | ``` 47 | You should have no GC allocs at all! 48 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7dad5581300f19f4c8c8c63c1959f63e 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 166913d585935de4faefcfd130548ef1 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/CapacityTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | using UnityEngine; 5 | using UnityEngine.TestTools; 6 | 7 | namespace csDelaunay.Tests 8 | { 9 | public class CapacityTest 10 | { 11 | [Test] 12 | public void _100_PointsTest() 13 | { 14 | Voronoi.FlushPools(); 15 | 16 | var points = VoronoiTest.CreateRandomPoints(100); 17 | var voronoi = VoronoiTest.TestVoronoi(points); 18 | 19 | Debug.Log(voronoi.DebugCapacities()); 20 | } 21 | 22 | [Test] 23 | public void _400_PointsTest() 24 | { 25 | Voronoi.FlushPools(); 26 | 27 | var points = VoronoiTest.CreateRandomPoints(400); 28 | var voronoi = VoronoiTest.TestVoronoi(points); 29 | 30 | Debug.Log(voronoi.DebugCapacities()); 31 | } 32 | 33 | [Test] 34 | public void _1000_PointsTest() 35 | { 36 | Voronoi.FlushPools(); 37 | 38 | var points = VoronoiTest.CreateRandomPoints(1000); 39 | var voronoi = VoronoiTest.TestVoronoi(points); 40 | 41 | Debug.Log(voronoi.DebugCapacities()); 42 | } 43 | 44 | [Test] 45 | public void _2000_PointsTest() 46 | { 47 | Voronoi.FlushPools(); 48 | 49 | var points = VoronoiTest.CreateRandomPoints(2000); 50 | var voronoi = VoronoiTest.TestVoronoi(points); 51 | 52 | Debug.Log(voronoi.DebugCapacities()); 53 | } 54 | 55 | [Test] 56 | public void _10000_Points_Test() 57 | { 58 | Voronoi.FlushPools(); 59 | 60 | var points = VoronoiTest.CreateRandomPoints(10000); 61 | var voronoi = VoronoiTest.TestVoronoi(points); 62 | 63 | Debug.Log(voronoi.DebugCapacities()); 64 | } 65 | 66 | [UnityTest] 67 | public IEnumerator SKIPFRAME() 68 | { 69 | yield return null; 70 | yield return null; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Tests/CapacityTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f68c023300753e845a3dc06e6ef38b8c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/EdgeReordererTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | using UnityEngine; 5 | using UnityEngine.TestTools; 6 | using UnityEngine.Profiling; 7 | 8 | namespace csDelaunay.Tests 9 | { 10 | 11 | public class EdgeReordererTest 12 | { 13 | [UnityTest] 14 | public IEnumerator AllocTest() 15 | { 16 | Profiler.BeginSample("Initing"); 17 | Random.InitState(10); 18 | var points = VoronoiTest.CreateRandomPoints(50); 19 | Voronoi voronoi = VoronoiTest.TestVoronoi(points); 20 | List edges = voronoi.sites[0].Edges; 21 | List edgeOrientations = new List(edges.Count); 22 | Profiler.EndSample(); 23 | 24 | //var reorderedEdges = er.Edges; 25 | List originalEdges = new List(); 26 | originalEdges.AddRange(edges); 27 | 28 | yield return null; 29 | 30 | Profiler.BeginSample("EdgeReorderer create"); 31 | EdgeReorderer.CreateInstance(); 32 | Profiler.EndSample(); 33 | 34 | yield return null; 35 | 36 | Profiler.BeginSample("First EdgeReorderer reorder"); 37 | EdgeReorderer.Reorder(ref edges, ref edgeOrientations, typeof(Vertex)); 38 | Profiler.EndSample(); 39 | 40 | yield return null; 41 | 42 | Profiler.BeginSample("NoGC EdgeReorderer reorder"); 43 | EdgeReorderer.Reorder(ref edges, ref edgeOrientations, typeof(Vertex)); 44 | Profiler.EndSample(); 45 | 46 | yield return null; 47 | 48 | edges = new List(); 49 | edges.AddRange(originalEdges); 50 | 51 | Profiler.BeginSample("NoGC EdgeReorder another reorder"); 52 | EdgeReorderer.Reorder(ref edges, ref edgeOrientations, typeof(Vertex)); 53 | Profiler.EndSample(); 54 | 55 | yield return null; 56 | 57 | Assert.Greater(originalEdges.Count, 0); 58 | Assert.IsNotNull(edges); 59 | Assert.IsNotNull(originalEdges); 60 | Assert.AreEqual(edges.Count, originalEdges.Count); 61 | 62 | yield return null; 63 | } 64 | 65 | [UnityTest] 66 | public IEnumerator CapacitiesInspect() 67 | { 68 | Debug.LogWarning(EdgeReorderer.GetCapacities()); 69 | 70 | yield return null; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /Tests/EdgeReordererTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b125814a2301fe74dbb4d12a95d07088 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/GCAllocsTest.cs: -------------------------------------------------------------------------------- 1 | /// Note, these tests must be performed manually by looking into the profiler, 2 | /// because there is no way to assert GC allocations automatically 3 | /// 1. Run the tests 4 | /// 2. Go to the profiler window and type in "NoGC" in the search 5 | /// 3. Scroll through the Profiler frames and see if any methods starting with "NoGC" has more than 0 GCAllocs 6 | 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using NUnit.Framework; 10 | using UnityEngine; 11 | using UnityEngine.TestTools; 12 | using UnityEngine.Profiling; 13 | 14 | namespace csDelaunay.Tests 15 | { 16 | 17 | public class GCAllocsTest 18 | { 19 | [UnityTest] 20 | public IEnumerator RedoSame20TimesNoGCAllocsTest() 21 | { 22 | Voronoi.FlushPools(); 23 | Voronoi.InitPools(500, 500, 40, 200); 24 | 25 | var points = VoronoiTest.CreateRandomPoints(50); 26 | 27 | yield return null; 28 | 29 | Voronoi voronoi = VoronoiTest.TestVoronoi(points); 30 | 31 | yield return null; 32 | 33 | for (int i = 0; i < 20; i++) 34 | { 35 | Profiler.BeginSample("NoGC Voronoi.Redo same points"); 36 | voronoi.Redo(points, VoronoiTest.TestBounds()); 37 | Profiler.EndSample(); 38 | yield return null; 39 | } 40 | 41 | Debug.Log(voronoi.DebugCapacities()); 42 | 43 | yield return null; 44 | } 45 | 46 | [UnityTest] 47 | public IEnumerator RedoRandom20TimesNoGCAllocsTest() 48 | { 49 | Voronoi.FlushPools(); 50 | Voronoi.InitPools(500, 500, 40, 200); 51 | 52 | var points = VoronoiTest.CreateRandomPoints(50); 53 | 54 | Voronoi voronoi = VoronoiTest.TestVoronoi(points); 55 | 56 | yield return null; 57 | 58 | for (int i = 0; i < 20; i++) 59 | { 60 | points = VoronoiTest.CreateRandomPoints(50); 61 | 62 | Profiler.BeginSample("NoGC Voronoi.Redo 20x50 random points"); 63 | voronoi.Redo(points, VoronoiTest.TestBounds()); 64 | Profiler.EndSample(); 65 | yield return null; 66 | } 67 | 68 | Debug.Log(voronoi.DebugCapacities()); 69 | 70 | yield return null; 71 | } 72 | 73 | [UnityTest] 74 | public IEnumerator RedoRandom200TimesWith200PointsTest() 75 | { 76 | Voronoi.FlushPools(); 77 | Voronoi.InitPools(900, 600, 40, 800); 78 | 79 | var points = VoronoiTest.CreateRandomPoints(200); 80 | 81 | Voronoi voronoi = VoronoiTest.TestVoronoi(points); 82 | 83 | yield return null; 84 | 85 | for (int i = 0; i < 200; i++) 86 | { 87 | points = VoronoiTest.CreateRandomPoints(200); 88 | 89 | Profiler.BeginSample("NoGC Voronoi.Redo 200x200 random points"); 90 | voronoi.Redo(points, VoronoiTest.TestBounds()); 91 | Profiler.EndSample(); 92 | yield return null; 93 | } 94 | 95 | Debug.Log(voronoi.DebugCapacities()); 96 | 97 | yield return null; 98 | } 99 | 100 | [UnityTest] 101 | public IEnumerator RedoRandom20TimesWith3000PointsTest() 102 | { 103 | Voronoi.FlushPools(); 104 | Voronoi.InitPools(12500, 9000, 40, 11000); 105 | 106 | var points = VoronoiTest.CreateRandomPoints(3000); 107 | 108 | Voronoi voronoi = VoronoiTest.TestVoronoi(points); 109 | 110 | yield return null; 111 | 112 | for (int i = 0; i < 20; i++) 113 | { 114 | points = VoronoiTest.CreateRandomPoints(3000); 115 | 116 | Profiler.BeginSample("NoGC Voronoi.Redo 20x3000 random points"); 117 | voronoi.Redo(points, VoronoiTest.TestBounds()); 118 | Profiler.EndSample(); 119 | yield return null; 120 | } 121 | 122 | Debug.Log(voronoi.DebugCapacities()); 123 | 124 | yield return null; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Tests/GCAllocsTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c3fe1458eb7a5fa4bb7c0ecfe95ca573 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/VoronoiTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | using UnityEngine; 5 | using UnityEngine.TestTools; 6 | 7 | namespace csDelaunay.Tests 8 | { 9 | public class VoronoiTest 10 | { 11 | [Test] 12 | public void PlotBoundsEqualityTest() 13 | { 14 | Random.InitState(10); 15 | var points = CreateRandomPoints(50); 16 | Voronoi voronoi = TestVoronoi(points); 17 | 18 | Rectf plotBounds = voronoi.PlotBounds; 19 | 20 | voronoi.Redo(points, TestBounds()); 21 | 22 | Rectf plotBounds2 = voronoi.PlotBounds; 23 | 24 | //Debug.Log($"Plot bounds: {plotBounds.x}, { plotBounds.y}, { plotBounds.width}, {plotBounds.height}"); 25 | 26 | Assert.AreEqual(plotBounds.x, plotBounds2.x, "Plot bounds x not equal"); 27 | Assert.AreEqual(plotBounds.y, plotBounds2.y, "Plot bounds y not equal"); 28 | Assert.AreEqual(plotBounds.width, plotBounds2.width, "Plot bounds width not equal"); 29 | Assert.AreEqual(plotBounds.height, plotBounds2.height, "Plot bounds height not equal"); 30 | } 31 | 32 | [Test] 33 | public void FirstClippedEdgeEqualityTest() 34 | { 35 | Random.InitState(10); 36 | var points = CreateRandomPoints(50); 37 | Voronoi voronoi = TestVoronoi(points); 38 | 39 | Rectf plotBounds = voronoi.PlotBounds; 40 | 41 | // first clipped edge 42 | Edge clippedEdge = GetFirstClippedEdge(voronoi); 43 | 44 | Vector2f e1v1 = clippedEdge.ClippedEnds[0]; 45 | 46 | voronoi.Redo(points, TestBounds()); 47 | 48 | // first clipped edge 49 | Edge clippedEdge2 = GetFirstClippedEdge(voronoi); 50 | 51 | Vector2f e2v1 = clippedEdge2.ClippedEnds[0]; 52 | 53 | Assert.AreEqual(e1v1, e2v1); 54 | } 55 | 56 | [Test] 57 | public void GetRegionFromDictTest() 58 | { 59 | Random.InitState(10); 60 | var points = CreateRandomPoints(50); 61 | Voronoi voronoi = TestVoronoi(points); 62 | 63 | var site = voronoi.sites[0]; 64 | Vector2f position = site.Coord; 65 | 66 | var siteTest = voronoi.SitesIndexedByLocation[position]; 67 | 68 | Assert.AreEqual(site, siteTest); 69 | } 70 | 71 | [Test] 72 | public void SameRegionFromRedoDiagramByLocationTest() 73 | { 74 | Random.InitState(10); 75 | var points = CreateRandomPoints(50); 76 | Voronoi voronoi = TestVoronoi(points); 77 | 78 | var site = voronoi.sites[0]; 79 | Vector2f site1coord = site.Coord; 80 | 81 | // redo multiple times 82 | for (int i = 0; i < 100; i++) 83 | { 84 | voronoi.Redo(points, TestBounds()); 85 | } 86 | 87 | var siteTest = voronoi.SitesIndexedByLocation[site1coord]; 88 | Vector2f site2coord = siteTest.Coord; 89 | 90 | Assert.AreEqual(site1coord, site2coord); 91 | } 92 | 93 | [Test] 94 | public void SameClippedVerticesAfterRedo() 95 | { 96 | Random.InitState(10); 97 | var points = CreateRandomPoints(50); 98 | Voronoi voronoi = TestVoronoi(points); 99 | 100 | var edges = new List(); 101 | voronoi.GetAllClippedLines(edges); 102 | 103 | // redo multiple times 104 | for (int i = 0; i < 100; i++) 105 | { 106 | voronoi.Redo(points, TestBounds()); 107 | } 108 | 109 | var edges2 = new List(); 110 | voronoi.GetAllClippedLines(edges2); 111 | 112 | for (int i = 0; i < edges.Count; i++) 113 | { 114 | Assert.AreEqual(edges[i], edges2[i]); 115 | } 116 | } 117 | 118 | 119 | 120 | [UnityTest] 121 | public IEnumerator SKIPFRAME() 122 | { 123 | yield return null; 124 | yield return null; 125 | } 126 | 127 | 128 | 129 | // Utilities: 130 | 131 | Edge GetFirstClippedEdge(Voronoi voronoi) 132 | { 133 | foreach (var edge in voronoi.Edges) 134 | { 135 | if (edge.Clipped) 136 | { 137 | return edge; 138 | } 139 | } 140 | 141 | return null; 142 | } 143 | 144 | public static Voronoi TestVoronoi(List points) { return new Voronoi(points, TestBounds()); } 145 | public static Rectf TestBounds() { return new Rectf(0, 0, 2, 2); } 146 | public static Vector2f MidPoint() { return new Vector2f(1, 1); } 147 | 148 | public static List CreateRandomPoints(int num) 149 | { 150 | List points = new List(num); 151 | for (int i = 0; i < num; i++) 152 | { 153 | Vector2 rv = Vector2.one + Random.insideUnitCircle * 0.9f; 154 | points.Add(new Vector2f(rv.x, rv.y)); 155 | } 156 | 157 | return points; 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Tests/VoronoiTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7d9e4661b6ccea9488548b1086c71ebd 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/csDelaunay.Tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csDelaunay.Tests", 3 | "references": [ 4 | "csDelaunay" 5 | ], 6 | "optionalUnityReferences": [ 7 | "TestAssemblies" 8 | ], 9 | "includePlatforms": [], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [] 16 | } -------------------------------------------------------------------------------- /Tests/csDelaunay.Tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0d3531b6263455045a64da331053600d 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /csDelaunay.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csDelaunay" 3 | } 4 | -------------------------------------------------------------------------------- /csDelaunay.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 63fa820125676494f927316b9b02ee28 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------