@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.
Files changed (55) hide show
  1. package/README.md +140 -16
  2. package/dist/3d/index.d.ts +29 -2
  3. package/dist/3d/index.js +49 -1
  4. package/dist/3d/index.js.map +1 -1
  5. package/dist/booleans/index.d.ts +34 -10
  6. package/dist/booleans/index.js +344 -352
  7. package/dist/booleans/index.js.map +1 -1
  8. package/dist/{chunk-MDT7F32A.js → chunk-6XSJY3TC.js} +10 -39
  9. package/dist/chunk-6XSJY3TC.js.map +1 -0
  10. package/dist/chunk-7KLJZFBY.js +573 -0
  11. package/dist/chunk-7KLJZFBY.js.map +1 -0
  12. package/dist/chunk-B53BERKG.js +386 -0
  13. package/dist/chunk-B53BERKG.js.map +1 -0
  14. package/dist/chunk-CE7IY7TB.js +104 -0
  15. package/dist/chunk-CE7IY7TB.js.map +1 -0
  16. package/dist/chunk-HGF5OVEN.js +129 -0
  17. package/dist/chunk-HGF5OVEN.js.map +1 -0
  18. package/dist/chunk-J2O2B54M.js +800 -0
  19. package/dist/chunk-J2O2B54M.js.map +1 -0
  20. package/dist/chunk-Q66NA2FU.js +174 -0
  21. package/dist/chunk-Q66NA2FU.js.map +1 -0
  22. package/dist/chunk-SASMXDBE.js +443 -0
  23. package/dist/chunk-SASMXDBE.js.map +1 -0
  24. package/dist/chunk-UI3FUWXM.js +533 -0
  25. package/dist/chunk-UI3FUWXM.js.map +1 -0
  26. package/dist/curvature-PkoLduGc.d.ts +454 -0
  27. package/dist/curves/index.d.ts +14 -5
  28. package/dist/curves/index.js +33 -111
  29. package/dist/curves/index.js.map +1 -1
  30. package/dist/index.d.ts +1697 -205
  31. package/dist/index.js +4729 -1308
  32. package/dist/index.js.map +1 -1
  33. package/dist/nd/index.d.ts +91 -27
  34. package/dist/nd/index.js +282 -0
  35. package/dist/nd/index.js.map +1 -1
  36. package/dist/path-BW8d_2RY.d.ts +157 -0
  37. package/dist/portCurve-C5HDWuuV.d.ts +30 -0
  38. package/dist/tessellate/index.d.ts +18 -4
  39. package/dist/tessellate/index.js +21 -277
  40. package/dist/tessellate/index.js.map +1 -1
  41. package/dist/warp/index.d.ts +252 -0
  42. package/dist/warp/index.js +1316 -0
  43. package/dist/warp/index.js.map +1 -0
  44. package/package.json +5 -1
  45. package/dist/chunk-MDT7F32A.js.map +0 -1
  46. package/dist/chunk-OXH3NOXC.js +0 -62
  47. package/dist/chunk-OXH3NOXC.js.map +0 -1
  48. package/dist/chunk-URQMDR6Q.js +0 -80
  49. package/dist/chunk-URQMDR6Q.js.map +0 -1
  50. package/dist/chunk-WQMYOCFB.js +0 -167
  51. package/dist/chunk-WQMYOCFB.js.map +0 -1
  52. package/dist/chunk-XS6ERH3X.js +0 -34
  53. package/dist/chunk-XS6ERH3X.js.map +0 -1
  54. package/dist/cubicMath-CFXbq6vp.d.ts +0 -116
  55. package/dist/path-Bf9KeVxC.d.ts +0 -44
@@ -1,11 +1,13 @@
1
- import { elevateQuadraticToCubic, splitQuadraticAt, splitCubicAt } from '../chunk-URQMDR6Q.js';
2
- import { DEFAULT_FLATTEN_TOLERANCE, fillsOpenSubpaths, flattenCubic, segmentsCross, pointInPolygon } from '../chunk-WQMYOCFB.js';
3
- import { EPS } from '../chunk-755Y4YZS.js';
4
- import { PATH_Z, PATH_Q, PATH_C, PATH_L, PATH_M } from '../chunk-OXH3NOXC.js';
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(path, opts = {}) {
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 multiPolygonToPath(polygonClipping.union(head, ...rest));
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 multiPolygonToPath(polygonClipping.intersection(head, ...rest));
500
+ return toPath2(polygonClipping.intersection(head, ...rest), paths);
229
501
  }
230
502
  function pathSubtract(a, b) {
231
- return multiPolygonToPath(polygonClipping.difference(pathToMultiPolygon(a), pathToMultiPolygon(b)));
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 multiPolygonToPath(polygonClipping.xor(head, ...rest));
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 pathDividePieces(...paths) {
246
- if (paths.length === 0) return [];
247
- if (paths.length === 1) {
248
- return [{ path: multiPolygonToPath(pathToMultiPolygon(paths[0])), sources: [0] }];
249
- }
250
- const mps = paths.map((p) => pathToMultiPolygon(p));
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 < n; i++) {
261
- if (mask & 1 << i) sources.push(i);
262
- (mask & 1 << i ? inside : outside).push(mps[i]);
263
- }
264
- const [insideHead, ...insideRest] = inside;
265
- let region = insideRest.length === 0 ? insideHead : polygonClipping.intersection(insideHead, ...insideRest);
266
- if (region.length === 0) continue;
267
- if (outside.length > 0) {
268
- const [outHead, ...outRest] = outside;
269
- const outsideUnion = polygonClipping.union(outHead, ...outRest);
270
- region = polygonClipping.difference(region, outsideUnion);
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 out;
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: multiPolygonToPath(shown), sources: [i] });
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: multiPolygonToPath(clipped), sources: [i] });
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
- export { multiPolygonToPath, pathCrop, pathCropPieces, pathDivide, pathDividePieces, pathExclude, pathIntersect, pathMerge, pathSubtract, pathToMultiPolygon, pathTrim, pathUnion, snipPathByPolyline, splitPathByPolyline, splitPathBySegment, trianglesToPath };
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