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