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.
@@ -0,0 +1,1009 @@
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 : FAST rectangular clipping *
8
+ * License : https://www.boost.org/LICENSE_1_0.txt *
9
+ *******************************************************************************/
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.RectClipLines64 = exports.RectClip64 = exports.OutPt2 = void 0;
12
+ const Core_1 = require("./Core");
13
+ class OutPt2 {
14
+ constructor(pt) {
15
+ this.next = null;
16
+ this.prev = null;
17
+ this.ownerIdx = 0;
18
+ this.edge = null;
19
+ this.pt = pt;
20
+ }
21
+ }
22
+ exports.OutPt2 = OutPt2;
23
+ var Location;
24
+ (function (Location) {
25
+ Location[Location["left"] = 0] = "left";
26
+ Location[Location["top"] = 1] = "top";
27
+ Location[Location["right"] = 2] = "right";
28
+ Location[Location["bottom"] = 3] = "bottom";
29
+ Location[Location["inside"] = 4] = "inside";
30
+ })(Location || (Location = {}));
31
+ class RectClip64 {
32
+ constructor(rect) {
33
+ this.pathBounds = { left: 0, top: 0, right: 0, bottom: 0 };
34
+ this.results = [];
35
+ this.edges = [];
36
+ this.currIdx = -1;
37
+ this.currIdx = -1;
38
+ this.rect = rect;
39
+ this.mp = Core_1.Rect64Utils.midPoint(rect);
40
+ this.rectPath = Core_1.Rect64Utils.asPath(this.rect);
41
+ this.results = [];
42
+ this.edges = [];
43
+ for (let i = 0; i < 8; i++) {
44
+ this.edges[i] = [];
45
+ }
46
+ }
47
+ add(pt, startingNewPath = false) {
48
+ // this method is only called by InternalExecute.
49
+ // Later splitting and rejoining won't create additional op's,
50
+ // though they will change the (non-storage) fResults count.
51
+ let currIdx = this.results.length;
52
+ let result;
53
+ if ((currIdx === 0) || startingNewPath) {
54
+ result = new OutPt2(pt);
55
+ this.results.push(result);
56
+ result.ownerIdx = currIdx;
57
+ result.prev = result;
58
+ result.next = result;
59
+ }
60
+ else {
61
+ currIdx--;
62
+ const prevOp = this.results[currIdx];
63
+ if (prevOp && Core_1.Point64Utils.equals(prevOp.pt, pt))
64
+ return prevOp;
65
+ result = new OutPt2(pt);
66
+ result.ownerIdx = currIdx;
67
+ result.next = prevOp.next;
68
+ prevOp.next.prev = result;
69
+ prevOp.next = result;
70
+ result.prev = prevOp;
71
+ this.results[currIdx] = result;
72
+ }
73
+ return result;
74
+ }
75
+ static path1ContainsPath2(path1, path2) {
76
+ // nb: occasionally, due to rounding, path1 may
77
+ // appear (momentarily) inside or outside path2.
78
+ let ioCount = 0;
79
+ for (const pt of path2) {
80
+ const pip = Core_1.InternalClipper.pointInPolygon(pt, path1);
81
+ switch (pip) {
82
+ case Core_1.PointInPolygonResult.IsInside:
83
+ ioCount--;
84
+ break;
85
+ case Core_1.PointInPolygonResult.IsOutside:
86
+ ioCount++;
87
+ break;
88
+ }
89
+ if (Math.abs(ioCount) > 1)
90
+ break;
91
+ }
92
+ return ioCount <= 0;
93
+ }
94
+ static isClockwise(prev, curr, prevPt, currPt, rectMidPoint) {
95
+ if (RectClip64.areOpposites(prev, curr)) {
96
+ return Core_1.InternalClipper.crossProduct(prevPt, rectMidPoint, currPt) < 0;
97
+ }
98
+ return RectClip64.headingClockwise(prev, curr);
99
+ }
100
+ static areOpposites(prev, curr) {
101
+ return Math.abs(prev - curr) === 2;
102
+ }
103
+ static headingClockwise(prev, curr) {
104
+ return (prev + 1) % 4 === curr;
105
+ }
106
+ static getAdjacentLocation(loc, isClockwise) {
107
+ const delta = isClockwise ? 1 : 3;
108
+ return (loc + delta) % 4;
109
+ }
110
+ static unlinkOp(op) {
111
+ if (op.next === op)
112
+ return null;
113
+ op.prev.next = op.next;
114
+ op.next.prev = op.prev;
115
+ return op.next;
116
+ }
117
+ static unlinkOpBack(op) {
118
+ if (op.next === op)
119
+ return null;
120
+ op.prev.next = op.next;
121
+ op.next.prev = op.prev;
122
+ return op.prev;
123
+ }
124
+ static getEdgesForPt(pt, rec) {
125
+ let result = 0;
126
+ if (pt.x === rec.left)
127
+ result = 1;
128
+ else if (pt.x === rec.right)
129
+ result = 4;
130
+ if (pt.y === rec.top)
131
+ result += 2;
132
+ else if (pt.y === rec.bottom)
133
+ result += 8;
134
+ return result;
135
+ }
136
+ static isHeadingClockwise(pt1, pt2, edgeIdx) {
137
+ switch (edgeIdx) {
138
+ case 0: return pt2.y < pt1.y;
139
+ case 1: return pt2.x > pt1.x;
140
+ case 2: return pt2.y > pt1.y;
141
+ default: return pt2.x < pt1.x;
142
+ }
143
+ }
144
+ static hasHorzOverlap(left1, right1, left2, right2) {
145
+ return (left1.x < right2.x) && (right1.x > left2.x);
146
+ }
147
+ static hasVertOverlap(top1, bottom1, top2, bottom2) {
148
+ return (top1.y < bottom2.y) && (bottom1.y > top2.y);
149
+ }
150
+ static addToEdge(edge, op) {
151
+ if (op.edge !== null)
152
+ return;
153
+ op.edge = edge;
154
+ edge.push(op);
155
+ }
156
+ static uncoupleEdge(op) {
157
+ if (op.edge === null)
158
+ return;
159
+ for (let i = 0; i < op.edge.length; i++) {
160
+ const op2 = op.edge[i];
161
+ if (op2 === op) {
162
+ op.edge[i] = null;
163
+ break;
164
+ }
165
+ }
166
+ op.edge = null;
167
+ }
168
+ static setNewOwner(op, newIdx) {
169
+ op.ownerIdx = newIdx;
170
+ let op2 = op.next;
171
+ while (op2 !== op) {
172
+ op2.ownerIdx = newIdx;
173
+ op2 = op2.next;
174
+ }
175
+ }
176
+ addCorner(prev, curr) {
177
+ this.add(RectClip64.headingClockwise(prev, curr) ?
178
+ this.rectPath[prev] : this.rectPath[curr]);
179
+ }
180
+ addCornerWithDirection(loc, isClockwise) {
181
+ if (isClockwise) {
182
+ this.add(this.rectPath[loc]);
183
+ return RectClip64.getAdjacentLocation(loc, true);
184
+ }
185
+ else {
186
+ const newLoc = RectClip64.getAdjacentLocation(loc, false);
187
+ this.add(this.rectPath[newLoc]);
188
+ return newLoc;
189
+ }
190
+ }
191
+ static getLocation(rec, pt) {
192
+ if (pt.x === rec.left && pt.y >= rec.top && pt.y <= rec.bottom) {
193
+ return { location: Location.left, isOnRect: true };
194
+ }
195
+ if (pt.x === rec.right && pt.y >= rec.top && pt.y <= rec.bottom) {
196
+ return { location: Location.right, isOnRect: true };
197
+ }
198
+ if (pt.y === rec.top && pt.x >= rec.left && pt.x <= rec.right) {
199
+ return { location: Location.top, isOnRect: true };
200
+ }
201
+ if (pt.y === rec.bottom && pt.x >= rec.left && pt.x <= rec.right) {
202
+ return { location: Location.bottom, isOnRect: true };
203
+ }
204
+ let location;
205
+ if (pt.x < rec.left)
206
+ location = Location.left;
207
+ else if (pt.x > rec.right)
208
+ location = Location.right;
209
+ else if (pt.y < rec.top)
210
+ location = Location.top;
211
+ else if (pt.y > rec.bottom)
212
+ location = Location.bottom;
213
+ else
214
+ location = Location.inside;
215
+ return { location, isOnRect: false };
216
+ }
217
+ static isHorizontal(pt1, pt2) {
218
+ return pt1.y === pt2.y;
219
+ }
220
+ static getSegmentIntersection(p1, p2, p3, p4) {
221
+ const res1 = Core_1.InternalClipper.crossProduct(p1, p3, p4);
222
+ const res2 = Core_1.InternalClipper.crossProduct(p2, p3, p4);
223
+ if (res1 === 0) {
224
+ const ip = p1;
225
+ if (res2 === 0)
226
+ return { intersects: false, point: ip }; // segments are collinear
227
+ if (Core_1.Point64Utils.equals(p1, p3) || Core_1.Point64Utils.equals(p1, p4))
228
+ return { intersects: true, point: ip };
229
+ if (RectClip64.isHorizontal(p3, p4)) {
230
+ return { intersects: (p1.x > p3.x) === (p1.x < p4.x), point: ip };
231
+ }
232
+ return { intersects: (p1.y > p3.y) === (p1.y < p4.y), point: ip };
233
+ }
234
+ if (res2 === 0) {
235
+ const ip = p2;
236
+ if (Core_1.Point64Utils.equals(p2, p3) || Core_1.Point64Utils.equals(p2, p4))
237
+ return { intersects: true, point: ip };
238
+ if (RectClip64.isHorizontal(p3, p4)) {
239
+ return { intersects: (p2.x > p3.x) === (p2.x < p4.x), point: ip };
240
+ }
241
+ return { intersects: (p2.y > p3.y) === (p2.y < p4.y), point: ip };
242
+ }
243
+ if ((res1 > 0) === (res2 > 0)) {
244
+ return { intersects: false, point: { x: 0, y: 0 } };
245
+ }
246
+ const res3 = Core_1.InternalClipper.crossProduct(p3, p1, p2);
247
+ const res4 = Core_1.InternalClipper.crossProduct(p4, p1, p2);
248
+ if (res3 === 0) {
249
+ const ip = p3;
250
+ if (Core_1.Point64Utils.equals(p3, p1) || Core_1.Point64Utils.equals(p3, p2))
251
+ return { intersects: true, point: ip };
252
+ if (RectClip64.isHorizontal(p1, p2)) {
253
+ return { intersects: (p3.x > p1.x) === (p3.x < p2.x), point: ip };
254
+ }
255
+ return { intersects: (p3.y > p1.y) === (p3.y < p2.y), point: ip };
256
+ }
257
+ if (res4 === 0) {
258
+ const ip = p4;
259
+ if (Core_1.Point64Utils.equals(p4, p1) || Core_1.Point64Utils.equals(p4, p2))
260
+ return { intersects: true, point: ip };
261
+ if (RectClip64.isHorizontal(p1, p2)) {
262
+ return { intersects: (p4.x > p1.x) === (p4.x < p2.x), point: ip };
263
+ }
264
+ return { intersects: (p4.y > p1.y) === (p4.y < p2.y), point: ip };
265
+ }
266
+ if ((res3 > 0) === (res4 > 0)) {
267
+ return { intersects: false, point: { x: 0, y: 0 } };
268
+ }
269
+ // segments must intersect to get here
270
+ return Core_1.InternalClipper.getLineIntersectPt(p1, p2, p3, p4);
271
+ }
272
+ static getIntersection(rectPath, p, p2, loc) {
273
+ // gets the pt of intersection between rectPath and segment(p, p2) that's closest to 'p'
274
+ // when result == false, loc will remain unchanged
275
+ let ip = { x: 0, y: 0 };
276
+ let newLocation = loc;
277
+ switch (loc) {
278
+ case Location.left:
279
+ {
280
+ const result1 = RectClip64.getSegmentIntersection(p, p2, rectPath[0], rectPath[3]);
281
+ if (result1.intersects) {
282
+ ip = result1.point;
283
+ return { intersects: true, point: ip, newLocation };
284
+ }
285
+ if (p.y < rectPath[0].y) {
286
+ const result2 = RectClip64.getSegmentIntersection(p, p2, rectPath[0], rectPath[1]);
287
+ if (result2.intersects) {
288
+ newLocation = Location.top;
289
+ return { intersects: true, point: result2.point, newLocation };
290
+ }
291
+ }
292
+ const result3 = RectClip64.getSegmentIntersection(p, p2, rectPath[2], rectPath[3]);
293
+ if (result3.intersects) {
294
+ newLocation = Location.bottom;
295
+ return { intersects: true, point: result3.point, newLocation };
296
+ }
297
+ return { intersects: false, point: ip, newLocation };
298
+ }
299
+ case Location.right:
300
+ {
301
+ const result1 = RectClip64.getSegmentIntersection(p, p2, rectPath[1], rectPath[2]);
302
+ if (result1.intersects) {
303
+ return { intersects: true, point: result1.point, newLocation };
304
+ }
305
+ if (p.y < rectPath[0].y) {
306
+ const result2 = RectClip64.getSegmentIntersection(p, p2, rectPath[0], rectPath[1]);
307
+ if (result2.intersects) {
308
+ newLocation = Location.top;
309
+ return { intersects: true, point: result2.point, newLocation };
310
+ }
311
+ }
312
+ const result3 = RectClip64.getSegmentIntersection(p, p2, rectPath[2], rectPath[3]);
313
+ if (result3.intersects) {
314
+ newLocation = Location.bottom;
315
+ return { intersects: true, point: result3.point, newLocation };
316
+ }
317
+ return { intersects: false, point: ip, newLocation };
318
+ }
319
+ case Location.top:
320
+ {
321
+ const result1 = RectClip64.getSegmentIntersection(p, p2, rectPath[0], rectPath[1]);
322
+ if (result1.intersects) {
323
+ return { intersects: true, point: result1.point, newLocation };
324
+ }
325
+ if (p.x < rectPath[0].x) {
326
+ const result2 = RectClip64.getSegmentIntersection(p, p2, rectPath[0], rectPath[3]);
327
+ if (result2.intersects) {
328
+ newLocation = Location.left;
329
+ return { intersects: true, point: result2.point, newLocation };
330
+ }
331
+ }
332
+ if (p.x <= rectPath[1].x) {
333
+ return { intersects: false, point: ip, newLocation };
334
+ }
335
+ const result3 = RectClip64.getSegmentIntersection(p, p2, rectPath[1], rectPath[2]);
336
+ if (result3.intersects) {
337
+ newLocation = Location.right;
338
+ return { intersects: true, point: result3.point, newLocation };
339
+ }
340
+ return { intersects: false, point: ip, newLocation };
341
+ }
342
+ case Location.bottom:
343
+ {
344
+ const result1 = RectClip64.getSegmentIntersection(p, p2, rectPath[2], rectPath[3]);
345
+ if (result1.intersects) {
346
+ return { intersects: true, point: result1.point, newLocation };
347
+ }
348
+ if (p.x < rectPath[3].x) {
349
+ const result2 = RectClip64.getSegmentIntersection(p, p2, rectPath[0], rectPath[3]);
350
+ if (result2.intersects) {
351
+ newLocation = Location.left;
352
+ return { intersects: true, point: result2.point, newLocation };
353
+ }
354
+ }
355
+ if (p.x <= rectPath[2].x) {
356
+ return { intersects: false, point: ip, newLocation };
357
+ }
358
+ const result3 = RectClip64.getSegmentIntersection(p, p2, rectPath[1], rectPath[2]);
359
+ if (result3.intersects) {
360
+ newLocation = Location.right;
361
+ return { intersects: true, point: result3.point, newLocation };
362
+ }
363
+ return { intersects: false, point: ip, newLocation };
364
+ }
365
+ default:
366
+ {
367
+ const result1 = RectClip64.getSegmentIntersection(p, p2, rectPath[0], rectPath[3]);
368
+ if (result1.intersects) {
369
+ newLocation = Location.left;
370
+ return { intersects: true, point: result1.point, newLocation };
371
+ }
372
+ const result2 = RectClip64.getSegmentIntersection(p, p2, rectPath[0], rectPath[1]);
373
+ if (result2.intersects) {
374
+ newLocation = Location.top;
375
+ return { intersects: true, point: result2.point, newLocation };
376
+ }
377
+ const result3 = RectClip64.getSegmentIntersection(p, p2, rectPath[1], rectPath[2]);
378
+ if (result3.intersects) {
379
+ newLocation = Location.right;
380
+ return { intersects: true, point: result3.point, newLocation };
381
+ }
382
+ const result4 = RectClip64.getSegmentIntersection(p, p2, rectPath[2], rectPath[3]);
383
+ if (result4.intersects) {
384
+ newLocation = Location.bottom;
385
+ return { intersects: true, point: result4.point, newLocation };
386
+ }
387
+ return { intersects: false, point: ip, newLocation };
388
+ }
389
+ }
390
+ }
391
+ getNextLocation(path, loc, i, highI) {
392
+ let newI = i;
393
+ let newLoc = loc;
394
+ switch (loc) {
395
+ case Location.left:
396
+ while (newI <= highI && path[newI].x <= this.rect.left)
397
+ newI++;
398
+ if (newI > highI)
399
+ break;
400
+ if (path[newI].x >= this.rect.right)
401
+ newLoc = Location.right;
402
+ else if (path[newI].y <= this.rect.top)
403
+ newLoc = Location.top;
404
+ else if (path[newI].y >= this.rect.bottom)
405
+ newLoc = Location.bottom;
406
+ else
407
+ newLoc = Location.inside;
408
+ break;
409
+ case Location.top:
410
+ while (newI <= highI && path[newI].y <= this.rect.top)
411
+ newI++;
412
+ if (newI > highI)
413
+ break;
414
+ if (path[newI].y >= this.rect.bottom)
415
+ newLoc = Location.bottom;
416
+ else if (path[newI].x <= this.rect.left)
417
+ newLoc = Location.left;
418
+ else if (path[newI].x >= this.rect.right)
419
+ newLoc = Location.right;
420
+ else
421
+ newLoc = Location.inside;
422
+ break;
423
+ case Location.right:
424
+ while (newI <= highI && path[newI].x >= this.rect.right)
425
+ newI++;
426
+ if (newI > highI)
427
+ break;
428
+ if (path[newI].x <= this.rect.left)
429
+ newLoc = Location.left;
430
+ else if (path[newI].y <= this.rect.top)
431
+ newLoc = Location.top;
432
+ else if (path[newI].y >= this.rect.bottom)
433
+ newLoc = Location.bottom;
434
+ else
435
+ newLoc = Location.inside;
436
+ break;
437
+ case Location.bottom:
438
+ while (newI <= highI && path[newI].y >= this.rect.bottom)
439
+ newI++;
440
+ if (newI > highI)
441
+ break;
442
+ if (path[newI].y <= this.rect.top)
443
+ newLoc = Location.top;
444
+ else if (path[newI].x <= this.rect.left)
445
+ newLoc = Location.left;
446
+ else if (path[newI].x >= this.rect.right)
447
+ newLoc = Location.right;
448
+ else
449
+ newLoc = Location.inside;
450
+ break;
451
+ case Location.inside:
452
+ while (newI <= highI) {
453
+ if (path[newI].x < this.rect.left)
454
+ newLoc = Location.left;
455
+ else if (path[newI].x > this.rect.right)
456
+ newLoc = Location.right;
457
+ else if (path[newI].y > this.rect.bottom)
458
+ newLoc = Location.bottom;
459
+ else if (path[newI].y < this.rect.top)
460
+ newLoc = Location.top;
461
+ else {
462
+ this.add(path[newI]);
463
+ newI++;
464
+ continue;
465
+ }
466
+ break;
467
+ }
468
+ break;
469
+ }
470
+ return { location: newLoc, index: newI };
471
+ }
472
+ static startLocsAreClockwise(startLocs) {
473
+ let result = 0;
474
+ for (let i = 1; i < startLocs.length; i++) {
475
+ const d = startLocs[i] - startLocs[i - 1];
476
+ switch (d) {
477
+ case -1:
478
+ result -= 1;
479
+ break;
480
+ case 1:
481
+ result += 1;
482
+ break;
483
+ case -3:
484
+ result += 1;
485
+ break;
486
+ case 3:
487
+ result -= 1;
488
+ break;
489
+ }
490
+ }
491
+ return result > 0;
492
+ }
493
+ executeInternal(path) {
494
+ if (path.length < 3 || Core_1.Rect64Utils.isEmpty(this.rect))
495
+ return;
496
+ const startLocs = [];
497
+ let firstCross = Location.inside;
498
+ let crossingLoc = firstCross;
499
+ let prev = firstCross;
500
+ const highI = path.length - 1;
501
+ const lastLocResult = RectClip64.getLocation(this.rect, path[highI]);
502
+ let loc = lastLocResult.location;
503
+ if (lastLocResult.isOnRect) {
504
+ let i = highI - 1;
505
+ while (i >= 0) {
506
+ const prevLocResult = RectClip64.getLocation(this.rect, path[i]);
507
+ if (!prevLocResult.isOnRect) {
508
+ prev = prevLocResult.location;
509
+ break;
510
+ }
511
+ i--;
512
+ }
513
+ if (i < 0) {
514
+ for (const pt of path) {
515
+ this.add(pt);
516
+ }
517
+ return;
518
+ }
519
+ if (prev === Location.inside)
520
+ loc = Location.inside;
521
+ }
522
+ const startingLoc = loc;
523
+ ///////////////////////////////////////////////////
524
+ let i = 0;
525
+ while (i <= highI) {
526
+ prev = loc;
527
+ const prevCrossLoc = crossingLoc;
528
+ const nextLocResult = this.getNextLocation(path, loc, i, highI);
529
+ loc = nextLocResult.location;
530
+ i = nextLocResult.index;
531
+ if (i > highI)
532
+ break;
533
+ const prevPt = (i === 0) ? path[highI] : path[i - 1];
534
+ crossingLoc = loc;
535
+ const intersectionResult = RectClip64.getIntersection(this.rectPath, path[i], prevPt, crossingLoc);
536
+ if (!intersectionResult.intersects) {
537
+ // ie remaining outside
538
+ if (prevCrossLoc === Location.inside) {
539
+ const isClockw = RectClip64.isClockwise(prev, loc, prevPt, path[i], this.mp);
540
+ do {
541
+ startLocs.push(prev);
542
+ prev = RectClip64.getAdjacentLocation(prev, isClockw);
543
+ } while (prev !== loc);
544
+ crossingLoc = prevCrossLoc; // still not crossed
545
+ }
546
+ else if (prev !== Location.inside && prev !== loc) {
547
+ const isClockw = RectClip64.isClockwise(prev, loc, prevPt, path[i], this.mp);
548
+ do {
549
+ prev = this.addCornerWithDirection(prev, isClockw);
550
+ } while (prev !== loc);
551
+ }
552
+ ++i;
553
+ continue;
554
+ }
555
+ const ip = intersectionResult.point;
556
+ crossingLoc = intersectionResult.newLocation;
557
+ ////////////////////////////////////////////////////
558
+ // we must be crossing the rect boundary to get here
559
+ ////////////////////////////////////////////////////
560
+ if (loc === Location.inside) { // path must be entering rect
561
+ if (firstCross === Location.inside) {
562
+ firstCross = crossingLoc;
563
+ startLocs.push(prev);
564
+ }
565
+ else if (prev !== crossingLoc) {
566
+ const isClockw = RectClip64.isClockwise(prev, crossingLoc, prevPt, path[i], this.mp);
567
+ do {
568
+ prev = this.addCornerWithDirection(prev, isClockw);
569
+ } while (prev !== crossingLoc);
570
+ }
571
+ }
572
+ else if (prev !== Location.inside) {
573
+ // passing right through rect. 'ip' here will be the second
574
+ // intersect pt but we'll also need the first intersect pt (ip2)
575
+ loc = prev;
576
+ const intersection2Result = RectClip64.getIntersection(this.rectPath, prevPt, path[i], loc);
577
+ const ip2 = intersection2Result.point;
578
+ if (prevCrossLoc !== Location.inside && prevCrossLoc !== loc) { //#597
579
+ this.addCorner(prevCrossLoc, loc);
580
+ }
581
+ if (firstCross === Location.inside) {
582
+ firstCross = loc;
583
+ startLocs.push(prev);
584
+ }
585
+ loc = crossingLoc;
586
+ this.add(ip2);
587
+ if (Core_1.Point64Utils.equals(ip, ip2)) {
588
+ // it's very likely that path[i] is on rect
589
+ const pathLocResult = RectClip64.getLocation(this.rect, path[i]);
590
+ loc = pathLocResult.location;
591
+ this.addCorner(crossingLoc, loc);
592
+ crossingLoc = loc;
593
+ continue;
594
+ }
595
+ }
596
+ else { // path must be exiting rect
597
+ loc = crossingLoc;
598
+ if (firstCross === Location.inside) {
599
+ firstCross = crossingLoc;
600
+ }
601
+ }
602
+ this.add(ip);
603
+ } //while i <= highI
604
+ ///////////////////////////////////////////////////
605
+ if (firstCross === Location.inside) {
606
+ // path never intersects
607
+ if (startingLoc === Location.inside)
608
+ return;
609
+ if (!Core_1.Rect64Utils.containsRect(this.pathBounds, this.rect) ||
610
+ !RectClip64.path1ContainsPath2(path, this.rectPath))
611
+ return;
612
+ const startLocsClockwise = RectClip64.startLocsAreClockwise(startLocs);
613
+ for (let j = 0; j < 4; j++) {
614
+ const k = startLocsClockwise ? j : 3 - j; // ie reverse result path
615
+ this.add(this.rectPath[k]);
616
+ RectClip64.addToEdge(this.edges[k * 2], this.results[0]);
617
+ }
618
+ }
619
+ else if (loc !== Location.inside &&
620
+ (loc !== firstCross || startLocs.length > 2)) {
621
+ if (startLocs.length > 0) {
622
+ prev = loc;
623
+ for (const loc2 of startLocs) {
624
+ if (prev === loc2)
625
+ continue;
626
+ prev = this.addCornerWithDirection(prev, RectClip64.headingClockwise(prev, loc2));
627
+ prev = loc2;
628
+ }
629
+ loc = prev;
630
+ }
631
+ if (loc !== firstCross) {
632
+ this.addCornerWithDirection(loc, RectClip64.headingClockwise(loc, firstCross));
633
+ }
634
+ }
635
+ }
636
+ execute(paths) {
637
+ const result = [];
638
+ if (Core_1.Rect64Utils.isEmpty(this.rect))
639
+ return result;
640
+ for (const path of paths) {
641
+ if (path.length < 3)
642
+ continue;
643
+ this.pathBounds = Core_1.InternalClipper.getBounds(path);
644
+ if (!Core_1.Rect64Utils.intersects(this.rect, this.pathBounds)) {
645
+ continue; // the path must be completely outside rect
646
+ }
647
+ if (Core_1.Rect64Utils.containsRect(this.rect, this.pathBounds)) {
648
+ // the path must be completely inside rect
649
+ result.push(path);
650
+ continue;
651
+ }
652
+ this.executeInternal(path);
653
+ this.checkEdges();
654
+ for (let i = 0; i < 4; ++i) {
655
+ this.tidyEdgePair(i, this.edges[i * 2], this.edges[i * 2 + 1]);
656
+ }
657
+ for (const op of this.results) {
658
+ const tmp = this.getPath(op);
659
+ if (tmp.length > 0)
660
+ result.push(tmp);
661
+ }
662
+ //clean up after every loop
663
+ this.results.length = 0;
664
+ for (let i = 0; i < 8; i++) {
665
+ this.edges[i].length = 0;
666
+ }
667
+ }
668
+ return result;
669
+ }
670
+ checkEdges() {
671
+ for (let i = 0; i < this.results.length; i++) {
672
+ let op = this.results[i];
673
+ let op2 = op;
674
+ if (op === null)
675
+ continue;
676
+ do {
677
+ if (Core_1.InternalClipper.isCollinear(op2.prev.pt, op2.pt, op2.next.pt)) {
678
+ if (op2 === op) {
679
+ op2 = RectClip64.unlinkOpBack(op2);
680
+ if (op2 === null)
681
+ break;
682
+ op = op2.prev;
683
+ }
684
+ else {
685
+ op2 = RectClip64.unlinkOpBack(op2);
686
+ if (op2 === null)
687
+ break;
688
+ }
689
+ }
690
+ else {
691
+ op2 = op2.next;
692
+ }
693
+ } while (op2 !== op);
694
+ if (op2 === null) {
695
+ this.results[i] = null;
696
+ continue;
697
+ }
698
+ this.results[i] = op2; // safety first
699
+ let edgeSet1 = RectClip64.getEdgesForPt(op.prev.pt, this.rect);
700
+ op2 = op;
701
+ do {
702
+ const edgeSet2 = RectClip64.getEdgesForPt(op2.pt, this.rect);
703
+ if (edgeSet2 !== 0 && op2.edge === null) {
704
+ const combinedSet = (edgeSet1 & edgeSet2);
705
+ for (let j = 0; j < 4; ++j) {
706
+ if ((combinedSet & (1 << j)) === 0)
707
+ continue;
708
+ if (RectClip64.isHeadingClockwise(op2.prev.pt, op2.pt, j)) {
709
+ RectClip64.addToEdge(this.edges[j * 2], op2);
710
+ }
711
+ else {
712
+ RectClip64.addToEdge(this.edges[j * 2 + 1], op2);
713
+ }
714
+ }
715
+ }
716
+ edgeSet1 = edgeSet2;
717
+ op2 = op2.next;
718
+ } while (op2 !== op);
719
+ }
720
+ }
721
+ tidyEdgePair(idx, cw, ccw) {
722
+ if (ccw.length === 0)
723
+ return;
724
+ const isHorz = ((idx === 1) || (idx === 3));
725
+ const cwIsTowardLarger = ((idx === 1) || (idx === 2));
726
+ let i = 0, j = 0;
727
+ while (i < cw.length) {
728
+ let p1 = cw[i];
729
+ if (p1 === null || p1.next === p1.prev) {
730
+ cw[i++] = null;
731
+ j = 0;
732
+ continue;
733
+ }
734
+ const jLim = ccw.length;
735
+ while (j < jLim && (ccw[j] === null || ccw[j].next === ccw[j].prev))
736
+ ++j;
737
+ if (j === jLim) {
738
+ ++i;
739
+ j = 0;
740
+ continue;
741
+ }
742
+ let p2;
743
+ let p1a;
744
+ let p2a;
745
+ if (cwIsTowardLarger) {
746
+ // p1 >>>> p1a;
747
+ // p2 <<<< p2a;
748
+ p1 = cw[i].prev;
749
+ p1a = cw[i];
750
+ p2 = ccw[j];
751
+ p2a = ccw[j].prev;
752
+ }
753
+ else {
754
+ // p1 <<<< p1a;
755
+ // p2 >>>> p2a;
756
+ p1 = cw[i];
757
+ p1a = cw[i].prev;
758
+ p2 = ccw[j].prev;
759
+ p2a = ccw[j];
760
+ }
761
+ if ((isHorz && !RectClip64.hasHorzOverlap(p1.pt, p1a.pt, p2.pt, p2a.pt)) ||
762
+ (!isHorz && !RectClip64.hasVertOverlap(p1.pt, p1a.pt, p2.pt, p2a.pt))) {
763
+ ++j;
764
+ continue;
765
+ }
766
+ // to get here we're either splitting or rejoining
767
+ const isRejoining = cw[i].ownerIdx !== ccw[j].ownerIdx;
768
+ if (isRejoining) {
769
+ this.results[p2.ownerIdx] = null;
770
+ RectClip64.setNewOwner(p2, p1.ownerIdx);
771
+ }
772
+ // do the split or re-join
773
+ if (cwIsTowardLarger) {
774
+ // p1 >> | >> p1a;
775
+ // p2 << | << p2a;
776
+ p1.next = p2;
777
+ p2.prev = p1;
778
+ p1a.prev = p2a;
779
+ p2a.next = p1a;
780
+ }
781
+ else {
782
+ // p1 << | << p1a;
783
+ // p2 >> | >> p2a;
784
+ p1.prev = p2;
785
+ p2.next = p1;
786
+ p1a.next = p2a;
787
+ p2a.prev = p1a;
788
+ }
789
+ if (!isRejoining) {
790
+ const newIdx = this.results.length;
791
+ this.results.push(p1a);
792
+ RectClip64.setNewOwner(p1a, newIdx);
793
+ }
794
+ let op;
795
+ let op2;
796
+ if (cwIsTowardLarger) {
797
+ op = p2;
798
+ op2 = p1a;
799
+ }
800
+ else {
801
+ op = p1;
802
+ op2 = p2a;
803
+ }
804
+ this.results[op.ownerIdx] = op;
805
+ this.results[op2.ownerIdx] = op2;
806
+ // and now lots of work to get ready for the next loop
807
+ let opIsLarger, op2IsLarger;
808
+ if (isHorz) { // X
809
+ opIsLarger = op.pt.x > op.prev.pt.x;
810
+ op2IsLarger = op2.pt.x > op2.prev.pt.x;
811
+ }
812
+ else { // Y
813
+ opIsLarger = op.pt.y > op.prev.pt.y;
814
+ op2IsLarger = op2.pt.y > op2.prev.pt.y;
815
+ }
816
+ if ((op.next === op.prev) || Core_1.Point64Utils.equals(op.pt, op.prev.pt)) {
817
+ if (op2IsLarger === cwIsTowardLarger) {
818
+ cw[i] = op2;
819
+ ccw[j++] = null;
820
+ }
821
+ else {
822
+ ccw[j] = op2;
823
+ cw[i++] = null;
824
+ }
825
+ }
826
+ else if ((op2.next === op2.prev) || Core_1.Point64Utils.equals(op2.pt, op2.prev.pt)) {
827
+ if (opIsLarger === cwIsTowardLarger) {
828
+ cw[i] = op;
829
+ ccw[j++] = null;
830
+ }
831
+ else {
832
+ ccw[j] = op;
833
+ cw[i++] = null;
834
+ }
835
+ }
836
+ else if (opIsLarger === op2IsLarger) {
837
+ if (opIsLarger === cwIsTowardLarger) {
838
+ cw[i] = op;
839
+ RectClip64.uncoupleEdge(op2);
840
+ RectClip64.addToEdge(cw, op2);
841
+ ccw[j++] = null;
842
+ }
843
+ else {
844
+ cw[i++] = null;
845
+ ccw[j] = op2;
846
+ RectClip64.uncoupleEdge(op);
847
+ RectClip64.addToEdge(ccw, op);
848
+ j = 0;
849
+ }
850
+ }
851
+ else {
852
+ if (opIsLarger === cwIsTowardLarger) {
853
+ cw[i] = op;
854
+ }
855
+ else {
856
+ ccw[j] = op;
857
+ }
858
+ if (op2IsLarger === cwIsTowardLarger) {
859
+ cw[i] = op2;
860
+ }
861
+ else {
862
+ ccw[j] = op2;
863
+ }
864
+ }
865
+ }
866
+ }
867
+ getPath(op) {
868
+ const result = [];
869
+ if (op === null || op.prev === op.next)
870
+ return result;
871
+ let op2 = op.next;
872
+ while (op2 !== null && op2 !== op) {
873
+ if (Core_1.InternalClipper.isCollinear(op2.prev.pt, op2.pt, op2.next.pt)) {
874
+ op = op2.prev;
875
+ op2 = RectClip64.unlinkOp(op2);
876
+ }
877
+ else {
878
+ op2 = op2.next;
879
+ }
880
+ }
881
+ if (op2 === null)
882
+ return [];
883
+ result.push(op.pt);
884
+ op2 = op.next;
885
+ while (op2 !== op) {
886
+ result.push(op2.pt);
887
+ op2 = op2.next;
888
+ }
889
+ return result;
890
+ }
891
+ }
892
+ exports.RectClip64 = RectClip64;
893
+ class RectClipLines64 extends RectClip64 {
894
+ constructor(rect) {
895
+ super(rect);
896
+ }
897
+ execute(paths) {
898
+ const result = [];
899
+ if (Core_1.Rect64Utils.isEmpty(this.rect))
900
+ return result;
901
+ for (const path of paths) {
902
+ if (path.length < 2)
903
+ continue;
904
+ this.pathBounds = Core_1.InternalClipper.getBounds(path);
905
+ if (!Core_1.Rect64Utils.intersects(this.rect, this.pathBounds)) {
906
+ continue; // the path must be completely outside rect
907
+ }
908
+ // Apart from that, we can't be sure whether the path
909
+ // is completely outside or completed inside or intersects
910
+ // rect, simply by comparing path bounds with rect.
911
+ this.executeInternalLines(path);
912
+ for (const op of this.results) {
913
+ const tmp = this.getPathLines(op);
914
+ if (tmp.length > 0)
915
+ result.push(tmp);
916
+ }
917
+ //clean up after every loop
918
+ this.results.length = 0;
919
+ for (let i = 0; i < 8; i++) {
920
+ this.edges[i].length = 0;
921
+ }
922
+ }
923
+ return result;
924
+ }
925
+ getPathLines(op) {
926
+ const result = [];
927
+ if (op === null || op === op.next)
928
+ return result;
929
+ op = op.next; // starting at path beginning
930
+ result.push(op.pt);
931
+ let op2 = op.next;
932
+ while (op2 !== op) {
933
+ result.push(op2.pt);
934
+ op2 = op2.next;
935
+ }
936
+ return result;
937
+ }
938
+ executeInternalLines(path) {
939
+ this.results.length = 0;
940
+ if (path.length < 2 || Core_1.Rect64Utils.isEmpty(this.rect))
941
+ return;
942
+ let prev = Location.inside;
943
+ let i = 1;
944
+ const highI = path.length - 1;
945
+ const firstLocResult = RectClip64.getLocation(this.rect, path[0]);
946
+ let loc = firstLocResult.location;
947
+ if (firstLocResult.isOnRect) {
948
+ while (i <= highI) {
949
+ const prevLocResult = RectClip64.getLocation(this.rect, path[i]);
950
+ if (!prevLocResult.isOnRect) {
951
+ prev = prevLocResult.location;
952
+ break;
953
+ }
954
+ i++;
955
+ }
956
+ if (i > highI) {
957
+ for (const pt of path) {
958
+ this.add(pt);
959
+ }
960
+ return;
961
+ }
962
+ if (prev === Location.inside)
963
+ loc = Location.inside;
964
+ i = 1;
965
+ }
966
+ if (loc === Location.inside)
967
+ this.add(path[0]);
968
+ ///////////////////////////////////////////////////
969
+ while (i <= highI) {
970
+ prev = loc;
971
+ const nextLocResult = this.getNextLocation(path, loc, i, highI);
972
+ loc = nextLocResult.location;
973
+ i = nextLocResult.index;
974
+ if (i > highI)
975
+ break;
976
+ const prevPt = path[i - 1];
977
+ let crossingLoc = loc;
978
+ const intersectionResult = RectClip64.getIntersection(this.rectPath, path[i], prevPt, crossingLoc);
979
+ if (!intersectionResult.intersects) {
980
+ // ie remaining outside (& crossingLoc still == loc)
981
+ ++i;
982
+ continue;
983
+ }
984
+ const ip = intersectionResult.point;
985
+ crossingLoc = intersectionResult.newLocation;
986
+ ////////////////////////////////////////////////////
987
+ // we must be crossing the rect boundary to get here
988
+ ////////////////////////////////////////////////////
989
+ if (loc === Location.inside) { // path must be entering rect
990
+ this.add(ip, true);
991
+ }
992
+ else if (prev !== Location.inside) {
993
+ // passing right through rect. 'ip' here will be the second
994
+ // intersect pt but we'll also need the first intersect pt (ip2)
995
+ crossingLoc = prev;
996
+ const intersection2Result = RectClip64.getIntersection(this.rectPath, prevPt, path[i], crossingLoc);
997
+ const ip2 = intersection2Result.point;
998
+ this.add(ip2, true);
999
+ this.add(ip);
1000
+ }
1001
+ else { // path must be exiting rect
1002
+ this.add(ip);
1003
+ }
1004
+ } //while i <= highI
1005
+ ///////////////////////////////////////////////////
1006
+ }
1007
+ }
1008
+ exports.RectClipLines64 = RectClipLines64;
1009
+ //# sourceMappingURL=RectClip.js.map