├── .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
--------------------------------------------------------------------------------