clipper2-ts 1.5.4-3.9a869ba
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.
- package/LICENSE +24 -0
- package/README.md +67 -0
- package/dist/Clipper.d.ts +114 -0
- package/dist/Clipper.d.ts.map +1 -0
- package/dist/Clipper.js +1134 -0
- package/dist/Clipper.js.map +1 -0
- package/dist/Core.d.ts +150 -0
- package/dist/Core.d.ts.map +1 -0
- package/dist/Core.js +645 -0
- package/dist/Core.js.map +1 -0
- package/dist/Engine.d.ts +337 -0
- package/dist/Engine.d.ts.map +1 -0
- package/dist/Engine.js +2972 -0
- package/dist/Engine.js.map +1 -0
- package/dist/Minkowski.d.ts +16 -0
- package/dist/Minkowski.d.ts.map +1 -0
- package/dist/Minkowski.js +131 -0
- package/dist/Minkowski.js.map +1 -0
- package/dist/Offset.d.ts +85 -0
- package/dist/Offset.d.ts.map +1 -0
- package/dist/Offset.js +649 -0
- package/dist/Offset.js.map +1 -0
- package/dist/RectClip.d.ts +80 -0
- package/dist/RectClip.d.ts.map +1 -0
- package/dist/RectClip.js +1009 -0
- package/dist/RectClip.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/src/Clipper.ts +1106 -0
- package/src/Core.ts +683 -0
- package/src/Engine.ts +3116 -0
- package/src/Minkowski.ts +153 -0
- package/src/Offset.ts +711 -0
- package/src/RectClip.ts +1028 -0
- package/src/index.ts +146 -0
package/dist/Engine.js
ADDED
|
@@ -0,0 +1,2972 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*******************************************************************************
|
|
3
|
+
* Author : Angus Johnson *
|
|
4
|
+
* Date : 11 October 2025 *
|
|
5
|
+
* Website : https://www.angusj.com *
|
|
6
|
+
* Copyright : Angus Johnson 2010-2025 *
|
|
7
|
+
* Purpose : This is the main polygon clipping module *
|
|
8
|
+
* License : https://www.boost.org/LICENSE_1_0.txt *
|
|
9
|
+
*******************************************************************************/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.Clipper = exports.ClipperD = exports.Clipper64 = exports.ClipperBase = exports.PolyTreeD = exports.PolyTree64 = exports.PolyPathD = exports.PolyPath64 = exports.PolyPathBase = exports.ReuseableDataContainer64 = exports.ClipperEngine = exports.Active = exports.HorzJoin = exports.HorzSegment = exports.OutRec = exports.HorzPosition = exports.JoinWith = exports.OutPt = exports.LocalMinima = exports.Vertex = exports.VertexFlags = void 0;
|
|
12
|
+
exports.createLocalMinima = createLocalMinima;
|
|
13
|
+
exports.createIntersectNode = createIntersectNode;
|
|
14
|
+
const Core_1 = require("./Core");
|
|
15
|
+
// Vertex: a pre-clipping data structure. It is used to separate polygons
|
|
16
|
+
// into ascending and descending 'bounds' (or sides) that start at local
|
|
17
|
+
// minima and ascend to a local maxima, before descending again.
|
|
18
|
+
var VertexFlags;
|
|
19
|
+
(function (VertexFlags) {
|
|
20
|
+
VertexFlags[VertexFlags["None"] = 0] = "None";
|
|
21
|
+
VertexFlags[VertexFlags["OpenStart"] = 1] = "OpenStart";
|
|
22
|
+
VertexFlags[VertexFlags["OpenEnd"] = 2] = "OpenEnd";
|
|
23
|
+
VertexFlags[VertexFlags["LocalMax"] = 4] = "LocalMax";
|
|
24
|
+
VertexFlags[VertexFlags["LocalMin"] = 8] = "LocalMin";
|
|
25
|
+
})(VertexFlags || (exports.VertexFlags = VertexFlags = {}));
|
|
26
|
+
// C# keeps scanlines in a sorted list; here we use a heap to avoid O(n) splices.
|
|
27
|
+
class ScanlineHeap {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.data = [];
|
|
30
|
+
}
|
|
31
|
+
push(value) {
|
|
32
|
+
this.data.push(value);
|
|
33
|
+
this.siftUp(this.data.length - 1);
|
|
34
|
+
}
|
|
35
|
+
pop() {
|
|
36
|
+
if (this.data.length === 0)
|
|
37
|
+
return null;
|
|
38
|
+
const max = this.data[0];
|
|
39
|
+
const last = this.data.pop();
|
|
40
|
+
if (this.data.length > 0) {
|
|
41
|
+
this.data[0] = last;
|
|
42
|
+
this.siftDown(0);
|
|
43
|
+
}
|
|
44
|
+
return max;
|
|
45
|
+
}
|
|
46
|
+
clear() {
|
|
47
|
+
this.data.length = 0;
|
|
48
|
+
}
|
|
49
|
+
siftUp(index) {
|
|
50
|
+
while (index > 0) {
|
|
51
|
+
const parent = (index - 1) >> 1;
|
|
52
|
+
if (this.data[parent] >= this.data[index])
|
|
53
|
+
break;
|
|
54
|
+
[this.data[parent], this.data[index]] = [this.data[index], this.data[parent]];
|
|
55
|
+
index = parent;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
siftDown(index) {
|
|
59
|
+
const length = this.data.length;
|
|
60
|
+
while (true) {
|
|
61
|
+
let largest = index;
|
|
62
|
+
const left = (index << 1) + 1;
|
|
63
|
+
const right = left + 1;
|
|
64
|
+
if (left < length && this.data[left] > this.data[largest])
|
|
65
|
+
largest = left;
|
|
66
|
+
if (right < length && this.data[right] > this.data[largest])
|
|
67
|
+
largest = right;
|
|
68
|
+
if (largest === index)
|
|
69
|
+
break;
|
|
70
|
+
[this.data[index], this.data[largest]] = [this.data[largest], this.data[index]];
|
|
71
|
+
index = largest;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
class Vertex {
|
|
76
|
+
constructor(pt, flags, prev) {
|
|
77
|
+
this.next = null;
|
|
78
|
+
this.prev = null;
|
|
79
|
+
this.pt = pt;
|
|
80
|
+
this.flags = flags;
|
|
81
|
+
this.prev = prev;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.Vertex = Vertex;
|
|
85
|
+
class LocalMinima {
|
|
86
|
+
constructor(vertex, polytype, isOpen = false) {
|
|
87
|
+
this.vertex = vertex;
|
|
88
|
+
this.polytype = polytype;
|
|
89
|
+
this.isOpen = isOpen;
|
|
90
|
+
}
|
|
91
|
+
equals(other) {
|
|
92
|
+
return other !== null && this.vertex === other.vertex;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.LocalMinima = LocalMinima;
|
|
96
|
+
// deprecated: kept for backward compatibility, use new LocalMinima() directly
|
|
97
|
+
// (no longer used internally for performance)
|
|
98
|
+
function createLocalMinima(vertex, polytype, isOpen = false) {
|
|
99
|
+
return new LocalMinima(vertex, polytype, isOpen);
|
|
100
|
+
}
|
|
101
|
+
function createIntersectNode(pt, edge1, edge2) {
|
|
102
|
+
// Create a copy of pt to avoid reference sharing (C# uses struct which copies by value)
|
|
103
|
+
return { pt: { x: pt.x, y: pt.y }, edge1, edge2 };
|
|
104
|
+
}
|
|
105
|
+
// OutPt: vertex data structure for clipping solutions
|
|
106
|
+
class OutPt {
|
|
107
|
+
constructor(pt, outrec) {
|
|
108
|
+
this._debugId = OutPt._nextId++;
|
|
109
|
+
this.pt = pt;
|
|
110
|
+
this.outrec = outrec;
|
|
111
|
+
this.next = this;
|
|
112
|
+
this.prev = this;
|
|
113
|
+
this.horz = null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.OutPt = OutPt;
|
|
117
|
+
OutPt._nextId = 1;
|
|
118
|
+
var JoinWith;
|
|
119
|
+
(function (JoinWith) {
|
|
120
|
+
JoinWith[JoinWith["None"] = 0] = "None";
|
|
121
|
+
JoinWith[JoinWith["Left"] = 1] = "Left";
|
|
122
|
+
JoinWith[JoinWith["Right"] = 2] = "Right";
|
|
123
|
+
})(JoinWith || (exports.JoinWith = JoinWith = {}));
|
|
124
|
+
var HorzPosition;
|
|
125
|
+
(function (HorzPosition) {
|
|
126
|
+
HorzPosition[HorzPosition["Bottom"] = 0] = "Bottom";
|
|
127
|
+
HorzPosition[HorzPosition["Middle"] = 1] = "Middle";
|
|
128
|
+
HorzPosition[HorzPosition["Top"] = 2] = "Top";
|
|
129
|
+
})(HorzPosition || (exports.HorzPosition = HorzPosition = {}));
|
|
130
|
+
// OutRec: path data structure for clipping solutions
|
|
131
|
+
class OutRec {
|
|
132
|
+
constructor() {
|
|
133
|
+
this.idx = 0;
|
|
134
|
+
this.owner = null;
|
|
135
|
+
this.frontEdge = null;
|
|
136
|
+
this.backEdge = null;
|
|
137
|
+
this.pts = null;
|
|
138
|
+
this.polypath = null;
|
|
139
|
+
this.bounds = { left: 0, top: 0, right: 0, bottom: 0 };
|
|
140
|
+
this.path = [];
|
|
141
|
+
this.isOpen = false;
|
|
142
|
+
this.splits = null;
|
|
143
|
+
this.recursiveSplit = null;
|
|
144
|
+
this._debugId = OutRec._nextId++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.OutRec = OutRec;
|
|
148
|
+
OutRec._nextId = 1;
|
|
149
|
+
class HorzSegment {
|
|
150
|
+
constructor(op) {
|
|
151
|
+
this.leftOp = op;
|
|
152
|
+
this.rightOp = null;
|
|
153
|
+
this.leftToRight = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.HorzSegment = HorzSegment;
|
|
157
|
+
class HorzJoin {
|
|
158
|
+
constructor(ltor, rtol) {
|
|
159
|
+
this.op1 = ltor;
|
|
160
|
+
this.op2 = rtol;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.HorzJoin = HorzJoin;
|
|
164
|
+
///////////////////////////////////////////////////////////////////
|
|
165
|
+
// Important: UP and DOWN here are premised on Y-axis positive down
|
|
166
|
+
// displays, which is the orientation used in Clipper's development.
|
|
167
|
+
///////////////////////////////////////////////////////////////////
|
|
168
|
+
class Active {
|
|
169
|
+
constructor() {
|
|
170
|
+
this.bot = { x: 0, y: 0 };
|
|
171
|
+
this.top = { x: 0, y: 0 };
|
|
172
|
+
this.curX = 0; // current (updated at every new scanline) - keep as number but ensure integer precision
|
|
173
|
+
this.dx = 0;
|
|
174
|
+
this.windDx = 0; // 1 or -1 depending on winding direction
|
|
175
|
+
this.windCount = 0;
|
|
176
|
+
this.windCount2 = 0; // winding count of the opposite polytype
|
|
177
|
+
this.outrec = null;
|
|
178
|
+
// AEL: 'active edge list' (Vatti's AET - active edge table)
|
|
179
|
+
// a linked list of all edges (from left to right) that are present
|
|
180
|
+
// (or 'active') within the current scanbeam (a horizontal 'beam' that
|
|
181
|
+
// sweeps from bottom to top over the paths in the clipping operation).
|
|
182
|
+
this.prevInAEL = null;
|
|
183
|
+
this.nextInAEL = null;
|
|
184
|
+
// SEL: 'sorted edge list' (Vatti's ST - sorted table)
|
|
185
|
+
// linked list used when sorting edges into their new positions at the
|
|
186
|
+
// top of scanbeams, but also (re)used to process horizontals.
|
|
187
|
+
this.prevInSEL = null;
|
|
188
|
+
this.nextInSEL = null;
|
|
189
|
+
this.jump = null;
|
|
190
|
+
this.vertexTop = null;
|
|
191
|
+
this.localMin = null; // the bottom of an edge 'bound' (also Vatti)
|
|
192
|
+
this.isLeftBound = false;
|
|
193
|
+
this.joinWith = JoinWith.None;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.Active = Active;
|
|
197
|
+
var ClipperEngine;
|
|
198
|
+
(function (ClipperEngine) {
|
|
199
|
+
function addLocMin(vert, polytype, isOpen, minimaList) {
|
|
200
|
+
// make sure the vertex is added only once ...
|
|
201
|
+
if ((vert.flags & VertexFlags.LocalMin) !== VertexFlags.None)
|
|
202
|
+
return;
|
|
203
|
+
vert.flags |= VertexFlags.LocalMin;
|
|
204
|
+
const lm = new LocalMinima(vert, polytype, isOpen);
|
|
205
|
+
minimaList.push(lm);
|
|
206
|
+
}
|
|
207
|
+
ClipperEngine.addLocMin = addLocMin;
|
|
208
|
+
function addPathsToVertexList(paths, polytype, isOpen, minimaList, vertexList) {
|
|
209
|
+
for (const path of paths) {
|
|
210
|
+
let v0 = null;
|
|
211
|
+
let prevV = null;
|
|
212
|
+
for (const pt of path) {
|
|
213
|
+
if (v0 === null) {
|
|
214
|
+
v0 = new Vertex(pt, VertexFlags.None, null);
|
|
215
|
+
vertexList.push(v0);
|
|
216
|
+
prevV = v0;
|
|
217
|
+
}
|
|
218
|
+
else if (!(prevV.pt.x === pt.x && prevV.pt.y === pt.y)) { // ie skips duplicates
|
|
219
|
+
const currV = new Vertex(pt, VertexFlags.None, prevV);
|
|
220
|
+
vertexList.push(currV);
|
|
221
|
+
prevV.next = currV;
|
|
222
|
+
prevV = currV;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (prevV?.prev === null)
|
|
226
|
+
continue;
|
|
227
|
+
if (!isOpen && prevV.pt.x === v0.pt.x && prevV.pt.y === v0.pt.y)
|
|
228
|
+
prevV = prevV.prev;
|
|
229
|
+
prevV.next = v0;
|
|
230
|
+
v0.prev = prevV;
|
|
231
|
+
if (!isOpen && prevV.next === prevV)
|
|
232
|
+
continue;
|
|
233
|
+
// OK, we have a valid path
|
|
234
|
+
let goingUp;
|
|
235
|
+
if (isOpen) {
|
|
236
|
+
let currV = v0.next;
|
|
237
|
+
while (currV !== v0 && currV.pt.y === v0.pt.y)
|
|
238
|
+
currV = currV.next;
|
|
239
|
+
goingUp = currV.pt.y <= v0.pt.y;
|
|
240
|
+
if (goingUp) {
|
|
241
|
+
v0.flags = VertexFlags.OpenStart;
|
|
242
|
+
addLocMin(v0, polytype, true, minimaList);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
v0.flags = VertexFlags.OpenStart | VertexFlags.LocalMax;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else { // closed path
|
|
249
|
+
prevV = v0.prev;
|
|
250
|
+
while (prevV !== v0 && prevV.pt.y === v0.pt.y)
|
|
251
|
+
prevV = prevV.prev;
|
|
252
|
+
if (prevV === v0)
|
|
253
|
+
continue; // only open paths can be completely flat
|
|
254
|
+
goingUp = prevV.pt.y > v0.pt.y;
|
|
255
|
+
}
|
|
256
|
+
const goingUp0 = goingUp;
|
|
257
|
+
prevV = v0;
|
|
258
|
+
let currV = v0.next;
|
|
259
|
+
while (currV !== v0) {
|
|
260
|
+
if (currV.pt.y > prevV.pt.y && goingUp) {
|
|
261
|
+
prevV.flags |= VertexFlags.LocalMax;
|
|
262
|
+
goingUp = false;
|
|
263
|
+
}
|
|
264
|
+
else if (currV.pt.y < prevV.pt.y && !goingUp) {
|
|
265
|
+
goingUp = true;
|
|
266
|
+
addLocMin(prevV, polytype, isOpen, minimaList);
|
|
267
|
+
}
|
|
268
|
+
prevV = currV;
|
|
269
|
+
currV = currV.next;
|
|
270
|
+
}
|
|
271
|
+
if (isOpen) {
|
|
272
|
+
prevV.flags |= VertexFlags.OpenEnd;
|
|
273
|
+
if (goingUp)
|
|
274
|
+
prevV.flags |= VertexFlags.LocalMax;
|
|
275
|
+
else
|
|
276
|
+
addLocMin(prevV, polytype, isOpen, minimaList);
|
|
277
|
+
}
|
|
278
|
+
else if (goingUp !== goingUp0) {
|
|
279
|
+
if (goingUp0)
|
|
280
|
+
addLocMin(prevV, polytype, false, minimaList);
|
|
281
|
+
else
|
|
282
|
+
prevV.flags |= VertexFlags.LocalMax;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
ClipperEngine.addPathsToVertexList = addPathsToVertexList;
|
|
287
|
+
})(ClipperEngine || (exports.ClipperEngine = ClipperEngine = {}));
|
|
288
|
+
class ReuseableDataContainer64 {
|
|
289
|
+
constructor() {
|
|
290
|
+
this.minimaList = [];
|
|
291
|
+
this.vertexList = [];
|
|
292
|
+
}
|
|
293
|
+
clear() {
|
|
294
|
+
this.minimaList.length = 0;
|
|
295
|
+
this.vertexList.length = 0;
|
|
296
|
+
}
|
|
297
|
+
addPaths(paths, pt, isOpen) {
|
|
298
|
+
ClipperEngine.addPathsToVertexList(paths, pt, isOpen, this.minimaList, this.vertexList);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
exports.ReuseableDataContainer64 = ReuseableDataContainer64;
|
|
302
|
+
class PolyPathBase {
|
|
303
|
+
constructor(parent = null) {
|
|
304
|
+
this.children = [];
|
|
305
|
+
this.parent = parent;
|
|
306
|
+
}
|
|
307
|
+
get isHole() {
|
|
308
|
+
return this.getIsHole();
|
|
309
|
+
}
|
|
310
|
+
getLevel() {
|
|
311
|
+
let result = 0;
|
|
312
|
+
let pp = this.parent;
|
|
313
|
+
while (pp !== null) {
|
|
314
|
+
++result;
|
|
315
|
+
pp = pp.parent;
|
|
316
|
+
}
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
get level() {
|
|
320
|
+
return this.getLevel();
|
|
321
|
+
}
|
|
322
|
+
getIsHole() {
|
|
323
|
+
const lvl = this.getLevel();
|
|
324
|
+
return lvl !== 0 && (lvl & 1) === 0;
|
|
325
|
+
}
|
|
326
|
+
get count() {
|
|
327
|
+
return this.children.length;
|
|
328
|
+
}
|
|
329
|
+
clear() {
|
|
330
|
+
this.children.length = 0;
|
|
331
|
+
}
|
|
332
|
+
toStringInternal(idx, level) {
|
|
333
|
+
let result = "";
|
|
334
|
+
const padding = " ".repeat(level);
|
|
335
|
+
const plural = this.children.length === 1 ? "" : "s";
|
|
336
|
+
if ((level & 1) === 0) {
|
|
337
|
+
result += `${padding}+- hole (${idx}) contains ${this.children.length} nested polygon${plural}.\n`;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
result += `${padding}+- polygon (${idx}) contains ${this.children.length} hole${plural}.\n`;
|
|
341
|
+
}
|
|
342
|
+
for (let i = 0; i < this.count; i++) {
|
|
343
|
+
if (this.children[i].count > 0) {
|
|
344
|
+
result += this.children[i].toStringInternal(i, level + 1);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
toString() {
|
|
350
|
+
if (this.level > 0)
|
|
351
|
+
return ""; // only accept tree root
|
|
352
|
+
const plural = this.children.length === 1 ? "" : "s";
|
|
353
|
+
let result = `Polytree with ${this.children.length} polygon${plural}.\n`;
|
|
354
|
+
for (let i = 0; i < this.count; i++) {
|
|
355
|
+
if (this.children[i].count > 0) {
|
|
356
|
+
result += this.children[i].toStringInternal(i, 1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return result + '\n';
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
exports.PolyPathBase = PolyPathBase;
|
|
363
|
+
class PolyPath64 extends PolyPathBase {
|
|
364
|
+
constructor(parent = null) {
|
|
365
|
+
super(parent);
|
|
366
|
+
this.polygon = null; // polytree root's polygon == null
|
|
367
|
+
}
|
|
368
|
+
get poly() {
|
|
369
|
+
return this.polygon;
|
|
370
|
+
}
|
|
371
|
+
addChild(p) {
|
|
372
|
+
const newChild = new PolyPath64(this);
|
|
373
|
+
newChild.polygon = p;
|
|
374
|
+
this.children.push(newChild);
|
|
375
|
+
return newChild;
|
|
376
|
+
}
|
|
377
|
+
child(index) {
|
|
378
|
+
if (index < 0 || index >= this.children.length) {
|
|
379
|
+
throw new Error("Index out of range");
|
|
380
|
+
}
|
|
381
|
+
return this.children[index];
|
|
382
|
+
}
|
|
383
|
+
area() {
|
|
384
|
+
let result = this.polygon === null ? 0 : Clipper.area(this.polygon);
|
|
385
|
+
for (const child of this.children) {
|
|
386
|
+
result += child.area();
|
|
387
|
+
}
|
|
388
|
+
return result;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
exports.PolyPath64 = PolyPath64;
|
|
392
|
+
class PolyPathD extends PolyPathBase {
|
|
393
|
+
constructor(parent = null) {
|
|
394
|
+
super(parent);
|
|
395
|
+
this.scale = 1.0;
|
|
396
|
+
this.polygon = null;
|
|
397
|
+
}
|
|
398
|
+
get poly() {
|
|
399
|
+
return this.polygon;
|
|
400
|
+
}
|
|
401
|
+
addChild(p) {
|
|
402
|
+
const newChild = new PolyPathD(this);
|
|
403
|
+
newChild.scale = this.scale;
|
|
404
|
+
newChild.polygon = Clipper.scalePathD(p, 1 / this.scale);
|
|
405
|
+
this.children.push(newChild);
|
|
406
|
+
return newChild;
|
|
407
|
+
}
|
|
408
|
+
addChildD(p) {
|
|
409
|
+
const newChild = new PolyPathD(this);
|
|
410
|
+
newChild.scale = this.scale;
|
|
411
|
+
newChild.polygon = p;
|
|
412
|
+
this.children.push(newChild);
|
|
413
|
+
return newChild;
|
|
414
|
+
}
|
|
415
|
+
child(index) {
|
|
416
|
+
if (index < 0 || index >= this.children.length) {
|
|
417
|
+
throw new Error("Index out of range");
|
|
418
|
+
}
|
|
419
|
+
return this.children[index];
|
|
420
|
+
}
|
|
421
|
+
area() {
|
|
422
|
+
let result = this.polygon === null ? 0 : Clipper.areaD(this.polygon);
|
|
423
|
+
for (const child of this.children) {
|
|
424
|
+
result += child.area();
|
|
425
|
+
}
|
|
426
|
+
return result;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
exports.PolyPathD = PolyPathD;
|
|
430
|
+
class PolyTree64 extends PolyPath64 {
|
|
431
|
+
}
|
|
432
|
+
exports.PolyTree64 = PolyTree64;
|
|
433
|
+
class PolyTreeD extends PolyPathD {
|
|
434
|
+
get scaleValue() {
|
|
435
|
+
return this.scale;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
exports.PolyTreeD = PolyTreeD;
|
|
439
|
+
class ClipperBase {
|
|
440
|
+
constructor() {
|
|
441
|
+
this.cliptype = Core_1.ClipType.NoClip;
|
|
442
|
+
this.fillrule = Core_1.FillRule.EvenOdd;
|
|
443
|
+
this.actives = null;
|
|
444
|
+
this.sel = null;
|
|
445
|
+
this.minimaList = [];
|
|
446
|
+
this.intersectList = [];
|
|
447
|
+
this.vertexList = [];
|
|
448
|
+
this.outrecList = [];
|
|
449
|
+
this.scanlineHeap = new ScanlineHeap();
|
|
450
|
+
this.scanlineSet = new Set();
|
|
451
|
+
this.horzSegList = [];
|
|
452
|
+
this.horzJoinList = [];
|
|
453
|
+
this.currentLocMin = 0;
|
|
454
|
+
this.currentBotY = 0;
|
|
455
|
+
this.isSortedMinimaList = false;
|
|
456
|
+
this.hasOpenPaths = false;
|
|
457
|
+
this.usingPolytree = false;
|
|
458
|
+
this.succeeded = false;
|
|
459
|
+
this.preserveCollinear = true;
|
|
460
|
+
this.reverseSolution = false;
|
|
461
|
+
}
|
|
462
|
+
// Helper functions
|
|
463
|
+
static isOdd(val) {
|
|
464
|
+
return (val & 1) !== 0;
|
|
465
|
+
}
|
|
466
|
+
static isHotEdge(ae) {
|
|
467
|
+
return ae.outrec != null;
|
|
468
|
+
}
|
|
469
|
+
static isOpen(ae) {
|
|
470
|
+
return ae.localMin.isOpen;
|
|
471
|
+
}
|
|
472
|
+
static isOpenEnd(ae) {
|
|
473
|
+
return ae.localMin.isOpen && ClipperBase.isOpenEndVertex(ae.vertexTop);
|
|
474
|
+
}
|
|
475
|
+
static isOpenEndVertex(v) {
|
|
476
|
+
return (v.flags & (VertexFlags.OpenStart | VertexFlags.OpenEnd)) !== VertexFlags.None;
|
|
477
|
+
}
|
|
478
|
+
static getPrevHotEdge(ae) {
|
|
479
|
+
let prev = ae.prevInAEL;
|
|
480
|
+
while (prev !== null && (ClipperBase.isOpen(prev) || !ClipperBase.isHotEdge(prev))) {
|
|
481
|
+
prev = prev.prevInAEL;
|
|
482
|
+
}
|
|
483
|
+
return prev;
|
|
484
|
+
}
|
|
485
|
+
static isFront(ae) {
|
|
486
|
+
return ae === ae.outrec.frontEdge;
|
|
487
|
+
}
|
|
488
|
+
/*******************************************************************************
|
|
489
|
+
* Dx: 0(90deg) *
|
|
490
|
+
* | *
|
|
491
|
+
* +inf (180deg) <--- o ---> -inf (0deg) *
|
|
492
|
+
*******************************************************************************/
|
|
493
|
+
static getDx(pt1, pt2) {
|
|
494
|
+
const dy = pt2.y - pt1.y;
|
|
495
|
+
if (dy !== 0) {
|
|
496
|
+
return (pt2.x - pt1.x) / dy;
|
|
497
|
+
}
|
|
498
|
+
return pt2.x > pt1.x ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
|
|
499
|
+
}
|
|
500
|
+
static topX(ae, currentY) {
|
|
501
|
+
if ((currentY === ae.top.y) || (ae.top.x === ae.bot.x))
|
|
502
|
+
return ae.top.x;
|
|
503
|
+
if (currentY === ae.bot.y)
|
|
504
|
+
return ae.bot.x;
|
|
505
|
+
// use MidpointRounding.ToEven in order to explicitly match the nearbyint behaviour on the C++ side
|
|
506
|
+
return Core_1.InternalClipper.roundToEven(ae.bot.x + ae.dx * (currentY - ae.bot.y));
|
|
507
|
+
}
|
|
508
|
+
static isHorizontal(ae) {
|
|
509
|
+
return ae.top.y === ae.bot.y;
|
|
510
|
+
}
|
|
511
|
+
static isHeadingRightHorz(ae) {
|
|
512
|
+
return ae.dx === Number.NEGATIVE_INFINITY;
|
|
513
|
+
}
|
|
514
|
+
static isHeadingLeftHorz(ae) {
|
|
515
|
+
return ae.dx === Number.POSITIVE_INFINITY;
|
|
516
|
+
}
|
|
517
|
+
static swapActives(ae1, ae2) {
|
|
518
|
+
return [ae2, ae1];
|
|
519
|
+
}
|
|
520
|
+
static getPolyType(ae) {
|
|
521
|
+
return ae.localMin.polytype;
|
|
522
|
+
}
|
|
523
|
+
static isSamePolyType(ae1, ae2) {
|
|
524
|
+
return ae1.localMin.polytype === ae2.localMin.polytype;
|
|
525
|
+
}
|
|
526
|
+
static setDx(ae) {
|
|
527
|
+
ae.dx = ClipperBase.getDx(ae.bot, ae.top);
|
|
528
|
+
}
|
|
529
|
+
static nextVertex(ae) {
|
|
530
|
+
return ae.windDx > 0 ? ae.vertexTop.next : ae.vertexTop.prev;
|
|
531
|
+
}
|
|
532
|
+
static prevPrevVertex(ae) {
|
|
533
|
+
return ae.windDx > 0 ? ae.vertexTop.prev.prev : ae.vertexTop.next.next;
|
|
534
|
+
}
|
|
535
|
+
static isMaxima(vertexOrAe) {
|
|
536
|
+
if ('flags' in vertexOrAe) {
|
|
537
|
+
// It's a Vertex
|
|
538
|
+
return (vertexOrAe.flags & VertexFlags.LocalMax) !== VertexFlags.None;
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
// It's an Active
|
|
542
|
+
return ClipperBase.isMaxima(vertexOrAe.vertexTop);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
static getMaximaPair(ae) {
|
|
546
|
+
let ae2 = ae.nextInAEL;
|
|
547
|
+
while (ae2 !== null) {
|
|
548
|
+
if (ae2.vertexTop === ae.vertexTop)
|
|
549
|
+
return ae2; // Found!
|
|
550
|
+
ae2 = ae2.nextInAEL;
|
|
551
|
+
}
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
// optimization (not in C# reference): fast bounding box overlap check for segment intersection
|
|
555
|
+
boundingBoxesOverlap(p1, p2, p3, p4) {
|
|
556
|
+
// segment 1: p1-p2, segment 2: p3-p4
|
|
557
|
+
const min1x = Math.min(p1.x, p2.x);
|
|
558
|
+
const max1x = Math.max(p1.x, p2.x);
|
|
559
|
+
const min1y = Math.min(p1.y, p2.y);
|
|
560
|
+
const max1y = Math.max(p1.y, p2.y);
|
|
561
|
+
const min2x = Math.min(p3.x, p4.x);
|
|
562
|
+
const max2x = Math.max(p3.x, p4.x);
|
|
563
|
+
const min2y = Math.min(p3.y, p4.y);
|
|
564
|
+
const max2y = Math.max(p3.y, p4.y);
|
|
565
|
+
return !(max1x < min2x || max2x < min1x || max1y < min2y || max2y < min1y);
|
|
566
|
+
}
|
|
567
|
+
clearSolutionOnly() {
|
|
568
|
+
while (this.actives !== null)
|
|
569
|
+
this.deleteFromAEL(this.actives);
|
|
570
|
+
this.scanlineHeap.clear();
|
|
571
|
+
this.scanlineSet.clear();
|
|
572
|
+
this.disposeIntersectNodes();
|
|
573
|
+
this.outrecList.length = 0;
|
|
574
|
+
this.horzSegList.length = 0;
|
|
575
|
+
this.horzJoinList.length = 0;
|
|
576
|
+
}
|
|
577
|
+
clear() {
|
|
578
|
+
this.clearSolutionOnly();
|
|
579
|
+
this.minimaList.length = 0;
|
|
580
|
+
this.vertexList.length = 0;
|
|
581
|
+
this.currentLocMin = 0;
|
|
582
|
+
this.isSortedMinimaList = false;
|
|
583
|
+
this.hasOpenPaths = false;
|
|
584
|
+
}
|
|
585
|
+
reset() {
|
|
586
|
+
if (!this.isSortedMinimaList) {
|
|
587
|
+
this.minimaList.sort((a, b) => b.vertex.pt.y - a.vertex.pt.y);
|
|
588
|
+
this.isSortedMinimaList = true;
|
|
589
|
+
}
|
|
590
|
+
this.scanlineHeap.clear();
|
|
591
|
+
this.scanlineSet.clear();
|
|
592
|
+
for (let i = this.minimaList.length - 1; i >= 0; i--) {
|
|
593
|
+
this.insertScanline(this.minimaList[i].vertex.pt.y);
|
|
594
|
+
}
|
|
595
|
+
this.currentBotY = 0;
|
|
596
|
+
this.currentLocMin = 0;
|
|
597
|
+
this.actives = null;
|
|
598
|
+
this.sel = null;
|
|
599
|
+
this.succeeded = true;
|
|
600
|
+
}
|
|
601
|
+
insertScanline(y) {
|
|
602
|
+
if (this.scanlineSet.has(y))
|
|
603
|
+
return;
|
|
604
|
+
this.scanlineSet.add(y);
|
|
605
|
+
this.scanlineHeap.push(y);
|
|
606
|
+
}
|
|
607
|
+
popScanline() {
|
|
608
|
+
const y = this.scanlineHeap.pop();
|
|
609
|
+
if (y === null)
|
|
610
|
+
return { success: false, y: 0 };
|
|
611
|
+
this.scanlineSet.delete(y);
|
|
612
|
+
return { success: true, y };
|
|
613
|
+
}
|
|
614
|
+
hasLocMinAtY(y) {
|
|
615
|
+
return this.currentLocMin < this.minimaList.length &&
|
|
616
|
+
this.minimaList[this.currentLocMin].vertex.pt.y === y;
|
|
617
|
+
}
|
|
618
|
+
popLocalMinima() {
|
|
619
|
+
return this.minimaList[this.currentLocMin++];
|
|
620
|
+
}
|
|
621
|
+
addPath(path, polytype, isOpen = false) {
|
|
622
|
+
const tmp = [path];
|
|
623
|
+
this.addPaths(tmp, polytype, isOpen);
|
|
624
|
+
}
|
|
625
|
+
addPaths(paths, polytype, isOpen = false) {
|
|
626
|
+
if (isOpen)
|
|
627
|
+
this.hasOpenPaths = true;
|
|
628
|
+
this.isSortedMinimaList = false;
|
|
629
|
+
ClipperEngine.addPathsToVertexList(paths, polytype, isOpen, this.minimaList, this.vertexList);
|
|
630
|
+
}
|
|
631
|
+
addReuseableData(reuseableData) {
|
|
632
|
+
if (reuseableData['minimaList'].length === 0)
|
|
633
|
+
return;
|
|
634
|
+
// nb: reuseableData will continue to own the vertices, so it's important
|
|
635
|
+
// that the reuseableData object isn't destroyed before the Clipper object
|
|
636
|
+
// that's using the data.
|
|
637
|
+
this.isSortedMinimaList = false;
|
|
638
|
+
for (const lm of reuseableData['minimaList']) {
|
|
639
|
+
this.minimaList.push(new LocalMinima(lm.vertex, lm.polytype, lm.isOpen));
|
|
640
|
+
if (lm.isOpen)
|
|
641
|
+
this.hasOpenPaths = true;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
deleteFromAEL(ae) {
|
|
645
|
+
const prev = ae.prevInAEL;
|
|
646
|
+
const next = ae.nextInAEL;
|
|
647
|
+
if (prev === null && next === null && (ae !== this.actives))
|
|
648
|
+
return; // already deleted
|
|
649
|
+
if (prev !== null) {
|
|
650
|
+
prev.nextInAEL = next;
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
this.actives = next;
|
|
654
|
+
}
|
|
655
|
+
if (next !== null)
|
|
656
|
+
next.prevInAEL = prev;
|
|
657
|
+
// delete ae;
|
|
658
|
+
}
|
|
659
|
+
getBounds() {
|
|
660
|
+
const bounds = {
|
|
661
|
+
left: Number.MAX_SAFE_INTEGER,
|
|
662
|
+
top: Number.MAX_SAFE_INTEGER,
|
|
663
|
+
right: Number.MIN_SAFE_INTEGER,
|
|
664
|
+
bottom: Number.MIN_SAFE_INTEGER
|
|
665
|
+
};
|
|
666
|
+
for (const t of this.vertexList) {
|
|
667
|
+
let v = t;
|
|
668
|
+
do {
|
|
669
|
+
if (v.pt.x < bounds.left)
|
|
670
|
+
bounds.left = v.pt.x;
|
|
671
|
+
if (v.pt.x > bounds.right)
|
|
672
|
+
bounds.right = v.pt.x;
|
|
673
|
+
if (v.pt.y < bounds.top)
|
|
674
|
+
bounds.top = v.pt.y;
|
|
675
|
+
if (v.pt.y > bounds.bottom)
|
|
676
|
+
bounds.bottom = v.pt.y;
|
|
677
|
+
v = v.next;
|
|
678
|
+
} while (v !== t);
|
|
679
|
+
}
|
|
680
|
+
return Core_1.Rect64Utils.isEmpty(bounds) ? { left: 0, top: 0, right: 0, bottom: 0 } : bounds;
|
|
681
|
+
}
|
|
682
|
+
executeInternal(ct, fillRule) {
|
|
683
|
+
if (ct === Core_1.ClipType.NoClip)
|
|
684
|
+
return;
|
|
685
|
+
this.fillrule = fillRule;
|
|
686
|
+
this.cliptype = ct;
|
|
687
|
+
this.reset();
|
|
688
|
+
const scanResult = this.popScanline();
|
|
689
|
+
if (!scanResult.success)
|
|
690
|
+
return;
|
|
691
|
+
let y = scanResult.y;
|
|
692
|
+
while (this.succeeded) {
|
|
693
|
+
this.insertLocalMinimaIntoAEL(y);
|
|
694
|
+
let ae;
|
|
695
|
+
while ((ae = this.popHorz()) !== null)
|
|
696
|
+
this.doHorizontal(ae);
|
|
697
|
+
if (this.horzSegList.length > 0) {
|
|
698
|
+
this.convertHorzSegsToJoins();
|
|
699
|
+
this.horzSegList.length = 0;
|
|
700
|
+
}
|
|
701
|
+
this.currentBotY = y; // bottom of scanbeam
|
|
702
|
+
const nextScanResult = this.popScanline();
|
|
703
|
+
if (!nextScanResult.success)
|
|
704
|
+
break; // y new top of scanbeam
|
|
705
|
+
y = nextScanResult.y;
|
|
706
|
+
this.doIntersections(y);
|
|
707
|
+
this.doTopOfScanbeam(y);
|
|
708
|
+
while ((ae = this.popHorz()) !== null)
|
|
709
|
+
this.doHorizontal(ae);
|
|
710
|
+
}
|
|
711
|
+
if (this.succeeded)
|
|
712
|
+
this.processHorzJoins();
|
|
713
|
+
}
|
|
714
|
+
insertLocalMinimaIntoAEL(botY) {
|
|
715
|
+
// Add any local minima (if any) at BotY ...
|
|
716
|
+
// NB horizontal local minima edges should contain locMin.vertex.prev
|
|
717
|
+
while (this.hasLocMinAtY(botY)) {
|
|
718
|
+
const localMinima = this.popLocalMinima();
|
|
719
|
+
let leftBound;
|
|
720
|
+
if ((localMinima.vertex.flags & VertexFlags.OpenStart) !== VertexFlags.None) {
|
|
721
|
+
leftBound = null;
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
leftBound = new Active();
|
|
725
|
+
leftBound.bot = { x: localMinima.vertex.pt.x, y: localMinima.vertex.pt.y }; // Create copy
|
|
726
|
+
leftBound.curX = localMinima.vertex.pt.x;
|
|
727
|
+
leftBound.windDx = -1;
|
|
728
|
+
leftBound.vertexTop = localMinima.vertex.prev;
|
|
729
|
+
leftBound.top = { x: localMinima.vertex.prev.pt.x, y: localMinima.vertex.prev.pt.y }; // Create copy
|
|
730
|
+
leftBound.outrec = null;
|
|
731
|
+
leftBound.localMin = localMinima;
|
|
732
|
+
ClipperBase.setDx(leftBound);
|
|
733
|
+
}
|
|
734
|
+
let rightBound;
|
|
735
|
+
if ((localMinima.vertex.flags & VertexFlags.OpenEnd) !== VertexFlags.None) {
|
|
736
|
+
rightBound = null;
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
rightBound = new Active();
|
|
740
|
+
rightBound.bot = { x: localMinima.vertex.pt.x, y: localMinima.vertex.pt.y }; // Create copy
|
|
741
|
+
rightBound.curX = localMinima.vertex.pt.x;
|
|
742
|
+
rightBound.windDx = 1;
|
|
743
|
+
rightBound.vertexTop = localMinima.vertex.next; // i.e. ascending
|
|
744
|
+
rightBound.top = { x: localMinima.vertex.next.pt.x, y: localMinima.vertex.next.pt.y }; // Create copy
|
|
745
|
+
rightBound.outrec = null;
|
|
746
|
+
rightBound.localMin = localMinima;
|
|
747
|
+
ClipperBase.setDx(rightBound);
|
|
748
|
+
}
|
|
749
|
+
// Currently LeftB is just the descending bound and RightB is the ascending.
|
|
750
|
+
// Now if the LeftB isn't on the left of RightB then we need swap them.
|
|
751
|
+
if (leftBound !== null && rightBound !== null) {
|
|
752
|
+
if (ClipperBase.isHorizontal(leftBound)) {
|
|
753
|
+
if (ClipperBase.isHeadingRightHorz(leftBound))
|
|
754
|
+
[leftBound, rightBound] = ClipperBase.swapActives(leftBound, rightBound);
|
|
755
|
+
}
|
|
756
|
+
else if (ClipperBase.isHorizontal(rightBound)) {
|
|
757
|
+
if (ClipperBase.isHeadingLeftHorz(rightBound))
|
|
758
|
+
[leftBound, rightBound] = ClipperBase.swapActives(leftBound, rightBound);
|
|
759
|
+
}
|
|
760
|
+
else if (leftBound.dx < rightBound.dx) {
|
|
761
|
+
[leftBound, rightBound] = ClipperBase.swapActives(leftBound, rightBound);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
else if (leftBound === null) {
|
|
765
|
+
leftBound = rightBound;
|
|
766
|
+
rightBound = null;
|
|
767
|
+
}
|
|
768
|
+
let contributing;
|
|
769
|
+
leftBound.isLeftBound = true;
|
|
770
|
+
this.insertLeftEdge(leftBound);
|
|
771
|
+
if (ClipperBase.isOpen(leftBound)) {
|
|
772
|
+
this.setWindCountForOpenPathEdge(leftBound);
|
|
773
|
+
contributing = this.isContributingOpen(leftBound);
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
this.setWindCountForClosedPathEdge(leftBound);
|
|
777
|
+
contributing = this.isContributingClosed(leftBound);
|
|
778
|
+
}
|
|
779
|
+
if (rightBound !== null) {
|
|
780
|
+
rightBound.windCount = leftBound.windCount;
|
|
781
|
+
rightBound.windCount2 = leftBound.windCount2;
|
|
782
|
+
this.insertRightEdge(leftBound, rightBound);
|
|
783
|
+
if (contributing) {
|
|
784
|
+
this.addLocalMinPoly(leftBound, rightBound, leftBound.bot, true);
|
|
785
|
+
if (!ClipperBase.isHorizontal(leftBound)) {
|
|
786
|
+
this.checkJoinLeft(leftBound, leftBound.bot);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
while (rightBound.nextInAEL !== null &&
|
|
790
|
+
this.isValidAelOrder(rightBound.nextInAEL, rightBound)) {
|
|
791
|
+
this.intersectEdges(rightBound, rightBound.nextInAEL, rightBound.bot);
|
|
792
|
+
this.swapPositionsInAEL(rightBound, rightBound.nextInAEL);
|
|
793
|
+
}
|
|
794
|
+
if (ClipperBase.isHorizontal(rightBound)) {
|
|
795
|
+
this.pushHorz(rightBound);
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
this.checkJoinRight(rightBound, rightBound.bot);
|
|
799
|
+
this.insertScanline(rightBound.top.y);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
else if (contributing) {
|
|
803
|
+
this.startOpenPath(leftBound, leftBound.bot);
|
|
804
|
+
}
|
|
805
|
+
if (ClipperBase.isHorizontal(leftBound)) {
|
|
806
|
+
this.pushHorz(leftBound);
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
this.insertScanline(leftBound.top.y);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
pushHorz(ae) {
|
|
814
|
+
ae.nextInSEL = this.sel;
|
|
815
|
+
this.sel = ae;
|
|
816
|
+
}
|
|
817
|
+
popHorz() {
|
|
818
|
+
const ae = this.sel;
|
|
819
|
+
if (ae === null)
|
|
820
|
+
return null;
|
|
821
|
+
this.sel = this.sel.nextInSEL;
|
|
822
|
+
return ae;
|
|
823
|
+
}
|
|
824
|
+
doHorizontal(horz) {
|
|
825
|
+
const horzIsOpen = ClipperBase.isOpen(horz);
|
|
826
|
+
const y = horz.bot.y;
|
|
827
|
+
const vertexMax = horzIsOpen ?
|
|
828
|
+
this.getCurrYMaximaVertexOpen(horz) :
|
|
829
|
+
this.getCurrYMaximaVertex(horz);
|
|
830
|
+
const { isLeftToRight, leftX, rightX } = this.resetHorzDirection(horz, vertexMax);
|
|
831
|
+
let leftX2 = leftX;
|
|
832
|
+
let rightX2 = rightX;
|
|
833
|
+
if (ClipperBase.isHotEdge(horz)) {
|
|
834
|
+
const op = this.addOutPt(horz, { x: horz.curX, y });
|
|
835
|
+
this.addToHorzSegList(op);
|
|
836
|
+
}
|
|
837
|
+
while (true) {
|
|
838
|
+
// loops through consec. horizontal edges (if open)
|
|
839
|
+
let ae = isLeftToRight ? horz.nextInAEL : horz.prevInAEL;
|
|
840
|
+
while (ae !== null) {
|
|
841
|
+
if (ae.vertexTop === vertexMax) {
|
|
842
|
+
// do this first!!
|
|
843
|
+
if (ClipperBase.isHotEdge(horz) && this.isJoined(ae))
|
|
844
|
+
this.split(ae, ae.top);
|
|
845
|
+
if (ClipperBase.isHotEdge(horz)) {
|
|
846
|
+
while (horz.vertexTop !== vertexMax) {
|
|
847
|
+
this.addOutPt(horz, horz.top);
|
|
848
|
+
this.updateEdgeIntoAEL(horz);
|
|
849
|
+
}
|
|
850
|
+
if (isLeftToRight) {
|
|
851
|
+
this.addLocalMaxPoly(horz, ae, horz.top);
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
this.addLocalMaxPoly(ae, horz, horz.top);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
this.deleteFromAEL(ae);
|
|
858
|
+
this.deleteFromAEL(horz);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
// if horzEdge is a maxima, keep going until we reach
|
|
862
|
+
// its maxima pair, otherwise check for break conditions
|
|
863
|
+
if (vertexMax !== horz.vertexTop || ClipperBase.isOpenEnd(horz)) {
|
|
864
|
+
// otherwise stop when 'ae' is beyond the end of the horizontal line
|
|
865
|
+
if ((isLeftToRight && ae.curX > rightX2) ||
|
|
866
|
+
(!isLeftToRight && ae.curX < leftX2))
|
|
867
|
+
break;
|
|
868
|
+
if (ae.curX === horz.top.x && !ClipperBase.isHorizontal(ae)) {
|
|
869
|
+
const pt = ClipperBase.nextVertex(horz).pt;
|
|
870
|
+
// to maximize the possibility of putting open edges into
|
|
871
|
+
// solutions, we'll only break if it's past HorzEdge's end
|
|
872
|
+
if (ClipperBase.isOpen(ae) && !ClipperBase.isSamePolyType(ae, horz) && !ClipperBase.isHotEdge(ae)) {
|
|
873
|
+
if ((isLeftToRight && (ClipperBase.topX(ae, pt.y) > pt.x)) ||
|
|
874
|
+
(!isLeftToRight && (ClipperBase.topX(ae, pt.y) < pt.x)))
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
// otherwise for edges at horzEdge's end, only stop when horzEdge's
|
|
878
|
+
// outslope is greater than e's slope when heading right or when
|
|
879
|
+
// horzEdge's outslope is less than e's slope when heading left.
|
|
880
|
+
else if ((isLeftToRight && (ClipperBase.topX(ae, pt.y) >= pt.x)) ||
|
|
881
|
+
(!isLeftToRight && (ClipperBase.topX(ae, pt.y) <= pt.x)))
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
const pt = { x: ae.curX, y };
|
|
886
|
+
if (isLeftToRight) {
|
|
887
|
+
this.intersectEdges(horz, ae, pt);
|
|
888
|
+
this.swapPositionsInAEL(horz, ae);
|
|
889
|
+
this.checkJoinLeft(ae, pt);
|
|
890
|
+
horz.curX = ae.curX;
|
|
891
|
+
ae = horz.nextInAEL;
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
this.intersectEdges(ae, horz, pt);
|
|
895
|
+
this.swapPositionsInAEL(ae, horz);
|
|
896
|
+
this.checkJoinRight(ae, pt);
|
|
897
|
+
horz.curX = ae.curX;
|
|
898
|
+
ae = horz.prevInAEL;
|
|
899
|
+
}
|
|
900
|
+
if (ClipperBase.isHotEdge(horz)) {
|
|
901
|
+
this.addToHorzSegList(this.getLastOp(horz));
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
// check if we've finished looping
|
|
905
|
+
// through consecutive horizontals
|
|
906
|
+
if (horzIsOpen && ClipperBase.isOpenEnd(horz)) { // ie open at top
|
|
907
|
+
if (ClipperBase.isHotEdge(horz)) {
|
|
908
|
+
this.addOutPt(horz, horz.top);
|
|
909
|
+
if (ClipperBase.isFront(horz)) {
|
|
910
|
+
horz.outrec.frontEdge = null;
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
horz.outrec.backEdge = null;
|
|
914
|
+
}
|
|
915
|
+
horz.outrec = null;
|
|
916
|
+
}
|
|
917
|
+
this.deleteFromAEL(horz);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (ClipperBase.nextVertex(horz).pt.y !== horz.top.y) {
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
//still more horizontals in bound to process ...
|
|
924
|
+
if (ClipperBase.isHotEdge(horz)) {
|
|
925
|
+
this.addOutPt(horz, horz.top);
|
|
926
|
+
}
|
|
927
|
+
this.updateEdgeIntoAEL(horz);
|
|
928
|
+
const resetResult = this.resetHorzDirection(horz, vertexMax);
|
|
929
|
+
leftX2 = resetResult.leftX;
|
|
930
|
+
rightX2 = resetResult.rightX;
|
|
931
|
+
}
|
|
932
|
+
if (ClipperBase.isHotEdge(horz)) {
|
|
933
|
+
const op = this.addOutPt(horz, horz.top);
|
|
934
|
+
this.addToHorzSegList(op);
|
|
935
|
+
}
|
|
936
|
+
this.updateEdgeIntoAEL(horz); // this is the end of an intermediate horiz.
|
|
937
|
+
}
|
|
938
|
+
convertHorzSegsToJoins() {
|
|
939
|
+
let k = 0;
|
|
940
|
+
for (const hs of this.horzSegList) {
|
|
941
|
+
if (this.updateHorzSegment(hs))
|
|
942
|
+
k++;
|
|
943
|
+
}
|
|
944
|
+
if (k < 2)
|
|
945
|
+
return;
|
|
946
|
+
this.horzSegList.sort((a, b) => this.horzSegSort(a, b));
|
|
947
|
+
for (let i = 0; i < k - 1; i++) {
|
|
948
|
+
const hs1 = this.horzSegList[i];
|
|
949
|
+
// for each HorzSegment, find others that overlap
|
|
950
|
+
for (let j = i + 1; j < k; j++) {
|
|
951
|
+
const hs2 = this.horzSegList[j];
|
|
952
|
+
if ((hs2.leftOp.pt.x >= hs1.rightOp.pt.x) ||
|
|
953
|
+
(hs2.leftToRight === hs1.leftToRight) ||
|
|
954
|
+
(hs2.rightOp.pt.x <= hs1.leftOp.pt.x))
|
|
955
|
+
continue;
|
|
956
|
+
const currY = hs1.leftOp.pt.y;
|
|
957
|
+
if (hs1.leftToRight) {
|
|
958
|
+
while (hs1.leftOp.next.pt.y === currY &&
|
|
959
|
+
hs1.leftOp.next.pt.x <= hs2.leftOp.pt.x)
|
|
960
|
+
hs1.leftOp = hs1.leftOp.next;
|
|
961
|
+
while (hs2.leftOp.prev.pt.y === currY &&
|
|
962
|
+
hs2.leftOp.prev.pt.x <= hs1.leftOp.pt.x)
|
|
963
|
+
hs2.leftOp = hs2.leftOp.prev;
|
|
964
|
+
const join = new HorzJoin(this.duplicateOp(hs1.leftOp, true), this.duplicateOp(hs2.leftOp, false));
|
|
965
|
+
this.horzJoinList.push(join);
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
while (hs1.leftOp.prev.pt.y === currY &&
|
|
969
|
+
hs1.leftOp.prev.pt.x <= hs2.leftOp.pt.x)
|
|
970
|
+
hs1.leftOp = hs1.leftOp.prev;
|
|
971
|
+
while (hs2.leftOp.next.pt.y === currY &&
|
|
972
|
+
hs2.leftOp.next.pt.x <= hs1.leftOp.pt.x)
|
|
973
|
+
hs2.leftOp = hs2.leftOp.next;
|
|
974
|
+
const join = new HorzJoin(this.duplicateOp(hs2.leftOp, true), this.duplicateOp(hs1.leftOp, false));
|
|
975
|
+
this.horzJoinList.push(join);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
updateHorzSegment(hs) {
|
|
981
|
+
const op = hs.leftOp;
|
|
982
|
+
const outrec = this.getRealOutRec(op.outrec);
|
|
983
|
+
const outrecHasEdges = outrec.frontEdge !== null;
|
|
984
|
+
const currY = op.pt.y;
|
|
985
|
+
let opP = op;
|
|
986
|
+
let opN = op;
|
|
987
|
+
if (outrecHasEdges) {
|
|
988
|
+
const opA = outrec.pts;
|
|
989
|
+
const opZ = opA.next;
|
|
990
|
+
while (opP !== opZ && opP.prev.pt.y === currY)
|
|
991
|
+
opP = opP.prev;
|
|
992
|
+
while (opN !== opA && opN.next.pt.y === currY)
|
|
993
|
+
opN = opN.next;
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
996
|
+
while (opP.prev !== opN && opP.prev.pt.y === currY)
|
|
997
|
+
opP = opP.prev;
|
|
998
|
+
while (opN.next !== opP && opN.next.pt.y === currY)
|
|
999
|
+
opN = opN.next;
|
|
1000
|
+
}
|
|
1001
|
+
const result = this.setHorzSegHeadingForward(hs, opP, opN) && hs.leftOp.horz === null;
|
|
1002
|
+
if (result) {
|
|
1003
|
+
hs.leftOp.horz = hs;
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
hs.rightOp = null; // (for sorting)
|
|
1007
|
+
}
|
|
1008
|
+
return result;
|
|
1009
|
+
}
|
|
1010
|
+
setHorzSegHeadingForward(hs, opP, opN) {
|
|
1011
|
+
if (opP.pt.x === opN.pt.x)
|
|
1012
|
+
return false;
|
|
1013
|
+
if (opP.pt.x < opN.pt.x) {
|
|
1014
|
+
hs.leftOp = opP;
|
|
1015
|
+
hs.rightOp = opN;
|
|
1016
|
+
hs.leftToRight = true;
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
hs.leftOp = opN;
|
|
1020
|
+
hs.rightOp = opP;
|
|
1021
|
+
hs.leftToRight = false;
|
|
1022
|
+
}
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
horzSegSort(hs1, hs2) {
|
|
1026
|
+
if (hs1.rightOp === null) {
|
|
1027
|
+
return hs2.rightOp === null ? 0 : 1;
|
|
1028
|
+
}
|
|
1029
|
+
if (hs2.rightOp === null)
|
|
1030
|
+
return -1;
|
|
1031
|
+
return hs1.leftOp.pt.x - hs2.leftOp.pt.x;
|
|
1032
|
+
}
|
|
1033
|
+
duplicateOp(op, insertAfter) {
|
|
1034
|
+
const result = new OutPt(op.pt, op.outrec);
|
|
1035
|
+
if (insertAfter) {
|
|
1036
|
+
result.next = op.next;
|
|
1037
|
+
result.next.prev = result;
|
|
1038
|
+
result.prev = op;
|
|
1039
|
+
op.next = result;
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
result.prev = op.prev;
|
|
1043
|
+
result.prev.next = result;
|
|
1044
|
+
result.next = op;
|
|
1045
|
+
op.prev = result;
|
|
1046
|
+
}
|
|
1047
|
+
return result;
|
|
1048
|
+
}
|
|
1049
|
+
getRealOutRec(outRec) {
|
|
1050
|
+
while (outRec !== null && outRec.pts === null) {
|
|
1051
|
+
outRec = outRec.owner;
|
|
1052
|
+
}
|
|
1053
|
+
return outRec;
|
|
1054
|
+
}
|
|
1055
|
+
doIntersections(y) {
|
|
1056
|
+
if (this.buildIntersectList(y)) {
|
|
1057
|
+
this.processIntersectList();
|
|
1058
|
+
this.disposeIntersectNodes();
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
doTopOfScanbeam(y) {
|
|
1062
|
+
this.sel = null; // sel is reused to flag horizontals (see PushHorz below)
|
|
1063
|
+
let ae = this.actives;
|
|
1064
|
+
while (ae !== null) {
|
|
1065
|
+
// NB 'ae' will never be horizontal here
|
|
1066
|
+
if (ae.top.y === y) {
|
|
1067
|
+
ae.curX = ae.top.x;
|
|
1068
|
+
if (ClipperBase.isMaxima(ae)) {
|
|
1069
|
+
ae = this.doMaxima(ae); // TOP OF BOUND (MAXIMA)
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
// INTERMEDIATE VERTEX ...
|
|
1074
|
+
if (ClipperBase.isHotEdge(ae))
|
|
1075
|
+
this.addOutPt(ae, ae.top);
|
|
1076
|
+
this.updateEdgeIntoAEL(ae);
|
|
1077
|
+
if (ClipperBase.isHorizontal(ae)) {
|
|
1078
|
+
this.pushHorz(ae); // horizontals are processed later
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
else { // i.e. not the top of the edge
|
|
1083
|
+
ae.curX = ClipperBase.topX(ae, y); // TopX already returns correctly rounded integer
|
|
1084
|
+
}
|
|
1085
|
+
ae = ae.nextInAEL;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
processHorzJoins() {
|
|
1089
|
+
for (const j of this.horzJoinList) {
|
|
1090
|
+
const or1 = this.getRealOutRec(j.op1.outrec);
|
|
1091
|
+
const or2 = this.getRealOutRec(j.op2.outrec);
|
|
1092
|
+
const op1b = j.op1.next;
|
|
1093
|
+
const op2b = j.op2.prev;
|
|
1094
|
+
j.op1.next = j.op2;
|
|
1095
|
+
j.op2.prev = j.op1;
|
|
1096
|
+
op1b.prev = op2b;
|
|
1097
|
+
op2b.next = op1b;
|
|
1098
|
+
if (or1 === or2) { // 'join' is really a split
|
|
1099
|
+
const or2New = this.newOutRec();
|
|
1100
|
+
or2New.pts = op1b;
|
|
1101
|
+
this.fixOutRecPts(or2New);
|
|
1102
|
+
//if or1->pts has moved to or2 then update or1->pts!!
|
|
1103
|
+
if (or1.pts.outrec === or2New) {
|
|
1104
|
+
or1.pts = j.op1;
|
|
1105
|
+
or1.pts.outrec = or1;
|
|
1106
|
+
}
|
|
1107
|
+
if (this.usingPolytree) {
|
|
1108
|
+
if (this.path1InsidePath2(or1.pts, or2New.pts)) {
|
|
1109
|
+
//swap or1's & or2's pts
|
|
1110
|
+
[or2New.pts, or1.pts] = [or1.pts, or2New.pts];
|
|
1111
|
+
this.fixOutRecPts(or1);
|
|
1112
|
+
this.fixOutRecPts(or2New);
|
|
1113
|
+
//or2 is now inside or1
|
|
1114
|
+
or2New.owner = or1;
|
|
1115
|
+
}
|
|
1116
|
+
else if (this.path1InsidePath2(or2New.pts, or1.pts)) {
|
|
1117
|
+
or2New.owner = or1;
|
|
1118
|
+
}
|
|
1119
|
+
else {
|
|
1120
|
+
or2New.owner = or1.owner;
|
|
1121
|
+
}
|
|
1122
|
+
if (or1.splits === null)
|
|
1123
|
+
or1.splits = [];
|
|
1124
|
+
or1.splits.push(or2New.idx);
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
or2New.owner = or1;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
or2.pts = null;
|
|
1132
|
+
if (this.usingPolytree) {
|
|
1133
|
+
this.setOwner(or2, or1);
|
|
1134
|
+
this.moveSplits(or2, or1);
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
or2.owner = or1;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
fixOutRecPts(outrec) {
|
|
1143
|
+
let op = outrec.pts;
|
|
1144
|
+
do {
|
|
1145
|
+
op.outrec = outrec;
|
|
1146
|
+
op = op.next;
|
|
1147
|
+
} while (op !== outrec.pts);
|
|
1148
|
+
}
|
|
1149
|
+
path1InsidePath2(op1, op2) {
|
|
1150
|
+
// we need to make some accommodation for rounding errors
|
|
1151
|
+
// so we won't jump if the first vertex is found outside
|
|
1152
|
+
let pip = Core_1.PointInPolygonResult.IsOn;
|
|
1153
|
+
let op = op1;
|
|
1154
|
+
do {
|
|
1155
|
+
switch (this.pointInOpPolygon(op.pt, op2)) {
|
|
1156
|
+
case Core_1.PointInPolygonResult.IsOutside:
|
|
1157
|
+
if (pip === Core_1.PointInPolygonResult.IsOutside)
|
|
1158
|
+
return false;
|
|
1159
|
+
pip = Core_1.PointInPolygonResult.IsOutside;
|
|
1160
|
+
break;
|
|
1161
|
+
case Core_1.PointInPolygonResult.IsInside:
|
|
1162
|
+
if (pip === Core_1.PointInPolygonResult.IsInside)
|
|
1163
|
+
return true;
|
|
1164
|
+
pip = Core_1.PointInPolygonResult.IsInside;
|
|
1165
|
+
break;
|
|
1166
|
+
default:
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
op = op.next;
|
|
1170
|
+
} while (op !== op1);
|
|
1171
|
+
// result is unclear, so try again using cleaned paths
|
|
1172
|
+
return Core_1.InternalClipper.path2ContainsPath1(this.getCleanPath(op1), this.getCleanPath(op2)); // (#973)
|
|
1173
|
+
}
|
|
1174
|
+
pointInOpPolygon(pt, op) {
|
|
1175
|
+
if (op === op.next || op.prev === op.next) {
|
|
1176
|
+
return Core_1.PointInPolygonResult.IsOutside;
|
|
1177
|
+
}
|
|
1178
|
+
let op2 = op;
|
|
1179
|
+
do {
|
|
1180
|
+
if (op.pt.y !== pt.y)
|
|
1181
|
+
break;
|
|
1182
|
+
op = op.next;
|
|
1183
|
+
} while (op !== op2);
|
|
1184
|
+
if (op.pt.y === pt.y) // not a proper polygon
|
|
1185
|
+
return Core_1.PointInPolygonResult.IsOutside;
|
|
1186
|
+
// must be above or below to get here
|
|
1187
|
+
let isAbove = op.pt.y < pt.y;
|
|
1188
|
+
const startingAbove = isAbove;
|
|
1189
|
+
let val = 0;
|
|
1190
|
+
op2 = op.next;
|
|
1191
|
+
while (op2 !== op) {
|
|
1192
|
+
if (isAbove) {
|
|
1193
|
+
while (op2 !== op && op2.pt.y < pt.y)
|
|
1194
|
+
op2 = op2.next;
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
while (op2 !== op && op2.pt.y > pt.y)
|
|
1198
|
+
op2 = op2.next;
|
|
1199
|
+
}
|
|
1200
|
+
if (op2 === op)
|
|
1201
|
+
break;
|
|
1202
|
+
// must have touched or crossed the pt.Y horizontal
|
|
1203
|
+
// and this must happen an even number of times
|
|
1204
|
+
if (op2.pt.y === pt.y) { // touching the horizontal
|
|
1205
|
+
if (op2.pt.x === pt.x || (op2.pt.y === op2.prev.pt.y &&
|
|
1206
|
+
(pt.x < op2.prev.pt.x) !== (pt.x < op2.pt.x)))
|
|
1207
|
+
return Core_1.PointInPolygonResult.IsOn;
|
|
1208
|
+
op2 = op2.next;
|
|
1209
|
+
if (op2 === op)
|
|
1210
|
+
break;
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1213
|
+
if (op2.pt.x <= pt.x || op2.prev.pt.x <= pt.x) {
|
|
1214
|
+
if ((op2.prev.pt.x < pt.x && op2.pt.x < pt.x)) {
|
|
1215
|
+
val = 1 - val; // toggle val
|
|
1216
|
+
}
|
|
1217
|
+
else {
|
|
1218
|
+
const d = Core_1.InternalClipper.crossProduct(op2.prev.pt, op2.pt, pt);
|
|
1219
|
+
if (d === 0)
|
|
1220
|
+
return Core_1.PointInPolygonResult.IsOn;
|
|
1221
|
+
if ((d < 0) === isAbove)
|
|
1222
|
+
val = 1 - val;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
isAbove = !isAbove;
|
|
1226
|
+
op2 = op2.next;
|
|
1227
|
+
}
|
|
1228
|
+
if (isAbove === startingAbove)
|
|
1229
|
+
return val === 0 ? Core_1.PointInPolygonResult.IsOutside : Core_1.PointInPolygonResult.IsInside;
|
|
1230
|
+
{
|
|
1231
|
+
const d = Core_1.InternalClipper.crossProduct(op2.prev.pt, op2.pt, pt);
|
|
1232
|
+
if (d === 0)
|
|
1233
|
+
return Core_1.PointInPolygonResult.IsOn;
|
|
1234
|
+
if ((d < 0) === isAbove)
|
|
1235
|
+
val = 1 - val;
|
|
1236
|
+
}
|
|
1237
|
+
return val === 0 ? Core_1.PointInPolygonResult.IsOutside : Core_1.PointInPolygonResult.IsInside;
|
|
1238
|
+
}
|
|
1239
|
+
getCleanPath(op) {
|
|
1240
|
+
const result = [];
|
|
1241
|
+
let op2 = op;
|
|
1242
|
+
while (op2.next !== op &&
|
|
1243
|
+
((op2.pt.x === op2.next.pt.x && op2.pt.x === op2.prev.pt.x) ||
|
|
1244
|
+
(op2.pt.y === op2.next.pt.y && op2.pt.y === op2.prev.pt.y)))
|
|
1245
|
+
op2 = op2.next;
|
|
1246
|
+
result.push(op2.pt);
|
|
1247
|
+
let prevOp = op2;
|
|
1248
|
+
op2 = op2.next;
|
|
1249
|
+
while (op2 !== op) {
|
|
1250
|
+
if ((op2.pt.x !== op2.next.pt.x || op2.pt.x !== prevOp.pt.x) &&
|
|
1251
|
+
(op2.pt.y !== op2.next.pt.y || op2.pt.y !== prevOp.pt.y)) {
|
|
1252
|
+
result.push(op2.pt);
|
|
1253
|
+
prevOp = op2;
|
|
1254
|
+
}
|
|
1255
|
+
op2 = op2.next;
|
|
1256
|
+
}
|
|
1257
|
+
return result;
|
|
1258
|
+
}
|
|
1259
|
+
moveSplits(fromOr, toOr) {
|
|
1260
|
+
if (fromOr.splits === null)
|
|
1261
|
+
return;
|
|
1262
|
+
if (toOr.splits === null)
|
|
1263
|
+
toOr.splits = [];
|
|
1264
|
+
for (const i of fromOr.splits) {
|
|
1265
|
+
if (i !== toOr.idx) {
|
|
1266
|
+
toOr.splits.push(i);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
fromOr.splits = null;
|
|
1270
|
+
}
|
|
1271
|
+
buildIntersectList(topY) {
|
|
1272
|
+
if (this.actives?.nextInAEL === null)
|
|
1273
|
+
return false;
|
|
1274
|
+
// Calculate edge positions at the top of the current scanbeam, and from this
|
|
1275
|
+
// we will determine the intersections required to reach these new positions.
|
|
1276
|
+
this.adjustCurrXAndCopyToSEL(topY);
|
|
1277
|
+
// Find all edge intersections in the current scanbeam using a stable merge
|
|
1278
|
+
// sort that ensures only adjacent edges are intersecting. Intersect info is
|
|
1279
|
+
// stored in intersectList ready to be processed in ProcessIntersectList.
|
|
1280
|
+
// Re merge sorts see https://stackoverflow.com/a/46319131/359538
|
|
1281
|
+
let left = this.sel;
|
|
1282
|
+
while (left !== null && left.jump !== null) {
|
|
1283
|
+
let prevBase = null;
|
|
1284
|
+
while (left !== null && left.jump !== null) {
|
|
1285
|
+
let currBase = left;
|
|
1286
|
+
let right = left.jump;
|
|
1287
|
+
let lEnd = right;
|
|
1288
|
+
const rEnd = right?.jump || null;
|
|
1289
|
+
left.jump = rEnd;
|
|
1290
|
+
while (left !== lEnd && right !== rEnd) {
|
|
1291
|
+
if (right.curX < left.curX) {
|
|
1292
|
+
let tmp = right.prevInSEL;
|
|
1293
|
+
while (true) {
|
|
1294
|
+
this.addNewIntersectNode(tmp, right, topY);
|
|
1295
|
+
if (tmp === left)
|
|
1296
|
+
break;
|
|
1297
|
+
tmp = tmp.prevInSEL;
|
|
1298
|
+
}
|
|
1299
|
+
tmp = right;
|
|
1300
|
+
right = this.extractFromSEL(tmp);
|
|
1301
|
+
lEnd = right; // Update lEnd - this is the critical fix!
|
|
1302
|
+
if (left !== null)
|
|
1303
|
+
this.insert1Before2InSEL(tmp, left);
|
|
1304
|
+
if (left !== currBase)
|
|
1305
|
+
continue;
|
|
1306
|
+
currBase = tmp;
|
|
1307
|
+
currBase.jump = rEnd;
|
|
1308
|
+
if (prevBase === null) {
|
|
1309
|
+
this.sel = currBase;
|
|
1310
|
+
}
|
|
1311
|
+
else {
|
|
1312
|
+
prevBase.jump = currBase;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
else {
|
|
1316
|
+
left = left.nextInSEL;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
prevBase = currBase;
|
|
1320
|
+
left = rEnd;
|
|
1321
|
+
}
|
|
1322
|
+
left = this.sel;
|
|
1323
|
+
}
|
|
1324
|
+
return this.intersectList.length > 0;
|
|
1325
|
+
}
|
|
1326
|
+
processIntersectList() {
|
|
1327
|
+
// We now have a list of intersections required so that edges will be
|
|
1328
|
+
// correctly positioned at the top of the scanbeam. However, it's important
|
|
1329
|
+
// that edge intersections are processed from the bottom up, but it's also
|
|
1330
|
+
// crucial that intersections only occur between adjacent edges.
|
|
1331
|
+
// First we do a quicksort so intersections proceed in a bottom up order ...
|
|
1332
|
+
this.intersectList.sort((a, b) => {
|
|
1333
|
+
if (a.pt.y !== b.pt.y)
|
|
1334
|
+
return (a.pt.y > b.pt.y) ? -1 : 1;
|
|
1335
|
+
if (a.pt.x !== b.pt.x)
|
|
1336
|
+
return (a.pt.x < b.pt.x) ? -1 : 1;
|
|
1337
|
+
// Tiebreaker: when points are identical, sort by edge1's curX position
|
|
1338
|
+
// This provides deterministic ordering matching C# IntroSort behavior
|
|
1339
|
+
if (a.edge1.curX !== b.edge1.curX)
|
|
1340
|
+
return (a.edge1.curX < b.edge1.curX) ? -1 : 1;
|
|
1341
|
+
// Final tiebreaker: edge2's curX
|
|
1342
|
+
return (a.edge2.curX < b.edge2.curX) ? -1 : (a.edge2.curX > b.edge2.curX) ? 1 : 0;
|
|
1343
|
+
});
|
|
1344
|
+
// Now as we process these intersections, we must sometimes adjust the order
|
|
1345
|
+
// to ensure that intersecting edges are always adjacent ...
|
|
1346
|
+
for (let i = 0; i < this.intersectList.length; ++i) {
|
|
1347
|
+
if (!this.edgesAdjacentInAEL(this.intersectList[i])) {
|
|
1348
|
+
let j = i + 1;
|
|
1349
|
+
while (!this.edgesAdjacentInAEL(this.intersectList[j]))
|
|
1350
|
+
j++;
|
|
1351
|
+
// swap
|
|
1352
|
+
[this.intersectList[j], this.intersectList[i]] = [this.intersectList[i], this.intersectList[j]];
|
|
1353
|
+
}
|
|
1354
|
+
const node = this.intersectList[i];
|
|
1355
|
+
this.intersectEdges(node.edge1, node.edge2, node.pt);
|
|
1356
|
+
this.swapPositionsInAEL(node.edge1, node.edge2);
|
|
1357
|
+
node.edge1.curX = node.pt.x;
|
|
1358
|
+
node.edge2.curX = node.pt.x;
|
|
1359
|
+
this.checkJoinLeft(node.edge2, node.pt, true);
|
|
1360
|
+
this.checkJoinRight(node.edge1, node.pt, true);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
edgesAdjacentInAEL(inode) {
|
|
1364
|
+
return (inode.edge1.nextInAEL === inode.edge2) || (inode.edge1.prevInAEL === inode.edge2);
|
|
1365
|
+
}
|
|
1366
|
+
adjustCurrXAndCopyToSEL(topY) {
|
|
1367
|
+
let ae = this.actives;
|
|
1368
|
+
this.sel = ae;
|
|
1369
|
+
while (ae !== null) {
|
|
1370
|
+
ae.prevInSEL = ae.prevInAEL;
|
|
1371
|
+
ae.nextInSEL = ae.nextInAEL;
|
|
1372
|
+
ae.jump = ae.nextInSEL;
|
|
1373
|
+
// it is safe to ignore 'joined' edges here because
|
|
1374
|
+
// if necessary they will be split in IntersectEdges()
|
|
1375
|
+
ae.curX = ClipperBase.topX(ae, topY);
|
|
1376
|
+
// NB don't update ae.curr.Y yet (see AddNewIntersectNode)
|
|
1377
|
+
ae = ae.nextInAEL;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
doMaxima(ae) {
|
|
1381
|
+
const prevE = ae.prevInAEL;
|
|
1382
|
+
let nextE = ae.nextInAEL;
|
|
1383
|
+
if (ClipperBase.isOpenEnd(ae)) {
|
|
1384
|
+
if (ClipperBase.isHotEdge(ae))
|
|
1385
|
+
this.addOutPt(ae, ae.top);
|
|
1386
|
+
if (ClipperBase.isHorizontal(ae))
|
|
1387
|
+
return nextE;
|
|
1388
|
+
if (ClipperBase.isHotEdge(ae)) {
|
|
1389
|
+
if (ClipperBase.isFront(ae)) {
|
|
1390
|
+
ae.outrec.frontEdge = null;
|
|
1391
|
+
}
|
|
1392
|
+
else {
|
|
1393
|
+
ae.outrec.backEdge = null;
|
|
1394
|
+
}
|
|
1395
|
+
ae.outrec = null;
|
|
1396
|
+
}
|
|
1397
|
+
this.deleteFromAEL(ae);
|
|
1398
|
+
return nextE;
|
|
1399
|
+
}
|
|
1400
|
+
const maxPair = ClipperBase.getMaximaPair(ae);
|
|
1401
|
+
if (maxPair === null)
|
|
1402
|
+
return nextE; // eMaxPair is horizontal
|
|
1403
|
+
if (this.isJoined(ae))
|
|
1404
|
+
this.split(ae, ae.top);
|
|
1405
|
+
if (this.isJoined(maxPair))
|
|
1406
|
+
this.split(maxPair, maxPair.top);
|
|
1407
|
+
// only non-horizontal maxima here.
|
|
1408
|
+
// process any edges between maxima pair ...
|
|
1409
|
+
while (nextE !== maxPair) {
|
|
1410
|
+
this.intersectEdges(ae, nextE, ae.top);
|
|
1411
|
+
this.swapPositionsInAEL(ae, nextE);
|
|
1412
|
+
nextE = ae.nextInAEL;
|
|
1413
|
+
}
|
|
1414
|
+
if (ClipperBase.isOpen(ae)) {
|
|
1415
|
+
if (ClipperBase.isHotEdge(ae)) {
|
|
1416
|
+
this.addLocalMaxPoly(ae, maxPair, ae.top);
|
|
1417
|
+
}
|
|
1418
|
+
this.deleteFromAEL(maxPair);
|
|
1419
|
+
this.deleteFromAEL(ae);
|
|
1420
|
+
return (prevE !== null ? prevE.nextInAEL : this.actives);
|
|
1421
|
+
}
|
|
1422
|
+
// here ae.nextInAel == ENext == EMaxPair ...
|
|
1423
|
+
if (ClipperBase.isHotEdge(ae)) {
|
|
1424
|
+
this.addLocalMaxPoly(ae, maxPair, ae.top);
|
|
1425
|
+
}
|
|
1426
|
+
this.deleteFromAEL(ae);
|
|
1427
|
+
this.deleteFromAEL(maxPair);
|
|
1428
|
+
return (prevE !== null ? prevE.nextInAEL : this.actives);
|
|
1429
|
+
}
|
|
1430
|
+
updateEdgeIntoAEL(ae) {
|
|
1431
|
+
ae.bot = { x: ae.top.x, y: ae.top.y }; // Create copy
|
|
1432
|
+
ae.vertexTop = ClipperBase.nextVertex(ae);
|
|
1433
|
+
ae.top = { x: ae.vertexTop.pt.x, y: ae.vertexTop.pt.y }; // Create copy
|
|
1434
|
+
ae.curX = ae.bot.x;
|
|
1435
|
+
ClipperBase.setDx(ae);
|
|
1436
|
+
if (this.isJoined(ae))
|
|
1437
|
+
this.split(ae, ae.bot);
|
|
1438
|
+
if (ClipperBase.isHorizontal(ae)) {
|
|
1439
|
+
if (!ClipperBase.isOpen(ae))
|
|
1440
|
+
this.trimHorz(ae, this.preserveCollinear);
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
this.insertScanline(ae.top.y);
|
|
1444
|
+
this.checkJoinLeft(ae, ae.bot);
|
|
1445
|
+
this.checkJoinRight(ae, ae.bot, true); // (#500)
|
|
1446
|
+
}
|
|
1447
|
+
trimHorz(horzEdge, preserveCollinear) {
|
|
1448
|
+
let wasTrimmed = false;
|
|
1449
|
+
let pt = ClipperBase.nextVertex(horzEdge).pt;
|
|
1450
|
+
while (pt.y === horzEdge.top.y) {
|
|
1451
|
+
// always trim 180 deg. spikes (in closed paths)
|
|
1452
|
+
// but otherwise break if preserveCollinear = true
|
|
1453
|
+
if (preserveCollinear &&
|
|
1454
|
+
(pt.x < horzEdge.top.x) !== (horzEdge.bot.x < horzEdge.top.x)) {
|
|
1455
|
+
break;
|
|
1456
|
+
}
|
|
1457
|
+
horzEdge.vertexTop = ClipperBase.nextVertex(horzEdge);
|
|
1458
|
+
horzEdge.top = pt;
|
|
1459
|
+
wasTrimmed = true;
|
|
1460
|
+
if (ClipperBase.isMaxima(horzEdge))
|
|
1461
|
+
break;
|
|
1462
|
+
pt = ClipperBase.nextVertex(horzEdge).pt;
|
|
1463
|
+
}
|
|
1464
|
+
if (wasTrimmed)
|
|
1465
|
+
ClipperBase.setDx(horzEdge); // +/-infinity
|
|
1466
|
+
}
|
|
1467
|
+
addToHorzSegList(op) {
|
|
1468
|
+
if (op.outrec.isOpen)
|
|
1469
|
+
return;
|
|
1470
|
+
this.horzSegList.push(new HorzSegment(op));
|
|
1471
|
+
}
|
|
1472
|
+
addNewIntersectNode(ae1, ae2, topY) {
|
|
1473
|
+
const intersectResult = Core_1.InternalClipper.getLineIntersectPt(ae1.bot, ae1.top, ae2.bot, ae2.top);
|
|
1474
|
+
let ip;
|
|
1475
|
+
if (!intersectResult.intersects) {
|
|
1476
|
+
ip = { x: ae1.curX, y: topY }; // parallel edges
|
|
1477
|
+
}
|
|
1478
|
+
else {
|
|
1479
|
+
ip = intersectResult.point;
|
|
1480
|
+
}
|
|
1481
|
+
if (ip.y > this.currentBotY || ip.y < topY) {
|
|
1482
|
+
const absDx1 = Math.abs(ae1.dx);
|
|
1483
|
+
const absDx2 = Math.abs(ae2.dx);
|
|
1484
|
+
if (absDx1 > 100 && absDx2 > 100) {
|
|
1485
|
+
if (absDx1 > absDx2) {
|
|
1486
|
+
ip = Core_1.InternalClipper.getClosestPtOnSegment(ip, ae1.bot, ae1.top);
|
|
1487
|
+
}
|
|
1488
|
+
else {
|
|
1489
|
+
ip = Core_1.InternalClipper.getClosestPtOnSegment(ip, ae2.bot, ae2.top);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
else if (absDx1 > 100) {
|
|
1493
|
+
ip = Core_1.InternalClipper.getClosestPtOnSegment(ip, ae1.bot, ae1.top);
|
|
1494
|
+
}
|
|
1495
|
+
else if (absDx2 > 100) {
|
|
1496
|
+
ip = Core_1.InternalClipper.getClosestPtOnSegment(ip, ae2.bot, ae2.top);
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
if (ip.y < topY)
|
|
1500
|
+
ip.y = topY;
|
|
1501
|
+
else
|
|
1502
|
+
ip.y = this.currentBotY;
|
|
1503
|
+
if (absDx1 < absDx2)
|
|
1504
|
+
ip.x = ClipperBase.topX(ae1, ip.y);
|
|
1505
|
+
else
|
|
1506
|
+
ip.x = ClipperBase.topX(ae2, ip.y);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
const node = createIntersectNode(ip, ae1, ae2);
|
|
1510
|
+
this.intersectList.push(node);
|
|
1511
|
+
}
|
|
1512
|
+
extractFromSEL(ae) {
|
|
1513
|
+
const res = ae.nextInSEL;
|
|
1514
|
+
if (res !== null) {
|
|
1515
|
+
res.prevInSEL = ae.prevInSEL;
|
|
1516
|
+
}
|
|
1517
|
+
ae.prevInSEL.nextInSEL = res;
|
|
1518
|
+
return res;
|
|
1519
|
+
}
|
|
1520
|
+
insert1Before2InSEL(ae1, ae2) {
|
|
1521
|
+
ae1.prevInSEL = ae2.prevInSEL;
|
|
1522
|
+
if (ae1.prevInSEL !== null) {
|
|
1523
|
+
ae1.prevInSEL.nextInSEL = ae1;
|
|
1524
|
+
}
|
|
1525
|
+
ae1.nextInSEL = ae2;
|
|
1526
|
+
ae2.prevInSEL = ae1;
|
|
1527
|
+
}
|
|
1528
|
+
getCurrYMaximaVertexOpen(ae) {
|
|
1529
|
+
let result = ae.vertexTop;
|
|
1530
|
+
if (ae.windDx > 0) {
|
|
1531
|
+
while (result.next.pt.y === result.pt.y &&
|
|
1532
|
+
((result.flags & (VertexFlags.OpenEnd | VertexFlags.LocalMax)) === VertexFlags.None))
|
|
1533
|
+
result = result.next;
|
|
1534
|
+
}
|
|
1535
|
+
else {
|
|
1536
|
+
while (result.prev.pt.y === result.pt.y &&
|
|
1537
|
+
((result.flags & (VertexFlags.OpenEnd | VertexFlags.LocalMax)) === VertexFlags.None))
|
|
1538
|
+
result = result.prev;
|
|
1539
|
+
}
|
|
1540
|
+
if (!ClipperBase.isMaxima(result))
|
|
1541
|
+
result = null; // not a maxima
|
|
1542
|
+
return result;
|
|
1543
|
+
}
|
|
1544
|
+
getCurrYMaximaVertex(ae) {
|
|
1545
|
+
let result = ae.vertexTop;
|
|
1546
|
+
if (ae.windDx > 0) {
|
|
1547
|
+
while (result.next.pt.y === result.pt.y)
|
|
1548
|
+
result = result.next;
|
|
1549
|
+
}
|
|
1550
|
+
else {
|
|
1551
|
+
while (result.prev.pt.y === result.pt.y)
|
|
1552
|
+
result = result.prev;
|
|
1553
|
+
}
|
|
1554
|
+
if (!ClipperBase.isMaxima(result))
|
|
1555
|
+
result = null; // not a maxima
|
|
1556
|
+
return result;
|
|
1557
|
+
}
|
|
1558
|
+
resetHorzDirection(horz, vertexMax) {
|
|
1559
|
+
if (horz.bot.x === horz.top.x) {
|
|
1560
|
+
// the horizontal edge is going nowhere ...
|
|
1561
|
+
const leftX = horz.curX;
|
|
1562
|
+
const rightX = horz.curX;
|
|
1563
|
+
let ae = horz.nextInAEL;
|
|
1564
|
+
while (ae !== null && ae.vertexTop !== vertexMax)
|
|
1565
|
+
ae = ae.nextInAEL;
|
|
1566
|
+
return { isLeftToRight: ae !== null, leftX, rightX };
|
|
1567
|
+
}
|
|
1568
|
+
if (horz.curX < horz.top.x) {
|
|
1569
|
+
return { isLeftToRight: true, leftX: horz.curX, rightX: horz.top.x };
|
|
1570
|
+
}
|
|
1571
|
+
else {
|
|
1572
|
+
return { isLeftToRight: false, leftX: horz.top.x, rightX: horz.curX };
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
getLastOp(hotEdge) {
|
|
1576
|
+
const outrec = hotEdge.outrec;
|
|
1577
|
+
return (hotEdge === outrec.frontEdge) ?
|
|
1578
|
+
outrec.pts : outrec.pts.next;
|
|
1579
|
+
}
|
|
1580
|
+
insertLeftEdge(ae) {
|
|
1581
|
+
if (this.actives === null) {
|
|
1582
|
+
ae.prevInAEL = null;
|
|
1583
|
+
ae.nextInAEL = null;
|
|
1584
|
+
this.actives = ae;
|
|
1585
|
+
}
|
|
1586
|
+
else if (!this.isValidAelOrder(this.actives, ae)) {
|
|
1587
|
+
ae.prevInAEL = null;
|
|
1588
|
+
ae.nextInAEL = this.actives;
|
|
1589
|
+
this.actives.prevInAEL = ae;
|
|
1590
|
+
this.actives = ae;
|
|
1591
|
+
}
|
|
1592
|
+
else {
|
|
1593
|
+
let ae2 = this.actives;
|
|
1594
|
+
while (ae2.nextInAEL !== null && this.isValidAelOrder(ae2.nextInAEL, ae)) {
|
|
1595
|
+
ae2 = ae2.nextInAEL;
|
|
1596
|
+
}
|
|
1597
|
+
//don't separate joined edges
|
|
1598
|
+
if (ae2.joinWith === JoinWith.Right)
|
|
1599
|
+
ae2 = ae2.nextInAEL;
|
|
1600
|
+
ae.nextInAEL = ae2.nextInAEL;
|
|
1601
|
+
if (ae2.nextInAEL !== null)
|
|
1602
|
+
ae2.nextInAEL.prevInAEL = ae;
|
|
1603
|
+
ae.prevInAEL = ae2;
|
|
1604
|
+
ae2.nextInAEL = ae;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
insertRightEdge(ae1, ae2) {
|
|
1608
|
+
ae2.nextInAEL = ae1.nextInAEL;
|
|
1609
|
+
if (ae1.nextInAEL !== null)
|
|
1610
|
+
ae1.nextInAEL.prevInAEL = ae2;
|
|
1611
|
+
ae2.prevInAEL = ae1;
|
|
1612
|
+
ae1.nextInAEL = ae2;
|
|
1613
|
+
}
|
|
1614
|
+
setWindCountForOpenPathEdge(ae) {
|
|
1615
|
+
let ae2 = this.actives;
|
|
1616
|
+
if (this.fillrule === Core_1.FillRule.EvenOdd) {
|
|
1617
|
+
let cnt1 = 0, cnt2 = 0;
|
|
1618
|
+
while (ae2 !== ae) {
|
|
1619
|
+
if (ClipperBase.getPolyType(ae2) === Core_1.PathType.Clip) {
|
|
1620
|
+
cnt2++;
|
|
1621
|
+
}
|
|
1622
|
+
else if (!ClipperBase.isOpen(ae2)) {
|
|
1623
|
+
cnt1++;
|
|
1624
|
+
}
|
|
1625
|
+
ae2 = ae2.nextInAEL;
|
|
1626
|
+
}
|
|
1627
|
+
ae.windCount = (ClipperBase.isOdd(cnt1) ? 1 : 0);
|
|
1628
|
+
ae.windCount2 = (ClipperBase.isOdd(cnt2) ? 1 : 0);
|
|
1629
|
+
}
|
|
1630
|
+
else {
|
|
1631
|
+
while (ae2 !== ae) {
|
|
1632
|
+
if (ClipperBase.getPolyType(ae2) === Core_1.PathType.Clip) {
|
|
1633
|
+
ae.windCount2 += ae2.windDx;
|
|
1634
|
+
}
|
|
1635
|
+
else if (!ClipperBase.isOpen(ae2)) {
|
|
1636
|
+
ae.windCount += ae2.windDx;
|
|
1637
|
+
}
|
|
1638
|
+
ae2 = ae2.nextInAEL;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
setWindCountForClosedPathEdge(ae) {
|
|
1643
|
+
// Wind counts refer to polygon regions not edges, so here an edge's WindCnt
|
|
1644
|
+
// indicates the higher of the wind counts for the two regions touching the
|
|
1645
|
+
// edge. (nb: Adjacent regions can only ever have their wind counts differ by
|
|
1646
|
+
// one. Also, open paths have no meaningful wind directions or counts.)
|
|
1647
|
+
let ae2 = ae.prevInAEL;
|
|
1648
|
+
// find the nearest closed path edge of the same PolyType in AEL (heading left)
|
|
1649
|
+
const pt = ClipperBase.getPolyType(ae);
|
|
1650
|
+
while (ae2 !== null && (ClipperBase.getPolyType(ae2) !== pt || ClipperBase.isOpen(ae2)))
|
|
1651
|
+
ae2 = ae2.prevInAEL;
|
|
1652
|
+
if (ae2 === null) {
|
|
1653
|
+
ae.windCount = ae.windDx;
|
|
1654
|
+
ae2 = this.actives;
|
|
1655
|
+
}
|
|
1656
|
+
else if (this.fillrule === Core_1.FillRule.EvenOdd) {
|
|
1657
|
+
ae.windCount = ae.windDx;
|
|
1658
|
+
ae.windCount2 = ae2.windCount2;
|
|
1659
|
+
ae2 = ae2.nextInAEL;
|
|
1660
|
+
}
|
|
1661
|
+
else {
|
|
1662
|
+
// NonZero, positive, or negative filling here ...
|
|
1663
|
+
// when e2's WindCnt is in the SAME direction as its WindDx,
|
|
1664
|
+
// then polygon will fill on the right of 'e2' (and 'e' will be inside)
|
|
1665
|
+
// nb: neither e2.WindCnt nor e2.WindDx should ever be 0.
|
|
1666
|
+
if (ae2.windCount * ae2.windDx < 0) {
|
|
1667
|
+
// opposite directions so 'ae' is outside 'ae2' ...
|
|
1668
|
+
if (Math.abs(ae2.windCount) > 1) {
|
|
1669
|
+
// outside prev poly but still inside another.
|
|
1670
|
+
if (ae2.windDx * ae.windDx < 0) {
|
|
1671
|
+
// reversing direction so use the same WC
|
|
1672
|
+
ae.windCount = ae2.windCount;
|
|
1673
|
+
}
|
|
1674
|
+
else {
|
|
1675
|
+
// otherwise keep 'reducing' the WC by 1 (i.e. towards 0) ...
|
|
1676
|
+
ae.windCount = ae2.windCount + ae.windDx;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
else {
|
|
1680
|
+
// now outside all polys of same polytype so set own WC ...
|
|
1681
|
+
ae.windCount = (ClipperBase.isOpen(ae) ? 1 : ae.windDx);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
else {
|
|
1685
|
+
//'ae' must be inside 'ae2'
|
|
1686
|
+
if (ae2.windDx * ae.windDx < 0) {
|
|
1687
|
+
// reversing direction so use the same WC
|
|
1688
|
+
ae.windCount = ae2.windCount;
|
|
1689
|
+
}
|
|
1690
|
+
else {
|
|
1691
|
+
// otherwise keep 'increasing' the WC by 1 (i.e. away from 0) ...
|
|
1692
|
+
ae.windCount = ae2.windCount + ae.windDx;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
ae.windCount2 = ae2.windCount2;
|
|
1696
|
+
ae2 = ae2.nextInAEL; // i.e. get ready to calc WindCnt2
|
|
1697
|
+
}
|
|
1698
|
+
// update windCount2 ...
|
|
1699
|
+
if (this.fillrule === Core_1.FillRule.EvenOdd) {
|
|
1700
|
+
while (ae2 !== ae) {
|
|
1701
|
+
if (ClipperBase.getPolyType(ae2) !== pt && !ClipperBase.isOpen(ae2)) {
|
|
1702
|
+
ae.windCount2 = (ae.windCount2 === 0 ? 1 : 0);
|
|
1703
|
+
}
|
|
1704
|
+
ae2 = ae2.nextInAEL;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
else {
|
|
1708
|
+
while (ae2 !== ae) {
|
|
1709
|
+
if (ClipperBase.getPolyType(ae2) !== pt && !ClipperBase.isOpen(ae2)) {
|
|
1710
|
+
ae.windCount2 += ae2.windDx;
|
|
1711
|
+
}
|
|
1712
|
+
ae2 = ae2.nextInAEL;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
isContributingOpen(ae) {
|
|
1717
|
+
let isInClip, isInSubj;
|
|
1718
|
+
switch (this.fillrule) {
|
|
1719
|
+
case Core_1.FillRule.Positive:
|
|
1720
|
+
isInSubj = ae.windCount > 0;
|
|
1721
|
+
isInClip = ae.windCount2 > 0;
|
|
1722
|
+
break;
|
|
1723
|
+
case Core_1.FillRule.Negative:
|
|
1724
|
+
isInSubj = ae.windCount < 0;
|
|
1725
|
+
isInClip = ae.windCount2 < 0;
|
|
1726
|
+
break;
|
|
1727
|
+
default:
|
|
1728
|
+
isInSubj = ae.windCount !== 0;
|
|
1729
|
+
isInClip = ae.windCount2 !== 0;
|
|
1730
|
+
break;
|
|
1731
|
+
}
|
|
1732
|
+
switch (this.cliptype) {
|
|
1733
|
+
case Core_1.ClipType.Intersection: return isInClip;
|
|
1734
|
+
case Core_1.ClipType.Union: return !isInSubj && !isInClip;
|
|
1735
|
+
default: return !isInClip;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
isContributingClosed(ae) {
|
|
1739
|
+
switch (this.fillrule) {
|
|
1740
|
+
case Core_1.FillRule.Positive:
|
|
1741
|
+
if (ae.windCount !== 1)
|
|
1742
|
+
return false;
|
|
1743
|
+
break;
|
|
1744
|
+
case Core_1.FillRule.Negative:
|
|
1745
|
+
if (ae.windCount !== -1)
|
|
1746
|
+
return false;
|
|
1747
|
+
break;
|
|
1748
|
+
case Core_1.FillRule.NonZero:
|
|
1749
|
+
if (Math.abs(ae.windCount) !== 1)
|
|
1750
|
+
return false;
|
|
1751
|
+
break;
|
|
1752
|
+
}
|
|
1753
|
+
switch (this.cliptype) {
|
|
1754
|
+
case Core_1.ClipType.Intersection:
|
|
1755
|
+
return this.fillrule === Core_1.FillRule.Positive ? ae.windCount2 > 0 :
|
|
1756
|
+
this.fillrule === Core_1.FillRule.Negative ? ae.windCount2 < 0 :
|
|
1757
|
+
ae.windCount2 !== 0;
|
|
1758
|
+
case Core_1.ClipType.Union:
|
|
1759
|
+
return this.fillrule === Core_1.FillRule.Positive ? ae.windCount2 <= 0 :
|
|
1760
|
+
this.fillrule === Core_1.FillRule.Negative ? ae.windCount2 >= 0 :
|
|
1761
|
+
ae.windCount2 === 0;
|
|
1762
|
+
case Core_1.ClipType.Difference:
|
|
1763
|
+
const result = this.fillrule === Core_1.FillRule.Positive ? (ae.windCount2 <= 0) :
|
|
1764
|
+
this.fillrule === Core_1.FillRule.Negative ? (ae.windCount2 >= 0) :
|
|
1765
|
+
(ae.windCount2 === 0);
|
|
1766
|
+
return (ClipperBase.getPolyType(ae) === Core_1.PathType.Subject) ? result : !result;
|
|
1767
|
+
case Core_1.ClipType.Xor:
|
|
1768
|
+
return true; // XOr is always contributing unless open
|
|
1769
|
+
default:
|
|
1770
|
+
return false;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
addLocalMinPoly(ae1, ae2, pt, isNew = false) {
|
|
1774
|
+
const outrec = this.newOutRec();
|
|
1775
|
+
ae1.outrec = outrec;
|
|
1776
|
+
ae2.outrec = outrec;
|
|
1777
|
+
if (ClipperBase.isOpen(ae1)) {
|
|
1778
|
+
outrec.owner = null;
|
|
1779
|
+
outrec.isOpen = true;
|
|
1780
|
+
if (ae1.windDx > 0) {
|
|
1781
|
+
this.setSides(outrec, ae1, ae2);
|
|
1782
|
+
}
|
|
1783
|
+
else {
|
|
1784
|
+
this.setSides(outrec, ae2, ae1);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
else {
|
|
1788
|
+
outrec.isOpen = false;
|
|
1789
|
+
const prevHotEdge = ClipperBase.getPrevHotEdge(ae1);
|
|
1790
|
+
// e.windDx is the winding direction of the **input** paths
|
|
1791
|
+
// and unrelated to the winding direction of output polygons.
|
|
1792
|
+
// Output orientation is determined by e.outrec.frontE which is
|
|
1793
|
+
// the ascending edge (see AddLocalMinPoly).
|
|
1794
|
+
if (prevHotEdge !== null) {
|
|
1795
|
+
if (this.usingPolytree) {
|
|
1796
|
+
this.setOwner(outrec, prevHotEdge.outrec);
|
|
1797
|
+
}
|
|
1798
|
+
outrec.owner = prevHotEdge.outrec;
|
|
1799
|
+
if (this.outrecIsAscending(prevHotEdge) === isNew) {
|
|
1800
|
+
this.setSides(outrec, ae2, ae1);
|
|
1801
|
+
}
|
|
1802
|
+
else {
|
|
1803
|
+
this.setSides(outrec, ae1, ae2);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
else {
|
|
1807
|
+
outrec.owner = null;
|
|
1808
|
+
if (isNew) {
|
|
1809
|
+
this.setSides(outrec, ae1, ae2);
|
|
1810
|
+
}
|
|
1811
|
+
else {
|
|
1812
|
+
this.setSides(outrec, ae2, ae1);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
const op = new OutPt(pt, outrec);
|
|
1817
|
+
outrec.pts = op;
|
|
1818
|
+
return op;
|
|
1819
|
+
}
|
|
1820
|
+
outrecIsAscending(hotEdge) {
|
|
1821
|
+
return hotEdge === hotEdge.outrec.frontEdge;
|
|
1822
|
+
}
|
|
1823
|
+
newOutRec() {
|
|
1824
|
+
const result = new OutRec();
|
|
1825
|
+
result.idx = this.outrecList.length;
|
|
1826
|
+
this.outrecList.push(result);
|
|
1827
|
+
return result;
|
|
1828
|
+
}
|
|
1829
|
+
startOpenPath(ae, pt) {
|
|
1830
|
+
const outrec = this.newOutRec();
|
|
1831
|
+
outrec.isOpen = true;
|
|
1832
|
+
if (ae.windDx > 0) {
|
|
1833
|
+
outrec.frontEdge = ae;
|
|
1834
|
+
outrec.backEdge = null;
|
|
1835
|
+
}
|
|
1836
|
+
else {
|
|
1837
|
+
outrec.frontEdge = null;
|
|
1838
|
+
outrec.backEdge = ae;
|
|
1839
|
+
}
|
|
1840
|
+
ae.outrec = outrec;
|
|
1841
|
+
const op = new OutPt(pt, outrec);
|
|
1842
|
+
outrec.pts = op;
|
|
1843
|
+
return op;
|
|
1844
|
+
}
|
|
1845
|
+
checkJoinLeft(ae, pt, checkCurrX = false) {
|
|
1846
|
+
const prev = ae.prevInAEL;
|
|
1847
|
+
if (prev === null ||
|
|
1848
|
+
!ClipperBase.isHotEdge(ae) || !ClipperBase.isHotEdge(prev) ||
|
|
1849
|
+
ClipperBase.isHorizontal(ae) || ClipperBase.isHorizontal(prev) ||
|
|
1850
|
+
ClipperBase.isOpen(ae) || ClipperBase.isOpen(prev))
|
|
1851
|
+
return;
|
|
1852
|
+
if ((pt.y < ae.top.y + 2 || pt.y < prev.top.y + 2) && // avoid trivial joins
|
|
1853
|
+
((ae.bot.y > pt.y) || (prev.bot.y > pt.y)))
|
|
1854
|
+
return; // (#490)
|
|
1855
|
+
if (checkCurrX) {
|
|
1856
|
+
if (this.perpendicDistFromLineSqrd(pt, prev.bot, prev.top) > 0.25)
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
else if (ae.curX !== prev.curX)
|
|
1860
|
+
return;
|
|
1861
|
+
if (!Core_1.InternalClipper.isCollinear(ae.top, pt, prev.top))
|
|
1862
|
+
return;
|
|
1863
|
+
if (ae.outrec.idx === prev.outrec.idx) {
|
|
1864
|
+
this.addLocalMaxPoly(prev, ae, pt);
|
|
1865
|
+
}
|
|
1866
|
+
else if (ae.outrec.idx < prev.outrec.idx) {
|
|
1867
|
+
this.joinOutrecPaths(ae, prev);
|
|
1868
|
+
}
|
|
1869
|
+
else {
|
|
1870
|
+
this.joinOutrecPaths(prev, ae);
|
|
1871
|
+
}
|
|
1872
|
+
prev.joinWith = JoinWith.Right;
|
|
1873
|
+
ae.joinWith = JoinWith.Left;
|
|
1874
|
+
}
|
|
1875
|
+
checkJoinRight(ae, pt, checkCurrX = false) {
|
|
1876
|
+
const next = ae.nextInAEL;
|
|
1877
|
+
if (next === null ||
|
|
1878
|
+
!ClipperBase.isHotEdge(ae) || !ClipperBase.isHotEdge(next) ||
|
|
1879
|
+
ClipperBase.isHorizontal(ae) || ClipperBase.isHorizontal(next) ||
|
|
1880
|
+
ClipperBase.isOpen(ae) || ClipperBase.isOpen(next))
|
|
1881
|
+
return;
|
|
1882
|
+
if ((pt.y < ae.top.y + 2 || pt.y < next.top.y + 2) && // avoid trivial joins
|
|
1883
|
+
((ae.bot.y > pt.y) || (next.bot.y > pt.y)))
|
|
1884
|
+
return; // (#490)
|
|
1885
|
+
if (checkCurrX) {
|
|
1886
|
+
if (this.perpendicDistFromLineSqrd(pt, next.bot, next.top) > 0.25)
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
else if (ae.curX !== next.curX)
|
|
1890
|
+
return;
|
|
1891
|
+
if (!Core_1.InternalClipper.isCollinear(ae.top, pt, next.top))
|
|
1892
|
+
return;
|
|
1893
|
+
if (ae.outrec.idx === next.outrec.idx) {
|
|
1894
|
+
this.addLocalMaxPoly(ae, next, pt);
|
|
1895
|
+
}
|
|
1896
|
+
else if (ae.outrec.idx < next.outrec.idx) {
|
|
1897
|
+
this.joinOutrecPaths(ae, next);
|
|
1898
|
+
}
|
|
1899
|
+
else {
|
|
1900
|
+
this.joinOutrecPaths(next, ae);
|
|
1901
|
+
}
|
|
1902
|
+
ae.joinWith = JoinWith.Right;
|
|
1903
|
+
next.joinWith = JoinWith.Left;
|
|
1904
|
+
}
|
|
1905
|
+
perpendicDistFromLineSqrd(pt, line1, line2) {
|
|
1906
|
+
const a = pt.x - line1.x;
|
|
1907
|
+
const b = pt.y - line1.y;
|
|
1908
|
+
const c = line2.x - line1.x;
|
|
1909
|
+
const d = line2.y - line1.y;
|
|
1910
|
+
if (c === 0 && d === 0)
|
|
1911
|
+
return 0;
|
|
1912
|
+
return ((a * d - c * b) * (a * d - c * b)) / (c * c + d * d);
|
|
1913
|
+
}
|
|
1914
|
+
intersectEdges(ae1, ae2, pt) {
|
|
1915
|
+
let resultOp = null;
|
|
1916
|
+
// MANAGE OPEN PATH INTERSECTIONS SEPARATELY ...
|
|
1917
|
+
if (this.hasOpenPaths && (ClipperBase.isOpen(ae1) || ClipperBase.isOpen(ae2))) {
|
|
1918
|
+
if (ClipperBase.isOpen(ae1) && ClipperBase.isOpen(ae2))
|
|
1919
|
+
return;
|
|
1920
|
+
// the following line avoids duplicating quite a bit of code
|
|
1921
|
+
if (ClipperBase.isOpen(ae2))
|
|
1922
|
+
[ae1, ae2] = ClipperBase.swapActives(ae1, ae2);
|
|
1923
|
+
if (this.isJoined(ae2))
|
|
1924
|
+
this.split(ae2, pt); // needed for safety
|
|
1925
|
+
if (this.cliptype === Core_1.ClipType.Union) {
|
|
1926
|
+
if (!ClipperBase.isHotEdge(ae2))
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
else if (ae2.localMin.polytype === Core_1.PathType.Subject)
|
|
1930
|
+
return;
|
|
1931
|
+
switch (this.fillrule) {
|
|
1932
|
+
case Core_1.FillRule.Positive:
|
|
1933
|
+
if (ae2.windCount !== 1)
|
|
1934
|
+
return;
|
|
1935
|
+
break;
|
|
1936
|
+
case Core_1.FillRule.Negative:
|
|
1937
|
+
if (ae2.windCount !== -1)
|
|
1938
|
+
return;
|
|
1939
|
+
break;
|
|
1940
|
+
default:
|
|
1941
|
+
if (Math.abs(ae2.windCount) !== 1)
|
|
1942
|
+
return;
|
|
1943
|
+
break;
|
|
1944
|
+
}
|
|
1945
|
+
// toggle contribution ...
|
|
1946
|
+
if (ClipperBase.isHotEdge(ae1)) {
|
|
1947
|
+
resultOp = this.addOutPt(ae1, pt);
|
|
1948
|
+
if (ClipperBase.isFront(ae1)) {
|
|
1949
|
+
ae1.outrec.frontEdge = null;
|
|
1950
|
+
}
|
|
1951
|
+
else {
|
|
1952
|
+
ae1.outrec.backEdge = null;
|
|
1953
|
+
}
|
|
1954
|
+
ae1.outrec = null;
|
|
1955
|
+
}
|
|
1956
|
+
// horizontal edges can pass under open paths at a LocMins
|
|
1957
|
+
else if (pt.x === ae1.localMin.vertex.pt.x && pt.y === ae1.localMin.vertex.pt.y &&
|
|
1958
|
+
!ClipperBase.isOpenEndVertex(ae1.localMin.vertex)) {
|
|
1959
|
+
// find the other side of the LocMin and
|
|
1960
|
+
// if it's 'hot' join up with it ...
|
|
1961
|
+
const ae3 = this.findEdgeWithMatchingLocMin(ae1);
|
|
1962
|
+
if (ae3 !== null && ClipperBase.isHotEdge(ae3)) {
|
|
1963
|
+
ae1.outrec = ae3.outrec;
|
|
1964
|
+
if (ae1.windDx > 0) {
|
|
1965
|
+
this.setSides(ae3.outrec, ae1, ae3);
|
|
1966
|
+
}
|
|
1967
|
+
else {
|
|
1968
|
+
this.setSides(ae3.outrec, ae3, ae1);
|
|
1969
|
+
}
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
resultOp = this.startOpenPath(ae1, pt);
|
|
1973
|
+
}
|
|
1974
|
+
else {
|
|
1975
|
+
resultOp = this.startOpenPath(ae1, pt);
|
|
1976
|
+
}
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
// MANAGING CLOSED PATHS FROM HERE ON
|
|
1980
|
+
if (this.isJoined(ae1))
|
|
1981
|
+
this.split(ae1, pt);
|
|
1982
|
+
if (this.isJoined(ae2))
|
|
1983
|
+
this.split(ae2, pt);
|
|
1984
|
+
// UPDATE WINDING COUNTS...
|
|
1985
|
+
let oldE1WindCount, oldE2WindCount;
|
|
1986
|
+
if (ae1.localMin.polytype === ae2.localMin.polytype) {
|
|
1987
|
+
if (this.fillrule === Core_1.FillRule.EvenOdd) {
|
|
1988
|
+
oldE1WindCount = ae1.windCount;
|
|
1989
|
+
ae1.windCount = ae2.windCount;
|
|
1990
|
+
ae2.windCount = oldE1WindCount;
|
|
1991
|
+
}
|
|
1992
|
+
else {
|
|
1993
|
+
if (ae1.windCount + ae2.windDx === 0) {
|
|
1994
|
+
ae1.windCount = -ae1.windCount;
|
|
1995
|
+
}
|
|
1996
|
+
else {
|
|
1997
|
+
ae1.windCount += ae2.windDx;
|
|
1998
|
+
}
|
|
1999
|
+
if (ae2.windCount - ae1.windDx === 0) {
|
|
2000
|
+
ae2.windCount = -ae2.windCount;
|
|
2001
|
+
}
|
|
2002
|
+
else {
|
|
2003
|
+
ae2.windCount -= ae1.windDx;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
else {
|
|
2008
|
+
if (this.fillrule !== Core_1.FillRule.EvenOdd) {
|
|
2009
|
+
ae1.windCount2 += ae2.windDx;
|
|
2010
|
+
}
|
|
2011
|
+
else {
|
|
2012
|
+
ae1.windCount2 = (ae1.windCount2 === 0 ? 1 : 0);
|
|
2013
|
+
}
|
|
2014
|
+
if (this.fillrule !== Core_1.FillRule.EvenOdd) {
|
|
2015
|
+
ae2.windCount2 -= ae1.windDx;
|
|
2016
|
+
}
|
|
2017
|
+
else {
|
|
2018
|
+
ae2.windCount2 = (ae2.windCount2 === 0 ? 1 : 0);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
switch (this.fillrule) {
|
|
2022
|
+
case Core_1.FillRule.Positive:
|
|
2023
|
+
oldE1WindCount = ae1.windCount;
|
|
2024
|
+
oldE2WindCount = ae2.windCount;
|
|
2025
|
+
break;
|
|
2026
|
+
case Core_1.FillRule.Negative:
|
|
2027
|
+
oldE1WindCount = -ae1.windCount;
|
|
2028
|
+
oldE2WindCount = -ae2.windCount;
|
|
2029
|
+
break;
|
|
2030
|
+
default:
|
|
2031
|
+
oldE1WindCount = Math.abs(ae1.windCount);
|
|
2032
|
+
oldE2WindCount = Math.abs(ae2.windCount);
|
|
2033
|
+
break;
|
|
2034
|
+
}
|
|
2035
|
+
const e1WindCountIs0or1 = oldE1WindCount === 0 || oldE1WindCount === 1;
|
|
2036
|
+
const e2WindCountIs0or1 = oldE2WindCount === 0 || oldE2WindCount === 1;
|
|
2037
|
+
if ((!ClipperBase.isHotEdge(ae1) && !e1WindCountIs0or1) ||
|
|
2038
|
+
(!ClipperBase.isHotEdge(ae2) && !e2WindCountIs0or1))
|
|
2039
|
+
return;
|
|
2040
|
+
// NOW PROCESS THE INTERSECTION ...
|
|
2041
|
+
// if both edges are 'hot' ...
|
|
2042
|
+
if (ClipperBase.isHotEdge(ae1) && ClipperBase.isHotEdge(ae2)) {
|
|
2043
|
+
if ((oldE1WindCount !== 0 && oldE1WindCount !== 1) || (oldE2WindCount !== 0 && oldE2WindCount !== 1) ||
|
|
2044
|
+
(ae1.localMin.polytype !== ae2.localMin.polytype && this.cliptype !== Core_1.ClipType.Xor)) {
|
|
2045
|
+
resultOp = this.addLocalMaxPoly(ae1, ae2, pt);
|
|
2046
|
+
}
|
|
2047
|
+
else if (ClipperBase.isFront(ae1) || (ae1.outrec === ae2.outrec)) {
|
|
2048
|
+
// this 'else if' condition isn't strictly needed but
|
|
2049
|
+
// it's sensible to split polygons that only touch at
|
|
2050
|
+
// a common vertex (not at common edges).
|
|
2051
|
+
resultOp = this.addLocalMaxPoly(ae1, ae2, pt);
|
|
2052
|
+
// C# non-USINGZ version calls AddLocalMinPoly here (without the max poly call)
|
|
2053
|
+
this.addLocalMinPoly(ae1, ae2, pt);
|
|
2054
|
+
}
|
|
2055
|
+
else {
|
|
2056
|
+
// can't treat as maxima & minima
|
|
2057
|
+
resultOp = this.addOutPt(ae1, pt);
|
|
2058
|
+
this.addOutPt(ae2, pt);
|
|
2059
|
+
this.swapOutrecs(ae1, ae2);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
// if one or other edge is 'hot' ...
|
|
2063
|
+
else if (ClipperBase.isHotEdge(ae1)) {
|
|
2064
|
+
resultOp = this.addOutPt(ae1, pt);
|
|
2065
|
+
this.swapOutrecs(ae1, ae2);
|
|
2066
|
+
}
|
|
2067
|
+
else if (ClipperBase.isHotEdge(ae2)) {
|
|
2068
|
+
resultOp = this.addOutPt(ae2, pt);
|
|
2069
|
+
this.swapOutrecs(ae1, ae2);
|
|
2070
|
+
}
|
|
2071
|
+
// neither edge is 'hot'
|
|
2072
|
+
else {
|
|
2073
|
+
let e1Wc2, e2Wc2;
|
|
2074
|
+
switch (this.fillrule) {
|
|
2075
|
+
case Core_1.FillRule.Positive:
|
|
2076
|
+
e1Wc2 = ae1.windCount2;
|
|
2077
|
+
e2Wc2 = ae2.windCount2;
|
|
2078
|
+
break;
|
|
2079
|
+
case Core_1.FillRule.Negative:
|
|
2080
|
+
e1Wc2 = -ae1.windCount2;
|
|
2081
|
+
e2Wc2 = -ae2.windCount2;
|
|
2082
|
+
break;
|
|
2083
|
+
default:
|
|
2084
|
+
e1Wc2 = Math.abs(ae1.windCount2);
|
|
2085
|
+
e2Wc2 = Math.abs(ae2.windCount2);
|
|
2086
|
+
break;
|
|
2087
|
+
}
|
|
2088
|
+
if (!ClipperBase.isSamePolyType(ae1, ae2)) {
|
|
2089
|
+
resultOp = this.addLocalMinPoly(ae1, ae2, pt);
|
|
2090
|
+
}
|
|
2091
|
+
else if (oldE1WindCount === 1 && oldE2WindCount === 1) {
|
|
2092
|
+
resultOp = null;
|
|
2093
|
+
switch (this.cliptype) {
|
|
2094
|
+
case Core_1.ClipType.Union:
|
|
2095
|
+
if (e1Wc2 > 0 && e2Wc2 > 0)
|
|
2096
|
+
return;
|
|
2097
|
+
resultOp = this.addLocalMinPoly(ae1, ae2, pt);
|
|
2098
|
+
break;
|
|
2099
|
+
case Core_1.ClipType.Difference:
|
|
2100
|
+
if (((ClipperBase.getPolyType(ae1) === Core_1.PathType.Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
|
|
2101
|
+
((ClipperBase.getPolyType(ae1) === Core_1.PathType.Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) {
|
|
2102
|
+
resultOp = this.addLocalMinPoly(ae1, ae2, pt);
|
|
2103
|
+
}
|
|
2104
|
+
break;
|
|
2105
|
+
case Core_1.ClipType.Xor:
|
|
2106
|
+
resultOp = this.addLocalMinPoly(ae1, ae2, pt);
|
|
2107
|
+
break;
|
|
2108
|
+
default: // ClipType.Intersection:
|
|
2109
|
+
if (e1Wc2 <= 0 || e2Wc2 <= 0)
|
|
2110
|
+
return;
|
|
2111
|
+
resultOp = this.addLocalMinPoly(ae1, ae2, pt);
|
|
2112
|
+
break;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
swapPositionsInAEL(ae1, ae2) {
|
|
2118
|
+
// preconditon: ae1 must be immediately to the left of ae2
|
|
2119
|
+
const next = ae2.nextInAEL;
|
|
2120
|
+
if (next !== null)
|
|
2121
|
+
next.prevInAEL = ae1;
|
|
2122
|
+
const prev = ae1.prevInAEL;
|
|
2123
|
+
if (prev !== null)
|
|
2124
|
+
prev.nextInAEL = ae2;
|
|
2125
|
+
ae2.prevInAEL = prev;
|
|
2126
|
+
ae2.nextInAEL = ae1;
|
|
2127
|
+
ae1.prevInAEL = ae2;
|
|
2128
|
+
ae1.nextInAEL = next;
|
|
2129
|
+
if (ae2.prevInAEL === null)
|
|
2130
|
+
this.actives = ae2;
|
|
2131
|
+
}
|
|
2132
|
+
isValidAelOrder(resident, newcomer) {
|
|
2133
|
+
if (newcomer.curX !== resident.curX) {
|
|
2134
|
+
return newcomer.curX > resident.curX;
|
|
2135
|
+
}
|
|
2136
|
+
// get the turning direction a1.top, a2.bot, a2.top
|
|
2137
|
+
const d = Core_1.InternalClipper.crossProduct(resident.top, newcomer.bot, newcomer.top);
|
|
2138
|
+
if (d !== 0)
|
|
2139
|
+
return d < 0;
|
|
2140
|
+
// edges must be collinear to get here
|
|
2141
|
+
// for starting open paths, place them according to
|
|
2142
|
+
// the direction they're about to turn
|
|
2143
|
+
if (!ClipperBase.isMaxima(resident) && (resident.top.y > newcomer.top.y)) {
|
|
2144
|
+
return Core_1.InternalClipper.crossProduct(newcomer.bot, resident.top, ClipperBase.nextVertex(resident).pt) <= 0;
|
|
2145
|
+
}
|
|
2146
|
+
if (!ClipperBase.isMaxima(newcomer) && (newcomer.top.y > resident.top.y)) {
|
|
2147
|
+
return Core_1.InternalClipper.crossProduct(newcomer.bot, newcomer.top, ClipperBase.nextVertex(newcomer).pt) >= 0;
|
|
2148
|
+
}
|
|
2149
|
+
const y = newcomer.bot.y;
|
|
2150
|
+
const newcomerIsLeft = newcomer.isLeftBound;
|
|
2151
|
+
if (resident.bot.y !== y || resident.localMin.vertex.pt.y !== y) {
|
|
2152
|
+
return newcomer.isLeftBound;
|
|
2153
|
+
}
|
|
2154
|
+
// resident must also have just been inserted
|
|
2155
|
+
if (resident.isLeftBound !== newcomerIsLeft) {
|
|
2156
|
+
return newcomerIsLeft;
|
|
2157
|
+
}
|
|
2158
|
+
if (Core_1.InternalClipper.isCollinear(ClipperBase.prevPrevVertex(resident).pt, resident.bot, resident.top))
|
|
2159
|
+
return true;
|
|
2160
|
+
// compare turning direction of the alternate bound
|
|
2161
|
+
return (Core_1.InternalClipper.crossProduct(ClipperBase.prevPrevVertex(resident).pt, newcomer.bot, ClipperBase.prevPrevVertex(newcomer).pt) > 0) === newcomerIsLeft;
|
|
2162
|
+
}
|
|
2163
|
+
isJoined(e) {
|
|
2164
|
+
return e.joinWith !== JoinWith.None;
|
|
2165
|
+
}
|
|
2166
|
+
split(e, currPt) {
|
|
2167
|
+
if (e.joinWith === JoinWith.Right) {
|
|
2168
|
+
e.joinWith = JoinWith.None;
|
|
2169
|
+
e.nextInAEL.joinWith = JoinWith.None;
|
|
2170
|
+
this.addLocalMinPoly(e, e.nextInAEL, currPt, true);
|
|
2171
|
+
}
|
|
2172
|
+
else {
|
|
2173
|
+
e.joinWith = JoinWith.None;
|
|
2174
|
+
e.prevInAEL.joinWith = JoinWith.None;
|
|
2175
|
+
this.addLocalMinPoly(e.prevInAEL, e, currPt, true);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
setSides(outrec, startEdge, endEdge) {
|
|
2179
|
+
outrec.frontEdge = startEdge;
|
|
2180
|
+
outrec.backEdge = endEdge;
|
|
2181
|
+
}
|
|
2182
|
+
findEdgeWithMatchingLocMin(e) {
|
|
2183
|
+
let result = e.nextInAEL;
|
|
2184
|
+
while (result !== null) {
|
|
2185
|
+
if (result.localMin?.equals(e.localMin))
|
|
2186
|
+
return result;
|
|
2187
|
+
if (!ClipperBase.isHorizontal(result) && !(e.bot.x === result.bot.x && e.bot.y === result.bot.y))
|
|
2188
|
+
result = null;
|
|
2189
|
+
else
|
|
2190
|
+
result = result.nextInAEL;
|
|
2191
|
+
}
|
|
2192
|
+
result = e.prevInAEL;
|
|
2193
|
+
while (result !== null) {
|
|
2194
|
+
if (result.localMin?.equals(e.localMin))
|
|
2195
|
+
return result;
|
|
2196
|
+
if (!ClipperBase.isHorizontal(result) && !(e.bot.x === result.bot.x && e.bot.y === result.bot.y))
|
|
2197
|
+
return null;
|
|
2198
|
+
result = result.prevInAEL;
|
|
2199
|
+
}
|
|
2200
|
+
return result;
|
|
2201
|
+
}
|
|
2202
|
+
addOutPt(ae, pt) {
|
|
2203
|
+
// Outrec.OutPts: a circular doubly-linked-list of POutPt where ...
|
|
2204
|
+
// opFront[.Prev]* ~~~> opBack & opBack == opFront.Next
|
|
2205
|
+
const outrec = ae.outrec;
|
|
2206
|
+
const toFront = ClipperBase.isFront(ae);
|
|
2207
|
+
const opFront = outrec.pts;
|
|
2208
|
+
const opBack = opFront.next;
|
|
2209
|
+
if (toFront && pt.x === opFront.pt.x && pt.y === opFront.pt.y) {
|
|
2210
|
+
return opFront;
|
|
2211
|
+
}
|
|
2212
|
+
else if (!toFront && pt.x === opBack.pt.x && pt.y === opBack.pt.y) {
|
|
2213
|
+
return opBack;
|
|
2214
|
+
}
|
|
2215
|
+
const newOp = new OutPt(pt, outrec);
|
|
2216
|
+
opBack.prev = newOp;
|
|
2217
|
+
newOp.prev = opFront;
|
|
2218
|
+
newOp.next = opBack;
|
|
2219
|
+
opFront.next = newOp;
|
|
2220
|
+
if (toFront)
|
|
2221
|
+
outrec.pts = newOp;
|
|
2222
|
+
return newOp;
|
|
2223
|
+
}
|
|
2224
|
+
addLocalMaxPoly(ae1, ae2, pt) {
|
|
2225
|
+
if (this.isJoined(ae1))
|
|
2226
|
+
this.split(ae1, pt);
|
|
2227
|
+
if (this.isJoined(ae2))
|
|
2228
|
+
this.split(ae2, pt);
|
|
2229
|
+
if (ClipperBase.isFront(ae1) === ClipperBase.isFront(ae2)) {
|
|
2230
|
+
if (ClipperBase.isOpenEnd(ae1)) {
|
|
2231
|
+
this.swapFrontBackSides(ae1.outrec);
|
|
2232
|
+
}
|
|
2233
|
+
else if (ClipperBase.isOpenEnd(ae2)) {
|
|
2234
|
+
this.swapFrontBackSides(ae2.outrec);
|
|
2235
|
+
}
|
|
2236
|
+
else {
|
|
2237
|
+
this.succeeded = false;
|
|
2238
|
+
return null;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
const result = this.addOutPt(ae1, pt);
|
|
2242
|
+
if (ae1.outrec === ae2.outrec) {
|
|
2243
|
+
const outrec = ae1.outrec;
|
|
2244
|
+
outrec.pts = result;
|
|
2245
|
+
if (this.usingPolytree) {
|
|
2246
|
+
const e = ClipperBase.getPrevHotEdge(ae1);
|
|
2247
|
+
if (e === null) {
|
|
2248
|
+
outrec.owner = null;
|
|
2249
|
+
}
|
|
2250
|
+
else {
|
|
2251
|
+
this.setOwner(outrec, e.outrec);
|
|
2252
|
+
}
|
|
2253
|
+
// nb: outRec.owner here is likely NOT the real
|
|
2254
|
+
// owner but this will be fixed in DeepCheckOwner()
|
|
2255
|
+
}
|
|
2256
|
+
this.uncoupleOutRec(ae1);
|
|
2257
|
+
}
|
|
2258
|
+
// and to preserve the winding orientation of outrec ...
|
|
2259
|
+
else if (ClipperBase.isOpen(ae1)) {
|
|
2260
|
+
if (ae1.windDx < 0) {
|
|
2261
|
+
this.joinOutrecPaths(ae1, ae2);
|
|
2262
|
+
}
|
|
2263
|
+
else {
|
|
2264
|
+
this.joinOutrecPaths(ae2, ae1);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
else if (ae1.outrec.idx < ae2.outrec.idx) {
|
|
2268
|
+
this.joinOutrecPaths(ae1, ae2);
|
|
2269
|
+
}
|
|
2270
|
+
else {
|
|
2271
|
+
this.joinOutrecPaths(ae2, ae1);
|
|
2272
|
+
}
|
|
2273
|
+
return result;
|
|
2274
|
+
}
|
|
2275
|
+
swapFrontBackSides(outrec) {
|
|
2276
|
+
// while this proc. is needed for open paths
|
|
2277
|
+
// it's almost never needed for closed paths
|
|
2278
|
+
const ae2 = outrec.frontEdge;
|
|
2279
|
+
outrec.frontEdge = outrec.backEdge;
|
|
2280
|
+
outrec.backEdge = ae2;
|
|
2281
|
+
outrec.pts = outrec.pts.next;
|
|
2282
|
+
}
|
|
2283
|
+
setOwner(outrec, newOwner) {
|
|
2284
|
+
//precondition1: new_owner is never null
|
|
2285
|
+
while (newOwner.owner !== null && newOwner.owner.pts === null) {
|
|
2286
|
+
newOwner.owner = newOwner.owner.owner;
|
|
2287
|
+
}
|
|
2288
|
+
//make sure that outrec isn't an owner of newOwner
|
|
2289
|
+
let tmp = newOwner;
|
|
2290
|
+
while (tmp !== null && tmp !== outrec) {
|
|
2291
|
+
tmp = tmp.owner;
|
|
2292
|
+
}
|
|
2293
|
+
if (tmp !== null) {
|
|
2294
|
+
newOwner.owner = outrec.owner;
|
|
2295
|
+
}
|
|
2296
|
+
outrec.owner = newOwner;
|
|
2297
|
+
}
|
|
2298
|
+
uncoupleOutRec(ae) {
|
|
2299
|
+
const outrec = ae.outrec;
|
|
2300
|
+
if (outrec === null)
|
|
2301
|
+
return;
|
|
2302
|
+
outrec.frontEdge.outrec = null;
|
|
2303
|
+
outrec.backEdge.outrec = null;
|
|
2304
|
+
outrec.frontEdge = null;
|
|
2305
|
+
outrec.backEdge = null;
|
|
2306
|
+
}
|
|
2307
|
+
joinOutrecPaths(ae1, ae2) {
|
|
2308
|
+
// join ae2 outrec path onto ae1 outrec path and then delete ae2 outrec path
|
|
2309
|
+
// pointers. (NB Only very rarely do the joining ends share the same coords.)
|
|
2310
|
+
const p1Start = ae1.outrec.pts;
|
|
2311
|
+
const p2Start = ae2.outrec.pts;
|
|
2312
|
+
const p1End = p1Start.next;
|
|
2313
|
+
const p2End = p2Start.next;
|
|
2314
|
+
if (ClipperBase.isFront(ae1)) {
|
|
2315
|
+
p2End.prev = p1Start;
|
|
2316
|
+
p1Start.next = p2End;
|
|
2317
|
+
p2Start.next = p1End;
|
|
2318
|
+
p1End.prev = p2Start;
|
|
2319
|
+
ae1.outrec.pts = p2Start;
|
|
2320
|
+
// nb: if IsOpen(e1) then e1 & e2 must be a 'maximaPair'
|
|
2321
|
+
ae1.outrec.frontEdge = ae2.outrec.frontEdge;
|
|
2322
|
+
if (ae1.outrec.frontEdge !== null) {
|
|
2323
|
+
ae1.outrec.frontEdge.outrec = ae1.outrec;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
else {
|
|
2327
|
+
p1End.prev = p2Start;
|
|
2328
|
+
p2Start.next = p1End;
|
|
2329
|
+
p1Start.next = p2End;
|
|
2330
|
+
p2End.prev = p1Start;
|
|
2331
|
+
ae1.outrec.backEdge = ae2.outrec.backEdge;
|
|
2332
|
+
if (ae1.outrec.backEdge !== null) {
|
|
2333
|
+
ae1.outrec.backEdge.outrec = ae1.outrec;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
// after joining, the ae2.OutRec must contains no vertices ...
|
|
2337
|
+
ae2.outrec.frontEdge = null;
|
|
2338
|
+
ae2.outrec.backEdge = null;
|
|
2339
|
+
ae2.outrec.pts = null;
|
|
2340
|
+
this.setOwner(ae2.outrec, ae1.outrec);
|
|
2341
|
+
if (ClipperBase.isOpenEnd(ae1)) {
|
|
2342
|
+
ae2.outrec.pts = ae1.outrec.pts;
|
|
2343
|
+
ae1.outrec.pts = null;
|
|
2344
|
+
}
|
|
2345
|
+
// and ae1 and ae2 are maxima and are about to be dropped from the Actives list.
|
|
2346
|
+
ae1.outrec = null;
|
|
2347
|
+
ae2.outrec = null;
|
|
2348
|
+
}
|
|
2349
|
+
swapOutrecs(ae1, ae2) {
|
|
2350
|
+
const or1 = ae1.outrec; // at least one edge has
|
|
2351
|
+
const or2 = ae2.outrec; // an assigned outrec
|
|
2352
|
+
if (or1 === or2) {
|
|
2353
|
+
const ae = or1.frontEdge;
|
|
2354
|
+
or1.frontEdge = or1.backEdge;
|
|
2355
|
+
or1.backEdge = ae;
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
if (or1 !== null) {
|
|
2359
|
+
if (ae1 === or1.frontEdge) {
|
|
2360
|
+
or1.frontEdge = ae2;
|
|
2361
|
+
}
|
|
2362
|
+
else {
|
|
2363
|
+
or1.backEdge = ae2;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
if (or2 !== null) {
|
|
2367
|
+
if (ae2 === or2.frontEdge) {
|
|
2368
|
+
or2.frontEdge = ae1;
|
|
2369
|
+
}
|
|
2370
|
+
else {
|
|
2371
|
+
or2.backEdge = ae1;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
ae1.outrec = or2;
|
|
2375
|
+
ae2.outrec = or1;
|
|
2376
|
+
}
|
|
2377
|
+
disposeIntersectNodes() {
|
|
2378
|
+
this.intersectList.length = 0;
|
|
2379
|
+
}
|
|
2380
|
+
static ptsReallyClose(pt1, pt2) {
|
|
2381
|
+
return (Math.abs(pt1.x - pt2.x) < 2) && (Math.abs(pt1.y - pt2.y) < 2);
|
|
2382
|
+
}
|
|
2383
|
+
static isVerySmallTriangle(op) {
|
|
2384
|
+
return op.next.next === op.prev &&
|
|
2385
|
+
(ClipperBase.ptsReallyClose(op.prev.pt, op.next.pt) ||
|
|
2386
|
+
ClipperBase.ptsReallyClose(op.pt, op.next.pt) ||
|
|
2387
|
+
ClipperBase.ptsReallyClose(op.pt, op.prev.pt));
|
|
2388
|
+
}
|
|
2389
|
+
static buildPath(op, reverse, isOpen, path) {
|
|
2390
|
+
if (op === null || op.next === op || (!isOpen && op.next === op.prev))
|
|
2391
|
+
return false;
|
|
2392
|
+
path.length = 0;
|
|
2393
|
+
let lastPt;
|
|
2394
|
+
let op2;
|
|
2395
|
+
if (reverse) {
|
|
2396
|
+
lastPt = op.pt;
|
|
2397
|
+
op2 = op.prev;
|
|
2398
|
+
}
|
|
2399
|
+
else {
|
|
2400
|
+
op = op.next;
|
|
2401
|
+
lastPt = op.pt;
|
|
2402
|
+
op2 = op.next;
|
|
2403
|
+
}
|
|
2404
|
+
path.push(lastPt);
|
|
2405
|
+
while (op2 !== op) {
|
|
2406
|
+
if (!(op2.pt.x === lastPt.x && op2.pt.y === lastPt.y)) {
|
|
2407
|
+
lastPt = op2.pt;
|
|
2408
|
+
path.push(lastPt);
|
|
2409
|
+
}
|
|
2410
|
+
if (reverse) {
|
|
2411
|
+
op2 = op2.prev;
|
|
2412
|
+
}
|
|
2413
|
+
else {
|
|
2414
|
+
op2 = op2.next;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
return path.length !== 3 || isOpen || !ClipperBase.isVerySmallTriangle(op2);
|
|
2418
|
+
}
|
|
2419
|
+
buildPaths(solutionClosed, solutionOpen) {
|
|
2420
|
+
solutionClosed.length = 0;
|
|
2421
|
+
solutionOpen.length = 0;
|
|
2422
|
+
let i = 0;
|
|
2423
|
+
// outrecList.length is not static here because
|
|
2424
|
+
// CleanCollinear can indirectly add additional OutRec
|
|
2425
|
+
while (i < this.outrecList.length) {
|
|
2426
|
+
const outrec = this.outrecList[i++];
|
|
2427
|
+
if (outrec.pts === null)
|
|
2428
|
+
continue;
|
|
2429
|
+
const path = [];
|
|
2430
|
+
if (outrec.isOpen) {
|
|
2431
|
+
if (ClipperBase.buildPath(outrec.pts, this.reverseSolution, true, path)) {
|
|
2432
|
+
solutionOpen.push(path);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
else {
|
|
2436
|
+
this.cleanCollinear(outrec);
|
|
2437
|
+
// closed paths should always return a Positive orientation
|
|
2438
|
+
// except when ReverseSolution == true
|
|
2439
|
+
if (ClipperBase.buildPath(outrec.pts, this.reverseSolution, false, path)) {
|
|
2440
|
+
solutionClosed.push(path);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
return true;
|
|
2445
|
+
}
|
|
2446
|
+
buildTree(polytree, solutionOpen) {
|
|
2447
|
+
polytree.clear();
|
|
2448
|
+
solutionOpen.length = 0;
|
|
2449
|
+
let i = 0;
|
|
2450
|
+
// outrecList.length is not static here because
|
|
2451
|
+
// checkBounds below can indirectly add additional
|
|
2452
|
+
// OutRec (via FixOutRecPts & CleanCollinear)
|
|
2453
|
+
while (i < this.outrecList.length) {
|
|
2454
|
+
const outrec = this.outrecList[i++];
|
|
2455
|
+
if (outrec.pts === null)
|
|
2456
|
+
continue;
|
|
2457
|
+
if (outrec.isOpen) {
|
|
2458
|
+
const openPath = [];
|
|
2459
|
+
if (ClipperBase.buildPath(outrec.pts, this.reverseSolution, true, openPath)) {
|
|
2460
|
+
solutionOpen.push(openPath);
|
|
2461
|
+
}
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2464
|
+
if (this.checkBounds(outrec)) {
|
|
2465
|
+
this.recursiveCheckOwners(outrec, polytree);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
checkBounds(outrec) {
|
|
2470
|
+
if (outrec.pts === null)
|
|
2471
|
+
return false;
|
|
2472
|
+
if (!Core_1.Rect64Utils.isEmpty(outrec.bounds))
|
|
2473
|
+
return true;
|
|
2474
|
+
this.cleanCollinear(outrec);
|
|
2475
|
+
if (outrec.pts === null ||
|
|
2476
|
+
!ClipperBase.buildPath(outrec.pts, this.reverseSolution, false, outrec.path)) {
|
|
2477
|
+
return false;
|
|
2478
|
+
}
|
|
2479
|
+
outrec.bounds = Core_1.InternalClipper.getBounds(outrec.path);
|
|
2480
|
+
return true;
|
|
2481
|
+
}
|
|
2482
|
+
recursiveCheckOwners(outrec, polypath) {
|
|
2483
|
+
// pre-condition: outrec will have valid bounds
|
|
2484
|
+
// post-condition: if a valid path, outrec will have a polypath
|
|
2485
|
+
if (outrec.polypath !== null || Core_1.Rect64Utils.isEmpty(outrec.bounds))
|
|
2486
|
+
return;
|
|
2487
|
+
while (outrec.owner !== null) {
|
|
2488
|
+
if (outrec.owner.splits !== null &&
|
|
2489
|
+
this.checkSplitOwner(outrec, outrec.owner.splits))
|
|
2490
|
+
break;
|
|
2491
|
+
if (outrec.owner.pts !== null && this.checkBounds(outrec.owner) &&
|
|
2492
|
+
this.path1InsidePath2(outrec.pts, outrec.owner.pts))
|
|
2493
|
+
break;
|
|
2494
|
+
outrec.owner = outrec.owner.owner;
|
|
2495
|
+
}
|
|
2496
|
+
if (outrec.owner !== null) {
|
|
2497
|
+
if (outrec.owner.polypath === null) {
|
|
2498
|
+
this.recursiveCheckOwners(outrec.owner, polypath);
|
|
2499
|
+
}
|
|
2500
|
+
outrec.polypath = outrec.owner.polypath.addChild(outrec.path);
|
|
2501
|
+
}
|
|
2502
|
+
else {
|
|
2503
|
+
outrec.polypath = polypath.addChild(outrec.path);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
cleanCollinear(outrec) {
|
|
2507
|
+
outrec = this.getRealOutRec(outrec);
|
|
2508
|
+
if (outrec === null || outrec.isOpen)
|
|
2509
|
+
return;
|
|
2510
|
+
if (!this.isValidClosedPath(outrec.pts)) {
|
|
2511
|
+
outrec.pts = null;
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
let startOp = outrec.pts;
|
|
2515
|
+
let op2 = startOp;
|
|
2516
|
+
while (true) {
|
|
2517
|
+
// NB if preserveCollinear == true, then only remove 180 deg. spikes
|
|
2518
|
+
if (op2 !== null && Core_1.InternalClipper.isCollinear(op2.prev.pt, op2.pt, op2.next.pt) &&
|
|
2519
|
+
((op2.pt.x === op2.prev.pt.x && op2.pt.y === op2.prev.pt.y) ||
|
|
2520
|
+
(op2.pt.x === op2.next.pt.x && op2.pt.y === op2.next.pt.y) ||
|
|
2521
|
+
!this.preserveCollinear ||
|
|
2522
|
+
Core_1.InternalClipper.dotProduct(op2.prev.pt, op2.pt, op2.next.pt) < 0)) {
|
|
2523
|
+
if (op2 === outrec.pts) {
|
|
2524
|
+
outrec.pts = op2.prev;
|
|
2525
|
+
}
|
|
2526
|
+
op2 = this.disposeOutPt(op2);
|
|
2527
|
+
if (!this.isValidClosedPath(op2)) {
|
|
2528
|
+
outrec.pts = null;
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
startOp = op2;
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
if (op2 === null)
|
|
2535
|
+
break;
|
|
2536
|
+
op2 = op2.next;
|
|
2537
|
+
if (op2 === startOp)
|
|
2538
|
+
break;
|
|
2539
|
+
}
|
|
2540
|
+
this.fixSelfIntersects(outrec);
|
|
2541
|
+
}
|
|
2542
|
+
isValidClosedPath(op) {
|
|
2543
|
+
return op !== null && op.next !== op &&
|
|
2544
|
+
(op.next !== op.prev || !ClipperBase.isVerySmallTriangle(op));
|
|
2545
|
+
}
|
|
2546
|
+
disposeOutPt(op) {
|
|
2547
|
+
const result = (op.next === op ? null : op.next);
|
|
2548
|
+
op.prev.next = op.next;
|
|
2549
|
+
op.next.prev = op.prev;
|
|
2550
|
+
return result;
|
|
2551
|
+
}
|
|
2552
|
+
fixSelfIntersects(outrec) {
|
|
2553
|
+
let op2 = outrec.pts;
|
|
2554
|
+
if (op2.prev === op2.next.next) {
|
|
2555
|
+
return; // because triangles can't self-intersect
|
|
2556
|
+
}
|
|
2557
|
+
while (true) {
|
|
2558
|
+
if (op2.next && op2.next.next &&
|
|
2559
|
+
// optimization (not in C# reference): bbox check before segsIntersect
|
|
2560
|
+
this.boundingBoxesOverlap(op2.prev.pt, op2.pt, op2.next.pt, op2.next.next.pt) && // TEST: Bbox only
|
|
2561
|
+
Core_1.InternalClipper.segsIntersect(op2.prev.pt, op2.pt, op2.next.pt, op2.next.next.pt)) {
|
|
2562
|
+
if (op2.next.next.next &&
|
|
2563
|
+
// optimization (not in C# reference): bbox check before segsIntersect
|
|
2564
|
+
this.boundingBoxesOverlap(op2.prev.pt, op2.pt, op2.next.next.pt, op2.next.next.next.pt) && // TEST: Bbox only
|
|
2565
|
+
Core_1.InternalClipper.segsIntersect(op2.prev.pt, op2.pt, op2.next.next.pt, op2.next.next.next.pt)) {
|
|
2566
|
+
// adjacent intersections (ie a micro self-intersection)
|
|
2567
|
+
op2 = this.duplicateOp(op2, false);
|
|
2568
|
+
op2.pt = op2.next.next.next.pt;
|
|
2569
|
+
op2 = op2.next;
|
|
2570
|
+
}
|
|
2571
|
+
else {
|
|
2572
|
+
if (op2 === outrec.pts || op2.next === outrec.pts) {
|
|
2573
|
+
outrec.pts = outrec.pts.prev;
|
|
2574
|
+
}
|
|
2575
|
+
this.doSplitOp(outrec, op2);
|
|
2576
|
+
if (outrec.pts === null)
|
|
2577
|
+
return;
|
|
2578
|
+
op2 = outrec.pts;
|
|
2579
|
+
// triangles can't self-intersect
|
|
2580
|
+
if (op2.prev === op2.next.next)
|
|
2581
|
+
break;
|
|
2582
|
+
continue;
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
op2 = op2.next;
|
|
2586
|
+
if (op2 === outrec.pts)
|
|
2587
|
+
break;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
doSplitOp(outrec, splitOp) {
|
|
2591
|
+
// splitOp.prev <=> splitOp &&
|
|
2592
|
+
// splitOp.next <=> splitOp.next.next are intersecting
|
|
2593
|
+
const prevOp = splitOp.prev;
|
|
2594
|
+
const nextNextOp = splitOp.next.next;
|
|
2595
|
+
outrec.pts = prevOp;
|
|
2596
|
+
const intersectResult = Core_1.InternalClipper.getLineIntersectPt(prevOp.pt, splitOp.pt, splitOp.next.pt, nextNextOp.pt);
|
|
2597
|
+
const ip = intersectResult.point;
|
|
2598
|
+
const area1 = ClipperBase.areaOutPt(prevOp);
|
|
2599
|
+
const absArea1 = Math.abs(area1);
|
|
2600
|
+
if (absArea1 < 2) {
|
|
2601
|
+
outrec.pts = null;
|
|
2602
|
+
return;
|
|
2603
|
+
}
|
|
2604
|
+
const area2 = this.areaTriangle(ip, splitOp.pt, splitOp.next.pt);
|
|
2605
|
+
const absArea2 = Math.abs(area2);
|
|
2606
|
+
// de-link splitOp and splitOp.next from the path
|
|
2607
|
+
// while inserting the intersection point
|
|
2608
|
+
if ((ip.x === prevOp.pt.x && ip.y === prevOp.pt.y) || (ip.x === nextNextOp.pt.x && ip.y === nextNextOp.pt.y)) {
|
|
2609
|
+
nextNextOp.prev = prevOp;
|
|
2610
|
+
prevOp.next = nextNextOp;
|
|
2611
|
+
}
|
|
2612
|
+
else {
|
|
2613
|
+
const newOp2 = new OutPt(ip, outrec);
|
|
2614
|
+
newOp2.prev = prevOp;
|
|
2615
|
+
newOp2.next = nextNextOp;
|
|
2616
|
+
nextNextOp.prev = newOp2;
|
|
2617
|
+
prevOp.next = newOp2;
|
|
2618
|
+
}
|
|
2619
|
+
if (!(absArea2 > 1) ||
|
|
2620
|
+
(!(absArea2 > absArea1) &&
|
|
2621
|
+
((area2 > 0) !== (area1 > 0))))
|
|
2622
|
+
return;
|
|
2623
|
+
const newOutRec = this.newOutRec();
|
|
2624
|
+
newOutRec.owner = outrec.owner;
|
|
2625
|
+
splitOp.outrec = newOutRec;
|
|
2626
|
+
splitOp.next.outrec = newOutRec;
|
|
2627
|
+
const newOp = new OutPt(ip, newOutRec);
|
|
2628
|
+
newOp.prev = splitOp.next;
|
|
2629
|
+
newOp.next = splitOp;
|
|
2630
|
+
newOutRec.pts = newOp;
|
|
2631
|
+
splitOp.prev = newOp;
|
|
2632
|
+
splitOp.next.next = newOp;
|
|
2633
|
+
if (!this.usingPolytree)
|
|
2634
|
+
return;
|
|
2635
|
+
if (this.path1InsidePath2(prevOp, newOp)) {
|
|
2636
|
+
if (newOutRec.splits === null)
|
|
2637
|
+
newOutRec.splits = [];
|
|
2638
|
+
newOutRec.splits.push(outrec.idx);
|
|
2639
|
+
}
|
|
2640
|
+
else {
|
|
2641
|
+
if (outrec.splits === null)
|
|
2642
|
+
outrec.splits = [];
|
|
2643
|
+
outrec.splits.push(newOutRec.idx);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
static areaOutPt(op) {
|
|
2647
|
+
// https://en.wikipedia.org/wiki/Shoelace_formula
|
|
2648
|
+
let area = 0.0;
|
|
2649
|
+
let op2 = op;
|
|
2650
|
+
do {
|
|
2651
|
+
area += (op2.prev.pt.y + op2.pt.y) * (op2.prev.pt.x - op2.pt.x);
|
|
2652
|
+
op2 = op2.next;
|
|
2653
|
+
} while (op2 !== op);
|
|
2654
|
+
return area * 0.5;
|
|
2655
|
+
}
|
|
2656
|
+
areaTriangle(pt1, pt2, pt3) {
|
|
2657
|
+
return ((pt3.y + pt1.y) * (pt3.x - pt1.x) +
|
|
2658
|
+
(pt1.y + pt2.y) * (pt1.x - pt2.x) +
|
|
2659
|
+
(pt2.y + pt3.y) * (pt2.x - pt3.x));
|
|
2660
|
+
}
|
|
2661
|
+
isValidOwner(outRec, testOwner) {
|
|
2662
|
+
while (testOwner !== null && testOwner !== outRec) {
|
|
2663
|
+
testOwner = testOwner.owner;
|
|
2664
|
+
}
|
|
2665
|
+
return testOwner === null;
|
|
2666
|
+
}
|
|
2667
|
+
containsRect(rect, rec) {
|
|
2668
|
+
return rec.left >= rect.left && rec.right <= rect.right &&
|
|
2669
|
+
rec.top >= rect.top && rec.bottom <= rect.bottom;
|
|
2670
|
+
}
|
|
2671
|
+
checkSplitOwner(outrec, splits) {
|
|
2672
|
+
// nb: use indexing (not an iterator) in case 'splits' is modified inside this loop (#1029)
|
|
2673
|
+
for (let i = 0; i < splits.length; i++) {
|
|
2674
|
+
let split = this.outrecList[splits[i]];
|
|
2675
|
+
if (split.pts === null && split.splits !== null &&
|
|
2676
|
+
this.checkSplitOwner(outrec, split.splits))
|
|
2677
|
+
return true; // #942
|
|
2678
|
+
split = this.getRealOutRec(split);
|
|
2679
|
+
if (split === null || split === outrec || split.recursiveSplit === outrec)
|
|
2680
|
+
continue;
|
|
2681
|
+
split.recursiveSplit = outrec; // #599
|
|
2682
|
+
if (split.splits !== null && this.checkSplitOwner(outrec, split.splits))
|
|
2683
|
+
return true;
|
|
2684
|
+
if (!this.checkBounds(split) ||
|
|
2685
|
+
!this.containsRect(split.bounds, outrec.bounds) ||
|
|
2686
|
+
!this.path1InsidePath2(outrec.pts, split.pts))
|
|
2687
|
+
continue;
|
|
2688
|
+
if (!this.isValidOwner(outrec, split)) { // split is owned by outrec (#957)
|
|
2689
|
+
split.owner = outrec.owner;
|
|
2690
|
+
}
|
|
2691
|
+
outrec.owner = split; // found in split
|
|
2692
|
+
return true;
|
|
2693
|
+
}
|
|
2694
|
+
return false;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
exports.ClipperBase = ClipperBase;
|
|
2698
|
+
class Clipper64 extends ClipperBase {
|
|
2699
|
+
addPath(path, polytype, isOpen = false) {
|
|
2700
|
+
super.addPath(path, polytype, isOpen);
|
|
2701
|
+
}
|
|
2702
|
+
addReuseableData(reuseableData) {
|
|
2703
|
+
super.addReuseableData(reuseableData);
|
|
2704
|
+
}
|
|
2705
|
+
addPaths(paths, polytype, isOpen = false) {
|
|
2706
|
+
super.addPaths(paths, polytype, isOpen);
|
|
2707
|
+
}
|
|
2708
|
+
addSubject(paths) {
|
|
2709
|
+
this.addPaths(paths, Core_1.PathType.Subject);
|
|
2710
|
+
}
|
|
2711
|
+
addOpenSubject(paths) {
|
|
2712
|
+
this.addPaths(paths, Core_1.PathType.Subject, true);
|
|
2713
|
+
}
|
|
2714
|
+
addClip(paths) {
|
|
2715
|
+
this.addPaths(paths, Core_1.PathType.Clip);
|
|
2716
|
+
}
|
|
2717
|
+
execute(clipType, fillRule, solutionOrTree, openPathsOrSolutionOpen) {
|
|
2718
|
+
if (Array.isArray(solutionOrTree)) {
|
|
2719
|
+
// Paths64 version
|
|
2720
|
+
const solutionClosed = solutionOrTree;
|
|
2721
|
+
const solutionOpen = openPathsOrSolutionOpen;
|
|
2722
|
+
solutionClosed.length = 0;
|
|
2723
|
+
if (solutionOpen)
|
|
2724
|
+
solutionOpen.length = 0;
|
|
2725
|
+
try {
|
|
2726
|
+
this.executeInternal(clipType, fillRule);
|
|
2727
|
+
this.buildPaths(solutionClosed, solutionOpen || []);
|
|
2728
|
+
}
|
|
2729
|
+
catch {
|
|
2730
|
+
this.succeeded = false;
|
|
2731
|
+
}
|
|
2732
|
+
this.clearSolutionOnly();
|
|
2733
|
+
return this.succeeded;
|
|
2734
|
+
}
|
|
2735
|
+
else {
|
|
2736
|
+
// PolyTree64 version
|
|
2737
|
+
const polytree = solutionOrTree;
|
|
2738
|
+
const openPaths = openPathsOrSolutionOpen;
|
|
2739
|
+
polytree.clear();
|
|
2740
|
+
if (openPaths)
|
|
2741
|
+
openPaths.length = 0;
|
|
2742
|
+
this.usingPolytree = true;
|
|
2743
|
+
try {
|
|
2744
|
+
this.executeInternal(clipType, fillRule);
|
|
2745
|
+
this.buildTree(polytree, openPaths || []);
|
|
2746
|
+
}
|
|
2747
|
+
catch {
|
|
2748
|
+
this.succeeded = false;
|
|
2749
|
+
}
|
|
2750
|
+
this.clearSolutionOnly();
|
|
2751
|
+
return this.succeeded;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
exports.Clipper64 = Clipper64;
|
|
2756
|
+
class ClipperD extends ClipperBase {
|
|
2757
|
+
constructor(roundingDecimalPrecision = 2) {
|
|
2758
|
+
super();
|
|
2759
|
+
Core_1.InternalClipper.checkPrecision(roundingDecimalPrecision);
|
|
2760
|
+
this.scale = Math.pow(10, roundingDecimalPrecision);
|
|
2761
|
+
this.invScale = 1 / this.scale;
|
|
2762
|
+
}
|
|
2763
|
+
scalePathDFromInt(path, scale) {
|
|
2764
|
+
const result = [];
|
|
2765
|
+
for (const pt of path) {
|
|
2766
|
+
result.push({
|
|
2767
|
+
x: pt.x * scale,
|
|
2768
|
+
y: pt.y * scale
|
|
2769
|
+
});
|
|
2770
|
+
}
|
|
2771
|
+
return result;
|
|
2772
|
+
}
|
|
2773
|
+
buildPathsD(solutionClosed, solutionOpen) {
|
|
2774
|
+
solutionClosed.length = 0;
|
|
2775
|
+
solutionOpen.length = 0;
|
|
2776
|
+
let i = 0;
|
|
2777
|
+
// outrecList.length is not static here because
|
|
2778
|
+
// CleanCollinear can indirectly add additional OutRec
|
|
2779
|
+
while (i < this.outrecList.length) {
|
|
2780
|
+
const outrec = this.outrecList[i++];
|
|
2781
|
+
if (outrec.pts === null)
|
|
2782
|
+
continue;
|
|
2783
|
+
const path = [];
|
|
2784
|
+
if (outrec.isOpen) {
|
|
2785
|
+
if (ClipperBase.buildPath(outrec.pts, this.reverseSolution, true, path)) {
|
|
2786
|
+
solutionOpen.push(this.scalePathDFromInt(path, this.invScale));
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
else {
|
|
2790
|
+
this.cleanCollinear(outrec);
|
|
2791
|
+
// closed paths should always return a Positive orientation
|
|
2792
|
+
// except when ReverseSolution == true
|
|
2793
|
+
if (ClipperBase.buildPath(outrec.pts, this.reverseSolution, false, path)) {
|
|
2794
|
+
solutionClosed.push(this.scalePathDFromInt(path, this.invScale));
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
return true;
|
|
2799
|
+
}
|
|
2800
|
+
buildTreeD(polytree, solutionOpen) {
|
|
2801
|
+
polytree.clear();
|
|
2802
|
+
solutionOpen.length = 0;
|
|
2803
|
+
let i = 0;
|
|
2804
|
+
// outrecList.length is not static here because
|
|
2805
|
+
// BuildPathD below can indirectly add additional OutRec
|
|
2806
|
+
while (i < this.outrecList.length) {
|
|
2807
|
+
const outrec = this.outrecList[i++];
|
|
2808
|
+
if (outrec.pts === null)
|
|
2809
|
+
continue;
|
|
2810
|
+
if (outrec.isOpen) {
|
|
2811
|
+
const openPath = [];
|
|
2812
|
+
if (ClipperBase.buildPath(outrec.pts, this.reverseSolution, true, openPath)) {
|
|
2813
|
+
solutionOpen.push(this.scalePathDFromInt(openPath, this.invScale));
|
|
2814
|
+
}
|
|
2815
|
+
continue;
|
|
2816
|
+
}
|
|
2817
|
+
if (this.checkBounds(outrec)) {
|
|
2818
|
+
this.recursiveCheckOwners(outrec, polytree);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
addPath(path, polytype, isOpen = false) {
|
|
2823
|
+
super.addPath(Clipper.scalePath64(path, this.scale), polytype, isOpen);
|
|
2824
|
+
}
|
|
2825
|
+
addPaths(paths, polytype, isOpen = false) {
|
|
2826
|
+
super.addPaths(Clipper.scalePaths64(paths, this.scale), polytype, isOpen);
|
|
2827
|
+
}
|
|
2828
|
+
addSubject(path) {
|
|
2829
|
+
this.addPath(path, Core_1.PathType.Subject);
|
|
2830
|
+
}
|
|
2831
|
+
addOpenSubject(path) {
|
|
2832
|
+
this.addPath(path, Core_1.PathType.Subject, true);
|
|
2833
|
+
}
|
|
2834
|
+
addClip(path) {
|
|
2835
|
+
this.addPath(path, Core_1.PathType.Clip);
|
|
2836
|
+
}
|
|
2837
|
+
addSubjectPaths(paths) {
|
|
2838
|
+
this.addPaths(paths, Core_1.PathType.Subject);
|
|
2839
|
+
}
|
|
2840
|
+
addOpenSubjectPaths(paths) {
|
|
2841
|
+
this.addPaths(paths, Core_1.PathType.Subject, true);
|
|
2842
|
+
}
|
|
2843
|
+
addClipPaths(paths) {
|
|
2844
|
+
this.addPaths(paths, Core_1.PathType.Clip);
|
|
2845
|
+
}
|
|
2846
|
+
execute(clipType, fillRule, solutionOrTree, openPathsOrSolutionOpen) {
|
|
2847
|
+
if (Array.isArray(solutionOrTree)) {
|
|
2848
|
+
// PathsD version - match C# implementation exactly
|
|
2849
|
+
const solutionClosed = solutionOrTree;
|
|
2850
|
+
const solutionOpen = openPathsOrSolutionOpen;
|
|
2851
|
+
// Use Paths64 internally like C# does
|
|
2852
|
+
const solClosed64 = [];
|
|
2853
|
+
const solOpen64 = [];
|
|
2854
|
+
solutionClosed.length = 0;
|
|
2855
|
+
if (solutionOpen)
|
|
2856
|
+
solutionOpen.length = 0;
|
|
2857
|
+
let success = true;
|
|
2858
|
+
try {
|
|
2859
|
+
this.executeInternal(clipType, fillRule);
|
|
2860
|
+
// Call regular buildPaths which includes cleanCollinear and fixSelfIntersects
|
|
2861
|
+
this.buildPaths(solClosed64, solOpen64);
|
|
2862
|
+
}
|
|
2863
|
+
catch {
|
|
2864
|
+
success = false;
|
|
2865
|
+
}
|
|
2866
|
+
this.clearSolutionOnly();
|
|
2867
|
+
if (!success)
|
|
2868
|
+
return false;
|
|
2869
|
+
// Convert Paths64 to PathsD
|
|
2870
|
+
for (const path of solClosed64) {
|
|
2871
|
+
solutionClosed.push(this.scalePathDFromInt(path, this.invScale));
|
|
2872
|
+
}
|
|
2873
|
+
if (solutionOpen) {
|
|
2874
|
+
for (const path of solOpen64) {
|
|
2875
|
+
solutionOpen.push(this.scalePathDFromInt(path, this.invScale));
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
return true;
|
|
2879
|
+
}
|
|
2880
|
+
else {
|
|
2881
|
+
// PolyTreeD version
|
|
2882
|
+
const polytree = solutionOrTree;
|
|
2883
|
+
const openPaths = openPathsOrSolutionOpen;
|
|
2884
|
+
polytree.clear();
|
|
2885
|
+
if (openPaths)
|
|
2886
|
+
openPaths.length = 0;
|
|
2887
|
+
this.usingPolytree = true;
|
|
2888
|
+
polytree.scale = this.scale;
|
|
2889
|
+
let success = true;
|
|
2890
|
+
try {
|
|
2891
|
+
this.executeInternal(clipType, fillRule);
|
|
2892
|
+
this.buildTreeD(polytree, openPaths || []);
|
|
2893
|
+
}
|
|
2894
|
+
catch {
|
|
2895
|
+
success = false;
|
|
2896
|
+
}
|
|
2897
|
+
this.clearSolutionOnly();
|
|
2898
|
+
return success;
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
exports.ClipperD = ClipperD;
|
|
2903
|
+
// Forward declaration for Clipper class
|
|
2904
|
+
var Clipper;
|
|
2905
|
+
(function (Clipper) {
|
|
2906
|
+
function area(path) {
|
|
2907
|
+
// https://en.wikipedia.org/wiki/Shoelace_formula
|
|
2908
|
+
let a = 0.0;
|
|
2909
|
+
const cnt = path.length;
|
|
2910
|
+
if (cnt < 3)
|
|
2911
|
+
return 0.0;
|
|
2912
|
+
let prevPt = path[cnt - 1];
|
|
2913
|
+
for (const pt of path) {
|
|
2914
|
+
a += (prevPt.y + pt.y) * (prevPt.x - pt.x);
|
|
2915
|
+
prevPt = pt;
|
|
2916
|
+
}
|
|
2917
|
+
return a * 0.5;
|
|
2918
|
+
}
|
|
2919
|
+
Clipper.area = area;
|
|
2920
|
+
function areaD(path) {
|
|
2921
|
+
let a = 0.0;
|
|
2922
|
+
const cnt = path.length;
|
|
2923
|
+
if (cnt < 3)
|
|
2924
|
+
return 0.0;
|
|
2925
|
+
let prevPt = path[cnt - 1];
|
|
2926
|
+
for (const pt of path) {
|
|
2927
|
+
a += (prevPt.y + pt.y) * (prevPt.x - pt.x);
|
|
2928
|
+
prevPt = pt;
|
|
2929
|
+
}
|
|
2930
|
+
return a * 0.5;
|
|
2931
|
+
}
|
|
2932
|
+
Clipper.areaD = areaD;
|
|
2933
|
+
function scalePath64(path, scale) {
|
|
2934
|
+
const result = [];
|
|
2935
|
+
for (const pt of path) {
|
|
2936
|
+
result.push({
|
|
2937
|
+
x: Math.round(pt.x * scale),
|
|
2938
|
+
y: Math.round(pt.y * scale)
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
return result;
|
|
2942
|
+
}
|
|
2943
|
+
Clipper.scalePath64 = scalePath64;
|
|
2944
|
+
function scalePaths64(paths, scale) {
|
|
2945
|
+
const result = [];
|
|
2946
|
+
for (const path of paths) {
|
|
2947
|
+
result.push(scalePath64(path, scale));
|
|
2948
|
+
}
|
|
2949
|
+
return result;
|
|
2950
|
+
}
|
|
2951
|
+
Clipper.scalePaths64 = scalePaths64;
|
|
2952
|
+
function scalePathD(path, scale) {
|
|
2953
|
+
const result = [];
|
|
2954
|
+
for (const pt of path) {
|
|
2955
|
+
result.push({
|
|
2956
|
+
x: pt.x * scale,
|
|
2957
|
+
y: pt.y * scale
|
|
2958
|
+
});
|
|
2959
|
+
}
|
|
2960
|
+
return result;
|
|
2961
|
+
}
|
|
2962
|
+
Clipper.scalePathD = scalePathD;
|
|
2963
|
+
function scalePathsD(paths, scale) {
|
|
2964
|
+
const result = [];
|
|
2965
|
+
for (const path of paths) {
|
|
2966
|
+
result.push(scalePathD(path, scale));
|
|
2967
|
+
}
|
|
2968
|
+
return result;
|
|
2969
|
+
}
|
|
2970
|
+
Clipper.scalePathsD = scalePathsD;
|
|
2971
|
+
})(Clipper || (exports.Clipper = Clipper = {}));
|
|
2972
|
+
//# sourceMappingURL=Engine.js.map
|