clipper2 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +165 -0
- data/examples/larb_transform.rb +17 -0
- data/examples/rugl_mesh.rb +33 -0
- data/examples/svg_boolean.rb +28 -0
- data/ext/clipper2/CMakeLists.txt +27 -0
- data/ext/clipper2/clipper2_native.cpp +2 -0
- data/ext/clipper2/extconf.rb +27 -0
- data/ext/clipper2/generate_bindings.rb +73 -0
- data/ext/clipper2/memory_check.cpp +48 -0
- data/ext/clipper2/vendor/LICENSE +23 -0
- data/ext/clipper2/vendor/UPSTREAM.md +11 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.core.h +1159 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.engine.h +635 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.export.h +851 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.h +796 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.minkowski.h +117 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.offset.h +125 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.rectclip.h +80 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.triangulation.h +27 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.version.h +6 -0
- data/ext/clipper2/vendor/src/clipper.engine.cpp +3152 -0
- data/ext/clipper2/vendor/src/clipper.offset.cpp +661 -0
- data/ext/clipper2/vendor/src/clipper.rectclip.cpp +1027 -0
- data/ext/clipper2/vendor/src/clipper.triangulation.cpp +1221 -0
- data/lib/clipper2/api.rb +45 -0
- data/lib/clipper2/c_paths64.rb +80 -0
- data/lib/clipper2/earcut/engine.rb +191 -0
- data/lib/clipper2/earcut/geometry.rb +100 -0
- data/lib/clipper2/earcut/node.rb +18 -0
- data/lib/clipper2/earcut/ring.rb +94 -0
- data/lib/clipper2/earcut.rb +18 -0
- data/lib/clipper2/errors.rb +19 -0
- data/lib/clipper2/generated.rb +62 -0
- data/lib/clipper2/native.rb +38 -0
- data/lib/clipper2/native_memory.rb +64 -0
- data/lib/clipper2/operations.rb +206 -0
- data/lib/clipper2/path.rb +171 -0
- data/lib/clipper2/path_simplifier.rb +61 -0
- data/lib/clipper2/paths.rb +130 -0
- data/lib/clipper2/quantizer.rb +48 -0
- data/lib/clipper2/triangulation.rb +103 -0
- data/lib/clipper2/version.rb +5 -0
- data/lib/clipper2.rb +34 -0
- data/licenses/CLIPPER2-BSL-1.0.txt +23 -0
- data/licenses/EARCUT-ISC.txt +15 -0
- metadata +113 -0
|
@@ -0,0 +1,1221 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
* Author : Angus Johnson *
|
|
3
|
+
* Date : 21 February 2026 *
|
|
4
|
+
* Release : BETA RELEASE *
|
|
5
|
+
* Website : https://www.angusj.com *
|
|
6
|
+
* Copyright : Angus Johnson 2010-2026 *
|
|
7
|
+
* Purpose : Constrained Delaunay Triangulation *
|
|
8
|
+
* License : https://www.boost.org/LICENSE_1_0.txt *
|
|
9
|
+
*******************************************************************************/
|
|
10
|
+
|
|
11
|
+
#include "clipper2/clipper.h"
|
|
12
|
+
#include "clipper2/clipper.triangulation.h"
|
|
13
|
+
|
|
14
|
+
namespace Clipper2Lib
|
|
15
|
+
{
|
|
16
|
+
|
|
17
|
+
enum class EdgeKind { loose, ascend, descend }; // ascend & descend are 'fixed' edges
|
|
18
|
+
enum class IntersectKind { none, collinear, intersect };
|
|
19
|
+
enum class EdgeContainsResult { neither, left, right };
|
|
20
|
+
|
|
21
|
+
//forward definitions
|
|
22
|
+
class Vertex2;
|
|
23
|
+
class Edge;
|
|
24
|
+
class Triangle;
|
|
25
|
+
|
|
26
|
+
typedef std::vector<Vertex2*> VertexList;
|
|
27
|
+
typedef std::vector<Edge*> EdgeList;
|
|
28
|
+
|
|
29
|
+
class Vertex2 {
|
|
30
|
+
public:
|
|
31
|
+
Point64 pt;
|
|
32
|
+
EdgeList edges;
|
|
33
|
+
bool innerLM = false;
|
|
34
|
+
Vertex2(const Point64& p64) : pt(p64) { edges.reserve(2); };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
class Edge {
|
|
38
|
+
public:
|
|
39
|
+
Vertex2* vL = nullptr;
|
|
40
|
+
Vertex2* vR = nullptr;
|
|
41
|
+
Vertex2* vB = nullptr;
|
|
42
|
+
Vertex2* vT = nullptr;
|
|
43
|
+
EdgeKind kind = EdgeKind::loose;
|
|
44
|
+
Triangle* triA = nullptr;
|
|
45
|
+
Triangle* triB = nullptr;
|
|
46
|
+
bool isActive = false;
|
|
47
|
+
Edge* nextE = nullptr;
|
|
48
|
+
Edge* prevE = nullptr;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
class Triangle {
|
|
52
|
+
public:
|
|
53
|
+
Edge* edges[3];
|
|
54
|
+
Triangle(Edge* e1, Edge* e2, Edge* e3)
|
|
55
|
+
{
|
|
56
|
+
edges[0] = e1;
|
|
57
|
+
edges[1] = e2;
|
|
58
|
+
edges[2] = e3;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
63
|
+
// Delaunay class declaration
|
|
64
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
65
|
+
|
|
66
|
+
class Delaunay {
|
|
67
|
+
private:
|
|
68
|
+
VertexList allVertices;
|
|
69
|
+
EdgeList allEdges;
|
|
70
|
+
std::vector<Triangle*> allTriangles;
|
|
71
|
+
std::stack<Edge*> pendingDelaunayStack;
|
|
72
|
+
std::stack<Edge*> horzEdgeStack;
|
|
73
|
+
std::stack<Vertex2*> locMinStack;
|
|
74
|
+
bool useDelaunay = true;
|
|
75
|
+
Vertex2* lowermostVertex = nullptr;
|
|
76
|
+
Edge* firstActive = nullptr;
|
|
77
|
+
void AddPath(const Path64& path);
|
|
78
|
+
bool AddPaths(const Paths64& paths);
|
|
79
|
+
void CleanUp();
|
|
80
|
+
bool FixupEdgeIntersects();
|
|
81
|
+
void MergeDupOrCollinearVertices();
|
|
82
|
+
void SplitEdge(Edge* longE, Edge* shortE);
|
|
83
|
+
bool RemoveIntersection(Edge* e1, Edge* e2);
|
|
84
|
+
Edge* CreateEdge(Vertex2* v1, Vertex2* v2, EdgeKind k);
|
|
85
|
+
Triangle* CreateTriangle(Edge* e1, Edge* e2, Edge* e3);
|
|
86
|
+
Edge* CreateInnerLocMinLooseEdge(Vertex2* vAbove);
|
|
87
|
+
Edge* HorizontalBetween(Vertex2* v1, Vertex2* v2);
|
|
88
|
+
void DoTriangulateLeft(Edge* edge, Vertex2* pivot, int64_t minY);
|
|
89
|
+
void DoTriangulateRight(Edge* edge, Vertex2* pivot, int64_t minY);
|
|
90
|
+
void AddEdgeToActives(Edge* edge);
|
|
91
|
+
void RemoveEdgeFromActives(Edge* edge);
|
|
92
|
+
void ForceLegal(Edge* edge);
|
|
93
|
+
public:
|
|
94
|
+
explicit Delaunay(bool delaunay = true) : useDelaunay(delaunay) {};
|
|
95
|
+
~Delaunay() { CleanUp(); };
|
|
96
|
+
Paths64 Execute(const Paths64& paths, TriangulateResult& triResult);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
100
|
+
// Miscellaneous functions
|
|
101
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
102
|
+
|
|
103
|
+
static bool VertexListSort(const Vertex2* a, const Vertex2* b)
|
|
104
|
+
{
|
|
105
|
+
return (a->pt.y == b->pt.y) ? (a->pt.x < b->pt.x) : (a->pt.y > b->pt.y);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static bool EdgeListSort(const Edge* a, const Edge* b)
|
|
109
|
+
{
|
|
110
|
+
return (a->vL->pt.x < b->vL->pt.x);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static bool IsLooseEdge(const Edge& e)
|
|
114
|
+
{
|
|
115
|
+
return e.kind == EdgeKind::loose;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static bool IsLeftEdge(const Edge& e)
|
|
119
|
+
{
|
|
120
|
+
// left edge (bound) of a fill region
|
|
121
|
+
// ie fills on the **right** side of the edge
|
|
122
|
+
// precondition - e is never a 'loose' edge
|
|
123
|
+
return e.kind == EdgeKind::ascend;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static bool IsRightEdge(const Edge& e)
|
|
127
|
+
{
|
|
128
|
+
// right edge (bound) of a fill region
|
|
129
|
+
// but still fills on the **right** side of the edge
|
|
130
|
+
// precondition - e is never a 'loose' edge
|
|
131
|
+
return e.kind == EdgeKind::descend;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static bool IsHorizontal(const Edge& e)
|
|
135
|
+
{
|
|
136
|
+
return e.vB->pt.y == e.vT->pt.y;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static bool LeftTurning(const Point64& p1, const Point64& p2, const Point64& p3)
|
|
140
|
+
{
|
|
141
|
+
return CrossProductSign(p1, p2, p3) < 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static bool RightTurning(const Point64& p1, const Point64& p2, const Point64& p3)
|
|
145
|
+
{
|
|
146
|
+
return CrossProductSign(p1, p2, p3) > 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static bool EdgeCompleted(Edge* edge)
|
|
150
|
+
{
|
|
151
|
+
if (!edge->triA) return false;
|
|
152
|
+
if (edge->triB) return true;
|
|
153
|
+
return edge->kind != EdgeKind::loose;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static EdgeContainsResult EdgeContains(const Edge* edge, const Vertex2* v)
|
|
157
|
+
{
|
|
158
|
+
if (edge->vL == v) return EdgeContainsResult::left;
|
|
159
|
+
else if (edge->vR == v) return EdgeContainsResult::right;
|
|
160
|
+
else return EdgeContainsResult::neither;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static double GetAngle(const Point64& a, const Point64& b, const Point64& c)
|
|
164
|
+
{
|
|
165
|
+
//https://stackoverflow.com/a/3487062/359538
|
|
166
|
+
double abx = static_cast<double>(b.x - a.x);
|
|
167
|
+
double aby = static_cast<double>(b.y - a.y);
|
|
168
|
+
double bcx = static_cast<double>(b.x - c.x);
|
|
169
|
+
double bcy = static_cast<double>(b.y - c.y);
|
|
170
|
+
double dp = (abx * bcx + aby * bcy);
|
|
171
|
+
double cp = (abx * bcy - aby * bcx);
|
|
172
|
+
return std::atan2(cp, dp); //range between -Pi and Pi
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static double GetLocMinAngle(Vertex2* v)
|
|
176
|
+
{
|
|
177
|
+
// todo - recheck the result's sign compared to left vs right turning
|
|
178
|
+
// (currently assumes left turning => positive values)
|
|
179
|
+
// precondition - this function is called before processing locMin.
|
|
180
|
+
//Assert(v->edges.size() == 2);
|
|
181
|
+
int asc, des;
|
|
182
|
+
if (v->edges[0]->kind == EdgeKind::ascend)
|
|
183
|
+
{
|
|
184
|
+
asc = 0;
|
|
185
|
+
des = 1;
|
|
186
|
+
}
|
|
187
|
+
else
|
|
188
|
+
{
|
|
189
|
+
des = 0;
|
|
190
|
+
asc = 1;
|
|
191
|
+
}
|
|
192
|
+
// winding direction - descending to ascending
|
|
193
|
+
return GetAngle(v->edges[des]->vT->pt, v->pt, v->edges[asc]->vT->pt);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
static void RemoveEdgeFromVertex(Vertex2* vert, Edge* edge)
|
|
197
|
+
{
|
|
198
|
+
auto it = std::find(vert->edges.begin(), vert->edges.end(), edge);
|
|
199
|
+
if (it == vert->edges.end()) DoError(undefined_error_i);
|
|
200
|
+
vert->edges.erase(it);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
static bool FindLocMinIdx(const Path64& path, size_t len, size_t& idx)
|
|
204
|
+
{
|
|
205
|
+
if (len < 3) return false;
|
|
206
|
+
size_t i0 = idx, n = (idx + 1) % len;
|
|
207
|
+
while (path[n].y <= path[idx].y)
|
|
208
|
+
{
|
|
209
|
+
idx = n; n = (n + 1) % len;
|
|
210
|
+
if (idx == i0) return false; // fails if the path is completely horizontal
|
|
211
|
+
}
|
|
212
|
+
while (path[n].y >= path[idx].y)
|
|
213
|
+
{
|
|
214
|
+
idx = n; n = (n + 1) % len;
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static size_t Prev(size_t& idx, size_t len)
|
|
220
|
+
{
|
|
221
|
+
if (idx == 0) return len - 1; else return idx - 1;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
static size_t Next(size_t& idx, size_t len)
|
|
225
|
+
{
|
|
226
|
+
return (idx + 1) % len;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
static Edge* FindLinkingEdge(const Vertex2* vert1, const Vertex2* vert2, bool preferAscending)
|
|
230
|
+
{
|
|
231
|
+
Edge* res = nullptr;
|
|
232
|
+
for (auto e : vert1->edges)
|
|
233
|
+
{
|
|
234
|
+
if (e->vL == vert2 || e->vR == vert2)
|
|
235
|
+
{
|
|
236
|
+
if (e->kind == EdgeKind::loose ||
|
|
237
|
+
((e->kind == EdgeKind::ascend) == preferAscending)) return e;
|
|
238
|
+
res = e;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return res;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static Path64 PathFromTriangle(Triangle tri)
|
|
245
|
+
{
|
|
246
|
+
Path64 res;
|
|
247
|
+
res.reserve(3);
|
|
248
|
+
res.push_back(tri.edges[0]->vL->pt);
|
|
249
|
+
res.push_back(tri.edges[0]->vR->pt);
|
|
250
|
+
const Edge& e = *tri.edges[1];
|
|
251
|
+
if (e.vL->pt == res[0] || e.vL->pt == res[1])
|
|
252
|
+
res.push_back(e.vR->pt);
|
|
253
|
+
else
|
|
254
|
+
res.push_back(e.vL->pt);
|
|
255
|
+
return res;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
static double InCircleTest(const Point64& ptA, const Point64& ptB,
|
|
259
|
+
const Point64& ptC, const Point64& ptD)
|
|
260
|
+
{
|
|
261
|
+
// Return the determinant value of 3 x 3 matrix ...
|
|
262
|
+
// | ax-dx ay-dy Sqr(ax-dx)+Sqr(ay-dy) |
|
|
263
|
+
// | bx-dx by-dy Sqr(bx-dx)+Sqr(by-dy) |
|
|
264
|
+
// | cx-dx cy-dy Sqr(cx-dx)+Sqr(cy-dy) |
|
|
265
|
+
|
|
266
|
+
// The *sign* of the return value is determined by
|
|
267
|
+
// the orientation (CW vs CCW) of ptA, ptB & ptC.
|
|
268
|
+
|
|
269
|
+
// precondition - ptA, ptB & ptC make a *non-empty* triangle
|
|
270
|
+
double m00 = static_cast<double>(ptA.x - ptD.x);
|
|
271
|
+
double m01 = static_cast<double>(ptA.y - ptD.y);
|
|
272
|
+
double m02 = (Sqr(m00) + Sqr(m01));
|
|
273
|
+
double m10 = static_cast<double>(ptB.x - ptD.x);
|
|
274
|
+
double m11 = static_cast<double>(ptB.y - ptD.y);
|
|
275
|
+
double m12 = (Sqr(m10) + Sqr(m11));
|
|
276
|
+
double m20 = static_cast<double>(ptC.x - ptD.x);
|
|
277
|
+
double m21 = static_cast<double>(ptC.y - ptD.y);
|
|
278
|
+
double m22 = (Sqr(m20) + Sqr(m21));
|
|
279
|
+
return m00 * (m11 * m22 - m21 * m12) -
|
|
280
|
+
m10 * (m01 * m22 - m21 * m02) +
|
|
281
|
+
m20 * (m01 * m12 - m11 * m02);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
static double ShortestDistFromSegment(const Point64& pt, const Point64& segPt1, const Point64& segPt2)
|
|
285
|
+
{
|
|
286
|
+
// precondition: segPt1 <> segPt2
|
|
287
|
+
double dx = static_cast<double>(segPt2.x - segPt1.x);
|
|
288
|
+
double dy = static_cast<double>(segPt2.y - segPt1.y);
|
|
289
|
+
//Assert((dx < > 0) or (dy < > 0)); // ie segPt1 <> segPt2
|
|
290
|
+
|
|
291
|
+
double ax = static_cast<double>(pt.x - segPt1.x);
|
|
292
|
+
double ay = static_cast<double>(pt.y - segPt1.y);
|
|
293
|
+
//q = (ax * dx + ay * dy) / (dx * dx + dy * dy);
|
|
294
|
+
double qNum = ax * dx + ay * dy;
|
|
295
|
+
if (qNum < 0) // pt is closest to seq1
|
|
296
|
+
return DistanceSqr(pt, segPt1);
|
|
297
|
+
else if (qNum > (Sqr(dx) + Sqr(dy))) // pt is closest to seq2
|
|
298
|
+
return DistanceSqr(pt, segPt2);
|
|
299
|
+
else
|
|
300
|
+
return Sqr(ax * dy - dx * ay) / (dx * dx + dy * dy);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
static IntersectKind SegsIntersect(const Point64 s1a, const Point64 s1b,
|
|
304
|
+
const Point64 s2a, const Point64 s2b)
|
|
305
|
+
{
|
|
306
|
+
//ignore segments sharing an end-point
|
|
307
|
+
if (s1a == s2a || s1b == s2a || s1b == s2b) return IntersectKind::none;
|
|
308
|
+
|
|
309
|
+
double dy1 = static_cast<double>(s1b.y - s1a.y);
|
|
310
|
+
double dx1 = static_cast<double>(s1b.x - s1a.x);
|
|
311
|
+
double dy2 = static_cast<double>(s2b.y - s2a.y);
|
|
312
|
+
double dx2 = static_cast<double>(s2b.x - s2a.x);
|
|
313
|
+
double cp = dy1 * dx2 - dy2 * dx1;
|
|
314
|
+
if (cp == 0) return IntersectKind::collinear;
|
|
315
|
+
|
|
316
|
+
double t = (static_cast<double>(s1a.x - s2a.x) * dy2 -
|
|
317
|
+
static_cast<double>(s1a.y - s2a.y) * dx2);
|
|
318
|
+
|
|
319
|
+
// nb: testing for t == 0 is unreliable due to float imprecision
|
|
320
|
+
if (t >= 0)
|
|
321
|
+
{
|
|
322
|
+
if (cp < 0 || t >= cp) return IntersectKind::none;
|
|
323
|
+
}
|
|
324
|
+
else
|
|
325
|
+
{
|
|
326
|
+
if (cp > 0 || t <= cp) return IntersectKind::none;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// so far, the *segment* 's1' intersects the *line* through 's2',
|
|
330
|
+
// but now make sure it also intersects the *segment* 's2'
|
|
331
|
+
t = ((s1a.x - s2a.x) * dy1 - (s1a.y - s2a.y) * dx1);
|
|
332
|
+
if (t >= 0)
|
|
333
|
+
{
|
|
334
|
+
if (cp > 0 && t < cp) return IntersectKind::intersect;
|
|
335
|
+
}
|
|
336
|
+
else
|
|
337
|
+
{
|
|
338
|
+
if (cp < 0 && t > cp) return IntersectKind::intersect;
|
|
339
|
+
}
|
|
340
|
+
return IntersectKind::none;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
344
|
+
// Delaunay class definitions
|
|
345
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
346
|
+
|
|
347
|
+
void Delaunay::CleanUp()
|
|
348
|
+
{
|
|
349
|
+
for (auto v : allVertices) delete v;
|
|
350
|
+
allVertices.resize(0);
|
|
351
|
+
for (auto e : allEdges) delete e;
|
|
352
|
+
allEdges.resize(0);
|
|
353
|
+
for (auto t : allTriangles) delete t;
|
|
354
|
+
allTriangles.resize(0);
|
|
355
|
+
|
|
356
|
+
firstActive = nullptr;
|
|
357
|
+
lowermostVertex = nullptr;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
void Delaunay::ForceLegal(Edge* edge)
|
|
361
|
+
{
|
|
362
|
+
// don't try to make empty triangles legal
|
|
363
|
+
if (!edge->triA || !edge->triB) return;
|
|
364
|
+
|
|
365
|
+
// vertA will be assigned the vertex in edge's triangleA
|
|
366
|
+
// that is NOT an end vertex of edge
|
|
367
|
+
// Likewise, vertB will be assigned the vertex in edge's
|
|
368
|
+
// triangleB that is NOT an end vertex of edge
|
|
369
|
+
// If edge is rotated, vertA & vertB will become its end vertices.
|
|
370
|
+
Vertex2* vertA = nullptr;
|
|
371
|
+
Vertex2* vertB = nullptr;
|
|
372
|
+
|
|
373
|
+
// Excluding 'edge', edgesA will contain two edges (one from
|
|
374
|
+
// triangleA and one from triangleB) that touch edge.vL.
|
|
375
|
+
// And edgesB will contain the two edges that touch edge.vR.
|
|
376
|
+
|
|
377
|
+
Edge* edgesA[3] = { nullptr, nullptr, nullptr };
|
|
378
|
+
Edge* edgesB[3] = { nullptr, nullptr, nullptr };
|
|
379
|
+
for (int i = 0; i < 3; ++i)
|
|
380
|
+
{
|
|
381
|
+
if (edge->triA->edges[i] == edge) continue;
|
|
382
|
+
switch (EdgeContains(edge->triA->edges[i], edge->vL))
|
|
383
|
+
{
|
|
384
|
+
case EdgeContainsResult::left:
|
|
385
|
+
edgesA[1] = edge->triA->edges[i];
|
|
386
|
+
vertA = edge->triA->edges[i]->vR;
|
|
387
|
+
break;
|
|
388
|
+
case EdgeContainsResult::right:
|
|
389
|
+
edgesA[1] = edge->triA->edges[i];
|
|
390
|
+
vertA = edge->triA->edges[i]->vL;
|
|
391
|
+
break;
|
|
392
|
+
default:
|
|
393
|
+
edgesB[1] = edge->triA->edges[i];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
for (int i = 0; i < 3; ++i)
|
|
398
|
+
{
|
|
399
|
+
if (edge->triB->edges[i] == edge) continue;
|
|
400
|
+
switch (EdgeContains(edge->triB->edges[i], edge->vL))
|
|
401
|
+
{
|
|
402
|
+
case EdgeContainsResult::left:
|
|
403
|
+
edgesA[2] = edge->triB->edges[i];
|
|
404
|
+
vertB = edge->triB->edges[i]->vR;
|
|
405
|
+
break;
|
|
406
|
+
case EdgeContainsResult::right:
|
|
407
|
+
edgesA[2] = edge->triB->edges[i];
|
|
408
|
+
vertB = edge->triB->edges[i]->vL;
|
|
409
|
+
break;
|
|
410
|
+
default:
|
|
411
|
+
edgesB[2] = edge->triB->edges[i];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// InCircleTest reqires edge.triangleA to be a valid triangle
|
|
416
|
+
// if IsEmptyTriangle(edge.triangleA) then Exit; // slower
|
|
417
|
+
if (CrossProductSign(vertA->pt, edge->vL->pt, edge->vR->pt) == 0) return;
|
|
418
|
+
|
|
419
|
+
// ictResult - result sign is dependant on triangleA's orientation
|
|
420
|
+
double ictResult = InCircleTest(vertA->pt, edge->vL->pt, edge->vR->pt, vertB->pt);
|
|
421
|
+
if (ictResult == 0 || // if on or out of circle then exit
|
|
422
|
+
(RightTurning(vertA->pt, edge->vL->pt, edge->vR->pt) == (ictResult < 0))) return;
|
|
423
|
+
|
|
424
|
+
// TRIANGLES HERE ARE **NOT** DELAUNAY COMPLIANT, SO MAKE THEM SO.
|
|
425
|
+
|
|
426
|
+
// NOTE: ONCE WE BEGIN DELAUNAY COMPLIANCE, vL & vR WILL
|
|
427
|
+
// NO LONGER REPRESENT LEFT AND RIGHT VERTEX ORIENTATION.
|
|
428
|
+
// THIS IS MINOR PERFORMANCE EFFICIENCY IS SAFE AS LONG AS
|
|
429
|
+
// THE TRIANGULATE() METHOD IS CALLED ONCE ONLY ON A GIVEN
|
|
430
|
+
// SET OF PATHS
|
|
431
|
+
|
|
432
|
+
edge->vL = vertA;
|
|
433
|
+
edge->vR = vertB;
|
|
434
|
+
|
|
435
|
+
edge->triA->edges[0] = edge;
|
|
436
|
+
for (int i = 1; i < 3; ++i)
|
|
437
|
+
{
|
|
438
|
+
edge->triA->edges[i] = edgesA[i];
|
|
439
|
+
if (!edgesA[i]) DoError(undefined_error_i);
|
|
440
|
+
if (IsLooseEdge(*edgesA[i]))
|
|
441
|
+
pendingDelaunayStack.push(edgesA[i]);
|
|
442
|
+
// since each edge has its own triangleA and triangleB, we have to be careful
|
|
443
|
+
// to update the correct one ...
|
|
444
|
+
if (edgesA[i]->triA == edge->triA || edgesA[i]->triB == edge->triA) continue;
|
|
445
|
+
|
|
446
|
+
if (edgesA[i]->triA == edge->triB)
|
|
447
|
+
edgesA[i]->triA = edge->triA;
|
|
448
|
+
else if (edgesA[i]->triB == edge->triB)
|
|
449
|
+
edgesA[i]->triB = edge->triA;
|
|
450
|
+
else DoError(undefined_error_i);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
edge->triB->edges[0] = edge;
|
|
454
|
+
for (int i = 1; i < 3; ++i)
|
|
455
|
+
{
|
|
456
|
+
edge->triB->edges[i] = edgesB[i];
|
|
457
|
+
if (!edgesB[i]) DoError(undefined_error_i);
|
|
458
|
+
if (IsLooseEdge(*edgesB[i]))
|
|
459
|
+
pendingDelaunayStack.push(edgesB[i]);
|
|
460
|
+
// since each edge has its own triangleA and triangleB, we have to be careful
|
|
461
|
+
// to update the correct one ...
|
|
462
|
+
if (edgesB[i]->triA == edge->triB || edgesB[i]->triB == edge->triB) continue;
|
|
463
|
+
|
|
464
|
+
if (edgesB[i]->triA == edge->triA)
|
|
465
|
+
edgesB[i]->triA = edge->triB;
|
|
466
|
+
else if (edgesB[i]->triB == edge->triA)
|
|
467
|
+
edgesB[i]->triB = edge->triB;
|
|
468
|
+
else DoError(undefined_error_i);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
Edge* Delaunay::CreateEdge(Vertex2* v1, Vertex2* v2, EdgeKind k)
|
|
474
|
+
{
|
|
475
|
+
Edge* res = allEdges.emplace_back(new Edge());
|
|
476
|
+
if (v1->pt.y == v2->pt.y)
|
|
477
|
+
{
|
|
478
|
+
res->vB = v1; res->vT = v2;
|
|
479
|
+
}
|
|
480
|
+
else if (v1->pt.y < v2->pt.y)
|
|
481
|
+
{
|
|
482
|
+
res->vB = v2; res->vT = v1;
|
|
483
|
+
}
|
|
484
|
+
else
|
|
485
|
+
{
|
|
486
|
+
res->vB = v1; res->vT = v2;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (v1->pt.x <= v2->pt.x)
|
|
490
|
+
{
|
|
491
|
+
res->vL = v1; res->vR = v2;
|
|
492
|
+
}
|
|
493
|
+
else
|
|
494
|
+
{
|
|
495
|
+
res->vL = v2; res->vR = v1;
|
|
496
|
+
}
|
|
497
|
+
res->kind = k;
|
|
498
|
+
v1->edges.push_back(res);
|
|
499
|
+
v2->edges.push_back(res);
|
|
500
|
+
|
|
501
|
+
if (k == EdgeKind::loose)
|
|
502
|
+
{
|
|
503
|
+
pendingDelaunayStack.push(res);
|
|
504
|
+
AddEdgeToActives(res);
|
|
505
|
+
}
|
|
506
|
+
return res;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
Triangle* Delaunay::CreateTriangle(Edge* e1, Edge* e2, Edge* e3)
|
|
510
|
+
{
|
|
511
|
+
Triangle* res = allTriangles.emplace_back(new Triangle(e1, e2, e3));
|
|
512
|
+
// nb: only expire loose edges when both sides of these edges have triangles.
|
|
513
|
+
for (int i = 0; i < 3; ++i)
|
|
514
|
+
if (res->edges[i]->triA)
|
|
515
|
+
{
|
|
516
|
+
res->edges[i]->triB = res;
|
|
517
|
+
// this is the edge's second triangle hence no longer active
|
|
518
|
+
RemoveEdgeFromActives(res->edges[i]);
|
|
519
|
+
}
|
|
520
|
+
else
|
|
521
|
+
{
|
|
522
|
+
res->edges[i]->triA = res;
|
|
523
|
+
// this is the edge's first triangle, so only remove
|
|
524
|
+
// this edge from actives if it's a fixed edge.
|
|
525
|
+
if (!IsLooseEdge(*res->edges[i]))
|
|
526
|
+
RemoveEdgeFromActives(res->edges[i]);
|
|
527
|
+
}
|
|
528
|
+
return res;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
bool Delaunay::RemoveIntersection(Edge* e1, Edge* e2)
|
|
532
|
+
{
|
|
533
|
+
// find which vertex is closest to the other segment
|
|
534
|
+
// (ie not the vertex closest to the intersection point) ...
|
|
535
|
+
|
|
536
|
+
Vertex2* v = e1->vL;
|
|
537
|
+
Edge* tmpE = e2;
|
|
538
|
+
double d = ShortestDistFromSegment(e1->vL->pt, e2->vL->pt, e2->vR->pt);
|
|
539
|
+
double d2 = ShortestDistFromSegment(e1->vR->pt, e2->vL->pt, e2->vR->pt);
|
|
540
|
+
if (d2 < d) { d = d2; v = e1->vR; }
|
|
541
|
+
d2 = ShortestDistFromSegment(e2->vL->pt, e1->vL->pt, e1->vR->pt);
|
|
542
|
+
if (d2 < d) { d = d2; tmpE = e1; v = e2->vL; }
|
|
543
|
+
d2 = ShortestDistFromSegment(e2->vR->pt, e1->vL->pt, e1->vR->pt);
|
|
544
|
+
if (d2 < d) { d = d2; tmpE = e1; v = e2->vR; }
|
|
545
|
+
if (d > 1.000)
|
|
546
|
+
return false; // Oops - this is not just a simple 'rounding' intersection
|
|
547
|
+
|
|
548
|
+
// split 'tmpE' into 2 edges at 'v'
|
|
549
|
+
Vertex2* v2 = tmpE->vT;
|
|
550
|
+
RemoveEdgeFromVertex(v2, tmpE);
|
|
551
|
+
// replace v2 in tmpE with v
|
|
552
|
+
if (tmpE->vL == v2) tmpE->vL = v;
|
|
553
|
+
else tmpE->vR = v;
|
|
554
|
+
tmpE->vT = v;
|
|
555
|
+
v->edges.push_back(tmpE);
|
|
556
|
+
v->innerLM = false; // #47
|
|
557
|
+
// left turning is angle positive
|
|
558
|
+
if (tmpE->vB->innerLM && GetLocMinAngle(tmpE->vB) <= 0)
|
|
559
|
+
tmpE->vB->innerLM = false; // #44, 52
|
|
560
|
+
// finally create a new edge between v and v2 ...
|
|
561
|
+
CreateEdge(v, v2, tmpE->kind);
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
bool Delaunay::FixupEdgeIntersects()
|
|
566
|
+
{
|
|
567
|
+
// precondition - edgeList must be sorted - ascending on edge.vL.pt.x
|
|
568
|
+
|
|
569
|
+
for (size_t i1 = 0; i1 < allEdges.size(); ++i1)
|
|
570
|
+
{
|
|
571
|
+
Edge* e1 = allEdges[i1];
|
|
572
|
+
// nb: we can safely ignore edges newly created inside this for loop
|
|
573
|
+
for (size_t i2 = i1 + 1; i2 < allEdges.size(); ++i2)
|
|
574
|
+
{
|
|
575
|
+
Edge* e2 = allEdges[i2];
|
|
576
|
+
if (e2->vL->pt.x >= e1->vR->pt.x)
|
|
577
|
+
break; // all 'e' from now on are too far right
|
|
578
|
+
|
|
579
|
+
// 'e2' is inside e1's horizontal region. If 'e2' is also inside
|
|
580
|
+
// e1's vertical region, only then check for an intersection ...
|
|
581
|
+
if (e2->vT->pt.y < e1->vB->pt.y && e2->vB->pt.y > e1->vT->pt.y &&
|
|
582
|
+
(SegsIntersect(e2->vL->pt, e2->vR->pt,
|
|
583
|
+
e1->vL->pt, e1->vR->pt) == IntersectKind::intersect))
|
|
584
|
+
{
|
|
585
|
+
if (!RemoveIntersection(e2, e1))
|
|
586
|
+
return false; // oops!!
|
|
587
|
+
}
|
|
588
|
+
// nb: collinear edges are managed in MergeDupOrCollinearVertices below
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
void Delaunay::SplitEdge(Edge* longE, Edge* shortE)
|
|
595
|
+
{
|
|
596
|
+
auto oldT = longE->vT, newT = shortE->vT;
|
|
597
|
+
// remove longEdge from longEdge.vT.edges
|
|
598
|
+
RemoveEdgeFromVertex(oldT, longE);
|
|
599
|
+
// shorten longEdge
|
|
600
|
+
longE->vT = newT;
|
|
601
|
+
if (longE->vL == oldT) longE->vL = newT;
|
|
602
|
+
else longE->vR = newT;
|
|
603
|
+
// add shortened longEdge to newT.edges
|
|
604
|
+
newT->edges.push_back(longE);
|
|
605
|
+
// and create a new edge betweem newV, oldT
|
|
606
|
+
CreateEdge(newT, oldT, longE->kind);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
void Delaunay::MergeDupOrCollinearVertices()
|
|
610
|
+
{
|
|
611
|
+
// note: this procedure may add new edges and change the
|
|
612
|
+
// number of edges connected with a given vertex, but it
|
|
613
|
+
// won't add or delete vertices (so it's safe to use iterators)
|
|
614
|
+
auto vIter1 = allVertices.begin();
|
|
615
|
+
for (auto vIter2 = allVertices.begin() + 1; vIter2 != allVertices.end(); ++vIter2)
|
|
616
|
+
{
|
|
617
|
+
if ((*vIter1)->pt != (*vIter2)->pt)
|
|
618
|
+
{
|
|
619
|
+
vIter1 = vIter2;
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// merge v1 & v2
|
|
624
|
+
Vertex2* v1 = *vIter1, * v2 = *vIter2;
|
|
625
|
+
if (!v1->innerLM || !v2->innerLM)
|
|
626
|
+
v1->innerLM = false;
|
|
627
|
+
|
|
628
|
+
// in all of v2's edges, replace links to v2 with links to v1
|
|
629
|
+
for (auto e : v2->edges)
|
|
630
|
+
{
|
|
631
|
+
if (e->vB == v2) e->vB = v1;
|
|
632
|
+
else e->vT = v1;
|
|
633
|
+
if (e->vL == v2) e->vL = v1;
|
|
634
|
+
else e->vR = v1;
|
|
635
|
+
}
|
|
636
|
+
// move all of v2's edges to v1
|
|
637
|
+
std::copy(v2->edges.begin(), v2->edges.end(), back_inserter(v1->edges));
|
|
638
|
+
v2->edges.resize(0);
|
|
639
|
+
|
|
640
|
+
// excluding horizontals, if pv.edges contains two edges
|
|
641
|
+
// that are *collinear* and share the same bottom coords
|
|
642
|
+
// but have different lengths, split the longer edge at
|
|
643
|
+
// the top of the shorter edge ...
|
|
644
|
+
for (auto itE = v1->edges.begin(); itE != v1->edges.end(); ++itE)
|
|
645
|
+
{
|
|
646
|
+
if (IsHorizontal(*(*itE)) || (*itE)->vB != v1) continue;
|
|
647
|
+
for (auto itE2 = itE + 1; itE2 != v1->edges.end(); ++itE2)
|
|
648
|
+
{
|
|
649
|
+
auto e1 = *itE, e2 = *itE2;
|
|
650
|
+
if (e2->vB != v1 || e1->vT->pt.y == e2->vT->pt.y ||
|
|
651
|
+
(CrossProductSign(e1->vT->pt, v1->pt, e2->vT->pt) != 0)) continue;
|
|
652
|
+
// we have parallel edges, both heading up from v1.pt.
|
|
653
|
+
// split the longer edge at the top of the shorter edge.
|
|
654
|
+
if (e1->vT->pt.y < e2->vT->pt.y) SplitEdge(e1, e2);
|
|
655
|
+
else SplitEdge(e2, e1);
|
|
656
|
+
break; // because only two edges can be collinear
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
Edge* Delaunay::CreateInnerLocMinLooseEdge(Vertex2* vAbove)
|
|
663
|
+
{
|
|
664
|
+
if (!firstActive) return nullptr; // oops!!
|
|
665
|
+
|
|
666
|
+
int64_t xAbove = vAbove->pt.x;
|
|
667
|
+
int64_t yAbove = vAbove->pt.y;
|
|
668
|
+
|
|
669
|
+
// find the closest 'active' edge with a vertex that's not above vAbove
|
|
670
|
+
Edge* e = firstActive, *eBelow = nullptr;
|
|
671
|
+
double bestD = -1.0;
|
|
672
|
+
while (e)
|
|
673
|
+
{
|
|
674
|
+
if (e->vL->pt.x <= xAbove && e->vR->pt.x >= xAbove &&
|
|
675
|
+
e->vB->pt.y >= yAbove && e->vB != vAbove && e->vT != vAbove &&
|
|
676
|
+
!LeftTurning(e->vL->pt, vAbove->pt, e->vR->pt))
|
|
677
|
+
{
|
|
678
|
+
double d = ShortestDistFromSegment(vAbove->pt, e->vL->pt, e->vR->pt);
|
|
679
|
+
if (!eBelow || d < bestD) // compare e with eBelow
|
|
680
|
+
{
|
|
681
|
+
eBelow = e;
|
|
682
|
+
bestD = d;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
e = e->nextE;
|
|
686
|
+
}
|
|
687
|
+
if (!eBelow) return nullptr; // oops!!
|
|
688
|
+
|
|
689
|
+
// get the best vertex from 'eBelow'
|
|
690
|
+
Vertex2* vBest = (eBelow->vT->pt.y <= yAbove) ? eBelow->vB : eBelow->vT;
|
|
691
|
+
int64_t xBest = vBest->pt.x;
|
|
692
|
+
int64_t yBest = vBest->pt.y;
|
|
693
|
+
|
|
694
|
+
// make sure no edges intersect 'vAbove' and 'vBest' ...
|
|
695
|
+
// todo: fActives is currently *unsorted* but consider making it
|
|
696
|
+
// a tree structure based on each edge's left and right bounds
|
|
697
|
+
e = firstActive;
|
|
698
|
+
if (xBest < xAbove)
|
|
699
|
+
{
|
|
700
|
+
while (e)
|
|
701
|
+
{
|
|
702
|
+
if (e->vR->pt.x > xBest && e->vL->pt.x < xAbove &&
|
|
703
|
+
e->vB->pt.y > yAbove && e->vT->pt.y < yBest &&
|
|
704
|
+
(SegsIntersect(e->vB->pt, e->vT->pt,
|
|
705
|
+
vBest->pt, vAbove->pt) == IntersectKind::intersect))
|
|
706
|
+
{
|
|
707
|
+
vBest = (e->vT->pt.y > yAbove) ? e->vT : e->vB;
|
|
708
|
+
xBest = vBest->pt.x;
|
|
709
|
+
yBest = vBest->pt.y;
|
|
710
|
+
}
|
|
711
|
+
e = e->nextE;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
else
|
|
715
|
+
{
|
|
716
|
+
while (e)
|
|
717
|
+
{
|
|
718
|
+
if (e->vR->pt.x < xBest && e->vL->pt.x > xAbove &&
|
|
719
|
+
e->vB->pt.y > yAbove && e->vT->pt.y < yBest &&
|
|
720
|
+
(SegsIntersect(e->vB->pt, e->vT->pt,
|
|
721
|
+
vBest->pt, vAbove->pt) == IntersectKind::intersect))
|
|
722
|
+
{
|
|
723
|
+
vBest = e->vT->pt.y > yAbove ? e->vT : e->vB;
|
|
724
|
+
xBest = vBest->pt.x;
|
|
725
|
+
yBest = vBest->pt.y;
|
|
726
|
+
}
|
|
727
|
+
e = e->nextE;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return CreateEdge(vBest, vAbove, EdgeKind::loose);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
Edge* Delaunay::HorizontalBetween(Vertex2* v1, Vertex2* v2)
|
|
734
|
+
{
|
|
735
|
+
int64_t y = v1->pt.y, l, r;
|
|
736
|
+
if (v1->pt.x > v2->pt.x)
|
|
737
|
+
{
|
|
738
|
+
l = v2->pt.x;
|
|
739
|
+
r = v1->pt.x;
|
|
740
|
+
}
|
|
741
|
+
else
|
|
742
|
+
{
|
|
743
|
+
l = v1->pt.x;
|
|
744
|
+
r = v2->pt.x;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
Edge* res = firstActive;
|
|
748
|
+
while (res)
|
|
749
|
+
{
|
|
750
|
+
if (res->vL->pt.y == y && res->vR->pt.y == y &&
|
|
751
|
+
res->vL->pt.x >= l && res->vR->pt.x <= r &&
|
|
752
|
+
(res->vL->pt.x != l || res->vL->pt.x != r)) break;
|
|
753
|
+
res = res->nextE;
|
|
754
|
+
}
|
|
755
|
+
return res;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
void Delaunay::DoTriangulateLeft(Edge* edge, Vertex2* pivot, int64_t minY)
|
|
759
|
+
{
|
|
760
|
+
// precondition - pivot must be one end of edge (Usually .vB)
|
|
761
|
+
//Assert(!EdgeCompleted(edge));
|
|
762
|
+
Vertex2* vAlt = nullptr;
|
|
763
|
+
Edge* eAlt = nullptr;
|
|
764
|
+
Vertex2* v = (edge->vB == pivot) ? edge->vT : edge->vB;
|
|
765
|
+
|
|
766
|
+
for (auto e : pivot->edges)
|
|
767
|
+
{
|
|
768
|
+
if (e == edge || !e->isActive) continue;
|
|
769
|
+
Vertex2* vX = e->vT == pivot ? e->vB : e->vT;
|
|
770
|
+
if (vX == v) continue;
|
|
771
|
+
|
|
772
|
+
int cps = CrossProductSign(v->pt, pivot->pt, vX->pt);
|
|
773
|
+
if (cps == 0) //collinear paths
|
|
774
|
+
{
|
|
775
|
+
// if pivot is between v and vX then continue;
|
|
776
|
+
// nb: this is important for both horiz and non-horiz collinear
|
|
777
|
+
if ((v->pt.x > pivot->pt.x) == (pivot->pt.x > vX->pt.x)) continue;
|
|
778
|
+
}
|
|
779
|
+
// else if right-turning or not the best edge, then continue
|
|
780
|
+
else if (cps > 0 || (vAlt && !LeftTurning(vX->pt, pivot->pt, vAlt->pt)))
|
|
781
|
+
continue;
|
|
782
|
+
vAlt = vX;
|
|
783
|
+
eAlt = e;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (!vAlt || vAlt->pt.y < minY) return;
|
|
787
|
+
|
|
788
|
+
// Don't triangulate **across** fixed edges
|
|
789
|
+
if (vAlt->pt.y < pivot->pt.y)
|
|
790
|
+
{
|
|
791
|
+
if (IsLeftEdge(*eAlt)) return;
|
|
792
|
+
}
|
|
793
|
+
else if (vAlt->pt.y > pivot->pt.y)
|
|
794
|
+
{
|
|
795
|
+
if (IsRightEdge(*eAlt)) return;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
Edge* eX = FindLinkingEdge(vAlt, v, (vAlt->pt.y < v->pt.y));
|
|
799
|
+
if (!eX)
|
|
800
|
+
{
|
|
801
|
+
// be very careful creating loose horizontals at minY
|
|
802
|
+
if (vAlt->pt.y == v->pt.y && v->pt.y == minY &&
|
|
803
|
+
HorizontalBetween(vAlt, v)) return;
|
|
804
|
+
eX = CreateEdge(vAlt, v, EdgeKind::loose);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
CreateTriangle(edge, eAlt, eX);
|
|
808
|
+
if (!EdgeCompleted(eX))
|
|
809
|
+
DoTriangulateLeft(eX, vAlt, minY);
|
|
810
|
+
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
void Delaunay::DoTriangulateRight(Edge* edge, Vertex2* pivot, int64_t minY)
|
|
814
|
+
{
|
|
815
|
+
// precondition - pivot must be one end of edge (Usually .vB)
|
|
816
|
+
//Assert(!EdgeCompleted(edge));
|
|
817
|
+
Vertex2* vAlt = nullptr;
|
|
818
|
+
Edge* eAlt = nullptr;
|
|
819
|
+
Vertex2* v = (edge->vB == pivot) ? edge->vT : edge->vB;
|
|
820
|
+
|
|
821
|
+
for (auto e : pivot->edges)
|
|
822
|
+
{
|
|
823
|
+
if (e == edge || !e->isActive) continue;
|
|
824
|
+
Vertex2* vX = e->vT == pivot ? e->vB : e->vT;
|
|
825
|
+
if (vX == v) continue;
|
|
826
|
+
|
|
827
|
+
int cps = CrossProductSign(v->pt, pivot->pt, vX->pt);
|
|
828
|
+
if (cps == 0) //collinear paths
|
|
829
|
+
{
|
|
830
|
+
// if pivot is between v and vX then continue;
|
|
831
|
+
// nb: this is important for both horiz and non-horiz collinear
|
|
832
|
+
if ((v->pt.x > pivot->pt.x) == (pivot->pt.x > vX->pt.x)) continue;
|
|
833
|
+
}
|
|
834
|
+
// else if right-turning or not the best edge, then continue
|
|
835
|
+
else if (cps < 0 || (vAlt && !RightTurning(vX->pt, pivot->pt, vAlt->pt)))
|
|
836
|
+
continue;
|
|
837
|
+
vAlt = vX;
|
|
838
|
+
eAlt = e;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (!vAlt || vAlt->pt.y < minY) return;
|
|
842
|
+
|
|
843
|
+
// Don't triangulate **across** fixed edges
|
|
844
|
+
if (vAlt->pt.y < pivot->pt.y)
|
|
845
|
+
{
|
|
846
|
+
if (IsRightEdge(*eAlt)) return;
|
|
847
|
+
}
|
|
848
|
+
else if (vAlt->pt.y > pivot->pt.y)
|
|
849
|
+
{
|
|
850
|
+
if (IsLeftEdge(*eAlt)) return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
Edge* eX = FindLinkingEdge(vAlt, v, (vAlt->pt.y > v->pt.y));
|
|
854
|
+
if (!eX)
|
|
855
|
+
{
|
|
856
|
+
// be very careful creating loose horizontals at minY
|
|
857
|
+
if (vAlt->pt.y == v->pt.y && v->pt.y == minY &&
|
|
858
|
+
HorizontalBetween(vAlt, v)) return;
|
|
859
|
+
eX = CreateEdge(vAlt, v, EdgeKind::loose);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
CreateTriangle(edge, eX, eAlt);
|
|
863
|
+
if (!EdgeCompleted(eX))
|
|
864
|
+
DoTriangulateRight(eX, vAlt, minY);
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
void Delaunay::AddEdgeToActives(Edge* edge)
|
|
870
|
+
{
|
|
871
|
+
// nb: on occassions this method can get called twice for a given edge
|
|
872
|
+
// This is because, in the Triangulate() method where vertex 'edges'
|
|
873
|
+
// arrays are being parsed, edges can can be removed from the array
|
|
874
|
+
// which changes the index of following edges.
|
|
875
|
+
if (edge->isActive) return;
|
|
876
|
+
|
|
877
|
+
edge->prevE = nullptr;
|
|
878
|
+
edge->nextE = firstActive;
|
|
879
|
+
edge->isActive = true;
|
|
880
|
+
if (firstActive)
|
|
881
|
+
firstActive->prevE = edge;
|
|
882
|
+
firstActive = edge;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
void Delaunay::RemoveEdgeFromActives(Edge* edge)
|
|
887
|
+
{
|
|
888
|
+
// first, remove the edge from its vertices
|
|
889
|
+
RemoveEdgeFromVertex(edge->vB, edge);
|
|
890
|
+
RemoveEdgeFromVertex(edge->vT, edge);
|
|
891
|
+
|
|
892
|
+
// now remove the edge from double linked list (AEL)
|
|
893
|
+
Edge* prev = edge->prevE;
|
|
894
|
+
Edge* next = edge->nextE;
|
|
895
|
+
if (next) next->prevE = prev;
|
|
896
|
+
if (prev) prev->nextE = next;
|
|
897
|
+
edge->isActive = false;
|
|
898
|
+
if (firstActive == edge) firstActive = next;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
Paths64 Delaunay::Execute(const Paths64& paths, TriangulateResult& triResult)
|
|
902
|
+
{
|
|
903
|
+
if (!AddPaths(paths))
|
|
904
|
+
{
|
|
905
|
+
triResult = TriangulateResult::no_polygons;
|
|
906
|
+
return Paths64(); // oops!
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// if necessary fix path orientation because the algorithm
|
|
910
|
+
// expects clockwise outer paths and counter-clockwise inner paths
|
|
911
|
+
if (lowermostVertex->innerLM)
|
|
912
|
+
{
|
|
913
|
+
// the orientation of added paths must be wrong, so
|
|
914
|
+
// 1. reverse innerLM flags ...
|
|
915
|
+
Vertex2* lm;
|
|
916
|
+
while (!locMinStack.empty())
|
|
917
|
+
{
|
|
918
|
+
lm = locMinStack.top();
|
|
919
|
+
lm->innerLM = !lm->innerLM;
|
|
920
|
+
locMinStack.pop();
|
|
921
|
+
}
|
|
922
|
+
// 2. swap edge kinds
|
|
923
|
+
for (Edge* e : allEdges)
|
|
924
|
+
if (e->kind == EdgeKind::ascend)
|
|
925
|
+
e->kind = EdgeKind::descend;
|
|
926
|
+
else
|
|
927
|
+
e->kind = EdgeKind::ascend;
|
|
928
|
+
}
|
|
929
|
+
else
|
|
930
|
+
{
|
|
931
|
+
// path orientation is fine so ...
|
|
932
|
+
while (!locMinStack.empty())
|
|
933
|
+
locMinStack.pop();
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
std::sort(allEdges.begin(), allEdges.end(), EdgeListSort);
|
|
937
|
+
if (!FixupEdgeIntersects())
|
|
938
|
+
{
|
|
939
|
+
CleanUp();
|
|
940
|
+
triResult = TriangulateResult::paths_intersect;
|
|
941
|
+
return Paths64(); // oops!
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
std::sort(allVertices.begin(), allVertices.end(), VertexListSort);
|
|
945
|
+
MergeDupOrCollinearVertices();
|
|
946
|
+
|
|
947
|
+
int64_t currY = allVertices[0]->pt.y;
|
|
948
|
+
for (auto vIt = allVertices.begin(); vIt != allVertices.end(); ++vIt)
|
|
949
|
+
{
|
|
950
|
+
Vertex2* v = *vIt;
|
|
951
|
+
if (v->edges.empty()) continue;
|
|
952
|
+
if (v->pt.y != currY)
|
|
953
|
+
{
|
|
954
|
+
// JOIN AN INNER LOCMIN WITH A SUITABLE EDGE BELOW
|
|
955
|
+
while (!locMinStack.empty())
|
|
956
|
+
{
|
|
957
|
+
Vertex2* lm = locMinStack.top();
|
|
958
|
+
locMinStack.pop();
|
|
959
|
+
Edge* e = CreateInnerLocMinLooseEdge(lm);
|
|
960
|
+
if (!e)
|
|
961
|
+
{
|
|
962
|
+
CleanUp();
|
|
963
|
+
triResult = TriangulateResult::fail;
|
|
964
|
+
return Paths64(); // oops!
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (IsHorizontal(*e))
|
|
968
|
+
{
|
|
969
|
+
if (e->vL == e->vB)
|
|
970
|
+
DoTriangulateLeft(e, e->vB, currY); else
|
|
971
|
+
DoTriangulateRight(e, e->vB, currY);
|
|
972
|
+
}
|
|
973
|
+
else
|
|
974
|
+
{
|
|
975
|
+
DoTriangulateLeft(e, e->vB, currY);
|
|
976
|
+
if (!EdgeCompleted(e))
|
|
977
|
+
DoTriangulateRight(e, e->vB, currY);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// and because adding locMin edges to Actives was delayed ..
|
|
981
|
+
AddEdgeToActives(lm->edges[0]);
|
|
982
|
+
AddEdgeToActives(lm->edges[1]);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
while (!horzEdgeStack.empty())
|
|
986
|
+
{
|
|
987
|
+
Edge* e = horzEdgeStack.top();
|
|
988
|
+
horzEdgeStack.pop();
|
|
989
|
+
if (EdgeCompleted(e)) continue;
|
|
990
|
+
if (e->vB == e->vL) // #45
|
|
991
|
+
{
|
|
992
|
+
if (IsLeftEdge(*e))
|
|
993
|
+
DoTriangulateLeft(e, e->vB, currY);
|
|
994
|
+
}
|
|
995
|
+
else
|
|
996
|
+
if (IsRightEdge(*e))
|
|
997
|
+
DoTriangulateRight(e, e->vB, currY);
|
|
998
|
+
}
|
|
999
|
+
currY = v->pt.y;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
for (int i = static_cast<int>(v->edges.size()) - 1; i >= 0; --i)
|
|
1003
|
+
{
|
|
1004
|
+
// the following line may look superfluous, but within this loop
|
|
1005
|
+
// v->edges may be altered with additions and or deletions.
|
|
1006
|
+
// So this line *is* necessary (and why we can't use an iterator).
|
|
1007
|
+
// Also, we need to use a *descending* index which is safe because
|
|
1008
|
+
// any additions will be loose edges which are ignored here.
|
|
1009
|
+
if (i >= static_cast<int>(v->edges.size())) continue;
|
|
1010
|
+
|
|
1011
|
+
Edge* e = v->edges[i];
|
|
1012
|
+
if (EdgeCompleted(e) || IsLooseEdge(*e)) continue;
|
|
1013
|
+
|
|
1014
|
+
if (v == e->vB)
|
|
1015
|
+
{
|
|
1016
|
+
if (IsHorizontal(*e))
|
|
1017
|
+
horzEdgeStack.push(e);
|
|
1018
|
+
// delay adding locMin edges to actives
|
|
1019
|
+
if (!v->innerLM)
|
|
1020
|
+
AddEdgeToActives(e);
|
|
1021
|
+
}
|
|
1022
|
+
else
|
|
1023
|
+
{
|
|
1024
|
+
if (IsHorizontal(*e))
|
|
1025
|
+
horzEdgeStack.push(e);
|
|
1026
|
+
else if (IsLeftEdge(*e))
|
|
1027
|
+
DoTriangulateLeft(e, e->vB, v->pt.y);
|
|
1028
|
+
else
|
|
1029
|
+
DoTriangulateRight(e, e->vB, v->pt.y);
|
|
1030
|
+
}
|
|
1031
|
+
} // for v->edges loop
|
|
1032
|
+
|
|
1033
|
+
if (v->innerLM) locMinStack.push(v);
|
|
1034
|
+
|
|
1035
|
+
} // for allVertices loop
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
while (!horzEdgeStack.empty())
|
|
1039
|
+
{
|
|
1040
|
+
Edge* e = horzEdgeStack.top();
|
|
1041
|
+
horzEdgeStack.pop();
|
|
1042
|
+
if (!EdgeCompleted(e) && e->vB == e->vL)
|
|
1043
|
+
DoTriangulateLeft(e, e->vB, currY);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if (useDelaunay)
|
|
1047
|
+
{
|
|
1048
|
+
// Convert triangles to Delaunay conforming
|
|
1049
|
+
while (!pendingDelaunayStack.empty())
|
|
1050
|
+
{
|
|
1051
|
+
Edge* e = pendingDelaunayStack.top();
|
|
1052
|
+
pendingDelaunayStack.pop();
|
|
1053
|
+
ForceLegal(e);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
Paths64 res;
|
|
1058
|
+
res.reserve(allTriangles.size());
|
|
1059
|
+
for (auto tri : allTriangles)
|
|
1060
|
+
{
|
|
1061
|
+
Path64 p = PathFromTriangle(*tri);
|
|
1062
|
+
int cps = CrossProductSign(p[0], p[1], p[2]);
|
|
1063
|
+
if (cps == 0) continue; // skip any empty triangles
|
|
1064
|
+
if (cps < 0) // ccw
|
|
1065
|
+
std::reverse(p.begin(), p.end());
|
|
1066
|
+
res.push_back(p);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
CleanUp();
|
|
1070
|
+
triResult = TriangulateResult::success;
|
|
1071
|
+
return res;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
static double DistSqr(const Point64& pt1, const Point64& pt2)
|
|
1075
|
+
{
|
|
1076
|
+
return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
void Delaunay::AddPath(const Path64& path)
|
|
1080
|
+
{
|
|
1081
|
+
size_t len = path.size(), i0 = 0, iPrev, iNext;
|
|
1082
|
+
// find the first locMin for the current path
|
|
1083
|
+
if (!FindLocMinIdx(path, len, i0)) return;
|
|
1084
|
+
iPrev = Prev(i0, len);
|
|
1085
|
+
while (path[iPrev] == path[i0]) iPrev = Prev(iPrev, len);
|
|
1086
|
+
iNext = Next(i0, len);
|
|
1087
|
+
|
|
1088
|
+
// it is possible for a locMin here to simply be a
|
|
1089
|
+
// collinear spike that should be ignored, so ...
|
|
1090
|
+
size_t i = i0;
|
|
1091
|
+
while (CrossProductSign(path[iPrev], path[i], path[iNext]) == 0)
|
|
1092
|
+
{
|
|
1093
|
+
FindLocMinIdx(path, len, i);
|
|
1094
|
+
if (i == i0) return; // this is an entirely collinear path
|
|
1095
|
+
iPrev = Prev(i, len);
|
|
1096
|
+
while (path[iPrev] == path[i]) iPrev = Prev(iPrev, len);
|
|
1097
|
+
iNext = Next(i, len);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
size_t vert_cnt = allVertices.size();
|
|
1101
|
+
|
|
1102
|
+
// we are now at the first legitimate locMin
|
|
1103
|
+
Vertex2* v0 = allVertices.emplace_back(new Vertex2(path[i]));
|
|
1104
|
+
|
|
1105
|
+
if (LeftTurning(path[iPrev], path[i], path[iNext]))
|
|
1106
|
+
v0->innerLM = true;
|
|
1107
|
+
Vertex2* vPrev = v0;
|
|
1108
|
+
i = iNext;
|
|
1109
|
+
|
|
1110
|
+
for (;;)
|
|
1111
|
+
{
|
|
1112
|
+
// vPrev is a locMin here
|
|
1113
|
+
locMinStack.push(vPrev);
|
|
1114
|
+
// ? update lowermostVertex ...
|
|
1115
|
+
if (!lowermostVertex ||
|
|
1116
|
+
vPrev->pt.y > lowermostVertex->pt.y ||
|
|
1117
|
+
(vPrev->pt.y == lowermostVertex->pt.y &&
|
|
1118
|
+
vPrev->pt.x < lowermostVertex->pt.x))
|
|
1119
|
+
lowermostVertex = vPrev;
|
|
1120
|
+
|
|
1121
|
+
iNext = Next(i, len);
|
|
1122
|
+
if (CrossProductSign(vPrev->pt, path[i], path[iNext]) == 0)
|
|
1123
|
+
{
|
|
1124
|
+
i = iNext;
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// ascend up next bound to LocMax
|
|
1129
|
+
while (path[i].y <= vPrev->pt.y)
|
|
1130
|
+
{
|
|
1131
|
+
Vertex2* v = allVertices.emplace_back(new Vertex2(path[i]));
|
|
1132
|
+
CreateEdge(vPrev, v, EdgeKind::ascend);
|
|
1133
|
+
vPrev = v;
|
|
1134
|
+
i = iNext;
|
|
1135
|
+
iNext = Next(i, len);
|
|
1136
|
+
|
|
1137
|
+
while (CrossProductSign(vPrev->pt, path[i], path[iNext]) == 0)
|
|
1138
|
+
{
|
|
1139
|
+
i = iNext;
|
|
1140
|
+
iNext = Next(i, len);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Now at a locMax, so descend to next locMin
|
|
1145
|
+
Vertex2* vPrevPrev = vPrev;
|
|
1146
|
+
while (i != i0 && path[i].y >= vPrev->pt.y)
|
|
1147
|
+
{
|
|
1148
|
+
Vertex2* v = allVertices.emplace_back(new Vertex2(path[i]));
|
|
1149
|
+
CreateEdge(v, vPrev, EdgeKind::descend);
|
|
1150
|
+
vPrevPrev = vPrev;
|
|
1151
|
+
vPrev = v;
|
|
1152
|
+
i = iNext;
|
|
1153
|
+
iNext = Next(i, len);
|
|
1154
|
+
|
|
1155
|
+
while (CrossProductSign(vPrev->pt, path[i], path[iNext]) == 0)
|
|
1156
|
+
{
|
|
1157
|
+
i = iNext;
|
|
1158
|
+
iNext = Next(i, len);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// now at the next locMin
|
|
1163
|
+
if (i == i0) break; // break for(;;) loop
|
|
1164
|
+
if (LeftTurning(vPrevPrev->pt, vPrev->pt, path[i]))
|
|
1165
|
+
vPrev->innerLM = true;
|
|
1166
|
+
}
|
|
1167
|
+
CreateEdge(v0, vPrev, EdgeKind::descend);
|
|
1168
|
+
|
|
1169
|
+
// finally, ignore this path if is not a polygon or too small
|
|
1170
|
+
len = allVertices.size() - vert_cnt;
|
|
1171
|
+
i = vert_cnt;
|
|
1172
|
+
if (len < 3 || (len == 3 && // or just a very tiny triangle
|
|
1173
|
+
((DistSqr(allVertices[i]->pt, allVertices[i + 1]->pt) <= 1) ||
|
|
1174
|
+
(DistSqr(allVertices[i + 1]->pt, allVertices[i + 2]->pt) <= 1) ||
|
|
1175
|
+
(DistSqr(allVertices[i + 2]->pt, allVertices[i]->pt) <= 1))))
|
|
1176
|
+
{
|
|
1177
|
+
for (size_t j = vert_cnt; j < allVertices.size(); ++j)
|
|
1178
|
+
allVertices[j]->edges.clear(); // flag to ignore
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
bool Delaunay::AddPaths(const Paths64& paths)
|
|
1183
|
+
{
|
|
1184
|
+
const auto total_vertex_count =
|
|
1185
|
+
std::accumulate(paths.begin(), paths.end(), size_t(0),
|
|
1186
|
+
[](const auto& a, const Path64& path)
|
|
1187
|
+
{return a + path.size(); });
|
|
1188
|
+
if (total_vertex_count == 0) return false;
|
|
1189
|
+
allVertices.reserve(allVertices.capacity() + total_vertex_count);
|
|
1190
|
+
allEdges.reserve(allEdges.capacity() + total_vertex_count);
|
|
1191
|
+
|
|
1192
|
+
for (const Path64& path : paths)
|
|
1193
|
+
AddPath(path);
|
|
1194
|
+
return (allVertices.size() > 2);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
TriangulateResult Triangulate(const Paths64& pp, Paths64& solution, bool useDelaunay)
|
|
1198
|
+
{
|
|
1199
|
+
TriangulateResult result;
|
|
1200
|
+
Delaunay d(useDelaunay);
|
|
1201
|
+
solution = d.Execute(pp, result);
|
|
1202
|
+
return result;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
TriangulateResult Triangulate(const PathsD& pp, int decPlaces, PathsD& solution, bool useDelaunay)
|
|
1206
|
+
{
|
|
1207
|
+
int ec = 0;
|
|
1208
|
+
double scale;
|
|
1209
|
+
TriangulateResult result;
|
|
1210
|
+
if (decPlaces <= 0) scale = 1;
|
|
1211
|
+
else if (decPlaces > 8) scale = std::pow(10, 8);
|
|
1212
|
+
else scale = std::pow(10, decPlaces);
|
|
1213
|
+
Paths64 pp64 = ScalePaths<int64_t, double>(pp, scale, ec);
|
|
1214
|
+
|
|
1215
|
+
Delaunay d(useDelaunay);
|
|
1216
|
+
Paths64 sol64 = d.Execute(pp64, result);
|
|
1217
|
+
solution = ScalePaths<double, int64_t>(sol64, 1 / scale, ec);
|
|
1218
|
+
return result;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
} // Clipper2Lib namespace
|