@weasel-js/geom 1.10.1 → 1.11.1

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 +9 -1
  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 +18 -11
  6. package/dist/booleans/index.js +432 -352
  7. package/dist/booleans/index.js.map +1 -1
  8. package/dist/chunk-4Y67BZL5.js +573 -0
  9. package/dist/chunk-4Y67BZL5.js.map +1 -0
  10. package/dist/chunk-6FGQML7D.js +152 -0
  11. package/dist/chunk-6FGQML7D.js.map +1 -0
  12. package/dist/{chunk-7WFFCHRL.js → chunk-7F66PQCJ.js} +6 -37
  13. package/dist/chunk-7F66PQCJ.js.map +1 -0
  14. package/dist/chunk-B53BERKG.js +386 -0
  15. package/dist/chunk-B53BERKG.js.map +1 -0
  16. package/dist/chunk-CE7IY7TB.js +104 -0
  17. package/dist/chunk-CE7IY7TB.js.map +1 -0
  18. package/dist/chunk-EAKE3N7W.js +804 -0
  19. package/dist/chunk-EAKE3N7W.js.map +1 -0
  20. package/dist/chunk-LRVPK7HI.js +491 -0
  21. package/dist/chunk-LRVPK7HI.js.map +1 -0
  22. package/dist/chunk-Q66NA2FU.js +174 -0
  23. package/dist/chunk-Q66NA2FU.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 +1511 -283
  31. package/dist/index.js +5755 -2674
  32. package/dist/index.js.map +1 -1
  33. package/dist/nd/index.d.ts +92 -27
  34. package/dist/nd/index.js +297 -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 +15 -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 +254 -0
  42. package/dist/warp/index.js +1448 -0
  43. package/dist/warp/index.js.map +1 -0
  44. package/package.json +5 -1
  45. package/dist/chunk-7WFFCHRL.js.map +0 -1
  46. package/dist/chunk-IDXY2NCA.js +0 -177
  47. package/dist/chunk-IDXY2NCA.js.map +0 -1
  48. package/dist/chunk-NJ3IGAKR.js +0 -16
  49. package/dist/chunk-NJ3IGAKR.js.map +0 -1
  50. package/dist/chunk-OXH3NOXC.js +0 -62
  51. package/dist/chunk-OXH3NOXC.js.map +0 -1
  52. package/dist/chunk-URQMDR6Q.js +0 -80
  53. package/dist/chunk-URQMDR6Q.js.map +0 -1
  54. package/dist/cubicMath-BW-HJrCE.d.ts +0 -16
  55. package/dist/path-ljHxobyu.d.ts +0 -44
@@ -1,11 +1,13 @@
1
- import { elevateQuadraticToCubic, splitQuadraticAt, splitCubicAt } from '../chunk-URQMDR6Q.js';
2
- import { DEFAULT_FLATTEN_TOLERANCE, fillsOpenSubpaths, flattenCubic, flattenQuadratic, segmentsCross, pointInPolygon } from '../chunk-IDXY2NCA.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-4Y67BZL5.js';
2
+ import '../chunk-CE7IY7TB.js';
3
+ import { resolveCorners, DEFAULT_FLATTEN_TOLERANCE, fillsOpenSubpaths, flattenCubic, flattenQuadratic, pointInPolygon } from '../chunk-EAKE3N7W.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,158 +570,150 @@ 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
  }
321
- function trianglesToPath(vertices, indices) {
322
- const triangles = [];
323
- for (let i = 0; i + 2 < indices.length; i += 3) {
324
- const a = indices[i] * 2, b = indices[i + 1] * 2, c = indices[i + 2] * 2;
325
- const ax = vertices[a], ay = vertices[a + 1];
326
- const bx = vertices[b], by = vertices[b + 1];
327
- const cx = vertices[c], cy = vertices[c + 1];
328
- if ((bx - ax) * (cy - ay) - (cx - ax) * (by - ay) === 0) continue;
329
- triangles.push([[[[ax, ay], [bx, by], [cx, cy], [ax, ay]]]]);
330
- }
331
- if (triangles.length === 0) return multiPolygonToPath([]);
332
- const [head, ...rest] = triangles;
333
- return multiPolygonToPath(polygonClipping.union(head, ...rest));
334
- }
335
577
 
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]]];
578
+ // src/booleans/meshJunctions.ts
579
+ var ON_EDGE = 2 ** -20;
580
+ function junctionRings(vertices, triangles) {
581
+ const n = vertices.length / 2;
582
+ let scale = 0, minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
583
+ for (let i = 0; i < n; i++) {
584
+ const x = vertices[i * 2], y = vertices[i * 2 + 1];
585
+ scale = Math.max(scale, Math.abs(x), Math.abs(y));
586
+ minX = Math.min(minX, x);
587
+ minY = Math.min(minY, y);
588
+ maxX = Math.max(maxX, x);
589
+ maxY = Math.max(maxY, y);
365
590
  }
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)];
591
+ const eps = scale * ON_EDGE;
592
+ const cell = Math.max(maxX - minX, maxY - minY, eps) / Math.max(1, Math.sqrt(n));
593
+ const grid = /* @__PURE__ */ new Map();
594
+ const key = (cx, cy) => `${cx},${cy}`;
595
+ for (const i of new Set(triangles)) {
596
+ const k = key(Math.floor((vertices[i * 2] - minX) / cell), Math.floor((vertices[i * 2 + 1] - minY) / cell));
597
+ const bucket = grid.get(k);
598
+ if (bucket) bucket.push(i);
599
+ else grid.set(k, [i]);
369
600
  }
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);
601
+ const onEdge = (p, q) => {
602
+ const px = vertices[p * 2], py = vertices[p * 2 + 1];
603
+ const dx = vertices[q * 2] - px, dy = vertices[q * 2 + 1] - py;
604
+ const len2 = dx * dx + dy * dy;
605
+ if (len2 === 0) return [];
606
+ const len = Math.sqrt(len2);
607
+ const hits = [];
608
+ const x0 = Math.floor((Math.min(px, px + dx) - eps - minX) / cell), x1 = Math.floor((Math.max(px, px + dx) + eps - minX) / cell);
609
+ const y0 = Math.floor((Math.min(py, py + dy) - eps - minY) / cell), y1 = Math.floor((Math.max(py, py + dy) + eps - minY) / cell);
610
+ for (let cx = x0; cx <= x1; cx++) {
611
+ for (let cy = y0; cy <= y1; cy++) {
612
+ for (const i of grid.get(key(cx, cy)) ?? []) {
613
+ const vx = vertices[i * 2] - px, vy = vertices[i * 2 + 1] - py;
614
+ const t = (vx * dx + vy * dy) / len2;
615
+ if (t * len <= eps || (1 - t) * len <= eps) continue;
616
+ if (Math.abs(vx * dy - vy * dx) / len <= eps) hits.push({ i, t });
617
+ }
618
+ }
392
619
  }
620
+ return hits.sort((a, b) => a.t - b.t).map((h) => h.i);
621
+ };
622
+ const rings = [];
623
+ for (let k = 0; k + 2 < triangles.length; k += 3) {
624
+ const ring = [];
625
+ for (let e = 0; e < 3; e++) {
626
+ const p = triangles[k + e], q = triangles[k + (e + 1) % 3];
627
+ for (const i of [p, ...onEdge(p, q)]) ring.push([vertices[i * 2], vertices[i * 2 + 1]]);
628
+ }
629
+ ring.push(ring[0]);
630
+ rings.push(ring);
393
631
  }
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];
632
+ return rings;
413
633
  }
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;
634
+
635
+ // src/booleans/meshOutline.ts
636
+ function meshOutline(rings) {
637
+ const points = /* @__PURE__ */ new Map();
638
+ const edges = /* @__PURE__ */ new Map();
639
+ const keyOf = (p) => {
640
+ const k = `${p[0]},${p[1]}`;
641
+ if (!points.has(k)) points.set(k, p);
642
+ return k;
643
+ };
644
+ for (const ring of rings) {
645
+ const keys = ring.slice(0, -1).map(keyOf);
646
+ if (ringArea(ring) < 0) keys.reverse();
647
+ for (let i = 0; i < keys.length; i++) {
648
+ const a = keys[i], b = keys[(i + 1) % keys.length];
649
+ if (a === b) continue;
650
+ const back = `${b}|${a}`;
651
+ const n = edges.get(back);
652
+ if (n) {
653
+ if (n === 1) edges.delete(back);
654
+ else edges.set(back, n - 1);
655
+ } else {
656
+ const fwd = `${a}|${b}`;
657
+ edges.set(fwd, (edges.get(fwd) ?? 0) + 1);
658
+ }
430
659
  }
431
- out.push({ t: (lo + hi) / 2, before: slo, after: shi });
432
660
  }
433
- return out;
661
+ const loops = walkLoops(edges).map((loop) => loop.map((k) => points.get(k)));
662
+ return loops.length === 0 ? null : bridge(loops);
434
663
  }
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];
664
+ function walkLoops(edges) {
665
+ const out = /* @__PURE__ */ new Map();
666
+ for (const [edge, n] of edges) {
667
+ const [a, b] = edge.split("|");
668
+ let list = out.get(a);
669
+ if (!list) out.set(a, list = []);
670
+ for (let i = 0; i < n; i++) list.push(b);
447
671
  }
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);
672
+ const loops = [];
673
+ for (const start of out.keys()) {
674
+ while ((out.get(start)?.length ?? 0) > 0) {
675
+ const loop = [start];
676
+ let at = out.get(start).pop();
677
+ while (at !== start) {
678
+ loop.push(at);
679
+ const next = out.get(at);
680
+ at = next.pop();
681
+ }
682
+ loops.push(loop);
458
683
  }
459
684
  }
460
- return out.filter((t) => t > 0 && t < 1).sort((x, y) => x - y);
685
+ return loops;
686
+ }
687
+ function bridge(loops) {
688
+ const entered = loops.map((loop) => {
689
+ let k = 0;
690
+ for (let i = 1; i < loop.length; i++) if (loop[i][0] < loop[k][0] || loop[i][0] === loop[k][0] && loop[i][1] < loop[k][1]) k = i;
691
+ return [...loop.slice(k), ...loop.slice(0, k)];
692
+ });
693
+ entered.sort((a, b) => a[0][0] - b[0][0] || a[0][1] - b[0][1]);
694
+ const ring = [];
695
+ for (const loop of entered) ring.push(...loop, loop[0]);
696
+ for (let i = entered.length - 2; i >= 0; i--) ring.push(entered[i][0]);
697
+ return ring;
698
+ }
699
+ function ringArea(ring) {
700
+ let a = 0;
701
+ for (let i = 0; i + 1 < ring.length; i++) a += ring[i][0] * ring[i + 1][1] - ring[i + 1][0] * ring[i][1];
702
+ return a;
461
703
  }
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);
704
+
705
+ // src/booleans/trianglesToPath.ts
706
+ function trianglesToPath(vertices, indices) {
707
+ const kept = [];
708
+ for (let i = 0; i + 2 < indices.length; i += 3) {
709
+ const a = indices[i] * 2, b = indices[i + 1] * 2, c = indices[i + 2] * 2;
710
+ const ax = vertices[a], ay = vertices[a + 1];
711
+ if ((vertices[b] - ax) * (vertices[c + 1] - ay) - (vertices[c] - ax) * (vertices[b + 1] - ay) === 0) continue;
712
+ kept.push(indices[i], indices[i + 1], indices[i + 2]);
467
713
  }
468
- return 0;
714
+ if (kept.length === 0) return multiPolygonToPath([]);
715
+ const outline = meshOutline(junctionRings(vertices, kept));
716
+ return multiPolygonToPath(outline ? polygonClipping.union([[outline]]) : []);
469
717
  }
470
718
 
471
719
  // src/booleans/cutCrossings.ts
@@ -508,144 +756,6 @@ function crossingsOnSegment(boundaries, cut, k, out) {
508
756
  });
509
757
  }
510
758
 
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
759
  // src/booleans/cutPolyline.ts
650
760
  function distinctPoints(cut) {
651
761
  const out = [];
@@ -839,36 +949,6 @@ function arcBetween(contour, from, to, whole) {
839
949
  return out;
840
950
  }
841
951
 
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
952
  // src/booleans/splitBySegment.ts
873
953
  function splitPathBySegment(path, a, b, opts = {}) {
874
954
  return splitPathByPolyline(path, [a, b], opts);
@@ -1012,7 +1092,7 @@ function snipPathsAtCrossings(paths, opts = {}) {
1012
1092
  });
1013
1093
  }
1014
1094
  });
1015
- return cut ? pieces : null;
1095
+ return cut ? pieces.map((piece) => piece.kind === "polygon" ? keepCorners([path], piece) : piece) : null;
1016
1096
  });
1017
1097
  }
1018
1098
  function cutLinesOf(path, tol) {
@@ -1033,6 +1113,6 @@ function cutLinesOf(path, tol) {
1033
1113
  });
1034
1114
  }
1035
1115
 
1036
- export { multiPolygonToPath, pathCrop, pathCropPieces, pathDivide, pathDividePieces, pathExclude, pathIntersect, pathMerge, pathSubtract, pathToMultiPolygon, pathTrim, pathUnion, snipPathByPolyline, snipPathsAtCrossings, splitPathByPolyline, splitPathBySegment, trianglesToPath };
1116
+ export { multiPolygonToPath, pathCrop, pathCropPieces, pathDivide, pathDividePieces, pathExclude, pathFacePieces, pathIntersect, pathMerge, pathSubtract, pathToMultiPolygon, pathTrim, pathUnion, snipPathByPolyline, snipPathsAtCrossings, splitPathByPolyline, splitPathBySegment, trianglesToPath };
1037
1117
  //# sourceMappingURL=index.js.map
1038
1118
  //# sourceMappingURL=index.js.map