├── .gitignore ├── Delaunay ├── Edge.cs ├── EdgeList.cs ├── EdgeReorderer.cs ├── Halfedge.cs ├── HalfedgePriorityQueue.cs ├── ICoord.cs ├── LR.cs ├── LRCollection.cs ├── Site.cs ├── SiteList.cs ├── Triangle.cs ├── Vertex.cs └── Voronoi.cs ├── Geom ├── Circle.cs ├── LineSegment.cs ├── Polygon.cs ├── Rectf.cs └── Winding.cs ├── LICENSE ├── README.md ├── RELEASE_NOTES.md ├── build.cmd ├── csDelaunay.csproj ├── csDelaunay.sln ├── directory.build.props ├── init.cmd ├── paket.dependencies ├── paket.lock └── paket.references /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignoreable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | node_modules/ 203 | orleans.codegen.cs 204 | 205 | # Since there are multiple workflows, uncomment next line to ignore bower_components 206 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 207 | #bower_components/ 208 | 209 | # RIA/Silverlight projects 210 | Generated_Code/ 211 | 212 | # Backup & report files from converting an old project file 213 | # to a newer Visual Studio version. Backup files are not needed, 214 | # because we have git ;-) 215 | _UpgradeReport_Files/ 216 | Backup*/ 217 | UpgradeLog*.XML 218 | UpgradeLog*.htm 219 | 220 | # SQL Server files 221 | *.mdf 222 | *.ldf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | 238 | # Visual Studio 6 build log 239 | *.plg 240 | 241 | # Visual Studio 6 workspace options file 242 | *.opt 243 | 244 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 245 | *.vbw 246 | 247 | # Visual Studio LightSwitch build output 248 | **/*.HTMLClient/GeneratedArtifacts 249 | **/*.DesktopClient/GeneratedArtifacts 250 | **/*.DesktopClient/ModelManifest.xml 251 | **/*.Server/GeneratedArtifacts 252 | **/*.Server/ModelManifest.xml 253 | _Pvt_Extensions 254 | 255 | # Paket dependency manager 256 | .paket/ 257 | paket-files/ 258 | 259 | # FAKE - F# Make 260 | .fake/ 261 | 262 | # JetBrains Rider 263 | .idea/ 264 | *.sln.iml 265 | 266 | # CodeRush 267 | .cr/ 268 | 269 | # Python Tools for Visual Studio (PTVS) 270 | __pycache__/ 271 | *.pyc 272 | 273 | # Cake - Uncomment if you are using it 274 | # tools/** 275 | # !tools/packages.config 276 | 277 | # Visual Studio Code 278 | .ionide -------------------------------------------------------------------------------- /Delaunay/Edge.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Numerics; 3 | 4 | namespace csDelaunay { 5 | 6 | /// The line segment connecting the two Sites is part of the Delaunay triangulation 7 | /// The line segment connecting the two Vertices is part of the Voronoi diagram 8 | public class Edge { 9 | 10 | #region Pool 11 | private static Queue pool = new Queue(); 12 | 13 | private static int nEdges = 0; 14 | 15 | /// This is the only way to create a new Edge 16 | public static Edge CreateBisectingEdge(Site s0, Site s1) { 17 | float dx, dy; 18 | float absdx, absdy; 19 | float a, b, c; 20 | 21 | dx = s1.x - s0.x; 22 | dy = s1.y - s0.y; 23 | absdx = dx > 0 ? dx : -dx; 24 | absdy = dy > 0 ? dy : -dy; 25 | c = s0.x * dx + s0.y * dy + (dx*dx + dy*dy) * 0.5f; 26 | 27 | if (absdx > absdy) { 28 | a = 1; 29 | b = dy/dx; 30 | c /= dx; 31 | } else { 32 | b = 1; 33 | a = dx/dy; 34 | c/= dy; 35 | } 36 | 37 | Edge edge = Edge.Create(); 38 | 39 | edge.LeftSite = s0; 40 | edge.RightSite = s1; 41 | s0.AddEdge(edge); 42 | s1.AddEdge(edge); 43 | 44 | edge.a = a; 45 | edge.b = b; 46 | edge.c = c; 47 | 48 | return edge; 49 | } 50 | 51 | private static Edge Create() { 52 | Edge edge; 53 | if (pool.Count > 0) { 54 | edge = pool.Dequeue(); 55 | edge.Init(); 56 | } else { 57 | edge = new Edge(); 58 | } 59 | 60 | return edge; 61 | } 62 | #endregion 63 | 64 | public static List SelectEdgesForSitePoint(Vector2 coord, List edgesToTest) { 65 | return edgesToTest.FindAll( 66 | delegate(Edge e) { 67 | if (e.LeftSite != null) { 68 | if (e.LeftSite.Coord == coord) return true; 69 | } 70 | if (e.RightSite != null) { 71 | if (e.RightSite.Coord == coord) return true; 72 | } 73 | return false; 74 | }); 75 | } 76 | 77 | public static readonly Edge DELETED = new Edge(); 78 | 79 | #region Object 80 | // The equation of the edge: ax + by = c 81 | public float a,b,c; 82 | 83 | // The two Voronoi vertices that the edge connects (if one of them is null, the edge extends to infinity) 84 | private Vertex leftVertex; 85 | public Vertex LeftVertex {get{return leftVertex;}} 86 | 87 | private Vertex rightVertex; 88 | public Vertex RightVertex {get{return rightVertex;}} 89 | 90 | public Vertex Vertex(LR leftRight) { 91 | return leftRight == LR.LEFT ? leftVertex : rightVertex; 92 | } 93 | 94 | public void SetVertex(LR leftRight, Vertex v) { 95 | if (leftRight == LR.LEFT) { 96 | leftVertex = v; 97 | } else { 98 | rightVertex = v; 99 | } 100 | } 101 | 102 | public bool IsPartOfConvexHull() { 103 | return leftVertex == null || rightVertex == null; 104 | } 105 | 106 | public float SitesDistance() { 107 | return (LeftSite.Coord - RightSite.Coord).Length(); 108 | } 109 | 110 | public static int CompareSitesDistances_MAX(Edge edge0, Edge edge1) { 111 | float length0 = edge0.SitesDistance(); 112 | float length1 = edge1.SitesDistance(); 113 | if (length0 < length1) { 114 | return 1; 115 | } 116 | if (length0 > length1) { 117 | return -1; 118 | } 119 | return 0; 120 | } 121 | 122 | public static int CompareSitesDistances(Edge edge0, Edge edge1) { 123 | return - CompareSitesDistances_MAX(edge0,edge1); 124 | } 125 | 126 | // Once clipVertices() is called, this Disctinary will hold two Points 127 | // representing the clipped coordinates of the left and the right ends... 128 | private LRCollection clippedVertices; 129 | public LRCollection ClippedEnds {get{return clippedVertices;}} 130 | 131 | // Unless the entire Edge is outside the bounds. 132 | // In that case visible will be false: 133 | public bool Visible() { 134 | return clippedVertices != null; 135 | } 136 | 137 | // The two input Sites for which this Edge is a bisector: 138 | private LRCollection sites; 139 | public Site LeftSite {get{return sites[LR.LEFT];} set{sites[LR.LEFT]=value;}} 140 | public Site RightSite {get{return sites[LR.RIGHT];} set{sites[LR.RIGHT]=value;}} 141 | 142 | public Site Site(LR leftRight) { 143 | return sites[leftRight]; 144 | } 145 | 146 | private int edgeIndex; 147 | public int EdgeIndex {get{return edgeIndex;}} 148 | 149 | public void Dispose() { 150 | leftVertex = null; 151 | rightVertex = null; 152 | if (clippedVertices != null) { 153 | clippedVertices.Clear(); 154 | clippedVertices = null; 155 | } 156 | sites.Clear(); 157 | sites = null; 158 | 159 | pool.Enqueue(this); 160 | } 161 | 162 | public Edge() { 163 | edgeIndex = nEdges++; 164 | Init(); 165 | } 166 | 167 | public Edge Init() { 168 | sites = new LRCollection(); 169 | 170 | return this; 171 | } 172 | 173 | public override string ToString() { 174 | return "Edge " + edgeIndex + "; sites " + sites[LR.LEFT] + ", " + sites[LR.RIGHT] + 175 | "; endVertices " + (leftVertex != null ? leftVertex.VertexIndex.ToString() : "null") + ", " + 176 | (rightVertex != null ? rightVertex.VertexIndex.ToString() : "null") + "::"; 177 | } 178 | 179 | /* 180 | * Set clippedVertices to contain the two ends of the portion of the Voronoi edge that is visible 181 | * within the bounds. If no part of the Edge falls within the bounds, leave clippedVertices null 182 | * @param bounds 183 | */ 184 | public void ClipVertices(Rectf bounds) { 185 | float xmin = bounds.x; 186 | float ymin = bounds.y; 187 | float xmax = bounds.right; 188 | float ymax = bounds.bottom; 189 | 190 | Vertex vertex0, vertex1; 191 | float x0, x1, y0, y1; 192 | 193 | if (a == 1 && b >= 0) { 194 | vertex0 = rightVertex; 195 | vertex1 = leftVertex; 196 | } else { 197 | vertex0 = leftVertex; 198 | vertex1 = rightVertex; 199 | } 200 | 201 | if (a == 1) { 202 | y0 = ymin; 203 | if (vertex0 != null && vertex0.y > ymin) { 204 | y0 = vertex0.y; 205 | } 206 | if (y0 > ymax) { 207 | return; 208 | } 209 | x0 = c - b * y0; 210 | 211 | y1 = ymax; 212 | if (vertex1 != null && vertex1.y < ymax) { 213 | y1 = vertex1.y; 214 | } 215 | if (y1 < ymin) { 216 | return; 217 | } 218 | x1 = c - b * y1; 219 | 220 | if ((x0 > xmax && x1 > xmax) || (x0 < xmin && x1 < xmin)) { 221 | return; 222 | } 223 | 224 | if (x0 > xmax) { 225 | x0 = xmax; 226 | y0 = (c - x0)/b; 227 | } else if (x0 < xmin) { 228 | x0 = xmin; 229 | y0 = (c - x0)/b; 230 | } 231 | 232 | if (x1 > xmax) { 233 | x1 = xmax; 234 | y1 = (c - x1)/b; 235 | } else if (x1 < xmin) { 236 | x1 = xmin; 237 | y1 = (c - x1)/b; 238 | } 239 | } else { 240 | x0 = xmin; 241 | if (vertex0 != null && vertex0.x > xmin) { 242 | x0 = vertex0.x; 243 | } 244 | if (x0 > xmax) { 245 | return; 246 | } 247 | y0 = c - a * x0; 248 | 249 | x1 = xmax; 250 | if (vertex1 != null && vertex1.x < xmax) { 251 | x1 = vertex1.x; 252 | } 253 | if (x1 < xmin) { 254 | return; 255 | } 256 | y1 = c - a * x1; 257 | 258 | if ((y0 > ymax && y1 > ymax) || (y0 < ymin && y1 < ymin)) { 259 | return; 260 | } 261 | 262 | if (y0 > ymax) { 263 | y0 = ymax; 264 | x0 = (c - y0)/a; 265 | } else if (y0 < ymin) { 266 | y0 = ymin; 267 | x0 = (c - y0)/a; 268 | } 269 | 270 | if (y1 > ymax) { 271 | y1 = ymax; 272 | x1 = (c - y1)/a; 273 | } else if (y1 < ymin) { 274 | y1 = ymin; 275 | x1 = (c - y1)/a; 276 | } 277 | } 278 | 279 | clippedVertices = new LRCollection(); 280 | if (vertex0 == leftVertex) { 281 | clippedVertices[LR.LEFT] = new Vector2(x0, y0); 282 | clippedVertices[LR.RIGHT] = new Vector2(x1, y1); 283 | } else { 284 | clippedVertices[LR.RIGHT] = new Vector2(x0, y0); 285 | clippedVertices[LR.LEFT] = new Vector2(x1, y1); 286 | } 287 | } 288 | #endregion 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /Delaunay/EdgeList.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace csDelaunay { 4 | 5 | public class EdgeList { 6 | 7 | private float deltaX; 8 | private float xmin; 9 | 10 | private int hashSize; 11 | private Halfedge[] hash; 12 | private Halfedge leftEnd; 13 | public Halfedge LeftEnd {get{return leftEnd;}} 14 | private Halfedge rightEnd; 15 | public Halfedge RightEnd {get{return rightEnd;}} 16 | 17 | public void Dispose() { 18 | Halfedge halfedge = leftEnd; 19 | Halfedge prevHe; 20 | while (halfedge != rightEnd) { 21 | prevHe = halfedge; 22 | halfedge = halfedge.edgeListRightNeighbor; 23 | prevHe.Dispose(); 24 | } 25 | leftEnd = null; 26 | rightEnd.Dispose(); 27 | rightEnd = null; 28 | 29 | hash = null; 30 | } 31 | 32 | public EdgeList(float xmin, float deltaX, int sqrtSitesNb) { 33 | this.xmin = xmin; 34 | this.deltaX = deltaX; 35 | hashSize = 2 * sqrtSitesNb; 36 | 37 | hash = new Halfedge[hashSize]; 38 | 39 | // Two dummy Halfedges: 40 | leftEnd = Halfedge.CreateDummy(); 41 | rightEnd = Halfedge.CreateDummy(); 42 | leftEnd.edgeListLeftNeighbor = null; 43 | leftEnd.edgeListRightNeighbor = rightEnd; 44 | rightEnd.edgeListLeftNeighbor = leftEnd; 45 | rightEnd.edgeListRightNeighbor = null; 46 | hash[0] = leftEnd; 47 | hash[hashSize - 1] = rightEnd; 48 | } 49 | 50 | /* 51 | * Insert newHalfedge to the right of lb 52 | * @param lb 53 | * @param newHalfedge 54 | */ 55 | public void Insert(Halfedge lb, Halfedge newHalfedge) { 56 | newHalfedge.edgeListLeftNeighbor = lb; 57 | newHalfedge.edgeListRightNeighbor = lb.edgeListRightNeighbor; 58 | lb.edgeListRightNeighbor.edgeListLeftNeighbor = newHalfedge; 59 | lb.edgeListRightNeighbor = newHalfedge; 60 | } 61 | 62 | /* 63 | * This function only removes the Halfedge from the left-right list. 64 | * We cannot dispose it yet because we are still using it. 65 | * @param halfEdge 66 | */ 67 | public void Remove(Halfedge halfedge) { 68 | halfedge.edgeListLeftNeighbor.edgeListRightNeighbor = halfedge.edgeListRightNeighbor; 69 | halfedge.edgeListRightNeighbor.edgeListLeftNeighbor = halfedge.edgeListLeftNeighbor; 70 | halfedge.edge = Edge.DELETED; 71 | halfedge.edgeListLeftNeighbor = halfedge.edgeListRightNeighbor = null; 72 | } 73 | 74 | /* 75 | * Find the rightmost Halfedge that is still elft of p 76 | * @param p 77 | * @return 78 | */ 79 | public Halfedge EdgeListLeftNeighbor(Vector2 p) { 80 | int bucket; 81 | Halfedge halfedge; 82 | 83 | // Use hash table to get close to desired halfedge 84 | bucket = (int)((p.X - xmin)/deltaX * hashSize); 85 | if (bucket < 0) { 86 | bucket = 0; 87 | } 88 | if (bucket >= hashSize) { 89 | bucket = hashSize - 1; 90 | } 91 | halfedge = GetHash(bucket); 92 | if (halfedge == null) { 93 | for (int i = 0; true; i++) { 94 | if ((halfedge = GetHash(bucket - i)) != null) break; 95 | if ((halfedge = GetHash(bucket + i)) != null) break; 96 | } 97 | } 98 | // Now search linear list of haledges for the correct one 99 | if (halfedge == leftEnd || (halfedge != rightEnd && halfedge.IsLeftOf(p))) { 100 | do { 101 | halfedge = halfedge.edgeListRightNeighbor; 102 | } while (halfedge != rightEnd && halfedge.IsLeftOf(p)); 103 | halfedge = halfedge.edgeListLeftNeighbor; 104 | 105 | } else { 106 | do { 107 | halfedge = halfedge.edgeListLeftNeighbor; 108 | } while (halfedge != leftEnd && !halfedge.IsLeftOf(p)); 109 | } 110 | 111 | // Update hash table and reference counts 112 | if (bucket > 0 && bucket < hashSize - 1) { 113 | hash[bucket] = halfedge; 114 | } 115 | return halfedge; 116 | } 117 | 118 | // Get entry from the has table, pruning any deleted nodes 119 | private Halfedge GetHash(int b) { 120 | Halfedge halfedge; 121 | 122 | if (b < 0 || b >= hashSize) { 123 | return null; 124 | } 125 | halfedge = hash[b]; 126 | if (halfedge != null && halfedge.edge == Edge.DELETED) { 127 | // Hash table points to deleted halfedge. Patch as necessary 128 | hash[b] = null; 129 | // Still can't dispose halfedge yet! 130 | return null; 131 | } else { 132 | return halfedge; 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Delaunay/EdgeReorderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace csDelaunay { 6 | 7 | public class EdgeReorderer { 8 | 9 | private List edges; 10 | private List edgeOrientations; 11 | 12 | public List Edges {get{return edges;}} 13 | public List EdgeOrientations {get{return edgeOrientations;}} 14 | 15 | public EdgeReorderer(List origEdges, Type criterion) { 16 | edges = new List(); 17 | edgeOrientations = new List(); 18 | if (origEdges.Count > 0) { 19 | edges = ReorderEdges(origEdges, criterion); 20 | } 21 | } 22 | 23 | public void Dispose() { 24 | edges = null; 25 | edgeOrientations = null; 26 | } 27 | 28 | private List ReorderEdges(List origEdges, Type criterion) { 29 | int i; 30 | int n = origEdges.Count; 31 | Edge edge; 32 | // We're going to reorder the edges in order of traversal 33 | List done = new List(); 34 | int nDone = 0; 35 | for (int b = 0; b < n; b++) done.Add(false); 36 | List newEdges = new List(); 37 | 38 | i = 0; 39 | edge = origEdges[i]; 40 | newEdges.Add(edge); 41 | edgeOrientations.Add(LR.LEFT); 42 | ICoord firstPoint; 43 | ICoord lastPoint; 44 | if (criterion == typeof(Vertex)) { 45 | firstPoint = edge.LeftVertex; 46 | lastPoint = edge.RightVertex; 47 | } else { 48 | firstPoint = edge.LeftSite; 49 | lastPoint = edge.RightSite; 50 | } 51 | 52 | if (firstPoint == Vertex.VERTEX_AT_INFINITY || lastPoint == Vertex.VERTEX_AT_INFINITY) { 53 | return new List(); 54 | } 55 | 56 | done[i] = true; 57 | nDone++; 58 | 59 | while (nDone < n) { 60 | for (i = 1; i < n; i++) { 61 | if (done[i]) { 62 | continue; 63 | } 64 | edge = origEdges[i]; 65 | ICoord leftPoint; 66 | ICoord rightPoint; 67 | if (criterion == typeof(Vertex)) { 68 | leftPoint = edge.LeftVertex; 69 | rightPoint = edge.RightVertex; 70 | } else { 71 | leftPoint = edge.LeftSite; 72 | rightPoint = edge.RightSite; 73 | } 74 | if (leftPoint == Vertex.VERTEX_AT_INFINITY || rightPoint == Vertex.VERTEX_AT_INFINITY) { 75 | return new List(); 76 | } 77 | if (leftPoint == lastPoint) { 78 | lastPoint = rightPoint; 79 | edgeOrientations.Add(LR.LEFT); 80 | newEdges.Add(edge); 81 | done[i] = true; 82 | } else if (rightPoint == firstPoint) { 83 | firstPoint = leftPoint; 84 | edgeOrientations.Insert(0, LR.LEFT); 85 | newEdges.Insert(0, edge); 86 | done[i] = true; 87 | } else if (leftPoint == firstPoint) { 88 | firstPoint = rightPoint; 89 | edgeOrientations.Insert(0, LR.RIGHT); 90 | newEdges.Insert(0, edge); 91 | done[i] = true; 92 | } else if (rightPoint == lastPoint) { 93 | lastPoint = leftPoint; 94 | edgeOrientations.Add(LR.RIGHT); 95 | newEdges.Add(edge); 96 | done[i] = true; 97 | } 98 | if (done[i]) { 99 | nDone++; 100 | } 101 | } 102 | } 103 | return newEdges; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Delaunay/Halfedge.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Numerics; 3 | 4 | namespace csDelaunay { 5 | 6 | public class Halfedge { 7 | 8 | #region Pool 9 | private static Queue pool = new Queue(); 10 | 11 | public static Halfedge Create(Edge edge, LR lr) { 12 | if (pool.Count > 0) { 13 | return pool.Dequeue().Init(edge,lr); 14 | } else { 15 | return new Halfedge(edge,lr); 16 | } 17 | } 18 | public static Halfedge CreateDummy() { 19 | return Create(null, null); 20 | } 21 | #endregion 22 | 23 | #region Object 24 | public Halfedge edgeListLeftNeighbor; 25 | public Halfedge edgeListRightNeighbor; 26 | public Halfedge nextInPriorityQueue; 27 | 28 | public Edge edge; 29 | public LR leftRight; 30 | public Vertex vertex; 31 | 32 | // The vertex's y-coordinate in the transformed Voronoi space V 33 | public float ystar; 34 | 35 | public Halfedge(Edge edge, LR lr) { 36 | Init(edge, lr); 37 | } 38 | 39 | private Halfedge Init(Edge edge, LR lr) { 40 | this.edge = edge; 41 | leftRight = lr; 42 | nextInPriorityQueue = null; 43 | vertex = null; 44 | 45 | return this; 46 | } 47 | 48 | public override string ToString() { 49 | return "Halfedge (LeftRight: " + leftRight + "; vertex: " + vertex + ")"; 50 | } 51 | 52 | public void Dispose() { 53 | if (edgeListLeftNeighbor != null || edgeListRightNeighbor != null) { 54 | // still in EdgeList 55 | return; 56 | } 57 | if (nextInPriorityQueue != null) { 58 | // still in PriorityQueue 59 | return; 60 | } 61 | edge = null; 62 | leftRight = null; 63 | vertex = null; 64 | pool.Enqueue(this); 65 | } 66 | 67 | public void ReallyDispose() { 68 | edgeListLeftNeighbor = null; 69 | edgeListRightNeighbor = null; 70 | nextInPriorityQueue = null; 71 | edge = null; 72 | leftRight = null; 73 | vertex = null; 74 | pool.Enqueue(this); 75 | } 76 | 77 | public bool IsLeftOf(Vector2 p) { 78 | Site topSite; 79 | bool rightOfSite, above, fast; 80 | float dxp, dyp, dxs, t1, t2, t3, y1; 81 | 82 | topSite = edge.RightSite; 83 | rightOfSite = p.X > topSite.x; 84 | if (rightOfSite && this.leftRight == LR.LEFT) { 85 | return true; 86 | } 87 | if (!rightOfSite && this.leftRight == LR.RIGHT) { 88 | return false; 89 | } 90 | 91 | if (edge.a == 1) { 92 | dyp = p.Y - topSite.y; 93 | dxp = p.X - topSite.x; 94 | fast = false; 95 | if ((!rightOfSite && edge.b < 0) || (rightOfSite && edge.b >= 0)) { 96 | above = dyp >= edge.b * dxp; 97 | fast = above; 98 | } else { 99 | above = p.X + p.Y * edge.b > edge.c; 100 | if (edge.b < 0) { 101 | above = !above; 102 | } 103 | if (!above) { 104 | fast = true; 105 | } 106 | } 107 | if (!fast) { 108 | dxs = topSite.x - edge.LeftSite.x; 109 | above = edge.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1+2 * dxp/dxs + edge.b * edge.b); 110 | if (edge.b < 0) { 111 | above = !above; 112 | } 113 | } 114 | } else { 115 | y1 = edge.c - edge.a * p.X; 116 | t1 = p.Y - y1; 117 | t2 = p.X - topSite.x; 118 | t3 = y1 - topSite.y; 119 | above = t1 * t1 > t2 * t2 + t3 * t3; 120 | } 121 | return this.leftRight == LR.LEFT ? above : !above; 122 | } 123 | #endregion 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Delaunay/HalfedgePriorityQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace csDelaunay { 4 | 5 | // Also know as heap 6 | public class HalfedgePriorityQueue { 7 | 8 | private Halfedge[] hash; 9 | private int count; 10 | private int minBucked; 11 | private int hashSize; 12 | 13 | private float ymin; 14 | private float deltaY; 15 | 16 | public HalfedgePriorityQueue(float ymin, float deltaY, int sqrtSitesNb) { 17 | this.ymin = ymin; 18 | this.deltaY = deltaY; 19 | hashSize = 4 * sqrtSitesNb; 20 | Init(); 21 | } 22 | 23 | public void Dispose() { 24 | // Get rid of dummies 25 | for (int i = 0; i < hashSize; i++) { 26 | hash[i].Dispose(); 27 | } 28 | hash = null; 29 | } 30 | 31 | public void Init() { 32 | count = 0; 33 | minBucked = 0; 34 | hash = new Halfedge[hashSize]; 35 | // Dummy Halfedge at the top of each hash 36 | for (int i = 0; i < hashSize; i++) { 37 | hash[i] = Halfedge.CreateDummy(); 38 | hash[i].nextInPriorityQueue = null; 39 | } 40 | } 41 | 42 | public void Insert(Halfedge halfedge) { 43 | Halfedge previous, next; 44 | 45 | int insertionBucket = Bucket(halfedge); 46 | if (insertionBucket < minBucked) { 47 | minBucked = insertionBucket; 48 | } 49 | previous = hash[insertionBucket]; 50 | while ((next = previous.nextInPriorityQueue) != null && 51 | (halfedge.ystar > next.ystar || (halfedge.ystar == next.ystar && halfedge.vertex.x > next.vertex.x))) { 52 | previous = next; 53 | } 54 | halfedge.nextInPriorityQueue = previous.nextInPriorityQueue; 55 | previous.nextInPriorityQueue = halfedge; 56 | count++; 57 | } 58 | 59 | public void Remove(Halfedge halfedge) { 60 | Halfedge previous; 61 | int removalBucket = Bucket(halfedge); 62 | 63 | if (halfedge.vertex != null) { 64 | previous = hash[removalBucket]; 65 | while (previous.nextInPriorityQueue != halfedge) { 66 | previous = previous.nextInPriorityQueue; 67 | } 68 | previous.nextInPriorityQueue = halfedge.nextInPriorityQueue; 69 | count--; 70 | halfedge.vertex = null; 71 | halfedge.nextInPriorityQueue = null; 72 | halfedge.Dispose(); 73 | } 74 | } 75 | 76 | private int Bucket(Halfedge halfedge) { 77 | int theBucket = (int)((halfedge.ystar - ymin)/deltaY * hashSize); 78 | if (theBucket < 0) theBucket = 0; 79 | if (theBucket >= hashSize) theBucket = hashSize - 1; 80 | return theBucket; 81 | } 82 | 83 | private bool IsEmpty(int bucket) { 84 | return (hash[bucket].nextInPriorityQueue == null); 85 | } 86 | 87 | /* 88 | * move minBucket until it contains an actual Halfedge (not just the dummy at the top); 89 | */ 90 | private void AdjustMinBucket() { 91 | while (minBucked < hashSize - 1 && IsEmpty(minBucked)) { 92 | minBucked++; 93 | } 94 | } 95 | 96 | public bool Empty() { 97 | return count == 0; 98 | } 99 | 100 | /* 101 | * @return coordinates of the Halfedge's vertex in V*, the transformed Voronoi diagram 102 | */ 103 | public Vector2 Min() { 104 | AdjustMinBucket(); 105 | Halfedge answer = hash[minBucked].nextInPriorityQueue; 106 | return new Vector2(answer.vertex.x, answer.ystar); 107 | } 108 | 109 | /* 110 | * Remove and return the min Halfedge 111 | */ 112 | public Halfedge ExtractMin() { 113 | Halfedge answer; 114 | 115 | // Get the first real Halfedge in minBucket 116 | answer = hash[minBucked].nextInPriorityQueue; 117 | 118 | hash[minBucked].nextInPriorityQueue = answer.nextInPriorityQueue; 119 | count--; 120 | answer.nextInPriorityQueue = null; 121 | 122 | return answer; 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /Delaunay/ICoord.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace csDelaunay { 4 | public interface ICoord { 5 | 6 | Vector2 Coord {get;set;} 7 | } 8 | } -------------------------------------------------------------------------------- /Delaunay/LR.cs: -------------------------------------------------------------------------------- 1 | namespace csDelaunay { 2 | 3 | public class LR { 4 | 5 | public static readonly LR LEFT = new LR("left"); 6 | public static readonly LR RIGHT = new LR("right"); 7 | 8 | private string name; 9 | 10 | public LR(string name) { 11 | this.name = name; 12 | } 13 | 14 | public static LR Other(LR leftRight) { 15 | return leftRight == LEFT ? RIGHT : LEFT; 16 | } 17 | 18 | public override string ToString() { 19 | return name; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Delaunay/LRCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace csDelaunay 4 | { 5 | public class LRCollection 6 | { 7 | private T left; 8 | private T right; 9 | public T this[LR index] 10 | { 11 | get 12 | { 13 | return index == LR.LEFT ? left : right; 14 | } 15 | set 16 | { 17 | if (index == LR.LEFT) 18 | { 19 | left = value; 20 | } 21 | else 22 | { 23 | right = value; 24 | } 25 | } 26 | } 27 | 28 | public void Clear() 29 | { 30 | left = default(T); 31 | right = default(T); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Delaunay/Site.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Numerics; 3 | 4 | namespace csDelaunay { 5 | 6 | public class Site : ICoord { 7 | 8 | private static Queue pool = new Queue(); 9 | 10 | public static Site Create(Vector2 p, int index, float weigth) { 11 | if (pool.Count > 0) { 12 | return pool.Dequeue().Init(p, index, weigth); 13 | } else { 14 | return new Site(p, index, weigth); 15 | } 16 | } 17 | 18 | public static void SortSites(List sites) { 19 | sites.Sort(delegate(Site s0, Site s1) { 20 | int returnValue = Voronoi.CompareByYThenX(s0,s1); 21 | 22 | int tempIndex; 23 | 24 | if (returnValue == -1) { 25 | if (s0.siteIndex > s1.SiteIndex) { 26 | tempIndex = s0.SiteIndex; 27 | s0.SiteIndex = s1.SiteIndex; 28 | s1.SiteIndex = tempIndex; 29 | } 30 | } else if (returnValue == 1) { 31 | if (s1.SiteIndex > s0.SiteIndex) { 32 | tempIndex = s1.SiteIndex; 33 | s1.SiteIndex = s0.SiteIndex; 34 | s0.SiteIndex = tempIndex; 35 | } 36 | } 37 | 38 | return returnValue; 39 | }); 40 | } 41 | 42 | public int Compare(Site s1, Site s2) { 43 | return s1.CompareTo(s2); 44 | } 45 | 46 | public int CompareTo(Site s1) { 47 | int returnValue = Voronoi.CompareByYThenX(this,s1); 48 | 49 | int tempIndex; 50 | 51 | if (returnValue == -1) { 52 | if (this.siteIndex > s1.SiteIndex) { 53 | tempIndex = this.SiteIndex; 54 | this.SiteIndex = s1.SiteIndex; 55 | s1.SiteIndex = tempIndex; 56 | } 57 | } else if (returnValue == 1) { 58 | if (s1.SiteIndex > this.SiteIndex) { 59 | tempIndex = s1.SiteIndex; 60 | s1.SiteIndex = this.SiteIndex; 61 | this.SiteIndex = tempIndex; 62 | } 63 | } 64 | 65 | return returnValue; 66 | } 67 | 68 | private const float EPSILON = 0.005f; 69 | private static bool CloseEnough(Vector2 p0, Vector2 p1) { 70 | return (p0-p1).Length() < EPSILON; 71 | } 72 | 73 | private int siteIndex; 74 | public int SiteIndex {get{return siteIndex;} set{siteIndex=value;}} 75 | 76 | private Vector2 coord; 77 | public Vector2 Coord {get{return coord;}set{coord=value;}} 78 | 79 | public float x {get{return coord.X;}} 80 | public float y {get{return coord.Y;}} 81 | 82 | private float weigth; 83 | public float Weigth {get{return weigth;}} 84 | 85 | // The edges that define this Site's Voronoi region: 86 | private List edges; 87 | public List Edges {get{return edges;}} 88 | // which end of each edge hooks up with the previous edge in edges: 89 | private List edgeOrientations; 90 | // ordered list of points that define the region clipped to bounds: 91 | private List region; 92 | 93 | public Site(Vector2 p, int index, float weigth) { 94 | Init(p, index, weigth); 95 | } 96 | 97 | private Site Init(Vector2 p, int index, float weigth) { 98 | coord = p; 99 | siteIndex = index; 100 | this.weigth = weigth; 101 | edges = new List(); 102 | region = null; 103 | 104 | return this; 105 | } 106 | 107 | public override string ToString() { 108 | return "Site " + siteIndex + ": " + coord; 109 | } 110 | 111 | private void Move(Vector2 p) { 112 | Clear(); 113 | coord = p; 114 | } 115 | 116 | public void Dispose() { 117 | Clear(); 118 | pool.Enqueue(this); 119 | } 120 | 121 | private void Clear() { 122 | if (edges != null) { 123 | edges.Clear(); 124 | edges = null; 125 | } 126 | if (edgeOrientations != null) { 127 | edgeOrientations.Clear(); 128 | edgeOrientations = null; 129 | } 130 | if (region != null) { 131 | region.Clear(); 132 | region = null; 133 | } 134 | } 135 | 136 | public void AddEdge(Edge edge) { 137 | edges.Add(edge); 138 | } 139 | 140 | public Edge NearestEdge() { 141 | edges.Sort(Edge.CompareSitesDistances); 142 | return edges[0]; 143 | } 144 | 145 | public List NeighborSites() { 146 | if (edges == null || edges.Count == 0) { 147 | return new List(); 148 | } 149 | if (edgeOrientations == null) { 150 | ReorderEdges(); 151 | } 152 | List list = new List(); 153 | foreach (Edge edge in edges) { 154 | list.Add(NeighborSite(edge)); 155 | } 156 | return list; 157 | } 158 | 159 | private Site NeighborSite(Edge edge) { 160 | if (this == edge.LeftSite) { 161 | return edge.RightSite; 162 | } 163 | if (this == edge.RightSite) { 164 | return edge.LeftSite; 165 | } 166 | return null; 167 | } 168 | 169 | public List Region(Rectf clippingBounds) { 170 | if (edges == null || edges.Count == 0) { 171 | return new List(); 172 | } 173 | if (edgeOrientations == null) { 174 | ReorderEdges(); 175 | } 176 | if (region == null) { 177 | region = ClipToBounds(clippingBounds); 178 | if ((new Polygon(region)).PolyWinding() == Winding.CLOCKWISE) { 179 | region.Reverse(); 180 | } 181 | } 182 | return region; 183 | } 184 | 185 | private void ReorderEdges() { 186 | EdgeReorderer reorderer = new EdgeReorderer(edges, typeof(Vertex)); 187 | edges = reorderer.Edges; 188 | edgeOrientations = reorderer.EdgeOrientations; 189 | reorderer.Dispose(); 190 | } 191 | 192 | private List ClipToBounds(Rectf bounds) { 193 | List points = new List(); 194 | int n = edges.Count; 195 | int i = 0; 196 | Edge edge; 197 | 198 | while (i < n && !edges[i].Visible()) { 199 | i++; 200 | } 201 | 202 | if (i == n) { 203 | // No edges visible 204 | return new List(); 205 | } 206 | edge = edges[i]; 207 | LR orientation = edgeOrientations[i]; 208 | points.Add(edge.ClippedEnds[orientation]); 209 | points.Add(edge.ClippedEnds[LR.Other(orientation)]); 210 | 211 | for (int j = i + 1; j < n; j++) { 212 | edge = edges[j]; 213 | if (!edge.Visible()) { 214 | continue; 215 | } 216 | Connect(ref points, j, bounds); 217 | } 218 | // Close up the polygon by adding another corner point of the bounds if needed: 219 | Connect(ref points, i, bounds, true); 220 | 221 | return points; 222 | } 223 | 224 | private void Connect(ref List points, int j, Rectf bounds, bool closingUp = false) { 225 | Vector2 rightPoint = points[points.Count-1]; 226 | Edge newEdge = edges[j]; 227 | LR newOrientation = edgeOrientations[j]; 228 | 229 | // The point that must be conected to rightPoint: 230 | Vector2 newPoint = newEdge.ClippedEnds[newOrientation]; 231 | 232 | if (!CloseEnough(rightPoint, newPoint)) { 233 | // The points do not coincide, so they must have been clipped at the bounds; 234 | // see if they are on the same border of the bounds: 235 | if (rightPoint.X != newPoint.X && rightPoint.Y != newPoint.Y) { 236 | // They are on different borders of the bounds; 237 | // insert one or two corners of bounds as needed to hook them up: 238 | // (NOTE this will not be correct if the region should take up more than 239 | // half of the bounds rect, for then we will have gone the wrong way 240 | // around the bounds and included the smaller part rather than the larger) 241 | int rightCheck = BoundsCheck.Check(rightPoint, bounds); 242 | int newCheck = BoundsCheck.Check(newPoint, bounds); 243 | float px, py; 244 | if ((rightCheck & BoundsCheck.RIGHT) != 0) { 245 | px = bounds.right; 246 | 247 | if ((newCheck & BoundsCheck.BOTTOM) != 0) { 248 | py = bounds.bottom; 249 | points.Add(new Vector2(px,py)); 250 | 251 | } else if ((newCheck & BoundsCheck.TOP) != 0) { 252 | py = bounds.top; 253 | points.Add(new Vector2(px,py)); 254 | 255 | } else if ((newCheck & BoundsCheck.LEFT) != 0) { 256 | if (rightPoint.Y - bounds.y + newPoint.Y - bounds.y < bounds.height) { 257 | py = bounds.top; 258 | } else { 259 | py = bounds.bottom; 260 | } 261 | points.Add(new Vector2(px,py)); 262 | points.Add(new Vector2(bounds.left, py)); 263 | } 264 | } else if ((rightCheck & BoundsCheck.LEFT) != 0) { 265 | px = bounds.left; 266 | 267 | if ((newCheck & BoundsCheck.BOTTOM) != 0) { 268 | py = bounds.bottom; 269 | points.Add(new Vector2(px,py)); 270 | 271 | } else if ((newCheck & BoundsCheck.TOP) != 0) { 272 | py = bounds.top; 273 | points.Add(new Vector2(px,py)); 274 | 275 | } else if ((newCheck & BoundsCheck.RIGHT) != 0) { 276 | if (rightPoint.Y - bounds.y + newPoint.Y - bounds.y < bounds.height) { 277 | py = bounds.top; 278 | } else { 279 | py = bounds.bottom; 280 | } 281 | points.Add(new Vector2(px,py)); 282 | points.Add(new Vector2(bounds.right, py)); 283 | } 284 | } else if ((rightCheck & BoundsCheck.TOP) != 0) { 285 | py = bounds.top; 286 | 287 | if ((newCheck & BoundsCheck.RIGHT) != 0) { 288 | px = bounds.right; 289 | points.Add(new Vector2(px,py)); 290 | 291 | } else if ((newCheck & BoundsCheck.LEFT) != 0) { 292 | px = bounds.left; 293 | points.Add(new Vector2(px,py)); 294 | 295 | } else if ((newCheck & BoundsCheck.BOTTOM) != 0) { 296 | if (rightPoint.X - bounds.x + newPoint.X - bounds.x < bounds.width) { 297 | px = bounds.left; 298 | } else { 299 | px = bounds.right; 300 | } 301 | points.Add(new Vector2(px,py)); 302 | points.Add(new Vector2(px, bounds.bottom)); 303 | } 304 | } else if ((rightCheck & BoundsCheck.BOTTOM) != 0) { 305 | py = bounds.bottom; 306 | 307 | if ((newCheck & BoundsCheck.RIGHT) != 0) { 308 | px = bounds.right; 309 | points.Add(new Vector2(px,py)); 310 | 311 | } else if ((newCheck & BoundsCheck.LEFT) != 0) { 312 | px = bounds.left; 313 | points.Add(new Vector2(px,py)); 314 | 315 | } else if ((newCheck & BoundsCheck.TOP) != 0) { 316 | if (rightPoint.X - bounds.x + newPoint.X - bounds.x < bounds.width) { 317 | px = bounds.left; 318 | } else { 319 | px = bounds.right; 320 | } 321 | points.Add(new Vector2(px,py)); 322 | points.Add(new Vector2(px, bounds.top)); 323 | } 324 | } 325 | } 326 | if (closingUp) { 327 | // newEdge's ends have already been added 328 | return; 329 | } 330 | points.Add(newPoint); 331 | } 332 | Vector2 newRightPoint = newEdge.ClippedEnds[LR.Other(newOrientation)]; 333 | if (!CloseEnough(points[0], newRightPoint)) { 334 | points.Add(newRightPoint); 335 | } 336 | } 337 | 338 | public float Dist(ICoord p) { 339 | return (this.Coord - p.Coord).Length(); 340 | } 341 | } 342 | 343 | public class BoundsCheck { 344 | public const int TOP = 1; 345 | public const int BOTTOM = 2; 346 | public const int LEFT = 4; 347 | public const int RIGHT = 8; 348 | 349 | /* 350 | * 351 | * @param point 352 | * @param bounds 353 | * @return an int with the appropriate bits set if the Point lies on the corresponding bounds lines 354 | */ 355 | public static int Check(Vector2 point, Rectf bounds) { 356 | int value = 0; 357 | if (point.X == bounds.left) { 358 | value |= LEFT; 359 | } 360 | if (point.X == bounds.right) { 361 | value |= RIGHT; 362 | } 363 | if (point.Y == bounds.top) { 364 | value |= TOP; 365 | } 366 | if (point.Y == bounds.bottom) { 367 | value |= BOTTOM; 368 | } 369 | 370 | return value; 371 | } 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /Delaunay/SiteList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Numerics; 4 | 5 | namespace csDelaunay { 6 | 7 | public class SiteList { 8 | 9 | private List sites; 10 | private int currentIndex; 11 | 12 | private bool sorted; 13 | 14 | public SiteList() { 15 | sites = new List(); 16 | sorted = false; 17 | } 18 | 19 | public void Dispose() { 20 | sites.Clear(); 21 | } 22 | 23 | public int Add(Site site) { 24 | sorted = false; 25 | sites.Add(site); 26 | return sites.Count; 27 | } 28 | 29 | public int Count() { 30 | return sites.Count; 31 | } 32 | 33 | public Site Next() { 34 | if (!sorted) { 35 | throw new Exception("SiteList.Next(): sites have not been sorted"); 36 | } 37 | if (currentIndex < sites.Count) { 38 | return sites[currentIndex++]; 39 | } else { 40 | return null; 41 | } 42 | } 43 | 44 | public Rectf GetSitesBounds() { 45 | if (!sorted) { 46 | SortList(); 47 | ResetListIndex(); 48 | } 49 | float xmin, xmax, ymin, ymax; 50 | if (sites.Count == 0) { 51 | return Rectf.zero; 52 | } 53 | xmin = float.MaxValue; 54 | xmax = float.MinValue; 55 | foreach (Site site in sites) { 56 | if (site.x < xmin) xmin = site.x; 57 | if (site.x > xmax) xmax = site.x; 58 | } 59 | // here's where we assume that the sites have been sorted on y: 60 | ymin = sites[0].y; 61 | ymax = sites[sites.Count-1].y; 62 | 63 | return new Rectf(xmin, ymin, xmax - xmin, ymax - ymin); 64 | } 65 | 66 | public List SiteCoords() { 67 | List coords = new List(); 68 | foreach (Site site in sites) { 69 | coords.Add(site.Coord); 70 | } 71 | 72 | return coords; 73 | } 74 | 75 | /* 76 | * 77 | * @return the largest circle centered at each site that fits in its region; 78 | * if the region is infinite, return a circle of radius 0. 79 | */ 80 | public List Circles() { 81 | List circles = new List(); 82 | foreach (Site site in sites) { 83 | float radius = 0; 84 | Edge nearestEdge = site.NearestEdge(); 85 | 86 | if (!nearestEdge.IsPartOfConvexHull()) radius = nearestEdge.SitesDistance() * 0.5f; 87 | circles.Add(new Circle(site.x,site.y, radius)); 88 | } 89 | return circles; 90 | } 91 | 92 | public List> Regions(Rectf plotBounds) { 93 | List> regions = new List>(); 94 | foreach (Site site in sites) { 95 | regions.Add(site.Region(plotBounds)); 96 | } 97 | return regions; 98 | } 99 | 100 | public void ResetListIndex() { 101 | currentIndex = 0; 102 | } 103 | 104 | public void SortList() { 105 | Site.SortSites(sites); 106 | sorted = true; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Delaunay/Triangle.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace csDelaunay { 5 | 6 | public class Triangle { 7 | 8 | private List sites; 9 | public List Sites {get{return sites;}} 10 | 11 | public Triangle(Site a, Site b, Site c) { 12 | sites = new List(); 13 | sites.Add(a); 14 | sites.Add(b); 15 | sites.Add(c); 16 | } 17 | 18 | public void Dispose() { 19 | sites.Clear(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Delaunay/Vertex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Numerics; 4 | 5 | namespace csDelaunay { 6 | 7 | public class Vertex : ICoord { 8 | 9 | public static readonly Vertex VERTEX_AT_INFINITY = new Vertex(float.NaN, float.NaN); 10 | 11 | #region Pool 12 | private static Queue pool = new Queue(); 13 | 14 | private static int nVertices = 0; 15 | 16 | private static Vertex Create(float x, float y) { 17 | if (float.IsNaN(x) || float.IsNaN(y)) { 18 | return VERTEX_AT_INFINITY; 19 | } 20 | if (pool.Count > 0) { 21 | return pool.Dequeue().Init(x,y); 22 | } else { 23 | return new Vertex(x,y); 24 | } 25 | } 26 | #endregion 27 | 28 | #region Object 29 | private Vector2 coord; 30 | public Vector2 Coord {get{return coord;}set{coord=value;}} 31 | 32 | public float x {get{return coord.X;}} 33 | public float y {get{return coord.Y;}} 34 | 35 | private int vertexIndex; 36 | public int VertexIndex {get{return vertexIndex;}} 37 | 38 | public Vertex(float x, float y) { 39 | Init(x,y); 40 | } 41 | 42 | private Vertex Init(float x, float y) { 43 | coord = new Vector2(x,y); 44 | 45 | return this; 46 | } 47 | 48 | public void Dispose() { 49 | coord = Vector2.Zero; 50 | pool.Enqueue(this); 51 | } 52 | 53 | public void SetIndex() { 54 | vertexIndex = nVertices++; 55 | } 56 | 57 | public override string ToString() { 58 | return "Vertex (" + vertexIndex + ")"; 59 | } 60 | 61 | /* 62 | * This is the only way to make a Vertex 63 | * 64 | * @param halfedge0 65 | * @param halfedge1 66 | * @return 67 | * 68 | */ 69 | public static Vertex Intersect(Halfedge halfedge0, Halfedge halfedge1) { 70 | Edge edge, edge0, edge1; 71 | Halfedge halfedge; 72 | float determinant, intersectionX, intersectionY; 73 | bool rightOfSite; 74 | 75 | edge0 = halfedge0.edge; 76 | edge1 = halfedge1.edge; 77 | if (edge0 == null || edge1 == null) { 78 | return null; 79 | } 80 | if (edge0.RightSite == edge1.RightSite) { 81 | return null; 82 | } 83 | 84 | determinant = edge0.a * edge1.b - edge0.b * edge1.a; 85 | if (Math.Abs(determinant) < 1E-10) { 86 | // The edges are parallel 87 | return null; 88 | } 89 | 90 | intersectionX = (edge0.c * edge1.b - edge1.c * edge0.b)/determinant; 91 | intersectionY = (edge1.c * edge0.a - edge0.c * edge1.a)/determinant; 92 | 93 | if (Voronoi.CompareByYThenX(edge0.RightSite, edge1.RightSite) < 0) { 94 | halfedge = halfedge0; 95 | edge = edge0; 96 | } else { 97 | halfedge = halfedge1; 98 | edge = edge1; 99 | } 100 | rightOfSite = intersectionX >= edge.RightSite.x; 101 | if ((rightOfSite && halfedge.leftRight == LR.LEFT) || 102 | (!rightOfSite && halfedge.leftRight == LR.RIGHT)) { 103 | return null; 104 | } 105 | 106 | return Vertex.Create(intersectionX, intersectionY); 107 | } 108 | #endregion 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Delaunay/Voronoi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Numerics; 4 | 5 | namespace csDelaunay { 6 | 7 | public class Voronoi { 8 | 9 | private SiteList sites; 10 | private List triangles; 11 | 12 | private List edges; 13 | public List Edges {get{return edges;}} 14 | 15 | // TODO generalize this so it doesn't have to be a rectangle; 16 | // then we can make the fractal voronois-within-voronois 17 | private Rectf plotBounds; 18 | public Rectf PlotBounds {get{return plotBounds;}} 19 | 20 | private Dictionary sitesIndexedByLocation; 21 | public Dictionary SitesIndexedByLocation {get{return sitesIndexedByLocation;}} 22 | 23 | private Random weigthDistributor; 24 | 25 | public void Dispose() { 26 | sites.Dispose(); 27 | sites = null; 28 | 29 | foreach (Triangle t in triangles) { 30 | t.Dispose(); 31 | } 32 | triangles.Clear(); 33 | 34 | foreach (Edge e in edges) { 35 | e.Dispose(); 36 | } 37 | edges.Clear(); 38 | 39 | plotBounds = Rectf.zero; 40 | sitesIndexedByLocation.Clear(); 41 | sitesIndexedByLocation = null; 42 | } 43 | 44 | public Voronoi(List points, Rectf plotBounds) { 45 | weigthDistributor = new Random(); 46 | Init(points,plotBounds); 47 | } 48 | 49 | public Voronoi(List points, Rectf plotBounds, int lloydIterations) { 50 | weigthDistributor = new Random(); 51 | Init(points,plotBounds); 52 | LloydRelaxation(lloydIterations); 53 | } 54 | 55 | private void Init(List points, Rectf plotBounds) { 56 | sites = new SiteList(); 57 | sitesIndexedByLocation = new Dictionary(); 58 | AddSites(points); 59 | this.plotBounds = plotBounds; 60 | triangles = new List(); 61 | edges = new List(); 62 | 63 | FortunesAlgorithm(); 64 | } 65 | 66 | private void AddSites(List points) { 67 | for (int i = 0; i < points.Count; i++) { 68 | AddSite(points[i], i); 69 | } 70 | } 71 | 72 | private void AddSite(Vector2 p, int index) { 73 | float weigth = (float)weigthDistributor.NextDouble() * 100; 74 | Site site = Site.Create(p, index, weigth); 75 | sites.Add(site); 76 | sitesIndexedByLocation[p] = site; 77 | } 78 | 79 | public List Region (Vector2 p) { 80 | Site site; 81 | if (sitesIndexedByLocation.TryGetValue(p, out site)) { 82 | return site.Region(plotBounds); 83 | } else { 84 | return new List(); 85 | } 86 | } 87 | 88 | public List NeighborSitesForSite(Vector2 coord) { 89 | List points = new List(); 90 | Site site; 91 | if (sitesIndexedByLocation.TryGetValue(coord, out site)) { 92 | List sites = site.NeighborSites(); 93 | foreach (Site neighbor in sites) { 94 | points.Add(neighbor.Coord); 95 | } 96 | } 97 | 98 | return points; 99 | } 100 | 101 | public List Circles() { 102 | return sites.Circles(); 103 | } 104 | 105 | public List VoronoiBoundarayForSite(Vector2 coord) { 106 | return LineSegment.VisibleLineSegments(Edge.SelectEdgesForSitePoint(coord, edges)); 107 | } 108 | /* 109 | public List DelaunayLinesForSite(Vector2 coord) { 110 | return DelaunayLinesForEdges(Edge.SelectEdgesForSitePoint(coord, edges)); 111 | }*/ 112 | 113 | public List VoronoiDiagram() { 114 | return LineSegment.VisibleLineSegments(edges); 115 | } 116 | /* 117 | public List Hull() { 118 | return DelaunayLinesForEdges(HullEdges()); 119 | }*/ 120 | 121 | public List HullEdges() { 122 | return edges.FindAll(edge=>edge.IsPartOfConvexHull()); 123 | } 124 | 125 | public List HullPointsInOrder() { 126 | List hullEdges = HullEdges(); 127 | 128 | List points = new List(); 129 | if (hullEdges.Count == 0) { 130 | return points; 131 | } 132 | 133 | EdgeReorderer reorderer = new EdgeReorderer(hullEdges, typeof(Site)); 134 | hullEdges = reorderer.Edges; 135 | List orientations = reorderer.EdgeOrientations; 136 | reorderer.Dispose(); 137 | 138 | LR orientation; 139 | for (int i = 0; i < hullEdges.Count; i++) { 140 | Edge edge = hullEdges[i]; 141 | orientation = orientations[i]; 142 | points.Add(edge.Site(orientation).Coord); 143 | } 144 | return points; 145 | } 146 | 147 | public List> Regions() { 148 | return sites.Regions(plotBounds); 149 | } 150 | 151 | public List SiteCoords() { 152 | return sites.SiteCoords(); 153 | } 154 | 155 | private void FortunesAlgorithm() { 156 | Site newSite, bottomSite, topSite, tempSite; 157 | Vertex v, vertex; 158 | Vector2 newIntStar = Vector2.Zero; 159 | LR leftRight; 160 | Halfedge lbnd, rbnd, llbnd, rrbnd, bisector; 161 | Edge edge; 162 | 163 | Rectf dataBounds = sites.GetSitesBounds(); 164 | 165 | int sqrtSitesNb = (int)Math.Sqrt(sites.Count() + 4); 166 | HalfedgePriorityQueue heap = new HalfedgePriorityQueue(dataBounds.y, dataBounds.height, sqrtSitesNb); 167 | EdgeList edgeList = new EdgeList(dataBounds.x, dataBounds.width, sqrtSitesNb); 168 | List halfEdges = new List(); 169 | List vertices = new List(); 170 | 171 | Site bottomMostSite = sites.Next(); 172 | newSite = sites.Next(); 173 | 174 | while (true) { 175 | if (!heap.Empty()) { 176 | newIntStar = heap.Min(); 177 | } 178 | 179 | if (newSite != null && 180 | (heap.Empty() || CompareByYThenX(newSite, newIntStar) < 0)) { 181 | // New site is smallest 182 | //Debug.Log("smallest: new site " + newSite); 183 | 184 | // Step 8: 185 | lbnd = edgeList.EdgeListLeftNeighbor(newSite.Coord); // The halfedge just to the left of newSite 186 | //UnityEngine.Debug.Log("lbnd: " + lbnd); 187 | rbnd = lbnd.edgeListRightNeighbor; // The halfedge just to the right 188 | //UnityEngine.Debug.Log("rbnd: " + rbnd); 189 | bottomSite = RightRegion(lbnd, bottomMostSite); // This is the same as leftRegion(rbnd) 190 | // This Site determines the region containing the new site 191 | //UnityEngine.Debug.Log("new Site is in region of existing site: " + bottomSite); 192 | 193 | // Step 9 194 | edge = Edge.CreateBisectingEdge(bottomSite, newSite); 195 | //UnityEngine.Debug.Log("new edge: " + edge); 196 | edges.Add(edge); 197 | 198 | bisector = Halfedge.Create(edge, LR.LEFT); 199 | halfEdges.Add(bisector); 200 | // Inserting two halfedges into edgelist constitutes Step 10: 201 | // Insert bisector to the right of lbnd: 202 | edgeList.Insert(lbnd, bisector); 203 | 204 | // First half of Step 11: 205 | if ((vertex = Vertex.Intersect(lbnd, bisector)) != null) { 206 | vertices.Add(vertex); 207 | heap.Remove(lbnd); 208 | lbnd.vertex = vertex; 209 | lbnd.ystar = vertex.y + newSite.Dist(vertex); 210 | heap.Insert(lbnd); 211 | } 212 | 213 | lbnd = bisector; 214 | bisector = Halfedge.Create(edge, LR.RIGHT); 215 | halfEdges.Add(bisector); 216 | // Second halfedge for Step 10:: 217 | // Insert bisector to the right of lbnd: 218 | edgeList.Insert(lbnd, bisector); 219 | 220 | // Second half of Step 11: 221 | if ((vertex = Vertex.Intersect(bisector, rbnd)) != null) { 222 | vertices.Add(vertex); 223 | bisector.vertex = vertex; 224 | bisector.ystar = vertex.y + newSite.Dist(vertex); 225 | heap.Insert(bisector); 226 | } 227 | 228 | newSite = sites.Next(); 229 | } else if (!heap.Empty()) { 230 | // Intersection is smallest 231 | lbnd = heap.ExtractMin(); 232 | llbnd = lbnd.edgeListLeftNeighbor; 233 | rbnd = lbnd.edgeListRightNeighbor; 234 | rrbnd = rbnd.edgeListRightNeighbor; 235 | bottomSite = LeftRegion(lbnd, bottomMostSite); 236 | topSite = RightRegion(rbnd, bottomMostSite); 237 | // These three sites define a Delaunay triangle 238 | // (not actually using these for anything...) 239 | // triangles.Add(new Triangle(bottomSite, topSite, RightRegion(lbnd, bottomMostSite))); 240 | 241 | v = lbnd.vertex; 242 | v.SetIndex(); 243 | lbnd.edge.SetVertex(lbnd.leftRight, v); 244 | rbnd.edge.SetVertex(rbnd.leftRight, v); 245 | edgeList.Remove(lbnd); 246 | heap.Remove(rbnd); 247 | edgeList.Remove(rbnd); 248 | leftRight = LR.LEFT; 249 | if (bottomSite.y > topSite.y) { 250 | tempSite = bottomSite; 251 | bottomSite = topSite; 252 | topSite = tempSite; 253 | leftRight = LR.RIGHT; 254 | } 255 | edge = Edge.CreateBisectingEdge(bottomSite, topSite); 256 | edges.Add(edge); 257 | bisector = Halfedge.Create(edge, leftRight); 258 | halfEdges.Add(bisector); 259 | edgeList.Insert(llbnd, bisector); 260 | edge.SetVertex(LR.Other(leftRight), v); 261 | if ((vertex = Vertex.Intersect(llbnd, bisector)) != null) { 262 | vertices.Add(vertex); 263 | heap.Remove(llbnd); 264 | llbnd.vertex = vertex; 265 | llbnd.ystar = vertex.y + bottomSite.Dist(vertex); 266 | heap.Insert(llbnd); 267 | } 268 | if ((vertex = Vertex.Intersect(bisector, rrbnd)) != null) { 269 | vertices.Add(vertex); 270 | bisector.vertex = vertex; 271 | bisector.ystar = vertex.y + bottomSite.Dist(vertex); 272 | heap.Insert(bisector); 273 | } 274 | } else { 275 | break; 276 | } 277 | } 278 | 279 | // Heap should be empty now 280 | heap.Dispose(); 281 | edgeList.Dispose(); 282 | 283 | foreach (Halfedge halfedge in halfEdges) { 284 | halfedge.ReallyDispose(); 285 | } 286 | halfEdges.Clear(); 287 | 288 | // we need the vertices to clip the edges 289 | foreach (Edge e in edges) { 290 | e.ClipVertices(plotBounds); 291 | } 292 | // But we don't actually ever use them again! 293 | foreach (Vertex ve in vertices) { 294 | ve.Dispose(); 295 | } 296 | vertices.Clear(); 297 | } 298 | 299 | public void LloydRelaxation(int nbIterations) { 300 | // Reapeat the whole process for the number of iterations asked 301 | for (int i = 0; i < nbIterations; i++) { 302 | List newPoints = new List(); 303 | // Go thourgh all sites 304 | sites.ResetListIndex(); 305 | Site site = sites.Next(); 306 | 307 | while (site != null) { 308 | // Loop all corners of the site to calculate the centroid 309 | List region = site.Region(plotBounds); 310 | if (region.Count < 1) { 311 | site = sites.Next(); 312 | continue; 313 | } 314 | 315 | Vector2 centroid = Vector2.Zero; 316 | float signedArea = 0; 317 | float x0 = 0; 318 | float y0 = 0; 319 | float x1 = 0; 320 | float y1 = 0; 321 | float a = 0; 322 | // For all vertices except last 323 | for (int j = 0; j < region.Count-1; j++) { 324 | x0 = region[j].X; 325 | y0 = region[j].Y; 326 | x1 = region[j+1].X; 327 | y1 = region[j+1].Y; 328 | a = x0*y1 - x1*y0; 329 | signedArea += a; 330 | centroid.X += (x0 + x1)*a; 331 | centroid.Y += (y0 + y1)*a; 332 | } 333 | // Do last vertex 334 | x0 = region[region.Count-1].X; 335 | y0 = region[region.Count-1].Y; 336 | x1 = region[0].X; 337 | y1 = region[0].Y; 338 | a = x0*y1 - x1*y0; 339 | signedArea += a; 340 | centroid.X += (x0 + x1)*a; 341 | centroid.Y += (y0 + y1)*a; 342 | 343 | signedArea *= 0.5f; 344 | centroid.X /= (6*signedArea); 345 | centroid.Y /= (6*signedArea); 346 | // Move site to the centroid of its Voronoi cell 347 | newPoints.Add(centroid); 348 | site = sites.Next(); 349 | } 350 | 351 | // Between each replacement of the cendroid of the cell, 352 | // we need to recompute Voronoi diagram: 353 | Rectf origPlotBounds = this.plotBounds; 354 | Dispose(); 355 | Init(newPoints,origPlotBounds); 356 | } 357 | } 358 | 359 | private Site LeftRegion(Halfedge he, Site bottomMostSite) { 360 | Edge edge = he.edge; 361 | if (edge == null) { 362 | return bottomMostSite; 363 | } 364 | return edge.Site(he.leftRight); 365 | } 366 | 367 | private Site RightRegion(Halfedge he, Site bottomMostSite) { 368 | Edge edge = he.edge; 369 | if (edge == null) { 370 | return bottomMostSite; 371 | } 372 | return edge.Site(LR.Other(he.leftRight)); 373 | } 374 | 375 | public static int CompareByYThenX(Site s1, Site s2) { 376 | if (s1.y < s2.y) return -1; 377 | if (s1.y > s2.y) return 1; 378 | if (s1.x < s2.x) return -1; 379 | if (s1.x > s2.x) return 1; 380 | return 0; 381 | } 382 | 383 | public static int CompareByYThenX(Site s1, Vector2 s2) { 384 | if (s1.y < s2.Y) return -1; 385 | if (s1.y > s2.Y) return 1; 386 | if (s1.x < s2.X) return -1; 387 | if (s1.x > s2.X) return 1; 388 | return 0; 389 | } 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /Geom/Circle.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace csDelaunay { 4 | public class Circle { 5 | 6 | public Vector2 center; 7 | public float radius; 8 | 9 | public Circle(float centerX, float centerY, float radius) { 10 | this.center = new Vector2(centerX, centerY); 11 | this.radius = radius; 12 | } 13 | 14 | public override string ToString () { 15 | return "Circle (center: " + center + "; radius: " + radius + ")"; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Geom/LineSegment.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Numerics; 3 | 4 | namespace csDelaunay { 5 | 6 | public class LineSegment { 7 | 8 | public static List VisibleLineSegments(List edges) { 9 | List segments = new List(); 10 | 11 | foreach (Edge edge in edges) { 12 | if (edge.Visible()) { 13 | Vector2 p1 = edge.ClippedEnds[LR.LEFT]; 14 | Vector2 p2 = edge.ClippedEnds[LR.RIGHT]; 15 | segments.Add(new LineSegment(p1,p2)); 16 | } 17 | } 18 | 19 | return segments; 20 | } 21 | 22 | public static float CompareLengths_MAX(LineSegment segment0, LineSegment segment1) { 23 | float length0 = (segment0.p0 - segment0.p1).Length(); 24 | float length1 = (segment1.p0 - segment1.p1).Length(); 25 | if (length0 < length1) { 26 | return 1; 27 | } 28 | if (length0 > length1) { 29 | return -1; 30 | } 31 | return 0; 32 | } 33 | 34 | public static float CompareLengths(LineSegment edge0, LineSegment edge1) { 35 | return - CompareLengths_MAX(edge0, edge1); 36 | } 37 | 38 | public Vector2 p0; 39 | public Vector2 p1; 40 | 41 | public LineSegment (Vector2 p0, Vector2 p1) { 42 | this.p0 = p0; 43 | this.p1 = p1; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Geom/Polygon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Numerics; 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 | Vector2 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/Rectf.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | public struct Rectf { 4 | 5 | public static readonly Rectf zero = new Rectf(0,0,0,0); 6 | public static readonly Rectf one = new Rectf(1,1,1,1); 7 | 8 | public float x,y,width,height; 9 | 10 | public Rectf(float x, float y, float width, float height) { 11 | this.x = x; 12 | this.y = y; 13 | this.width = width; 14 | this.height = height; 15 | } 16 | 17 | public float left { 18 | get { 19 | return x;} 20 | } 21 | 22 | public float right { 23 | get { 24 | return x+width; 25 | } 26 | } 27 | 28 | public float top { 29 | get { 30 | return y; 31 | } 32 | } 33 | 34 | public float bottom { 35 | get { 36 | return y+height; 37 | } 38 | } 39 | 40 | public Vector2 topLeft { 41 | get { 42 | return new Vector2(left, top); 43 | } 44 | } 45 | 46 | public Vector2 bottomRight { 47 | get { 48 | return new Vector2(right, bottom); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Geom/Winding.cs: -------------------------------------------------------------------------------- 1 | namespace csDelaunay { 2 | 3 | public enum Winding { 4 | CLOCKWISE, COUNTERCLOCKWISE, NONE 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 PouletFrit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # csDelaunay 2 | 3 | A .NET library providing Delaunay triangulation and Lloyd relaxation. 4 | 5 | This is a port and interpretation of ActionScript library [as3delaunay](http://nodename.github.io/as3delaunay/). 6 | - @PouletFrit ported the library from AS3 and added a Lloyd relaxation function. 7 | - @frabert made significant optimizations. 8 | - @charlieturndorf provided a cross-platform build (.NET Standard 2) that will also work on .NET Framework. 9 | 10 | ### Cross-Platform 11 | csDelaunay will run [anywhere .NET Standard 2.0 will run](https://docs.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0). 12 | You can build and develop on Windows, Mac, or Linux (requires .NET SDK 2.1.5 or higher). 13 | 14 | **Mac/Linux Dev Note:** 15 | The scripts `init.cmd` and `build.cmd` are for Windows, but they're very simple and you can do the same thing in the terminal. Please feel free to submit a PR adding bash scripts. 16 | 17 | ## Setup 18 | 1. Clone the repository (you might wish to create a [submodule](https://github.blog/2016-02-01-working-with-submodules/) under another project) 19 | 2. Download and install the [.NET SDK](https://dotnet.microsoft.com/en-us/download), if you don't have it 20 | 3. On the command line, navigate to the root folder of your csDelaunay clone 21 | 4. Run `init.cmd` to bootstrap the dependency manager 22 | 23 | ## Build 24 | 1. On the command line, navigate to the root folder of your csDelaunay clone 25 | 2. Run `build.cmd` 26 | 27 | OR 28 | 1. Build with Visual Studio 29 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | #### 1.0.1.0 - February 27, 2019 2 | * .NET Standard 2.0 migration -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | dotnet build csDelaunay.sln -c Release -------------------------------------------------------------------------------- /csDelaunay.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | csDelaunay 8 | 9 | 10 | true 11 | 12 | 13 | -------------------------------------------------------------------------------- /csDelaunay.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2046 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csDelaunay", "csDelaunay.csproj", "{8233A279-E59C-4685-9192-4EF9A61B0DE7}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{335937CA-13F4-4D9D-9B9A-4A884E4C5B0E}" 9 | ProjectSection(SolutionItems) = preProject 10 | paket.dependencies = paket.dependencies 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {8233A279-E59C-4685-9192-4EF9A61B0DE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {8233A279-E59C-4685-9192-4EF9A61B0DE7}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {8233A279-E59C-4685-9192-4EF9A61B0DE7}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {8233A279-E59C-4685-9192-4EF9A61B0DE7}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {A71A468D-83F1-43EF-852B-EBE9A785355C} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /directory.build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.1.0.0 4 | 1.1.0.0 5 | 1.1.0.0 6 | PouletFrit 7 | csDelaunay 8 | Copyright © PouletFrit 2014 (MIT license) 9 | 10 | -------------------------------------------------------------------------------- /init.cmd: -------------------------------------------------------------------------------- 1 | dotnet tool install Paket --tool-path .paket 2 | .paket\paket restore -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://api.nuget.org/v3/index.json 2 | framework: >= netstandard2.0 3 | 4 | nuget System.Numerics.Vectors ~> 4.5.0 -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | RESTRICTION: >= netstandard2.0 2 | NUGET 3 | remote: https://api.nuget.org/v3/index.json 4 | System.Numerics.Vectors (4.5) 5 | -------------------------------------------------------------------------------- /paket.references: -------------------------------------------------------------------------------- 1 | System.Numerics.Vectors --------------------------------------------------------------------------------