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/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();
|