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/src/Clipper.ts ADDED
@@ -0,0 +1,1106 @@
1
+ /*******************************************************************************
2
+ * Author : Angus Johnson *
3
+ * Date : 5 March 2025 *
4
+ * Website : https://www.angusj.com *
5
+ * Copyright : Angus Johnson 2010-2025 *
6
+ * Purpose : This module contains simple functions that will likely cover *
7
+ * most polygon boolean and offsetting needs, while also avoiding *
8
+ * the inherent complexities of the other modules. *
9
+ * License : https://www.boost.org/LICENSE_1_0.txt *
10
+ *******************************************************************************/
11
+
12
+ import {
13
+ Point64, PointD, Path64, PathD, Paths64, PathsD, Rect64, RectD,
14
+ ClipType, PathType, FillRule, PointInPolygonResult,
15
+ InternalClipper, Point64Utils, PointDUtils, Rect64Utils, RectDUtils,
16
+ InvalidRect64, InvalidRectD
17
+ } from './Core';
18
+ import { Clipper64, ClipperD, PolyTree64, PolyTreeD, PolyPathD } from './Engine';
19
+ import { ClipperOffset, JoinType, EndType } from './Offset';
20
+ import { RectClip64, RectClipLines64 } from './RectClip';
21
+ import { Minkowski } from './Minkowski';
22
+
23
+ export namespace Clipper {
24
+ // Constants
25
+ export const invalidRect64 = InvalidRect64;
26
+ export const invalidRectD = InvalidRectD;
27
+
28
+ // Boolean operations
29
+ export function intersect(subject: Paths64, clip: Paths64, fillRule: FillRule): Paths64 {
30
+ return booleanOp(ClipType.Intersection, subject, clip, fillRule);
31
+ }
32
+
33
+ export function intersectD(subject: PathsD, clip: PathsD, fillRule: FillRule, precision: number = 2): PathsD {
34
+ return booleanOpD(ClipType.Intersection, subject, clip, fillRule, precision);
35
+ }
36
+
37
+ export function union(subject: Paths64, fillRule: FillRule): Paths64;
38
+ export function union(subject: Paths64, clip: Paths64, fillRule: FillRule): Paths64;
39
+ export function union(subject: Paths64, clipOrFillRule: Paths64 | FillRule, fillRule?: FillRule): Paths64 {
40
+ if (typeof clipOrFillRule === 'number') {
41
+ // First overload: union(subject, fillRule)
42
+ return booleanOp(ClipType.Union, subject, null, clipOrFillRule);
43
+ } else {
44
+ // Second overload: union(subject, clip, fillRule)
45
+ return booleanOp(ClipType.Union, subject, clipOrFillRule, fillRule!);
46
+ }
47
+ }
48
+
49
+ export function unionD(subject: PathsD, fillRule: FillRule): PathsD;
50
+ export function unionD(subject: PathsD, clip: PathsD, fillRule: FillRule, precision?: number): PathsD;
51
+ export function unionD(subject: PathsD, clipOrFillRule: PathsD | FillRule, fillRuleOrPrecision?: FillRule | number, precision?: number): PathsD {
52
+ if (typeof clipOrFillRule === 'number') {
53
+ // First overload: unionD(subject, fillRule)
54
+ return booleanOpD(ClipType.Union, subject, null, clipOrFillRule);
55
+ } else {
56
+ // Second overload: unionD(subject, clip, fillRule, precision)
57
+ return booleanOpD(ClipType.Union, subject, clipOrFillRule, fillRuleOrPrecision as FillRule, precision || 2);
58
+ }
59
+ }
60
+
61
+ export function difference(subject: Paths64, clip: Paths64, fillRule: FillRule): Paths64 {
62
+ return booleanOp(ClipType.Difference, subject, clip, fillRule);
63
+ }
64
+
65
+ export function differenceD(subject: PathsD, clip: PathsD, fillRule: FillRule, precision: number = 2): PathsD {
66
+ return booleanOpD(ClipType.Difference, subject, clip, fillRule, precision);
67
+ }
68
+
69
+ export function xor(subject: Paths64, clip: Paths64, fillRule: FillRule): Paths64 {
70
+ return booleanOp(ClipType.Xor, subject, clip, fillRule);
71
+ }
72
+
73
+ export function xorD(subject: PathsD, clip: PathsD, fillRule: FillRule, precision: number = 2): PathsD {
74
+ return booleanOpD(ClipType.Xor, subject, clip, fillRule, precision);
75
+ }
76
+
77
+ export function booleanOp(clipType: ClipType, subject: Paths64 | null, clip: Paths64 | null, fillRule: FillRule): Paths64 {
78
+ const solution: Paths64 = [];
79
+ if (subject === null) return solution;
80
+ const c = new Clipper64();
81
+ c.addPaths(subject, PathType.Subject);
82
+ if (clip !== null) {
83
+ c.addPaths(clip, PathType.Clip);
84
+ }
85
+ c.execute(clipType, fillRule, solution);
86
+ return solution;
87
+ }
88
+
89
+ export function booleanOpWithPolyTree(clipType: ClipType, subject: Paths64 | null, clip: Paths64 | null, polytree: PolyTree64, fillRule: FillRule): void {
90
+ if (subject === null) return;
91
+ const c = new Clipper64();
92
+ c.addPaths(subject, PathType.Subject);
93
+ if (clip !== null) {
94
+ c.addPaths(clip, PathType.Clip);
95
+ }
96
+ c.execute(clipType, fillRule, polytree);
97
+ }
98
+
99
+ export function booleanOpD(clipType: ClipType, subject: PathsD, clip: PathsD | null, fillRule: FillRule, precision: number = 2): PathsD {
100
+ const solution: PathsD = [];
101
+ const c = new ClipperD(precision);
102
+ c.addSubjectPaths(subject);
103
+ if (clip !== null) {
104
+ c.addClipPaths(clip);
105
+ }
106
+ c.execute(clipType, fillRule, solution);
107
+ return solution;
108
+ }
109
+
110
+ export function booleanOpDWithPolyTree(
111
+ clipType: ClipType,
112
+ subject: PathsD | null,
113
+ clip: PathsD | null,
114
+ polytree: PolyTreeD,
115
+ fillRule: FillRule,
116
+ precision: number = 2
117
+ ): void {
118
+ if (subject === null) return;
119
+ const c = new ClipperD(precision);
120
+ c.addSubjectPaths(subject);
121
+ if (clip !== null) {
122
+ c.addClipPaths(clip);
123
+ }
124
+ c.execute(clipType, fillRule, polytree);
125
+ }
126
+
127
+ export function inflatePaths(paths: Paths64, delta: number, joinType: JoinType, endType: EndType, miterLimit: number = 2.0, arcTolerance: number = 0.0): Paths64 {
128
+ const co = new ClipperOffset(miterLimit, arcTolerance);
129
+ co.addPaths(paths, joinType, endType);
130
+ const solution: Paths64 = [];
131
+ co.execute(delta, solution);
132
+ return solution;
133
+ }
134
+
135
+ export function inflatePathsD(paths: PathsD, delta: number, joinType: JoinType, endType: EndType, miterLimit: number = 2.0, precision: number = 2, arcTolerance: number = 0.0): PathsD {
136
+ InternalClipper.checkPrecision(precision);
137
+ const scale = Math.pow(10, precision);
138
+ const tmp = scalePaths64(paths, scale);
139
+ const co = new ClipperOffset(miterLimit, scale * arcTolerance);
140
+ co.addPaths(tmp, joinType, endType);
141
+ const solution: Paths64 = [];
142
+ co.execute(delta * scale, solution); // reuse solution to receive (scaled) solution
143
+ return scalePathsD(solution, 1 / scale);
144
+ }
145
+
146
+ export function rectClip(rect: Rect64, paths: Paths64): Paths64;
147
+ export function rectClip(rect: Rect64, path: Path64): Paths64;
148
+ export function rectClip(rect: RectD, paths: PathsD, precision?: number): PathsD;
149
+ export function rectClip(rect: RectD, path: PathD, precision?: number): PathsD;
150
+ export function rectClip(rect: Rect64 | RectD, pathsOrPath: Paths64 | Path64 | PathsD | PathD, precision?: number): Paths64 | PathsD {
151
+ if ('left' in rect && typeof rect.left === 'number' && Number.isInteger(rect.left)) {
152
+ // Rect64 case
153
+ const rect64 = rect as Rect64;
154
+ if (Rect64Utils.isEmpty(rect64)) return [];
155
+
156
+ if (Array.isArray(pathsOrPath[0])) {
157
+ // Paths64
158
+ const paths = pathsOrPath as Paths64;
159
+ if (paths.length === 0) return [];
160
+ const rc = new RectClip64(rect64);
161
+ return rc.execute(paths);
162
+ } else {
163
+ // Path64
164
+ const path = pathsOrPath as Path64;
165
+ if (path.length === 0) return [];
166
+ const tmp: Paths64 = [path];
167
+ return rectClip(rect64, tmp);
168
+ }
169
+ } else {
170
+ // RectD case
171
+ const rectD = rect as RectD;
172
+ const prec = precision || 2;
173
+ InternalClipper.checkPrecision(prec);
174
+ if (RectDUtils.isEmpty(rectD)) return [];
175
+
176
+ const scale = Math.pow(10, prec);
177
+ const r = scaleRect(rectD, scale);
178
+
179
+ if (Array.isArray(pathsOrPath[0])) {
180
+ // PathsD
181
+ const paths = pathsOrPath as PathsD;
182
+ if (paths.length === 0) return [];
183
+ const tmpPath = scalePaths64(paths, scale);
184
+ const rc = new RectClip64(r);
185
+ const result = rc.execute(tmpPath);
186
+ return scalePathsD(result, 1 / scale);
187
+ } else {
188
+ // PathD
189
+ const path = pathsOrPath as PathD;
190
+ if (path.length === 0) return [];
191
+ const tmp: PathsD = [path];
192
+ return rectClip(rectD, tmp, prec);
193
+ }
194
+ }
195
+ }
196
+
197
+ export function rectClipLines(rect: Rect64, paths: Paths64): Paths64;
198
+ export function rectClipLines(rect: Rect64, path: Path64): Paths64;
199
+ export function rectClipLines(rect: RectD, paths: PathsD, precision?: number): PathsD;
200
+ export function rectClipLines(rect: RectD, path: PathD, precision?: number): PathsD;
201
+ export function rectClipLines(rect: Rect64 | RectD, pathsOrPath: Paths64 | Path64 | PathsD | PathD, precision?: number): Paths64 | PathsD {
202
+ if ('left' in rect && typeof rect.left === 'number' && Number.isInteger(rect.left)) {
203
+ // Rect64 case
204
+ const rect64 = rect as Rect64;
205
+ if (Rect64Utils.isEmpty(rect64)) return [];
206
+
207
+ if (Array.isArray(pathsOrPath[0])) {
208
+ // Paths64
209
+ const paths = pathsOrPath as Paths64;
210
+ if (paths.length === 0) return [];
211
+ const rc = new RectClipLines64(rect64);
212
+ return rc.execute(paths);
213
+ } else {
214
+ // Path64
215
+ const path = pathsOrPath as Path64;
216
+ if (path.length === 0) return [];
217
+ const tmp: Paths64 = [path];
218
+ return rectClipLines(rect64, tmp);
219
+ }
220
+ } else {
221
+ // RectD case
222
+ const rectD = rect as RectD;
223
+ const prec = precision || 2;
224
+ InternalClipper.checkPrecision(prec);
225
+ if (RectDUtils.isEmpty(rectD)) return [];
226
+
227
+ const scale = Math.pow(10, prec);
228
+ const r = scaleRect(rectD, scale);
229
+
230
+ if (Array.isArray(pathsOrPath[0])) {
231
+ // PathsD
232
+ const paths = pathsOrPath as PathsD;
233
+ if (paths.length === 0) return [];
234
+ const tmpPath = scalePaths64(paths, scale);
235
+ const rc = new RectClipLines64(r);
236
+ const result = rc.execute(tmpPath);
237
+ return scalePathsD(result, 1 / scale);
238
+ } else {
239
+ // PathD
240
+ const path = pathsOrPath as PathD;
241
+ if (path.length === 0) return [];
242
+ const tmp: PathsD = [path];
243
+ return rectClipLines(rectD, tmp, prec);
244
+ }
245
+ }
246
+ }
247
+
248
+ export function minkowskiSum(pattern: Path64, path: Path64, isClosed: boolean): Paths64 {
249
+ return Minkowski.sum(pattern, path, isClosed);
250
+ }
251
+
252
+ export function minkowskiSumD(pattern: PathD, path: PathD, isClosed: boolean): PathsD {
253
+ return Minkowski.sumD(pattern, path, isClosed);
254
+ }
255
+
256
+ export function minkowskiDiff(pattern: Path64, path: Path64, isClosed: boolean): Paths64 {
257
+ return Minkowski.diff(pattern, path, isClosed);
258
+ }
259
+
260
+ export function minkowskiDiffD(pattern: PathD, path: PathD, isClosed: boolean): PathsD {
261
+ return Minkowski.diffD(pattern, path, isClosed);
262
+ }
263
+
264
+ export function area(path: Path64): number {
265
+ // https://en.wikipedia.org/wiki/Shoelace_formula
266
+ let a = 0.0;
267
+ const cnt = path.length;
268
+ if (cnt < 3) return 0.0;
269
+ let prevPt = path[cnt - 1];
270
+ for (const pt of path) {
271
+ a += (prevPt.y + pt.y) * (prevPt.x - pt.x);
272
+ prevPt = pt;
273
+ }
274
+ return a * 0.5;
275
+ }
276
+
277
+ export function areaPaths(paths: Paths64): number {
278
+ let a = 0.0;
279
+ for (const path of paths) {
280
+ a += area(path);
281
+ }
282
+ return a;
283
+ }
284
+
285
+ export function areaD(path: PathD): number {
286
+ let a = 0.0;
287
+ const cnt = path.length;
288
+ if (cnt < 3) return 0.0;
289
+ let prevPt = path[cnt - 1];
290
+ for (const pt of path) {
291
+ a += (prevPt.y + pt.y) * (prevPt.x - pt.x);
292
+ prevPt = pt;
293
+ }
294
+ return a * 0.5;
295
+ }
296
+
297
+ export function areaPathsD(paths: PathsD): number {
298
+ let a = 0.0;
299
+ for (const path of paths) {
300
+ a += areaD(path);
301
+ }
302
+ return a;
303
+ }
304
+
305
+ export function isPositive(poly: Path64): boolean {
306
+ return area(poly) >= 0;
307
+ }
308
+
309
+ export function isPositiveD(poly: PathD): boolean {
310
+ return areaD(poly) >= 0;
311
+ }
312
+
313
+ export function path64ToString(path: Path64): string {
314
+ let result = "";
315
+ for (const pt of path) {
316
+ result += Point64Utils.toString(pt);
317
+ }
318
+ return result + '\n';
319
+ }
320
+
321
+ export function paths64ToString(paths: Paths64): string {
322
+ let result = "";
323
+ for (const path of paths) {
324
+ result += path64ToString(path);
325
+ }
326
+ return result;
327
+ }
328
+
329
+ export function pathDToString(path: PathD, precision: number = 2): string {
330
+ let result = "";
331
+ for (const pt of path) {
332
+ result += PointDUtils.toString(pt, precision);
333
+ }
334
+ return result + '\n';
335
+ }
336
+
337
+ export function pathsDToString(paths: PathsD, precision: number = 2): string {
338
+ let result = "";
339
+ for (const path of paths) {
340
+ result += pathDToString(path, precision);
341
+ }
342
+ return result;
343
+ }
344
+
345
+ export function offsetPath(path: Path64, dx: number, dy: number): Path64 {
346
+ const result: Path64 = [];
347
+ for (const pt of path) {
348
+ result.push({ x: pt.x + dx, y: pt.y + dy });
349
+ }
350
+ return result;
351
+ }
352
+
353
+ export function scalePoint64(pt: Point64, scale: number): Point64 {
354
+ return {
355
+ x: Math.round(pt.x * scale),
356
+ y: Math.round(pt.y * scale)
357
+ };
358
+ }
359
+
360
+ export function scalePointD(pt: Point64, scale: number): PointD {
361
+ return {
362
+ x: pt.x * scale,
363
+ y: pt.y * scale
364
+ };
365
+ }
366
+
367
+ export function scaleRect(rec: RectD, scale: number): Rect64 {
368
+ return {
369
+ left: Math.round(rec.left * scale),
370
+ top: Math.round(rec.top * scale),
371
+ right: Math.round(rec.right * scale),
372
+ bottom: Math.round(rec.bottom * scale)
373
+ };
374
+ }
375
+
376
+ export function scalePath(path: Path64, scale: number): Path64 {
377
+ if (InternalClipper.isAlmostZero(scale - 1)) return path;
378
+ const result: Path64 = [];
379
+ for (const pt of path) {
380
+ result.push({
381
+ x: Math.round(pt.x * scale),
382
+ y: Math.round(pt.y * scale)
383
+ });
384
+ }
385
+ return result;
386
+ }
387
+
388
+ export function scalePaths(paths: Paths64, scale: number): Paths64 {
389
+ if (InternalClipper.isAlmostZero(scale - 1)) return paths;
390
+ const result: Paths64 = [];
391
+ for (const path of paths) {
392
+ result.push(scalePath(path, scale));
393
+ }
394
+ return result;
395
+ }
396
+
397
+ export function scalePathD(path: PathD, scale: number): PathD {
398
+ if (InternalClipper.isAlmostZero(scale - 1)) return path;
399
+ const result: PathD = [];
400
+ for (const pt of path) {
401
+ result.push(PointDUtils.scale(pt, scale));
402
+ }
403
+ return result;
404
+ }
405
+
406
+ export function scalePathsD(paths: PathsD, scale: number): PathsD {
407
+ if (InternalClipper.isAlmostZero(scale - 1)) return paths;
408
+ const result: PathsD = [];
409
+ for (const path of paths) {
410
+ result.push(scalePathD(path, scale));
411
+ }
412
+ return result;
413
+ }
414
+
415
+ // Unlike ScalePath, both ScalePath64 & ScalePathD also involve type conversion
416
+ export function scalePath64(path: PathD, scale: number): Path64 {
417
+ const result: Path64 = [];
418
+ for (const pt of path) {
419
+ result.push({
420
+ x: Math.round(pt.x * scale),
421
+ y: Math.round(pt.y * scale)
422
+ });
423
+ }
424
+ return result;
425
+ }
426
+
427
+ export function scalePaths64(paths: PathsD, scale: number): Paths64 {
428
+ const result: Paths64 = [];
429
+ for (const path of paths) {
430
+ result.push(scalePath64(path, scale));
431
+ }
432
+ return result;
433
+ }
434
+
435
+ export function scalePathDFromInt(path: Path64, scale: number): PathD {
436
+ const result: PathD = [];
437
+ for (const pt of path) {
438
+ result.push({
439
+ x: pt.x * scale,
440
+ y: pt.y * scale
441
+ });
442
+ }
443
+ return result;
444
+ }
445
+
446
+ export function scalePathsDFromInt(paths: Paths64, scale: number): PathsD {
447
+ const result: PathsD = [];
448
+ for (const path of paths) {
449
+ result.push(scalePathDFromInt(path, scale));
450
+ }
451
+ return result;
452
+ }
453
+
454
+ // The static functions Path64 and PathD convert path types without scaling
455
+ export function path64FromD(path: PathD): Path64 {
456
+ const result: Path64 = [];
457
+ for (const pt of path) {
458
+ result.push(Point64Utils.fromPointD(pt));
459
+ }
460
+ return result;
461
+ }
462
+
463
+ export function paths64FromD(paths: PathsD): Paths64 {
464
+ const result: Paths64 = [];
465
+ for (const path of paths) {
466
+ result.push(path64FromD(path));
467
+ }
468
+ return result;
469
+ }
470
+
471
+ export function pathsD(paths: Paths64): PathsD {
472
+ const result: PathsD = [];
473
+ for (const path of paths) {
474
+ result.push(pathD(path));
475
+ }
476
+ return result;
477
+ }
478
+
479
+ export function pathD(path: Path64): PathD {
480
+ const result: PathD = [];
481
+ for (const pt of path) {
482
+ result.push(PointDUtils.fromPoint64(pt));
483
+ }
484
+ return result;
485
+ }
486
+
487
+ export function translatePath(path: Path64, dx: number, dy: number): Path64 {
488
+ const result: Path64 = [];
489
+ for (const pt of path) {
490
+ result.push({ x: pt.x + dx, y: pt.y + dy });
491
+ }
492
+ return result;
493
+ }
494
+
495
+ export function translatePaths(paths: Paths64, dx: number, dy: number): Paths64 {
496
+ const result: Paths64 = [];
497
+ for (const path of paths) {
498
+ result.push(offsetPath(path, dx, dy));
499
+ }
500
+ return result;
501
+ }
502
+
503
+ export function translatePathD(path: PathD, dx: number, dy: number): PathD {
504
+ const result: PathD = [];
505
+ for (const pt of path) {
506
+ result.push({ x: pt.x + dx, y: pt.y + dy });
507
+ }
508
+ return result;
509
+ }
510
+
511
+ export function translatePathsD(paths: PathsD, dx: number, dy: number): PathsD {
512
+ const result: PathsD = [];
513
+ for (const path of paths) {
514
+ result.push(translatePathD(path, dx, dy));
515
+ }
516
+ return result;
517
+ }
518
+
519
+ export function reversePath(path: Path64): Path64 {
520
+ return [...path].reverse();
521
+ }
522
+
523
+ export function reversePathD(path: PathD): PathD {
524
+ return [...path].reverse();
525
+ }
526
+
527
+ export function reversePaths(paths: Paths64): Paths64 {
528
+ const result: Paths64 = [];
529
+ for (const path of paths) {
530
+ result.push(reversePath(path));
531
+ }
532
+ return result;
533
+ }
534
+
535
+ export function reversePathsD(paths: PathsD): PathsD {
536
+ const result: PathsD = [];
537
+ for (const path of paths) {
538
+ result.push(reversePathD(path));
539
+ }
540
+ return result;
541
+ }
542
+
543
+ export function getBounds(path: Path64): Rect64 {
544
+ return InternalClipper.getBounds(path);
545
+ }
546
+
547
+ export function getBoundsPaths(paths: Paths64): Rect64 {
548
+ const result = Rect64Utils.createInvalid();
549
+ for (const path of paths) {
550
+ for (const pt of path) {
551
+ if (pt.x < result.left) result.left = pt.x;
552
+ if (pt.x > result.right) result.right = pt.x;
553
+ if (pt.y < result.top) result.top = pt.y;
554
+ if (pt.y > result.bottom) result.bottom = pt.y;
555
+ }
556
+ }
557
+ return result.left === Number.MAX_SAFE_INTEGER ? { left: 0, top: 0, right: 0, bottom: 0 } : result;
558
+ }
559
+
560
+ export function getBoundsD(path: PathD): RectD {
561
+ const result = RectDUtils.createInvalid();
562
+ for (const pt of path) {
563
+ if (pt.x < result.left) result.left = pt.x;
564
+ if (pt.x > result.right) result.right = pt.x;
565
+ if (pt.y < result.top) result.top = pt.y;
566
+ if (pt.y > result.bottom) result.bottom = pt.y;
567
+ }
568
+ return Math.abs(result.left - Number.MAX_VALUE) < InternalClipper.floatingPointTolerance ?
569
+ { left: 0, top: 0, right: 0, bottom: 0 } : result;
570
+ }
571
+
572
+ export function getBoundsPathsD(paths: PathsD): RectD {
573
+ const result = RectDUtils.createInvalid();
574
+ for (const path of paths) {
575
+ for (const pt of path) {
576
+ if (pt.x < result.left) result.left = pt.x;
577
+ if (pt.x > result.right) result.right = pt.x;
578
+ if (pt.y < result.top) result.top = pt.y;
579
+ if (pt.y > result.bottom) result.bottom = pt.y;
580
+ }
581
+ }
582
+ return Math.abs(result.left - Number.MAX_VALUE) < InternalClipper.floatingPointTolerance ?
583
+ { left: 0, top: 0, right: 0, bottom: 0 } : result;
584
+ }
585
+
586
+ export function makePath(arr: number[]): Path64 {
587
+ const len = Math.floor(arr.length / 2);
588
+ const p: Path64 = [];
589
+ for (let i = 0; i < len; i++) {
590
+ p.push({ x: arr[i * 2], y: arr[i * 2 + 1] });
591
+ }
592
+ return p;
593
+ }
594
+
595
+ export function makePathD(arr: number[]): PathD {
596
+ const len = Math.floor(arr.length / 2);
597
+ const p: PathD = [];
598
+ for (let i = 0; i < len; i++) {
599
+ p.push({ x: arr[i * 2], y: arr[i * 2 + 1] });
600
+ }
601
+ return p;
602
+ }
603
+
604
+ export function sqr(val: number): number {
605
+ return val * val;
606
+ }
607
+
608
+ export function distanceSqr(pt1: Point64, pt2: Point64): number {
609
+ return sqr(pt1.x - pt2.x) + sqr(pt1.y - pt2.y);
610
+ }
611
+
612
+ export function midPoint(pt1: Point64, pt2: Point64): Point64 {
613
+ return { x: Math.round((pt1.x + pt2.x) / 2), y: Math.round((pt1.y + pt2.y) / 2) };
614
+ }
615
+
616
+ export function midPointD(pt1: PointD, pt2: PointD): PointD {
617
+ return { x: (pt1.x + pt2.x) / 2, y: (pt1.y + pt2.y) / 2 };
618
+ }
619
+
620
+ export function inflateRect(rec: Rect64, dx: number, dy: number): void {
621
+ rec.left -= dx;
622
+ rec.right += dx;
623
+ rec.top -= dy;
624
+ rec.bottom += dy;
625
+ }
626
+
627
+ export function inflateRectD(rec: RectD, dx: number, dy: number): void {
628
+ rec.left -= dx;
629
+ rec.right += dx;
630
+ rec.top -= dy;
631
+ rec.bottom += dy;
632
+ }
633
+
634
+ export function pointsNearEqual(pt1: PointD, pt2: PointD, distanceSqrd: number): boolean {
635
+ return sqr(pt1.x - pt2.x) + sqr(pt1.y - pt2.y) < distanceSqrd;
636
+ }
637
+
638
+ export function stripNearDuplicates(path: PathD, minEdgeLenSqrd: number, isClosedPath: boolean): PathD {
639
+ const cnt = path.length;
640
+ const result: PathD = [];
641
+ if (cnt === 0) return result;
642
+
643
+ let lastPt = path[0];
644
+ result.push(lastPt);
645
+ for (let i = 1; i < cnt; i++) {
646
+ if (!pointsNearEqual(lastPt, path[i], minEdgeLenSqrd)) {
647
+ lastPt = path[i];
648
+ result.push(lastPt);
649
+ }
650
+ }
651
+
652
+ if (isClosedPath && pointsNearEqual(lastPt, result[0], minEdgeLenSqrd)) {
653
+ result.pop();
654
+ }
655
+
656
+ return result;
657
+ }
658
+
659
+ export function stripDuplicates(path: Path64, isClosedPath: boolean): Path64 {
660
+ const cnt = path.length;
661
+ const result: Path64 = [];
662
+ if (cnt === 0) return result;
663
+
664
+ let lastPt = path[0];
665
+ result.push(lastPt);
666
+ for (let i = 1; i < cnt; i++) {
667
+ if (!Point64Utils.equals(lastPt, path[i])) {
668
+ lastPt = path[i];
669
+ result.push(lastPt);
670
+ }
671
+ }
672
+ if (isClosedPath && Point64Utils.equals(lastPt, result[0])) {
673
+ result.pop();
674
+ }
675
+ return result;
676
+ }
677
+
678
+ function addPolyNodeToPaths(polyPath: PolyTree64, paths: Paths64): void {
679
+ if (polyPath.poly && polyPath.poly.length > 0) {
680
+ paths.push(polyPath.poly);
681
+ }
682
+ for (let i = 0; i < polyPath.count; i++) {
683
+ addPolyNodeToPaths(polyPath.child(i), paths);
684
+ }
685
+ }
686
+
687
+ export function polyTreeToPaths64(polyTree: PolyTree64): Paths64 {
688
+ const result: Paths64 = [];
689
+ for (let i = 0; i < polyTree.count; i++) {
690
+ addPolyNodeToPaths(polyTree.child(i), result);
691
+ }
692
+ return result;
693
+ }
694
+
695
+ export function addPolyNodeToPathsD(polyPath: PolyPathD, paths: PathsD): void {
696
+ if (polyPath.poly && polyPath.poly.length > 0) {
697
+ paths.push(polyPath.poly);
698
+ }
699
+ for (let i = 0; i < polyPath.count; i++) {
700
+ addPolyNodeToPathsD(polyPath.child(i), paths);
701
+ }
702
+ }
703
+
704
+ export function polyTreeToPathsD(polyTree: PolyTreeD): PathsD {
705
+ const result: PathsD = [];
706
+ for (let i = 0; i < polyTree.count; i++) {
707
+ addPolyNodeToPathsD(polyTree.child(i), result);
708
+ }
709
+ return result;
710
+ }
711
+
712
+ export function perpendicDistFromLineSqrd(pt: PointD, line1: PointD, line2: PointD): number {
713
+ const a = pt.x - line1.x;
714
+ const b = pt.y - line1.y;
715
+ const c = line2.x - line1.x;
716
+ const d = line2.y - line1.y;
717
+ if (c === 0 && d === 0) return 0;
718
+ return sqr(a * d - c * b) / (c * c + d * d);
719
+ }
720
+
721
+ export function perpendicDistFromLineSqrd64(pt: Point64, line1: Point64, line2: Point64): number {
722
+ const a = pt.x - line1.x;
723
+ const b = pt.y - line1.y;
724
+ const c = line2.x - line1.x;
725
+ const d = line2.y - line1.y;
726
+ if (c === 0 && d === 0) return 0;
727
+ return sqr(a * d - c * b) / (c * c + d * d);
728
+ }
729
+
730
+ function rdp(path: Path64, begin: number, end: number, epsSqrd: number, flags: boolean[]): void {
731
+ while (true) {
732
+ let idx = 0;
733
+ let maxD = 0;
734
+ while (end > begin && Point64Utils.equals(path[begin], path[end])) flags[end--] = false;
735
+ for (let i = begin + 1; i < end; ++i) {
736
+ // PerpendicDistFromLineSqrd - avoids expensive Sqrt()
737
+ const d = perpendicDistFromLineSqrd64(path[i], path[begin], path[end]);
738
+ if (d <= maxD) continue;
739
+ maxD = d;
740
+ idx = i;
741
+ }
742
+
743
+ if (maxD <= epsSqrd) return;
744
+ flags[idx] = true;
745
+ if (idx > begin + 1) rdp(path, begin, idx, epsSqrd, flags);
746
+ if (idx < end - 1) {
747
+ begin = idx;
748
+ continue;
749
+ }
750
+ break;
751
+ }
752
+ }
753
+
754
+ export function ramerDouglasPeucker(path: Path64, epsilon: number): Path64 {
755
+ const len = path.length;
756
+ if (len < 5) return path;
757
+ const flags = new Array(len).fill(false);
758
+ flags[0] = true;
759
+ flags[len - 1] = true;
760
+ rdp(path, 0, len - 1, sqr(epsilon), flags);
761
+ const result: Path64 = [];
762
+ for (let i = 0; i < len; ++i) {
763
+ if (flags[i]) result.push(path[i]);
764
+ }
765
+ return result;
766
+ }
767
+
768
+ export function ramerDouglasPeuckerPaths(paths: Paths64, epsilon: number): Paths64 {
769
+ const result: Paths64 = [];
770
+ for (const path of paths) {
771
+ result.push(ramerDouglasPeucker(path, epsilon));
772
+ }
773
+ return result;
774
+ }
775
+
776
+ function rdpD(path: PathD, begin: number, end: number, epsSqrd: number, flags: boolean[]): void {
777
+ while (true) {
778
+ let idx = 0;
779
+ let maxD = 0;
780
+ while (end > begin && PointDUtils.equals(path[begin], path[end])) flags[end--] = false;
781
+ for (let i = begin + 1; i < end; ++i) {
782
+ // PerpendicDistFromLineSqrd - avoids expensive Sqrt()
783
+ const d = perpendicDistFromLineSqrd(path[i], path[begin], path[end]);
784
+ if (d <= maxD) continue;
785
+ maxD = d;
786
+ idx = i;
787
+ }
788
+
789
+ if (maxD <= epsSqrd) return;
790
+ flags[idx] = true;
791
+ if (idx > begin + 1) rdpD(path, begin, idx, epsSqrd, flags);
792
+ if (idx < end - 1) {
793
+ begin = idx;
794
+ continue;
795
+ }
796
+ break;
797
+ }
798
+ }
799
+
800
+ export function ramerDouglasPeuckerD(path: PathD, epsilon: number): PathD {
801
+ const len = path.length;
802
+ if (len < 5) return path;
803
+ const flags = new Array(len).fill(false);
804
+ flags[0] = true;
805
+ flags[len - 1] = true;
806
+ rdpD(path, 0, len - 1, sqr(epsilon), flags);
807
+ const result: PathD = [];
808
+ for (let i = 0; i < len; ++i) {
809
+ if (flags[i]) result.push(path[i]);
810
+ }
811
+ return result;
812
+ }
813
+
814
+ export function ramerDouglasPeuckerPathsD(paths: PathsD, epsilon: number): PathsD {
815
+ const result: PathsD = [];
816
+ for (const path of paths) {
817
+ result.push(ramerDouglasPeuckerD(path, epsilon));
818
+ }
819
+ return result;
820
+ }
821
+
822
+ function getNext(current: number, high: number, flags: boolean[]): number {
823
+ ++current;
824
+ while (current <= high && flags[current]) ++current;
825
+ if (current <= high) return current;
826
+ current = 0;
827
+ while (flags[current]) ++current;
828
+ return current;
829
+ }
830
+
831
+ function getPrior(current: number, high: number, flags: boolean[]): number {
832
+ if (current === 0) current = high;
833
+ else --current;
834
+ while (current > 0 && flags[current]) --current;
835
+ if (!flags[current]) return current;
836
+ current = high;
837
+ while (flags[current]) --current;
838
+ return current;
839
+ }
840
+
841
+ export function simplifyPath(path: Path64, epsilon: number, isClosedPath: boolean = true): Path64 {
842
+ const len = path.length;
843
+ const high = len - 1;
844
+ const epsSqr = sqr(epsilon);
845
+ if (len < 4) return path;
846
+
847
+ const flags = new Array(len).fill(false);
848
+ const dsq = new Array(len).fill(0);
849
+ let curr = 0;
850
+
851
+ if (isClosedPath) {
852
+ dsq[0] = perpendicDistFromLineSqrd64(path[0], path[high], path[1]);
853
+ dsq[high] = perpendicDistFromLineSqrd64(path[high], path[0], path[high - 1]);
854
+ } else {
855
+ dsq[0] = Number.MAX_VALUE;
856
+ dsq[high] = Number.MAX_VALUE;
857
+ }
858
+
859
+ for (let i = 1; i < high; ++i) {
860
+ dsq[i] = perpendicDistFromLineSqrd64(path[i], path[i - 1], path[i + 1]);
861
+ }
862
+
863
+ while (true) {
864
+ if (dsq[curr] > epsSqr) {
865
+ const start = curr;
866
+ do {
867
+ curr = getNext(curr, high, flags);
868
+ } while (curr !== start && dsq[curr] > epsSqr);
869
+ if (curr === start) break;
870
+ }
871
+
872
+ const prev = getPrior(curr, high, flags);
873
+ const next = getNext(curr, high, flags);
874
+ if (next === prev) break;
875
+
876
+ let prior2: number;
877
+ if (dsq[next] < dsq[curr]) {
878
+ prior2 = prev;
879
+ const newPrev = curr;
880
+ curr = next;
881
+ const newNext = getNext(next, high, flags);
882
+ flags[curr] = true;
883
+ curr = newNext;
884
+ const nextNext = getNext(newNext, high, flags);
885
+ if (isClosedPath || ((curr !== high) && (curr !== 0))) {
886
+ dsq[curr] = perpendicDistFromLineSqrd64(path[curr], path[newPrev], path[nextNext]);
887
+ }
888
+ if (isClosedPath || ((newPrev !== 0) && (newPrev !== high))) {
889
+ dsq[newPrev] = perpendicDistFromLineSqrd64(path[newPrev], path[prior2], path[curr]);
890
+ }
891
+ } else {
892
+ prior2 = getPrior(prev, high, flags);
893
+ flags[curr] = true;
894
+ curr = next;
895
+ const nextNext = getNext(next, high, flags);
896
+ if (isClosedPath || ((curr !== high) && (curr !== 0))) {
897
+ dsq[curr] = perpendicDistFromLineSqrd64(path[curr], path[prev], path[nextNext]);
898
+ }
899
+ if (isClosedPath || ((prev !== 0) && (prev !== high))) {
900
+ dsq[prev] = perpendicDistFromLineSqrd64(path[prev], path[prior2], path[curr]);
901
+ }
902
+ }
903
+ }
904
+ const result: Path64 = [];
905
+ for (let i = 0; i < len; i++) {
906
+ if (!flags[i]) result.push(path[i]);
907
+ }
908
+ return result;
909
+ }
910
+
911
+ export function simplifyPaths(paths: Paths64, epsilon: number, isClosedPaths: boolean = true): Paths64 {
912
+ const result: Paths64 = [];
913
+ for (const path of paths) {
914
+ result.push(simplifyPath(path, epsilon, isClosedPaths));
915
+ }
916
+ return result;
917
+ }
918
+
919
+ export function simplifyPathD(path: PathD, epsilon: number, isClosedPath: boolean = true): PathD {
920
+ const len = path.length;
921
+ const high = len - 1;
922
+ const epsSqr = sqr(epsilon);
923
+ if (len < 4) return path;
924
+
925
+ const flags = new Array(len).fill(false);
926
+ const dsq = new Array(len).fill(0);
927
+ let curr = 0;
928
+
929
+ if (isClosedPath) {
930
+ dsq[0] = perpendicDistFromLineSqrd(path[0], path[high], path[1]);
931
+ dsq[high] = perpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
932
+ } else {
933
+ dsq[0] = Number.MAX_VALUE;
934
+ dsq[high] = Number.MAX_VALUE;
935
+ }
936
+
937
+ for (let i = 1; i < high; ++i) {
938
+ dsq[i] = perpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
939
+ }
940
+
941
+ while (true) {
942
+ if (dsq[curr] > epsSqr) {
943
+ const start = curr;
944
+ do {
945
+ curr = getNext(curr, high, flags);
946
+ } while (curr !== start && dsq[curr] > epsSqr);
947
+ if (curr === start) break;
948
+ }
949
+
950
+ const prev = getPrior(curr, high, flags);
951
+ const next = getNext(curr, high, flags);
952
+ if (next === prev) break;
953
+
954
+ let prior2: number;
955
+ if (dsq[next] < dsq[curr]) {
956
+ prior2 = prev;
957
+ const newPrev = curr;
958
+ curr = next;
959
+ const newNext = getNext(next, high, flags);
960
+ flags[curr] = true;
961
+ curr = newNext;
962
+ const nextNext = getNext(newNext, high, flags);
963
+ if (isClosedPath || ((curr !== high) && (curr !== 0))) {
964
+ dsq[curr] = perpendicDistFromLineSqrd(path[curr], path[newPrev], path[nextNext]);
965
+ }
966
+ if (isClosedPath || ((newPrev !== 0) && (newPrev !== high))) {
967
+ dsq[newPrev] = perpendicDistFromLineSqrd(path[newPrev], path[prior2], path[curr]);
968
+ }
969
+ } else {
970
+ prior2 = getPrior(prev, high, flags);
971
+ flags[curr] = true;
972
+ curr = next;
973
+ const nextNext = getNext(next, high, flags);
974
+ if (isClosedPath || ((curr !== high) && (curr !== 0))) {
975
+ dsq[curr] = perpendicDistFromLineSqrd(path[curr], path[prev], path[nextNext]);
976
+ }
977
+ if (isClosedPath || ((prev !== 0) && (prev !== high))) {
978
+ dsq[prev] = perpendicDistFromLineSqrd(path[prev], path[prior2], path[curr]);
979
+ }
980
+ }
981
+ }
982
+ const result: PathD = [];
983
+ for (let i = 0; i < len; i++) {
984
+ if (!flags[i]) result.push(path[i]);
985
+ }
986
+ return result;
987
+ }
988
+
989
+ export function simplifyPathsD(paths: PathsD, epsilon: number, isClosedPath: boolean = true): PathsD {
990
+ const result: PathsD = [];
991
+ for (const path of paths) {
992
+ result.push(simplifyPathD(path, epsilon, isClosedPath));
993
+ }
994
+ return result;
995
+ }
996
+
997
+ export function trimCollinear(path: Path64, isOpen: boolean = false): Path64 {
998
+ let len = path.length;
999
+ let i = 0;
1000
+ if (!isOpen) {
1001
+ while (i < len - 1 && InternalClipper.isCollinear(path[len - 1], path[i], path[i + 1])) i++;
1002
+ while (i < len - 1 && InternalClipper.isCollinear(path[len - 2], path[len - 1], path[i])) len--;
1003
+ }
1004
+
1005
+ if (len - i < 3) {
1006
+ if (!isOpen || len < 2 || Point64Utils.equals(path[0], path[1])) {
1007
+ return [];
1008
+ }
1009
+ return path;
1010
+ }
1011
+
1012
+ const result: Path64 = [];
1013
+ let last = path[i];
1014
+ result.push(last);
1015
+ for (i++; i < len - 1; i++) {
1016
+ if (InternalClipper.isCollinear(last, path[i], path[i + 1])) continue;
1017
+ last = path[i];
1018
+ result.push(last);
1019
+ }
1020
+
1021
+ if (isOpen) {
1022
+ result.push(path[len - 1]);
1023
+ } else if (!InternalClipper.isCollinear(last, path[len - 1], result[0])) {
1024
+ result.push(path[len - 1]);
1025
+ } else {
1026
+ while (result.length > 2 && InternalClipper.isCollinear(
1027
+ result[result.length - 1], result[result.length - 2], result[0])) {
1028
+ result.pop();
1029
+ }
1030
+ if (result.length < 3) {
1031
+ result.length = 0;
1032
+ }
1033
+ }
1034
+ return result;
1035
+ }
1036
+
1037
+ export function trimCollinearD(path: PathD, precision: number, isOpen: boolean = false): PathD {
1038
+ InternalClipper.checkPrecision(precision);
1039
+ const scale = Math.pow(10, precision);
1040
+ let p = scalePath64(path, scale);
1041
+ p = trimCollinear(p, isOpen);
1042
+ return scalePathDFromInt(p, 1 / scale);
1043
+ }
1044
+
1045
+ export function pointInPolygon(pt: Point64, polygon: Path64): PointInPolygonResult {
1046
+ return InternalClipper.pointInPolygon(pt, polygon);
1047
+ }
1048
+
1049
+ export function pointInPolygonD(pt: PointD, polygon: PathD, precision: number = 2): PointInPolygonResult {
1050
+ InternalClipper.checkPrecision(precision);
1051
+ const scale = Math.pow(10, precision);
1052
+ const p = Point64Utils.fromPointD(PointDUtils.scale(pt, scale));
1053
+ const pathScaled = scalePath64(polygon, scale);
1054
+ return InternalClipper.pointInPolygon(p, pathScaled);
1055
+ }
1056
+
1057
+ export function ellipse(center: Point64, radiusX: number, radiusY: number = 0, steps: number = 0): Path64 {
1058
+ if (radiusX <= 0) return [];
1059
+ if (radiusY <= 0) radiusY = radiusX;
1060
+ if (steps <= 2) {
1061
+ steps = Math.ceil(Math.PI * Math.sqrt((radiusX + radiusY) / 2));
1062
+ }
1063
+
1064
+ const si = Math.sin(2 * Math.PI / steps);
1065
+ const co = Math.cos(2 * Math.PI / steps);
1066
+ let dx = co;
1067
+ let dy = si;
1068
+ const result: Path64 = [{ x: Math.round(center.x + radiusX), y: center.y }];
1069
+
1070
+ for (let i = 1; i < steps; ++i) {
1071
+ result.push({
1072
+ x: Math.round(center.x + radiusX * dx),
1073
+ y: Math.round(center.y + radiusY * dy)
1074
+ });
1075
+ const x = dx * co - dy * si;
1076
+ dy = dy * co + dx * si;
1077
+ dx = x;
1078
+ }
1079
+ return result;
1080
+ }
1081
+
1082
+ export function ellipseD(center: PointD, radiusX: number, radiusY: number = 0, steps: number = 0): PathD {
1083
+ if (radiusX <= 0) return [];
1084
+ if (radiusY <= 0) radiusY = radiusX;
1085
+ if (steps <= 2) {
1086
+ steps = Math.ceil(Math.PI * Math.sqrt((radiusX + radiusY) / 2));
1087
+ }
1088
+
1089
+ const si = Math.sin(2 * Math.PI / steps);
1090
+ const co = Math.cos(2 * Math.PI / steps);
1091
+ let dx = co;
1092
+ let dy = si;
1093
+ const result: PathD = [{ x: center.x + radiusX, y: center.y }];
1094
+
1095
+ for (let i = 1; i < steps; ++i) {
1096
+ result.push({
1097
+ x: center.x + radiusX * dx,
1098
+ y: center.y + radiusY * dy
1099
+ });
1100
+ const x = dx * co - dy * si;
1101
+ dy = dy * co + dx * si;
1102
+ dx = x;
1103
+ }
1104
+ return result;
1105
+ }
1106
+ }