@weasel-js/geom 1.10.0 → 1.11.0
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/README.md +140 -16
- package/dist/3d/index.d.ts +29 -2
- package/dist/3d/index.js +49 -1
- package/dist/3d/index.js.map +1 -1
- package/dist/booleans/index.d.ts +34 -10
- package/dist/booleans/index.js +344 -352
- package/dist/booleans/index.js.map +1 -1
- package/dist/{chunk-MDT7F32A.js → chunk-6XSJY3TC.js} +10 -39
- package/dist/chunk-6XSJY3TC.js.map +1 -0
- package/dist/chunk-7KLJZFBY.js +573 -0
- package/dist/chunk-7KLJZFBY.js.map +1 -0
- package/dist/chunk-B53BERKG.js +386 -0
- package/dist/chunk-B53BERKG.js.map +1 -0
- package/dist/chunk-CE7IY7TB.js +104 -0
- package/dist/chunk-CE7IY7TB.js.map +1 -0
- package/dist/chunk-HGF5OVEN.js +129 -0
- package/dist/chunk-HGF5OVEN.js.map +1 -0
- package/dist/chunk-J2O2B54M.js +800 -0
- package/dist/chunk-J2O2B54M.js.map +1 -0
- package/dist/chunk-Q66NA2FU.js +174 -0
- package/dist/chunk-Q66NA2FU.js.map +1 -0
- package/dist/chunk-SASMXDBE.js +443 -0
- package/dist/chunk-SASMXDBE.js.map +1 -0
- package/dist/chunk-UI3FUWXM.js +533 -0
- package/dist/chunk-UI3FUWXM.js.map +1 -0
- package/dist/curvature-PkoLduGc.d.ts +454 -0
- package/dist/curves/index.d.ts +14 -5
- package/dist/curves/index.js +33 -111
- package/dist/curves/index.js.map +1 -1
- package/dist/index.d.ts +1697 -205
- package/dist/index.js +4729 -1308
- package/dist/index.js.map +1 -1
- package/dist/nd/index.d.ts +91 -27
- package/dist/nd/index.js +282 -0
- package/dist/nd/index.js.map +1 -1
- package/dist/path-BW8d_2RY.d.ts +157 -0
- package/dist/portCurve-C5HDWuuV.d.ts +30 -0
- package/dist/tessellate/index.d.ts +18 -4
- package/dist/tessellate/index.js +21 -277
- package/dist/tessellate/index.js.map +1 -1
- package/dist/warp/index.d.ts +252 -0
- package/dist/warp/index.js +1316 -0
- package/dist/warp/index.js.map +1 -0
- package/package.json +5 -1
- package/dist/chunk-MDT7F32A.js.map +0 -1
- package/dist/chunk-OXH3NOXC.js +0 -62
- package/dist/chunk-OXH3NOXC.js.map +0 -1
- package/dist/chunk-URQMDR6Q.js +0 -80
- package/dist/chunk-URQMDR6Q.js.map +0 -1
- package/dist/chunk-WQMYOCFB.js +0 -167
- package/dist/chunk-WQMYOCFB.js.map +0 -1
- package/dist/chunk-XS6ERH3X.js +0 -34
- package/dist/chunk-XS6ERH3X.js.map +0 -1
- package/dist/cubicMath-CFXbq6vp.d.ts +0 -116
- package/dist/path-Bf9KeVxC.d.ts +0 -44
package/dist/booleans/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
import { orientBoundaries, parseContours, contoursIntersect, assembleRegions, signedArea, negligibleArea, regionArea, toPath, parseSubpaths, writePath, signChanges, sideCoeffs, evalSeg, extentOf, keepCorners, windingAt, reverseContour, sampleContour, subSeg, endSide } from '../chunk-7KLJZFBY.js';
|
|
2
|
+
import '../chunk-CE7IY7TB.js';
|
|
3
|
+
import { resolveCorners, DEFAULT_FLATTEN_TOLERANCE, fillsOpenSubpaths, flattenCubic, flattenQuadratic, pointInPolygon } from '../chunk-J2O2B54M.js';
|
|
4
|
+
import '../chunk-755Y4YZS.js';
|
|
5
|
+
import { PATH_Z, PATH_Q, elevateQuadraticToCubic, PATH_C, PATH_L, PATH_M } from '../chunk-B53BERKG.js';
|
|
5
6
|
import polygonClipping from 'polygon-clipping';
|
|
6
7
|
|
|
7
8
|
// src/booleans/adapter.ts
|
|
8
|
-
function pathToMultiPolygon(
|
|
9
|
+
function pathToMultiPolygon(given, opts = {}) {
|
|
10
|
+
const path = resolveCorners(given);
|
|
9
11
|
if (path.kind === "rect") {
|
|
10
12
|
const { x, y, width, height } = path;
|
|
11
13
|
const ring = [
|
|
@@ -214,27 +216,297 @@ function multiPolygonToPath(mp) {
|
|
|
214
216
|
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
215
217
|
}
|
|
216
218
|
|
|
219
|
+
// src/booleans/arrangement.ts
|
|
220
|
+
var VertexPool = class {
|
|
221
|
+
constructor(eps) {
|
|
222
|
+
this.eps = eps;
|
|
223
|
+
this.cell = eps * 2;
|
|
224
|
+
}
|
|
225
|
+
eps;
|
|
226
|
+
xs = [];
|
|
227
|
+
ys = [];
|
|
228
|
+
cells = /* @__PURE__ */ new Map();
|
|
229
|
+
cell;
|
|
230
|
+
id(x, y) {
|
|
231
|
+
const cx = Math.floor(x / this.cell), cy = Math.floor(y / this.cell);
|
|
232
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
233
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
234
|
+
for (const v2 of this.cells.get(`${cx + dx},${cy + dy}`) ?? []) {
|
|
235
|
+
if (Math.abs(this.xs[v2] - x) <= this.eps && Math.abs(this.ys[v2] - y) <= this.eps) return v2;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const v = this.xs.length;
|
|
240
|
+
this.xs.push(x);
|
|
241
|
+
this.ys.push(y);
|
|
242
|
+
const key = `${cx},${cy}`;
|
|
243
|
+
const bucket = this.cells.get(key);
|
|
244
|
+
if (bucket) bucket.push(v);
|
|
245
|
+
else this.cells.set(key, [v]);
|
|
246
|
+
return v;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
function segmentsOf(inputs) {
|
|
250
|
+
const segs = [];
|
|
251
|
+
let extent = 0;
|
|
252
|
+
inputs.forEach((mp, input) => {
|
|
253
|
+
for (const polygon of mp) {
|
|
254
|
+
for (const ring of polygon) {
|
|
255
|
+
for (let k = 0; k + 1 < ring.length; k++) {
|
|
256
|
+
const [ax, ay] = ring[k];
|
|
257
|
+
const [bx, by] = ring[k + 1];
|
|
258
|
+
if (ax === bx && ay === by) continue;
|
|
259
|
+
extent = Math.max(extent, Math.abs(ax), Math.abs(ay), Math.abs(bx), Math.abs(by));
|
|
260
|
+
segs.push({
|
|
261
|
+
ax,
|
|
262
|
+
ay,
|
|
263
|
+
bx,
|
|
264
|
+
by,
|
|
265
|
+
input,
|
|
266
|
+
minX: Math.min(ax, bx),
|
|
267
|
+
maxX: Math.max(ax, bx),
|
|
268
|
+
minY: Math.min(ay, by),
|
|
269
|
+
maxY: Math.max(ay, by),
|
|
270
|
+
cuts: []
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
return { segs, extent };
|
|
277
|
+
}
|
|
278
|
+
function cutAtPoint(seg, px, py, eps) {
|
|
279
|
+
const rx = seg.bx - seg.ax, ry = seg.by - seg.ay;
|
|
280
|
+
const len2 = rx * rx + ry * ry;
|
|
281
|
+
const t = ((px - seg.ax) * rx + (py - seg.ay) * ry) / len2;
|
|
282
|
+
if (t <= 0 || t >= 1) return;
|
|
283
|
+
const ex = seg.ax + t * rx - px, ey = seg.ay + t * ry - py;
|
|
284
|
+
if (ex * ex + ey * ey <= eps * eps) seg.cuts.push({ t, x: px, y: py });
|
|
285
|
+
}
|
|
286
|
+
function cutPair(s, o, eps) {
|
|
287
|
+
const rx = s.bx - s.ax, ry = s.by - s.ay;
|
|
288
|
+
const sx = o.bx - o.ax, sy = o.by - o.ay;
|
|
289
|
+
const d = rx * sy - ry * sx;
|
|
290
|
+
const lenR = Math.hypot(rx, ry), lenS = Math.hypot(sx, sy);
|
|
291
|
+
if (Math.abs(d) > 1e-12 * lenR * lenS) {
|
|
292
|
+
const qx = o.ax - s.ax, qy = o.ay - s.ay;
|
|
293
|
+
const t = (qx * sy - qy * sx) / d;
|
|
294
|
+
const u = (qx * ry - qy * rx) / d;
|
|
295
|
+
const tolT = eps / lenR, tolU = eps / lenS;
|
|
296
|
+
if (t > -tolT && t < 1 + tolT && u > -tolU && u < 1 + tolU) {
|
|
297
|
+
const tc = Math.min(1, Math.max(0, t));
|
|
298
|
+
const x = s.ax + tc * rx, y = s.ay + tc * ry;
|
|
299
|
+
s.cuts.push({ t: tc, x, y });
|
|
300
|
+
o.cuts.push({ t: Math.min(1, Math.max(0, u)), x, y });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
cutAtPoint(s, o.ax, o.ay, eps);
|
|
304
|
+
cutAtPoint(s, o.bx, o.by, eps);
|
|
305
|
+
cutAtPoint(o, s.ax, s.ay, eps);
|
|
306
|
+
cutAtPoint(o, s.bx, s.by, eps);
|
|
307
|
+
}
|
|
308
|
+
function cutAll(segs, eps) {
|
|
309
|
+
const order = segs.map((_, i) => i).sort((i, j) => segs[i].minX - segs[j].minX);
|
|
310
|
+
for (let a = 0; a < order.length; a++) {
|
|
311
|
+
const s = segs[order[a]];
|
|
312
|
+
for (let b = a + 1; b < order.length; b++) {
|
|
313
|
+
const o = segs[order[b]];
|
|
314
|
+
if (o.minX > s.maxX + eps) break;
|
|
315
|
+
if (o.minY > s.maxY + eps || o.maxY < s.minY - eps) continue;
|
|
316
|
+
cutPair(s, o, eps);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function linkFaces(xs, ys, origin) {
|
|
321
|
+
const out = xs.map(() => []);
|
|
322
|
+
const angle = [];
|
|
323
|
+
for (let h = 0; h < origin.length; h++) {
|
|
324
|
+
const u = origin[h], v = origin[h ^ 1];
|
|
325
|
+
out[u].push(h);
|
|
326
|
+
angle[h] = Math.atan2(ys[v] - ys[u], xs[v] - xs[u]);
|
|
327
|
+
}
|
|
328
|
+
const slot = [];
|
|
329
|
+
for (const list of out) {
|
|
330
|
+
list.sort((a, b) => angle[a] - angle[b]);
|
|
331
|
+
list.forEach((h, k) => {
|
|
332
|
+
slot[h] = k;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return origin.map((_, h) => {
|
|
336
|
+
const list = out[origin[h ^ 1]];
|
|
337
|
+
return list[(slot[h ^ 1] - 1 + list.length) % list.length];
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function buildArrangement(inputs) {
|
|
341
|
+
const { segs, extent } = segmentsOf(inputs);
|
|
342
|
+
const eps = 1e-9 * Math.max(1, extent);
|
|
343
|
+
cutAll(segs, eps);
|
|
344
|
+
const pool = new VertexPool(eps);
|
|
345
|
+
const edges = /* @__PURE__ */ new Map();
|
|
346
|
+
for (const seg of segs) {
|
|
347
|
+
const points = [{ t: 0, x: seg.ax, y: seg.ay }, ...seg.cuts, { t: 1, x: seg.bx, y: seg.by }].sort((a, b) => a.t - b.t);
|
|
348
|
+
const bit = 1n << BigInt(seg.input);
|
|
349
|
+
let prev = pool.id(points[0].x, points[0].y);
|
|
350
|
+
for (let k = 1; k < points.length; k++) {
|
|
351
|
+
const id = pool.id(points[k].x, points[k].y);
|
|
352
|
+
if (id === prev) continue;
|
|
353
|
+
const u = Math.min(prev, id), v = Math.max(prev, id);
|
|
354
|
+
const key = `${u},${v}`;
|
|
355
|
+
const edge = edges.get(key);
|
|
356
|
+
if (edge) edge.owners ^= bit;
|
|
357
|
+
else edges.set(key, { u, v, owners: bit });
|
|
358
|
+
prev = id;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const origin = [];
|
|
362
|
+
const owners = [];
|
|
363
|
+
for (const { u, v, owners: o } of edges.values()) {
|
|
364
|
+
if (o === 0n) continue;
|
|
365
|
+
origin.push(u, v);
|
|
366
|
+
owners.push(o);
|
|
367
|
+
}
|
|
368
|
+
return { xs: pool.xs, ys: pool.ys, origin, next: linkFaces(pool.xs, pool.ys, origin), owners };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/booleans/arrangementFaces.ts
|
|
372
|
+
function components(arr) {
|
|
373
|
+
const parent = arr.xs.map((_, i) => i);
|
|
374
|
+
const find = (v) => {
|
|
375
|
+
while (parent[v] !== v) {
|
|
376
|
+
parent[v] = parent[parent[v]];
|
|
377
|
+
v = parent[v];
|
|
378
|
+
}
|
|
379
|
+
return v;
|
|
380
|
+
};
|
|
381
|
+
for (let h = 0; h < arr.origin.length; h += 2) parent[find(arr.origin[h])] = find(arr.origin[h + 1]);
|
|
382
|
+
return arr.xs.map((_, v) => find(v));
|
|
383
|
+
}
|
|
384
|
+
function traceCycles(arr, compOf) {
|
|
385
|
+
const cycleOf = new Array(arr.origin.length).fill(-1);
|
|
386
|
+
const cycles = [];
|
|
387
|
+
for (let start = 0; start < arr.origin.length; start++) {
|
|
388
|
+
if (cycleOf[start] !== -1) continue;
|
|
389
|
+
const c = { edges: [], area: 0, component: compOf[arr.origin[start]], minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity };
|
|
390
|
+
let h = start;
|
|
391
|
+
do {
|
|
392
|
+
cycleOf[h] = cycles.length;
|
|
393
|
+
c.edges.push(h);
|
|
394
|
+
const x0 = arr.xs[arr.origin[h]], y0 = arr.ys[arr.origin[h]];
|
|
395
|
+
const x1 = arr.xs[arr.origin[h ^ 1]], y1 = arr.ys[arr.origin[h ^ 1]];
|
|
396
|
+
c.area += (x0 * y1 - x1 * y0) / 2;
|
|
397
|
+
c.minX = Math.min(c.minX, x0);
|
|
398
|
+
c.maxX = Math.max(c.maxX, x0);
|
|
399
|
+
c.minY = Math.min(c.minY, y0);
|
|
400
|
+
c.maxY = Math.max(c.maxY, y0);
|
|
401
|
+
h = arr.next[h];
|
|
402
|
+
} while (h !== start);
|
|
403
|
+
cycles.push(c);
|
|
404
|
+
}
|
|
405
|
+
return { cycles, cycleOf };
|
|
406
|
+
}
|
|
407
|
+
function coverAt(arr, compOf, component, px, py) {
|
|
408
|
+
let mask = 0n;
|
|
409
|
+
for (let h = 0; h < arr.origin.length; h += 2) {
|
|
410
|
+
const u = arr.origin[h], v = arr.origin[h + 1];
|
|
411
|
+
if (compOf[u] === component) continue;
|
|
412
|
+
const y0 = arr.ys[u], y1 = arr.ys[v];
|
|
413
|
+
if (y0 > py === y1 > py) continue;
|
|
414
|
+
const x = arr.xs[u] + (py - y0) / (y1 - y0) * (arr.xs[v] - arr.xs[u]);
|
|
415
|
+
if (x < px) mask ^= arr.owners[h >> 1];
|
|
416
|
+
}
|
|
417
|
+
return mask;
|
|
418
|
+
}
|
|
419
|
+
function ringOf(arr, cycle) {
|
|
420
|
+
const ring = cycle.edges.map((h) => [arr.xs[arr.origin[h]], arr.ys[arr.origin[h]]]);
|
|
421
|
+
ring.push([ring[0][0], ring[0][1]]);
|
|
422
|
+
return ring;
|
|
423
|
+
}
|
|
424
|
+
function ringHolds(ring, px, py) {
|
|
425
|
+
let inside = false;
|
|
426
|
+
for (let k = 0; k + 1 < ring.length; k++) {
|
|
427
|
+
const [x0, y0] = ring[k];
|
|
428
|
+
const [x1, y1] = ring[k + 1];
|
|
429
|
+
if (y0 > py !== y1 > py && px < x0 + (py - y0) / (y1 - y0) * (x1 - x0)) inside = !inside;
|
|
430
|
+
}
|
|
431
|
+
return inside;
|
|
432
|
+
}
|
|
433
|
+
function popcount(mask) {
|
|
434
|
+
let c = 0;
|
|
435
|
+
for (let m = mask; m; m &= m - 1n) c++;
|
|
436
|
+
return c;
|
|
437
|
+
}
|
|
438
|
+
function arrangementFaces(inputs) {
|
|
439
|
+
const arr = buildArrangement(inputs);
|
|
440
|
+
const compOf = components(arr);
|
|
441
|
+
const { cycles, cycleOf } = traceCycles(arr, compOf);
|
|
442
|
+
const outerOf = /* @__PURE__ */ new Map();
|
|
443
|
+
cycles.forEach((c, i) => {
|
|
444
|
+
const best = outerOf.get(c.component);
|
|
445
|
+
if (best === void 0 || c.area < cycles[best].area) outerOf.set(c.component, i);
|
|
446
|
+
});
|
|
447
|
+
const label = new Array(cycles.length);
|
|
448
|
+
for (const [component, outer] of outerOf) {
|
|
449
|
+
const v = arr.origin[cycles[outer].edges[0]];
|
|
450
|
+
label[outer] = coverAt(arr, compOf, component, arr.xs[v], arr.ys[v]);
|
|
451
|
+
const queue = [outer];
|
|
452
|
+
while (queue.length > 0) {
|
|
453
|
+
const f = queue.pop();
|
|
454
|
+
for (const h of cycles[f].edges) {
|
|
455
|
+
const g = cycleOf[h ^ 1];
|
|
456
|
+
if (label[g] !== void 0) continue;
|
|
457
|
+
label[g] = label[f] ^ arr.owners[h >> 1];
|
|
458
|
+
queue.push(g);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
let extent = 1;
|
|
463
|
+
for (let v = 0; v < arr.xs.length; v++) extent = Math.max(extent, Math.abs(arr.xs[v]), Math.abs(arr.ys[v]));
|
|
464
|
+
const minArea = 1e-12 * extent * extent;
|
|
465
|
+
const faces2 = /* @__PURE__ */ new Map();
|
|
466
|
+
cycles.forEach((c, i) => {
|
|
467
|
+
if (c.area > minArea && label[i]) faces2.set(i, { mask: label[i], polygon: [ringOf(arr, c)] });
|
|
468
|
+
});
|
|
469
|
+
for (const [component, outer] of outerOf) {
|
|
470
|
+
const v = arr.origin[cycles[outer].edges[0]];
|
|
471
|
+
const px = arr.xs[v], py = arr.ys[v];
|
|
472
|
+
let host = -1;
|
|
473
|
+
cycles.forEach((c, i) => {
|
|
474
|
+
if (c.component === component || c.area <= minArea) return;
|
|
475
|
+
if (px < c.minX || px > c.maxX || py < c.minY || py > c.maxY) return;
|
|
476
|
+
if (host !== -1 && cycles[host].area <= c.area) return;
|
|
477
|
+
if (ringHolds(ringOf(arr, c), px, py)) host = i;
|
|
478
|
+
});
|
|
479
|
+
faces2.get(host)?.polygon.push(ringOf(arr, cycles[outer]));
|
|
480
|
+
}
|
|
481
|
+
const left = (f) => Math.min(...f.polygon[0].map((p) => p[0]));
|
|
482
|
+
const low = (f) => Math.min(...f.polygon[0].map((p) => p[1]));
|
|
483
|
+
return [...faces2.values()].sort((a, b) => popcount(a.mask) - popcount(b.mask) || (a.mask < b.mask ? -1 : a.mask > b.mask ? 1 : 0) || left(a) - left(b) || low(a) - low(b));
|
|
484
|
+
}
|
|
485
|
+
|
|
217
486
|
// src/booleans/ops.ts
|
|
487
|
+
function toPath2(mp, sources) {
|
|
488
|
+
return keepCorners(sources, multiPolygonToPath(mp));
|
|
489
|
+
}
|
|
218
490
|
function pathUnion(...paths) {
|
|
219
491
|
if (paths.length === 0) return multiPolygonToPath([]);
|
|
220
492
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
221
493
|
const [head, ...rest] = mps;
|
|
222
|
-
return
|
|
494
|
+
return toPath2(polygonClipping.union(head, ...rest), paths);
|
|
223
495
|
}
|
|
224
496
|
function pathIntersect(...paths) {
|
|
225
497
|
if (paths.length === 0) return multiPolygonToPath([]);
|
|
226
498
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
227
499
|
const [head, ...rest] = mps;
|
|
228
|
-
return
|
|
500
|
+
return toPath2(polygonClipping.intersection(head, ...rest), paths);
|
|
229
501
|
}
|
|
230
502
|
function pathSubtract(a, b) {
|
|
231
|
-
return
|
|
503
|
+
return toPath2(polygonClipping.difference(pathToMultiPolygon(a), pathToMultiPolygon(b)), [a, b]);
|
|
232
504
|
}
|
|
233
505
|
function pathExclude(...paths) {
|
|
234
506
|
if (paths.length === 0) return multiPolygonToPath([]);
|
|
235
507
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
236
508
|
const [head, ...rest] = mps;
|
|
237
|
-
return
|
|
509
|
+
return toPath2(polygonClipping.xor(head, ...rest), paths);
|
|
238
510
|
}
|
|
239
511
|
function pathDivide(...paths) {
|
|
240
512
|
return pathDividePieces(...paths).map((p) => p.path);
|
|
@@ -242,37 +514,29 @@ function pathDivide(...paths) {
|
|
|
242
514
|
function pathCrop(...paths) {
|
|
243
515
|
return pathCropPieces(...paths).map((p) => p.path);
|
|
244
516
|
}
|
|
245
|
-
function
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const n = mps.length;
|
|
252
|
-
const out = [];
|
|
253
|
-
const subsets = [];
|
|
254
|
-
for (let mask = 1; mask < 1 << n; mask++) subsets.push(mask);
|
|
255
|
-
subsets.sort((a, b) => popcount(a) - popcount(b) || a - b);
|
|
256
|
-
for (const mask of subsets) {
|
|
257
|
-
const inside = [];
|
|
258
|
-
const outside = [];
|
|
517
|
+
function faces(paths) {
|
|
518
|
+
const clean = paths.map((p) => {
|
|
519
|
+
const mp = pathToMultiPolygon(p);
|
|
520
|
+
return mp.length === 0 ? mp : polygonClipping.union(mp);
|
|
521
|
+
});
|
|
522
|
+
return arrangementFaces(clean).map(({ mask, polygon }) => {
|
|
259
523
|
const sources = [];
|
|
260
|
-
for (let i = 0; i <
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (region.length === 0) continue;
|
|
272
|
-
}
|
|
273
|
-
out.push({ path: multiPolygonToPath(region), sources });
|
|
524
|
+
for (let i = 0; i < paths.length; i++) if (mask & 1n << BigInt(i)) sources.push(i);
|
|
525
|
+
return { polygon, sources };
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
function pathDividePieces(...paths) {
|
|
529
|
+
const regions = /* @__PURE__ */ new Map();
|
|
530
|
+
for (const { polygon, sources } of faces(paths)) {
|
|
531
|
+
const key = sources.join(",");
|
|
532
|
+
const region = regions.get(key);
|
|
533
|
+
if (region) region.polygons.push(polygon);
|
|
534
|
+
else regions.set(key, { polygons: [polygon], sources });
|
|
274
535
|
}
|
|
275
|
-
return
|
|
536
|
+
return [...regions.values()].map(({ polygons, sources }) => ({ path: toPath2(polygons, paths), sources }));
|
|
537
|
+
}
|
|
538
|
+
function pathFacePieces(...paths) {
|
|
539
|
+
return faces(paths).map(({ polygon, sources }) => ({ path: toPath2([polygon], paths), sources }));
|
|
276
540
|
}
|
|
277
541
|
function pathTrim(...paths) {
|
|
278
542
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
@@ -280,7 +544,7 @@ function pathTrim(...paths) {
|
|
|
280
544
|
let cover = null;
|
|
281
545
|
for (let i = mps.length - 1; i >= 0; i--) {
|
|
282
546
|
const shown = cover ? polygonClipping.difference(mps[i], cover) : mps[i];
|
|
283
|
-
if (shown.length > 0) out.unshift({ path:
|
|
547
|
+
if (shown.length > 0) out.unshift({ path: toPath2(shown, paths), sources: [i] });
|
|
284
548
|
cover = cover ? polygonClipping.union(cover, mps[i]) : mps[i];
|
|
285
549
|
}
|
|
286
550
|
return out;
|
|
@@ -299,14 +563,6 @@ function pathMerge(paths, keyOf) {
|
|
|
299
563
|
sources: g.map((p) => p.sources[0])
|
|
300
564
|
}).sort((a, b) => top(a) - top(b));
|
|
301
565
|
}
|
|
302
|
-
function popcount(n) {
|
|
303
|
-
let c = 0;
|
|
304
|
-
while (n) {
|
|
305
|
-
n &= n - 1;
|
|
306
|
-
c++;
|
|
307
|
-
}
|
|
308
|
-
return c;
|
|
309
|
-
}
|
|
310
566
|
function pathCropPieces(...paths) {
|
|
311
567
|
if (paths.length < 2) return [];
|
|
312
568
|
const mps = paths.map((p) => pathToMultiPolygon(p));
|
|
@@ -314,7 +570,7 @@ function pathCropPieces(...paths) {
|
|
|
314
570
|
const out = [];
|
|
315
571
|
for (let i = 0; i < mps.length - 1; i++) {
|
|
316
572
|
const clipped = polygonClipping.intersection(mps[i], mask);
|
|
317
|
-
if (clipped.length > 0) out.push({ path:
|
|
573
|
+
if (clipped.length > 0) out.push({ path: toPath2(clipped, paths), sources: [i] });
|
|
318
574
|
}
|
|
319
575
|
return out;
|
|
320
576
|
}
|
|
@@ -333,141 +589,6 @@ function trianglesToPath(vertices, indices) {
|
|
|
333
589
|
return multiPolygonToPath(polygonClipping.union(head, ...rest));
|
|
334
590
|
}
|
|
335
591
|
|
|
336
|
-
// src/booleans/contourSegments.ts
|
|
337
|
-
function evalSeg(s, t) {
|
|
338
|
-
const u = 1 - t;
|
|
339
|
-
if (s.length === 2) return { x: u * s[0].x + t * s[1].x, y: u * s[0].y + t * s[1].y };
|
|
340
|
-
if (s.length === 3) {
|
|
341
|
-
const a2 = u * u, b2 = 2 * u * t, c2 = t * t;
|
|
342
|
-
return { x: a2 * s[0].x + b2 * s[1].x + c2 * s[2].x, y: a2 * s[0].y + b2 * s[1].y + c2 * s[2].y };
|
|
343
|
-
}
|
|
344
|
-
const a = u * u * u, b = 3 * u * u * t, c = 3 * u * t * t, d = t * t * t;
|
|
345
|
-
return {
|
|
346
|
-
x: a * s[0].x + b * s[1].x + c * s[2].x + d * s[3].x,
|
|
347
|
-
y: a * s[0].y + b * s[1].y + c * s[2].y + d * s[3].y
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
function derivSeg(s, t) {
|
|
351
|
-
const n = s.length - 1;
|
|
352
|
-
const d = [];
|
|
353
|
-
for (let i = 0; i < n; i++) d.push({ x: n * (s[i + 1].x - s[i].x), y: n * (s[i + 1].y - s[i].y) });
|
|
354
|
-
return d.length === 1 ? d[0] : evalSeg(d, t);
|
|
355
|
-
}
|
|
356
|
-
function splitSeg(s, t) {
|
|
357
|
-
const pts = (c) => {
|
|
358
|
-
const out = [];
|
|
359
|
-
for (let i = 0; i < c.length; i += 2) out.push({ x: c[i], y: c[i + 1] });
|
|
360
|
-
return out;
|
|
361
|
-
};
|
|
362
|
-
if (s.length === 2) {
|
|
363
|
-
const m = evalSeg(s, t);
|
|
364
|
-
return [[s[0], m], [m, s[1]]];
|
|
365
|
-
}
|
|
366
|
-
if (s.length === 3) {
|
|
367
|
-
const [l2, r2] = splitQuadraticAt(s[0].x, s[0].y, s[1].x, s[1].y, s[2].x, s[2].y, t);
|
|
368
|
-
return [pts(l2), pts(r2)];
|
|
369
|
-
}
|
|
370
|
-
const [l, r] = splitCubicAt(s[0].x, s[0].y, s[1].x, s[1].y, s[2].x, s[2].y, s[3].x, s[3].y, t);
|
|
371
|
-
return [pts(l), pts(r)];
|
|
372
|
-
}
|
|
373
|
-
function subSeg(s, t0, t1, p0, p1) {
|
|
374
|
-
if (t1 - t0 <= 0) return null;
|
|
375
|
-
let out = s;
|
|
376
|
-
if (t0 > 0) out = splitSeg(out, t0)[1];
|
|
377
|
-
const u = (t1 - t0) / (1 - t0);
|
|
378
|
-
if (u < 1) out = splitSeg(out, u)[0];
|
|
379
|
-
out = out.slice();
|
|
380
|
-
out[0] = p0;
|
|
381
|
-
out[out.length - 1] = p1;
|
|
382
|
-
return out;
|
|
383
|
-
}
|
|
384
|
-
var reverseContour = (c) => c.map((s) => s.slice().reverse()).reverse();
|
|
385
|
-
function sampleContour(c) {
|
|
386
|
-
const out = [];
|
|
387
|
-
for (const s of c) {
|
|
388
|
-
const n = s.length === 2 ? 1 : 16;
|
|
389
|
-
for (let i = 0; i < n; i++) {
|
|
390
|
-
const p = evalSeg(s, i / n);
|
|
391
|
-
out.push(p.x, p.y);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return out;
|
|
395
|
-
}
|
|
396
|
-
function signedArea(c) {
|
|
397
|
-
const pts = sampleContour(c);
|
|
398
|
-
let a = 0;
|
|
399
|
-
for (let i = 0; i < pts.length; i += 2) {
|
|
400
|
-
const j = (i + 2) % pts.length;
|
|
401
|
-
a += pts[i] * pts[j + 1] - pts[j] * pts[i + 1];
|
|
402
|
-
}
|
|
403
|
-
return a / 2;
|
|
404
|
-
}
|
|
405
|
-
function sideCoeffs(s, o, d) {
|
|
406
|
-
return s.map((p) => d.x * (p.y - o.y) - d.y * (p.x - o.x));
|
|
407
|
-
}
|
|
408
|
-
function bernstein(f, t) {
|
|
409
|
-
const u = 1 - t;
|
|
410
|
-
if (f.length === 2) return u * f[0] + t * f[1];
|
|
411
|
-
if (f.length === 3) return u * u * f[0] + 2 * u * t * f[1] + t * t * f[2];
|
|
412
|
-
return u * u * u * f[0] + 3 * u * u * t * f[1] + 3 * u * t * t * f[2] + t * t * t * f[3];
|
|
413
|
-
}
|
|
414
|
-
function signChanges(f, zeroIsPositive) {
|
|
415
|
-
const sgn = (v) => v > 0 ? 1 : v < 0 ? -1 : zeroIsPositive ? 1 : 0;
|
|
416
|
-
const cuts = [0, ...criticalPoints(f), 1];
|
|
417
|
-
const out = [];
|
|
418
|
-
for (let i = 0; i + 1 < cuts.length; i++) {
|
|
419
|
-
let lo = cuts[i], hi = cuts[i + 1];
|
|
420
|
-
const slo = sgn(bernstein(f, lo)), shi = sgn(bernstein(f, hi));
|
|
421
|
-
if (slo === 0 && i > 0) {
|
|
422
|
-
const before = sgn(bernstein(f, lo - 1e-7)), after = sgn(bernstein(f, lo + 1e-7));
|
|
423
|
-
if (before * after < 0) out.push({ t: lo, before, after });
|
|
424
|
-
}
|
|
425
|
-
if (slo * shi >= 0) continue;
|
|
426
|
-
for (let it = 0; it < 64; it++) {
|
|
427
|
-
const mid = (lo + hi) / 2;
|
|
428
|
-
if (sgn(bernstein(f, mid)) === slo) lo = mid;
|
|
429
|
-
else hi = mid;
|
|
430
|
-
}
|
|
431
|
-
out.push({ t: (lo + hi) / 2, before: slo, after: shi });
|
|
432
|
-
}
|
|
433
|
-
return out;
|
|
434
|
-
}
|
|
435
|
-
function criticalPoints(f) {
|
|
436
|
-
if (f.length < 3) return [];
|
|
437
|
-
const n = f.length - 1;
|
|
438
|
-
const d = f.slice(1).map((v, i) => n * (v - f[i]));
|
|
439
|
-
let A = 0, B, C;
|
|
440
|
-
if (d.length === 2) {
|
|
441
|
-
B = d[1] - d[0];
|
|
442
|
-
C = d[0];
|
|
443
|
-
} else {
|
|
444
|
-
A = d[0] - 2 * d[1] + d[2];
|
|
445
|
-
B = 2 * (d[1] - d[0]);
|
|
446
|
-
C = d[0];
|
|
447
|
-
}
|
|
448
|
-
const out = [];
|
|
449
|
-
const scale = Math.max(Math.abs(A), Math.abs(B), Math.abs(C)) || 1;
|
|
450
|
-
if (Math.abs(A) / scale < 1e-12) {
|
|
451
|
-
if (Math.abs(B) / scale > 1e-12) out.push(-C / B);
|
|
452
|
-
} else {
|
|
453
|
-
const disc = B * B - 4 * A * C;
|
|
454
|
-
if (disc >= 0) {
|
|
455
|
-
const q = -0.5 * (B + Math.sign(B || 1) * Math.sqrt(disc));
|
|
456
|
-
out.push(q / A);
|
|
457
|
-
if (q !== 0) out.push(C / q);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
return out.filter((t) => t > 0 && t < 1).sort((x, y) => x - y);
|
|
461
|
-
}
|
|
462
|
-
function endSide(f, fromStart) {
|
|
463
|
-
const n = f.length;
|
|
464
|
-
for (let i = 1; i < n; i++) {
|
|
465
|
-
const v = fromStart ? f[i] : f[n - 1 - i];
|
|
466
|
-
if (v !== 0) return Math.sign(v);
|
|
467
|
-
}
|
|
468
|
-
return 0;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
592
|
// src/booleans/cutCrossings.ts
|
|
472
593
|
function cutPosition(cut, k, pt) {
|
|
473
594
|
const a = cut[k], b = cut[k + 1];
|
|
@@ -508,144 +629,6 @@ function crossingsOnSegment(boundaries, cut, k, out) {
|
|
|
508
629
|
});
|
|
509
630
|
}
|
|
510
631
|
|
|
511
|
-
// src/booleans/contours.ts
|
|
512
|
-
function parseContours(path) {
|
|
513
|
-
const fillsOpen = path.kind === "rect" || fillsOpenSubpaths(path);
|
|
514
|
-
return parseSubpaths(path).filter((s) => s.closed || fillsOpen).map((s) => s.segs);
|
|
515
|
-
}
|
|
516
|
-
function parseSubpaths(path, implicitClose = true) {
|
|
517
|
-
if (path.kind === "rect") {
|
|
518
|
-
const { x, y, width: w, height: h } = path;
|
|
519
|
-
const p = [{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }];
|
|
520
|
-
return [{ segs: p.map((q, i) => [q, p[(i + 1) % 4]]), closed: true }];
|
|
521
|
-
}
|
|
522
|
-
const out = [];
|
|
523
|
-
let cur = [];
|
|
524
|
-
let start = { x: 0, y: 0 };
|
|
525
|
-
let pen = start;
|
|
526
|
-
const finish = (closed) => {
|
|
527
|
-
if (cur.length > 0 && (closed || implicitClose) && (pen.x !== start.x || pen.y !== start.y)) {
|
|
528
|
-
cur.push([pen, start]);
|
|
529
|
-
}
|
|
530
|
-
if (cur.length > 0) out.push({ segs: cur, closed });
|
|
531
|
-
cur = [];
|
|
532
|
-
pen = start;
|
|
533
|
-
};
|
|
534
|
-
const c = path.coords;
|
|
535
|
-
let k = 0;
|
|
536
|
-
for (const cmd of path.commands) {
|
|
537
|
-
if (cmd === PATH_M) {
|
|
538
|
-
finish(false);
|
|
539
|
-
start = pen = { x: c[k], y: c[k + 1] };
|
|
540
|
-
k += 2;
|
|
541
|
-
} else if (cmd === PATH_L) {
|
|
542
|
-
const e = { x: c[k], y: c[k + 1] };
|
|
543
|
-
if (e.x !== pen.x || e.y !== pen.y) cur.push([pen, e]);
|
|
544
|
-
pen = e;
|
|
545
|
-
k += 2;
|
|
546
|
-
} else if (cmd === PATH_Q) {
|
|
547
|
-
const e = { x: c[k + 2], y: c[k + 3] };
|
|
548
|
-
cur.push([pen, { x: c[k], y: c[k + 1] }, e]);
|
|
549
|
-
pen = e;
|
|
550
|
-
k += 4;
|
|
551
|
-
} else if (cmd === PATH_C) {
|
|
552
|
-
const e = { x: c[k + 4], y: c[k + 5] };
|
|
553
|
-
cur.push([pen, { x: c[k], y: c[k + 1] }, { x: c[k + 2], y: c[k + 3] }, e]);
|
|
554
|
-
pen = e;
|
|
555
|
-
k += 6;
|
|
556
|
-
} else if (cmd === PATH_Z) {
|
|
557
|
-
finish(true);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
finish(false);
|
|
561
|
-
return out;
|
|
562
|
-
}
|
|
563
|
-
function contoursIntersect(contours, tol) {
|
|
564
|
-
const edges = [];
|
|
565
|
-
contours.forEach((contour, ci) => {
|
|
566
|
-
const pts = [];
|
|
567
|
-
for (const s of contour) {
|
|
568
|
-
let n = 1;
|
|
569
|
-
if (s.length > 2) {
|
|
570
|
-
let len = 0;
|
|
571
|
-
for (let i = 0; i + 1 < s.length; i++) len += Math.hypot(s[i + 1].x - s[i].x, s[i + 1].y - s[i].y);
|
|
572
|
-
n = Math.min(64, Math.max(2, Math.ceil(Math.sqrt(len / Math.max(tol, 1e-3)))));
|
|
573
|
-
}
|
|
574
|
-
for (let i = 0; i < n; i++) pts.push(evalSeg(s, i / n));
|
|
575
|
-
}
|
|
576
|
-
for (let i = 0; i < pts.length; i++) {
|
|
577
|
-
const p = pts[i], q = pts[(i + 1) % pts.length];
|
|
578
|
-
edges.push({ x0: p.x, y0: p.y, x1: q.x, y1: q.y, c: ci, i, n: pts.length });
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
for (let i = 0; i < edges.length; i++) {
|
|
582
|
-
const e = edges[i];
|
|
583
|
-
for (let j = i + 1; j < edges.length; j++) {
|
|
584
|
-
const f = edges[j];
|
|
585
|
-
if (e.c === f.c && (j - i === 1 || i === 0 && f.i === f.n - 1)) continue;
|
|
586
|
-
if (Math.max(e.x0, e.x1) < Math.min(f.x0, f.x1) || Math.max(f.x0, f.x1) < Math.min(e.x0, e.x1)) continue;
|
|
587
|
-
if (Math.max(e.y0, e.y1) < Math.min(f.y0, f.y1) || Math.max(f.y0, f.y1) < Math.min(e.y0, e.y1)) continue;
|
|
588
|
-
if (segmentsCross(e.x0, e.y0, e.x1, e.y1, f.x0, f.y0, f.x1, f.y1)) return true;
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
return false;
|
|
592
|
-
}
|
|
593
|
-
function windingAt(q, contours) {
|
|
594
|
-
const dir = { x: 1, y: 0 };
|
|
595
|
-
let w = 0;
|
|
596
|
-
for (const contour of contours) {
|
|
597
|
-
for (const s of contour) {
|
|
598
|
-
for (const c of signChanges(sideCoeffs(s, q, dir), true)) {
|
|
599
|
-
if (evalSeg(s, c.t).x > q.x) w += c.after > 0 ? 1 : -1;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
return w;
|
|
604
|
-
}
|
|
605
|
-
function extentOf(contours) {
|
|
606
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
607
|
-
for (const c of contours) for (const s of c) for (const p of s) {
|
|
608
|
-
minX = Math.min(minX, p.x);
|
|
609
|
-
maxX = Math.max(maxX, p.x);
|
|
610
|
-
minY = Math.min(minY, p.y);
|
|
611
|
-
maxY = Math.max(maxY, p.y);
|
|
612
|
-
}
|
|
613
|
-
return Math.hypot(maxX - minX, maxY - minY);
|
|
614
|
-
}
|
|
615
|
-
function negligibleArea(contours) {
|
|
616
|
-
const extent = extentOf(contours);
|
|
617
|
-
return extent * extent * EPS * EPS;
|
|
618
|
-
}
|
|
619
|
-
function orientBoundaries(contours, fillRule) {
|
|
620
|
-
const filled = (q) => {
|
|
621
|
-
const w = windingAt(q, contours);
|
|
622
|
-
return fillRule === "evenodd" ? (w & 1) !== 0 : w !== 0;
|
|
623
|
-
};
|
|
624
|
-
const eps = Math.max(extentOf(contours) * 1e-6, 1e-9);
|
|
625
|
-
const out = [];
|
|
626
|
-
for (const contour of contours) {
|
|
627
|
-
let probe = contour[0];
|
|
628
|
-
let best = -1;
|
|
629
|
-
for (const s of contour) {
|
|
630
|
-
const len = Math.hypot(s[s.length - 1].x - s[0].x, s[s.length - 1].y - s[0].y);
|
|
631
|
-
if (len > best) {
|
|
632
|
-
best = len;
|
|
633
|
-
probe = s;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
const p = evalSeg(probe, 0.5);
|
|
637
|
-
let tan = derivSeg(probe, 0.5);
|
|
638
|
-
const tl = Math.hypot(tan.x, tan.y);
|
|
639
|
-
if (tl === 0) continue;
|
|
640
|
-
tan = { x: tan.x / tl, y: tan.y / tl };
|
|
641
|
-
const left = filled({ x: p.x - tan.y * eps, y: p.y + tan.x * eps });
|
|
642
|
-
const right = filled({ x: p.x + tan.y * eps, y: p.y - tan.x * eps });
|
|
643
|
-
if (left && !right) out.push(contour);
|
|
644
|
-
else if (right && !left) out.push(reverseContour(contour));
|
|
645
|
-
}
|
|
646
|
-
return out;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
632
|
// src/booleans/cutPolyline.ts
|
|
650
633
|
function distinctPoints(cut) {
|
|
651
634
|
const out = [];
|
|
@@ -839,36 +822,6 @@ function arcBetween(contour, from, to, whole) {
|
|
|
839
822
|
return out;
|
|
840
823
|
}
|
|
841
824
|
|
|
842
|
-
// src/booleans/cutRegions.ts
|
|
843
|
-
function assembleRegions(outers, holes) {
|
|
844
|
-
const regions = outers.map((o) => ({ loops: [o], ring: sampleContour(o), area: signedArea(o) }));
|
|
845
|
-
for (const h of holes) {
|
|
846
|
-
const hp = evalSeg(h[0], 0.5);
|
|
847
|
-
let best = null;
|
|
848
|
-
for (const r of regions) {
|
|
849
|
-
if (pointInPolygon(r.ring, hp.x, hp.y) && (!best || r.area < best.area)) best = r;
|
|
850
|
-
}
|
|
851
|
-
best?.loops.push(h);
|
|
852
|
-
}
|
|
853
|
-
return regions.map((r) => r.loops);
|
|
854
|
-
}
|
|
855
|
-
var regionArea = (r) => r.reduce((s, c) => s + signedArea(c), 0);
|
|
856
|
-
var toPath = (loops, fillRule) => writePath(loops.map((segs) => ({ segs, closed: true })), fillRule);
|
|
857
|
-
function writePath(subpaths, fillRule) {
|
|
858
|
-
const cmds = [];
|
|
859
|
-
const xs = [];
|
|
860
|
-
for (const { segs, closed } of subpaths) {
|
|
861
|
-
cmds.push(PATH_M);
|
|
862
|
-
xs.push(segs[0][0].x, segs[0][0].y);
|
|
863
|
-
for (const s of segs) {
|
|
864
|
-
cmds.push(s.length === 2 ? PATH_L : s.length === 3 ? PATH_Q : PATH_C);
|
|
865
|
-
for (let i = 1; i < s.length; i++) xs.push(s[i].x, s[i].y);
|
|
866
|
-
}
|
|
867
|
-
if (closed) cmds.push(PATH_Z);
|
|
868
|
-
}
|
|
869
|
-
return { kind: "polygon", commands: new Uint8Array(cmds), coords: new Float32Array(xs), fillRule };
|
|
870
|
-
}
|
|
871
|
-
|
|
872
825
|
// src/booleans/splitBySegment.ts
|
|
873
826
|
function splitPathBySegment(path, a, b, opts = {}) {
|
|
874
827
|
return splitPathByPolyline(path, [a, b], opts);
|
|
@@ -994,6 +947,45 @@ function snipStations(sub, cut) {
|
|
|
994
947
|
return groups.filter((g) => g.length % 2 === 1).map((g) => g[0]).filter((st) => sub.closed || !same(st.pt, first) && !same(st.pt, end));
|
|
995
948
|
}
|
|
996
949
|
|
|
997
|
-
|
|
950
|
+
// src/booleans/snipAtCrossings.ts
|
|
951
|
+
function snipPathsAtCrossings(paths, opts = {}) {
|
|
952
|
+
const tol = opts.flattenTolerance ?? 0.05;
|
|
953
|
+
const cuts = paths.map((p) => cutLinesOf(p, tol));
|
|
954
|
+
return paths.map((path, i) => {
|
|
955
|
+
let pieces = [path];
|
|
956
|
+
let cut = false;
|
|
957
|
+
cuts.forEach((lines, j) => {
|
|
958
|
+
if (j === i) return;
|
|
959
|
+
for (const line of lines) {
|
|
960
|
+
pieces = pieces.flatMap((piece) => {
|
|
961
|
+
const snipped = snipPathByPolyline(piece, line);
|
|
962
|
+
if (!snipped) return [piece];
|
|
963
|
+
cut = true;
|
|
964
|
+
return snipped;
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
return cut ? pieces.map((piece) => piece.kind === "polygon" ? keepCorners([path], piece) : piece) : null;
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
function cutLinesOf(path, tol) {
|
|
972
|
+
return parseSubpaths(path, false).map(({ segs, closed }) => {
|
|
973
|
+
const flat = [segs[0][0].x, segs[0][0].y];
|
|
974
|
+
for (const s of segs) {
|
|
975
|
+
if (s.length === 2) flat.push(s[1].x, s[1].y);
|
|
976
|
+
else if (s.length === 3) flattenQuadratic(s[0].x, s[0].y, s[1].x, s[1].y, s[2].x, s[2].y, tol, flat);
|
|
977
|
+
else flattenCubic(s[0].x, s[0].y, s[1].x, s[1].y, s[2].x, s[2].y, s[3].x, s[3].y, tol, flat);
|
|
978
|
+
}
|
|
979
|
+
const pts = [];
|
|
980
|
+
for (let k = 0; k < flat.length; k += 2) pts.push({ x: flat[k], y: flat[k + 1] });
|
|
981
|
+
if (closed) {
|
|
982
|
+
const end = pts[pts.length - 1];
|
|
983
|
+
if (end.x !== pts[0].x || end.y !== pts[0].y) pts.push({ ...pts[0] });
|
|
984
|
+
}
|
|
985
|
+
return pts;
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export { multiPolygonToPath, pathCrop, pathCropPieces, pathDivide, pathDividePieces, pathExclude, pathFacePieces, pathIntersect, pathMerge, pathSubtract, pathToMultiPolygon, pathTrim, pathUnion, snipPathByPolyline, snipPathsAtCrossings, splitPathByPolyline, splitPathBySegment, trianglesToPath };
|
|
998
990
|
//# sourceMappingURL=index.js.map
|
|
999
991
|
//# sourceMappingURL=index.js.map
|