@emasoft/svg-matrix 1.0.30 → 1.0.31
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/bin/svg-matrix.js +310 -61
- package/bin/svglinter.cjs +102 -3
- package/bin/svgm.js +236 -27
- package/package.json +1 -1
- package/src/animation-optimization.js +137 -17
- package/src/animation-references.js +123 -6
- package/src/arc-length.js +213 -4
- package/src/bezier-analysis.js +217 -21
- package/src/bezier-intersections.js +275 -12
- package/src/browser-verify.js +237 -4
- package/src/clip-path-resolver.js +168 -0
- package/src/convert-path-data.js +479 -28
- package/src/css-specificity.js +73 -10
- package/src/douglas-peucker.js +219 -2
- package/src/flatten-pipeline.js +284 -26
- package/src/geometry-to-path.js +250 -25
- package/src/gjk-collision.js +236 -33
- package/src/index.js +261 -3
- package/src/inkscape-support.js +86 -28
- package/src/logger.js +48 -3
- package/src/marker-resolver.js +278 -74
- package/src/mask-resolver.js +265 -66
- package/src/matrix.js +44 -5
- package/src/mesh-gradient.js +352 -102
- package/src/off-canvas-detection.js +382 -13
- package/src/path-analysis.js +192 -18
- package/src/path-data-plugins.js +309 -5
- package/src/path-optimization.js +129 -5
- package/src/path-simplification.js +188 -32
- package/src/pattern-resolver.js +454 -106
- package/src/polygon-clip.js +324 -1
- package/src/svg-boolean-ops.js +226 -9
- package/src/svg-collections.js +7 -5
- package/src/svg-flatten.js +386 -62
- package/src/svg-parser.js +179 -8
- package/src/svg-rendering-context.js +235 -6
- package/src/svg-toolbox.js +45 -8
- package/src/svg2-polyfills.js +40 -10
- package/src/transform-decomposition.js +258 -32
- package/src/transform-optimization.js +259 -13
- package/src/transforms2d.js +82 -9
- package/src/transforms3d.js +62 -10
- package/src/use-symbol-resolver.js +286 -42
- package/src/vector.js +64 -8
- package/src/verification.js +392 -1
package/src/gjk-collision.js
CHANGED
|
@@ -52,8 +52,11 @@ const MAX_ITERATIONS = 100;
|
|
|
52
52
|
* @param {number|string|Decimal} x - X coordinate
|
|
53
53
|
* @param {number|string|Decimal} y - Y coordinate
|
|
54
54
|
* @returns {{x: Decimal, y: Decimal}} Point object
|
|
55
|
+
* @throws {TypeError} If x or y is null or undefined
|
|
55
56
|
*/
|
|
56
57
|
export function point(x, y) {
|
|
58
|
+
if (x == null) throw new TypeError('point: x coordinate cannot be null or undefined');
|
|
59
|
+
if (y == null) throw new TypeError('point: y coordinate cannot be null or undefined');
|
|
57
60
|
return { x: D(x), y: D(y) };
|
|
58
61
|
}
|
|
59
62
|
|
|
@@ -62,8 +65,11 @@ export function point(x, y) {
|
|
|
62
65
|
* @param {{x: Decimal, y: Decimal}} a - First vector
|
|
63
66
|
* @param {{x: Decimal, y: Decimal}} b - Second vector
|
|
64
67
|
* @returns {{x: Decimal, y: Decimal}} Sum vector
|
|
68
|
+
* @throws {TypeError} If a or b is invalid or missing x/y properties
|
|
65
69
|
*/
|
|
66
70
|
export function vectorAdd(a, b) {
|
|
71
|
+
if (!a || a.x == null || a.y == null) throw new TypeError('vectorAdd: first vector must have x and y properties');
|
|
72
|
+
if (!b || b.x == null || b.y == null) throw new TypeError('vectorAdd: second vector must have x and y properties');
|
|
67
73
|
return { x: a.x.plus(b.x), y: a.y.plus(b.y) };
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -72,8 +78,11 @@ export function vectorAdd(a, b) {
|
|
|
72
78
|
* @param {{x: Decimal, y: Decimal}} a - First vector
|
|
73
79
|
* @param {{x: Decimal, y: Decimal}} b - Second vector
|
|
74
80
|
* @returns {{x: Decimal, y: Decimal}} Difference vector (a - b)
|
|
81
|
+
* @throws {TypeError} If a or b is invalid or missing x/y properties
|
|
75
82
|
*/
|
|
76
83
|
export function vectorSub(a, b) {
|
|
84
|
+
if (!a || a.x == null || a.y == null) throw new TypeError('vectorSub: first vector must have x and y properties');
|
|
85
|
+
if (!b || b.x == null || b.y == null) throw new TypeError('vectorSub: second vector must have x and y properties');
|
|
77
86
|
return { x: a.x.minus(b.x), y: a.y.minus(b.y) };
|
|
78
87
|
}
|
|
79
88
|
|
|
@@ -81,8 +90,10 @@ export function vectorSub(a, b) {
|
|
|
81
90
|
* Negate a vector.
|
|
82
91
|
* @param {{x: Decimal, y: Decimal}} v - Vector to negate
|
|
83
92
|
* @returns {{x: Decimal, y: Decimal}} Negated vector
|
|
93
|
+
* @throws {TypeError} If v is invalid or missing x/y properties
|
|
84
94
|
*/
|
|
85
95
|
export function vectorNeg(v) {
|
|
96
|
+
if (!v || v.x == null || v.y == null) throw new TypeError('vectorNeg: vector must have x and y properties');
|
|
86
97
|
return { x: v.x.neg(), y: v.y.neg() };
|
|
87
98
|
}
|
|
88
99
|
|
|
@@ -91,8 +102,11 @@ export function vectorNeg(v) {
|
|
|
91
102
|
* @param {{x: Decimal, y: Decimal}} v - Vector to scale
|
|
92
103
|
* @param {number|string|Decimal} s - Scale factor
|
|
93
104
|
* @returns {{x: Decimal, y: Decimal}} Scaled vector
|
|
105
|
+
* @throws {TypeError} If v is invalid, missing x/y properties, or s is null/undefined
|
|
94
106
|
*/
|
|
95
107
|
export function vectorScale(v, s) {
|
|
108
|
+
if (!v || v.x == null || v.y == null) throw new TypeError('vectorScale: vector must have x and y properties');
|
|
109
|
+
if (s == null) throw new TypeError('vectorScale: scale factor cannot be null or undefined');
|
|
96
110
|
const sd = D(s);
|
|
97
111
|
return { x: v.x.mul(sd), y: v.y.mul(sd) };
|
|
98
112
|
}
|
|
@@ -102,8 +116,11 @@ export function vectorScale(v, s) {
|
|
|
102
116
|
* @param {{x: Decimal, y: Decimal}} a - First vector
|
|
103
117
|
* @param {{x: Decimal, y: Decimal}} b - Second vector
|
|
104
118
|
* @returns {Decimal} Dot product
|
|
119
|
+
* @throws {TypeError} If a or b is invalid or missing x/y properties
|
|
105
120
|
*/
|
|
106
121
|
export function dot(a, b) {
|
|
122
|
+
if (!a || a.x == null || a.y == null) throw new TypeError('dot: first vector must have x and y properties');
|
|
123
|
+
if (!b || b.x == null || b.y == null) throw new TypeError('dot: second vector must have x and y properties');
|
|
107
124
|
return a.x.mul(b.x).plus(a.y.mul(b.y));
|
|
108
125
|
}
|
|
109
126
|
|
|
@@ -112,8 +129,11 @@ export function dot(a, b) {
|
|
|
112
129
|
* @param {{x: Decimal, y: Decimal}} a - First vector
|
|
113
130
|
* @param {{x: Decimal, y: Decimal}} b - Second vector
|
|
114
131
|
* @returns {Decimal} Cross product (a.x * b.y - a.y * b.x)
|
|
132
|
+
* @throws {TypeError} If a or b is invalid or missing x/y properties
|
|
115
133
|
*/
|
|
116
134
|
export function cross(a, b) {
|
|
135
|
+
if (!a || a.x == null || a.y == null) throw new TypeError('cross: first vector must have x and y properties');
|
|
136
|
+
if (!b || b.x == null || b.y == null) throw new TypeError('cross: second vector must have x and y properties');
|
|
117
137
|
return a.x.mul(b.y).minus(a.y.mul(b.x));
|
|
118
138
|
}
|
|
119
139
|
|
|
@@ -121,8 +141,10 @@ export function cross(a, b) {
|
|
|
121
141
|
* Squared magnitude of a vector.
|
|
122
142
|
* @param {{x: Decimal, y: Decimal}} v - Vector
|
|
123
143
|
* @returns {Decimal} Squared magnitude
|
|
144
|
+
* @throws {TypeError} If v is invalid or missing x/y properties
|
|
124
145
|
*/
|
|
125
146
|
export function magnitudeSquared(v) {
|
|
147
|
+
if (!v || v.x == null || v.y == null) throw new TypeError('magnitudeSquared: vector must have x and y properties');
|
|
126
148
|
return v.x.mul(v.x).plus(v.y.mul(v.y));
|
|
127
149
|
}
|
|
128
150
|
|
|
@@ -130,8 +152,10 @@ export function magnitudeSquared(v) {
|
|
|
130
152
|
* Magnitude of a vector.
|
|
131
153
|
* @param {{x: Decimal, y: Decimal}} v - Vector
|
|
132
154
|
* @returns {Decimal} Magnitude
|
|
155
|
+
* @throws {TypeError} If v is invalid or missing x/y properties
|
|
133
156
|
*/
|
|
134
157
|
export function magnitude(v) {
|
|
158
|
+
if (!v || v.x == null || v.y == null) throw new TypeError('magnitude: vector must have x and y properties');
|
|
135
159
|
return magnitudeSquared(v).sqrt();
|
|
136
160
|
}
|
|
137
161
|
|
|
@@ -139,8 +163,10 @@ export function magnitude(v) {
|
|
|
139
163
|
* Normalize a vector to unit length.
|
|
140
164
|
* @param {{x: Decimal, y: Decimal}} v - Vector to normalize
|
|
141
165
|
* @returns {{x: Decimal, y: Decimal}} Unit vector
|
|
166
|
+
* @throws {TypeError} If v is invalid or missing x/y properties
|
|
142
167
|
*/
|
|
143
168
|
export function normalize(v) {
|
|
169
|
+
if (!v || v.x == null || v.y == null) throw new TypeError('normalize: vector must have x and y properties');
|
|
144
170
|
const mag = magnitude(v);
|
|
145
171
|
if (mag.lessThan(EPSILON)) {
|
|
146
172
|
return { x: D(0), y: D(0) };
|
|
@@ -152,8 +178,10 @@ export function normalize(v) {
|
|
|
152
178
|
* Get perpendicular vector (90° counter-clockwise rotation).
|
|
153
179
|
* @param {{x: Decimal, y: Decimal}} v - Vector
|
|
154
180
|
* @returns {{x: Decimal, y: Decimal}} Perpendicular vector
|
|
181
|
+
* @throws {TypeError} If v is invalid or missing x/y properties
|
|
155
182
|
*/
|
|
156
183
|
export function perpendicular(v) {
|
|
184
|
+
if (!v || v.x == null || v.y == null) throw new TypeError('perpendicular: vector must have x and y properties');
|
|
157
185
|
return { x: v.y.neg(), y: v.x };
|
|
158
186
|
}
|
|
159
187
|
|
|
@@ -164,8 +192,12 @@ export function perpendicular(v) {
|
|
|
164
192
|
* @param {{x: Decimal, y: Decimal}} b - Second vector
|
|
165
193
|
* @param {{x: Decimal, y: Decimal}} c - Third vector
|
|
166
194
|
* @returns {{x: Decimal, y: Decimal}} Triple product result
|
|
195
|
+
* @throws {TypeError} If any vector is invalid or missing x/y properties
|
|
167
196
|
*/
|
|
168
197
|
export function tripleProduct(a, b, c) {
|
|
198
|
+
if (!a || a.x == null || a.y == null) throw new TypeError('tripleProduct: first vector must have x and y properties');
|
|
199
|
+
if (!b || b.x == null || b.y == null) throw new TypeError('tripleProduct: second vector must have x and y properties');
|
|
200
|
+
if (!c || c.x == null || c.y == null) throw new TypeError('tripleProduct: third vector must have x and y properties');
|
|
169
201
|
// In 2D: (A × B) × C = B(A·C) - A(B·C)
|
|
170
202
|
const ac = dot(a, c);
|
|
171
203
|
const bc = dot(b, c);
|
|
@@ -185,16 +217,27 @@ export function tripleProduct(a, b, c) {
|
|
|
185
217
|
* @param {Array<{x: Decimal, y: Decimal}>} polygon - Convex polygon vertices
|
|
186
218
|
* @param {{x: Decimal, y: Decimal}} direction - Direction to search
|
|
187
219
|
* @returns {{x: Decimal, y: Decimal}} Farthest point
|
|
220
|
+
* @throws {TypeError} If polygon is not an array or direction is invalid
|
|
188
221
|
*/
|
|
189
222
|
export function supportPoint(polygon, direction) {
|
|
223
|
+
if (!Array.isArray(polygon)) throw new TypeError('supportPoint: polygon must be an array');
|
|
224
|
+
if (!direction || direction.x == null || direction.y == null) throw new TypeError('supportPoint: direction must have x and y properties');
|
|
190
225
|
if (polygon.length === 0) {
|
|
191
226
|
return point(0, 0);
|
|
192
227
|
}
|
|
193
228
|
|
|
229
|
+
// Validate first point
|
|
230
|
+
if (!polygon[0] || polygon[0].x == null || polygon[0].y == null) {
|
|
231
|
+
throw new TypeError('supportPoint: polygon[0] must have x and y properties');
|
|
232
|
+
}
|
|
233
|
+
|
|
194
234
|
let maxDot = dot(polygon[0], direction);
|
|
195
235
|
let maxPoint = polygon[0];
|
|
196
236
|
|
|
197
237
|
for (let i = 1; i < polygon.length; i++) {
|
|
238
|
+
if (!polygon[i] || polygon[i].x == null || polygon[i].y == null) {
|
|
239
|
+
throw new TypeError(`supportPoint: polygon[${i}] must have x and y properties`);
|
|
240
|
+
}
|
|
198
241
|
const d = dot(polygon[i], direction);
|
|
199
242
|
if (d.greaterThan(maxDot)) {
|
|
200
243
|
maxDot = d;
|
|
@@ -215,8 +258,12 @@ export function supportPoint(polygon, direction) {
|
|
|
215
258
|
* @param {Array<{x: Decimal, y: Decimal}>} polygonB - Second convex polygon
|
|
216
259
|
* @param {{x: Decimal, y: Decimal}} direction - Direction to search
|
|
217
260
|
* @returns {{x: Decimal, y: Decimal}} Support point on Minkowski difference
|
|
261
|
+
* @throws {TypeError} If polygons are not arrays or direction is invalid
|
|
218
262
|
*/
|
|
219
263
|
export function minkowskiSupport(polygonA, polygonB, direction) {
|
|
264
|
+
if (!Array.isArray(polygonA)) throw new TypeError('minkowskiSupport: polygonA must be an array');
|
|
265
|
+
if (!Array.isArray(polygonB)) throw new TypeError('minkowskiSupport: polygonB must be an array');
|
|
266
|
+
if (!direction || direction.x == null || direction.y == null) throw new TypeError('minkowskiSupport: direction must have x and y properties');
|
|
220
267
|
const pointA = supportPoint(polygonA, direction);
|
|
221
268
|
const pointB = supportPoint(polygonB, vectorNeg(direction));
|
|
222
269
|
return vectorSub(pointA, pointB);
|
|
@@ -236,8 +283,14 @@ export function minkowskiSupport(polygonA, polygonB, direction) {
|
|
|
236
283
|
* @param {Array<{x: Decimal, y: Decimal}>} simplex - Current simplex (modified in place)
|
|
237
284
|
* @param {{x: Decimal, y: Decimal}} direction - Current search direction (modified)
|
|
238
285
|
* @returns {{contains: boolean, newDirection: {x: Decimal, y: Decimal}}}
|
|
286
|
+
* @throws {TypeError} If simplex is not an array, has wrong length, or direction is invalid
|
|
239
287
|
*/
|
|
240
288
|
export function processLineSimplex(simplex, direction) {
|
|
289
|
+
if (!Array.isArray(simplex)) throw new TypeError('processLineSimplex: simplex must be an array');
|
|
290
|
+
if (simplex.length !== 2) throw new TypeError('processLineSimplex: simplex must have exactly 2 points');
|
|
291
|
+
if (!direction || direction.x == null || direction.y == null) throw new TypeError('processLineSimplex: direction must have x and y properties');
|
|
292
|
+
if (!simplex[0] || simplex[0].x == null || simplex[0].y == null) throw new TypeError('processLineSimplex: simplex[0] must have x and y properties');
|
|
293
|
+
if (!simplex[1] || simplex[1].x == null || simplex[1].y == null) throw new TypeError('processLineSimplex: simplex[1] must have x and y properties');
|
|
241
294
|
const A = simplex[0]; // Newest point
|
|
242
295
|
const B = simplex[1];
|
|
243
296
|
|
|
@@ -276,8 +329,15 @@ export function processLineSimplex(simplex, direction) {
|
|
|
276
329
|
* @param {Array<{x: Decimal, y: Decimal}>} simplex - Current simplex (modified in place)
|
|
277
330
|
* @param {{x: Decimal, y: Decimal}} direction - Current search direction
|
|
278
331
|
* @returns {{contains: boolean, newDirection: {x: Decimal, y: Decimal}, newSimplex: Array}}
|
|
332
|
+
* @throws {TypeError} If simplex is not an array, has wrong length, or direction is invalid
|
|
279
333
|
*/
|
|
280
334
|
export function processTriangleSimplex(simplex, direction) {
|
|
335
|
+
if (!Array.isArray(simplex)) throw new TypeError('processTriangleSimplex: simplex must be an array');
|
|
336
|
+
if (simplex.length !== 3) throw new TypeError('processTriangleSimplex: simplex must have exactly 3 points');
|
|
337
|
+
if (!direction || direction.x == null || direction.y == null) throw new TypeError('processTriangleSimplex: direction must have x and y properties');
|
|
338
|
+
if (!simplex[0] || simplex[0].x == null || simplex[0].y == null) throw new TypeError('processTriangleSimplex: simplex[0] must have x and y properties');
|
|
339
|
+
if (!simplex[1] || simplex[1].x == null || simplex[1].y == null) throw new TypeError('processTriangleSimplex: simplex[1] must have x and y properties');
|
|
340
|
+
if (!simplex[2] || simplex[2].x == null || simplex[2].y == null) throw new TypeError('processTriangleSimplex: simplex[2] must have x and y properties');
|
|
281
341
|
const A = simplex[0]; // Newest point
|
|
282
342
|
const B = simplex[1];
|
|
283
343
|
const C = simplex[2];
|
|
@@ -336,8 +396,11 @@ export function processTriangleSimplex(simplex, direction) {
|
|
|
336
396
|
* @param {Array<{x: Decimal, y: Decimal}>} polygonA - First convex polygon
|
|
337
397
|
* @param {Array<{x: Decimal, y: Decimal}>} polygonB - Second convex polygon
|
|
338
398
|
* @returns {{intersects: boolean, iterations: number, simplex: Array, verified: boolean}}
|
|
399
|
+
* @throws {TypeError} If polygons are not arrays
|
|
339
400
|
*/
|
|
340
401
|
export function gjkIntersects(polygonA, polygonB) {
|
|
402
|
+
if (!Array.isArray(polygonA)) throw new TypeError('gjkIntersects: polygonA must be an array');
|
|
403
|
+
if (!Array.isArray(polygonB)) throw new TypeError('gjkIntersects: polygonB must be an array');
|
|
341
404
|
// Handle empty polygons
|
|
342
405
|
if (polygonA.length === 0 || polygonB.length === 0) {
|
|
343
406
|
return { intersects: false, iterations: 0, simplex: [], verified: true };
|
|
@@ -345,6 +408,12 @@ export function gjkIntersects(polygonA, polygonB) {
|
|
|
345
408
|
|
|
346
409
|
// Handle single points
|
|
347
410
|
if (polygonA.length === 1 && polygonB.length === 1) {
|
|
411
|
+
if (!polygonA[0] || polygonA[0].x == null || polygonA[0].y == null) {
|
|
412
|
+
throw new TypeError('gjkIntersects: polygonA[0] must have x and y properties');
|
|
413
|
+
}
|
|
414
|
+
if (!polygonB[0] || polygonB[0].x == null || polygonB[0].y == null) {
|
|
415
|
+
throw new TypeError('gjkIntersects: polygonB[0] must have x and y properties');
|
|
416
|
+
}
|
|
348
417
|
const dist = magnitude(vectorSub(polygonA[0], polygonB[0]));
|
|
349
418
|
return {
|
|
350
419
|
intersects: dist.lessThan(EPSILON),
|
|
@@ -460,8 +529,10 @@ export function gjkIntersects(polygonA, polygonB) {
|
|
|
460
529
|
* Calculate centroid of a polygon.
|
|
461
530
|
* @param {Array<{x: Decimal, y: Decimal}>} polygon - Polygon vertices
|
|
462
531
|
* @returns {{x: Decimal, y: Decimal}} Centroid point
|
|
532
|
+
* @throws {TypeError} If polygon is not an array
|
|
463
533
|
*/
|
|
464
534
|
export function centroid(polygon) {
|
|
535
|
+
if (!Array.isArray(polygon)) throw new TypeError('centroid: polygon must be an array');
|
|
465
536
|
if (polygon.length === 0) {
|
|
466
537
|
return point(0, 0);
|
|
467
538
|
}
|
|
@@ -469,9 +540,12 @@ export function centroid(polygon) {
|
|
|
469
540
|
let sumX = D(0);
|
|
470
541
|
let sumY = D(0);
|
|
471
542
|
|
|
472
|
-
for (
|
|
473
|
-
|
|
474
|
-
|
|
543
|
+
for (let i = 0; i < polygon.length; i++) {
|
|
544
|
+
if (!polygon[i] || polygon[i].x == null || polygon[i].y == null) {
|
|
545
|
+
throw new TypeError(`centroid: polygon[${i}] must have x and y properties`);
|
|
546
|
+
}
|
|
547
|
+
sumX = sumX.plus(polygon[i].x);
|
|
548
|
+
sumY = sumY.plus(polygon[i].y);
|
|
475
549
|
}
|
|
476
550
|
|
|
477
551
|
const n = D(polygon.length);
|
|
@@ -487,8 +561,11 @@ export function centroid(polygon) {
|
|
|
487
561
|
* @param {{x: Decimal, y: Decimal}} pt - Point to test
|
|
488
562
|
* @param {Array<{x: Decimal, y: Decimal}>} polygon - Convex polygon
|
|
489
563
|
* @returns {boolean} True if point is inside (including boundary)
|
|
564
|
+
* @throws {TypeError} If pt is invalid or polygon is not an array
|
|
490
565
|
*/
|
|
491
566
|
export function pointInConvexPolygon(pt, polygon) {
|
|
567
|
+
if (!pt || pt.x == null || pt.y == null) throw new TypeError('pointInConvexPolygon: point must have x and y properties');
|
|
568
|
+
if (!Array.isArray(polygon)) throw new TypeError('pointInConvexPolygon: polygon must be an array');
|
|
492
569
|
if (polygon.length < 3) {
|
|
493
570
|
return false;
|
|
494
571
|
}
|
|
@@ -499,6 +576,13 @@ export function pointInConvexPolygon(pt, polygon) {
|
|
|
499
576
|
const p1 = polygon[i];
|
|
500
577
|
const p2 = polygon[(i + 1) % polygon.length];
|
|
501
578
|
|
|
579
|
+
if (!p1 || p1.x == null || p1.y == null) {
|
|
580
|
+
throw new TypeError(`pointInConvexPolygon: polygon[${i}] must have x and y properties`);
|
|
581
|
+
}
|
|
582
|
+
if (!p2 || p2.x == null || p2.y == null) {
|
|
583
|
+
throw new TypeError(`pointInConvexPolygon: polygon[${(i + 1) % polygon.length}] must have x and y properties`);
|
|
584
|
+
}
|
|
585
|
+
|
|
502
586
|
const edge = vectorSub(p2, p1);
|
|
503
587
|
const toPoint = vectorSub(pt, p1);
|
|
504
588
|
const crossVal = cross(edge, toPoint);
|
|
@@ -530,18 +614,27 @@ export function pointInConvexPolygon(pt, polygon) {
|
|
|
530
614
|
* @param {Array<{x: Decimal, y: Decimal}>} polygonA - First polygon
|
|
531
615
|
* @param {Array<{x: Decimal, y: Decimal}>} polygonB - Second polygon
|
|
532
616
|
* @returns {boolean} True if intersection is verified
|
|
617
|
+
* @throws {TypeError} If polygons are not arrays
|
|
533
618
|
*/
|
|
534
619
|
export function verifyIntersection(polygonA, polygonB) {
|
|
620
|
+
if (!Array.isArray(polygonA)) throw new TypeError('verifyIntersection: polygonA must be an array');
|
|
621
|
+
if (!Array.isArray(polygonB)) throw new TypeError('verifyIntersection: polygonB must be an array');
|
|
535
622
|
// Check if any vertex of A is inside B
|
|
536
|
-
for (
|
|
537
|
-
if (
|
|
623
|
+
for (let i = 0; i < polygonA.length; i++) {
|
|
624
|
+
if (!polygonA[i] || polygonA[i].x == null || polygonA[i].y == null) {
|
|
625
|
+
throw new TypeError(`verifyIntersection: polygonA[${i}] must have x and y properties`);
|
|
626
|
+
}
|
|
627
|
+
if (pointInConvexPolygon(polygonA[i], polygonB)) {
|
|
538
628
|
return true;
|
|
539
629
|
}
|
|
540
630
|
}
|
|
541
631
|
|
|
542
632
|
// Check if any vertex of B is inside A
|
|
543
|
-
for (
|
|
544
|
-
if (
|
|
633
|
+
for (let i = 0; i < polygonB.length; i++) {
|
|
634
|
+
if (!polygonB[i] || polygonB[i].x == null || polygonB[i].y == null) {
|
|
635
|
+
throw new TypeError(`verifyIntersection: polygonB[${i}] must have x and y properties`);
|
|
636
|
+
}
|
|
637
|
+
if (pointInConvexPolygon(polygonB[i], polygonA)) {
|
|
545
638
|
return true;
|
|
546
639
|
}
|
|
547
640
|
}
|
|
@@ -551,10 +644,24 @@ export function verifyIntersection(polygonA, polygonB) {
|
|
|
551
644
|
const a1 = polygonA[i];
|
|
552
645
|
const a2 = polygonA[(i + 1) % polygonA.length];
|
|
553
646
|
|
|
647
|
+
if (!a1 || a1.x == null || a1.y == null) {
|
|
648
|
+
throw new TypeError(`verifyIntersection: polygonA[${i}] must have x and y properties`);
|
|
649
|
+
}
|
|
650
|
+
if (!a2 || a2.x == null || a2.y == null) {
|
|
651
|
+
throw new TypeError(`verifyIntersection: polygonA[${(i + 1) % polygonA.length}] must have x and y properties`);
|
|
652
|
+
}
|
|
653
|
+
|
|
554
654
|
for (let j = 0; j < polygonB.length; j++) {
|
|
555
655
|
const b1 = polygonB[j];
|
|
556
656
|
const b2 = polygonB[(j + 1) % polygonB.length];
|
|
557
657
|
|
|
658
|
+
if (!b1 || b1.x == null || b1.y == null) {
|
|
659
|
+
throw new TypeError(`verifyIntersection: polygonB[${j}] must have x and y properties`);
|
|
660
|
+
}
|
|
661
|
+
if (!b2 || b2.x == null || b2.y == null) {
|
|
662
|
+
throw new TypeError(`verifyIntersection: polygonB[${(j + 1) % polygonB.length}] must have x and y properties`);
|
|
663
|
+
}
|
|
664
|
+
|
|
558
665
|
if (segmentsIntersect(a1, a2, b1, b2)) {
|
|
559
666
|
return true;
|
|
560
667
|
}
|
|
@@ -572,8 +679,14 @@ export function verifyIntersection(polygonA, polygonB) {
|
|
|
572
679
|
* @param {{x: Decimal, y: Decimal}} b1 - Second segment start
|
|
573
680
|
* @param {{x: Decimal, y: Decimal}} b2 - Second segment end
|
|
574
681
|
* @returns {boolean} True if segments intersect
|
|
682
|
+
* @throws {TypeError} If any point is invalid or missing x/y properties
|
|
575
683
|
*/
|
|
576
684
|
export function segmentsIntersect(a1, a2, b1, b2) {
|
|
685
|
+
if (!a1 || a1.x == null || a1.y == null) throw new TypeError('segmentsIntersect: a1 must have x and y properties');
|
|
686
|
+
if (!a2 || a2.x == null || a2.y == null) throw new TypeError('segmentsIntersect: a2 must have x and y properties');
|
|
687
|
+
if (!b1 || b1.x == null || b1.y == null) throw new TypeError('segmentsIntersect: b1 must have x and y properties');
|
|
688
|
+
if (!b2 || b2.x == null || b2.y == null) throw new TypeError('segmentsIntersect: b2 must have x and y properties');
|
|
689
|
+
|
|
577
690
|
const d1 = vectorSub(a2, a1);
|
|
578
691
|
const d2 = vectorSub(b2, b1);
|
|
579
692
|
|
|
@@ -585,9 +698,22 @@ export function segmentsIntersect(a1, a2, b1, b2) {
|
|
|
585
698
|
const d3 = vectorSub(b1, a1);
|
|
586
699
|
if (cross(d1, d3).abs().lessThan(EPSILON)) {
|
|
587
700
|
// Collinear - check overlap
|
|
588
|
-
const
|
|
701
|
+
const d1LengthSq = dot(d1, d1);
|
|
702
|
+
// Handle degenerate first segment (a1 == a2)
|
|
703
|
+
if (d1LengthSq.lessThan(EPSILON)) {
|
|
704
|
+
// First segment is a point - check if it lies on second segment
|
|
705
|
+
const d2LengthSq = dot(d2, d2);
|
|
706
|
+
if (d2LengthSq.lessThan(EPSILON)) {
|
|
707
|
+
// Both segments are points - check if same point
|
|
708
|
+
return magnitude(d3).lessThan(EPSILON);
|
|
709
|
+
}
|
|
710
|
+
// Check if point a1 is on segment b1-b2
|
|
711
|
+
const t = dot(d3, d2).div(d2LengthSq);
|
|
712
|
+
return t.greaterThanOrEqualTo(0) && t.lessThanOrEqualTo(1);
|
|
713
|
+
}
|
|
714
|
+
const t0 = dot(d3, d1).div(d1LengthSq);
|
|
589
715
|
const d4 = vectorSub(b2, a1);
|
|
590
|
-
const t1 = dot(d4, d1).div(
|
|
716
|
+
const t1 = dot(d4, d1).div(d1LengthSq);
|
|
591
717
|
|
|
592
718
|
const tMin = Decimal.min(t0, t1);
|
|
593
719
|
const tMax = Decimal.max(t0, t1);
|
|
@@ -620,8 +746,14 @@ export function segmentsIntersect(a1, a2, b1, b2) {
|
|
|
620
746
|
* @param {Array<{x: Decimal, y: Decimal}>} polygonA - First convex polygon
|
|
621
747
|
* @param {Array<{x: Decimal, y: Decimal}>} polygonB - Second convex polygon
|
|
622
748
|
* @returns {{distance: Decimal, closestA: {x: Decimal, y: Decimal}, closestB: {x: Decimal, y: Decimal}, verified: boolean}}
|
|
749
|
+
* @throws {TypeError} If polygons are not arrays or are empty
|
|
623
750
|
*/
|
|
624
751
|
export function gjkDistance(polygonA, polygonB) {
|
|
752
|
+
if (!Array.isArray(polygonA)) throw new TypeError('gjkDistance: polygonA must be an array');
|
|
753
|
+
if (!Array.isArray(polygonB)) throw new TypeError('gjkDistance: polygonB must be an array');
|
|
754
|
+
if (polygonA.length === 0) throw new TypeError('gjkDistance: polygonA cannot be empty');
|
|
755
|
+
if (polygonB.length === 0) throw new TypeError('gjkDistance: polygonB cannot be empty');
|
|
756
|
+
|
|
625
757
|
// First check if they intersect
|
|
626
758
|
const intersection = gjkIntersects(polygonA, polygonB);
|
|
627
759
|
|
|
@@ -640,47 +772,79 @@ export function gjkDistance(polygonA, polygonB) {
|
|
|
640
772
|
// For non-intersecting shapes, find the closest points by
|
|
641
773
|
// examining all vertex-edge pairs
|
|
642
774
|
|
|
775
|
+
// Validate first points before using them as initial values
|
|
776
|
+
if (!polygonA[0] || polygonA[0].x == null || polygonA[0].y == null) {
|
|
777
|
+
throw new TypeError('gjkDistance: polygonA[0] must have x and y properties');
|
|
778
|
+
}
|
|
779
|
+
if (!polygonB[0] || polygonB[0].x == null || polygonB[0].y == null) {
|
|
780
|
+
throw new TypeError('gjkDistance: polygonB[0] must have x and y properties');
|
|
781
|
+
}
|
|
782
|
+
|
|
643
783
|
let minDist = D(Infinity);
|
|
644
784
|
let closestA = polygonA[0];
|
|
645
785
|
let closestB = polygonB[0];
|
|
646
786
|
|
|
647
787
|
// Check all pairs of vertices
|
|
648
|
-
for (
|
|
649
|
-
|
|
650
|
-
|
|
788
|
+
for (let i = 0; i < polygonA.length; i++) {
|
|
789
|
+
if (!polygonA[i] || polygonA[i].x == null || polygonA[i].y == null) {
|
|
790
|
+
throw new TypeError(`gjkDistance: polygonA[${i}] must have x and y properties`);
|
|
791
|
+
}
|
|
792
|
+
for (let j = 0; j < polygonB.length; j++) {
|
|
793
|
+
if (!polygonB[j] || polygonB[j].x == null || polygonB[j].y == null) {
|
|
794
|
+
throw new TypeError(`gjkDistance: polygonB[${j}] must have x and y properties`);
|
|
795
|
+
}
|
|
796
|
+
const dist = magnitude(vectorSub(polygonA[i], polygonB[j]));
|
|
651
797
|
if (dist.lessThan(minDist)) {
|
|
652
798
|
minDist = dist;
|
|
653
|
-
closestA =
|
|
654
|
-
closestB =
|
|
799
|
+
closestA = polygonA[i];
|
|
800
|
+
closestB = polygonB[j];
|
|
655
801
|
}
|
|
656
802
|
}
|
|
657
803
|
}
|
|
658
804
|
|
|
659
805
|
// Check vertex-to-edge distances
|
|
660
|
-
for (
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const
|
|
806
|
+
for (let i = 0; i < polygonA.length; i++) {
|
|
807
|
+
if (!polygonA[i] || polygonA[i].x == null || polygonA[i].y == null) {
|
|
808
|
+
throw new TypeError(`gjkDistance: polygonA[${i}] must have x and y properties`);
|
|
809
|
+
}
|
|
810
|
+
for (let j = 0; j < polygonB.length; j++) {
|
|
811
|
+
const e1 = polygonB[j];
|
|
812
|
+
const e2 = polygonB[(j + 1) % polygonB.length];
|
|
813
|
+
if (!e1 || e1.x == null || e1.y == null) {
|
|
814
|
+
throw new TypeError(`gjkDistance: polygonB[${j}] must have x and y properties`);
|
|
815
|
+
}
|
|
816
|
+
if (!e2 || e2.x == null || e2.y == null) {
|
|
817
|
+
throw new TypeError(`gjkDistance: polygonB[${(j + 1) % polygonB.length}] must have x and y properties`);
|
|
818
|
+
}
|
|
819
|
+
const closest = closestPointOnSegment(polygonA[i], e1, e2);
|
|
820
|
+
const dist = magnitude(vectorSub(polygonA[i], closest));
|
|
666
821
|
if (dist.lessThan(minDist)) {
|
|
667
822
|
minDist = dist;
|
|
668
|
-
closestA =
|
|
823
|
+
closestA = polygonA[i];
|
|
669
824
|
closestB = closest;
|
|
670
825
|
}
|
|
671
826
|
}
|
|
672
827
|
}
|
|
673
828
|
|
|
674
|
-
for (
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const
|
|
829
|
+
for (let i = 0; i < polygonB.length; i++) {
|
|
830
|
+
if (!polygonB[i] || polygonB[i].x == null || polygonB[i].y == null) {
|
|
831
|
+
throw new TypeError(`gjkDistance: polygonB[${i}] must have x and y properties`);
|
|
832
|
+
}
|
|
833
|
+
for (let j = 0; j < polygonA.length; j++) {
|
|
834
|
+
const e1 = polygonA[j];
|
|
835
|
+
const e2 = polygonA[(j + 1) % polygonA.length];
|
|
836
|
+
if (!e1 || e1.x == null || e1.y == null) {
|
|
837
|
+
throw new TypeError(`gjkDistance: polygonA[${j}] must have x and y properties`);
|
|
838
|
+
}
|
|
839
|
+
if (!e2 || e2.x == null || e2.y == null) {
|
|
840
|
+
throw new TypeError(`gjkDistance: polygonA[${(j + 1) % polygonA.length}] must have x and y properties`);
|
|
841
|
+
}
|
|
842
|
+
const closest = closestPointOnSegment(polygonB[i], e1, e2);
|
|
843
|
+
const dist = magnitude(vectorSub(polygonB[i], closest));
|
|
680
844
|
if (dist.lessThan(minDist)) {
|
|
681
845
|
minDist = dist;
|
|
682
846
|
closestA = closest;
|
|
683
|
-
closestB =
|
|
847
|
+
closestB = polygonB[i];
|
|
684
848
|
}
|
|
685
849
|
}
|
|
686
850
|
}
|
|
@@ -704,8 +868,13 @@ export function gjkDistance(polygonA, polygonB) {
|
|
|
704
868
|
* @param {{x: Decimal, y: Decimal}} a - Segment start
|
|
705
869
|
* @param {{x: Decimal, y: Decimal}} b - Segment end
|
|
706
870
|
* @returns {{x: Decimal, y: Decimal}} Closest point on segment
|
|
871
|
+
* @throws {TypeError} If any point is invalid or missing x/y properties
|
|
707
872
|
*/
|
|
708
873
|
export function closestPointOnSegment(pt, a, b) {
|
|
874
|
+
if (!pt || pt.x == null || pt.y == null) throw new TypeError('closestPointOnSegment: pt must have x and y properties');
|
|
875
|
+
if (!a || a.x == null || a.y == null) throw new TypeError('closestPointOnSegment: a must have x and y properties');
|
|
876
|
+
if (!b || b.x == null || b.y == null) throw new TypeError('closestPointOnSegment: b must have x and y properties');
|
|
877
|
+
|
|
709
878
|
const ab = vectorSub(b, a);
|
|
710
879
|
const ap = vectorSub(pt, a);
|
|
711
880
|
|
|
@@ -735,11 +904,24 @@ export function closestPointOnSegment(pt, a, b) {
|
|
|
735
904
|
* @param {Array<{x: number|Decimal, y: number|Decimal}>} polygonA - First polygon
|
|
736
905
|
* @param {Array<{x: number|Decimal, y: number|Decimal}>} polygonB - Second polygon
|
|
737
906
|
* @returns {{overlaps: boolean, verified: boolean}}
|
|
907
|
+
* @throws {TypeError} If polygons are not arrays
|
|
738
908
|
*/
|
|
739
909
|
export function polygonsOverlap(polygonA, polygonB) {
|
|
910
|
+
if (!Array.isArray(polygonA)) throw new TypeError('polygonsOverlap: polygonA must be an array');
|
|
911
|
+
if (!Array.isArray(polygonB)) throw new TypeError('polygonsOverlap: polygonB must be an array');
|
|
740
912
|
// Normalize input to Decimal
|
|
741
|
-
const normA = polygonA.map(
|
|
742
|
-
|
|
913
|
+
const normA = polygonA.map((p, i) => {
|
|
914
|
+
if (!p || p.x == null || p.y == null) {
|
|
915
|
+
throw new TypeError(`polygonsOverlap: polygonA[${i}] must have x and y properties`);
|
|
916
|
+
}
|
|
917
|
+
return point(p.x, p.y);
|
|
918
|
+
});
|
|
919
|
+
const normB = polygonB.map((p, i) => {
|
|
920
|
+
if (!p || p.x == null || p.y == null) {
|
|
921
|
+
throw new TypeError(`polygonsOverlap: polygonB[${i}] must have x and y properties`);
|
|
922
|
+
}
|
|
923
|
+
return point(p.x, p.y);
|
|
924
|
+
});
|
|
743
925
|
|
|
744
926
|
const result = gjkIntersects(normA, normB);
|
|
745
927
|
|
|
@@ -755,11 +937,24 @@ export function polygonsOverlap(polygonA, polygonB) {
|
|
|
755
937
|
* @param {Array<{x: number|Decimal, y: number|Decimal}>} polygonA - First polygon
|
|
756
938
|
* @param {Array<{x: number|Decimal, y: number|Decimal}>} polygonB - Second polygon
|
|
757
939
|
* @returns {{distance: Decimal, verified: boolean}}
|
|
940
|
+
* @throws {TypeError} If polygons are not arrays
|
|
758
941
|
*/
|
|
759
942
|
export function polygonsDistance(polygonA, polygonB) {
|
|
943
|
+
if (!Array.isArray(polygonA)) throw new TypeError('polygonsDistance: polygonA must be an array');
|
|
944
|
+
if (!Array.isArray(polygonB)) throw new TypeError('polygonsDistance: polygonB must be an array');
|
|
760
945
|
// Normalize input to Decimal
|
|
761
|
-
const normA = polygonA.map(
|
|
762
|
-
|
|
946
|
+
const normA = polygonA.map((p, i) => {
|
|
947
|
+
if (!p || p.x == null || p.y == null) {
|
|
948
|
+
throw new TypeError(`polygonsDistance: polygonA[${i}] must have x and y properties`);
|
|
949
|
+
}
|
|
950
|
+
return point(p.x, p.y);
|
|
951
|
+
});
|
|
952
|
+
const normB = polygonB.map((p, i) => {
|
|
953
|
+
if (!p || p.x == null || p.y == null) {
|
|
954
|
+
throw new TypeError(`polygonsDistance: polygonB[${i}] must have x and y properties`);
|
|
955
|
+
}
|
|
956
|
+
return point(p.x, p.y);
|
|
957
|
+
});
|
|
763
958
|
|
|
764
959
|
const result = gjkDistance(normA, normB);
|
|
765
960
|
|
|
@@ -777,10 +972,18 @@ export function polygonsDistance(polygonA, polygonB) {
|
|
|
777
972
|
* @param {{x: number|Decimal, y: number|Decimal}} pt - Point to test
|
|
778
973
|
* @param {Array<{x: number|Decimal, y: number|Decimal}>} polygon - Polygon
|
|
779
974
|
* @returns {boolean} True if inside
|
|
975
|
+
* @throws {TypeError} If pt is invalid or polygon is not an array
|
|
780
976
|
*/
|
|
781
977
|
export function isPointInPolygon(pt, polygon) {
|
|
978
|
+
if (!pt || pt.x == null || pt.y == null) throw new TypeError('isPointInPolygon: pt must have x and y properties');
|
|
979
|
+
if (!Array.isArray(polygon)) throw new TypeError('isPointInPolygon: polygon must be an array');
|
|
782
980
|
const normPt = point(pt.x, pt.y);
|
|
783
|
-
const normPoly = polygon.map(
|
|
981
|
+
const normPoly = polygon.map((p, i) => {
|
|
982
|
+
if (!p || p.x == null || p.y == null) {
|
|
983
|
+
throw new TypeError(`isPointInPolygon: polygon[${i}] must have x and y properties`);
|
|
984
|
+
}
|
|
985
|
+
return point(p.x, p.y);
|
|
986
|
+
});
|
|
784
987
|
|
|
785
988
|
return pointInConvexPolygon(normPt, normPoly);
|
|
786
989
|
}
|