pattapatta 0.2.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 (74) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +98 -0
  4. package/dist/circle-BDbKfrdR.d.ts +12 -0
  5. package/dist/circlePacking/index.d.ts +78 -0
  6. package/dist/circlePacking/index.js +381 -0
  7. package/dist/circlePacking/index.js.map +1 -0
  8. package/dist/cli.js +365 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/construction/index.d.ts +26 -0
  11. package/dist/construction/index.js +102 -0
  12. package/dist/construction/index.js.map +1 -0
  13. package/dist/contour/index.d.ts +20 -0
  14. package/dist/contour/index.js +118 -0
  15. package/dist/contour/index.js.map +1 -0
  16. package/dist/conversion/index.d.ts +31 -0
  17. package/dist/conversion/index.js +99 -0
  18. package/dist/conversion/index.js.map +1 -0
  19. package/dist/group-BNjVVYSQ.d.ts +13 -0
  20. package/dist/hatch/index.d.ts +41 -0
  21. package/dist/hatch/index.js +253 -0
  22. package/dist/hatch/index.js.map +1 -0
  23. package/dist/hull/index.d.ts +16 -0
  24. package/dist/hull/index.js +97 -0
  25. package/dist/hull/index.js.map +1 -0
  26. package/dist/index.d.ts +67 -0
  27. package/dist/index.js +3933 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/meshing/index.d.ts +125 -0
  30. package/dist/meshing/index.js +1333 -0
  31. package/dist/meshing/index.js.map +1 -0
  32. package/dist/morphology/index.d.ts +58 -0
  33. package/dist/morphology/index.js +268 -0
  34. package/dist/morphology/index.js.map +1 -0
  35. package/dist/optimisation/index.d.ts +43 -0
  36. package/dist/optimisation/index.js +400 -0
  37. package/dist/optimisation/index.js.map +1 -0
  38. package/dist/path-0RjhYjJs.d.ts +21 -0
  39. package/dist/pointSet/index.d.ts +24 -0
  40. package/dist/pointSet/index.js +139 -0
  41. package/dist/pointSet/index.js.map +1 -0
  42. package/dist/polygonisation/index.d.ts +44 -0
  43. package/dist/polygonisation/index.js +310 -0
  44. package/dist/polygonisation/index.js.map +1 -0
  45. package/dist/predicates/index.d.ts +35 -0
  46. package/dist/predicates/index.js +159 -0
  47. package/dist/predicates/index.js.map +1 -0
  48. package/dist/processing/index.d.ts +54 -0
  49. package/dist/processing/index.js +457 -0
  50. package/dist/processing/index.js.map +1 -0
  51. package/dist/segment-BKuIFMKD.d.ts +10 -0
  52. package/dist/segmentSet/index.d.ts +25 -0
  53. package/dist/segmentSet/index.js +109 -0
  54. package/dist/segmentSet/index.js.map +1 -0
  55. package/dist/shapeBoolean/index.d.ts +41 -0
  56. package/dist/shapeBoolean/index.js +211 -0
  57. package/dist/shapeBoolean/index.js.map +1 -0
  58. package/dist/svg/index.d.ts +38 -0
  59. package/dist/svg/index.js +317 -0
  60. package/dist/svg/index.js.map +1 -0
  61. package/dist/tiling/index.d.ts +32 -0
  62. package/dist/tiling/index.js +344 -0
  63. package/dist/tiling/index.js.map +1 -0
  64. package/dist/transformation/index.d.ts +37 -0
  65. package/dist/transformation/index.js +184 -0
  66. package/dist/transformation/index.js.map +1 -0
  67. package/dist/triangulation/index.d.ts +48 -0
  68. package/dist/triangulation/index.js +334 -0
  69. package/dist/triangulation/index.js.map +1 -0
  70. package/dist/vec2-CWyXN1Wj.d.ts +10 -0
  71. package/dist/voronoi/index.d.ts +31 -0
  72. package/dist/voronoi/index.js +211 -0
  73. package/dist/voronoi/index.js.map +1 -0
  74. package/package.json +165 -0
@@ -0,0 +1,1333 @@
1
+ import { FillRule, simplifyPathD, pointInPolygonD, PointInPolygonResult, unionD, isPositiveD, areaD } from 'clipper2-ts';
2
+ import { Delaunay } from 'd3-delaunay';
3
+
4
+ // src/types/vec2.ts
5
+ function vec2(x, y) {
6
+ return { x, y };
7
+ }
8
+ function cloneVec2(v) {
9
+ return { x: v.x, y: v.y };
10
+ }
11
+ function equalsVec2(a, b, eps = 1e-9) {
12
+ return Math.abs(a.x - b.x) <= eps && Math.abs(a.y - b.y) <= eps;
13
+ }
14
+
15
+ // src/types/segment.ts
16
+ function segment(a, b) {
17
+ return { a, b };
18
+ }
19
+
20
+ // src/types/path.ts
21
+ function path(rings, closed = true) {
22
+ return {
23
+ rings: rings.map((ring) => ring.map(cloneVec2)),
24
+ closed
25
+ };
26
+ }
27
+ function polyline(points) {
28
+ return path([points], false);
29
+ }
30
+ function polygon(exterior, holes = []) {
31
+ return path([exterior, ...holes], true);
32
+ }
33
+ function normalizeRing(ring, eps = 1e-9) {
34
+ if (ring.length < 2) return ring.map(cloneVec2);
35
+ const first = ring[0];
36
+ const last = ring[ring.length - 1];
37
+ if (first && last && equalsVec2(first, last, eps)) {
38
+ return ring.slice(0, -1).map(cloneVec2);
39
+ }
40
+ return ring.map(cloneVec2);
41
+ }
42
+ function clonePath(p) {
43
+ return {
44
+ closed: p.closed,
45
+ rings: p.rings.map((ring) => ring.map(cloneVec2))
46
+ };
47
+ }
48
+
49
+ // src/types/group.ts
50
+ function group(paths = []) {
51
+ return { paths: paths.map(clonePath) };
52
+ }
53
+ var DEFAULT_FILL_RULE = FillRule.NonZero;
54
+ function ringToPathD(ring) {
55
+ const pts = [];
56
+ for (const v of normalizeRing(ring)) {
57
+ pts.push({ x: v.x, y: v.y, z: 0 });
58
+ }
59
+ return pts;
60
+ }
61
+ function pathToPathsD(p) {
62
+ if (!p.closed || p.rings.length === 0) {
63
+ throw new Error("Boolean ops require a closed path with at least one ring");
64
+ }
65
+ return p.rings.map((ring) => ringToPathD(ring));
66
+ }
67
+ function pathsDToGroup(paths) {
68
+ if (paths.length === 0) return group([]);
69
+ const outers = [];
70
+ const holes = [];
71
+ for (const pd of paths) {
72
+ const ring = pathDToRing(pd);
73
+ if (ring.length < 3) continue;
74
+ if (isPositiveD(pd)) {
75
+ outers.push({ ring, holes: [] });
76
+ } else {
77
+ holes.push(ring);
78
+ }
79
+ }
80
+ for (const hole of holes) {
81
+ const sample = hole[0];
82
+ if (!sample) continue;
83
+ let assigned = false;
84
+ for (const outer of outers) {
85
+ if (pointInRing(sample, outer.ring)) {
86
+ outer.holes.push(hole);
87
+ assigned = true;
88
+ break;
89
+ }
90
+ }
91
+ if (!assigned) {
92
+ outers.push({ ring: [...hole].reverse(), holes: [] });
93
+ }
94
+ }
95
+ return group(outers.map((o) => path([o.ring, ...o.holes], true)));
96
+ }
97
+ function pathDToRing(pd) {
98
+ return normalizeRing(pd.map((p) => vec2(p.x, p.y)));
99
+ }
100
+ function pointInRing(point, ring) {
101
+ const result = pointInPolygonD(
102
+ { x: point.x, y: point.y, z: 0 },
103
+ ringToPathD(ring)
104
+ );
105
+ return result === PointInPolygonResult.IsInside || result === PointInPolygonResult.IsOn;
106
+ }
107
+ function clipUnion(a, b) {
108
+ return unionD(a, b, DEFAULT_FILL_RULE);
109
+ }
110
+ function pathsArea(paths) {
111
+ let sum = 0;
112
+ for (const p of paths) {
113
+ sum += Math.abs(areaD(p));
114
+ }
115
+ return sum;
116
+ }
117
+
118
+ // src/predicates/index.ts
119
+ function ringArea(ring) {
120
+ if (ring.length < 3) return 0;
121
+ let sum = 0;
122
+ for (let i = 0; i < ring.length; i++) {
123
+ const a = ring[i];
124
+ const b = ring[(i + 1) % ring.length];
125
+ sum += a.x * b.y - b.x * a.y;
126
+ }
127
+ return sum / 2;
128
+ }
129
+ function area(p) {
130
+ if (!p.closed || p.rings.length === 0) return 0;
131
+ try {
132
+ return pathsArea(pathToPathsD(p));
133
+ } catch {
134
+ let total = 0;
135
+ for (let i = 0; i < p.rings.length; i++) {
136
+ const a = Math.abs(ringArea(p.rings[i]));
137
+ total += i === 0 ? a : -a;
138
+ }
139
+ return Math.max(0, total);
140
+ }
141
+ }
142
+ function centroid(p) {
143
+ const ring = p.rings[0];
144
+ if (!ring || ring.length === 0) return vec2(0, 0);
145
+ const a = ringArea(ring);
146
+ if (Math.abs(a) < 1e-12) {
147
+ let sx = 0;
148
+ let sy = 0;
149
+ for (const v of ring) {
150
+ sx += v.x;
151
+ sy += v.y;
152
+ }
153
+ return vec2(sx / ring.length, sy / ring.length);
154
+ }
155
+ let cx = 0;
156
+ let cy = 0;
157
+ for (let i = 0; i < ring.length; i++) {
158
+ const p0 = ring[i];
159
+ const p1 = ring[(i + 1) % ring.length];
160
+ const cross2 = p0.x * p1.y - p1.x * p0.y;
161
+ cx += (p0.x + p1.x) * cross2;
162
+ cy += (p0.y + p1.y) * cross2;
163
+ }
164
+ return vec2(cx / (6 * a), cy / (6 * a));
165
+ }
166
+ function containsPoint(p, point) {
167
+ if (!p.closed || !p.rings[0]) return false;
168
+ if (!pointInRing(point, p.rings[0])) return false;
169
+ for (let i = 1; i < p.rings.length; i++) {
170
+ const hole = p.rings[i];
171
+ if (hole && pointInRing(point, hole)) return false;
172
+ }
173
+ return true;
174
+ }
175
+ function delaunayTriangulationPoints(points) {
176
+ if (points.length < 3) return [];
177
+ const delaunay = Delaunay.from(
178
+ points,
179
+ (d) => d.x,
180
+ (d) => d.y
181
+ );
182
+ const tris = [];
183
+ const { triangles } = delaunay;
184
+ for (let i = 0; i < triangles.length; i += 3) {
185
+ const a = points[triangles[i]];
186
+ const b = points[triangles[i + 1]];
187
+ const c = points[triangles[i + 2]];
188
+ tris.push(polygon([a, b, c]));
189
+ }
190
+ return tris;
191
+ }
192
+
193
+ // src/shapeBoolean/index.ts
194
+ function ensureClosed(p) {
195
+ if (!p.closed) {
196
+ throw new Error("shapeBoolean requires closed paths");
197
+ }
198
+ return p;
199
+ }
200
+ function union(a, b) {
201
+ const result = clipUnion(pathToPathsD(ensureClosed(a)), pathToPathsD(ensureClosed(b)));
202
+ return pathsDToGroup(result);
203
+ }
204
+ function simplify(p, epsilon) {
205
+ if (epsilon <= 0 || p.rings.length === 0) return p;
206
+ const rings = [];
207
+ for (const ring of p.rings) {
208
+ const simplified = simplifyPathD(ringToPathD(ring), epsilon, p.closed);
209
+ const out = pathDToRing(simplified);
210
+ if (out.length >= (p.closed ? 3 : 2)) rings.push(out);
211
+ }
212
+ if (rings.length === 0) return path([], p.closed);
213
+ return path(rings, p.closed);
214
+ }
215
+
216
+ // src/meshing/index.ts
217
+ function edgeKey(a, b) {
218
+ const aKey = `${a.x},${a.y}`;
219
+ const bKey = `${b.x},${b.y}`;
220
+ return aKey < bKey ? `${aKey}|${bKey}` : `${bKey}|${aKey}`;
221
+ }
222
+ function vertKey(v) {
223
+ return `${v.x},${v.y}`;
224
+ }
225
+ function same(a, b, eps = 1e-12) {
226
+ return Math.abs(a.x - b.x) < eps && Math.abs(a.y - b.y) < eps;
227
+ }
228
+ function dist(a, b) {
229
+ return Math.hypot(a.x - b.x, a.y - b.y);
230
+ }
231
+ function mid(a, b) {
232
+ return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
233
+ }
234
+ function lerp(a, b, t) {
235
+ return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
236
+ }
237
+ function unique(points) {
238
+ const seen = /* @__PURE__ */ new Set();
239
+ const out = [];
240
+ for (const p of points) {
241
+ const k = vertKey(p);
242
+ if (seen.has(k)) continue;
243
+ seen.add(k);
244
+ out.push({ ...p });
245
+ }
246
+ return out;
247
+ }
248
+ function cross(o, a, b) {
249
+ return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
250
+ }
251
+ function ringOf(face) {
252
+ return face.rings[0] ?? [];
253
+ }
254
+ function buildEdgeMap(faces) {
255
+ const edges = /* @__PURE__ */ new Map();
256
+ faces.forEach((f, fi) => {
257
+ const ring = ringOf(f);
258
+ if (ring.length < 2) return;
259
+ const n = ring.length;
260
+ const closed = f.closed !== false && n >= 3;
261
+ const lim = closed ? n : n - 1;
262
+ for (let i = 0; i < lim; i++) {
263
+ const a = ring[i];
264
+ const b = ring[(i + 1) % n];
265
+ const key = edgeKey(a, b);
266
+ const rec = edges.get(key);
267
+ if (rec) rec.faceIds.push(fi);
268
+ else edges.set(key, { a: { ...a }, b: { ...b }, faceIds: [fi] });
269
+ }
270
+ });
271
+ return edges;
272
+ }
273
+ function perimeterKeys(edgeMap) {
274
+ const out = /* @__PURE__ */ new Set();
275
+ for (const [k, e] of edgeMap) {
276
+ if (e.faceIds.length === 1) out.add(k);
277
+ }
278
+ return out;
279
+ }
280
+ function faceAdjacency(faces) {
281
+ const edgeMap = buildEdgeMap(faces);
282
+ const adj = faces.map(() => []);
283
+ for (const e of edgeMap.values()) {
284
+ if (e.faceIds.length === 2) {
285
+ const [i, j] = e.faceIds;
286
+ adj[i].push(j);
287
+ adj[j].push(i);
288
+ }
289
+ }
290
+ return adj;
291
+ }
292
+ function sitesFromFaces(faces) {
293
+ return unique(faces.flatMap((f) => ringOf(f)));
294
+ }
295
+ function facesFromKeptEdges(tris, kept) {
296
+ if (tris.length === 0) return [];
297
+ const parent = tris.map((_, i) => i);
298
+ const find = (i) => {
299
+ let x = i;
300
+ while (parent[x] !== x) {
301
+ parent[x] = parent[parent[x]];
302
+ x = parent[x];
303
+ }
304
+ return x;
305
+ };
306
+ const unite = (a, b) => {
307
+ const ra = find(a);
308
+ const rb = find(b);
309
+ if (ra !== rb) parent[rb] = ra;
310
+ };
311
+ const edgeMap = buildEdgeMap(tris);
312
+ for (const [key, e] of edgeMap) {
313
+ if (e.faceIds.length === 2 && !kept.has(key)) {
314
+ unite(e.faceIds[0], e.faceIds[1]);
315
+ }
316
+ }
317
+ const components = /* @__PURE__ */ new Map();
318
+ for (let i = 0; i < tris.length; i++) {
319
+ const r = find(i);
320
+ const list = components.get(r) ?? [];
321
+ list.push(i);
322
+ components.set(r, list);
323
+ }
324
+ const out = [];
325
+ for (const members of components.values()) {
326
+ const ring = boundaryRingOfComponent(tris, members);
327
+ if (ring && ring.length >= 3) out.push(polygon(ring));
328
+ }
329
+ return out;
330
+ }
331
+ function boundaryRingOfComponent(tris, members) {
332
+ const edgeCount = /* @__PURE__ */ new Map();
333
+ for (const fi of members) {
334
+ const ring = ringOf(tris[fi]);
335
+ if (ring.length < 3) continue;
336
+ for (let i = 0; i < ring.length; i++) {
337
+ const a = ring[i];
338
+ const b = ring[(i + 1) % ring.length];
339
+ const key = edgeKey(a, b);
340
+ const rec = edgeCount.get(key);
341
+ if (rec) rec.n++;
342
+ else edgeCount.set(key, { a: { ...a }, b: { ...b }, n: 1 });
343
+ }
344
+ }
345
+ const boundary = [];
346
+ for (const e of edgeCount.values()) {
347
+ if (e.n === 1) boundary.push({ a: e.a, b: e.b });
348
+ }
349
+ if (boundary.length < 3) return null;
350
+ return orderEdgesIntoRing(boundary);
351
+ }
352
+ function orderEdgesIntoRing(edges) {
353
+ if (edges.length === 0) return null;
354
+ const adj = /* @__PURE__ */ new Map();
355
+ const add = (from, to) => {
356
+ const k = vertKey(from);
357
+ const list = adj.get(k) ?? [];
358
+ list.push(to);
359
+ adj.set(k, list);
360
+ };
361
+ for (const e of edges) {
362
+ add(e.a, e.b);
363
+ add(e.b, e.a);
364
+ }
365
+ const start = edges[0].a;
366
+ const ring = [{ ...start }];
367
+ let prev = start;
368
+ let curr = edges[0].b;
369
+ const maxSteps = edges.length + 2;
370
+ for (let step = 0; step < maxSteps; step++) {
371
+ ring.push({ ...curr });
372
+ if (same(curr, start) && ring.length > 2) {
373
+ ring.pop();
374
+ return ring;
375
+ }
376
+ const nbrs = adj.get(vertKey(curr)) ?? [];
377
+ let next = null;
378
+ for (const n of nbrs) {
379
+ if (!same(n, prev)) {
380
+ next = n;
381
+ break;
382
+ }
383
+ }
384
+ if (!next) break;
385
+ prev = curr;
386
+ curr = next;
387
+ }
388
+ return ring.length >= 3 ? ring : null;
389
+ }
390
+ function applyEdgeFilter(tris, shouldKeep, preservePerimeter) {
391
+ const edgeMap = buildEdgeMap(tris);
392
+ const peri = perimeterKeys(edgeMap);
393
+ const kept = /* @__PURE__ */ new Set();
394
+ for (const [key, e] of edgeMap) {
395
+ const isPeri = peri.has(key);
396
+ if (isPeri && preservePerimeter) {
397
+ kept.add(key);
398
+ continue;
399
+ }
400
+ if (shouldKeep(e.a, e.b, isPeri)) kept.add(key);
401
+ }
402
+ return facesFromKeptEdges(tris, kept);
403
+ }
404
+ function asTriFaces(input) {
405
+ if (input.length === 0) return [];
406
+ const first = input[0];
407
+ if ("rings" in first) return input;
408
+ return delaunayTriangulationPoints(input);
409
+ }
410
+ function pointsOf(input) {
411
+ if (input.length === 0) return [];
412
+ const first = input[0];
413
+ if ("rings" in first) return sitesFromFaces(input);
414
+ return input;
415
+ }
416
+ function extractInnerEdges(faces) {
417
+ const edgeMap = buildEdgeMap(faces);
418
+ const out = [];
419
+ for (const e of edgeMap.values()) {
420
+ if (e.faceIds.length >= 2) out.push(segment(e.a, e.b));
421
+ }
422
+ return out;
423
+ }
424
+ function extractInnerVertices(faces) {
425
+ const edgeMap = buildEdgeMap(faces);
426
+ const peri = perimeterKeys(edgeMap);
427
+ const periVerts = /* @__PURE__ */ new Set();
428
+ for (const key of peri) {
429
+ const e = edgeMap.get(key);
430
+ periVerts.add(vertKey(e.a));
431
+ periVerts.add(vertKey(e.b));
432
+ }
433
+ const out = [];
434
+ const seen = /* @__PURE__ */ new Set();
435
+ for (const f of faces) {
436
+ for (const v of ringOf(f)) {
437
+ const k = vertKey(v);
438
+ if (periVerts.has(k) || seen.has(k)) continue;
439
+ seen.add(k);
440
+ out.push({ ...v });
441
+ }
442
+ }
443
+ return out;
444
+ }
445
+ function findContainingFace(faces, point) {
446
+ for (const f of faces) {
447
+ if (containsPoint(f, point)) return f;
448
+ }
449
+ return null;
450
+ }
451
+ function splitEdges(faces, maxLen) {
452
+ const lim = Math.max(maxLen, 1e-9);
453
+ const out = [];
454
+ for (const f of faces) {
455
+ out.push(
456
+ polygon(
457
+ densifyRing(ringOf(f), lim),
458
+ f.rings.slice(1).map((r) => densifyRing(r, lim))
459
+ )
460
+ );
461
+ }
462
+ return group(out);
463
+ }
464
+ function densifyRing(ring, maxLen) {
465
+ if (ring.length < 2) return ring.map((v) => ({ ...v }));
466
+ const out = [];
467
+ const n = ring.length;
468
+ for (let i = 0; i < n; i++) {
469
+ const a = ring[i];
470
+ const b = ring[(i + 1) % n];
471
+ out.push({ ...a });
472
+ const d = dist(a, b);
473
+ const steps = Math.floor(d / maxLen);
474
+ for (let s = 1; s < steps; s++) out.push(lerp(a, b, s / steps));
475
+ }
476
+ return out;
477
+ }
478
+ function isGabriel(a, b, points) {
479
+ const m = mid(a, b);
480
+ const r = dist(a, b) / 2;
481
+ for (const p of points) {
482
+ if (same(p, a) || same(p, b)) continue;
483
+ if (dist(p, m) < r - 1e-9) return false;
484
+ }
485
+ return true;
486
+ }
487
+ function isRelativeNeighbor(a, b, points) {
488
+ const dab = dist(a, b);
489
+ for (const p of points) {
490
+ if (same(p, a) || same(p, b)) continue;
491
+ if (Math.max(dist(p, a), dist(p, b)) < dab - 1e-9) return false;
492
+ }
493
+ return true;
494
+ }
495
+ function urquhartFaces(input, preservePerimeter = false) {
496
+ const tris = asTriFaces(input);
497
+ const edgeMap = buildEdgeMap(tris);
498
+ const peri = perimeterKeys(edgeMap);
499
+ const removed = /* @__PURE__ */ new Set();
500
+ for (const t of tris) {
501
+ const r = ringOf(t);
502
+ if (r.length < 3) continue;
503
+ let longestKey = "";
504
+ let longestLen = -1;
505
+ for (let i = 0; i < 3; i++) {
506
+ const a = r[i];
507
+ const b = r[(i + 1) % 3];
508
+ const d = dist(a, b);
509
+ if (d > longestLen) {
510
+ longestLen = d;
511
+ longestKey = edgeKey(a, b);
512
+ }
513
+ }
514
+ if (longestKey && !(preservePerimeter && peri.has(longestKey))) {
515
+ removed.add(longestKey);
516
+ }
517
+ }
518
+ const kept = /* @__PURE__ */ new Set();
519
+ for (const key of edgeMap.keys()) {
520
+ if (!removed.has(key)) kept.add(key);
521
+ }
522
+ return group(facesFromKeptEdges(tris, kept));
523
+ }
524
+ function gabrielFaces(input, preservePerimeter = false) {
525
+ const tris = asTriFaces(input);
526
+ const pts = pointsOf(input);
527
+ return group(
528
+ applyEdgeFilter(
529
+ tris,
530
+ (a, b) => isGabriel(a, b, pts),
531
+ preservePerimeter
532
+ )
533
+ );
534
+ }
535
+ function relativeNeighborFaces(input, preservePerimeter = false) {
536
+ const tris = asTriFaces(input);
537
+ const pts = pointsOf(input);
538
+ return group(
539
+ applyEdgeFilter(
540
+ tris,
541
+ (a, b) => isRelativeNeighbor(a, b, pts),
542
+ preservePerimeter
543
+ )
544
+ );
545
+ }
546
+ function spannerFaces(input, k = 1, preservePerimeter = false) {
547
+ const tris = asTriFaces(input);
548
+ const edgeMap = buildEdgeMap(tris);
549
+ const peri = perimeterKeys(edgeMap);
550
+ const stretch = Math.max(1, k);
551
+ const edges = [...edgeMap.entries()].map(([key, e]) => ({
552
+ key,
553
+ a: e.a,
554
+ b: e.b,
555
+ len: dist(e.a, e.b),
556
+ peri: peri.has(key)
557
+ }));
558
+ edges.sort((a, b) => a.len - b.len);
559
+ const adj = /* @__PURE__ */ new Map();
560
+ const link = (a, b, w) => {
561
+ const ka = vertKey(a);
562
+ const kb = vertKey(b);
563
+ if (!adj.has(ka)) adj.set(ka, []);
564
+ if (!adj.has(kb)) adj.set(kb, []);
565
+ adj.get(ka).push({ to: kb, w });
566
+ adj.get(kb).push({ to: ka, w });
567
+ };
568
+ const shortest = (from, to) => {
569
+ const start = vertKey(from);
570
+ const goal = vertKey(to);
571
+ const distMap = /* @__PURE__ */ new Map([[start, 0]]);
572
+ const pq = [{ k: start, d: 0 }];
573
+ while (pq.length) {
574
+ pq.sort((x, y) => x.d - y.d);
575
+ const cur = pq.shift();
576
+ if (cur.k === goal) return cur.d;
577
+ if (cur.d > (distMap.get(cur.k) ?? Infinity)) continue;
578
+ for (const e of adj.get(cur.k) ?? []) {
579
+ const nd = cur.d + e.w;
580
+ if (nd < (distMap.get(e.to) ?? Infinity)) {
581
+ distMap.set(e.to, nd);
582
+ pq.push({ k: e.to, d: nd });
583
+ }
584
+ }
585
+ }
586
+ return Infinity;
587
+ };
588
+ const kept = /* @__PURE__ */ new Set();
589
+ for (const e of edges) {
590
+ if (e.peri && preservePerimeter) {
591
+ kept.add(e.key);
592
+ link(e.a, e.b, e.len);
593
+ continue;
594
+ }
595
+ const pathLen = shortest(e.a, e.b);
596
+ if (pathLen > stretch * e.len + 1e-9) {
597
+ kept.add(e.key);
598
+ link(e.a, e.b, e.len);
599
+ } else if (e.peri) {
600
+ kept.add(e.key);
601
+ link(e.a, e.b, e.len);
602
+ }
603
+ }
604
+ return group(facesFromKeptEdges(tris, kept));
605
+ }
606
+ function dualFaces(faces) {
607
+ if (faces.length === 0) return group([]);
608
+ const edgeMap = buildEdgeMap(faces);
609
+ const peri = perimeterKeys(edgeMap);
610
+ const periVerts = /* @__PURE__ */ new Set();
611
+ for (const key of peri) {
612
+ const e = edgeMap.get(key);
613
+ periVerts.add(vertKey(e.a));
614
+ periVerts.add(vertKey(e.b));
615
+ }
616
+ const incident = /* @__PURE__ */ new Map();
617
+ faces.forEach((f, fi) => {
618
+ const c = centroid(f);
619
+ for (const v of ringOf(f)) {
620
+ const k = vertKey(v);
621
+ if (periVerts.has(k)) continue;
622
+ const list = incident.get(k) ?? [];
623
+ list.push({ fi, ang: Math.atan2(c.y - v.y, c.x - v.x), v });
624
+ incident.set(k, list);
625
+ }
626
+ });
627
+ const out = [];
628
+ for (const list of incident.values()) {
629
+ if (list.length < 3) continue;
630
+ const byFace = /* @__PURE__ */ new Map();
631
+ for (const item of list) byFace.set(item.fi, item);
632
+ const uniqueList = [...byFace.values()];
633
+ if (uniqueList.length < 3) continue;
634
+ uniqueList.sort((a, b) => a.ang - b.ang);
635
+ const ring = uniqueList.map((item) => centroid(faces[item.fi]));
636
+ out.push(polygon(ring));
637
+ }
638
+ return group(out);
639
+ }
640
+ function triVerts(t) {
641
+ const r = ringOf(t);
642
+ if (r.length < 3) return null;
643
+ return [r[0], r[1], r[2]];
644
+ }
645
+ function midpointMap(faces) {
646
+ const mids = /* @__PURE__ */ new Map();
647
+ const edgeMap = buildEdgeMap(faces);
648
+ for (const [key, e] of edgeMap) {
649
+ mids.set(key, mid(e.a, e.b));
650
+ }
651
+ return mids;
652
+ }
653
+ function centroidQuadrangulation(input, preservePerimeter = true) {
654
+ const tris = asTriFaces(input);
655
+ const edgeMap = buildEdgeMap(tris);
656
+ const centers = tris.map((t) => centroid(t));
657
+ const out = [];
658
+ const peri = perimeterKeys(edgeMap);
659
+ for (const [key, e] of edgeMap) {
660
+ const ids = e.faceIds;
661
+ if (ids.length === 2) {
662
+ const c0 = centers[ids[0]];
663
+ const c1 = centers[ids[1]];
664
+ out.push(polygon([e.a, c0, e.b, c1]));
665
+ } else if (ids.length === 1 && preservePerimeter) {
666
+ const c0 = centers[ids[0]];
667
+ out.push(polygon([e.a, e.b, c0]));
668
+ } else if (ids.length === 1 && !preservePerimeter && peri.has(key)) ;
669
+ }
670
+ return group(out);
671
+ }
672
+ function splitQuadrangulation(input) {
673
+ const tris = asTriFaces(input);
674
+ const mids = midpointMap(tris);
675
+ const out = [];
676
+ for (const t of tris) {
677
+ const v = triVerts(t);
678
+ if (!v) continue;
679
+ const [a, b, c] = v;
680
+ const g = centroid(t);
681
+ const mab = mids.get(edgeKey(a, b)) ?? mid(a, b);
682
+ const mbc = mids.get(edgeKey(b, c)) ?? mid(b, c);
683
+ const mca = mids.get(edgeKey(c, a)) ?? mid(c, a);
684
+ out.push(polygon([a, mab, g, mca]));
685
+ out.push(polygon([b, mbc, g, mab]));
686
+ out.push(polygon([c, mca, g, mbc]));
687
+ }
688
+ return group(out);
689
+ }
690
+ function edgeCollapseQuadrangulation(input, preservePerimeter = true) {
691
+ const tris = asTriFaces(input);
692
+ const edgeMap = buildEdgeMap(tris);
693
+ const candidates = [];
694
+ for (const [key, e] of edgeMap) {
695
+ if (e.faceIds.length !== 2) continue;
696
+ const [i, j] = e.faceIds;
697
+ const quad = mergeTriPair(tris[i], tris[j], e.a, e.b);
698
+ if (!quad) continue;
699
+ candidates.push({
700
+ key,
701
+ a: e.a,
702
+ b: e.b,
703
+ i,
704
+ j,
705
+ q: quadQuality(quad)
706
+ });
707
+ }
708
+ candidates.sort((x, y) => y.q - x.q);
709
+ const used = /* @__PURE__ */ new Set();
710
+ const out = [];
711
+ for (const c of candidates) {
712
+ if (used.has(c.i) || used.has(c.j)) continue;
713
+ const quad = mergeTriPair(tris[c.i], tris[c.j], c.a, c.b);
714
+ if (!quad) continue;
715
+ out.push(quad);
716
+ used.add(c.i);
717
+ used.add(c.j);
718
+ }
719
+ for (let i = 0; i < tris.length; i++) {
720
+ if (used.has(i)) continue;
721
+ if (preservePerimeter) out.push(tris[i]);
722
+ }
723
+ return group(out);
724
+ }
725
+ function matchingQuadrangulation(input) {
726
+ return edgeCollapseQuadrangulation(input, true);
727
+ }
728
+ function mergeTriPair(t0, t1, ea, eb) {
729
+ const opp0 = oppositeVertex(t0, ea, eb);
730
+ const opp1 = oppositeVertex(t1, ea, eb);
731
+ if (!opp0 || !opp1) return null;
732
+ return polygon([opp0, ea, opp1, eb]);
733
+ }
734
+ function oppositeVertex(t, ea, eb) {
735
+ for (const v of ringOf(t)) {
736
+ if (!same(v, ea) && !same(v, eb)) return v;
737
+ }
738
+ return null;
739
+ }
740
+ function quadQuality(q) {
741
+ const r = ringOf(q);
742
+ if (r.length < 4) return 0;
743
+ const lens = [];
744
+ for (let i = 0; i < 4; i++) {
745
+ lens.push(dist(r[i], r[(i + 1) % 4]));
746
+ }
747
+ const minL = Math.min(...lens);
748
+ const maxL = Math.max(...lens);
749
+ if (maxL < 1e-12) return 0;
750
+ return minL / maxL;
751
+ }
752
+ function spiralQuadrangulation(points) {
753
+ if (points.length < 4) return group([]);
754
+ const pts = unique(points);
755
+ const cx = pts.reduce((s, p) => s + p.x, 0) / pts.length;
756
+ const cy = pts.reduce((s, p) => s + p.y, 0) / pts.length;
757
+ const withMeta = pts.map((p) => ({
758
+ p,
759
+ ang: Math.atan2(p.y - cy, p.x - cx),
760
+ rad: Math.hypot(p.x - cx, p.y - cy)
761
+ }));
762
+ withMeta.sort((a, b) => a.rad - b.rad || a.ang - b.ang);
763
+ const ringCount = Math.max(2, Math.ceil(Math.sqrt(pts.length)));
764
+ const rings = Array.from({ length: ringCount }, () => []);
765
+ withMeta.forEach((item, i) => {
766
+ const ri = Math.min(
767
+ ringCount - 1,
768
+ Math.floor(i / withMeta.length * ringCount)
769
+ );
770
+ rings[ri].push(item.p);
771
+ });
772
+ for (const ring of rings) {
773
+ ring.sort(
774
+ (a, b) => Math.atan2(a.y - cy, a.x - cx) - Math.atan2(b.y - cy, b.x - cx)
775
+ );
776
+ }
777
+ const out = [];
778
+ for (let r = 0; r < rings.length - 1; r++) {
779
+ const inner = rings[r];
780
+ const outer = rings[r + 1];
781
+ if (inner.length === 0 || outer.length === 0) continue;
782
+ const n = Math.max(inner.length, outer.length);
783
+ for (let i = 0; i < n; i++) {
784
+ const a = inner[i % inner.length];
785
+ const b = inner[(i + 1) % inner.length];
786
+ const c = outer[(i + 1) % outer.length];
787
+ const d = outer[i % outer.length];
788
+ const verts = unique([a, b, c, d]);
789
+ if (verts.length >= 3) out.push(polygon(verts.length === 3 ? verts : [a, b, c, d]));
790
+ }
791
+ }
792
+ return group(out);
793
+ }
794
+ function vertexNeighborhood(faces) {
795
+ const edgeMap = buildEdgeMap(faces);
796
+ const peri = perimeterKeys(edgeMap);
797
+ const perimeter = /* @__PURE__ */ new Set();
798
+ for (const key of peri) {
799
+ const e = edgeMap.get(key);
800
+ perimeter.add(vertKey(e.a));
801
+ perimeter.add(vertKey(e.b));
802
+ }
803
+ const positions = /* @__PURE__ */ new Map();
804
+ const neighbors = /* @__PURE__ */ new Map();
805
+ const link = (a, b) => {
806
+ const ka = vertKey(a);
807
+ const kb = vertKey(b);
808
+ positions.set(ka, { ...a });
809
+ positions.set(kb, { ...b });
810
+ if (!neighbors.has(ka)) neighbors.set(ka, /* @__PURE__ */ new Set());
811
+ if (!neighbors.has(kb)) neighbors.set(kb, /* @__PURE__ */ new Set());
812
+ neighbors.get(ka).add(kb);
813
+ neighbors.get(kb).add(ka);
814
+ };
815
+ for (const e of edgeMap.values()) link(e.a, e.b);
816
+ return { positions, neighbors, perimeter };
817
+ }
818
+ function remapFaces(faces, positions) {
819
+ return faces.map(
820
+ (f) => polygon(
821
+ ringOf(f).map((v) => {
822
+ const p = positions.get(vertKey(v));
823
+ return p ? { ...p } : { ...v };
824
+ }),
825
+ f.rings.slice(1).map(
826
+ (r) => r.map((v) => {
827
+ const p = positions.get(vertKey(v));
828
+ return p ? { ...p } : { ...v };
829
+ })
830
+ )
831
+ )
832
+ );
833
+ }
834
+ function smoothMesh(faces, iterationsOrCutoff, preservePerimeter = true) {
835
+ if (faces.length === 0) return group([]);
836
+ let { positions, neighbors, perimeter } = vertexNeighborhood(faces);
837
+ const maxIter = iterationsOrCutoff >= 1 && Number.isInteger(iterationsOrCutoff) ? iterationsOrCutoff : 200;
838
+ const cutoff = iterationsOrCutoff > 0 && iterationsOrCutoff < 1 ? iterationsOrCutoff : 0;
839
+ for (let iter = 0; iter < maxIter; iter++) {
840
+ const next = /* @__PURE__ */ new Map();
841
+ let maxDisp = 0;
842
+ for (const [k, p] of positions) {
843
+ if (preservePerimeter && perimeter.has(k)) {
844
+ next.set(k, p);
845
+ continue;
846
+ }
847
+ const nbrs = neighbors.get(k);
848
+ if (!nbrs || nbrs.size === 0) {
849
+ next.set(k, p);
850
+ continue;
851
+ }
852
+ let sx = 0;
853
+ let sy = 0;
854
+ for (const nk of nbrs) {
855
+ const n = positions.get(nk);
856
+ sx += n.x;
857
+ sy += n.y;
858
+ }
859
+ const np = { x: sx / nbrs.size, y: sy / nbrs.size };
860
+ maxDisp = Math.max(maxDisp, dist(p, np));
861
+ next.set(k, np);
862
+ }
863
+ positions = next;
864
+ if (cutoff > 0 && maxDisp < cutoff) break;
865
+ }
866
+ return group(remapFaces(faces, positions));
867
+ }
868
+ function subdivideMesh(faces, edgeSplitRatio = 0.5) {
869
+ const t = Math.min(1, Math.max(0, edgeSplitRatio));
870
+ const out = [];
871
+ for (const f of faces) {
872
+ const ring = ringOf(f);
873
+ if (ring.length < 3) continue;
874
+ const c = centroid(f);
875
+ const n = ring.length;
876
+ const splits = [];
877
+ for (let i = 0; i < n; i++) {
878
+ const a = ring[i];
879
+ const b = ring[(i + 1) % n];
880
+ splits.push(lerp(a, b, t));
881
+ }
882
+ for (let i = 0; i < n; i++) {
883
+ const v = ring[i];
884
+ const sPrev = splits[(i - 1 + n) % n];
885
+ const sNext = splits[i];
886
+ out.push(polygon([v, sNext, c, sPrev]));
887
+ }
888
+ }
889
+ return group(out);
890
+ }
891
+ function simplifyMesh(faces, tolerance, preservePerimeter = true) {
892
+ if (tolerance <= 0 || faces.length === 0) return group(faces.map((f) => polygon(ringOf(f), f.rings.slice(1))));
893
+ const edgeMap = buildEdgeMap(faces);
894
+ const peri = perimeterKeys(edgeMap);
895
+ const simplified = faces.map((f) => {
896
+ const ring = ringOf(f);
897
+ if (ring.length <= 3) return polygon(ring);
898
+ if (preservePerimeter) {
899
+ const s = simplify(f, tolerance);
900
+ const sr = ringOf(s);
901
+ if (sr.length < 3) return polygon(ring);
902
+ return polygon(sr, s.rings.slice(1));
903
+ }
904
+ return simplify(f, tolerance);
905
+ });
906
+ const all = unique(simplified.flatMap((f) => ringOf(f)));
907
+ const snapped = clusterSnap(all, Math.max(tolerance * 0.5, 1e-9));
908
+ const remap = (v) => {
909
+ let best = v;
910
+ let bestD = Infinity;
911
+ for (const s of snapped) {
912
+ const d = dist(v, s);
913
+ if (d < bestD) {
914
+ bestD = d;
915
+ best = s;
916
+ }
917
+ }
918
+ return { ...best };
919
+ };
920
+ const periVerts = /* @__PURE__ */ new Set();
921
+ if (preservePerimeter) {
922
+ for (const key of peri) {
923
+ const e = edgeMap.get(key);
924
+ periVerts.add(vertKey(e.a));
925
+ periVerts.add(vertKey(e.b));
926
+ }
927
+ }
928
+ return group(
929
+ simplified.map((f, i) => {
930
+ const original = ringOf(faces[i]);
931
+ const ring = ringOf(f).map((v, vi) => {
932
+ if (preservePerimeter && original[vi] && periVerts.has(vertKey(original[vi]))) {
933
+ return { ...original[vi] };
934
+ }
935
+ for (const ov of original) {
936
+ if (periVerts.has(vertKey(ov)) && dist(v, ov) < tolerance * 2) {
937
+ return { ...ov };
938
+ }
939
+ }
940
+ return remap(v);
941
+ });
942
+ return polygon(unique(ring).length >= 3 ? ring : original);
943
+ })
944
+ );
945
+ }
946
+ function clusterSnap(points, tol) {
947
+ const clusters = [];
948
+ for (const p of points) {
949
+ let found = false;
950
+ for (const c of clusters) {
951
+ if (dist(p, c[0]) <= tol) {
952
+ c.push(p);
953
+ found = true;
954
+ break;
955
+ }
956
+ }
957
+ if (!found) clusters.push([p]);
958
+ }
959
+ return clusters.map((c) => ({
960
+ x: c.reduce((s, p) => s + p.x, 0) / c.length,
961
+ y: c.reduce((s, p) => s + p.y, 0) / c.length
962
+ }));
963
+ }
964
+ function stochasticMerge(faces, nClasses, seed = 1) {
965
+ if (faces.length === 0) return group([]);
966
+ const classes = Math.max(1, Math.floor(nClasses));
967
+ const rng = mulberry32(seed >>> 0);
968
+ const label = faces.map(() => Math.floor(rng() * classes));
969
+ const adj = faceAdjacency(faces);
970
+ const parent = faces.map((_, i) => i);
971
+ const find = (i) => {
972
+ let x = i;
973
+ while (parent[x] !== x) {
974
+ parent[x] = parent[parent[x]];
975
+ x = parent[x];
976
+ }
977
+ return x;
978
+ };
979
+ const unite = (a, b) => {
980
+ const ra = find(a);
981
+ const rb = find(b);
982
+ if (ra !== rb) parent[rb] = ra;
983
+ };
984
+ for (let i = 0; i < faces.length; i++) {
985
+ for (const j of adj[i]) {
986
+ if (j > i && label[i] === label[j]) unite(i, j);
987
+ }
988
+ }
989
+ const comps = /* @__PURE__ */ new Map();
990
+ for (let i = 0; i < faces.length; i++) {
991
+ const r = find(i);
992
+ const list = comps.get(r) ?? [];
993
+ list.push(i);
994
+ comps.set(r, list);
995
+ }
996
+ const out = [];
997
+ for (const members of comps.values()) {
998
+ if (members.length === 1) {
999
+ out.push(faces[members[0]]);
1000
+ continue;
1001
+ }
1002
+ const merged = dissolveFaces(members.map((i) => faces[i]));
1003
+ out.push(...merged);
1004
+ }
1005
+ return group(out);
1006
+ }
1007
+ function dissolveFaces(faces) {
1008
+ if (faces.length === 0) return [];
1009
+ if (faces.length === 1) return [faces[0]];
1010
+ try {
1011
+ let acc = faces[0];
1012
+ for (let i = 1; i < faces.length; i++) {
1013
+ const u = union(acc, faces[i]);
1014
+ if (u.paths.length === 0) continue;
1015
+ acc = u.paths.reduce(
1016
+ (best, p) => area(p) > area(best) ? p : best
1017
+ );
1018
+ }
1019
+ return [acc];
1020
+ } catch {
1021
+ return [convexHullOf(faces.flatMap((f) => ringOf(f)))];
1022
+ }
1023
+ }
1024
+ function areaMerge(faces, minAreaOrOptions) {
1025
+ let working = [...faces];
1026
+ if (working.length === 0) return group([]);
1027
+ if (typeof minAreaOrOptions === "object" && minAreaOrOptions.remainingFaces != null) {
1028
+ const target = Math.max(1, Math.floor(minAreaOrOptions.remainingFaces));
1029
+ while (working.length > target) {
1030
+ const next = mergeSmallestAdjacent(working);
1031
+ if (next.length >= working.length) break;
1032
+ working = next;
1033
+ }
1034
+ return group(working);
1035
+ }
1036
+ const minArea = typeof minAreaOrOptions === "number" ? minAreaOrOptions : minAreaOrOptions.minArea ?? 0;
1037
+ let guard = 0;
1038
+ while (guard++ < working.length + 10) {
1039
+ const smallIdx = working.findIndex((f) => area(f) < minArea);
1040
+ if (smallIdx < 0) break;
1041
+ const next = mergeFaceIntoNeighbor(working, smallIdx);
1042
+ if (!next) break;
1043
+ working = next;
1044
+ }
1045
+ return group(working);
1046
+ }
1047
+ function mergeSmallestAdjacent(faces) {
1048
+ let best = -1;
1049
+ let bestA = Infinity;
1050
+ for (let i = 0; i < faces.length; i++) {
1051
+ const a = area(faces[i]);
1052
+ if (a < bestA) {
1053
+ bestA = a;
1054
+ best = i;
1055
+ }
1056
+ }
1057
+ if (best < 0) return faces;
1058
+ return mergeFaceIntoNeighbor(faces, best) ?? faces;
1059
+ }
1060
+ function mergeFaceIntoNeighbor(faces, idx) {
1061
+ const adj = faceAdjacency(faces);
1062
+ const nbrs = adj[idx] ?? [];
1063
+ if (nbrs.length === 0) {
1064
+ return faces.filter((_, i) => i !== idx);
1065
+ }
1066
+ let bestN = nbrs[0];
1067
+ let bestA = -1;
1068
+ for (const n of nbrs) {
1069
+ const a = area(faces[n]);
1070
+ if (a > bestA) {
1071
+ bestA = a;
1072
+ bestN = n;
1073
+ }
1074
+ }
1075
+ const merged = dissolveFaces([faces[idx], faces[bestN]]);
1076
+ const out = [];
1077
+ for (let i = 0; i < faces.length; i++) {
1078
+ if (i === idx || i === bestN) continue;
1079
+ out.push(faces[i]);
1080
+ }
1081
+ out.push(...merged);
1082
+ return out;
1083
+ }
1084
+ function convexHullOf(points) {
1085
+ const pts = unique(points);
1086
+ if (pts.length < 3) return polygon(pts);
1087
+ pts.sort((a, b) => a.x === b.x ? a.y - b.y : a.x - b.x);
1088
+ const lower = [];
1089
+ for (const p of pts) {
1090
+ while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
1091
+ lower.pop();
1092
+ }
1093
+ lower.push(p);
1094
+ }
1095
+ const upper = [];
1096
+ for (let i = pts.length - 1; i >= 0; i--) {
1097
+ const p = pts[i];
1098
+ while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
1099
+ upper.pop();
1100
+ }
1101
+ upper.push(p);
1102
+ }
1103
+ lower.pop();
1104
+ upper.pop();
1105
+ return polygon([...lower, ...upper]);
1106
+ }
1107
+ function mulberry32(a) {
1108
+ return () => {
1109
+ a |= 0;
1110
+ a = a + 1831565813 | 0;
1111
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
1112
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
1113
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1114
+ };
1115
+ }
1116
+ function findBreaks(faces) {
1117
+ const edgeMap = buildEdgeMap(faces);
1118
+ const segs = [];
1119
+ const peri = [];
1120
+ for (const e of edgeMap.values()) {
1121
+ if (e.faceIds.length === 1) peri.push(e);
1122
+ }
1123
+ const used = /* @__PURE__ */ new Set();
1124
+ for (let i = 0; i < peri.length; i++) {
1125
+ if (used.has(i)) continue;
1126
+ const a = peri[i];
1127
+ for (let j = i + 1; j < peri.length; j++) {
1128
+ if (used.has(j)) continue;
1129
+ const b = peri[j];
1130
+ if (nearDuplicateEdge(a, b, 1e-3)) {
1131
+ segs.push(polyline([a.a, a.b]));
1132
+ segs.push(polyline([b.a, b.b]));
1133
+ used.add(i);
1134
+ used.add(j);
1135
+ break;
1136
+ }
1137
+ }
1138
+ }
1139
+ return group(segs);
1140
+ }
1141
+ function nearDuplicateEdge(a, b, tol) {
1142
+ const d1 = dist(a.a, b.a) + dist(a.b, b.b);
1143
+ const d2 = dist(a.a, b.b) + dist(a.b, b.a);
1144
+ const len = dist(a.a, a.b);
1145
+ return Math.min(d1, d2) < tol * 2 + len * 0.05 && Math.min(d1, d2) > 1e-9;
1146
+ }
1147
+ function fixBreaks(faces, maxGapWidth) {
1148
+ if (faces.length === 0) return group([]);
1149
+ const tol = Math.max(maxGapWidth, 1e-9);
1150
+ const verts = unique(faces.flatMap((f) => ringOf(f)));
1151
+ const snapped = clusterSnap(verts, tol);
1152
+ const remap = (v) => {
1153
+ let best = snapped[0];
1154
+ let bestD = Infinity;
1155
+ for (const s of snapped) {
1156
+ const d = dist(v, s);
1157
+ if (d < bestD) {
1158
+ bestD = d;
1159
+ best = s;
1160
+ }
1161
+ }
1162
+ return { ...best };
1163
+ };
1164
+ const fixed = faces.map((f) => {
1165
+ const ring = unique(ringOf(f).map(remap));
1166
+ return ring.length >= 3 ? polygon(ring) : f;
1167
+ });
1168
+ try {
1169
+ return group(dissolveOverlaps(fixed));
1170
+ } catch {
1171
+ return group(fixed);
1172
+ }
1173
+ }
1174
+ function dissolveOverlaps(faces) {
1175
+ let working = [...faces];
1176
+ let changed = true;
1177
+ let guard = 0;
1178
+ while (changed && guard++ < working.length) {
1179
+ changed = false;
1180
+ outer: for (let i = 0; i < working.length; i++) {
1181
+ for (let j = i + 1; j < working.length; j++) {
1182
+ const a = working[i];
1183
+ const b = working[j];
1184
+ const u = union(a, b);
1185
+ const ua = u.paths.reduce((s, p) => s + area(p), 0);
1186
+ if (ua < area(a) + area(b) - 1e-6) {
1187
+ const next = working.filter((_, k) => k !== i && k !== j);
1188
+ next.push(...u.paths);
1189
+ working = next;
1190
+ changed = true;
1191
+ break outer;
1192
+ }
1193
+ }
1194
+ }
1195
+ }
1196
+ return working;
1197
+ }
1198
+ function fixBrokenFaces(coverage, tolerance, polygonise = true) {
1199
+ const tol = Math.max(tolerance, 1e-9);
1200
+ const anchors = [];
1201
+ const endpoints = [];
1202
+ coverage.forEach((p, pi) => {
1203
+ const ring = ringOf(p);
1204
+ if (ring.length === 0) return;
1205
+ if (p.closed) {
1206
+ for (const v of ring) anchors.push(v);
1207
+ } else {
1208
+ endpoints.push({ pathIdx: pi, end: 0, v: ring[0] });
1209
+ endpoints.push({
1210
+ pathIdx: pi,
1211
+ end: 1,
1212
+ v: ring[ring.length - 1]
1213
+ });
1214
+ }
1215
+ });
1216
+ const snapTargets = clusterSnap(
1217
+ [...anchors, ...endpoints.map((e) => e.v)],
1218
+ tol
1219
+ );
1220
+ const snapTo = (v, preferAnchor) => {
1221
+ let best = v;
1222
+ let bestD = Infinity;
1223
+ for (const s of snapTargets) {
1224
+ const d = dist(v, s);
1225
+ if (d <= tol && d < bestD) {
1226
+ bestD = d;
1227
+ best = s;
1228
+ }
1229
+ }
1230
+ {
1231
+ for (const a of anchors) {
1232
+ if (dist(v, a) <= tol) return { ...a };
1233
+ }
1234
+ }
1235
+ return { ...best };
1236
+ };
1237
+ const snappedPaths = coverage.map((p) => {
1238
+ const ring = ringOf(p);
1239
+ if (ring.length === 0) return p;
1240
+ if (p.closed) return p;
1241
+ const next = ring.map((v) => ({ ...v }));
1242
+ next[0] = snapTo(ring[0]);
1243
+ next[next.length - 1] = snapTo(ring[ring.length - 1]);
1244
+ return polyline(next);
1245
+ });
1246
+ if (!polygonise) return group(snappedPaths);
1247
+ const out = [];
1248
+ for (const p of snappedPaths) {
1249
+ const ring = ringOf(p);
1250
+ if (p.closed) {
1251
+ out.push(p);
1252
+ continue;
1253
+ }
1254
+ if (ring.length >= 3 && same(ring[0], ring[ring.length - 1], tol * 2)) {
1255
+ out.push(polygon(ring.slice(0, -1)));
1256
+ } else if (ring.length >= 3 && dist(ring[0], ring[ring.length - 1]) <= tol * 2) {
1257
+ out.push(polygon(ring));
1258
+ } else {
1259
+ out.push(p);
1260
+ }
1261
+ }
1262
+ return group(out);
1263
+ }
1264
+ function findIslands(faces) {
1265
+ if (faces.length === 0) return [];
1266
+ const adj = faceAdjacency(faces);
1267
+ const seen = /* @__PURE__ */ new Set();
1268
+ const islands = [];
1269
+ for (let i = 0; i < faces.length; i++) {
1270
+ if (seen.has(i)) continue;
1271
+ const stack = [i];
1272
+ const members = [];
1273
+ seen.add(i);
1274
+ while (stack.length) {
1275
+ const cur = stack.pop();
1276
+ members.push(cur);
1277
+ for (const n of adj[cur]) {
1278
+ if (!seen.has(n)) {
1279
+ seen.add(n);
1280
+ stack.push(n);
1281
+ }
1282
+ }
1283
+ }
1284
+ islands.push(members);
1285
+ }
1286
+ islands.sort((a, b) => b.length - a.length);
1287
+ return islands.map((m) => group(m.map((i) => faces[i])));
1288
+ }
1289
+ function radialSortFaces(faces) {
1290
+ if (faces.length === 0) return [];
1291
+ const cs = faces.map((f) => centroid(f));
1292
+ const cx = cs.reduce((s, p) => s + p.x, 0) / cs.length;
1293
+ const cy = cs.reduce((s, p) => s + p.y, 0) / cs.length;
1294
+ return faces.map((f, i) => ({ f, a: Math.atan2(cs[i].y - cy, cs[i].x - cx) })).sort((a, b) => a.a - b.a).map((x) => x.f);
1295
+ }
1296
+ function centroidSortFaces(faces) {
1297
+ return [...faces].sort((a, b) => {
1298
+ const ca = centroid(a);
1299
+ const cb = centroid(b);
1300
+ return ca.x === cb.x ? ca.y - cb.y : ca.x - cb.x;
1301
+ });
1302
+ }
1303
+ var meshing = {
1304
+ extractInnerEdges,
1305
+ extractInnerVertices,
1306
+ findContainingFace,
1307
+ splitEdges,
1308
+ urquhartFaces,
1309
+ gabrielFaces,
1310
+ relativeNeighborFaces,
1311
+ spannerFaces,
1312
+ dualFaces,
1313
+ centroidQuadrangulation,
1314
+ edgeCollapseQuadrangulation,
1315
+ splitQuadrangulation,
1316
+ spiralQuadrangulation,
1317
+ matchingQuadrangulation,
1318
+ smoothMesh,
1319
+ subdivideMesh,
1320
+ simplifyMesh,
1321
+ stochasticMerge,
1322
+ areaMerge,
1323
+ radialSortFaces,
1324
+ centroidSortFaces,
1325
+ findBreaks,
1326
+ fixBreaks,
1327
+ fixBrokenFaces,
1328
+ findIslands
1329
+ };
1330
+
1331
+ export { areaMerge, centroidQuadrangulation, centroidSortFaces, dualFaces, edgeCollapseQuadrangulation, extractInnerEdges, extractInnerVertices, findBreaks, findContainingFace, findIslands, fixBreaks, fixBrokenFaces, gabrielFaces, matchingQuadrangulation, meshing, radialSortFaces, relativeNeighborFaces, simplifyMesh, smoothMesh, spannerFaces, spiralQuadrangulation, splitEdges, splitQuadrangulation, stochasticMerge, subdivideMesh, urquhartFaces };
1332
+ //# sourceMappingURL=index.js.map
1333
+ //# sourceMappingURL=index.js.map