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/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