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,3152 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
* Author : Angus Johnson *
|
|
3
|
+
* Date : 21 February 2026 *
|
|
4
|
+
* Website : https://www.angusj.com *
|
|
5
|
+
* Copyright : Angus Johnson 2010-2026 *
|
|
6
|
+
* Purpose : This is the main polygon clipping module *
|
|
7
|
+
* License : https://www.boost.org/LICENSE_1_0.txt *
|
|
8
|
+
*******************************************************************************/
|
|
9
|
+
|
|
10
|
+
#include "clipper2/clipper.engine.h"
|
|
11
|
+
#include "clipper2/clipper.h"
|
|
12
|
+
#include <stdexcept>
|
|
13
|
+
|
|
14
|
+
// https://github.com/AngusJohnson/Clipper2/discussions/334
|
|
15
|
+
// #discussioncomment-4248602
|
|
16
|
+
#if defined(_MSC_VER) && ( defined(_M_AMD64) || defined(_M_X64) )
|
|
17
|
+
#include <xmmintrin.h>
|
|
18
|
+
#include <emmintrin.h>
|
|
19
|
+
#define fmin(a,b) _mm_cvtsd_f64(_mm_min_sd(_mm_set_sd(a),_mm_set_sd(b)))
|
|
20
|
+
#define fmax(a,b) _mm_cvtsd_f64(_mm_max_sd(_mm_set_sd(a),_mm_set_sd(b)))
|
|
21
|
+
#define nearbyint(a) _mm_cvtsd_si64(_mm_set_sd(a)) /* Note: expression type is (int64_t) */
|
|
22
|
+
#endif
|
|
23
|
+
|
|
24
|
+
namespace Clipper2Lib {
|
|
25
|
+
|
|
26
|
+
static const Rect64 invalid_rect = Rect64(false);
|
|
27
|
+
|
|
28
|
+
// Every closed path (ie polygon) is made up of a series of vertices forming edge
|
|
29
|
+
// 'bounds' that alternate between ascending bounds (containing edges going up
|
|
30
|
+
// relative to the Y-axis) and descending bounds. 'Local Minima' refers to
|
|
31
|
+
// vertices where ascending and descending bounds join at the bottom, and
|
|
32
|
+
// 'Local Maxima' are where ascending and descending bounds join at the top.
|
|
33
|
+
|
|
34
|
+
struct Scanline {
|
|
35
|
+
int64_t y = 0;
|
|
36
|
+
Scanline* next = nullptr;
|
|
37
|
+
|
|
38
|
+
explicit Scanline(int64_t y_) : y(y_) {}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
struct HorzSegSorter {
|
|
42
|
+
inline bool operator()(const HorzSegment& hs1, const HorzSegment& hs2)
|
|
43
|
+
{
|
|
44
|
+
if (!hs1.right_op || !hs2.right_op) return (hs1.right_op);
|
|
45
|
+
return hs2.left_op->pt.x > hs1.left_op->pt.x;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
struct LocMinSorter {
|
|
50
|
+
inline bool operator()(const LocalMinima_ptr& locMin1,
|
|
51
|
+
const LocalMinima_ptr& locMin2)
|
|
52
|
+
{
|
|
53
|
+
if (locMin2->vertex->pt.y != locMin1->vertex->pt.y)
|
|
54
|
+
return locMin2->vertex->pt.y < locMin1->vertex->pt.y;
|
|
55
|
+
else
|
|
56
|
+
return locMin2->vertex->pt.x > locMin1->vertex->pt.x;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
inline bool IsOdd(int val)
|
|
62
|
+
{
|
|
63
|
+
return (val & 1) ? true : false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
inline bool IsHotEdge(const Active& e)
|
|
68
|
+
{
|
|
69
|
+
return (e.outrec);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
inline bool IsOpen(const Active& e)
|
|
74
|
+
{
|
|
75
|
+
return (e.local_min->is_open);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
inline bool IsOpenEnd(const Vertex& v)
|
|
80
|
+
{
|
|
81
|
+
return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) !=
|
|
82
|
+
VertexFlags::Empty;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
inline bool IsOpenEnd(const Active& ae)
|
|
87
|
+
{
|
|
88
|
+
return IsOpenEnd(*ae.vertex_top);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
inline Active* GetPrevHotEdge(const Active& e)
|
|
93
|
+
{
|
|
94
|
+
Active* prev = e.prev_in_ael;
|
|
95
|
+
while (prev && (IsOpen(*prev) || !IsHotEdge(*prev)))
|
|
96
|
+
prev = prev->prev_in_ael;
|
|
97
|
+
return prev;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
inline bool IsFront(const Active& e)
|
|
101
|
+
{
|
|
102
|
+
return (&e == e.outrec->front_edge);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
inline bool IsInvalidPath(OutPt* op)
|
|
106
|
+
{
|
|
107
|
+
return (!op || op->next == op);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/*******************************************************************************
|
|
111
|
+
* Dx: 0(90deg) *
|
|
112
|
+
* | *
|
|
113
|
+
* +inf (180deg) <--- o ---> -inf (0deg) *
|
|
114
|
+
*******************************************************************************/
|
|
115
|
+
|
|
116
|
+
inline double GetDx(const Point64& pt1, const Point64& pt2)
|
|
117
|
+
{
|
|
118
|
+
double dy = double(pt2.y - pt1.y);
|
|
119
|
+
if (dy != 0)
|
|
120
|
+
return double(pt2.x - pt1.x) / dy;
|
|
121
|
+
else if (pt2.x > pt1.x)
|
|
122
|
+
return -std::numeric_limits<double>::max();
|
|
123
|
+
else
|
|
124
|
+
return std::numeric_limits<double>::max();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
inline int64_t TopX(const Active& ae, const int64_t currentY)
|
|
128
|
+
{
|
|
129
|
+
if ((currentY == ae.top.y) || (ae.top.x == ae.bot.x)) return ae.top.x;
|
|
130
|
+
else if (currentY == ae.bot.y) return ae.bot.x;
|
|
131
|
+
else return ae.bot.x + static_cast<int64_t>(nearbyint(ae.dx * (currentY - ae.bot.y)));
|
|
132
|
+
// nb: std::nearbyint (or std::round) substantially *improves* performance here
|
|
133
|
+
// as it greatly improves the likelihood of edge adjacency in ProcessIntersectList().
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
inline bool IsHorizontal(const Active& e)
|
|
138
|
+
{
|
|
139
|
+
return (e.top.y == e.bot.y);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
inline bool IsHeadingRightHorz(const Active& e)
|
|
144
|
+
{
|
|
145
|
+
return e.dx == -std::numeric_limits<double>::max();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
inline bool IsHeadingLeftHorz(const Active& e)
|
|
150
|
+
{
|
|
151
|
+
return e.dx == std::numeric_limits<double>::max();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
inline void SwapActives(Active*& e1, Active*& e2)
|
|
156
|
+
{
|
|
157
|
+
Active* e = e1;
|
|
158
|
+
e1 = e2;
|
|
159
|
+
e2 = e;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
inline PathType GetPolyType(const Active& e)
|
|
163
|
+
{
|
|
164
|
+
return e.local_min->polytype;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
inline bool IsSamePolyType(const Active& e1, const Active& e2)
|
|
168
|
+
{
|
|
169
|
+
return e1.local_min->polytype == e2.local_min->polytype;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
inline void SetDx(Active& e)
|
|
173
|
+
{
|
|
174
|
+
e.dx = GetDx(e.bot, e.top);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
inline Vertex* NextVertex(const Active& e)
|
|
178
|
+
{
|
|
179
|
+
if (e.wind_dx > 0)
|
|
180
|
+
return e.vertex_top->next;
|
|
181
|
+
else
|
|
182
|
+
return e.vertex_top->prev;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
//PrevPrevVertex: useful to get the (inverted Y-axis) top of the
|
|
186
|
+
//alternate edge (ie left or right bound) during edge insertion.
|
|
187
|
+
inline Vertex* PrevPrevVertex(const Active& ae)
|
|
188
|
+
{
|
|
189
|
+
if (ae.wind_dx > 0)
|
|
190
|
+
return ae.vertex_top->prev->prev;
|
|
191
|
+
else
|
|
192
|
+
return ae.vertex_top->next->next;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
inline Active* ExtractFromSEL(Active* ae)
|
|
197
|
+
{
|
|
198
|
+
Active* res = ae->next_in_sel;
|
|
199
|
+
if (res)
|
|
200
|
+
res->prev_in_sel = ae->prev_in_sel;
|
|
201
|
+
ae->prev_in_sel->next_in_sel = res;
|
|
202
|
+
return res;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
inline void Insert1Before2InSEL(Active* ae1, Active* ae2)
|
|
207
|
+
{
|
|
208
|
+
ae1->prev_in_sel = ae2->prev_in_sel;
|
|
209
|
+
if (ae1->prev_in_sel)
|
|
210
|
+
ae1->prev_in_sel->next_in_sel = ae1;
|
|
211
|
+
ae1->next_in_sel = ae2;
|
|
212
|
+
ae2->prev_in_sel = ae1;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
inline bool IsMaxima(const Vertex& v)
|
|
216
|
+
{
|
|
217
|
+
return ((v.flags & VertexFlags::LocalMax) != VertexFlags::Empty);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
inline bool IsMaxima(const Active& e)
|
|
222
|
+
{
|
|
223
|
+
return IsMaxima(*e.vertex_top);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
inline Vertex* GetCurrYMaximaVertex_Open(const Active& e)
|
|
227
|
+
{
|
|
228
|
+
Vertex* result = e.vertex_top;
|
|
229
|
+
if (e.wind_dx > 0)
|
|
230
|
+
while ((result->next->pt.y == result->pt.y) &&
|
|
231
|
+
((result->flags & (VertexFlags::OpenEnd |
|
|
232
|
+
VertexFlags::LocalMax)) == VertexFlags::Empty))
|
|
233
|
+
result = result->next;
|
|
234
|
+
else
|
|
235
|
+
while (result->prev->pt.y == result->pt.y &&
|
|
236
|
+
((result->flags & (VertexFlags::OpenEnd |
|
|
237
|
+
VertexFlags::LocalMax)) == VertexFlags::Empty))
|
|
238
|
+
result = result->prev;
|
|
239
|
+
if (!IsMaxima(*result)) result = nullptr; // not a maxima
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
inline Vertex* GetCurrYMaximaVertex(const Active& e)
|
|
244
|
+
{
|
|
245
|
+
Vertex* result = e.vertex_top;
|
|
246
|
+
if (e.wind_dx > 0)
|
|
247
|
+
while (result->next->pt.y == result->pt.y) result = result->next;
|
|
248
|
+
else
|
|
249
|
+
while (result->prev->pt.y == result->pt.y) result = result->prev;
|
|
250
|
+
if (!IsMaxima(*result)) result = nullptr; // not a maxima
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
Active* GetMaximaPair(const Active& e)
|
|
255
|
+
{
|
|
256
|
+
Active* e2;
|
|
257
|
+
e2 = e.next_in_ael;
|
|
258
|
+
while (e2)
|
|
259
|
+
{
|
|
260
|
+
if (e2->vertex_top == e.vertex_top) return e2; // Found!
|
|
261
|
+
e2 = e2->next_in_ael;
|
|
262
|
+
}
|
|
263
|
+
return nullptr;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
inline int PointCount(OutPt* op)
|
|
267
|
+
{
|
|
268
|
+
OutPt* op2 = op;
|
|
269
|
+
int cnt = 0;
|
|
270
|
+
do
|
|
271
|
+
{
|
|
272
|
+
op2 = op2->next;
|
|
273
|
+
++cnt;
|
|
274
|
+
} while (op2 != op);
|
|
275
|
+
return cnt;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
inline OutPt* DuplicateOp(OutPt* op, bool insert_after)
|
|
279
|
+
{
|
|
280
|
+
OutPt* result = new OutPt(op->pt, op->outrec);
|
|
281
|
+
if (insert_after)
|
|
282
|
+
{
|
|
283
|
+
result->next = op->next;
|
|
284
|
+
result->next->prev = result;
|
|
285
|
+
result->prev = op;
|
|
286
|
+
op->next = result;
|
|
287
|
+
}
|
|
288
|
+
else
|
|
289
|
+
{
|
|
290
|
+
result->prev = op->prev;
|
|
291
|
+
result->prev->next = result;
|
|
292
|
+
result->next = op;
|
|
293
|
+
op->prev = result;
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
inline OutPt* DisposeOutPt(OutPt* op)
|
|
299
|
+
{
|
|
300
|
+
OutPt* result = op->next;
|
|
301
|
+
op->prev->next = op->next;
|
|
302
|
+
op->next->prev = op->prev;
|
|
303
|
+
delete op;
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
inline void DisposeOutPts(OutRec* outrec)
|
|
309
|
+
{
|
|
310
|
+
OutPt* op = outrec->pts;
|
|
311
|
+
op->prev->next = nullptr;
|
|
312
|
+
while (op)
|
|
313
|
+
{
|
|
314
|
+
OutPt* tmp = op;
|
|
315
|
+
op = op->next;
|
|
316
|
+
delete tmp;
|
|
317
|
+
};
|
|
318
|
+
outrec->pts = nullptr;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
bool IntersectListSort(const IntersectNode& a, const IntersectNode& b)
|
|
323
|
+
{
|
|
324
|
+
//note different inequality tests ...
|
|
325
|
+
return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge)
|
|
330
|
+
{
|
|
331
|
+
outrec.front_edge = &start_edge;
|
|
332
|
+
outrec.back_edge = &end_edge;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
void SwapOutrecs(Active& e1, Active& e2)
|
|
337
|
+
{
|
|
338
|
+
OutRec* or1 = e1.outrec;
|
|
339
|
+
OutRec* or2 = e2.outrec;
|
|
340
|
+
if (or1 == or2)
|
|
341
|
+
{
|
|
342
|
+
Active* e = or1->front_edge;
|
|
343
|
+
or1->front_edge = or1->back_edge;
|
|
344
|
+
or1->back_edge = e;
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (or1)
|
|
348
|
+
{
|
|
349
|
+
if (&e1 == or1->front_edge)
|
|
350
|
+
or1->front_edge = &e2;
|
|
351
|
+
else
|
|
352
|
+
or1->back_edge = &e2;
|
|
353
|
+
}
|
|
354
|
+
if (or2)
|
|
355
|
+
{
|
|
356
|
+
if (&e2 == or2->front_edge)
|
|
357
|
+
or2->front_edge = &e1;
|
|
358
|
+
else
|
|
359
|
+
or2->back_edge = &e1;
|
|
360
|
+
}
|
|
361
|
+
e1.outrec = or2;
|
|
362
|
+
e2.outrec = or1;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
double Area(OutPt* op)
|
|
367
|
+
{
|
|
368
|
+
//https://en.wikipedia.org/wiki/Shoelace_formula
|
|
369
|
+
double result = 0.0;
|
|
370
|
+
OutPt* op2 = op;
|
|
371
|
+
do
|
|
372
|
+
{
|
|
373
|
+
result += static_cast<double>(op2->prev->pt.y + op2->pt.y) *
|
|
374
|
+
static_cast<double>(op2->prev->pt.x - op2->pt.x);
|
|
375
|
+
op2 = op2->next;
|
|
376
|
+
} while (op2 != op);
|
|
377
|
+
return result * 0.5;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
inline double AreaTriangle(const Point64& pt1,
|
|
381
|
+
const Point64& pt2, const Point64& pt3)
|
|
382
|
+
{
|
|
383
|
+
return (static_cast<double>(pt3.y + pt1.y) * static_cast<double>(pt3.x - pt1.x) +
|
|
384
|
+
static_cast<double>(pt1.y + pt2.y) * static_cast<double>(pt1.x - pt2.x) +
|
|
385
|
+
static_cast<double>(pt2.y + pt3.y) * static_cast<double>(pt2.x - pt3.x));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
void ReverseOutPts(OutPt* op)
|
|
389
|
+
{
|
|
390
|
+
if (!op) return;
|
|
391
|
+
|
|
392
|
+
OutPt* op1 = op;
|
|
393
|
+
OutPt* op2;
|
|
394
|
+
|
|
395
|
+
do
|
|
396
|
+
{
|
|
397
|
+
op2 = op1->next;
|
|
398
|
+
op1->next = op1->prev;
|
|
399
|
+
op1->prev = op2;
|
|
400
|
+
op1 = op2;
|
|
401
|
+
} while (op1 != op);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
inline void SwapSides(OutRec& outrec)
|
|
405
|
+
{
|
|
406
|
+
Active* e2 = outrec.front_edge;
|
|
407
|
+
outrec.front_edge = outrec.back_edge;
|
|
408
|
+
outrec.back_edge = e2;
|
|
409
|
+
outrec.pts = outrec.pts->next;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
inline OutRec* GetRealOutRec(OutRec* outrec)
|
|
413
|
+
{
|
|
414
|
+
while (outrec && !outrec->pts) outrec = outrec->owner;
|
|
415
|
+
return outrec;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
inline bool IsValidOwner(OutRec* outrec, OutRec* testOwner)
|
|
419
|
+
{
|
|
420
|
+
// prevent outrec owning itself either directly or indirectly
|
|
421
|
+
while (testOwner && testOwner != outrec) testOwner = testOwner->owner;
|
|
422
|
+
return !testOwner;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
inline void UncoupleOutRec(Active ae)
|
|
426
|
+
{
|
|
427
|
+
OutRec* outrec = ae.outrec;
|
|
428
|
+
if (!outrec) return;
|
|
429
|
+
outrec->front_edge->outrec = nullptr;
|
|
430
|
+
outrec->back_edge->outrec = nullptr;
|
|
431
|
+
outrec->front_edge = nullptr;
|
|
432
|
+
outrec->back_edge = nullptr;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
inline bool PtsReallyClose(const Point64& pt1, const Point64& pt2)
|
|
437
|
+
{
|
|
438
|
+
return (std::llabs(pt1.x - pt2.x) < 2) && (std::llabs(pt1.y - pt2.y) < 2);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
inline bool IsVerySmallTriangle(const OutPt& op)
|
|
442
|
+
{
|
|
443
|
+
return op.next->next == op.prev &&
|
|
444
|
+
(PtsReallyClose(op.prev->pt, op.next->pt) ||
|
|
445
|
+
PtsReallyClose(op.pt, op.next->pt) ||
|
|
446
|
+
PtsReallyClose(op.pt, op.prev->pt));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
inline bool IsValidClosedPath(const OutPt* op)
|
|
450
|
+
{
|
|
451
|
+
return op && (op->next != op) && (op->next != op->prev) &&
|
|
452
|
+
!IsVerySmallTriangle(*op);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
inline bool OutrecIsAscending(const Active* hotEdge)
|
|
456
|
+
{
|
|
457
|
+
return (hotEdge == hotEdge->outrec->front_edge);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
inline void SwapFrontBackSides(OutRec& outrec)
|
|
461
|
+
{
|
|
462
|
+
Active* tmp = outrec.front_edge;
|
|
463
|
+
outrec.front_edge = outrec.back_edge;
|
|
464
|
+
outrec.back_edge = tmp;
|
|
465
|
+
outrec.pts = outrec.pts->next;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
inline bool EdgesAdjacentInAEL(const IntersectNode& inode)
|
|
469
|
+
{
|
|
470
|
+
return (inode.edge1->next_in_ael == inode.edge2) || (inode.edge1->prev_in_ael == inode.edge2);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
inline bool IsJoined(const Active& e)
|
|
474
|
+
{
|
|
475
|
+
return e.join_with != JoinWith::NoJoin;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
inline void SetOwner(OutRec* outrec, OutRec* new_owner)
|
|
479
|
+
{
|
|
480
|
+
//precondition1: new_owner is never null
|
|
481
|
+
new_owner->owner = GetRealOutRec(new_owner->owner);
|
|
482
|
+
OutRec* tmp = new_owner;
|
|
483
|
+
while (tmp && tmp != outrec) tmp = tmp->owner;
|
|
484
|
+
if (tmp) new_owner->owner = outrec->owner;
|
|
485
|
+
outrec->owner = new_owner;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op)
|
|
489
|
+
{
|
|
490
|
+
if (op == op->next || op->prev == op->next)
|
|
491
|
+
return PointInPolygonResult::IsOutside;
|
|
492
|
+
|
|
493
|
+
OutPt* op2 = op;
|
|
494
|
+
do
|
|
495
|
+
{
|
|
496
|
+
if (op->pt.y != pt.y) break;
|
|
497
|
+
op = op->next;
|
|
498
|
+
} while (op != op2);
|
|
499
|
+
if (op->pt.y == pt.y) // not a proper polygon
|
|
500
|
+
return PointInPolygonResult::IsOutside;
|
|
501
|
+
|
|
502
|
+
bool is_above = op->pt.y < pt.y, starting_above = is_above;
|
|
503
|
+
int val = 0;
|
|
504
|
+
op2 = op->next;
|
|
505
|
+
while (op2 != op)
|
|
506
|
+
{
|
|
507
|
+
if (is_above)
|
|
508
|
+
while (op2 != op && op2->pt.y < pt.y) op2 = op2->next;
|
|
509
|
+
else
|
|
510
|
+
while (op2 != op && op2->pt.y > pt.y) op2 = op2->next;
|
|
511
|
+
if (op2 == op) break;
|
|
512
|
+
|
|
513
|
+
// must have touched or crossed the pt.Y horizontal
|
|
514
|
+
// and this must happen an even number of times
|
|
515
|
+
|
|
516
|
+
if (op2->pt.y == pt.y) // touching the horizontal
|
|
517
|
+
{
|
|
518
|
+
if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y &&
|
|
519
|
+
(pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x)))
|
|
520
|
+
return PointInPolygonResult::IsOn;
|
|
521
|
+
|
|
522
|
+
op2 = op2->next;
|
|
523
|
+
if (op2 == op) break;
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x);
|
|
528
|
+
// do nothing because
|
|
529
|
+
// we're only interested in edges crossing on the left
|
|
530
|
+
else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x))
|
|
531
|
+
val = 1 - val; // toggle val
|
|
532
|
+
else
|
|
533
|
+
{
|
|
534
|
+
int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
|
|
535
|
+
if (i == 0) return PointInPolygonResult::IsOn;
|
|
536
|
+
if ((i < 0) == is_above) val = 1 - val;
|
|
537
|
+
}
|
|
538
|
+
is_above = !is_above;
|
|
539
|
+
op2 = op2->next;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (is_above != starting_above)
|
|
543
|
+
{
|
|
544
|
+
int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
|
|
545
|
+
if (i == 0) return PointInPolygonResult::IsOn;
|
|
546
|
+
if ((i < 0) == is_above) val = 1 - val;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (val == 0) return PointInPolygonResult::IsOutside;
|
|
550
|
+
else return PointInPolygonResult::IsInside;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
inline Path64 GetCleanPath(OutPt* op)
|
|
554
|
+
{
|
|
555
|
+
Path64 result;
|
|
556
|
+
OutPt* op2 = op;
|
|
557
|
+
while (op2->next != op &&
|
|
558
|
+
((op2->pt.x == op2->next->pt.x && op2->pt.x == op2->prev->pt.x) ||
|
|
559
|
+
(op2->pt.y == op2->next->pt.y && op2->pt.y == op2->prev->pt.y))) op2 = op2->next;
|
|
560
|
+
result.emplace_back(op2->pt);
|
|
561
|
+
OutPt* prevOp = op2;
|
|
562
|
+
op2 = op2->next;
|
|
563
|
+
while (op2 != op)
|
|
564
|
+
{
|
|
565
|
+
if ((op2->pt.x != op2->next->pt.x || op2->pt.x != prevOp->pt.x) &&
|
|
566
|
+
(op2->pt.y != op2->next->pt.y || op2->pt.y != prevOp->pt.y))
|
|
567
|
+
{
|
|
568
|
+
result.emplace_back(op2->pt);
|
|
569
|
+
prevOp = op2;
|
|
570
|
+
}
|
|
571
|
+
op2 = op2->next;
|
|
572
|
+
}
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
inline bool Path2ContainsPath1(OutPt* op1, OutPt* op2)
|
|
577
|
+
{
|
|
578
|
+
// this function accommodates rounding errors that
|
|
579
|
+
// can cause path micro intersections
|
|
580
|
+
PointInPolygonResult pip = PointInPolygonResult::IsOn;
|
|
581
|
+
OutPt* op = op1;
|
|
582
|
+
do {
|
|
583
|
+
switch (PointInOpPolygon(op->pt, op2))
|
|
584
|
+
{
|
|
585
|
+
case PointInPolygonResult::IsOutside:
|
|
586
|
+
if (pip == PointInPolygonResult::IsOutside) return false;
|
|
587
|
+
pip = PointInPolygonResult::IsOutside;
|
|
588
|
+
break;
|
|
589
|
+
case PointInPolygonResult::IsInside:
|
|
590
|
+
if (pip == PointInPolygonResult::IsInside) return true;
|
|
591
|
+
pip = PointInPolygonResult::IsInside;
|
|
592
|
+
break;
|
|
593
|
+
default: break;
|
|
594
|
+
}
|
|
595
|
+
op = op->next;
|
|
596
|
+
} while (op != op1);
|
|
597
|
+
// result unclear, so try again using cleaned paths
|
|
598
|
+
return Path2ContainsPath1(GetCleanPath(op1), GetCleanPath(op2)); // (#973)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
void AddLocMin(LocalMinimaList& list,
|
|
602
|
+
Vertex& vert, PathType polytype, bool is_open)
|
|
603
|
+
{
|
|
604
|
+
//make sure the vertex is added only once ...
|
|
605
|
+
if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return;
|
|
606
|
+
|
|
607
|
+
vert.flags = (vert.flags | VertexFlags::LocalMin);
|
|
608
|
+
list.emplace_back(std::make_unique <LocalMinima>(&vert, polytype, is_open));
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
void AddPaths_(const Paths64& paths, PathType polytype, bool is_open,
|
|
612
|
+
std::vector<Vertex*>& vertexLists, LocalMinimaList& locMinList)
|
|
613
|
+
{
|
|
614
|
+
const auto total_vertex_count =
|
|
615
|
+
std::accumulate(paths.begin(), paths.end(), size_t(0),
|
|
616
|
+
[](const auto& a, const Path64& path)
|
|
617
|
+
{return a + path.size(); });
|
|
618
|
+
if (total_vertex_count == 0) return;
|
|
619
|
+
|
|
620
|
+
Vertex* allVertices = new Vertex[total_vertex_count], * v = allVertices;
|
|
621
|
+
for (const Path64& path : paths)
|
|
622
|
+
{
|
|
623
|
+
//for each path create a circular double linked list of vertices
|
|
624
|
+
Vertex* v0 = v, * curr_v = v, * prev_v = nullptr;
|
|
625
|
+
|
|
626
|
+
if (path.empty())
|
|
627
|
+
continue;
|
|
628
|
+
|
|
629
|
+
v->prev = nullptr;
|
|
630
|
+
int cnt = 0;
|
|
631
|
+
for (const Point64& pt : path)
|
|
632
|
+
{
|
|
633
|
+
if (prev_v)
|
|
634
|
+
{
|
|
635
|
+
if (prev_v->pt == pt) continue; // ie skips duplicates
|
|
636
|
+
prev_v->next = curr_v;
|
|
637
|
+
}
|
|
638
|
+
curr_v->prev = prev_v;
|
|
639
|
+
curr_v->pt = pt;
|
|
640
|
+
curr_v->flags = VertexFlags::Empty;
|
|
641
|
+
prev_v = curr_v++;
|
|
642
|
+
cnt++;
|
|
643
|
+
}
|
|
644
|
+
if (!prev_v || !prev_v->prev) continue;
|
|
645
|
+
if (!is_open && prev_v->pt == v0->pt)
|
|
646
|
+
prev_v = prev_v->prev;
|
|
647
|
+
prev_v->next = v0;
|
|
648
|
+
v0->prev = prev_v;
|
|
649
|
+
v = curr_v; // ie get ready for next path
|
|
650
|
+
if (cnt < 2 || (cnt == 2 && !is_open)) continue;
|
|
651
|
+
|
|
652
|
+
//now find and assign local minima
|
|
653
|
+
bool going_up, going_up0;
|
|
654
|
+
if (is_open)
|
|
655
|
+
{
|
|
656
|
+
curr_v = v0->next;
|
|
657
|
+
while (curr_v != v0 && curr_v->pt.y == v0->pt.y)
|
|
658
|
+
curr_v = curr_v->next;
|
|
659
|
+
going_up = curr_v->pt.y <= v0->pt.y;
|
|
660
|
+
if (going_up)
|
|
661
|
+
{
|
|
662
|
+
v0->flags = VertexFlags::OpenStart;
|
|
663
|
+
AddLocMin(locMinList , *v0, polytype, true);
|
|
664
|
+
}
|
|
665
|
+
else
|
|
666
|
+
v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax;
|
|
667
|
+
}
|
|
668
|
+
else // closed path
|
|
669
|
+
{
|
|
670
|
+
prev_v = v0->prev;
|
|
671
|
+
while (prev_v != v0 && prev_v->pt.y == v0->pt.y)
|
|
672
|
+
prev_v = prev_v->prev;
|
|
673
|
+
if (prev_v == v0)
|
|
674
|
+
continue; // only open paths can be completely flat
|
|
675
|
+
going_up = prev_v->pt.y > v0->pt.y;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
going_up0 = going_up;
|
|
679
|
+
prev_v = v0;
|
|
680
|
+
curr_v = v0->next;
|
|
681
|
+
while (curr_v != v0)
|
|
682
|
+
{
|
|
683
|
+
if (curr_v->pt.y > prev_v->pt.y && going_up)
|
|
684
|
+
{
|
|
685
|
+
prev_v->flags = (prev_v->flags | VertexFlags::LocalMax);
|
|
686
|
+
going_up = false;
|
|
687
|
+
}
|
|
688
|
+
else if (curr_v->pt.y < prev_v->pt.y && !going_up)
|
|
689
|
+
{
|
|
690
|
+
going_up = true;
|
|
691
|
+
AddLocMin(locMinList, *prev_v, polytype, is_open);
|
|
692
|
+
}
|
|
693
|
+
prev_v = curr_v;
|
|
694
|
+
curr_v = curr_v->next;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (is_open)
|
|
698
|
+
{
|
|
699
|
+
prev_v->flags = prev_v->flags | VertexFlags::OpenEnd;
|
|
700
|
+
if (going_up)
|
|
701
|
+
prev_v->flags = prev_v->flags | VertexFlags::LocalMax;
|
|
702
|
+
else
|
|
703
|
+
AddLocMin(locMinList, *prev_v, polytype, is_open);
|
|
704
|
+
}
|
|
705
|
+
else if (going_up != going_up0)
|
|
706
|
+
{
|
|
707
|
+
if (going_up0) AddLocMin(locMinList, *prev_v, polytype, false);
|
|
708
|
+
else prev_v->flags = prev_v->flags | VertexFlags::LocalMax;
|
|
709
|
+
}
|
|
710
|
+
} // end processing current path
|
|
711
|
+
|
|
712
|
+
vertexLists.emplace_back(allVertices);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
//------------------------------------------------------------------------------
|
|
716
|
+
// ReuseableDataContainer64 methods ...
|
|
717
|
+
//------------------------------------------------------------------------------
|
|
718
|
+
|
|
719
|
+
void ReuseableDataContainer64::AddLocMin(Vertex& vert, PathType polytype, bool is_open)
|
|
720
|
+
{
|
|
721
|
+
//make sure the vertex is added only once ...
|
|
722
|
+
if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return;
|
|
723
|
+
|
|
724
|
+
vert.flags = (vert.flags | VertexFlags::LocalMin);
|
|
725
|
+
minima_list_.emplace_back(std::make_unique <LocalMinima>(&vert, polytype, is_open));
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
void ReuseableDataContainer64::AddPaths(const Paths64& paths,
|
|
729
|
+
PathType polytype, bool is_open)
|
|
730
|
+
{
|
|
731
|
+
AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
ReuseableDataContainer64::~ReuseableDataContainer64()
|
|
735
|
+
{
|
|
736
|
+
Clear();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
void ReuseableDataContainer64::Clear()
|
|
740
|
+
{
|
|
741
|
+
minima_list_.clear();
|
|
742
|
+
for (auto v : vertex_lists_) delete[] v;
|
|
743
|
+
vertex_lists_.clear();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
//------------------------------------------------------------------------------
|
|
747
|
+
// ClipperBase methods ...
|
|
748
|
+
//------------------------------------------------------------------------------
|
|
749
|
+
|
|
750
|
+
ClipperBase::~ClipperBase()
|
|
751
|
+
{
|
|
752
|
+
Clear();
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
void ClipperBase::DeleteEdges(Active*& e)
|
|
756
|
+
{
|
|
757
|
+
while (e)
|
|
758
|
+
{
|
|
759
|
+
Active* e2 = e;
|
|
760
|
+
e = e->next_in_ael;
|
|
761
|
+
delete e2;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
void ClipperBase::CleanUp()
|
|
766
|
+
{
|
|
767
|
+
DeleteEdges(actives_);
|
|
768
|
+
scanline_list_ = std::priority_queue<int64_t>();
|
|
769
|
+
intersect_nodes_.clear();
|
|
770
|
+
DisposeAllOutRecs();
|
|
771
|
+
horz_seg_list_.clear();
|
|
772
|
+
horz_join_list_.clear();
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
void ClipperBase::Clear()
|
|
777
|
+
{
|
|
778
|
+
CleanUp();
|
|
779
|
+
DisposeVerticesAndLocalMinima();
|
|
780
|
+
current_locmin_iter_ = minima_list_.begin();
|
|
781
|
+
minima_list_sorted_ = false;
|
|
782
|
+
has_open_paths_ = false;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
void ClipperBase::Reset()
|
|
787
|
+
{
|
|
788
|
+
if (!minima_list_sorted_)
|
|
789
|
+
{
|
|
790
|
+
std::stable_sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); //#594
|
|
791
|
+
minima_list_sorted_ = true;
|
|
792
|
+
}
|
|
793
|
+
LocalMinimaList::const_reverse_iterator i;
|
|
794
|
+
for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i)
|
|
795
|
+
InsertScanline((*i)->vertex->pt.y);
|
|
796
|
+
|
|
797
|
+
current_locmin_iter_ = minima_list_.begin();
|
|
798
|
+
actives_ = nullptr;
|
|
799
|
+
sel_ = nullptr;
|
|
800
|
+
succeeded_ = true;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
#ifdef USINGZ
|
|
805
|
+
void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip)
|
|
806
|
+
{
|
|
807
|
+
if (!zCallback_) return;
|
|
808
|
+
// prioritize subject over clip vertices by passing
|
|
809
|
+
// subject vertices before clip vertices in the callback
|
|
810
|
+
if (GetPolyType(e1) == PathType::Subject)
|
|
811
|
+
{
|
|
812
|
+
if (ip == e1.bot) ip.z = e1.bot.z;
|
|
813
|
+
else if (ip == e1.top) ip.z = e1.top.z;
|
|
814
|
+
else if (ip == e2.bot) ip.z = e2.bot.z;
|
|
815
|
+
else if (ip == e2.top) ip.z = e2.top.z;
|
|
816
|
+
else ip.z = DefaultZ;
|
|
817
|
+
zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip);
|
|
818
|
+
}
|
|
819
|
+
else
|
|
820
|
+
{
|
|
821
|
+
if (ip == e2.bot) ip.z = e2.bot.z;
|
|
822
|
+
else if (ip == e2.top) ip.z = e2.top.z;
|
|
823
|
+
else if (ip == e1.bot) ip.z = e1.bot.z;
|
|
824
|
+
else if (ip == e1.top) ip.z = e1.top.z;
|
|
825
|
+
else ip.z = DefaultZ;
|
|
826
|
+
zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
#endif
|
|
830
|
+
|
|
831
|
+
void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open)
|
|
832
|
+
{
|
|
833
|
+
AddPaths(Paths64(1, path), polytype, is_open);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open)
|
|
837
|
+
{
|
|
838
|
+
if (is_open) has_open_paths_ = true;
|
|
839
|
+
minima_list_sorted_ = false;
|
|
840
|
+
AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
void ClipperBase::AddReuseableData(const ReuseableDataContainer64& reuseable_data)
|
|
844
|
+
{
|
|
845
|
+
// nb: reuseable_data will continue to own the vertices
|
|
846
|
+
// and remains responsible for their clean up.
|
|
847
|
+
succeeded_ = false;
|
|
848
|
+
minima_list_sorted_ = false;
|
|
849
|
+
LocalMinimaList::const_iterator i;
|
|
850
|
+
for (i = reuseable_data.minima_list_.cbegin(); i != reuseable_data.minima_list_.cend(); ++i)
|
|
851
|
+
{
|
|
852
|
+
minima_list_.emplace_back(std::make_unique <LocalMinima>((*i)->vertex, (*i)->polytype, (*i)->is_open));
|
|
853
|
+
if ((*i)->is_open) has_open_paths_ = true;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
void ClipperBase::InsertScanline(int64_t y)
|
|
858
|
+
{
|
|
859
|
+
scanline_list_.push(y);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
bool ClipperBase::PopScanline(int64_t& y)
|
|
864
|
+
{
|
|
865
|
+
if (scanline_list_.empty()) return false;
|
|
866
|
+
y = scanline_list_.top();
|
|
867
|
+
scanline_list_.pop();
|
|
868
|
+
while (!scanline_list_.empty() && y == scanline_list_.top())
|
|
869
|
+
scanline_list_.pop(); // Pop duplicates.
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima)
|
|
875
|
+
{
|
|
876
|
+
if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false;
|
|
877
|
+
local_minima = (current_locmin_iter_++)->get();
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
void ClipperBase::DisposeAllOutRecs()
|
|
882
|
+
{
|
|
883
|
+
for (auto outrec : outrec_list_)
|
|
884
|
+
{
|
|
885
|
+
if (outrec->pts) DisposeOutPts(outrec);
|
|
886
|
+
delete outrec;
|
|
887
|
+
}
|
|
888
|
+
outrec_list_.resize(0);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
void ClipperBase::DisposeVerticesAndLocalMinima()
|
|
892
|
+
{
|
|
893
|
+
minima_list_.clear();
|
|
894
|
+
for (auto v : vertex_lists_) delete[] v;
|
|
895
|
+
vertex_lists_.clear();
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open)
|
|
900
|
+
{
|
|
901
|
+
//make sure the vertex is added only once ...
|
|
902
|
+
if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return;
|
|
903
|
+
|
|
904
|
+
vert.flags = (vert.flags | VertexFlags::LocalMin);
|
|
905
|
+
minima_list_.emplace_back(std::make_unique <LocalMinima>(&vert, polytype, is_open));
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
bool ClipperBase::IsContributingClosed(const Active& e) const
|
|
909
|
+
{
|
|
910
|
+
switch (fillrule_)
|
|
911
|
+
{
|
|
912
|
+
case FillRule::EvenOdd:
|
|
913
|
+
break;
|
|
914
|
+
case FillRule::NonZero:
|
|
915
|
+
if (abs(e.wind_cnt) != 1) return false;
|
|
916
|
+
break;
|
|
917
|
+
case FillRule::Positive:
|
|
918
|
+
if (e.wind_cnt != 1) return false;
|
|
919
|
+
break;
|
|
920
|
+
case FillRule::Negative:
|
|
921
|
+
if (e.wind_cnt != -1) return false;
|
|
922
|
+
break;
|
|
923
|
+
// Should never happen, but adding this to stop a compiler warning
|
|
924
|
+
default:
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
switch (cliptype_)
|
|
929
|
+
{
|
|
930
|
+
case ClipType::NoClip:
|
|
931
|
+
return false;
|
|
932
|
+
case ClipType::Intersection:
|
|
933
|
+
switch (fillrule_)
|
|
934
|
+
{
|
|
935
|
+
case FillRule::Positive:
|
|
936
|
+
return (e.wind_cnt2 > 0);
|
|
937
|
+
case FillRule::Negative:
|
|
938
|
+
return (e.wind_cnt2 < 0);
|
|
939
|
+
default:
|
|
940
|
+
return (e.wind_cnt2 != 0);
|
|
941
|
+
}
|
|
942
|
+
break;
|
|
943
|
+
|
|
944
|
+
case ClipType::Union:
|
|
945
|
+
switch (fillrule_)
|
|
946
|
+
{
|
|
947
|
+
case FillRule::Positive:
|
|
948
|
+
return (e.wind_cnt2 <= 0);
|
|
949
|
+
case FillRule::Negative:
|
|
950
|
+
return (e.wind_cnt2 >= 0);
|
|
951
|
+
default:
|
|
952
|
+
return (e.wind_cnt2 == 0);
|
|
953
|
+
}
|
|
954
|
+
break;
|
|
955
|
+
|
|
956
|
+
case ClipType::Difference:
|
|
957
|
+
bool result;
|
|
958
|
+
switch (fillrule_)
|
|
959
|
+
{
|
|
960
|
+
case FillRule::Positive:
|
|
961
|
+
result = (e.wind_cnt2 <= 0);
|
|
962
|
+
break;
|
|
963
|
+
case FillRule::Negative:
|
|
964
|
+
result = (e.wind_cnt2 >= 0);
|
|
965
|
+
break;
|
|
966
|
+
default:
|
|
967
|
+
result = (e.wind_cnt2 == 0);
|
|
968
|
+
}
|
|
969
|
+
if (GetPolyType(e) == PathType::Subject)
|
|
970
|
+
return result;
|
|
971
|
+
else
|
|
972
|
+
return !result;
|
|
973
|
+
break;
|
|
974
|
+
|
|
975
|
+
case ClipType::Xor: return true; break;
|
|
976
|
+
// Should never happen, but adding this to stop a compiler warning
|
|
977
|
+
default:
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
return false; // we should never get here
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
inline bool ClipperBase::IsContributingOpen(const Active& e) const
|
|
985
|
+
{
|
|
986
|
+
bool is_in_clip, is_in_subj;
|
|
987
|
+
switch (fillrule_)
|
|
988
|
+
{
|
|
989
|
+
case FillRule::Positive:
|
|
990
|
+
is_in_clip = e.wind_cnt2 > 0;
|
|
991
|
+
is_in_subj = e.wind_cnt > 0;
|
|
992
|
+
break;
|
|
993
|
+
case FillRule::Negative:
|
|
994
|
+
is_in_clip = e.wind_cnt2 < 0;
|
|
995
|
+
is_in_subj = e.wind_cnt < 0;
|
|
996
|
+
break;
|
|
997
|
+
default:
|
|
998
|
+
is_in_clip = e.wind_cnt2 != 0;
|
|
999
|
+
is_in_subj = e.wind_cnt != 0;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
switch (cliptype_)
|
|
1003
|
+
{
|
|
1004
|
+
case ClipType::Intersection: return is_in_clip;
|
|
1005
|
+
case ClipType::Union: return (!is_in_subj && !is_in_clip);
|
|
1006
|
+
default: return !is_in_clip;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
void ClipperBase::SetWindCountForClosedPathEdge(Active& e)
|
|
1012
|
+
{
|
|
1013
|
+
//Wind counts refer to polygon regions not edges, so here an edge's WindCnt
|
|
1014
|
+
//indicates the higher of the wind counts for the two regions touching the
|
|
1015
|
+
//edge. (NB Adjacent regions can only ever have their wind counts differ by
|
|
1016
|
+
//one. Also, open paths have no meaningful wind directions or counts.)
|
|
1017
|
+
|
|
1018
|
+
Active* e2 = e.prev_in_ael;
|
|
1019
|
+
//find the nearest closed path edge of the same PolyType in AEL (heading left)
|
|
1020
|
+
PathType pt = GetPolyType(e);
|
|
1021
|
+
while (e2 && (GetPolyType(*e2) != pt || IsOpen(*e2))) e2 = e2->prev_in_ael;
|
|
1022
|
+
|
|
1023
|
+
if (!e2)
|
|
1024
|
+
{
|
|
1025
|
+
e.wind_cnt = e.wind_dx;
|
|
1026
|
+
e2 = actives_;
|
|
1027
|
+
}
|
|
1028
|
+
else if (fillrule_ == FillRule::EvenOdd)
|
|
1029
|
+
{
|
|
1030
|
+
e.wind_cnt = e.wind_dx;
|
|
1031
|
+
e.wind_cnt2 = e2->wind_cnt2;
|
|
1032
|
+
e2 = e2->next_in_ael;
|
|
1033
|
+
}
|
|
1034
|
+
else
|
|
1035
|
+
{
|
|
1036
|
+
//NonZero, positive, or negative filling here ...
|
|
1037
|
+
//if e's WindCnt is in the SAME direction as its WindDx, then polygon
|
|
1038
|
+
//filling will be on the right of 'e'.
|
|
1039
|
+
//NB neither e2.WindCnt nor e2.WindDx should ever be 0.
|
|
1040
|
+
if (e2->wind_cnt * e2->wind_dx < 0)
|
|
1041
|
+
{
|
|
1042
|
+
//opposite directions so 'e' is outside 'e2' ...
|
|
1043
|
+
if (abs(e2->wind_cnt) > 1)
|
|
1044
|
+
{
|
|
1045
|
+
//outside prev poly but still inside another.
|
|
1046
|
+
if (e2->wind_dx * e.wind_dx < 0)
|
|
1047
|
+
//reversing direction so use the same WC
|
|
1048
|
+
e.wind_cnt = e2->wind_cnt;
|
|
1049
|
+
else
|
|
1050
|
+
//otherwise keep 'reducing' the WC by 1 (ie towards 0) ...
|
|
1051
|
+
e.wind_cnt = e2->wind_cnt + e.wind_dx;
|
|
1052
|
+
}
|
|
1053
|
+
else
|
|
1054
|
+
//now outside all polys of same polytype so set own WC ...
|
|
1055
|
+
e.wind_cnt = (IsOpen(e) ? 1 : e.wind_dx);
|
|
1056
|
+
}
|
|
1057
|
+
else
|
|
1058
|
+
{
|
|
1059
|
+
//'e' must be inside 'e2'
|
|
1060
|
+
if (e2->wind_dx * e.wind_dx < 0)
|
|
1061
|
+
//reversing direction so use the same WC
|
|
1062
|
+
e.wind_cnt = e2->wind_cnt;
|
|
1063
|
+
else
|
|
1064
|
+
//otherwise keep 'increasing' the WC by 1 (ie away from 0) ...
|
|
1065
|
+
e.wind_cnt = e2->wind_cnt + e.wind_dx;
|
|
1066
|
+
}
|
|
1067
|
+
e.wind_cnt2 = e2->wind_cnt2;
|
|
1068
|
+
e2 = e2->next_in_ael; // ie get ready to calc WindCnt2
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
//update wind_cnt2 ...
|
|
1072
|
+
if (fillrule_ == FillRule::EvenOdd)
|
|
1073
|
+
while (e2 != &e)
|
|
1074
|
+
{
|
|
1075
|
+
if (GetPolyType(*e2) != pt && !IsOpen(*e2))
|
|
1076
|
+
e.wind_cnt2 = (e.wind_cnt2 == 0 ? 1 : 0);
|
|
1077
|
+
e2 = e2->next_in_ael;
|
|
1078
|
+
}
|
|
1079
|
+
else
|
|
1080
|
+
while (e2 != &e)
|
|
1081
|
+
{
|
|
1082
|
+
if (GetPolyType(*e2) != pt && !IsOpen(*e2))
|
|
1083
|
+
e.wind_cnt2 += e2->wind_dx;
|
|
1084
|
+
e2 = e2->next_in_ael;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
void ClipperBase::SetWindCountForOpenPathEdge(Active& e)
|
|
1090
|
+
{
|
|
1091
|
+
Active* e2 = actives_;
|
|
1092
|
+
if (fillrule_ == FillRule::EvenOdd)
|
|
1093
|
+
{
|
|
1094
|
+
int cnt1 = 0, cnt2 = 0;
|
|
1095
|
+
while (e2 != &e)
|
|
1096
|
+
{
|
|
1097
|
+
if (GetPolyType(*e2) == PathType::Clip)
|
|
1098
|
+
cnt2++;
|
|
1099
|
+
else if (!IsOpen(*e2))
|
|
1100
|
+
cnt1++;
|
|
1101
|
+
e2 = e2->next_in_ael;
|
|
1102
|
+
}
|
|
1103
|
+
e.wind_cnt = (IsOdd(cnt1) ? 1 : 0);
|
|
1104
|
+
e.wind_cnt2 = (IsOdd(cnt2) ? 1 : 0);
|
|
1105
|
+
}
|
|
1106
|
+
else
|
|
1107
|
+
{
|
|
1108
|
+
while (e2 != &e)
|
|
1109
|
+
{
|
|
1110
|
+
if (GetPolyType(*e2) == PathType::Clip)
|
|
1111
|
+
e.wind_cnt2 += e2->wind_dx;
|
|
1112
|
+
else if (!IsOpen(*e2))
|
|
1113
|
+
e.wind_cnt += e2->wind_dx;
|
|
1114
|
+
e2 = e2->next_in_ael;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
bool IsValidAelOrder(const Active& resident, const Active& newcomer)
|
|
1120
|
+
{
|
|
1121
|
+
if (newcomer.curr_x != resident.curr_x)
|
|
1122
|
+
return newcomer.curr_x > resident.curr_x;
|
|
1123
|
+
|
|
1124
|
+
//get the turning direction a1.top, a2.bot, a2.top
|
|
1125
|
+
int i = CrossProductSign(resident.top, newcomer.bot, newcomer.top);
|
|
1126
|
+
if (i != 0) return i < 0;
|
|
1127
|
+
|
|
1128
|
+
//edges must be collinear to get here
|
|
1129
|
+
//for starting open paths, place them according to
|
|
1130
|
+
//the direction they're about to turn
|
|
1131
|
+
if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y))
|
|
1132
|
+
{
|
|
1133
|
+
return (CrossProductSign(newcomer.bot, resident.top, NextVertex(resident)->pt) <= 0);
|
|
1134
|
+
}
|
|
1135
|
+
else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y))
|
|
1136
|
+
{
|
|
1137
|
+
return (CrossProductSign(newcomer.bot, newcomer.top, NextVertex(newcomer)->pt) >= 0);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
int64_t y = newcomer.bot.y;
|
|
1141
|
+
bool newcomerIsLeft = newcomer.is_left_bound;
|
|
1142
|
+
|
|
1143
|
+
if (resident.bot.y != y || resident.local_min->vertex->pt.y != y)
|
|
1144
|
+
return newcomer.is_left_bound;
|
|
1145
|
+
//resident must also have just been inserted
|
|
1146
|
+
else if (resident.is_left_bound != newcomerIsLeft)
|
|
1147
|
+
return newcomerIsLeft;
|
|
1148
|
+
else if (IsCollinear(PrevPrevVertex(resident)->pt,
|
|
1149
|
+
resident.bot, resident.top)) return true;
|
|
1150
|
+
else
|
|
1151
|
+
//compare turning direction of the alternate bound
|
|
1152
|
+
return (CrossProductSign(PrevPrevVertex(resident)->pt,
|
|
1153
|
+
newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
void ClipperBase::InsertLeftEdge(Active& e)
|
|
1158
|
+
{
|
|
1159
|
+
Active* e2;
|
|
1160
|
+
if (!actives_)
|
|
1161
|
+
{
|
|
1162
|
+
e.prev_in_ael = nullptr;
|
|
1163
|
+
e.next_in_ael = nullptr;
|
|
1164
|
+
actives_ = &e;
|
|
1165
|
+
}
|
|
1166
|
+
else if (!IsValidAelOrder(*actives_, e))
|
|
1167
|
+
{
|
|
1168
|
+
e.prev_in_ael = nullptr;
|
|
1169
|
+
e.next_in_ael = actives_;
|
|
1170
|
+
actives_->prev_in_ael = &e;
|
|
1171
|
+
actives_ = &e;
|
|
1172
|
+
}
|
|
1173
|
+
else
|
|
1174
|
+
{
|
|
1175
|
+
e2 = actives_;
|
|
1176
|
+
while (e2->next_in_ael && IsValidAelOrder(*e2->next_in_ael, e))
|
|
1177
|
+
e2 = e2->next_in_ael;
|
|
1178
|
+
if (e2->join_with == JoinWith::Right)
|
|
1179
|
+
e2 = e2->next_in_ael;
|
|
1180
|
+
if (!e2) return; // should never happen and stops compiler warning :)
|
|
1181
|
+
e.next_in_ael = e2->next_in_ael;
|
|
1182
|
+
if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e;
|
|
1183
|
+
e.prev_in_ael = e2;
|
|
1184
|
+
e2->next_in_ael = &e;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
void InsertRightEdge(Active& e, Active& e2)
|
|
1190
|
+
{
|
|
1191
|
+
e2.next_in_ael = e.next_in_ael;
|
|
1192
|
+
if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2;
|
|
1193
|
+
e2.prev_in_ael = &e;
|
|
1194
|
+
e.next_in_ael = &e2;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y)
|
|
1199
|
+
{
|
|
1200
|
+
LocalMinima* local_minima;
|
|
1201
|
+
Active* left_bound, * right_bound;
|
|
1202
|
+
//Add any local minima (if any) at BotY ...
|
|
1203
|
+
//nb: horizontal local minima edges should contain locMin.vertex.prev
|
|
1204
|
+
|
|
1205
|
+
while (PopLocalMinima(bot_y, local_minima))
|
|
1206
|
+
{
|
|
1207
|
+
if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::Empty)
|
|
1208
|
+
{
|
|
1209
|
+
left_bound = nullptr;
|
|
1210
|
+
}
|
|
1211
|
+
else
|
|
1212
|
+
{
|
|
1213
|
+
left_bound = new Active();
|
|
1214
|
+
left_bound->bot = local_minima->vertex->pt;
|
|
1215
|
+
left_bound->curr_x = left_bound->bot.x;
|
|
1216
|
+
left_bound->wind_dx = -1;
|
|
1217
|
+
left_bound->vertex_top = local_minima->vertex->prev; // ie descending
|
|
1218
|
+
left_bound->top = left_bound->vertex_top->pt;
|
|
1219
|
+
left_bound->local_min = local_minima;
|
|
1220
|
+
SetDx(*left_bound);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::Empty)
|
|
1224
|
+
{
|
|
1225
|
+
right_bound = nullptr;
|
|
1226
|
+
}
|
|
1227
|
+
else
|
|
1228
|
+
{
|
|
1229
|
+
right_bound = new Active();
|
|
1230
|
+
right_bound->bot = local_minima->vertex->pt;
|
|
1231
|
+
right_bound->curr_x = right_bound->bot.x;
|
|
1232
|
+
right_bound->wind_dx = 1;
|
|
1233
|
+
right_bound->vertex_top = local_minima->vertex->next; // ie ascending
|
|
1234
|
+
right_bound->top = right_bound->vertex_top->pt;
|
|
1235
|
+
right_bound->local_min = local_minima;
|
|
1236
|
+
SetDx(*right_bound);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
//Currently LeftB is just the descending bound and RightB is the ascending.
|
|
1240
|
+
//Now if the LeftB isn't on the left of RightB then we need swap them.
|
|
1241
|
+
if (left_bound && right_bound)
|
|
1242
|
+
{
|
|
1243
|
+
if (IsHorizontal(*left_bound))
|
|
1244
|
+
{
|
|
1245
|
+
if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound);
|
|
1246
|
+
}
|
|
1247
|
+
else if (IsHorizontal(*right_bound))
|
|
1248
|
+
{
|
|
1249
|
+
if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound);
|
|
1250
|
+
}
|
|
1251
|
+
else if (left_bound->dx < right_bound->dx)
|
|
1252
|
+
SwapActives(left_bound, right_bound);
|
|
1253
|
+
}
|
|
1254
|
+
else if (!left_bound)
|
|
1255
|
+
{
|
|
1256
|
+
left_bound = right_bound;
|
|
1257
|
+
right_bound = nullptr;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
bool contributing;
|
|
1261
|
+
left_bound->is_left_bound = true;
|
|
1262
|
+
InsertLeftEdge(*left_bound);
|
|
1263
|
+
|
|
1264
|
+
if (IsOpen(*left_bound))
|
|
1265
|
+
{
|
|
1266
|
+
SetWindCountForOpenPathEdge(*left_bound);
|
|
1267
|
+
contributing = IsContributingOpen(*left_bound);
|
|
1268
|
+
}
|
|
1269
|
+
else
|
|
1270
|
+
{
|
|
1271
|
+
SetWindCountForClosedPathEdge(*left_bound);
|
|
1272
|
+
contributing = IsContributingClosed(*left_bound);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
if (right_bound)
|
|
1276
|
+
{
|
|
1277
|
+
right_bound->is_left_bound = false;
|
|
1278
|
+
right_bound->wind_cnt = left_bound->wind_cnt;
|
|
1279
|
+
right_bound->wind_cnt2 = left_bound->wind_cnt2;
|
|
1280
|
+
InsertRightEdge(*left_bound, *right_bound); ///////
|
|
1281
|
+
if (contributing)
|
|
1282
|
+
{
|
|
1283
|
+
AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true);
|
|
1284
|
+
if (!IsHorizontal(*left_bound))
|
|
1285
|
+
CheckJoinLeft(*left_bound, left_bound->bot);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
while (right_bound->next_in_ael &&
|
|
1289
|
+
IsValidAelOrder(*right_bound->next_in_ael, *right_bound))
|
|
1290
|
+
{
|
|
1291
|
+
IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot);
|
|
1292
|
+
SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (IsHorizontal(*right_bound))
|
|
1296
|
+
PushHorz(*right_bound);
|
|
1297
|
+
else
|
|
1298
|
+
{
|
|
1299
|
+
CheckJoinRight(*right_bound, right_bound->bot);
|
|
1300
|
+
InsertScanline(right_bound->top.y);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
else if (contributing)
|
|
1304
|
+
{
|
|
1305
|
+
StartOpenPath(*left_bound, left_bound->bot);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
if (IsHorizontal(*left_bound))
|
|
1309
|
+
PushHorz(*left_bound);
|
|
1310
|
+
else
|
|
1311
|
+
InsertScanline(left_bound->top.y);
|
|
1312
|
+
} // while (PopLocalMinima())
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
inline void ClipperBase::PushHorz(Active& e)
|
|
1317
|
+
{
|
|
1318
|
+
e.next_in_sel = (sel_ ? sel_ : nullptr);
|
|
1319
|
+
sel_ = &e;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
inline bool ClipperBase::PopHorz(Active*& e)
|
|
1324
|
+
{
|
|
1325
|
+
e = sel_;
|
|
1326
|
+
if (!e) return false;
|
|
1327
|
+
sel_ = sel_->next_in_sel;
|
|
1328
|
+
return true;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2,
|
|
1333
|
+
const Point64& pt, bool is_new)
|
|
1334
|
+
{
|
|
1335
|
+
OutRec* outrec = NewOutRec();
|
|
1336
|
+
e1.outrec = outrec;
|
|
1337
|
+
e2.outrec = outrec;
|
|
1338
|
+
|
|
1339
|
+
if (IsOpen(e1))
|
|
1340
|
+
{
|
|
1341
|
+
outrec->owner = nullptr;
|
|
1342
|
+
outrec->is_open = true;
|
|
1343
|
+
if (e1.wind_dx > 0)
|
|
1344
|
+
SetSides(*outrec, e1, e2);
|
|
1345
|
+
else
|
|
1346
|
+
SetSides(*outrec, e2, e1);
|
|
1347
|
+
}
|
|
1348
|
+
else
|
|
1349
|
+
{
|
|
1350
|
+
Active* prevHotEdge = GetPrevHotEdge(e1);
|
|
1351
|
+
//e.windDx is the winding direction of the **input** paths
|
|
1352
|
+
//and unrelated to the winding direction of output polygons.
|
|
1353
|
+
//Output orientation is determined by e.outrec.frontE which is
|
|
1354
|
+
//the ascending edge (see AddLocalMinPoly).
|
|
1355
|
+
if (prevHotEdge)
|
|
1356
|
+
{
|
|
1357
|
+
if (using_polytree_)
|
|
1358
|
+
SetOwner(outrec, prevHotEdge->outrec);
|
|
1359
|
+
if (OutrecIsAscending(prevHotEdge) == is_new)
|
|
1360
|
+
SetSides(*outrec, e2, e1);
|
|
1361
|
+
else
|
|
1362
|
+
SetSides(*outrec, e1, e2);
|
|
1363
|
+
}
|
|
1364
|
+
else
|
|
1365
|
+
{
|
|
1366
|
+
outrec->owner = nullptr;
|
|
1367
|
+
if (is_new)
|
|
1368
|
+
SetSides(*outrec, e1, e2);
|
|
1369
|
+
else
|
|
1370
|
+
SetSides(*outrec, e2, e1);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
OutPt* op = new OutPt(pt, outrec);
|
|
1375
|
+
outrec->pts = op;
|
|
1376
|
+
return op;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt)
|
|
1381
|
+
{
|
|
1382
|
+
if (IsJoined(e1)) Split(e1, pt);
|
|
1383
|
+
if (IsJoined(e2)) Split(e2, pt);
|
|
1384
|
+
|
|
1385
|
+
if (IsFront(e1) == IsFront(e2))
|
|
1386
|
+
{
|
|
1387
|
+
if (IsOpenEnd(e1))
|
|
1388
|
+
SwapFrontBackSides(*e1.outrec);
|
|
1389
|
+
else if (IsOpenEnd(e2))
|
|
1390
|
+
SwapFrontBackSides(*e2.outrec);
|
|
1391
|
+
else
|
|
1392
|
+
{
|
|
1393
|
+
succeeded_ = false;
|
|
1394
|
+
return nullptr;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
OutPt* result = AddOutPt(e1, pt);
|
|
1399
|
+
if (e1.outrec == e2.outrec)
|
|
1400
|
+
{
|
|
1401
|
+
OutRec& outrec = *e1.outrec;
|
|
1402
|
+
outrec.pts = result;
|
|
1403
|
+
|
|
1404
|
+
if (using_polytree_)
|
|
1405
|
+
{
|
|
1406
|
+
Active* e = GetPrevHotEdge(e1);
|
|
1407
|
+
if (!e)
|
|
1408
|
+
outrec.owner = nullptr;
|
|
1409
|
+
else
|
|
1410
|
+
SetOwner(&outrec, e->outrec);
|
|
1411
|
+
// nb: outRec.owner here is likely NOT the real
|
|
1412
|
+
// owner but this will be checked in RecursiveCheckOwners()
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
UncoupleOutRec(e1);
|
|
1416
|
+
result = outrec.pts;
|
|
1417
|
+
if (outrec.owner && !outrec.owner->front_edge)
|
|
1418
|
+
outrec.owner = GetRealOutRec(outrec.owner);
|
|
1419
|
+
}
|
|
1420
|
+
//and to preserve the winding orientation of outrec ...
|
|
1421
|
+
else if (IsOpen(e1))
|
|
1422
|
+
{
|
|
1423
|
+
if (e1.wind_dx < 0)
|
|
1424
|
+
JoinOutrecPaths(e1, e2);
|
|
1425
|
+
else
|
|
1426
|
+
JoinOutrecPaths(e2, e1);
|
|
1427
|
+
}
|
|
1428
|
+
else if (e1.outrec->idx < e2.outrec->idx)
|
|
1429
|
+
JoinOutrecPaths(e1, e2);
|
|
1430
|
+
else
|
|
1431
|
+
JoinOutrecPaths(e2, e1);
|
|
1432
|
+
return result;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2)
|
|
1436
|
+
{
|
|
1437
|
+
//join e2 outrec path onto e1 outrec path and then delete e2 outrec path
|
|
1438
|
+
//pointers. (NB Only very rarely do the joining ends share the same coords.)
|
|
1439
|
+
OutPt* p1_st = e1.outrec->pts;
|
|
1440
|
+
OutPt* p2_st = e2.outrec->pts;
|
|
1441
|
+
OutPt* p1_end = p1_st->next;
|
|
1442
|
+
OutPt* p2_end = p2_st->next;
|
|
1443
|
+
if (IsFront(e1))
|
|
1444
|
+
{
|
|
1445
|
+
p2_end->prev = p1_st;
|
|
1446
|
+
p1_st->next = p2_end;
|
|
1447
|
+
p2_st->next = p1_end;
|
|
1448
|
+
p1_end->prev = p2_st;
|
|
1449
|
+
e1.outrec->pts = p2_st;
|
|
1450
|
+
e1.outrec->front_edge = e2.outrec->front_edge;
|
|
1451
|
+
if (e1.outrec->front_edge)
|
|
1452
|
+
e1.outrec->front_edge->outrec = e1.outrec;
|
|
1453
|
+
}
|
|
1454
|
+
else
|
|
1455
|
+
{
|
|
1456
|
+
p1_end->prev = p2_st;
|
|
1457
|
+
p2_st->next = p1_end;
|
|
1458
|
+
p1_st->next = p2_end;
|
|
1459
|
+
p2_end->prev = p1_st;
|
|
1460
|
+
e1.outrec->back_edge = e2.outrec->back_edge;
|
|
1461
|
+
if (e1.outrec->back_edge)
|
|
1462
|
+
e1.outrec->back_edge->outrec = e1.outrec;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
//after joining, the e2.OutRec must contains no vertices ...
|
|
1466
|
+
e2.outrec->front_edge = nullptr;
|
|
1467
|
+
e2.outrec->back_edge = nullptr;
|
|
1468
|
+
e2.outrec->pts = nullptr;
|
|
1469
|
+
|
|
1470
|
+
if (IsOpenEnd(e1))
|
|
1471
|
+
{
|
|
1472
|
+
e2.outrec->pts = e1.outrec->pts;
|
|
1473
|
+
e1.outrec->pts = nullptr;
|
|
1474
|
+
}
|
|
1475
|
+
else
|
|
1476
|
+
SetOwner(e2.outrec, e1.outrec);
|
|
1477
|
+
|
|
1478
|
+
//and e1 and e2 are maxima and are about to be dropped from the Actives list.
|
|
1479
|
+
e1.outrec = nullptr;
|
|
1480
|
+
e2.outrec = nullptr;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
OutRec* ClipperBase::NewOutRec()
|
|
1484
|
+
{
|
|
1485
|
+
OutRec* result = new OutRec();
|
|
1486
|
+
result->idx = outrec_list_.size();
|
|
1487
|
+
outrec_list_.emplace_back(result);
|
|
1488
|
+
result->pts = nullptr;
|
|
1489
|
+
result->owner = nullptr;
|
|
1490
|
+
result->polypath = nullptr;
|
|
1491
|
+
result->is_open = false;
|
|
1492
|
+
result->splits = nullptr;
|
|
1493
|
+
return result;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
|
|
1497
|
+
OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt)
|
|
1498
|
+
{
|
|
1499
|
+
OutPt* new_op = nullptr;
|
|
1500
|
+
|
|
1501
|
+
//Outrec.OutPts: a circular doubly-linked-list of POutPt where ...
|
|
1502
|
+
//op_front[.Prev]* ~~~> op_back & op_back == op_front.Next
|
|
1503
|
+
OutRec* outrec = e.outrec;
|
|
1504
|
+
bool to_front = IsFront(e);
|
|
1505
|
+
OutPt* op_front = outrec->pts;
|
|
1506
|
+
OutPt* op_back = op_front->next;
|
|
1507
|
+
|
|
1508
|
+
if (to_front)
|
|
1509
|
+
{
|
|
1510
|
+
if (pt == op_front->pt)
|
|
1511
|
+
return op_front;
|
|
1512
|
+
}
|
|
1513
|
+
else if (pt == op_back->pt)
|
|
1514
|
+
return op_back;
|
|
1515
|
+
|
|
1516
|
+
new_op = new OutPt(pt, outrec);
|
|
1517
|
+
op_back->prev = new_op;
|
|
1518
|
+
new_op->prev = op_front;
|
|
1519
|
+
new_op->next = op_back;
|
|
1520
|
+
op_front->next = new_op;
|
|
1521
|
+
if (to_front) outrec->pts = new_op;
|
|
1522
|
+
return new_op;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
void ClipperBase::CleanCollinear(OutRec* outrec)
|
|
1526
|
+
{
|
|
1527
|
+
outrec = GetRealOutRec(outrec);
|
|
1528
|
+
if (!outrec || outrec->is_open) return;
|
|
1529
|
+
if (!IsValidClosedPath(outrec->pts))
|
|
1530
|
+
{
|
|
1531
|
+
DisposeOutPts(outrec);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
OutPt* startOp = outrec->pts, * op2 = startOp;
|
|
1536
|
+
for (; ; )
|
|
1537
|
+
{
|
|
1538
|
+
//NB if preserveCollinear == true, then only remove 180 deg. spikes
|
|
1539
|
+
if (IsCollinear(op2->prev->pt, op2->pt, op2->next->pt) &&
|
|
1540
|
+
(op2->pt == op2->prev->pt ||
|
|
1541
|
+
op2->pt == op2->next->pt || !preserve_collinear_ ||
|
|
1542
|
+
DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0))
|
|
1543
|
+
{
|
|
1544
|
+
|
|
1545
|
+
if (op2 == outrec->pts) outrec->pts = op2->prev;
|
|
1546
|
+
|
|
1547
|
+
op2 = DisposeOutPt(op2);
|
|
1548
|
+
if (!IsValidClosedPath(op2))
|
|
1549
|
+
{
|
|
1550
|
+
DisposeOutPts(outrec);
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
startOp = op2;
|
|
1554
|
+
continue;
|
|
1555
|
+
}
|
|
1556
|
+
op2 = op2->next;
|
|
1557
|
+
if (op2 == startOp) break;
|
|
1558
|
+
}
|
|
1559
|
+
FixSelfIntersects(outrec);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
void ClipperBase::DoSplitOp (OutRec* outrec, OutPt* splitOp)
|
|
1563
|
+
{
|
|
1564
|
+
// splitOp.prev -> splitOp &&
|
|
1565
|
+
// splitOp.next -> splitOp.next.next are intersecting
|
|
1566
|
+
OutPt* prevOp = splitOp->prev;
|
|
1567
|
+
OutPt* nextNextOp = splitOp->next->next;
|
|
1568
|
+
outrec->pts = prevOp;
|
|
1569
|
+
|
|
1570
|
+
Point64 ip;
|
|
1571
|
+
GetLineIntersectPt(prevOp->pt, splitOp->pt,
|
|
1572
|
+
splitOp->next->pt, nextNextOp->pt, ip);
|
|
1573
|
+
|
|
1574
|
+
#ifdef USINGZ
|
|
1575
|
+
if (zCallback_) zCallback_(prevOp->pt, splitOp->pt,
|
|
1576
|
+
splitOp->next->pt, nextNextOp->pt, ip);
|
|
1577
|
+
#endif
|
|
1578
|
+
double area1 = Area(outrec->pts);
|
|
1579
|
+
double absArea1 = std::fabs(area1);
|
|
1580
|
+
if (absArea1 < 2)
|
|
1581
|
+
{
|
|
1582
|
+
DisposeOutPts(outrec);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt);
|
|
1587
|
+
double absArea2 = std::fabs(area2);
|
|
1588
|
+
|
|
1589
|
+
// de-link splitOp and splitOp.next from the path
|
|
1590
|
+
// while inserting the intersection point
|
|
1591
|
+
if (ip == prevOp->pt || ip == nextNextOp->pt)
|
|
1592
|
+
{
|
|
1593
|
+
nextNextOp->prev = prevOp;
|
|
1594
|
+
prevOp->next = nextNextOp;
|
|
1595
|
+
}
|
|
1596
|
+
else
|
|
1597
|
+
{
|
|
1598
|
+
OutPt* newOp2 = new OutPt(ip, prevOp->outrec);
|
|
1599
|
+
newOp2->prev = prevOp;
|
|
1600
|
+
newOp2->next = nextNextOp;
|
|
1601
|
+
nextNextOp->prev = newOp2;
|
|
1602
|
+
prevOp->next = newOp2;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// area1 is the path's area *before* splitting, whereas area2 is
|
|
1606
|
+
// the area of the triangle containing splitOp & splitOp.next.
|
|
1607
|
+
// So the only way for these areas to have the same sign is if
|
|
1608
|
+
// the split triangle is larger than the path containing prevOp or
|
|
1609
|
+
// if there's more than one self-intersection.
|
|
1610
|
+
if (absArea2 >= 1 &&
|
|
1611
|
+
(absArea2 > absArea1 || (area2 > 0) == (area1 > 0)))
|
|
1612
|
+
{
|
|
1613
|
+
OutRec* newOr = NewOutRec();
|
|
1614
|
+
newOr->owner = outrec->owner;
|
|
1615
|
+
|
|
1616
|
+
splitOp->outrec = newOr;
|
|
1617
|
+
splitOp->next->outrec = newOr;
|
|
1618
|
+
OutPt* newOp = new OutPt(ip, newOr);
|
|
1619
|
+
newOp->prev = splitOp->next;
|
|
1620
|
+
newOp->next = splitOp;
|
|
1621
|
+
newOr->pts = newOp;
|
|
1622
|
+
splitOp->prev = newOp;
|
|
1623
|
+
splitOp->next->next = newOp;
|
|
1624
|
+
|
|
1625
|
+
if (using_polytree_)
|
|
1626
|
+
{
|
|
1627
|
+
if (Path2ContainsPath1(prevOp, newOp))
|
|
1628
|
+
{
|
|
1629
|
+
newOr->splits = new OutRecList();
|
|
1630
|
+
newOr->splits->emplace_back(outrec);
|
|
1631
|
+
}
|
|
1632
|
+
else
|
|
1633
|
+
{
|
|
1634
|
+
if (!outrec->splits) outrec->splits = new OutRecList();
|
|
1635
|
+
outrec->splits->emplace_back(newOr);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
else
|
|
1640
|
+
{
|
|
1641
|
+
delete splitOp->next;
|
|
1642
|
+
delete splitOp;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
void ClipperBase::FixSelfIntersects(OutRec* outrec)
|
|
1647
|
+
{
|
|
1648
|
+
OutPt* op2 = outrec->pts;
|
|
1649
|
+
if (op2->prev == op2->next->next)
|
|
1650
|
+
return; // because triangles can't self-intersect
|
|
1651
|
+
for (; ; )
|
|
1652
|
+
{
|
|
1653
|
+
if (SegmentsIntersect(op2->prev->pt,
|
|
1654
|
+
op2->pt, op2->next->pt, op2->next->next->pt))
|
|
1655
|
+
{
|
|
1656
|
+
if (op2 == outrec->pts || op2->next == outrec->pts)
|
|
1657
|
+
outrec->pts = outrec->pts->prev;
|
|
1658
|
+
DoSplitOp(outrec, op2);
|
|
1659
|
+
if (!outrec->pts) break;
|
|
1660
|
+
op2 = outrec->pts;
|
|
1661
|
+
if (op2->prev == op2->next->next)
|
|
1662
|
+
break; // again, because triangles can't self-intersect
|
|
1663
|
+
continue;
|
|
1664
|
+
}
|
|
1665
|
+
else
|
|
1666
|
+
op2 = op2->next;
|
|
1667
|
+
|
|
1668
|
+
if (op2 == outrec->pts) break;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
|
|
1673
|
+
inline void UpdateOutrecOwner(OutRec* outrec)
|
|
1674
|
+
{
|
|
1675
|
+
OutPt* opCurr = outrec->pts;
|
|
1676
|
+
for (; ; )
|
|
1677
|
+
{
|
|
1678
|
+
opCurr->outrec = outrec;
|
|
1679
|
+
opCurr = opCurr->next;
|
|
1680
|
+
if (opCurr == outrec->pts) return;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
|
|
1685
|
+
OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt)
|
|
1686
|
+
{
|
|
1687
|
+
OutRec* outrec = NewOutRec();
|
|
1688
|
+
outrec->is_open = true;
|
|
1689
|
+
|
|
1690
|
+
if (e.wind_dx > 0)
|
|
1691
|
+
{
|
|
1692
|
+
outrec->front_edge = &e;
|
|
1693
|
+
outrec->back_edge = nullptr;
|
|
1694
|
+
}
|
|
1695
|
+
else
|
|
1696
|
+
{
|
|
1697
|
+
outrec->front_edge = nullptr;
|
|
1698
|
+
outrec->back_edge = &e;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
e.outrec = outrec;
|
|
1702
|
+
|
|
1703
|
+
OutPt* op = new OutPt(pt, outrec);
|
|
1704
|
+
outrec->pts = op;
|
|
1705
|
+
return op;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
inline void TrimHorz(Active& horzEdge, bool preserveCollinear)
|
|
1709
|
+
{
|
|
1710
|
+
bool wasTrimmed = false;
|
|
1711
|
+
Point64 pt = NextVertex(horzEdge)->pt;
|
|
1712
|
+
while (pt.y == horzEdge.top.y)
|
|
1713
|
+
{
|
|
1714
|
+
//always trim 180 deg. spikes (in closed paths)
|
|
1715
|
+
//but otherwise break if preserveCollinear = true
|
|
1716
|
+
if (preserveCollinear &&
|
|
1717
|
+
((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x)))
|
|
1718
|
+
break;
|
|
1719
|
+
|
|
1720
|
+
horzEdge.vertex_top = NextVertex(horzEdge);
|
|
1721
|
+
horzEdge.top = pt;
|
|
1722
|
+
wasTrimmed = true;
|
|
1723
|
+
if (IsMaxima(horzEdge)) break;
|
|
1724
|
+
pt = NextVertex(horzEdge)->pt;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
if (wasTrimmed) SetDx(horzEdge); // +/-infinity
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
|
|
1731
|
+
inline void ClipperBase::UpdateEdgeIntoAEL(Active* e)
|
|
1732
|
+
{
|
|
1733
|
+
e->bot = e->top;
|
|
1734
|
+
e->vertex_top = NextVertex(*e);
|
|
1735
|
+
e->top = e->vertex_top->pt;
|
|
1736
|
+
e->curr_x = e->bot.x;
|
|
1737
|
+
SetDx(*e);
|
|
1738
|
+
|
|
1739
|
+
if (IsJoined(*e)) Split(*e, e->bot);
|
|
1740
|
+
|
|
1741
|
+
if (IsHorizontal(*e))
|
|
1742
|
+
{
|
|
1743
|
+
if (!IsOpen(*e)) TrimHorz(*e, preserve_collinear_);
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
InsertScanline(e->top.y);
|
|
1748
|
+
CheckJoinLeft(*e, e->bot);
|
|
1749
|
+
CheckJoinRight(*e, e->bot, true); // (#500)
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
Active* FindEdgeWithMatchingLocMin(Active* e)
|
|
1753
|
+
{
|
|
1754
|
+
Active* result = e->next_in_ael;
|
|
1755
|
+
while (result)
|
|
1756
|
+
{
|
|
1757
|
+
if (result->local_min == e->local_min) return result;
|
|
1758
|
+
else if (!IsHorizontal(*result) && e->bot != result->bot) result = nullptr;
|
|
1759
|
+
else result = result->next_in_ael;
|
|
1760
|
+
}
|
|
1761
|
+
result = e->prev_in_ael;
|
|
1762
|
+
while (result)
|
|
1763
|
+
{
|
|
1764
|
+
if (result->local_min == e->local_min) return result;
|
|
1765
|
+
else if (!IsHorizontal(*result) && e->bot != result->bot) return nullptr;
|
|
1766
|
+
else result = result->prev_in_ael;
|
|
1767
|
+
}
|
|
1768
|
+
return result;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
|
|
1772
|
+
void ClipperBase::IntersectEdges(Active& e1, Active& e2, const Point64& pt)
|
|
1773
|
+
{
|
|
1774
|
+
//MANAGE OPEN PATH INTERSECTIONS SEPARATELY ...
|
|
1775
|
+
if (has_open_paths_ && (IsOpen(e1) || IsOpen(e2)))
|
|
1776
|
+
{
|
|
1777
|
+
if (IsOpen(e1) && IsOpen(e2)) return;
|
|
1778
|
+
Active* edge_o, * edge_c;
|
|
1779
|
+
if (IsOpen(e1))
|
|
1780
|
+
{
|
|
1781
|
+
edge_o = &e1;
|
|
1782
|
+
edge_c = &e2;
|
|
1783
|
+
}
|
|
1784
|
+
else
|
|
1785
|
+
{
|
|
1786
|
+
edge_o = &e2;
|
|
1787
|
+
edge_c = &e1;
|
|
1788
|
+
}
|
|
1789
|
+
if (IsJoined(*edge_c)) Split(*edge_c, pt); // needed for safety
|
|
1790
|
+
|
|
1791
|
+
if (abs(edge_c->wind_cnt) != 1) return;
|
|
1792
|
+
switch (cliptype_)
|
|
1793
|
+
{
|
|
1794
|
+
case ClipType::Union:
|
|
1795
|
+
if (!IsHotEdge(*edge_c)) return;
|
|
1796
|
+
break;
|
|
1797
|
+
default:
|
|
1798
|
+
if (edge_c->local_min->polytype == PathType::Subject)
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
switch (fillrule_)
|
|
1803
|
+
{
|
|
1804
|
+
case FillRule::Positive:
|
|
1805
|
+
if (edge_c->wind_cnt != 1) return;
|
|
1806
|
+
break;
|
|
1807
|
+
case FillRule::Negative:
|
|
1808
|
+
if (edge_c->wind_cnt != -1) return;
|
|
1809
|
+
break;
|
|
1810
|
+
default:
|
|
1811
|
+
if (std::abs(edge_c->wind_cnt) != 1) return;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
#ifdef USINGZ
|
|
1815
|
+
OutPt* resultOp;
|
|
1816
|
+
#endif
|
|
1817
|
+
//toggle contribution ...
|
|
1818
|
+
if (IsHotEdge(*edge_o))
|
|
1819
|
+
{
|
|
1820
|
+
#ifdef USINGZ
|
|
1821
|
+
resultOp = AddOutPt(*edge_o, pt);
|
|
1822
|
+
#else
|
|
1823
|
+
AddOutPt(*edge_o, pt);
|
|
1824
|
+
#endif
|
|
1825
|
+
if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr;
|
|
1826
|
+
else edge_o->outrec->back_edge = nullptr;
|
|
1827
|
+
edge_o->outrec = nullptr;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
//horizontal edges can pass under open paths at a LocMins
|
|
1831
|
+
else if (pt == edge_o->local_min->vertex->pt &&
|
|
1832
|
+
!IsOpenEnd(*edge_o->local_min->vertex))
|
|
1833
|
+
{
|
|
1834
|
+
//find the other side of the LocMin and
|
|
1835
|
+
//if it's 'hot' join up with it ...
|
|
1836
|
+
Active* e3 = FindEdgeWithMatchingLocMin(edge_o);
|
|
1837
|
+
if (e3 && IsHotEdge(*e3))
|
|
1838
|
+
{
|
|
1839
|
+
edge_o->outrec = e3->outrec;
|
|
1840
|
+
if (edge_o->wind_dx > 0)
|
|
1841
|
+
SetSides(*e3->outrec, *edge_o, *e3);
|
|
1842
|
+
else
|
|
1843
|
+
SetSides(*e3->outrec, *e3, *edge_o);
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
else
|
|
1847
|
+
#ifdef USINGZ
|
|
1848
|
+
resultOp = StartOpenPath(*edge_o, pt);
|
|
1849
|
+
#else
|
|
1850
|
+
StartOpenPath(*edge_o, pt);
|
|
1851
|
+
#endif
|
|
1852
|
+
}
|
|
1853
|
+
else
|
|
1854
|
+
#ifdef USINGZ
|
|
1855
|
+
resultOp = StartOpenPath(*edge_o, pt);
|
|
1856
|
+
#else
|
|
1857
|
+
StartOpenPath(*edge_o, pt);
|
|
1858
|
+
#endif
|
|
1859
|
+
|
|
1860
|
+
#ifdef USINGZ
|
|
1861
|
+
if (zCallback_) SetZ(*edge_o, *edge_c, resultOp->pt);
|
|
1862
|
+
#endif
|
|
1863
|
+
return;
|
|
1864
|
+
} // end of an open path intersection
|
|
1865
|
+
|
|
1866
|
+
//MANAGING CLOSED PATHS FROM HERE ON
|
|
1867
|
+
|
|
1868
|
+
if (IsJoined(e1)) Split(e1, pt);
|
|
1869
|
+
if (IsJoined(e2)) Split(e2, pt);
|
|
1870
|
+
|
|
1871
|
+
//UPDATE WINDING COUNTS...
|
|
1872
|
+
|
|
1873
|
+
int old_e1_windcnt, old_e2_windcnt;
|
|
1874
|
+
if (e1.local_min->polytype == e2.local_min->polytype)
|
|
1875
|
+
{
|
|
1876
|
+
if (fillrule_ == FillRule::EvenOdd)
|
|
1877
|
+
{
|
|
1878
|
+
old_e1_windcnt = e1.wind_cnt;
|
|
1879
|
+
e1.wind_cnt = e2.wind_cnt;
|
|
1880
|
+
e2.wind_cnt = old_e1_windcnt;
|
|
1881
|
+
}
|
|
1882
|
+
else
|
|
1883
|
+
{
|
|
1884
|
+
if (e1.wind_cnt + e2.wind_dx == 0)
|
|
1885
|
+
e1.wind_cnt = -e1.wind_cnt;
|
|
1886
|
+
else
|
|
1887
|
+
e1.wind_cnt += e2.wind_dx;
|
|
1888
|
+
if (e2.wind_cnt - e1.wind_dx == 0)
|
|
1889
|
+
e2.wind_cnt = -e2.wind_cnt;
|
|
1890
|
+
else
|
|
1891
|
+
e2.wind_cnt -= e1.wind_dx;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
else
|
|
1895
|
+
{
|
|
1896
|
+
if (fillrule_ != FillRule::EvenOdd)
|
|
1897
|
+
{
|
|
1898
|
+
e1.wind_cnt2 += e2.wind_dx;
|
|
1899
|
+
e2.wind_cnt2 -= e1.wind_dx;
|
|
1900
|
+
}
|
|
1901
|
+
else
|
|
1902
|
+
{
|
|
1903
|
+
e1.wind_cnt2 = (e1.wind_cnt2 == 0 ? 1 : 0);
|
|
1904
|
+
e2.wind_cnt2 = (e2.wind_cnt2 == 0 ? 1 : 0);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
switch (fillrule_)
|
|
1909
|
+
{
|
|
1910
|
+
case FillRule::EvenOdd:
|
|
1911
|
+
case FillRule::NonZero:
|
|
1912
|
+
old_e1_windcnt = abs(e1.wind_cnt);
|
|
1913
|
+
old_e2_windcnt = abs(e2.wind_cnt);
|
|
1914
|
+
break;
|
|
1915
|
+
default:
|
|
1916
|
+
if (fillrule_ == fillpos)
|
|
1917
|
+
{
|
|
1918
|
+
old_e1_windcnt = e1.wind_cnt;
|
|
1919
|
+
old_e2_windcnt = e2.wind_cnt;
|
|
1920
|
+
}
|
|
1921
|
+
else
|
|
1922
|
+
{
|
|
1923
|
+
old_e1_windcnt = -e1.wind_cnt;
|
|
1924
|
+
old_e2_windcnt = -e2.wind_cnt;
|
|
1925
|
+
}
|
|
1926
|
+
break;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1;
|
|
1930
|
+
const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1;
|
|
1931
|
+
|
|
1932
|
+
if ((!IsHotEdge(e1) && !e1_windcnt_in_01) ||
|
|
1933
|
+
(!IsHotEdge(e2) && !e2_windcnt_in_01))
|
|
1934
|
+
return;
|
|
1935
|
+
|
|
1936
|
+
//NOW PROCESS THE INTERSECTION ...
|
|
1937
|
+
#ifdef USINGZ
|
|
1938
|
+
OutPt* resultOp = nullptr;
|
|
1939
|
+
#endif
|
|
1940
|
+
//if both edges are 'hot' ...
|
|
1941
|
+
if (IsHotEdge(e1) && IsHotEdge(e2))
|
|
1942
|
+
{
|
|
1943
|
+
if ((old_e1_windcnt != 0 && old_e1_windcnt != 1) || (old_e2_windcnt != 0 && old_e2_windcnt != 1) ||
|
|
1944
|
+
(e1.local_min->polytype != e2.local_min->polytype && cliptype_ != ClipType::Xor))
|
|
1945
|
+
{
|
|
1946
|
+
#ifdef USINGZ
|
|
1947
|
+
resultOp = AddLocalMaxPoly(e1, e2, pt);
|
|
1948
|
+
if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt);
|
|
1949
|
+
#else
|
|
1950
|
+
AddLocalMaxPoly(e1, e2, pt);
|
|
1951
|
+
#endif
|
|
1952
|
+
}
|
|
1953
|
+
else if (IsFront(e1) || (e1.outrec == e2.outrec))
|
|
1954
|
+
{
|
|
1955
|
+
//this 'else if' condition isn't strictly needed but
|
|
1956
|
+
//it's sensible to split polygons that only touch at
|
|
1957
|
+
//a common vertex (not at common edges).
|
|
1958
|
+
|
|
1959
|
+
#ifdef USINGZ
|
|
1960
|
+
resultOp = AddLocalMaxPoly(e1, e2, pt);
|
|
1961
|
+
OutPt* op2 = AddLocalMinPoly(e1, e2, pt);
|
|
1962
|
+
if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt);
|
|
1963
|
+
if (zCallback_) SetZ(e1, e2, op2->pt);
|
|
1964
|
+
#else
|
|
1965
|
+
AddLocalMaxPoly(e1, e2, pt);
|
|
1966
|
+
AddLocalMinPoly(e1, e2, pt);
|
|
1967
|
+
#endif
|
|
1968
|
+
}
|
|
1969
|
+
else
|
|
1970
|
+
{
|
|
1971
|
+
#ifdef USINGZ
|
|
1972
|
+
resultOp = AddOutPt(e1, pt);
|
|
1973
|
+
OutPt* op2 = AddOutPt(e2, pt);
|
|
1974
|
+
if (zCallback_)
|
|
1975
|
+
{
|
|
1976
|
+
SetZ(e1, e2, resultOp->pt);
|
|
1977
|
+
SetZ(e1, e2, op2->pt);
|
|
1978
|
+
}
|
|
1979
|
+
#else
|
|
1980
|
+
AddOutPt(e1, pt);
|
|
1981
|
+
AddOutPt(e2, pt);
|
|
1982
|
+
#endif
|
|
1983
|
+
SwapOutrecs(e1, e2);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
else if (IsHotEdge(e1))
|
|
1987
|
+
{
|
|
1988
|
+
#ifdef USINGZ
|
|
1989
|
+
resultOp = AddOutPt(e1, pt);
|
|
1990
|
+
if (zCallback_) SetZ(e1, e2, resultOp->pt);
|
|
1991
|
+
#else
|
|
1992
|
+
AddOutPt(e1, pt);
|
|
1993
|
+
#endif
|
|
1994
|
+
SwapOutrecs(e1, e2);
|
|
1995
|
+
}
|
|
1996
|
+
else if (IsHotEdge(e2))
|
|
1997
|
+
{
|
|
1998
|
+
#ifdef USINGZ
|
|
1999
|
+
resultOp = AddOutPt(e2, pt);
|
|
2000
|
+
if (zCallback_) SetZ(e1, e2, resultOp->pt);
|
|
2001
|
+
#else
|
|
2002
|
+
AddOutPt(e2, pt);
|
|
2003
|
+
#endif
|
|
2004
|
+
SwapOutrecs(e1, e2);
|
|
2005
|
+
}
|
|
2006
|
+
else
|
|
2007
|
+
{
|
|
2008
|
+
int64_t e1Wc2, e2Wc2;
|
|
2009
|
+
switch (fillrule_)
|
|
2010
|
+
{
|
|
2011
|
+
case FillRule::EvenOdd:
|
|
2012
|
+
case FillRule::NonZero:
|
|
2013
|
+
e1Wc2 = abs(e1.wind_cnt2);
|
|
2014
|
+
e2Wc2 = abs(e2.wind_cnt2);
|
|
2015
|
+
break;
|
|
2016
|
+
default:
|
|
2017
|
+
if (fillrule_ == fillpos)
|
|
2018
|
+
{
|
|
2019
|
+
e1Wc2 = e1.wind_cnt2;
|
|
2020
|
+
e2Wc2 = e2.wind_cnt2;
|
|
2021
|
+
}
|
|
2022
|
+
else
|
|
2023
|
+
{
|
|
2024
|
+
e1Wc2 = -e1.wind_cnt2;
|
|
2025
|
+
e2Wc2 = -e2.wind_cnt2;
|
|
2026
|
+
}
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
if (!IsSamePolyType(e1, e2))
|
|
2031
|
+
{
|
|
2032
|
+
#ifdef USINGZ
|
|
2033
|
+
resultOp = AddLocalMinPoly(e1, e2, pt, false);
|
|
2034
|
+
if (zCallback_) SetZ(e1, e2, resultOp->pt);
|
|
2035
|
+
#else
|
|
2036
|
+
AddLocalMinPoly(e1, e2, pt, false);
|
|
2037
|
+
#endif
|
|
2038
|
+
}
|
|
2039
|
+
else if (old_e1_windcnt == 1 && old_e2_windcnt == 1)
|
|
2040
|
+
{
|
|
2041
|
+
#ifdef USINGZ
|
|
2042
|
+
resultOp = nullptr;
|
|
2043
|
+
#endif
|
|
2044
|
+
switch (cliptype_)
|
|
2045
|
+
{
|
|
2046
|
+
case ClipType::Union:
|
|
2047
|
+
if (e1Wc2 <= 0 && e2Wc2 <= 0)
|
|
2048
|
+
#ifdef USINGZ
|
|
2049
|
+
resultOp = AddLocalMinPoly(e1, e2, pt, false);
|
|
2050
|
+
#else
|
|
2051
|
+
AddLocalMinPoly(e1, e2, pt, false);
|
|
2052
|
+
#endif
|
|
2053
|
+
break;
|
|
2054
|
+
case ClipType::Difference:
|
|
2055
|
+
if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
|
|
2056
|
+
((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
|
|
2057
|
+
{
|
|
2058
|
+
#ifdef USINGZ
|
|
2059
|
+
resultOp = AddLocalMinPoly(e1, e2, pt, false);
|
|
2060
|
+
#else
|
|
2061
|
+
AddLocalMinPoly(e1, e2, pt, false);
|
|
2062
|
+
#endif
|
|
2063
|
+
}
|
|
2064
|
+
break;
|
|
2065
|
+
case ClipType::Xor:
|
|
2066
|
+
#ifdef USINGZ
|
|
2067
|
+
resultOp = AddLocalMinPoly(e1, e2, pt, false);
|
|
2068
|
+
#else
|
|
2069
|
+
AddLocalMinPoly(e1, e2, pt, false);
|
|
2070
|
+
#endif
|
|
2071
|
+
break;
|
|
2072
|
+
default:
|
|
2073
|
+
if (e1Wc2 > 0 && e2Wc2 > 0)
|
|
2074
|
+
#ifdef USINGZ
|
|
2075
|
+
resultOp = AddLocalMinPoly(e1, e2, pt, false);
|
|
2076
|
+
#else
|
|
2077
|
+
AddLocalMinPoly(e1, e2, pt, false);
|
|
2078
|
+
#endif
|
|
2079
|
+
break;
|
|
2080
|
+
}
|
|
2081
|
+
#ifdef USINGZ
|
|
2082
|
+
if (resultOp && zCallback_) SetZ(e1, e2, resultOp->pt);
|
|
2083
|
+
#endif
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
inline void ClipperBase::DeleteFromAEL(Active& e)
|
|
2089
|
+
{
|
|
2090
|
+
Active* prev = e.prev_in_ael;
|
|
2091
|
+
Active* next = e.next_in_ael;
|
|
2092
|
+
if (!prev && !next && (&e != actives_)) return; // already deleted
|
|
2093
|
+
if (prev)
|
|
2094
|
+
prev->next_in_ael = next;
|
|
2095
|
+
else
|
|
2096
|
+
actives_ = next;
|
|
2097
|
+
if (next) next->prev_in_ael = prev;
|
|
2098
|
+
delete& e;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
|
|
2102
|
+
inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y)
|
|
2103
|
+
{
|
|
2104
|
+
Active* e = actives_;
|
|
2105
|
+
sel_ = e;
|
|
2106
|
+
while (e)
|
|
2107
|
+
{
|
|
2108
|
+
e->prev_in_sel = e->prev_in_ael;
|
|
2109
|
+
e->next_in_sel = e->next_in_ael;
|
|
2110
|
+
e->jump = e->next_in_sel;
|
|
2111
|
+
// it is safe to ignore 'joined' edges here because
|
|
2112
|
+
// if necessary they will be split in IntersectEdges()
|
|
2113
|
+
e->curr_x = TopX(*e, top_y);
|
|
2114
|
+
e = e->next_in_ael;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees)
|
|
2119
|
+
{
|
|
2120
|
+
cliptype_ = ct;
|
|
2121
|
+
fillrule_ = fillrule;
|
|
2122
|
+
using_polytree_ = use_polytrees;
|
|
2123
|
+
Reset();
|
|
2124
|
+
int64_t y;
|
|
2125
|
+
if (ct == ClipType::NoClip || !PopScanline(y)) return true;
|
|
2126
|
+
|
|
2127
|
+
while (succeeded_)
|
|
2128
|
+
{
|
|
2129
|
+
InsertLocalMinimaIntoAEL(y);
|
|
2130
|
+
Active* e;
|
|
2131
|
+
while (PopHorz(e)) DoHorizontal(*e);
|
|
2132
|
+
if (horz_seg_list_.size() > 0)
|
|
2133
|
+
{
|
|
2134
|
+
ConvertHorzSegsToJoins();
|
|
2135
|
+
horz_seg_list_.clear();
|
|
2136
|
+
}
|
|
2137
|
+
bot_y_ = y; // bot_y_ == bottom of scanbeam
|
|
2138
|
+
if (!PopScanline(y)) break; // y new top of scanbeam
|
|
2139
|
+
DoIntersections(y);
|
|
2140
|
+
DoTopOfScanbeam(y);
|
|
2141
|
+
while (PopHorz(e)) DoHorizontal(*e);
|
|
2142
|
+
}
|
|
2143
|
+
if (succeeded_) ProcessHorzJoins();
|
|
2144
|
+
return succeeded_;
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
inline void FixOutRecPts(OutRec* outrec)
|
|
2148
|
+
{
|
|
2149
|
+
OutPt* op = outrec->pts;
|
|
2150
|
+
do {
|
|
2151
|
+
op->outrec = outrec;
|
|
2152
|
+
op = op->next;
|
|
2153
|
+
} while (op != outrec->pts);
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
inline bool SetHorzSegHeadingForward(HorzSegment& hs, OutPt* opP, OutPt* opN)
|
|
2157
|
+
{
|
|
2158
|
+
if (opP->pt.x == opN->pt.x) return false;
|
|
2159
|
+
if (opP->pt.x < opN->pt.x)
|
|
2160
|
+
{
|
|
2161
|
+
hs.left_op = opP;
|
|
2162
|
+
hs.right_op = opN;
|
|
2163
|
+
hs.left_to_right = true;
|
|
2164
|
+
}
|
|
2165
|
+
else
|
|
2166
|
+
{
|
|
2167
|
+
hs.left_op = opN;
|
|
2168
|
+
hs.right_op = opP;
|
|
2169
|
+
hs.left_to_right = false;
|
|
2170
|
+
}
|
|
2171
|
+
return true;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
inline bool UpdateHorzSegment(HorzSegment& hs)
|
|
2175
|
+
{
|
|
2176
|
+
OutPt* op = hs.left_op;
|
|
2177
|
+
OutRec* outrec = GetRealOutRec(op->outrec);
|
|
2178
|
+
bool outrecHasEdges = outrec->front_edge;
|
|
2179
|
+
int64_t curr_y = op->pt.y;
|
|
2180
|
+
OutPt* opP = op, * opN = op;
|
|
2181
|
+
if (outrecHasEdges)
|
|
2182
|
+
{
|
|
2183
|
+
OutPt* opA = outrec->pts, * opZ = opA->next;
|
|
2184
|
+
while (opP != opZ && opP->prev->pt.y == curr_y)
|
|
2185
|
+
opP = opP->prev;
|
|
2186
|
+
while (opN != opA && opN->next->pt.y == curr_y)
|
|
2187
|
+
opN = opN->next;
|
|
2188
|
+
}
|
|
2189
|
+
else
|
|
2190
|
+
{
|
|
2191
|
+
while (opP->prev != opN && opP->prev->pt.y == curr_y)
|
|
2192
|
+
opP = opP->prev;
|
|
2193
|
+
while (opN->next != opP && opN->next->pt.y == curr_y)
|
|
2194
|
+
opN = opN->next;
|
|
2195
|
+
}
|
|
2196
|
+
bool result =
|
|
2197
|
+
SetHorzSegHeadingForward(hs, opP, opN) &&
|
|
2198
|
+
!hs.left_op->horz;
|
|
2199
|
+
|
|
2200
|
+
if (result)
|
|
2201
|
+
hs.left_op->horz = &hs;
|
|
2202
|
+
else
|
|
2203
|
+
hs.right_op = nullptr; // (for sorting)
|
|
2204
|
+
return result;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
void ClipperBase::ConvertHorzSegsToJoins()
|
|
2208
|
+
{
|
|
2209
|
+
auto j = std::count_if(horz_seg_list_.begin(),
|
|
2210
|
+
horz_seg_list_.end(),
|
|
2211
|
+
[](HorzSegment& hs) { return UpdateHorzSegment(hs); });
|
|
2212
|
+
if (j < 2) return;
|
|
2213
|
+
|
|
2214
|
+
std::stable_sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter());
|
|
2215
|
+
|
|
2216
|
+
HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2;
|
|
2217
|
+
HorzSegmentList::iterator hs_end = hs1 +j;
|
|
2218
|
+
HorzSegmentList::iterator hs_end1 = hs_end - 1;
|
|
2219
|
+
|
|
2220
|
+
for (; hs1 != hs_end1; ++hs1)
|
|
2221
|
+
{
|
|
2222
|
+
for (hs2 = hs1 + 1; hs2 != hs_end; ++hs2)
|
|
2223
|
+
{
|
|
2224
|
+
if ((hs2->left_op->pt.x >= hs1->right_op->pt.x) ||
|
|
2225
|
+
(hs2->left_to_right == hs1->left_to_right) ||
|
|
2226
|
+
(hs2->right_op->pt.x <= hs1->left_op->pt.x)) continue;
|
|
2227
|
+
int64_t curr_y = hs1->left_op->pt.y;
|
|
2228
|
+
if (hs1->left_to_right)
|
|
2229
|
+
{
|
|
2230
|
+
while (hs1->left_op->next->pt.y == curr_y &&
|
|
2231
|
+
hs1->left_op->next->pt.x <= hs2->left_op->pt.x)
|
|
2232
|
+
hs1->left_op = hs1->left_op->next;
|
|
2233
|
+
while (hs2->left_op->prev->pt.y == curr_y &&
|
|
2234
|
+
hs2->left_op->prev->pt.x <= hs1->left_op->pt.x)
|
|
2235
|
+
hs2->left_op = hs2->left_op->prev;
|
|
2236
|
+
HorzJoin join = HorzJoin(
|
|
2237
|
+
DuplicateOp(hs1->left_op, true),
|
|
2238
|
+
DuplicateOp(hs2->left_op, false));
|
|
2239
|
+
horz_join_list_.emplace_back(join);
|
|
2240
|
+
}
|
|
2241
|
+
else
|
|
2242
|
+
{
|
|
2243
|
+
while (hs1->left_op->prev->pt.y == curr_y &&
|
|
2244
|
+
hs1->left_op->prev->pt.x <= hs2->left_op->pt.x)
|
|
2245
|
+
hs1->left_op = hs1->left_op->prev;
|
|
2246
|
+
while (hs2->left_op->next->pt.y == curr_y &&
|
|
2247
|
+
hs2->left_op->next->pt.x <= hs1->left_op->pt.x)
|
|
2248
|
+
hs2->left_op = hs2->left_op->next;
|
|
2249
|
+
HorzJoin join = HorzJoin(
|
|
2250
|
+
DuplicateOp(hs2->left_op, true),
|
|
2251
|
+
DuplicateOp(hs1->left_op, false));
|
|
2252
|
+
horz_join_list_.emplace_back(join);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
void MoveSplits(OutRec* fromOr, OutRec* toOr)
|
|
2259
|
+
{
|
|
2260
|
+
if (!toOr->splits) toOr->splits = new OutRecList();
|
|
2261
|
+
OutRecList::iterator orIter = fromOr->splits->begin();
|
|
2262
|
+
for (; orIter != fromOr->splits->end(); ++orIter)
|
|
2263
|
+
if (toOr != *orIter) // #987
|
|
2264
|
+
toOr->splits->emplace_back(*orIter);
|
|
2265
|
+
fromOr->splits->clear();
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
void ClipperBase::ProcessHorzJoins()
|
|
2269
|
+
{
|
|
2270
|
+
for (const HorzJoin& j : horz_join_list_)
|
|
2271
|
+
{
|
|
2272
|
+
OutRec* or1 = GetRealOutRec(j.op1->outrec);
|
|
2273
|
+
OutRec* or2 = GetRealOutRec(j.op2->outrec);
|
|
2274
|
+
|
|
2275
|
+
OutPt* op1b = j.op1->next;
|
|
2276
|
+
OutPt* op2b = j.op2->prev;
|
|
2277
|
+
j.op1->next = j.op2;
|
|
2278
|
+
j.op2->prev = j.op1;
|
|
2279
|
+
op1b->prev = op2b;
|
|
2280
|
+
op2b->next = op1b;
|
|
2281
|
+
|
|
2282
|
+
if (or1 == or2) // 'join' is really a split
|
|
2283
|
+
{
|
|
2284
|
+
or2 = NewOutRec();
|
|
2285
|
+
or2->pts = op1b;
|
|
2286
|
+
FixOutRecPts(or2);
|
|
2287
|
+
|
|
2288
|
+
//if or1->pts has moved to or2 then update or1->pts!!
|
|
2289
|
+
if (or1->pts->outrec == or2)
|
|
2290
|
+
{
|
|
2291
|
+
or1->pts = j.op1;
|
|
2292
|
+
or1->pts->outrec = or1;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
if (using_polytree_) //#498, #520, #584, D#576, #618
|
|
2296
|
+
{
|
|
2297
|
+
if (Path2ContainsPath1(or1->pts, or2->pts))
|
|
2298
|
+
{
|
|
2299
|
+
//swap or1's & or2's pts
|
|
2300
|
+
OutPt* tmp = or1->pts;
|
|
2301
|
+
or1->pts = or2->pts;
|
|
2302
|
+
or2->pts = tmp;
|
|
2303
|
+
FixOutRecPts(or1);
|
|
2304
|
+
FixOutRecPts(or2);
|
|
2305
|
+
//or2 is now inside or1
|
|
2306
|
+
or2->owner = or1;
|
|
2307
|
+
}
|
|
2308
|
+
else if (Path2ContainsPath1(or2->pts, or1->pts))
|
|
2309
|
+
{
|
|
2310
|
+
or2->owner = or1;
|
|
2311
|
+
}
|
|
2312
|
+
else
|
|
2313
|
+
or2->owner = or1->owner;
|
|
2314
|
+
|
|
2315
|
+
if (!or1->splits) or1->splits = new OutRecList();
|
|
2316
|
+
or1->splits->emplace_back(or2);
|
|
2317
|
+
}
|
|
2318
|
+
else
|
|
2319
|
+
or2->owner = or1;
|
|
2320
|
+
}
|
|
2321
|
+
else // joining, not splitting
|
|
2322
|
+
{
|
|
2323
|
+
or2->pts = nullptr;
|
|
2324
|
+
if (using_polytree_)
|
|
2325
|
+
{
|
|
2326
|
+
SetOwner(or2, or1);
|
|
2327
|
+
if (or2->splits)
|
|
2328
|
+
MoveSplits(or2, or1); //#618
|
|
2329
|
+
}
|
|
2330
|
+
else
|
|
2331
|
+
or2->owner = or1;
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
void ClipperBase::DoIntersections(const int64_t top_y)
|
|
2337
|
+
{
|
|
2338
|
+
if (BuildIntersectList(top_y))
|
|
2339
|
+
{
|
|
2340
|
+
ProcessIntersectList();
|
|
2341
|
+
intersect_nodes_.clear();
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y)
|
|
2346
|
+
{
|
|
2347
|
+
Point64 ip;
|
|
2348
|
+
if (!GetLineIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip))
|
|
2349
|
+
ip = Point64(e1.curr_x, top_y); //parallel edges
|
|
2350
|
+
|
|
2351
|
+
//rounding errors can occasionally place the calculated intersection
|
|
2352
|
+
//point either below or above the scanbeam, so check and correct ...
|
|
2353
|
+
if (ip.y > bot_y_ || ip.y < top_y)
|
|
2354
|
+
{
|
|
2355
|
+
double abs_dx1 = std::fabs(e1.dx);
|
|
2356
|
+
double abs_dx2 = std::fabs(e2.dx);
|
|
2357
|
+
if (abs_dx1 > 100 && abs_dx2 > 100)
|
|
2358
|
+
{
|
|
2359
|
+
if (abs_dx1 > abs_dx2)
|
|
2360
|
+
ip = GetClosestPointOnSegment(ip, e1.bot, e1.top);
|
|
2361
|
+
else
|
|
2362
|
+
ip = GetClosestPointOnSegment(ip, e2.bot, e2.top);
|
|
2363
|
+
}
|
|
2364
|
+
else if (abs_dx1 > 100)
|
|
2365
|
+
ip = GetClosestPointOnSegment(ip, e1.bot, e1.top);
|
|
2366
|
+
else if (abs_dx2 > 100)
|
|
2367
|
+
ip = GetClosestPointOnSegment(ip, e2.bot, e2.top);
|
|
2368
|
+
else
|
|
2369
|
+
{
|
|
2370
|
+
if (ip.y < top_y) ip.y = top_y;
|
|
2371
|
+
else ip.y = bot_y_;
|
|
2372
|
+
if (abs_dx1 < abs_dx2) ip.x = TopX(e1, ip.y);
|
|
2373
|
+
else ip.x = TopX(e2, ip.y);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
intersect_nodes_.emplace_back(&e1, &e2, ip);
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
bool ClipperBase::BuildIntersectList(const int64_t top_y)
|
|
2380
|
+
{
|
|
2381
|
+
if (!actives_ || !actives_->next_in_ael) return false;
|
|
2382
|
+
|
|
2383
|
+
//Calculate edge positions at the top of the current scanbeam, and from this
|
|
2384
|
+
//we will determine the intersections required to reach these new positions.
|
|
2385
|
+
AdjustCurrXAndCopyToSEL(top_y);
|
|
2386
|
+
//Find all edge intersections in the current scanbeam using a stable merge
|
|
2387
|
+
//sort that ensures only adjacent edges are intersecting. Intersect info is
|
|
2388
|
+
//stored in FIntersectList ready to be processed in ProcessIntersectList.
|
|
2389
|
+
//Re merge sorts see https://stackoverflow.com/a/46319131/359538
|
|
2390
|
+
|
|
2391
|
+
Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp;
|
|
2392
|
+
|
|
2393
|
+
while (left && left->jump)
|
|
2394
|
+
{
|
|
2395
|
+
Active* prev_base = nullptr;
|
|
2396
|
+
while (left && left->jump)
|
|
2397
|
+
{
|
|
2398
|
+
curr_base = left;
|
|
2399
|
+
right = left->jump;
|
|
2400
|
+
l_end = right;
|
|
2401
|
+
r_end = right->jump;
|
|
2402
|
+
left->jump = r_end;
|
|
2403
|
+
while (left != l_end && right != r_end)
|
|
2404
|
+
{
|
|
2405
|
+
if (right->curr_x < left->curr_x)
|
|
2406
|
+
{
|
|
2407
|
+
tmp = right->prev_in_sel;
|
|
2408
|
+
for (; ; )
|
|
2409
|
+
{
|
|
2410
|
+
AddNewIntersectNode(*tmp, *right, top_y);
|
|
2411
|
+
if (tmp == left) break;
|
|
2412
|
+
tmp = tmp->prev_in_sel;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
tmp = right;
|
|
2416
|
+
right = ExtractFromSEL(tmp);
|
|
2417
|
+
l_end = right;
|
|
2418
|
+
Insert1Before2InSEL(tmp, left);
|
|
2419
|
+
if (left == curr_base)
|
|
2420
|
+
{
|
|
2421
|
+
curr_base = tmp;
|
|
2422
|
+
curr_base->jump = r_end;
|
|
2423
|
+
if (!prev_base) sel_ = curr_base;
|
|
2424
|
+
else prev_base->jump = curr_base;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
else left = left->next_in_sel;
|
|
2428
|
+
}
|
|
2429
|
+
prev_base = curr_base;
|
|
2430
|
+
left = r_end;
|
|
2431
|
+
}
|
|
2432
|
+
left = sel_;
|
|
2433
|
+
}
|
|
2434
|
+
return intersect_nodes_.size() > 0;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
void ClipperBase::ProcessIntersectList()
|
|
2438
|
+
{
|
|
2439
|
+
//We now have a list of intersections required so that edges will be
|
|
2440
|
+
//correctly positioned at the top of the scanbeam. However, it's important
|
|
2441
|
+
//that edge intersections are processed from the bottom up, but it's also
|
|
2442
|
+
//crucial that intersections only occur between adjacent edges.
|
|
2443
|
+
|
|
2444
|
+
//First we do a quicksort so intersections proceed in a bottom up order ...
|
|
2445
|
+
std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort);
|
|
2446
|
+
//Now as we process these intersections, we must sometimes adjust the order
|
|
2447
|
+
//to ensure that intersecting edges are always adjacent ...
|
|
2448
|
+
|
|
2449
|
+
IntersectNodeList::iterator node_iter, node_iter2;
|
|
2450
|
+
for (node_iter = intersect_nodes_.begin();
|
|
2451
|
+
node_iter != intersect_nodes_.end(); ++node_iter)
|
|
2452
|
+
{
|
|
2453
|
+
if (!EdgesAdjacentInAEL(*node_iter))
|
|
2454
|
+
{
|
|
2455
|
+
node_iter2 = node_iter + 1;
|
|
2456
|
+
while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2;
|
|
2457
|
+
std::swap(*node_iter, *node_iter2);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
IntersectNode& node = *node_iter;
|
|
2461
|
+
IntersectEdges(*node.edge1, *node.edge2, node.pt);
|
|
2462
|
+
SwapPositionsInAEL(*node.edge1, *node.edge2);
|
|
2463
|
+
|
|
2464
|
+
node.edge1->curr_x = node.pt.x;
|
|
2465
|
+
node.edge2->curr_x = node.pt.x;
|
|
2466
|
+
CheckJoinLeft(*node.edge2, node.pt, true);
|
|
2467
|
+
CheckJoinRight(*node.edge1, node.pt, true);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2)
|
|
2472
|
+
{
|
|
2473
|
+
//preconditon: e1 must be immediately to the left of e2
|
|
2474
|
+
Active* next = e2.next_in_ael;
|
|
2475
|
+
if (next) next->prev_in_ael = &e1;
|
|
2476
|
+
Active* prev = e1.prev_in_ael;
|
|
2477
|
+
if (prev) prev->next_in_ael = &e2;
|
|
2478
|
+
e2.prev_in_ael = prev;
|
|
2479
|
+
e2.next_in_ael = &e1;
|
|
2480
|
+
e1.prev_in_ael = &e2;
|
|
2481
|
+
e1.next_in_ael = next;
|
|
2482
|
+
if (!e2.prev_in_ael) actives_ = &e2;
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
inline OutPt* GetLastOp(const Active& hot_edge)
|
|
2486
|
+
{
|
|
2487
|
+
OutRec* outrec = hot_edge.outrec;
|
|
2488
|
+
OutPt* result = outrec->pts;
|
|
2489
|
+
if (&hot_edge != outrec->front_edge)
|
|
2490
|
+
result = result->next;
|
|
2491
|
+
return result;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
void ClipperBase::AddTrialHorzJoin(OutPt* op)
|
|
2495
|
+
{
|
|
2496
|
+
if (op->outrec->is_open) return;
|
|
2497
|
+
horz_seg_list_.emplace_back(op);
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
bool ClipperBase::ResetHorzDirection(const Active& horz,
|
|
2501
|
+
const Vertex* max_vertex, int64_t& horz_left, int64_t& horz_right)
|
|
2502
|
+
{
|
|
2503
|
+
if (horz.bot.x == horz.top.x)
|
|
2504
|
+
{
|
|
2505
|
+
//the horizontal edge is going nowhere ...
|
|
2506
|
+
horz_left = horz.curr_x;
|
|
2507
|
+
horz_right = horz.curr_x;
|
|
2508
|
+
Active* e = horz.next_in_ael;
|
|
2509
|
+
while (e && e->vertex_top != max_vertex) e = e->next_in_ael;
|
|
2510
|
+
return e != nullptr;
|
|
2511
|
+
}
|
|
2512
|
+
else if (horz.curr_x < horz.top.x)
|
|
2513
|
+
{
|
|
2514
|
+
horz_left = horz.curr_x;
|
|
2515
|
+
horz_right = horz.top.x;
|
|
2516
|
+
return true;
|
|
2517
|
+
}
|
|
2518
|
+
else
|
|
2519
|
+
{
|
|
2520
|
+
horz_left = horz.top.x;
|
|
2521
|
+
horz_right = horz.curr_x;
|
|
2522
|
+
return false; // right to left
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
void ClipperBase::DoHorizontal(Active& horz)
|
|
2527
|
+
/*******************************************************************************
|
|
2528
|
+
* Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or *
|
|
2529
|
+
* bottom of a scanbeam) are processed as if layered.The order in which HEs *
|
|
2530
|
+
* are processed doesn't matter. HEs intersect with the bottom vertices of *
|
|
2531
|
+
* other HEs[#] and with non-horizontal edges [*]. Once these intersections *
|
|
2532
|
+
* are completed, intermediate HEs are 'promoted' to the next edge in their *
|
|
2533
|
+
* bounds, and they in turn may be intersected[%] by other HEs. *
|
|
2534
|
+
* *
|
|
2535
|
+
* eg: 3 horizontals at a scanline: / | / / *
|
|
2536
|
+
* | / | (HE3)o ========%========== o *
|
|
2537
|
+
* o ======= o(HE2) / | / / *
|
|
2538
|
+
* o ============#=========*======*========#=========o (HE1) *
|
|
2539
|
+
* / | / | / *
|
|
2540
|
+
*******************************************************************************/
|
|
2541
|
+
{
|
|
2542
|
+
Point64 pt;
|
|
2543
|
+
bool horzIsOpen = IsOpen(horz);
|
|
2544
|
+
int64_t y = horz.bot.y;
|
|
2545
|
+
Vertex* vertex_max;
|
|
2546
|
+
if (horzIsOpen)
|
|
2547
|
+
vertex_max = GetCurrYMaximaVertex_Open(horz);
|
|
2548
|
+
else
|
|
2549
|
+
vertex_max = GetCurrYMaximaVertex(horz);
|
|
2550
|
+
|
|
2551
|
+
//// remove 180 deg.spikes and also simplify
|
|
2552
|
+
//// consecutive horizontals when PreserveCollinear = true
|
|
2553
|
+
//if (!horzIsOpen && vertex_max != horz.vertex_top)
|
|
2554
|
+
// TrimHorz(horz, PreserveCollinear);
|
|
2555
|
+
|
|
2556
|
+
int64_t horz_left, horz_right;
|
|
2557
|
+
bool is_left_to_right =
|
|
2558
|
+
ResetHorzDirection(horz, vertex_max, horz_left, horz_right);
|
|
2559
|
+
|
|
2560
|
+
if (IsHotEdge(horz))
|
|
2561
|
+
{
|
|
2562
|
+
#ifdef USINGZ
|
|
2563
|
+
OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y, horz.bot.z));
|
|
2564
|
+
#else
|
|
2565
|
+
OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y));
|
|
2566
|
+
#endif
|
|
2567
|
+
AddTrialHorzJoin(op);
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
while (true) // loop through consec. horizontal edges
|
|
2571
|
+
{
|
|
2572
|
+
Active* e;
|
|
2573
|
+
if (is_left_to_right) e = horz.next_in_ael;
|
|
2574
|
+
else e = horz.prev_in_ael;
|
|
2575
|
+
|
|
2576
|
+
while (e)
|
|
2577
|
+
{
|
|
2578
|
+
if (e->vertex_top == vertex_max)
|
|
2579
|
+
{
|
|
2580
|
+
if (IsHotEdge(horz) && IsJoined(*e))
|
|
2581
|
+
Split(*e, e->top);
|
|
2582
|
+
|
|
2583
|
+
//if (IsHotEdge(horz) != IsHotEdge(*e))
|
|
2584
|
+
// DoError(undefined_error_i);
|
|
2585
|
+
|
|
2586
|
+
if (IsHotEdge(horz))
|
|
2587
|
+
{
|
|
2588
|
+
while (horz.vertex_top != vertex_max)
|
|
2589
|
+
{
|
|
2590
|
+
AddOutPt(horz, horz.top);
|
|
2591
|
+
UpdateEdgeIntoAEL(&horz);
|
|
2592
|
+
}
|
|
2593
|
+
if (is_left_to_right)
|
|
2594
|
+
AddLocalMaxPoly(horz, *e, horz.top);
|
|
2595
|
+
else
|
|
2596
|
+
AddLocalMaxPoly(*e, horz, horz.top);
|
|
2597
|
+
}
|
|
2598
|
+
DeleteFromAEL(*e);
|
|
2599
|
+
DeleteFromAEL(horz);
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
//if horzEdge is a maxima, keep going until we reach
|
|
2604
|
+
//its maxima pair, otherwise check for break conditions
|
|
2605
|
+
if (vertex_max != horz.vertex_top || IsOpenEnd(horz))
|
|
2606
|
+
{
|
|
2607
|
+
//otherwise stop when 'ae' is beyond the end of the horizontal line
|
|
2608
|
+
if ((is_left_to_right && e->curr_x > horz_right) ||
|
|
2609
|
+
(!is_left_to_right && e->curr_x < horz_left)) break;
|
|
2610
|
+
|
|
2611
|
+
if (e->curr_x == horz.top.x && !IsHorizontal(*e))
|
|
2612
|
+
{
|
|
2613
|
+
pt = NextVertex(horz)->pt;
|
|
2614
|
+
if (is_left_to_right)
|
|
2615
|
+
{
|
|
2616
|
+
//with open paths we'll only break once past horz's end
|
|
2617
|
+
if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e))
|
|
2618
|
+
{
|
|
2619
|
+
if (TopX(*e, pt.y) > pt.x) break;
|
|
2620
|
+
}
|
|
2621
|
+
//otherwise we'll only break when horz's outslope is greater than e's
|
|
2622
|
+
else if (TopX(*e, pt.y) >= pt.x) break;
|
|
2623
|
+
}
|
|
2624
|
+
else
|
|
2625
|
+
{
|
|
2626
|
+
if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e))
|
|
2627
|
+
{
|
|
2628
|
+
if (TopX(*e, pt.y) < pt.x) break;
|
|
2629
|
+
}
|
|
2630
|
+
else if (TopX(*e, pt.y) <= pt.x) break;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
pt = Point64(e->curr_x, horz.bot.y);
|
|
2636
|
+
if (is_left_to_right)
|
|
2637
|
+
{
|
|
2638
|
+
IntersectEdges(horz, *e, pt);
|
|
2639
|
+
SwapPositionsInAEL(horz, *e);
|
|
2640
|
+
CheckJoinLeft(*e, pt);
|
|
2641
|
+
horz.curr_x = e->curr_x;
|
|
2642
|
+
e = horz.next_in_ael;
|
|
2643
|
+
}
|
|
2644
|
+
else
|
|
2645
|
+
{
|
|
2646
|
+
IntersectEdges(*e, horz, pt);
|
|
2647
|
+
SwapPositionsInAEL(*e, horz);
|
|
2648
|
+
CheckJoinRight(*e, pt);
|
|
2649
|
+
horz.curr_x = e->curr_x;
|
|
2650
|
+
e = horz.prev_in_ael;
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
if (horz.outrec)
|
|
2654
|
+
{
|
|
2655
|
+
//nb: The outrec containing the op returned by IntersectEdges
|
|
2656
|
+
//above may no longer be associated with horzEdge.
|
|
2657
|
+
AddTrialHorzJoin(GetLastOp(horz));
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
//check if we've finished with (consecutive) horizontals ...
|
|
2662
|
+
if (horzIsOpen && IsOpenEnd(horz)) // ie open at top
|
|
2663
|
+
{
|
|
2664
|
+
if (IsHotEdge(horz))
|
|
2665
|
+
{
|
|
2666
|
+
AddOutPt(horz, horz.top);
|
|
2667
|
+
if (IsFront(horz))
|
|
2668
|
+
horz.outrec->front_edge = nullptr;
|
|
2669
|
+
else
|
|
2670
|
+
horz.outrec->back_edge = nullptr;
|
|
2671
|
+
horz.outrec = nullptr;
|
|
2672
|
+
}
|
|
2673
|
+
DeleteFromAEL(horz);
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
else if (NextVertex(horz)->pt.y != horz.top.y)
|
|
2677
|
+
break;
|
|
2678
|
+
|
|
2679
|
+
//still more horizontals in bound to process ...
|
|
2680
|
+
if (IsHotEdge(horz))
|
|
2681
|
+
AddOutPt(horz, horz.top);
|
|
2682
|
+
UpdateEdgeIntoAEL(&horz);
|
|
2683
|
+
|
|
2684
|
+
is_left_to_right =
|
|
2685
|
+
ResetHorzDirection(horz, vertex_max, horz_left, horz_right);
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
if (IsHotEdge(horz))
|
|
2689
|
+
{
|
|
2690
|
+
OutPt* op = AddOutPt(horz, horz.top);
|
|
2691
|
+
AddTrialHorzJoin(op);
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
UpdateEdgeIntoAEL(&horz); // end of an intermediate horiz.
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
void ClipperBase::DoTopOfScanbeam(const int64_t y)
|
|
2698
|
+
{
|
|
2699
|
+
sel_ = nullptr; // sel_ is reused to flag horizontals (see PushHorz below)
|
|
2700
|
+
Active* e = actives_;
|
|
2701
|
+
while (e)
|
|
2702
|
+
{
|
|
2703
|
+
//nb: 'e' will never be horizontal here
|
|
2704
|
+
if (e->top.y == y)
|
|
2705
|
+
{
|
|
2706
|
+
e->curr_x = e->top.x;
|
|
2707
|
+
if (IsMaxima(*e))
|
|
2708
|
+
{
|
|
2709
|
+
e = DoMaxima(*e); // TOP OF BOUND (MAXIMA)
|
|
2710
|
+
continue;
|
|
2711
|
+
}
|
|
2712
|
+
else
|
|
2713
|
+
{
|
|
2714
|
+
//INTERMEDIATE VERTEX ...
|
|
2715
|
+
if (IsHotEdge(*e)) AddOutPt(*e, e->top);
|
|
2716
|
+
UpdateEdgeIntoAEL(e);
|
|
2717
|
+
if (IsHorizontal(*e))
|
|
2718
|
+
PushHorz(*e); // horizontals are processed later
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
else // i.e. not the top of the edge
|
|
2722
|
+
e->curr_x = TopX(*e, y);
|
|
2723
|
+
|
|
2724
|
+
e = e->next_in_ael;
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
|
|
2729
|
+
Active* ClipperBase::DoMaxima(Active& e)
|
|
2730
|
+
{
|
|
2731
|
+
Active* next_e, * prev_e, * max_pair;
|
|
2732
|
+
prev_e = e.prev_in_ael;
|
|
2733
|
+
next_e = e.next_in_ael;
|
|
2734
|
+
if (IsOpenEnd(e))
|
|
2735
|
+
{
|
|
2736
|
+
if (IsHotEdge(e)) AddOutPt(e, e.top);
|
|
2737
|
+
if (!IsHorizontal(e))
|
|
2738
|
+
{
|
|
2739
|
+
if (IsHotEdge(e))
|
|
2740
|
+
{
|
|
2741
|
+
if (IsFront(e))
|
|
2742
|
+
e.outrec->front_edge = nullptr;
|
|
2743
|
+
else
|
|
2744
|
+
e.outrec->back_edge = nullptr;
|
|
2745
|
+
e.outrec = nullptr;
|
|
2746
|
+
}
|
|
2747
|
+
DeleteFromAEL(e);
|
|
2748
|
+
}
|
|
2749
|
+
return next_e;
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
max_pair = GetMaximaPair(e);
|
|
2753
|
+
if (!max_pair) return next_e; // eMaxPair is horizontal
|
|
2754
|
+
|
|
2755
|
+
if (IsJoined(e)) Split(e, e.top);
|
|
2756
|
+
if (IsJoined(*max_pair)) Split(*max_pair, max_pair->top);
|
|
2757
|
+
|
|
2758
|
+
//only non-horizontal maxima here.
|
|
2759
|
+
//process any edges between maxima pair ...
|
|
2760
|
+
while (next_e != max_pair)
|
|
2761
|
+
{
|
|
2762
|
+
IntersectEdges(e, *next_e, e.top);
|
|
2763
|
+
SwapPositionsInAEL(e, *next_e);
|
|
2764
|
+
next_e = e.next_in_ael;
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
if (IsOpen(e))
|
|
2768
|
+
{
|
|
2769
|
+
if (IsHotEdge(e))
|
|
2770
|
+
AddLocalMaxPoly(e, *max_pair, e.top);
|
|
2771
|
+
DeleteFromAEL(*max_pair);
|
|
2772
|
+
DeleteFromAEL(e);
|
|
2773
|
+
return (prev_e ? prev_e->next_in_ael : actives_);
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
// e.next_in_ael== max_pair ...
|
|
2777
|
+
if (IsHotEdge(e))
|
|
2778
|
+
AddLocalMaxPoly(e, *max_pair, e.top);
|
|
2779
|
+
|
|
2780
|
+
DeleteFromAEL(e);
|
|
2781
|
+
DeleteFromAEL(*max_pair);
|
|
2782
|
+
return (prev_e ? prev_e->next_in_ael : actives_);
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
void ClipperBase::Split(Active& e, const Point64& pt)
|
|
2786
|
+
{
|
|
2787
|
+
if (e.join_with == JoinWith::Right)
|
|
2788
|
+
{
|
|
2789
|
+
e.join_with = JoinWith::NoJoin;
|
|
2790
|
+
e.next_in_ael->join_with = JoinWith::NoJoin;
|
|
2791
|
+
AddLocalMinPoly(e, *e.next_in_ael, pt, true);
|
|
2792
|
+
}
|
|
2793
|
+
else
|
|
2794
|
+
{
|
|
2795
|
+
e.join_with = JoinWith::NoJoin;
|
|
2796
|
+
e.prev_in_ael->join_with = JoinWith::NoJoin;
|
|
2797
|
+
AddLocalMinPoly(*e.prev_in_ael, e, pt, true);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
void ClipperBase::CheckJoinLeft(Active& e,
|
|
2802
|
+
const Point64& pt, bool check_curr_x)
|
|
2803
|
+
{
|
|
2804
|
+
Active* prev = e.prev_in_ael;
|
|
2805
|
+
if (!prev ||
|
|
2806
|
+
!IsHotEdge(e) || !IsHotEdge(*prev) ||
|
|
2807
|
+
IsHorizontal(e) || IsHorizontal(*prev) ||
|
|
2808
|
+
IsOpen(e) || IsOpen(*prev) ) return;
|
|
2809
|
+
if ((pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) &&
|
|
2810
|
+
((e.bot.y > pt.y) || (prev->bot.y > pt.y))) return; // avoid trivial joins
|
|
2811
|
+
|
|
2812
|
+
if (check_curr_x)
|
|
2813
|
+
{
|
|
2814
|
+
if (PerpendicDistFromLineSqrd(pt, prev->bot, prev->top) > 0.25) return;
|
|
2815
|
+
}
|
|
2816
|
+
else if (e.curr_x != prev->curr_x) return;
|
|
2817
|
+
if (!IsCollinear(e.top, pt, prev->top)) return;
|
|
2818
|
+
|
|
2819
|
+
if (e.outrec->idx == prev->outrec->idx)
|
|
2820
|
+
AddLocalMaxPoly(*prev, e, pt);
|
|
2821
|
+
else if (e.outrec->idx < prev->outrec->idx)
|
|
2822
|
+
JoinOutrecPaths(e, *prev);
|
|
2823
|
+
else
|
|
2824
|
+
JoinOutrecPaths(*prev, e);
|
|
2825
|
+
prev->join_with = JoinWith::Right;
|
|
2826
|
+
e.join_with = JoinWith::Left;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
void ClipperBase::CheckJoinRight(Active& e,
|
|
2830
|
+
const Point64& pt, bool check_curr_x)
|
|
2831
|
+
{
|
|
2832
|
+
Active* next = e.next_in_ael;
|
|
2833
|
+
if (!next ||
|
|
2834
|
+
!IsHotEdge(e) || !IsHotEdge(*next) ||
|
|
2835
|
+
IsHorizontal(e) || IsHorizontal(*next) ||
|
|
2836
|
+
IsOpen(e) || IsOpen(*next)) return;
|
|
2837
|
+
if ((pt.y < e.top.y +2 || pt.y < next->top.y +2) &&
|
|
2838
|
+
((e.bot.y > pt.y) || (next->bot.y > pt.y))) return; // avoid trivial joins
|
|
2839
|
+
|
|
2840
|
+
if (check_curr_x)
|
|
2841
|
+
{
|
|
2842
|
+
if (PerpendicDistFromLineSqrd(pt, next->bot, next->top) > 0.35) return;
|
|
2843
|
+
}
|
|
2844
|
+
else if (e.curr_x != next->curr_x) return;
|
|
2845
|
+
if (!IsCollinear(e.top, pt, next->top)) return;
|
|
2846
|
+
|
|
2847
|
+
if (e.outrec->idx == next->outrec->idx)
|
|
2848
|
+
AddLocalMaxPoly(e, *next, pt);
|
|
2849
|
+
else if (e.outrec->idx < next->outrec->idx)
|
|
2850
|
+
JoinOutrecPaths(e, *next);
|
|
2851
|
+
else
|
|
2852
|
+
JoinOutrecPaths(*next, e);
|
|
2853
|
+
|
|
2854
|
+
e.join_with = JoinWith::Right;
|
|
2855
|
+
next->join_with = JoinWith::Left;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2)
|
|
2859
|
+
{
|
|
2860
|
+
OutRec* outrec = GetRealOutRec(op->outrec);
|
|
2861
|
+
op2 = op;
|
|
2862
|
+
if (outrec->front_edge)
|
|
2863
|
+
{
|
|
2864
|
+
while (op->prev != outrec->pts &&
|
|
2865
|
+
op->prev->pt.y == op->pt.y) op = op->prev;
|
|
2866
|
+
while (op2 != outrec->pts &&
|
|
2867
|
+
op2->next->pt.y == op2->pt.y) op2 = op2->next;
|
|
2868
|
+
return op2 != op;
|
|
2869
|
+
}
|
|
2870
|
+
else
|
|
2871
|
+
{
|
|
2872
|
+
while (op->prev != op2 && op->prev->pt.y == op->pt.y)
|
|
2873
|
+
op = op->prev;
|
|
2874
|
+
while (op2->next != op && op2->next->pt.y == op2->pt.y)
|
|
2875
|
+
op2 = op2->next;
|
|
2876
|
+
return op2 != op && op2->next != op;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path)
|
|
2881
|
+
{
|
|
2882
|
+
if (!op || op->next == op || (!isOpen && op->next == op->prev))
|
|
2883
|
+
return false;
|
|
2884
|
+
|
|
2885
|
+
path.resize(0);
|
|
2886
|
+
Point64 lastPt;
|
|
2887
|
+
OutPt* op2;
|
|
2888
|
+
if (reverse)
|
|
2889
|
+
{
|
|
2890
|
+
lastPt = op->pt;
|
|
2891
|
+
op2 = op->prev;
|
|
2892
|
+
}
|
|
2893
|
+
else
|
|
2894
|
+
{
|
|
2895
|
+
op = op->next;
|
|
2896
|
+
lastPt = op->pt;
|
|
2897
|
+
op2 = op->next;
|
|
2898
|
+
}
|
|
2899
|
+
path.emplace_back(lastPt);
|
|
2900
|
+
|
|
2901
|
+
while (op2 != op)
|
|
2902
|
+
{
|
|
2903
|
+
if (op2->pt != lastPt)
|
|
2904
|
+
{
|
|
2905
|
+
lastPt = op2->pt;
|
|
2906
|
+
path.emplace_back(lastPt);
|
|
2907
|
+
}
|
|
2908
|
+
if (reverse)
|
|
2909
|
+
op2 = op2->prev;
|
|
2910
|
+
else
|
|
2911
|
+
op2 = op2->next;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
if (!isOpen && path.size() == 3 && IsVerySmallTriangle(*op2)) return false;
|
|
2915
|
+
else return true;
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
bool ClipperBase::CheckBounds(OutRec* outrec)
|
|
2919
|
+
{
|
|
2920
|
+
if (!outrec->pts) return false;
|
|
2921
|
+
if (!outrec->bounds.IsEmpty()) return true;
|
|
2922
|
+
CleanCollinear(outrec);
|
|
2923
|
+
if (!outrec->pts ||
|
|
2924
|
+
!BuildPath64(outrec->pts, reverse_solution_, false, outrec->path)){
|
|
2925
|
+
return false;}
|
|
2926
|
+
outrec->bounds = GetBounds(outrec->path);
|
|
2927
|
+
return true;
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits)
|
|
2931
|
+
{
|
|
2932
|
+
// nb: use indexing (not an iterator) in case 'splits' is modified inside this loop (#1029)
|
|
2933
|
+
for (size_t idx = 0; idx < splits->size(); ++idx)
|
|
2934
|
+
{
|
|
2935
|
+
OutRec* split = (*splits)[idx];
|
|
2936
|
+
if (!split->pts && split->splits &&
|
|
2937
|
+
CheckSplitOwner(outrec, split->splits)) return true; //#942
|
|
2938
|
+
split = GetRealOutRec(split);
|
|
2939
|
+
if (!split || split == outrec || split->recursive_split == outrec) continue;
|
|
2940
|
+
split->recursive_split = outrec; // prevent infinite loops
|
|
2941
|
+
|
|
2942
|
+
if (split->splits && CheckSplitOwner(outrec, split->splits))
|
|
2943
|
+
return true;
|
|
2944
|
+
|
|
2945
|
+
if (!CheckBounds(split) || !split->bounds.Contains(outrec->bounds) ||
|
|
2946
|
+
!Path2ContainsPath1(outrec->pts, split->pts)) continue;
|
|
2947
|
+
|
|
2948
|
+
if (!IsValidOwner(outrec, split)) // split is owned by outrec! (#957)
|
|
2949
|
+
split->owner = outrec->owner;
|
|
2950
|
+
|
|
2951
|
+
outrec->owner = split;
|
|
2952
|
+
return true;
|
|
2953
|
+
|
|
2954
|
+
}
|
|
2955
|
+
return false;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
void ClipperBase::RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath)
|
|
2959
|
+
{
|
|
2960
|
+
// pre-condition: outrec will have valid bounds
|
|
2961
|
+
// post-condition: if a valid path, outrec will have a polypath
|
|
2962
|
+
|
|
2963
|
+
if (outrec->polypath || outrec->bounds.IsEmpty()) return;
|
|
2964
|
+
while (outrec->owner)
|
|
2965
|
+
{
|
|
2966
|
+
if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break;
|
|
2967
|
+
if (outrec->owner->pts && CheckBounds(outrec->owner) &&
|
|
2968
|
+
outrec->owner->bounds.Contains(outrec->bounds) &&
|
|
2969
|
+
Path2ContainsPath1(outrec->pts, outrec->owner->pts)) break;
|
|
2970
|
+
outrec->owner = outrec->owner->owner;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
if (outrec->owner)
|
|
2974
|
+
{
|
|
2975
|
+
if (!outrec->owner->polypath)
|
|
2976
|
+
RecursiveCheckOwners(outrec->owner, polypath);
|
|
2977
|
+
outrec->polypath = outrec->owner->polypath->AddChild(outrec->path);
|
|
2978
|
+
}
|
|
2979
|
+
else
|
|
2980
|
+
outrec->polypath = polypath->AddChild(outrec->path);
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen)
|
|
2984
|
+
{
|
|
2985
|
+
solutionClosed.resize(0);
|
|
2986
|
+
solutionClosed.reserve(outrec_list_.size());
|
|
2987
|
+
if (solutionOpen)
|
|
2988
|
+
{
|
|
2989
|
+
solutionOpen->resize(0);
|
|
2990
|
+
solutionOpen->reserve(outrec_list_.size());
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
// nb: outrec_list_.size() may change in the following
|
|
2994
|
+
// while loop because polygons may be split during
|
|
2995
|
+
// calls to CleanCollinear which calls FixSelfIntersects
|
|
2996
|
+
for (size_t i = 0; i < outrec_list_.size(); ++i)
|
|
2997
|
+
{
|
|
2998
|
+
OutRec* outrec = outrec_list_[i];
|
|
2999
|
+
if (outrec->pts == nullptr) continue;
|
|
3000
|
+
|
|
3001
|
+
Path64 path;
|
|
3002
|
+
if (solutionOpen && outrec->is_open)
|
|
3003
|
+
{
|
|
3004
|
+
if (BuildPath64(outrec->pts, reverse_solution_, true, path))
|
|
3005
|
+
solutionOpen->emplace_back(std::move(path));
|
|
3006
|
+
}
|
|
3007
|
+
else
|
|
3008
|
+
{
|
|
3009
|
+
// nb: CleanCollinear can add to outrec_list_
|
|
3010
|
+
CleanCollinear(outrec);
|
|
3011
|
+
//closed paths should always return a Positive orientation
|
|
3012
|
+
if (BuildPath64(outrec->pts, reverse_solution_, false, path))
|
|
3013
|
+
solutionClosed.emplace_back(std::move(path));
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths)
|
|
3019
|
+
{
|
|
3020
|
+
polytree.Clear();
|
|
3021
|
+
open_paths.resize(0);
|
|
3022
|
+
if (has_open_paths_)
|
|
3023
|
+
open_paths.reserve(outrec_list_.size());
|
|
3024
|
+
|
|
3025
|
+
// outrec_list_.size() is not static here because
|
|
3026
|
+
// CheckBounds below can indirectly add additional
|
|
3027
|
+
// OutRec (via FixOutRecPts & CleanCollinear)
|
|
3028
|
+
for (size_t i = 0; i < outrec_list_.size(); ++i)
|
|
3029
|
+
{
|
|
3030
|
+
OutRec* outrec = outrec_list_[i];
|
|
3031
|
+
if (!outrec || !outrec->pts) continue;
|
|
3032
|
+
|
|
3033
|
+
if (outrec->is_open)
|
|
3034
|
+
{
|
|
3035
|
+
Path64 path;
|
|
3036
|
+
if (BuildPath64(outrec->pts, reverse_solution_, true, path))
|
|
3037
|
+
open_paths.emplace_back(std::move(path));
|
|
3038
|
+
continue;
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
if (CheckBounds(outrec))
|
|
3042
|
+
RecursiveCheckOwners(outrec, &polytree);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale)
|
|
3047
|
+
{
|
|
3048
|
+
if (!op || op->next == op || (!isOpen && op->next == op->prev))
|
|
3049
|
+
return false;
|
|
3050
|
+
|
|
3051
|
+
path.resize(0);
|
|
3052
|
+
Point64 lastPt;
|
|
3053
|
+
OutPt* op2;
|
|
3054
|
+
if (reverse)
|
|
3055
|
+
{
|
|
3056
|
+
lastPt = op->pt;
|
|
3057
|
+
op2 = op->prev;
|
|
3058
|
+
}
|
|
3059
|
+
else
|
|
3060
|
+
{
|
|
3061
|
+
op = op->next;
|
|
3062
|
+
lastPt = op->pt;
|
|
3063
|
+
op2 = op->next;
|
|
3064
|
+
}
|
|
3065
|
+
#ifdef USINGZ
|
|
3066
|
+
path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z);
|
|
3067
|
+
#else
|
|
3068
|
+
path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale);
|
|
3069
|
+
#endif
|
|
3070
|
+
|
|
3071
|
+
while (op2 != op)
|
|
3072
|
+
{
|
|
3073
|
+
if (op2->pt != lastPt)
|
|
3074
|
+
{
|
|
3075
|
+
lastPt = op2->pt;
|
|
3076
|
+
#ifdef USINGZ
|
|
3077
|
+
path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z);
|
|
3078
|
+
#else
|
|
3079
|
+
path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale);
|
|
3080
|
+
#endif
|
|
3081
|
+
|
|
3082
|
+
}
|
|
3083
|
+
if (reverse)
|
|
3084
|
+
op2 = op2->prev;
|
|
3085
|
+
else
|
|
3086
|
+
op2 = op2->next;
|
|
3087
|
+
}
|
|
3088
|
+
if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false;
|
|
3089
|
+
return true;
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen)
|
|
3093
|
+
{
|
|
3094
|
+
solutionClosed.resize(0);
|
|
3095
|
+
solutionClosed.reserve(outrec_list_.size());
|
|
3096
|
+
if (solutionOpen)
|
|
3097
|
+
{
|
|
3098
|
+
solutionOpen->resize(0);
|
|
3099
|
+
solutionOpen->reserve(outrec_list_.size());
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
// outrec_list_.size() is not static here because
|
|
3103
|
+
// CleanCollinear below can indirectly add additional
|
|
3104
|
+
// OutRec (via FixOutRecPts)
|
|
3105
|
+
for (std::size_t i = 0; i < outrec_list_.size(); ++i)
|
|
3106
|
+
{
|
|
3107
|
+
OutRec* outrec = outrec_list_[i];
|
|
3108
|
+
if (outrec->pts == nullptr) continue;
|
|
3109
|
+
|
|
3110
|
+
PathD path;
|
|
3111
|
+
if (solutionOpen && outrec->is_open)
|
|
3112
|
+
{
|
|
3113
|
+
if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_))
|
|
3114
|
+
solutionOpen->emplace_back(std::move(path));
|
|
3115
|
+
}
|
|
3116
|
+
else
|
|
3117
|
+
{
|
|
3118
|
+
CleanCollinear(outrec);
|
|
3119
|
+
//closed paths should always return a Positive orientation
|
|
3120
|
+
if (BuildPathD(outrec->pts, reverse_solution_, false, path, invScale_))
|
|
3121
|
+
solutionClosed.emplace_back(std::move(path));
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths)
|
|
3127
|
+
{
|
|
3128
|
+
polytree.Clear();
|
|
3129
|
+
open_paths.resize(0);
|
|
3130
|
+
if (has_open_paths_)
|
|
3131
|
+
open_paths.reserve(outrec_list_.size());
|
|
3132
|
+
|
|
3133
|
+
// outrec_list_.size() is not static here because
|
|
3134
|
+
// BuildPathD below can indirectly add additional OutRec //#607
|
|
3135
|
+
for (size_t i = 0; i < outrec_list_.size(); ++i)
|
|
3136
|
+
{
|
|
3137
|
+
OutRec* outrec = outrec_list_[i];
|
|
3138
|
+
if (!outrec || !outrec->pts) continue;
|
|
3139
|
+
if (outrec->is_open)
|
|
3140
|
+
{
|
|
3141
|
+
PathD path;
|
|
3142
|
+
if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_))
|
|
3143
|
+
open_paths.emplace_back(std::move(path));
|
|
3144
|
+
continue;
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
if (CheckBounds(outrec))
|
|
3148
|
+
RecursiveCheckOwners(outrec, &polytree);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
} // namespace clipper2lib
|