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/Core.ts ADDED
@@ -0,0 +1,683 @@
1
+ /*******************************************************************************
2
+ * Author : Angus Johnson *
3
+ * Date : 12 October 2025 *
4
+ * Website : https://www.angusj.com *
5
+ * Copyright : Angus Johnson 2010-2025 *
6
+ * Purpose : Core structures and functions for the Clipper Library *
7
+ * License : https://www.boost.org/LICENSE_1_0.txt *
8
+ *******************************************************************************/
9
+
10
+ export interface Point64 {
11
+ x: number;
12
+ y: number;
13
+ }
14
+
15
+ export interface PointD {
16
+ x: number;
17
+ y: number;
18
+ }
19
+
20
+ export type Path64 = Point64[];
21
+ export type PathD = PointD[];
22
+ export type Paths64 = Path64[];
23
+ export type PathsD = PathD[];
24
+
25
+ export interface Rect64 {
26
+ left: number;
27
+ top: number;
28
+ right: number;
29
+ bottom: number;
30
+ }
31
+
32
+ export interface RectD {
33
+ left: number;
34
+ top: number;
35
+ right: number;
36
+ bottom: number;
37
+ }
38
+
39
+ // Note: all clipping operations except for Difference are commutative.
40
+ export enum ClipType {
41
+ NoClip = 0,
42
+ Intersection = 1,
43
+ Union = 2,
44
+ Difference = 3,
45
+ Xor = 4
46
+ }
47
+
48
+ export enum PathType {
49
+ Subject = 0,
50
+ Clip = 1
51
+ }
52
+
53
+ // By far the most widely used filling rules for polygons are EvenOdd
54
+ // and NonZero, sometimes called Alternate and Winding respectively.
55
+ // https://en.wikipedia.org/wiki/Nonzero-rule
56
+ export enum FillRule {
57
+ EvenOdd = 0,
58
+ NonZero = 1,
59
+ Positive = 2,
60
+ Negative = 3
61
+ }
62
+
63
+ // PointInPolygon
64
+ export enum PointInPolygonResult {
65
+ IsOn = 0,
66
+ IsInside = 1,
67
+ IsOutside = 2
68
+ }
69
+
70
+ export namespace InternalClipper {
71
+ export const MaxInt64 = 9223372036854775807n;
72
+ export const MaxCoord = Number(MaxInt64 / 4n);
73
+ export const max_coord = MaxCoord;
74
+ export const min_coord = -MaxCoord;
75
+ export const Invalid64 = Number(MaxInt64);
76
+
77
+ export const floatingPointTolerance = 1E-12;
78
+ export const defaultMinimumEdgeLength = 0.1;
79
+
80
+ export function crossProduct(pt1: Point64, pt2: Point64, pt3: Point64): number {
81
+ // typecast to avoid potential int overflow
82
+ return ((pt2.x - pt1.x) * (pt3.y - pt2.y) -
83
+ (pt2.y - pt1.y) * (pt3.x - pt2.x));
84
+ }
85
+
86
+ export function crossProductSign(pt1: Point64, pt2: Point64, pt3: Point64): number {
87
+ const a = pt2.x - pt1.x;
88
+ const b = pt3.y - pt2.y;
89
+ const c = pt2.y - pt1.y;
90
+ const d = pt3.x - pt2.x;
91
+
92
+ const ab = multiplyUInt64(Math.abs(a), Math.abs(b));
93
+ const cd = multiplyUInt64(Math.abs(c), Math.abs(d));
94
+ const signAB = triSign(a) * triSign(b);
95
+ const signCD = triSign(c) * triSign(d);
96
+
97
+ if (signAB === signCD) {
98
+ let result: number;
99
+ if (ab.hi64 === cd.hi64) {
100
+ if (ab.lo64 === cd.lo64) return 0;
101
+ result = (ab.lo64 > cd.lo64) ? 1 : -1;
102
+ } else {
103
+ result = (ab.hi64 > cd.hi64) ? 1 : -1;
104
+ }
105
+ return (signAB > 0) ? result : -result;
106
+ }
107
+ return (signAB > signCD) ? 1 : -1;
108
+ }
109
+
110
+ export function checkPrecision(precision: number): void {
111
+ if (precision < -8 || precision > 8) {
112
+ throw new Error("Error: Precision is out of range.");
113
+ }
114
+ }
115
+
116
+ export function isAlmostZero(value: number): boolean {
117
+ return Math.abs(value) <= floatingPointTolerance;
118
+ }
119
+
120
+ export function triSign(x: number): number {
121
+ return (x < 0) ? -1 : (x > 0) ? 1 : 0;
122
+ }
123
+
124
+ export interface UInt128Struct {
125
+ lo64: number;
126
+ hi64: number;
127
+ }
128
+
129
+ export function multiplyUInt64(a: number, b: number): UInt128Struct {
130
+ // Convert to BigInt for accurate 64-bit multiplication
131
+ const aBig = BigInt(a >>> 0); // Ensure unsigned
132
+ const bBig = BigInt(b >>> 0);
133
+
134
+ const x1 = (aBig & 0xFFFFFFFFn) * (bBig & 0xFFFFFFFFn);
135
+ const x2 = (aBig >> 32n) * (bBig & 0xFFFFFFFFn) + (x1 >> 32n);
136
+ const x3 = (aBig & 0xFFFFFFFFn) * (bBig >> 32n) + (x2 & 0xFFFFFFFFn);
137
+
138
+ const lobits = (x3 & 0xFFFFFFFFn) << 32n | (x1 & 0xFFFFFFFFn);
139
+ const hibits = (aBig >> 32n) * (bBig >> 32n) + (x2 >> 32n) + (x3 >> 32n);
140
+
141
+ return {
142
+ lo64: Number(lobits & 0xFFFFFFFFFFFFFFFFn),
143
+ hi64: Number(hibits & 0xFFFFFFFFFFFFFFFFn)
144
+ };
145
+ }
146
+
147
+ // returns true if (and only if) a * b == c * d
148
+ export function productsAreEqual(a: number, b: number, c: number, d: number): boolean {
149
+ // nb: unsigned values will be needed for CalcOverflowCarry()
150
+ const absA = Math.abs(a);
151
+ const absB = Math.abs(b);
152
+ const absC = Math.abs(c);
153
+ const absD = Math.abs(d);
154
+
155
+ // fast path for typical coordinates: 46341^2 < 2^31 (safe for JS number multiplication)
156
+ if (absA < 46341 && absB < 46341 && absC < 46341 && absD < 46341) {
157
+ return a * b === c * d;
158
+ }
159
+
160
+ const mulAb = multiplyUInt64(absA, absB);
161
+ const mulCd = multiplyUInt64(absC, absD);
162
+
163
+ // nb: it's important to differentiate 0 values here from other values
164
+ const signAb = triSign(a) * triSign(b);
165
+ const signCd = triSign(c) * triSign(d);
166
+
167
+ return mulAb.lo64 === mulCd.lo64 && mulAb.hi64 === mulCd.hi64 && signAb === signCd;
168
+ }
169
+
170
+ export function isCollinear(pt1: Point64, sharedPt: Point64, pt2: Point64): boolean {
171
+ const a = sharedPt.x - pt1.x;
172
+ const b = pt2.y - sharedPt.y;
173
+ const c = sharedPt.y - pt1.y;
174
+ const d = pt2.x - sharedPt.x;
175
+ // When checking for collinearity with very large coordinate values
176
+ // then ProductsAreEqual is more accurate than using CrossProduct.
177
+ return productsAreEqual(a, b, c, d);
178
+ }
179
+
180
+ export function dotProduct(pt1: Point64, pt2: Point64, pt3: Point64): number {
181
+ // typecast to avoid potential int overflow
182
+ return ((pt2.x - pt1.x) * (pt3.x - pt2.x) +
183
+ (pt2.y - pt1.y) * (pt3.y - pt2.y));
184
+ }
185
+
186
+ export function crossProductD(vec1: PointD, vec2: PointD): number {
187
+ return (vec1.y * vec2.x - vec2.y * vec1.x);
188
+ }
189
+
190
+ export function dotProductD(vec1: PointD, vec2: PointD): number {
191
+ return (vec1.x * vec2.x + vec1.y * vec2.y);
192
+ }
193
+
194
+ // Banker's rounding (round half to even) to match C# MidpointRounding.ToEven
195
+ export function roundToEven(value: number): number {
196
+ // Use the built-in behavior that's closer to C# MidpointRounding.ToEven
197
+ // JavaScript's Math.round actually implements "round half away from zero"
198
+ // but for most practical cases, the difference is minimal
199
+ const floor = Math.floor(value);
200
+ const diff = value - floor;
201
+
202
+ if (Math.abs(diff - 0.5) < 1e-10) {
203
+ // Exactly halfway - round to even
204
+ return floor % 2 === 0 ? floor : floor + 1;
205
+ }
206
+
207
+ return Math.round(value);
208
+ }
209
+
210
+ export function checkCastInt64(val: number): number {
211
+ if ((val >= max_coord) || (val <= min_coord)) return Invalid64;
212
+ return Math.round(val);
213
+ }
214
+
215
+ // GetLineIntersectPt - a 'true' result is non-parallel. The 'ip' will also
216
+ // be constrained to seg1. However, it's possible that 'ip' won't be inside
217
+ // seg2, even when 'ip' hasn't been constrained (ie 'ip' is inside seg1).
218
+ export function getLineIntersectPt(
219
+ ln1a: Point64, ln1b: Point64,
220
+ ln2a: Point64, ln2b: Point64
221
+ ): { intersects: boolean; point: Point64 } {
222
+ const dy1 = (ln1b.y - ln1a.y);
223
+ const dx1 = (ln1b.x - ln1a.x);
224
+ const dy2 = (ln2b.y - ln2a.y);
225
+ const dx2 = (ln2b.x - ln2a.x);
226
+ const det = dy1 * dx2 - dy2 * dx1;
227
+
228
+ if (det === 0.0) {
229
+ return { intersects: false, point: { x: 0, y: 0 } };
230
+ }
231
+
232
+ const t = ((ln1a.x - ln2a.x) * dy2 - (ln1a.y - ln2a.y) * dx2) / det;
233
+ let ip: Point64;
234
+
235
+ if (t <= 0.0) {
236
+ ip = { x: ln1a.x, y: ln1a.y }; // Create a copy to avoid mutating original
237
+ } else if (t >= 1.0) {
238
+ ip = { x: ln1b.x, y: ln1b.y }; // Create a copy to avoid mutating original
239
+ } else {
240
+ // avoid using constructor (and rounding too) as they affect performance
241
+ // Use Math.trunc to match C# (long) cast behavior which truncates towards zero
242
+ const rawX = ln1a.x + t * dx1;
243
+ const rawY = ln1a.y + t * dy1;
244
+ ip = {
245
+ x: Math.trunc(rawX),
246
+ y: Math.trunc(rawY)
247
+ };
248
+ }
249
+
250
+ return { intersects: true, point: ip };
251
+ }
252
+
253
+ export function getLineIntersectPtD(
254
+ ln1a: PointD, ln1b: PointD,
255
+ ln2a: PointD, ln2b: PointD
256
+ ): { success: boolean; ip: PointD } {
257
+ const dy1 = ln1b.y - ln1a.y;
258
+ const dx1 = ln1b.x - ln1a.x;
259
+ const dy2 = ln2b.y - ln2a.y;
260
+ const dx2 = ln2b.x - ln2a.x;
261
+ const det = dy1 * dx2 - dy2 * dx1;
262
+
263
+ if (det === 0.0) {
264
+ return { success: false, ip: { x: 0, y: 0 } };
265
+ }
266
+
267
+ const t = ((ln1a.x - ln2a.x) * dy2 - (ln1a.y - ln2a.y) * dx2) / det;
268
+ let ip: PointD;
269
+
270
+ if (t <= 0.0) {
271
+ ip = { ...ln1a };
272
+ } else if (t >= 1.0) {
273
+ ip = { ...ln1b };
274
+ } else {
275
+ ip = {
276
+ x: ln1a.x + t * dx1,
277
+ y: ln1a.y + t * dy1
278
+ };
279
+ }
280
+
281
+ return { success: true, ip };
282
+ }
283
+
284
+ export function segsIntersect(
285
+ seg1a: Point64, seg1b: Point64,
286
+ seg2a: Point64, seg2b: Point64,
287
+ inclusive: boolean = false
288
+ ): boolean {
289
+ if (!inclusive) {
290
+ // Match C# fast path - use cross product multiplication
291
+ // This avoids floating point equality checks (safer than === 0)
292
+ return (crossProduct(seg1a, seg2a, seg2b) *
293
+ crossProduct(seg1b, seg2a, seg2b) < 0) &&
294
+ (crossProduct(seg2a, seg1a, seg1b) *
295
+ crossProduct(seg2b, seg1a, seg1b) < 0);
296
+ }
297
+
298
+ // Inclusive case - match C# implementation
299
+ const res1 = crossProduct(seg1a, seg2a, seg2b);
300
+ const res2 = crossProduct(seg1b, seg2a, seg2b);
301
+ if (res1 * res2 > 0) return false;
302
+ const res3 = crossProduct(seg2a, seg1a, seg1b);
303
+ const res4 = crossProduct(seg2b, seg1a, seg1b);
304
+ if (res3 * res4 > 0) return false;
305
+ // ensure NOT collinear
306
+ return (res1 !== 0 || res2 !== 0 || res3 !== 0 || res4 !== 0);
307
+ }
308
+
309
+ export function getBounds(path: Path64): Rect64 {
310
+ if (path.length === 0) return { left: 0, top: 0, right: 0, bottom: 0 };
311
+
312
+ const result: Rect64 = {
313
+ left: Number.MAX_SAFE_INTEGER,
314
+ top: Number.MAX_SAFE_INTEGER,
315
+ right: Number.MIN_SAFE_INTEGER,
316
+ bottom: Number.MIN_SAFE_INTEGER
317
+ };
318
+
319
+ for (const pt of path) {
320
+ if (pt.x < result.left) result.left = pt.x;
321
+ if (pt.x > result.right) result.right = pt.x;
322
+ if (pt.y < result.top) result.top = pt.y;
323
+ if (pt.y > result.bottom) result.bottom = pt.y;
324
+ }
325
+
326
+ return result.left === Number.MAX_SAFE_INTEGER ?
327
+ { left: 0, top: 0, right: 0, bottom: 0 } : result;
328
+ }
329
+
330
+ export function getClosestPtOnSegment(offPt: Point64, seg1: Point64, seg2: Point64): Point64 {
331
+ if (seg1.x === seg2.x && seg1.y === seg2.y) return { x: seg1.x, y: seg1.y }; // Return copy, not reference
332
+
333
+ const dx = (seg2.x - seg1.x);
334
+ const dy = (seg2.y - seg1.y);
335
+ const q = ((offPt.x - seg1.x) * dx + (offPt.y - seg1.y) * dy) / ((dx * dx) + (dy * dy));
336
+ const qClamped = q < 0 ? 0 : (q > 1 ? 1 : q);
337
+
338
+ return {
339
+ // use Math.round to match the C# MidpointRounding.ToEven behavior
340
+ x: Math.round(seg1.x + qClamped * dx),
341
+ y: Math.round(seg1.y + qClamped * dy)
342
+ };
343
+ }
344
+
345
+ export function pointInPolygon(pt: Point64, polygon: Path64): PointInPolygonResult {
346
+ const len = polygon.length;
347
+ let start = 0;
348
+ if (len < 3) return PointInPolygonResult.IsOutside;
349
+
350
+ while (start < len && polygon[start].y === pt.y) start++;
351
+ if (start === len) return PointInPolygonResult.IsOutside;
352
+
353
+ let isAbove = polygon[start].y < pt.y;
354
+ const startingAbove = isAbove;
355
+ let val = 0;
356
+ let i = start + 1;
357
+ let end = len;
358
+
359
+ while (true) {
360
+ if (i === end) {
361
+ if (end === 0 || start === 0) break;
362
+ end = start;
363
+ i = 0;
364
+ }
365
+
366
+ if (isAbove) {
367
+ while (i < end && polygon[i].y < pt.y) i++;
368
+ } else {
369
+ while (i < end && polygon[i].y > pt.y) i++;
370
+ }
371
+
372
+ if (i === end) continue;
373
+
374
+ const curr = polygon[i];
375
+ const prev = i > 0 ? polygon[i - 1] : polygon[len - 1];
376
+
377
+ if (curr.y === pt.y) {
378
+ if (curr.x === pt.x || (curr.y === prev.y &&
379
+ ((pt.x < prev.x) !== (pt.x < curr.x)))) {
380
+ return PointInPolygonResult.IsOn;
381
+ }
382
+ i++;
383
+ if (i === start) break;
384
+ continue;
385
+ }
386
+
387
+ if (pt.x < curr.x && pt.x < prev.x) {
388
+ // we're only interested in edges crossing on the left
389
+ } else if (pt.x > prev.x && pt.x > curr.x) {
390
+ val = 1 - val; // toggle val
391
+ } else {
392
+ const cps = crossProductSign(prev, curr, pt);
393
+ if (cps === 0) return PointInPolygonResult.IsOn;
394
+ if ((cps < 0) === isAbove) val = 1 - val;
395
+ }
396
+ isAbove = !isAbove;
397
+ i++;
398
+ }
399
+
400
+ if (isAbove === startingAbove) {
401
+ return val === 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside;
402
+ }
403
+
404
+ if (i === len) i = 0;
405
+ const cps = i === 0 ?
406
+ crossProductSign(polygon[len - 1], polygon[0], pt) :
407
+ crossProductSign(polygon[i - 1], polygon[i], pt);
408
+ if (cps === 0) return PointInPolygonResult.IsOn;
409
+ if ((cps < 0) === isAbove) val = 1 - val;
410
+
411
+ return val === 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside;
412
+ }
413
+
414
+ export function path2ContainsPath1(path1: Path64, path2: Path64): boolean {
415
+ // we need to make some accommodation for rounding errors
416
+ // so we won't jump if the first vertex is found outside
417
+ let pip = PointInPolygonResult.IsOn;
418
+ for (const pt of path1) {
419
+ switch (pointInPolygon(pt, path2)) {
420
+ case PointInPolygonResult.IsOutside:
421
+ if (pip === PointInPolygonResult.IsOutside) return false;
422
+ pip = PointInPolygonResult.IsOutside;
423
+ break;
424
+ case PointInPolygonResult.IsInside:
425
+ if (pip === PointInPolygonResult.IsInside) return true;
426
+ pip = PointInPolygonResult.IsInside;
427
+ break;
428
+ default:
429
+ break;
430
+ }
431
+ }
432
+ // since path1's location is still equivocal, check its midpoint
433
+ const mp = getBounds(path1);
434
+ const midPt: Point64 = {
435
+ x: Math.round((mp.left + mp.right) / 2),
436
+ y: Math.round((mp.top + mp.bottom) / 2)
437
+ };
438
+ return pointInPolygon(midPt, path2) !== PointInPolygonResult.IsOutside;
439
+ }
440
+ }
441
+
442
+ // Point64 utility functions
443
+ export namespace Point64Utils {
444
+ export function create(x: number = 0, y: number = 0): Point64 {
445
+ return { x: Math.round(x), y: Math.round(y) };
446
+ }
447
+
448
+ export function fromPointD(pt: PointD): Point64 {
449
+ return { x: Math.round(pt.x), y: Math.round(pt.y) };
450
+ }
451
+
452
+ export function scale(pt: Point64, scale: number): Point64 {
453
+ return {
454
+ x: Math.round(pt.x * scale),
455
+ y: Math.round(pt.y * scale)
456
+ };
457
+ }
458
+
459
+ export function equals(a: Point64, b: Point64): boolean {
460
+ return a.x === b.x && a.y === b.y;
461
+ }
462
+
463
+ export function add(a: Point64, b: Point64): Point64 {
464
+ return { x: a.x + b.x, y: a.y + b.y };
465
+ }
466
+
467
+ export function subtract(a: Point64, b: Point64): Point64 {
468
+ return { x: a.x - b.x, y: a.y - b.y };
469
+ }
470
+
471
+ export function toString(pt: Point64): string {
472
+ return `${pt.x},${pt.y} `;
473
+ }
474
+ }
475
+
476
+ // PointD utility functions
477
+ export namespace PointDUtils {
478
+ export function create(x: number = 0, y: number = 0): PointD {
479
+ return { x, y };
480
+ }
481
+
482
+ export function fromPoint64(pt: Point64): PointD {
483
+ return { x: pt.x, y: pt.y };
484
+ }
485
+
486
+ export function scale(pt: PointD, scale: number): PointD {
487
+ return { x: pt.x * scale, y: pt.y * scale };
488
+ }
489
+
490
+ export function equals(a: PointD, b: PointD): boolean {
491
+ return InternalClipper.isAlmostZero(a.x - b.x) &&
492
+ InternalClipper.isAlmostZero(a.y - b.y);
493
+ }
494
+
495
+ export function negate(pt: PointD): void {
496
+ pt.x = -pt.x;
497
+ pt.y = -pt.y;
498
+ }
499
+
500
+ export function toString(pt: PointD, precision: number = 2): string {
501
+ return `${pt.x.toFixed(precision)},${pt.y.toFixed(precision)}`;
502
+ }
503
+ }
504
+
505
+ // Rect64 utility functions
506
+ export namespace Rect64Utils {
507
+ export function create(l: number = 0, t: number = 0, r: number = 0, b: number = 0): Rect64 {
508
+ return { left: l, top: t, right: r, bottom: b };
509
+ }
510
+
511
+ export function createInvalid(): Rect64 {
512
+ return {
513
+ left: Number.MAX_SAFE_INTEGER,
514
+ top: Number.MAX_SAFE_INTEGER,
515
+ right: Number.MIN_SAFE_INTEGER,
516
+ bottom: Number.MIN_SAFE_INTEGER
517
+ };
518
+ }
519
+
520
+ export function width(rect: Rect64): number {
521
+ return rect.right - rect.left;
522
+ }
523
+
524
+ export function height(rect: Rect64): number {
525
+ return rect.bottom - rect.top;
526
+ }
527
+
528
+ export function isEmpty(rect: Rect64): boolean {
529
+ return rect.bottom <= rect.top || rect.right <= rect.left;
530
+ }
531
+
532
+ export function isValid(rect: Rect64): boolean {
533
+ return rect.left < Number.MAX_SAFE_INTEGER;
534
+ }
535
+
536
+ export function midPoint(rect: Rect64): Point64 {
537
+ return {
538
+ x: Math.round((rect.left + rect.right) / 2),
539
+ y: Math.round((rect.top + rect.bottom) / 2)
540
+ };
541
+ }
542
+
543
+ export function contains(rect: Rect64, pt: Point64): boolean {
544
+ return pt.x > rect.left && pt.x < rect.right &&
545
+ pt.y > rect.top && pt.y < rect.bottom;
546
+ }
547
+
548
+ export function containsRect(rect: Rect64, rec: Rect64): boolean {
549
+ return rec.left >= rect.left && rec.right <= rect.right &&
550
+ rec.top >= rect.top && rec.bottom <= rect.bottom;
551
+ }
552
+
553
+ export function intersects(rect: Rect64, rec: Rect64): boolean {
554
+ return (Math.max(rect.left, rec.left) <= Math.min(rect.right, rec.right)) &&
555
+ (Math.max(rect.top, rec.top) <= Math.min(rect.bottom, rec.bottom));
556
+ }
557
+
558
+ export function asPath(rect: Rect64): Path64 {
559
+ return [
560
+ { x: rect.left, y: rect.top },
561
+ { x: rect.right, y: rect.top },
562
+ { x: rect.right, y: rect.bottom },
563
+ { x: rect.left, y: rect.bottom }
564
+ ];
565
+ }
566
+ }
567
+
568
+ // RectD utility functions
569
+ export namespace RectDUtils {
570
+ export function create(l: number = 0, t: number = 0, r: number = 0, b: number = 0): RectD {
571
+ return { left: l, top: t, right: r, bottom: b };
572
+ }
573
+
574
+ export function createInvalid(): RectD {
575
+ return {
576
+ left: Number.MAX_VALUE,
577
+ top: Number.MAX_VALUE,
578
+ right: -Number.MAX_VALUE,
579
+ bottom: -Number.MAX_VALUE
580
+ };
581
+ }
582
+
583
+ export function width(rect: RectD): number {
584
+ return rect.right - rect.left;
585
+ }
586
+
587
+ export function height(rect: RectD): number {
588
+ return rect.bottom - rect.top;
589
+ }
590
+
591
+ export function isEmpty(rect: RectD): boolean {
592
+ return rect.bottom <= rect.top || rect.right <= rect.left;
593
+ }
594
+
595
+ export function midPoint(rect: RectD): PointD {
596
+ return {
597
+ x: (rect.left + rect.right) / 2,
598
+ y: (rect.top + rect.bottom) / 2
599
+ };
600
+ }
601
+
602
+ export function contains(rect: RectD, pt: PointD): boolean {
603
+ return pt.x > rect.left && pt.x < rect.right &&
604
+ pt.y > rect.top && pt.y < rect.bottom;
605
+ }
606
+
607
+ export function containsRect(rect: RectD, rec: RectD): boolean {
608
+ return rec.left >= rect.left && rec.right <= rect.right &&
609
+ rec.top >= rect.top && rec.bottom <= rect.bottom;
610
+ }
611
+
612
+ export function intersects(rect: RectD, rec: RectD): boolean {
613
+ return (Math.max(rect.left, rec.left) < Math.min(rect.right, rec.right)) &&
614
+ (Math.max(rect.top, rec.top) < Math.min(rect.bottom, rec.bottom));
615
+ }
616
+
617
+ export function asPath(rect: RectD): PathD {
618
+ return [
619
+ { x: rect.left, y: rect.top },
620
+ { x: rect.right, y: rect.top },
621
+ { x: rect.right, y: rect.bottom },
622
+ { x: rect.left, y: rect.bottom }
623
+ ];
624
+ }
625
+ }
626
+
627
+ // Path utility functions
628
+ export namespace PathUtils {
629
+ export function toString64(path: Path64): string {
630
+ let result = "";
631
+ for (const pt of path) {
632
+ result += Point64Utils.toString(pt);
633
+ }
634
+ return result + '\n';
635
+ }
636
+
637
+ export function toStringD(path: PathD, precision: number = 2): string {
638
+ let result = "";
639
+ for (const pt of path) {
640
+ result += PointDUtils.toString(pt, precision) + ", ";
641
+ }
642
+ if (result !== "") result = result.slice(0, -2);
643
+ return result;
644
+ }
645
+
646
+ export function reverse64(path: Path64): Path64 {
647
+ return [...path].reverse();
648
+ }
649
+
650
+ export function reverseD(path: PathD): PathD {
651
+ return [...path].reverse();
652
+ }
653
+ }
654
+
655
+ export namespace PathsUtils {
656
+ export function toString64(paths: Paths64): string {
657
+ let result = "";
658
+ for (const path of paths) {
659
+ result += PathUtils.toString64(path);
660
+ }
661
+ return result;
662
+ }
663
+
664
+ export function toStringD(paths: PathsD, precision: number = 2): string {
665
+ let result = "";
666
+ for (const path of paths) {
667
+ result += PathUtils.toStringD(path, precision) + "\n";
668
+ }
669
+ return result;
670
+ }
671
+
672
+ export function reverse64(paths: Paths64): Paths64 {
673
+ return paths.map(path => PathUtils.reverse64(path));
674
+ }
675
+
676
+ export function reverseD(paths: PathsD): PathsD {
677
+ return paths.map(path => PathUtils.reverseD(path));
678
+ }
679
+ }
680
+
681
+ // Constants
682
+ export const InvalidRect64: Rect64 = Rect64Utils.createInvalid();
683
+ export const InvalidRectD: RectD = RectDUtils.createInvalid();