pattapatta 0.2.1 → 0.4.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 (46) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +0 -43
  3. package/dist/{circle-BDbKfrdR.d.ts → circle-CauDMv8V.d.ts} +1 -1
  4. package/dist/circlePacking/index.d.ts +2 -3
  5. package/dist/construction/index.d.ts +11 -3
  6. package/dist/construction/index.js +429 -2
  7. package/dist/construction/index.js.map +1 -1
  8. package/dist/contour/index.d.ts +90 -4
  9. package/dist/contour/index.js +1326 -3
  10. package/dist/contour/index.js.map +1 -1
  11. package/dist/conversion/index.d.ts +2 -3
  12. package/dist/{group-BNjVVYSQ.d.ts → group-YVmSJuGo.d.ts} +1 -1
  13. package/dist/hatch/index.d.ts +70 -4
  14. package/dist/hatch/index.js +414 -3
  15. package/dist/hatch/index.js.map +1 -1
  16. package/dist/hull/index.d.ts +1 -2
  17. package/dist/index.d.ts +13 -14
  18. package/dist/index.js +2228 -632
  19. package/dist/index.js.map +1 -1
  20. package/dist/meshing/index.d.ts +3 -4
  21. package/dist/morphology/index.d.ts +2 -3
  22. package/dist/optimisation/index.d.ts +2 -3
  23. package/dist/{path-0RjhYjJs.d.ts → path-Boeshk9L.d.ts} +9 -2
  24. package/dist/pointSet/index.d.ts +15 -2
  25. package/dist/pointSet/index.js +148 -2
  26. package/dist/pointSet/index.js.map +1 -1
  27. package/dist/polygonisation/index.d.ts +1 -2
  28. package/dist/polygonisation/index.js +54 -50
  29. package/dist/polygonisation/index.js.map +1 -1
  30. package/dist/predicates/index.d.ts +2 -3
  31. package/dist/processing/index.d.ts +3 -4
  32. package/dist/{segment-BKuIFMKD.d.ts → segment-CpLILSd7.d.ts} +1 -1
  33. package/dist/segmentSet/index.d.ts +41 -3
  34. package/dist/segmentSet/index.js +261 -1
  35. package/dist/segmentSet/index.js.map +1 -1
  36. package/dist/shapeBoolean/index.d.ts +2 -3
  37. package/dist/svg/index.d.ts +2 -3
  38. package/dist/tiling/index.d.ts +10 -4
  39. package/dist/tiling/index.js +148 -13
  40. package/dist/tiling/index.js.map +1 -1
  41. package/dist/transformation/index.d.ts +1 -2
  42. package/dist/triangulation/index.d.ts +1 -2
  43. package/dist/triangulation/index.js.map +1 -1
  44. package/dist/voronoi/index.d.ts +2 -3
  45. package/package.json +2 -2
  46. package/dist/vec2-CWyXN1Wj.d.ts +0 -10
package/dist/index.js CHANGED
@@ -765,6 +765,193 @@ function parallelSegments(centerX, centerY, length, spacing, angle, n) {
765
765
  }
766
766
  return edges;
767
767
  }
768
+ function stochasticSegments(width2, height2, n, minLength, maxLength, seed = 1, originX = 0, originY = 0) {
769
+ const minL = minLength ?? 1;
770
+ const maxL = maxLength ?? Math.min(width2, height2);
771
+ if (n < 1 || width2 <= 0 || height2 <= 0) return [];
772
+ const monoLength = minL === maxL;
773
+ const rng = mulberry32(seed >>> 0);
774
+ const edges = [];
775
+ const maxAttempts = Math.max(1e4, n * 500);
776
+ let attempts = 0;
777
+ while (edges.length < n && attempts < maxAttempts) {
778
+ attempts++;
779
+ const length = monoLength ? minL : minL + rng() * (maxL - minL);
780
+ if (!(length > 0) || length * 2 >= width2 || length * 2 >= height2) {
781
+ continue;
782
+ }
783
+ const ax = originX + rng() * (width2 - length * 2) + length;
784
+ const ay = originY + rng() * (height2 - length * 2) + length;
785
+ const theta = rng() * Math.PI * 2;
786
+ const bx = ax + Math.cos(theta) * length;
787
+ const by = ay + Math.sin(theta) * length;
788
+ const a = vec2(ax, ay);
789
+ const b = vec2(bx, by);
790
+ let hits = false;
791
+ for (const s of edges) {
792
+ if (segmentsIntersect(a, b, s.a, s.b)) {
793
+ hits = true;
794
+ break;
795
+ }
796
+ }
797
+ if (!hits) edges.push(segment(a, b));
798
+ }
799
+ return edges;
800
+ }
801
+ function weaveSegments(width2, height2, cellSize, A = 1, B = 1, C = 1, options = {}) {
802
+ if (!(cellSize > 0)) {
803
+ throw new Error("weaveSegments: cellSize must be > 0");
804
+ }
805
+ if (A <= 0 || B <= 0) {
806
+ throw new Error("weaveSegments: A and B must be > 0");
807
+ }
808
+ const {
809
+ originX = 0,
810
+ originY = 0,
811
+ swapColors = false,
812
+ cellFraction = 1,
813
+ extendSingletonsToEdge = true
814
+ } = options;
815
+ if (!(cellFraction > 0)) {
816
+ throw new Error("weaveSegments: cellFraction must be > 0");
817
+ }
818
+ const P = A + B;
819
+ const f = Math.max(0, Math.min(1, cellFraction));
820
+ const cols = Math.max(1, Math.floor(width2 / cellSize));
821
+ const rows = Math.max(1, Math.floor(height2 / cellSize));
822
+ const gridW = cols * cellSize;
823
+ const gridH = rows * cellSize;
824
+ const dx = originX + (width2 - gridW) * 0.5;
825
+ const dy = originY + (height2 - gridH) * 0.5;
826
+ const gridLeft = dx;
827
+ const gridRight = dx + gridW;
828
+ const gridBottom = dy;
829
+ const gridTop = dy + gridH;
830
+ const weft = Array.from(
831
+ { length: rows },
832
+ () => Array.from({ length: cols }, () => false)
833
+ );
834
+ for (let r = 0; r < rows; r++) {
835
+ const shift = floorMod(r * C, P);
836
+ for (let c = 0; c < cols; c++) {
837
+ const idx = floorMod(c + shift, P);
838
+ let isWeft = idx < A;
839
+ if (swapColors) isWeft = !isWeft;
840
+ weft[r][c] = isWeft;
841
+ }
842
+ }
843
+ const segs = [];
844
+ for (let r = 0; r < rows; r++) {
845
+ let c = 0;
846
+ while (c < cols) {
847
+ if (!weft[r][c]) {
848
+ c++;
849
+ continue;
850
+ }
851
+ const start = c;
852
+ while (c + 1 < cols && weft[r][c + 1]) c++;
853
+ const end = c;
854
+ const runLen = end - start + 1;
855
+ const y = dy + (r + 0.5) * cellSize;
856
+ let x0 = dx + (start + (1 - f)) * cellSize;
857
+ let x1 = dx + (end + f) * cellSize;
858
+ if (extendSingletonsToEdge && runLen === 1) {
859
+ const cx = dx + (start + 0.5) * cellSize;
860
+ if (start === 0) {
861
+ x0 = gridLeft;
862
+ x1 = cx;
863
+ } else if (end === cols - 1) {
864
+ x0 = cx;
865
+ x1 = gridRight;
866
+ }
867
+ }
868
+ x0 = clamp(x0, gridLeft, gridRight);
869
+ x1 = clamp(x1, gridLeft, gridRight);
870
+ segs.push(segment(vec2(x0, y), vec2(x1, y)));
871
+ c++;
872
+ }
873
+ }
874
+ for (let c = 0; c < cols; c++) {
875
+ let r = 0;
876
+ while (r < rows) {
877
+ if (weft[r][c]) {
878
+ r++;
879
+ continue;
880
+ }
881
+ const start = r;
882
+ while (r + 1 < rows && !weft[r + 1][c]) r++;
883
+ const end = r;
884
+ const runLen = end - start + 1;
885
+ const x = dx + (c + 0.5) * cellSize;
886
+ let y0 = dy + (start + (1 - f)) * cellSize;
887
+ let y1 = dy + (end + f) * cellSize;
888
+ if (extendSingletonsToEdge && runLen === 1) {
889
+ const cy = dy + (start + 0.5) * cellSize;
890
+ if (start === 0) {
891
+ y0 = gridBottom;
892
+ y1 = cy;
893
+ } else if (end === rows - 1) {
894
+ y0 = cy;
895
+ y1 = gridTop;
896
+ }
897
+ }
898
+ y0 = clamp(y0, gridBottom, gridTop);
899
+ y1 = clamp(y1, gridBottom, gridTop);
900
+ segs.push(segment(vec2(x, y0), vec2(x, y1)));
901
+ r++;
902
+ }
903
+ }
904
+ return segs;
905
+ }
906
+ function perpendicularPathSegments(path3, interSegmentDistance, length, startOffset = 0) {
907
+ if (!(interSegmentDistance > 0)) return [];
908
+ const lengthFn = typeof length === "function" ? length : () => length;
909
+ const startNorm = (startOffset % 1 + 1) % 1;
910
+ const edges = [];
911
+ for (const ring2 of path3.rings) {
912
+ if (ring2.length < 2) continue;
913
+ const closed = path3.closed;
914
+ let pts = ring2.map((v) => vec2(v.x, v.y));
915
+ if (closed && shoelace(pts) > 0) {
916
+ pts = pts.slice().reverse();
917
+ }
918
+ const indexed = lengthIndex(pts, closed);
919
+ const end = indexed.total;
920
+ if (!(end > 0)) continue;
921
+ const count = Math.max(1, Math.round(end / interSegmentDistance));
922
+ const increment = 1 / count;
923
+ const eps = Math.max(
924
+ end * 1e-4,
925
+ Math.min(interSegmentDistance * 0.35, end * 0.01)
926
+ );
927
+ for (let i = 0; i < count; i++) {
928
+ const posFrac = (startNorm + i * increment) % 1;
929
+ const idx = posFrac * end;
930
+ const idxM = closed ? wrapIndex(idx - eps, end) : clamp(idx - eps, 0, end);
931
+ const idxP = closed ? wrapIndex(idx + eps, end) : clamp(idx + eps, 0, end);
932
+ const pm = indexed.pointAt(idxM);
933
+ const pp = indexed.pointAt(idxP);
934
+ const pc = indexed.pointAt(idx);
935
+ const tx = pp.x - pm.x;
936
+ const ty = pp.y - pm.y;
937
+ const tlen = Math.hypot(tx, ty);
938
+ if (tlen === 0) continue;
939
+ const nx = -ty / tlen;
940
+ const ny = tx / tlen;
941
+ const normalAngle = Math.atan2(ny, nx);
942
+ const L = lengthFn(pc.x, pc.y, posFrac, normalAngle);
943
+ if (!(L > 0)) continue;
944
+ const half = L * 0.5;
945
+ edges.push(
946
+ segment(
947
+ vec2(pc.x - nx * half, pc.y - ny * half),
948
+ vec2(pc.x + nx * half, pc.y + ny * half)
949
+ )
950
+ );
951
+ }
952
+ }
953
+ return edges;
954
+ }
768
955
  function filterByMinLength(segments, minLength) {
769
956
  return segments.filter((s) => segmentLength(s) >= minLength);
770
957
  }
@@ -790,11 +977,84 @@ function segmentsToOpenPaths(segments) {
790
977
  }
791
978
  var segmentSet = {
792
979
  parallelSegments,
980
+ stochasticSegments,
981
+ weaveSegments,
982
+ perpendicularPathSegments,
793
983
  filterByMinLength,
794
984
  filterAxisAligned,
795
985
  segmentLength,
796
986
  segmentsToOpenPaths
797
987
  };
988
+ function floorMod(n, m) {
989
+ return (n % m + m) % m;
990
+ }
991
+ function clamp(v, lo, hi) {
992
+ return Math.max(lo, Math.min(hi, v));
993
+ }
994
+ function wrapIndex(idx, end) {
995
+ if (end <= 0) return 0;
996
+ let x = idx % end;
997
+ if (x < 0) x += end;
998
+ return x;
999
+ }
1000
+ function shoelace(ring2) {
1001
+ let sum = 0;
1002
+ for (let i = 0; i < ring2.length; i++) {
1003
+ const a = ring2[i];
1004
+ const b = ring2[(i + 1) % ring2.length];
1005
+ sum += a.x * b.y - b.x * a.y;
1006
+ }
1007
+ return sum / 2;
1008
+ }
1009
+ function segmentsIntersect(a, b, c, d) {
1010
+ return ccw(a, c, d) !== ccw(b, c, d) && ccw(a, b, c) !== ccw(a, b, d);
1011
+ }
1012
+ function ccw(a, b, c) {
1013
+ return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
1014
+ }
1015
+ function mulberry32(seed) {
1016
+ return function() {
1017
+ let t = seed += 1831565813;
1018
+ t = Math.imul(t ^ t >>> 15, t | 1);
1019
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
1020
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1021
+ };
1022
+ }
1023
+ function lengthIndex(ring2, closed) {
1024
+ const n = ring2.length;
1025
+ const edgeCount = closed ? n : n - 1;
1026
+ const lengths = [];
1027
+ let total = 0;
1028
+ for (let i = 0; i < edgeCount; i++) {
1029
+ const a = ring2[i];
1030
+ const b = ring2[(i + 1) % n];
1031
+ const L = Math.hypot(b.x - a.x, b.y - a.y);
1032
+ lengths.push(L);
1033
+ total += L;
1034
+ }
1035
+ return {
1036
+ total,
1037
+ pointAt(dist2) {
1038
+ let remain = dist2;
1039
+ if (remain <= 0) return { ...ring2[0] };
1040
+ if (remain >= total) return { ...ring2[closed ? 0 : n - 1] };
1041
+ for (let i = 0; i < lengths.length; i++) {
1042
+ const L = lengths[i];
1043
+ if (remain <= L || i === lengths.length - 1) {
1044
+ const t = L < 1e-12 ? 0 : remain / L;
1045
+ const a = ring2[i];
1046
+ const b = ring2[(i + 1) % n];
1047
+ return {
1048
+ x: a.x + (b.x - a.x) * t,
1049
+ y: a.y + (b.y - a.y) * t
1050
+ };
1051
+ }
1052
+ remain -= L;
1053
+ }
1054
+ return { ...ring2[0] };
1055
+ }
1056
+ };
1057
+ }
798
1058
 
799
1059
  // src/segmentSet/clip.ts
800
1060
  function clipSegmentToPath(seg, clip) {
@@ -825,8 +1085,8 @@ function clipSegmentToPath(seg, clip) {
825
1085
  const h0 = hits[i];
826
1086
  const h1 = hits[i + 1];
827
1087
  if (h1.t - h0.t < 1e-9) continue;
828
- const mid3 = vec2((h0.p.x + h1.p.x) / 2, (h0.p.y + h1.p.y) / 2);
829
- if (containsPoint(clip, mid3)) {
1088
+ const mid5 = vec2((h0.p.x + h1.p.x) / 2, (h0.p.y + h1.p.y) / 2);
1089
+ if (containsPoint(clip, mid5)) {
830
1090
  out.push(segment(h0.p, h1.p));
831
1091
  }
832
1092
  }
@@ -854,6 +1114,34 @@ function segmentIntersection(a, b, c, d) {
854
1114
  p: vec2(a.x + t * rX, a.y + t * rY)
855
1115
  };
856
1116
  }
1117
+ function buffer(path3, delta, options = {}) {
1118
+ const subject = pathToPathsD(path3);
1119
+ const join = options.join === "miter" ? JoinType.Miter : options.join === "square" ? JoinType.Square : JoinType.Round;
1120
+ const inflated = inflatePathsD(subject, delta, join, EndType.Polygon);
1121
+ return pathsDToGroup(inflated);
1122
+ }
1123
+ function erosionDilation(p, amount) {
1124
+ const a = Math.abs(amount);
1125
+ if (a < 1e-12) return group([p]);
1126
+ const eroded = buffer(p, -a);
1127
+ if (eroded.paths.length === 0) return eroded;
1128
+ const dilated = [];
1129
+ for (const q of eroded.paths) {
1130
+ dilated.push(...buffer(q, a).paths);
1131
+ }
1132
+ return group(dilated);
1133
+ }
1134
+ function dilationErosion(p, amount) {
1135
+ const a = Math.abs(amount);
1136
+ if (a < 1e-12) return group([p]);
1137
+ const dilated = buffer(p, a);
1138
+ if (dilated.paths.length === 0) return dilated;
1139
+ const eroded = [];
1140
+ for (const q of dilated.paths) {
1141
+ eroded.push(...buffer(q, -a).paths);
1142
+ }
1143
+ return group(eroded);
1144
+ }
857
1145
 
858
1146
  // src/hatch/index.ts
859
1147
  function parallel(path3, options = {}) {
@@ -886,6 +1174,94 @@ function cross(path3, options = {}) {
886
1174
  const b = parallel(path3, { ...options, angle: angle + Math.PI / 2 });
887
1175
  return [...a, ...b];
888
1176
  }
1177
+ function weave(path3, options = {}) {
1178
+ if (!path3.closed || !path3.rings[0]) {
1179
+ throw new Error("hatch.weave requires a closed path");
1180
+ }
1181
+ const bounds2 = pathBounds(path3);
1182
+ const width2 = Math.max(bounds2.maxX - bounds2.minX, 1e-9);
1183
+ const height2 = Math.max(bounds2.maxY - bounds2.minY, 1e-9);
1184
+ const cellSize = options.cellSize ?? Math.max(Math.min(width2, height2) / 12, 1e-6);
1185
+ const weaveOpts = {
1186
+ originX: bounds2.minX,
1187
+ originY: bounds2.minY
1188
+ };
1189
+ if (options.swapColors !== void 0) weaveOpts.swapColors = options.swapColors;
1190
+ if (options.cellFraction !== void 0) {
1191
+ weaveOpts.cellFraction = options.cellFraction;
1192
+ }
1193
+ if (options.extendSingletonsToEdge !== void 0) {
1194
+ weaveOpts.extendSingletonsToEdge = options.extendSingletonsToEdge;
1195
+ }
1196
+ const raw = weaveSegments(
1197
+ width2,
1198
+ height2,
1199
+ cellSize,
1200
+ options.A ?? 1,
1201
+ options.B ?? 1,
1202
+ options.C ?? 1,
1203
+ weaveOpts
1204
+ );
1205
+ return clipSegmentsToPath(raw, path3);
1206
+ }
1207
+ function stochastic(path3, options = {}) {
1208
+ if (!path3.closed || !path3.rings[0]) {
1209
+ throw new Error("hatch.stochastic requires a closed path");
1210
+ }
1211
+ const bounds2 = pathBounds(path3);
1212
+ const width2 = Math.max(bounds2.maxX - bounds2.minX, 1e-9);
1213
+ const height2 = Math.max(bounds2.maxY - bounds2.minY, 1e-9);
1214
+ const count = options.count ?? 40;
1215
+ const minLength = options.length ?? options.minLength ?? 1;
1216
+ const maxLength = options.length ?? options.maxLength ?? Math.min(width2, height2);
1217
+ const raw = stochasticSegments(
1218
+ width2,
1219
+ height2,
1220
+ count,
1221
+ minLength,
1222
+ maxLength,
1223
+ options.seed ?? 1,
1224
+ bounds2.minX,
1225
+ bounds2.minY
1226
+ );
1227
+ return clipSegmentsToPath(raw, path3);
1228
+ }
1229
+ function perpendicular(path3, options = {}) {
1230
+ if (!path3.rings[0]) {
1231
+ throw new Error("hatch.perpendicular requires a path with at least one ring");
1232
+ }
1233
+ const spacing = Math.max(options.spacing ?? 0.15, 1e-9);
1234
+ const length = options.length ?? 0.2;
1235
+ return perpendicularPathSegments(
1236
+ path3,
1237
+ spacing,
1238
+ length,
1239
+ options.startOffset ?? 0
1240
+ );
1241
+ }
1242
+ function concentric(path3, options = {}) {
1243
+ if (!path3.closed || !path3.rings[0]) {
1244
+ throw new Error("hatch.concentric requires a closed path");
1245
+ }
1246
+ const spacing = Math.max(options.spacing ?? 0.15, 1e-9);
1247
+ const bounds2 = pathBounds(path3);
1248
+ const diag = Math.hypot(bounds2.maxX - bounds2.minX, bounds2.maxY - bounds2.minY);
1249
+ const maxCount = options.count ?? Math.max(1, Math.floor(diag / (2 * spacing)));
1250
+ const out = [];
1251
+ for (let i = 1; i <= maxCount; i++) {
1252
+ const shell = buffer(path3, -spacing * i);
1253
+ if (shell.paths.length === 0) break;
1254
+ for (const p of shell.paths) {
1255
+ out.push(
1256
+ path(
1257
+ p.rings.map((r) => r.map((v) => ({ x: v.x, y: v.y }))),
1258
+ true
1259
+ )
1260
+ );
1261
+ }
1262
+ }
1263
+ return out;
1264
+ }
889
1265
  function hatchLineCount(bounds2, spacing, angle) {
890
1266
  const nx = Math.cos(angle + Math.PI / 2);
891
1267
  const ny = Math.sin(angle + Math.PI / 2);
@@ -922,7 +1298,11 @@ function pathBounds(path3) {
922
1298
  }
923
1299
  var hatch = {
924
1300
  parallel,
925
- cross
1301
+ cross,
1302
+ weave,
1303
+ stochastic,
1304
+ perpendicular,
1305
+ concentric
926
1306
  };
927
1307
 
928
1308
  // src/circlePacking/index.ts
@@ -988,7 +1368,7 @@ function centeredLatticeGrid(b, stepX, stepY, inset, endPad) {
988
1368
  return { ox, oy, nX, nY };
989
1369
  }
990
1370
  function stochasticPack(path3, points, minRadius, seed = 1) {
991
- const rng = mulberry32(seed >>> 0);
1371
+ const rng = mulberry322(seed >>> 0);
992
1372
  const b = bounds(path3);
993
1373
  const packing = [];
994
1374
  for (let attempt = 0; attempt < points; attempt++) {
@@ -1029,7 +1409,7 @@ function obstaclePack(path3, obstacles, n, tolerance = 1) {
1029
1409
  function frontChainPack(path3, radiusMin, radiusMax, seed = 1) {
1030
1410
  let rMin = Math.max(1e-6, Math.min(radiusMin, radiusMax));
1031
1411
  let rMax = Math.max(rMin, Math.max(radiusMin, radiusMax));
1032
- const rng = mulberry32(seed >>> 0);
1412
+ const rng = mulberry322(seed >>> 0);
1033
1413
  const b = bounds(path3);
1034
1414
  const packing = [];
1035
1415
  const area2 = Math.max(1e-6, (b.maxX - b.minX) * (b.maxY - b.minY));
@@ -1088,7 +1468,7 @@ function frontChainPack(path3, radiusMin, radiusMax, seed = 1) {
1088
1468
  function repulsionPack(path3, radiusMin, radiusMax, seed = 1, iterations = 80) {
1089
1469
  const rMin = Math.max(1e-6, Math.min(radiusMin, radiusMax));
1090
1470
  const rMax = Math.max(rMin, Math.max(radiusMin, radiusMax));
1091
- const rng = mulberry32(seed >>> 0);
1471
+ const rng = mulberry322(seed >>> 0);
1092
1472
  const b = bounds(path3);
1093
1473
  const area2 = Math.max(1e-6, (b.maxX - b.minX) * (b.maxY - b.minY));
1094
1474
  const avgR = (rMin + rMax) / 2;
@@ -1212,7 +1592,7 @@ function vecInBounds(b, rng) {
1212
1592
  y: b.minY + rng() * (b.maxY - b.minY)
1213
1593
  };
1214
1594
  }
1215
- function mulberry32(a) {
1595
+ function mulberry322(a) {
1216
1596
  return function() {
1217
1597
  let t = a += 1831565813;
1218
1598
  t = Math.imul(t ^ t >>> 15, t | 1);
@@ -1232,36 +1612,6 @@ var circlePacking = {
1232
1612
  circleOverlapsPath,
1233
1613
  circleContainedInPath
1234
1614
  };
1235
- function buffer(path3, delta, options = {}) {
1236
- const subject = pathToPathsD(path3);
1237
- const join = options.join === "miter" ? JoinType.Miter : options.join === "square" ? JoinType.Square : JoinType.Round;
1238
- const inflated = inflatePathsD(subject, delta, join, EndType.Polygon);
1239
- return pathsDToGroup(inflated);
1240
- }
1241
- function erosionDilation(p, amount) {
1242
- const a = Math.abs(amount);
1243
- if (a < 1e-12) return group([p]);
1244
- const eroded = buffer(p, -a);
1245
- if (eroded.paths.length === 0) return eroded;
1246
- const dilated = [];
1247
- for (const q of eroded.paths) {
1248
- dilated.push(...buffer(q, a).paths);
1249
- }
1250
- return group(dilated);
1251
- }
1252
- function dilationErosion(p, amount) {
1253
- const a = Math.abs(amount);
1254
- if (a < 1e-12) return group([p]);
1255
- const dilated = buffer(p, a);
1256
- if (dilated.paths.length === 0) return dilated;
1257
- const eroded = [];
1258
- for (const q of dilated.paths) {
1259
- eroded.push(...buffer(q, -a).paths);
1260
- }
1261
- return group(eroded);
1262
- }
1263
-
1264
- // src/morphology/index.ts
1265
1615
  function simplify(p, epsilon) {
1266
1616
  if (epsilon <= 0 || p.rings.length === 0) return p;
1267
1617
  const rings = [];
@@ -1508,13 +1858,13 @@ function removeSmallHoles(p, minArea2) {
1508
1858
  if (p.rings.length <= 1) return p;
1509
1859
  const exterior = p.rings[0];
1510
1860
  const kept = p.rings.slice(1).filter((hole) => {
1511
- const a = Math.abs(shoelace(hole));
1861
+ const a = Math.abs(shoelace2(hole));
1512
1862
  return a >= minArea2;
1513
1863
  });
1514
1864
  return path([exterior, ...kept], p.closed);
1515
1865
  }
1516
1866
  function generateRandomPoints(p, count, seed = 1) {
1517
- const rng = mulberry322(seed >>> 0);
1867
+ const rng = mulberry323(seed >>> 0);
1518
1868
  const b = bounds(p);
1519
1869
  const out = [];
1520
1870
  let attempts = 0;
@@ -1569,10 +1919,10 @@ function segmentsOnExterior(p) {
1569
1919
  function slice(p, a, b) {
1570
1920
  const dx = b.x - a.x;
1571
1921
  const dy = b.y - a.y;
1572
- const len = Math.hypot(dx, dy);
1573
- if (len < 1e-12) return group([p]);
1574
- const nx = -dy / len;
1575
- const ny = dx / len;
1922
+ const len2 = Math.hypot(dx, dy);
1923
+ if (len2 < 1e-12) return group([p]);
1924
+ const nx = -dy / len2;
1925
+ const ny = dx / len2;
1576
1926
  const bb = bounds(p);
1577
1927
  const span = Math.hypot(bb.maxX - bb.minX, bb.maxY - bb.minY) * 4 + 10;
1578
1928
  const left = halfPlanePoly(a, nx, ny, span);
@@ -1601,9 +1951,9 @@ function intersectionPoints(a, b) {
1601
1951
  for (const sb of segsB) {
1602
1952
  const hit = segmentIntersection2(sa.a, sa.b, sb.a, sb.b);
1603
1953
  if (!hit) continue;
1604
- const key = `${hit.x.toFixed(9)},${hit.y.toFixed(9)}`;
1605
- if (seen.has(key)) continue;
1606
- seen.add(key);
1954
+ const key4 = `${hit.x.toFixed(9)},${hit.y.toFixed(9)}`;
1955
+ if (seen.has(key4)) continue;
1956
+ seen.add(key4);
1607
1957
  out.push(hit);
1608
1958
  }
1609
1959
  }
@@ -1684,7 +2034,7 @@ function pointAtArcLength(ring2, lengths, target) {
1684
2034
  }
1685
2035
  return { ...ring2[0] };
1686
2036
  }
1687
- function shoelace(ring2) {
2037
+ function shoelace2(ring2) {
1688
2038
  let sum = 0;
1689
2039
  for (let i = 0; i < ring2.length; i++) {
1690
2040
  const a = ring2[i];
@@ -1693,7 +2043,7 @@ function shoelace(ring2) {
1693
2043
  }
1694
2044
  return sum / 2;
1695
2045
  }
1696
- function mulberry322(a) {
2046
+ function mulberry323(a) {
1697
2047
  return function() {
1698
2048
  let t = a += 1831565813;
1699
2049
  t = Math.imul(t ^ t >>> 15, t | 1);
@@ -1762,79 +2112,62 @@ var conversion = {
1762
2112
  group
1763
2113
  };
1764
2114
 
1765
- // src/contour/index.ts
1766
- function offsetCurvesOutward(p, distance) {
1767
- return buffer(p, Math.abs(distance));
1768
- }
1769
- function offsetCurvesInward(p, distance) {
1770
- return buffer(p, -Math.abs(distance));
1771
- }
1772
- var contour = {
1773
- offsetCurvesOutward,
1774
- offsetCurvesInward
1775
- };
1776
-
1777
- // src/hull/index.ts
1778
- function convexHull(points) {
1779
- const pts = uniquePoints(points);
1780
- if (pts.length < 3) return polygon(pts);
1781
- pts.sort((a, b) => a.x === b.x ? a.y - b.y : a.x - b.x);
1782
- const lower = [];
1783
- for (const p of pts) {
1784
- while (lower.length >= 2 && cross2(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
1785
- lower.pop();
1786
- }
1787
- lower.push(p);
2115
+ // src/pointSet/hilbert.ts
2116
+ function xy2d(order, x, y) {
2117
+ let rx;
2118
+ let ry;
2119
+ let s;
2120
+ let d = 0;
2121
+ for (s = 1 << order - 1; s > 0; s >>= 1) {
2122
+ rx = (x & s) > 0 ? 1 : 0;
2123
+ ry = (y & s) > 0 ? 1 : 0;
2124
+ d += s * s * (3 * rx ^ ry);
2125
+ ({ x, y } = rot(s, x, y, rx, ry));
1788
2126
  }
1789
- const upper = [];
1790
- for (let i = pts.length - 1; i >= 0; i--) {
1791
- const p = pts[i];
1792
- while (upper.length >= 2 && cross2(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
1793
- upper.pop();
2127
+ return d;
2128
+ }
2129
+ function rot(n, x, y, rx, ry) {
2130
+ if (ry === 0) {
2131
+ if (rx === 1) {
2132
+ x = n - 1 - x;
2133
+ y = n - 1 - y;
1794
2134
  }
1795
- upper.push(p);
2135
+ return { x: y, y: x };
1796
2136
  }
1797
- lower.pop();
1798
- upper.pop();
1799
- return polygon([...lower, ...upper]);
1800
- }
1801
- function convexHullPath(p) {
1802
- const pts = [];
1803
- for (const ring2 of p.rings) pts.push(...ring2);
1804
- return convexHull(pts);
1805
- }
1806
- function boundingBox(p) {
1807
- const b = bounds(p);
1808
- return polygon([
1809
- { x: b.minX, y: b.minY },
1810
- { x: b.maxX, y: b.minY },
1811
- { x: b.maxX, y: b.maxY },
1812
- { x: b.minX, y: b.maxY }
1813
- ]);
1814
- }
1815
- function cross2(o, a, b) {
1816
- return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
2137
+ return { x, y };
1817
2138
  }
1818
- function uniquePoints(points) {
1819
- const seen = /* @__PURE__ */ new Set();
1820
- const out = [];
2139
+ function hilbertSort(points, order = 10) {
2140
+ if (points.length === 0) return [];
2141
+ let minX = Infinity;
2142
+ let minY = Infinity;
2143
+ let maxX = -Infinity;
2144
+ let maxY = -Infinity;
1821
2145
  for (const p of points) {
1822
- const key = `${p.x},${p.y}`;
1823
- if (seen.has(key)) continue;
1824
- seen.add(key);
1825
- out.push({ x: p.x, y: p.y });
2146
+ minX = Math.min(minX, p.x);
2147
+ minY = Math.min(minY, p.y);
2148
+ maxX = Math.max(maxX, p.x);
2149
+ maxY = Math.max(maxY, p.y);
1826
2150
  }
1827
- return out;
1828
- }
1829
- var hull = {
1830
- convexHull,
1831
- convexHullPath,
1832
- boundingBox
1833
- };
1834
-
2151
+ const span = Math.max(maxX - minX, maxY - minY, 1e-9);
2152
+ const n = 1 << order;
2153
+ const scored = points.map((p) => {
2154
+ const x = Math.min(
2155
+ n - 1,
2156
+ Math.max(0, Math.floor((p.x - minX) / span * (n - 1)))
2157
+ );
2158
+ const y = Math.min(
2159
+ n - 1,
2160
+ Math.max(0, Math.floor((p.y - minY) / span * (n - 1)))
2161
+ );
2162
+ return { p, h: xy2d(order, x, y) };
2163
+ });
2164
+ scored.sort((a, b) => a.h - b.h);
2165
+ return scored.map((s) => s.p);
2166
+ }
2167
+
1835
2168
  // src/pointSet/index.ts
1836
2169
  function random(count, minX, minY, maxX, maxY, seed = 1) {
1837
- const rng = mulberry323(seed >>> 0);
2170
+ const rng = mulberry324(seed >>> 0);
1838
2171
  const out = [];
1839
2172
  for (let i = 0; i < count; i++) {
1840
2173
  out.push({
@@ -1879,7 +2212,7 @@ function ring(count, center, radius) {
1879
2212
  }
1880
2213
  return out;
1881
2214
  }
1882
- function mulberry323(a) {
2215
+ function mulberry324(a) {
1883
2216
  return function() {
1884
2217
  let t = a += 1831565813;
1885
2218
  t = Math.imul(t ^ t >>> 15, t | 1);
@@ -1888,7 +2221,7 @@ function mulberry323(a) {
1888
2221
  };
1889
2222
  }
1890
2223
  function poisson(minDistance, minX, minY, maxX, maxY, seed = 1, maxAttempts = 30) {
1891
- const rng = mulberry323(seed >>> 0);
2224
+ const rng = mulberry324(seed >>> 0);
1892
2225
  const r = Math.max(minDistance, 1e-9);
1893
2226
  const cell = r / Math.SQRT2;
1894
2227
  const w = maxX - minX;
@@ -1959,13 +2292,87 @@ function prunePointsWithinDistance(points, minDistance) {
1959
2292
  }
1960
2293
  return out;
1961
2294
  }
2295
+ function findShortestTour(points) {
2296
+ if (points.length === 0) return polygon([]);
2297
+ if (points.length === 1) return polygon([{ ...points[0] }]);
2298
+ if (points.length === 2) {
2299
+ return polyline([
2300
+ { ...points[0] },
2301
+ { ...points[1] },
2302
+ { ...points[0] }
2303
+ ]);
2304
+ }
2305
+ const tour = nearestNeighborTour(points);
2306
+ twoOpt(tour);
2307
+ return polygon(tour);
2308
+ }
2309
+ function nearestNeighborTour(points) {
2310
+ const remaining = points.map((p) => ({ ...p }));
2311
+ let startI = 0;
2312
+ for (let i = 1; i < remaining.length; i++) {
2313
+ const a = remaining[i];
2314
+ const b = remaining[startI];
2315
+ if (a.x < b.x || a.x === b.x && a.y < b.y) startI = i;
2316
+ }
2317
+ const ordered = [remaining.splice(startI, 1)[0]];
2318
+ while (remaining.length) {
2319
+ const last = ordered[ordered.length - 1];
2320
+ let bestI = 0;
2321
+ let bestD = Infinity;
2322
+ for (let i = 0; i < remaining.length; i++) {
2323
+ const d = Math.hypot(
2324
+ remaining[i].x - last.x,
2325
+ remaining[i].y - last.y
2326
+ );
2327
+ if (d < bestD) {
2328
+ bestD = d;
2329
+ bestI = i;
2330
+ }
2331
+ }
2332
+ ordered.push(remaining.splice(bestI, 1)[0]);
2333
+ }
2334
+ return ordered;
2335
+ }
2336
+ function twoOpt(tour) {
2337
+ const n = tour.length;
2338
+ if (n < 4) return;
2339
+ let improved = true;
2340
+ while (improved) {
2341
+ improved = false;
2342
+ for (let i = 0; i < n - 1; i++) {
2343
+ for (let j = i + 2; j < n; j++) {
2344
+ if (i === 0 && j === n - 1) continue;
2345
+ const a = tour[i];
2346
+ const b = tour[(i + 1) % n];
2347
+ const c = tour[j];
2348
+ const d = tour[(j + 1) % n];
2349
+ const before = Math.hypot(a.x - b.x, a.y - b.y) + Math.hypot(c.x - d.x, c.y - d.y);
2350
+ const after = Math.hypot(a.x - c.x, a.y - c.y) + Math.hypot(b.x - d.x, b.y - d.y);
2351
+ if (after + 1e-12 < before) {
2352
+ let lo = i + 1;
2353
+ let hi = j;
2354
+ while (lo < hi) {
2355
+ const tmp = tour[lo];
2356
+ tour[lo] = tour[hi];
2357
+ tour[hi] = tmp;
2358
+ lo++;
2359
+ hi--;
2360
+ }
2361
+ improved = true;
2362
+ }
2363
+ }
2364
+ }
2365
+ }
2366
+ }
1962
2367
  var pointSet = {
1963
2368
  random,
1964
2369
  squareGrid,
1965
2370
  hexGrid,
1966
2371
  ring,
1967
2372
  poisson,
1968
- prunePointsWithinDistance
2373
+ prunePointsWithinDistance,
2374
+ hilbertSort,
2375
+ findShortestTour
1969
2376
  };
1970
2377
 
1971
2378
  // src/triangulation/index.ts
@@ -2121,9 +2528,9 @@ function longestEdgeMidpoint(a, b, c) {
2121
2528
  let best = edges[0];
2122
2529
  let bestLen = -1;
2123
2530
  for (const e of edges) {
2124
- const len = Math.hypot(e[0].x - e[1].x, e[0].y - e[1].y);
2125
- if (len > bestLen) {
2126
- bestLen = len;
2531
+ const len2 = Math.hypot(e[0].x - e[1].x, e[0].y - e[1].y);
2532
+ if (len2 > bestLen) {
2533
+ bestLen = len2;
2127
2534
  best = e;
2128
2535
  }
2129
2536
  }
@@ -2138,6 +2545,205 @@ var triangulation = {
2138
2545
  refine
2139
2546
  };
2140
2547
 
2548
+ // src/contour/dissolve.ts
2549
+ function key(v, digits = 9) {
2550
+ return `${v.x.toFixed(digits)},${v.y.toFixed(digits)}`;
2551
+ }
2552
+ function same(a, b, eps = 1e-9) {
2553
+ return Math.abs(a.x - b.x) <= eps && Math.abs(a.y - b.y) <= eps;
2554
+ }
2555
+ function dissolveSegments(segments) {
2556
+ const segs = segments.filter((s) => !same(s.a, s.b));
2557
+ if (segs.length === 0) return group([]);
2558
+ const adj = /* @__PURE__ */ new Map();
2559
+ const pts = /* @__PURE__ */ new Map();
2560
+ const add = (a, b, segIdx) => {
2561
+ const ka = key(a);
2562
+ const kb = key(b);
2563
+ pts.set(ka, a);
2564
+ pts.set(kb, b);
2565
+ if (!adj.has(ka)) adj.set(ka, []);
2566
+ if (!adj.has(kb)) adj.set(kb, []);
2567
+ adj.get(ka).push({ segIdx, other: kb });
2568
+ adj.get(kb).push({ segIdx, other: ka });
2569
+ };
2570
+ for (let i = 0; i < segs.length; i++) {
2571
+ add(segs[i].a, segs[i].b, i);
2572
+ }
2573
+ const used = /* @__PURE__ */ new Set();
2574
+ const paths = [];
2575
+ const unusedDegree = (k) => (adj.get(k) ?? []).filter((e) => !used.has(e.segIdx)).length;
2576
+ const nextUnused = (k) => (adj.get(k) ?? []).find((e) => !used.has(e.segIdx));
2577
+ const starts = [...adj.keys()].sort(
2578
+ (a, b) => unusedDegree(a) - unusedDegree(b)
2579
+ );
2580
+ for (const start of starts) {
2581
+ let link = nextUnused(start);
2582
+ while (link) {
2583
+ const chain = [pts.get(start)];
2584
+ used.add(link.segIdx);
2585
+ let next = link.other;
2586
+ chain.push(pts.get(next));
2587
+ while (unusedDegree(next) === 1) {
2588
+ const e = nextUnused(next);
2589
+ if (!e) break;
2590
+ used.add(e.segIdx);
2591
+ next = e.other;
2592
+ chain.push(pts.get(next));
2593
+ if (next === start) break;
2594
+ }
2595
+ if (chain.length >= 2) paths.push(polyline(chain));
2596
+ link = nextUnused(start);
2597
+ }
2598
+ }
2599
+ for (let i = 0; i < segs.length; i++) {
2600
+ if (used.has(i)) continue;
2601
+ const s = segs[i];
2602
+ paths.push(polyline([s.a, s.b]));
2603
+ }
2604
+ return group(paths);
2605
+ }
2606
+
2607
+ // src/contour/chordalAxis.ts
2608
+ function mid(a, b) {
2609
+ return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
2610
+ }
2611
+ function len(a, b) {
2612
+ return Math.hypot(b.x - a.x, b.y - a.y);
2613
+ }
2614
+ function edgeKey(a, b) {
2615
+ const ka = `${a.x.toFixed(8)},${a.y.toFixed(8)}`;
2616
+ const kb = `${b.x.toFixed(8)},${b.y.toFixed(8)}`;
2617
+ return ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`;
2618
+ }
2619
+ function chordalAxis(shape) {
2620
+ if (!shape.closed || shape.rings.length === 0) return dissolveSegments([]);
2621
+ const b = boundsSpan(shape);
2622
+ const densified = densify(shape, Math.max(b / 40, 1e-3));
2623
+ const tris = delaunayTriangulation(densified);
2624
+ if (tris.length === 0) return dissolveSegments([]);
2625
+ const edgeCount = /* @__PURE__ */ new Map();
2626
+ const triEdges = [];
2627
+ for (const t of tris) {
2628
+ const r = t.rings[0];
2629
+ if (!r || r.length < 3) continue;
2630
+ const a = r[0];
2631
+ const b0 = r[1];
2632
+ const c = r[2];
2633
+ triEdges.push([a, b0, c]);
2634
+ for (const [u, v] of [
2635
+ [a, b0],
2636
+ [b0, c],
2637
+ [c, a]
2638
+ ]) {
2639
+ const k = edgeKey(u, v);
2640
+ edgeCount.set(k, (edgeCount.get(k) ?? 0) + 1);
2641
+ }
2642
+ }
2643
+ const isBoundary = (u, v) => (edgeCount.get(edgeKey(u, v)) ?? 0) <= 1;
2644
+ const segs = [];
2645
+ for (const [a, b0, c] of triEdges) {
2646
+ const edges = [
2647
+ [a, b0],
2648
+ [b0, c],
2649
+ [c, a]
2650
+ ];
2651
+ const interior = edges.filter(([u, v]) => !isBoundary(u, v));
2652
+ const degree = interior.length;
2653
+ if (degree === 1) {
2654
+ const [u, v] = interior[0];
2655
+ const cen = centroid({ rings: [[a, b0, c]]});
2656
+ segs.push(segment(cen, mid(u, v)));
2657
+ } else if (degree === 2) {
2658
+ const m0 = mid(interior[0][0], interior[0][1]);
2659
+ const m1 = mid(interior[1][0], interior[1][1]);
2660
+ segs.push(segment(m0, m1));
2661
+ } else if (degree === 3) {
2662
+ const ranked = edges.map(([u, v]) => ({ u, v, l: len(u, v) })).sort((x, y) => y.l - x.l);
2663
+ const longest = ranked[0];
2664
+ const shortA = ranked[1];
2665
+ const shortB = ranked[2];
2666
+ const mL = mid(longest.u, longest.v);
2667
+ segs.push(segment(mid(shortA.u, shortA.v), mL));
2668
+ segs.push(segment(mid(shortB.u, shortB.v), mL));
2669
+ }
2670
+ }
2671
+ return dissolveSegments(segs);
2672
+ }
2673
+ function boundsSpan(p) {
2674
+ let minX = Infinity;
2675
+ let minY = Infinity;
2676
+ let maxX = -Infinity;
2677
+ let maxY = -Infinity;
2678
+ for (const ring2 of p.rings) {
2679
+ for (const v of ring2) {
2680
+ minX = Math.min(minX, v.x);
2681
+ minY = Math.min(minY, v.y);
2682
+ maxX = Math.max(maxX, v.x);
2683
+ maxY = Math.max(maxY, v.y);
2684
+ }
2685
+ }
2686
+ return Math.max(maxX - minX, maxY - minY, 1);
2687
+ }
2688
+
2689
+ // src/hull/index.ts
2690
+ function convexHull(points) {
2691
+ const pts = uniquePoints(points);
2692
+ if (pts.length < 3) return polygon(pts);
2693
+ pts.sort((a, b) => a.x === b.x ? a.y - b.y : a.x - b.x);
2694
+ const lower = [];
2695
+ for (const p of pts) {
2696
+ while (lower.length >= 2 && cross2(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
2697
+ lower.pop();
2698
+ }
2699
+ lower.push(p);
2700
+ }
2701
+ const upper = [];
2702
+ for (let i = pts.length - 1; i >= 0; i--) {
2703
+ const p = pts[i];
2704
+ while (upper.length >= 2 && cross2(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
2705
+ upper.pop();
2706
+ }
2707
+ upper.push(p);
2708
+ }
2709
+ lower.pop();
2710
+ upper.pop();
2711
+ return polygon([...lower, ...upper]);
2712
+ }
2713
+ function convexHullPath(p) {
2714
+ const pts = [];
2715
+ for (const ring2 of p.rings) pts.push(...ring2);
2716
+ return convexHull(pts);
2717
+ }
2718
+ function boundingBox(p) {
2719
+ const b = bounds(p);
2720
+ return polygon([
2721
+ { x: b.minX, y: b.minY },
2722
+ { x: b.maxX, y: b.minY },
2723
+ { x: b.maxX, y: b.maxY },
2724
+ { x: b.minX, y: b.maxY }
2725
+ ]);
2726
+ }
2727
+ function cross2(o, a, b) {
2728
+ return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
2729
+ }
2730
+ function uniquePoints(points) {
2731
+ const seen = /* @__PURE__ */ new Set();
2732
+ const out = [];
2733
+ for (const p of points) {
2734
+ const key4 = `${p.x},${p.y}`;
2735
+ if (seen.has(key4)) continue;
2736
+ seen.add(key4);
2737
+ out.push({ x: p.x, y: p.y });
2738
+ }
2739
+ return out;
2740
+ }
2741
+ var hull = {
2742
+ convexHull,
2743
+ convexHullPath,
2744
+ boundingBox
2745
+ };
2746
+
2141
2747
  // src/optimisation/index.ts
2142
2748
  function envelope(p) {
2143
2749
  return boundingBox(p);
@@ -2170,12 +2776,12 @@ function maximumInscribedAARectangle(p) {
2170
2776
  let hi = Math.min(w, h) / 2;
2171
2777
  let best = hi;
2172
2778
  for (let i = 0; i < 40; i++) {
2173
- const mid3 = (lo + hi) / 2;
2174
- if (fits(mid3)) {
2175
- best = mid3;
2176
- hi = mid3;
2779
+ const mid5 = (lo + hi) / 2;
2780
+ if (fits(mid5)) {
2781
+ best = mid5;
2782
+ hi = mid5;
2177
2783
  } else {
2178
- lo = mid3;
2784
+ lo = mid5;
2179
2785
  }
2180
2786
  }
2181
2787
  const m = best;
@@ -2320,399 +2926,649 @@ var optimisation = {
2320
2926
  farthestPointPair,
2321
2927
  minimumBoundingCircle
2322
2928
  };
2323
- function compoundVoronoi(sites, options = {}) {
2324
- if (sites.length === 0) return group([]);
2325
- if (sites.length === 1) {
2326
- const b2 = options.bounds ?? padBounds(sites, 1);
2327
- return group([
2328
- polygon([
2329
- { x: b2.minX, y: b2.minY },
2330
- { x: b2.maxX, y: b2.minY },
2331
- { x: b2.maxX, y: b2.maxY },
2332
- { x: b2.minX, y: b2.maxY }
2333
- ])
2334
- ]);
2929
+
2930
+ // src/contour/boundaryDistance.ts
2931
+ function distanceToBoundary2(path3, p) {
2932
+ let best = Infinity;
2933
+ for (const ring2 of path3.rings) {
2934
+ if (ring2.length < 2) continue;
2935
+ const n = ring2.length;
2936
+ const closed = path3.closed;
2937
+ const limit = closed ? n : n - 1;
2938
+ for (let i = 0; i < limit; i++) {
2939
+ const a = ring2[i];
2940
+ const b = ring2[(i + 1) % n];
2941
+ best = Math.min(best, distPointSeg(p, a, b));
2942
+ }
2335
2943
  }
2336
- const b = options.bounds ?? padBounds(sites, 0.1);
2944
+ return best;
2945
+ }
2946
+ function sampleBoundary(path3, maxSegLen) {
2947
+ const pts = [];
2948
+ const lim = Math.max(maxSegLen, 1e-9);
2949
+ for (const ring2 of path3.rings) {
2950
+ if (ring2.length < 2) continue;
2951
+ const n = ring2.length;
2952
+ const limit = path3.closed ? n : n - 1;
2953
+ for (let i = 0; i < limit; i++) {
2954
+ const a = ring2[i];
2955
+ const b = ring2[(i + 1) % n];
2956
+ const len2 = Math.hypot(b.x - a.x, b.y - a.y);
2957
+ const steps = Math.max(1, Math.ceil(len2 / lim));
2958
+ for (let s = 0; s < steps; s++) {
2959
+ const t = s / steps;
2960
+ pts.push({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });
2961
+ }
2962
+ }
2963
+ }
2964
+ return pts;
2965
+ }
2966
+ function distPointSeg(p, a, b) {
2967
+ const dx = b.x - a.x;
2968
+ const dy = b.y - a.y;
2969
+ const len2 = dx * dx + dy * dy;
2970
+ if (len2 < 1e-18) return Math.hypot(p.x - a.x, p.y - a.y);
2971
+ let t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;
2972
+ t = Math.max(0, Math.min(1, t));
2973
+ return Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy));
2974
+ }
2975
+
2976
+ // src/contour/medialAxis.ts
2977
+ function medialAxis(shape, axialThreshold = 0, distanceThreshold = 0, areaThreshold = 0) {
2978
+ const g = buildMedialAxisGraph(shape);
2979
+ if (!g) return dissolveSegments([]);
2980
+ const kept = pruneEdges(g, axialThreshold, distanceThreshold, areaThreshold);
2981
+ const segs = kept.map((e) => {
2982
+ const a = g.nodes[e.head].position;
2983
+ const b = g.nodes[e.tail].position;
2984
+ return segment(a, b);
2985
+ });
2986
+ return dissolveSegments(segs);
2987
+ }
2988
+ function buildMedialAxisGraph(shape) {
2989
+ if (!shape.closed || shape.rings.length === 0) return null;
2990
+ const b = bounds(shape);
2991
+ const span = Math.max(b.maxX - b.minX, b.maxY - b.minY, 1);
2992
+ const densified = densify(shape, Math.max(span / 60, 1e-3));
2993
+ const samples = sampleBoundary(densified, Math.max(span / 80, 1e-3));
2994
+ if (samples.length < 3) return null;
2995
+ const uniq = [];
2996
+ const seen = /* @__PURE__ */ new Set();
2997
+ for (const p of samples) {
2998
+ const k = `${p.x.toFixed(6)},${p.y.toFixed(6)}`;
2999
+ if (seen.has(k)) continue;
3000
+ seen.add(k);
3001
+ uniq.push(p);
3002
+ }
3003
+ if (uniq.length < 3) return null;
2337
3004
  const delaunay = Delaunay.from(
2338
- sites,
3005
+ uniq,
2339
3006
  (d) => d.x,
2340
3007
  (d) => d.y
2341
3008
  );
2342
- const voronoi2 = delaunay.voronoi([b.minX, b.minY, b.maxX, b.maxY]);
2343
- const paths = [];
2344
- for (let i = 0; i < sites.length; i++) {
3009
+ const pad = span * 0.5;
3010
+ const voronoi2 = delaunay.voronoi([
3011
+ b.minX - pad,
3012
+ b.minY - pad,
3013
+ b.maxX + pad,
3014
+ b.maxY + pad
3015
+ ]);
3016
+ const raw = [];
3017
+ const edgeSeen = /* @__PURE__ */ new Set();
3018
+ for (let i = 0; i < uniq.length; i++) {
2345
3019
  const poly = voronoi2.cellPolygon(i);
2346
- if (!poly || poly.length < 3) continue;
2347
- const ring2 = poly.slice(0, -1).map(([x, y]) => ({ x, y }));
2348
- if (ring2.length >= 3) paths.push(polygon(ring2));
3020
+ if (!poly || poly.length < 2) continue;
3021
+ for (let j = 0; j < poly.length - 1; j++) {
3022
+ const p0 = poly[j];
3023
+ const p1 = poly[j + 1];
3024
+ const a = { x: p0[0], y: p0[1] };
3025
+ const bb = { x: p1[0], y: p1[1] };
3026
+ if (!containsPoint(shape, a) || !containsPoint(shape, bb)) continue;
3027
+ const ek = edgeKey2(a, bb);
3028
+ if (edgeSeen.has(ek)) continue;
3029
+ edgeSeen.add(ek);
3030
+ const mid5 = { x: (a.x + bb.x) / 2, y: (a.y + bb.y) / 2 };
3031
+ const site = uniq[i];
3032
+ const radius = Math.hypot(mid5.x - site.x, mid5.y - site.y);
3033
+ raw.push({ a, b: bb, radius });
3034
+ }
2349
3035
  }
2350
- return group(paths);
2351
- }
2352
- function innerVoronoi(sites, container, options = {}) {
2353
- const b = options.bounds ?? {
2354
- ...bounds(container)
3036
+ if (raw.length === 0) return null;
3037
+ const nodeIndex = /* @__PURE__ */ new Map();
3038
+ const nodes = [];
3039
+ const ensure = (p, radius) => {
3040
+ const k = key2(p);
3041
+ let id = nodeIndex.get(k);
3042
+ if (id !== void 0) {
3043
+ nodes[id].radius = Math.max(nodes[id].radius, radius);
3044
+ return id;
3045
+ }
3046
+ id = nodes.length;
3047
+ nodeIndex.set(k, id);
3048
+ nodes.push({
3049
+ id,
3050
+ position: p,
3051
+ radius,
3052
+ parent: null,
3053
+ children: [],
3054
+ rootDist: 0,
3055
+ featureArea: Math.PI * radius * radius
3056
+ });
3057
+ return id;
2355
3058
  };
2356
- const cells = compoundVoronoi(sites, { bounds: b });
2357
- const clipped = [];
2358
- for (const cell of cells.paths) {
2359
- clipped.push(...intersect(cell, container).paths);
3059
+ const undirected = [];
3060
+ for (const e of raw) {
3061
+ const midR = e.radius;
3062
+ const u = ensure(e.a, midR);
3063
+ const v = ensure(e.b, midR);
3064
+ if (u === v) continue;
3065
+ const length = Math.hypot(
3066
+ nodes[u].position.x - nodes[v].position.x,
3067
+ nodes[u].position.y - nodes[v].position.y
3068
+ );
3069
+ const axial = length > 1e-12 ? Math.abs(nodes[u].radius - nodes[v].radius) / length : 0;
3070
+ undirected.push({ u, v, axial, length });
2360
3071
  }
2361
- return group(clipped);
2362
- }
2363
- function innerVoronoiRaw(sites, options = {}) {
2364
- return compoundVoronoi(sites, options);
3072
+ const mic = maximumInscribedCircle(shape, Math.max(span * 0.01, 0.1));
3073
+ const rootPos = mic ? { x: mic.x, y: mic.y } : {
3074
+ x: (b.minX + b.maxX) / 2,
3075
+ y: (b.minY + b.maxY) / 2
3076
+ };
3077
+ let root = 0;
3078
+ let bestD = Infinity;
3079
+ for (const n of nodes) {
3080
+ const d = Math.hypot(n.position.x - rootPos.x, n.position.y - rootPos.y);
3081
+ if (d < bestD) {
3082
+ bestD = d;
3083
+ root = n.id;
3084
+ }
3085
+ }
3086
+ const adj = /* @__PURE__ */ new Map();
3087
+ for (const e of undirected) {
3088
+ if (!adj.has(e.u)) adj.set(e.u, []);
3089
+ if (!adj.has(e.v)) adj.set(e.v, []);
3090
+ adj.get(e.u).push({ to: e.v, axial: e.axial, length: e.length });
3091
+ adj.get(e.v).push({ to: e.u, axial: e.axial, length: e.length });
3092
+ }
3093
+ const edges = [];
3094
+ const visited = /* @__PURE__ */ new Set([root]);
3095
+ const queue = [root];
3096
+ nodes[root].rootDist = 0;
3097
+ while (queue.length) {
3098
+ const u = queue.shift();
3099
+ for (const link of adj.get(u) ?? []) {
3100
+ if (visited.has(link.to)) continue;
3101
+ visited.add(link.to);
3102
+ nodes[link.to].parent = u;
3103
+ nodes[u].children.push(link.to);
3104
+ nodes[link.to].rootDist = nodes[u].rootDist + link.length;
3105
+ edges.push({
3106
+ head: u,
3107
+ tail: link.to,
3108
+ axial: link.axial,
3109
+ length: link.length
3110
+ });
3111
+ queue.push(link.to);
3112
+ }
3113
+ }
3114
+ const post = [];
3115
+ const stack = [root];
3116
+ const seenN = /* @__PURE__ */ new Set();
3117
+ while (stack.length) {
3118
+ const u = stack.pop();
3119
+ if (seenN.has(u)) {
3120
+ post.push(u);
3121
+ continue;
3122
+ }
3123
+ seenN.add(u);
3124
+ stack.push(u);
3125
+ for (const c of nodes[u].children) stack.push(c);
3126
+ }
3127
+ for (const u of post) {
3128
+ let area2 = nodes[u].featureArea;
3129
+ for (const c of nodes[u].children) area2 += nodes[c].featureArea;
3130
+ nodes[u].featureArea = area2;
3131
+ }
3132
+ return { nodes, edges, root };
3133
+ }
3134
+ function pruneEdges(g, axialT, distT, areaT) {
3135
+ const maxDist = Math.max(...g.nodes.map((n) => n.rootDist), 1e-9);
3136
+ const maxArea2 = Math.max(...g.nodes.map((n) => n.featureArea), 1e-9);
3137
+ const maxAxial = Math.max(...g.edges.map((e) => e.axial), 1e-9);
3138
+ const aT = clamp01(axialT);
3139
+ const dT = clamp01(distT);
3140
+ const arT = clamp01(areaT);
3141
+ return g.edges.filter((e) => {
3142
+ const tail = g.nodes[e.tail];
3143
+ if (aT > 0 && e.axial / maxAxial < aT * 0.15 && aT > 0.05) {
3144
+ if (tail.children.length === 0 && e.axial / maxAxial < aT) return false;
3145
+ }
3146
+ if (dT > 0) {
3147
+ const nd = tail.rootDist / maxDist;
3148
+ if (tail.children.length === 0 && nd > 1 - dT && dT > 0) ;
3149
+ if (tail.children.length === 0 && 1 - nd < dT * 0.5 && dT >= 0.5) {
3150
+ return false;
3151
+ }
3152
+ if (tail.children.length === 0 && nd * dT > 0.85) return false;
3153
+ }
3154
+ if (arT > 0) {
3155
+ const na = tail.featureArea / maxArea2;
3156
+ if (na < arT) return false;
3157
+ }
3158
+ if (aT > 0 && e.axial / maxAxial > 1 - aT * 0.5 && tail.children.length === 0) {
3159
+ return false;
3160
+ }
3161
+ return true;
3162
+ });
2365
3163
  }
2366
- function padBounds(sites, padRatio) {
2367
- let minX = Infinity;
2368
- let minY = Infinity;
2369
- let maxX = -Infinity;
2370
- let maxY = -Infinity;
2371
- for (const s of sites) {
2372
- minX = Math.min(minX, s.x);
2373
- minY = Math.min(minY, s.y);
2374
- maxX = Math.max(maxX, s.x);
2375
- maxY = Math.max(maxY, s.y);
3164
+ function centerLine(shape, straightnessWeighting = 0.7, smoothing = 50) {
3165
+ const g = buildMedialAxisGraph(shape);
3166
+ if (!g || g.nodes.length < 2) {
3167
+ return polyline([]);
2376
3168
  }
2377
- const pad = Math.max(maxX - minX, maxY - minY, 1) * padRatio + 1e-6;
2378
- return {
2379
- minX: minX - pad,
2380
- minY: minY - pad,
2381
- maxX: maxX + pad,
2382
- maxY: maxY + pad
3169
+ const root = g.nodes[g.root];
3170
+ const childRoots = root.children;
3171
+ const leavesOf = (start) => {
3172
+ const out = [];
3173
+ const stack = [start];
3174
+ while (stack.length) {
3175
+ const u = stack.pop();
3176
+ const n = g.nodes[u];
3177
+ if (n.children.length === 0) out.push(n);
3178
+ else stack.push(...n.children);
3179
+ }
3180
+ return out;
2383
3181
  };
2384
- }
2385
- var voronoi = {
2386
- compoundVoronoi,
2387
- innerVoronoi,
2388
- innerVoronoiRaw
2389
- };
2390
-
2391
- // src/construction/index.ts
2392
- function createCircle(cx, cy, radius, segments = 64) {
2393
- return createRegularPolygon(cx, cy, radius, Math.max(3, Math.floor(segments)));
2394
- }
2395
- function createRect(x, y, width2, height2) {
2396
- return polygon([
2397
- { x, y },
2398
- { x: x + width2, y },
2399
- { x: x + width2, y: y + height2 },
2400
- { x, y: y + height2 }
2401
- ]);
2402
- }
2403
- function createRegularPolygon(cx, cy, radius, sides, rotation = -Math.PI / 2) {
2404
- const n = Math.max(3, Math.floor(sides));
2405
- const ring2 = [];
2406
- for (let i = 0; i < n; i++) {
2407
- const t = rotation + i / n * Math.PI * 2;
2408
- ring2.push({ x: cx + Math.cos(t) * radius, y: cy + Math.sin(t) * radius });
3182
+ let bestPath = [];
3183
+ if (childRoots.length <= 1) {
3184
+ bestPath = longestPathNodes(g);
3185
+ } else if (childRoots.length === 2) {
3186
+ bestPath = pathBetweenLeaves(
3187
+ g,
3188
+ leavesOf(childRoots[0]),
3189
+ leavesOf(childRoots[1]),
3190
+ root,
3191
+ straightnessWeighting
3192
+ );
3193
+ } else {
3194
+ const groups = childRoots.map(leavesOf);
3195
+ let bestW = -Infinity;
3196
+ for (let i = 0; i < groups.length; i++) {
3197
+ for (let j = i + 1; j < groups.length; j++) {
3198
+ const { path: p, weight } = bestLeafPair(
3199
+ g,
3200
+ groups[i],
3201
+ groups[j],
3202
+ root,
3203
+ straightnessWeighting
3204
+ );
3205
+ if (weight > bestW) {
3206
+ bestW = weight;
3207
+ bestPath = p;
3208
+ }
3209
+ }
3210
+ }
2409
3211
  }
2410
- return polygon(ring2);
3212
+ if (bestPath.length < 2) bestPath = longestPathNodes(g);
3213
+ const pts = bestPath.map((id) => g.nodes[id].position);
3214
+ return polyline(gaussianSmooth(pts, smoothing));
3215
+ }
3216
+ function bestLeafPair(g, a, b, root, straightness) {
3217
+ let bestW = -Infinity;
3218
+ let best = [];
3219
+ for (const d1 of a) {
3220
+ for (const d2 of b) {
3221
+ const angle = angleBetween(d1.position, root.position, d2.position);
3222
+ const aw = Math.pow(1 + angle, straightness);
3223
+ const weight = (d1.rootDist + d2.rootDist) * Math.max(aw - 1, 1);
3224
+ if (weight > bestW) {
3225
+ bestW = weight;
3226
+ best = [...pathToRoot(g, d1.id), ...pathToRoot(g, d2.id).reverse().slice(1)];
3227
+ }
3228
+ }
3229
+ }
3230
+ return { path: best, weight: bestW };
2411
3231
  }
2412
- function createRing(cx, cy, outerRadius, innerRadius, segments = 64) {
2413
- const outer = createCircle(cx, cy, outerRadius, segments).rings[0];
2414
- const inner = createCircle(cx, cy, Math.min(innerRadius, outerRadius), segments).rings[0];
2415
- return path([outer, [...inner].reverse()], true);
3232
+ function pathBetweenLeaves(g, a, b, root, straightness) {
3233
+ return bestLeafPair(g, a, b, root, straightness).path;
2416
3234
  }
2417
- function createArc(cx, cy, radius, startAngle, endAngle, segments = 32) {
2418
- const n = Math.max(2, Math.floor(segments));
2419
- const pts = [];
2420
- for (let i = 0; i <= n; i++) {
2421
- const t = startAngle + (endAngle - startAngle) * i / n;
2422
- pts.push({ x: cx + Math.cos(t) * radius, y: cy + Math.sin(t) * radius });
3235
+ function pathToRoot(g, id) {
3236
+ const path3 = [id];
3237
+ let cur = id;
3238
+ while (g.nodes[cur].parent !== null) {
3239
+ cur = g.nodes[cur].parent;
3240
+ path3.push(cur);
2423
3241
  }
2424
- return polyline(pts);
3242
+ return path3;
2425
3243
  }
2426
- function createStar(cx, cy, outerRadius, innerRadius, points = 5, rotation = -Math.PI / 2) {
2427
- const n = Math.max(3, Math.floor(points));
2428
- const ring2 = [];
2429
- for (let i = 0; i < n * 2; i++) {
2430
- const r = i % 2 === 0 ? outerRadius : innerRadius;
2431
- const t = rotation + i / (n * 2) * Math.PI * 2;
2432
- ring2.push({ x: cx + Math.cos(t) * r, y: cy + Math.sin(t) * r });
3244
+ function longestPathNodes(g) {
3245
+ const leaves = g.nodes.filter((n) => n.children.length === 0 && n.id !== g.root);
3246
+ if (leaves.length === 0) return g.nodes.map((n) => n.id);
3247
+ let farthest = leaves[0];
3248
+ let best = -1;
3249
+ for (const L of leaves) {
3250
+ if (L.rootDist > best) {
3251
+ best = L.rootDist;
3252
+ farthest = L;
3253
+ }
2433
3254
  }
2434
- return polygon(ring2);
2435
- }
2436
- function createKochSnowflake(cx, cy, radius, iterations = 3) {
2437
- const tri = createRegularPolygon(cx, cy, radius, 3);
2438
- let ring2 = tri.rings[0];
2439
- const iters = Math.max(0, Math.min(6, Math.floor(iterations)));
2440
- for (let i = 0; i < iters; i++) {
2441
- ring2 = kochRefine(ring2);
3255
+ let bestPath = pathToRoot(g, farthest.id);
3256
+ let bestLen = farthest.rootDist;
3257
+ for (const L of leaves) {
3258
+ if (L.id === farthest.id) continue;
3259
+ const p = [
3260
+ ...pathToRoot(g, farthest.id),
3261
+ ...pathToRoot(g, L.id).reverse().slice(1)
3262
+ ];
3263
+ const len2 = farthest.rootDist + L.rootDist;
3264
+ if (len2 > bestLen) {
3265
+ bestLen = len2;
3266
+ bestPath = p;
3267
+ }
2442
3268
  }
2443
- return polygon(ring2);
3269
+ return bestPath;
2444
3270
  }
2445
- function kochRefine(ring2) {
3271
+ function angleBetween(a, apex, b) {
3272
+ const ax = a.x - apex.x;
3273
+ const ay = a.y - apex.y;
3274
+ const bx = b.x - apex.x;
3275
+ const by = b.y - apex.y;
3276
+ const la = Math.hypot(ax, ay) || 1;
3277
+ const lb = Math.hypot(bx, by) || 1;
3278
+ const cos = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
3279
+ return Math.acos(cos);
3280
+ }
3281
+ function gaussianSmooth(pts, sigma) {
3282
+ if (pts.length < 3 || sigma <= 0) return pts;
3283
+ const radius = Math.max(1, Math.round(Math.min(pts.length / 4, sigma / 10)));
2446
3284
  const out = [];
2447
- const n = ring2.length;
2448
- for (let i = 0; i < n; i++) {
2449
- const a = ring2[i];
2450
- const b = ring2[(i + 1) % n];
2451
- const dx = b.x - a.x;
2452
- const dy = b.y - a.y;
2453
- const p1 = { x: a.x + dx / 3, y: a.y + dy / 3 };
2454
- const p2 = { x: a.x + 2 * dx / 3, y: a.y + 2 * dy / 3 };
2455
- const px = a.x + dx / 2 - dy * (Math.sqrt(3) / 6);
2456
- const py = a.y + dy / 2 + dx * (Math.sqrt(3) / 6);
2457
- out.push(a, p1, { x: px, y: py }, p2);
3285
+ for (let i = 0; i < pts.length; i++) {
3286
+ let sx = 0;
3287
+ let sy = 0;
3288
+ let w = 0;
3289
+ for (let k = -radius; k <= radius; k++) {
3290
+ const j = Math.max(0, Math.min(pts.length - 1, i + k));
3291
+ const wk = Math.exp(-(k * k) / (2 * (radius * 0.5) ** 2 + 1e-9));
3292
+ sx += pts[j].x * wk;
3293
+ sy += pts[j].y * wk;
3294
+ w += wk;
3295
+ }
3296
+ out.push({ x: sx / w, y: sy / w });
2458
3297
  }
3298
+ out[0] = pts[0];
3299
+ out[out.length - 1] = pts[pts.length - 1];
2459
3300
  return out;
2460
3301
  }
2461
- var construction = {
2462
- createCircle,
2463
- createRect,
2464
- createRegularPolygon,
2465
- createRing,
2466
- createArc,
2467
- createStar,
2468
- createKochSnowflake
2469
- };
3302
+ function key2(p) {
3303
+ return `${p.x.toFixed(7)},${p.y.toFixed(7)}`;
3304
+ }
3305
+ function edgeKey2(a, b) {
3306
+ const ka = key2(a);
3307
+ const kb = key2(b);
3308
+ return ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`;
3309
+ }
3310
+ function clamp01(v) {
3311
+ return Math.max(0, Math.min(1, v));
3312
+ }
2470
3313
 
2471
- // src/tiling/index.ts
2472
- function squareGrid2(spacing, minX, minY, maxX, maxY) {
2473
- const s = Math.max(spacing, 1e-9);
2474
- const paths = [];
2475
- for (let x = minX; x < maxX - 1e-12; x += s) {
2476
- for (let y = minY; y < maxY - 1e-12; y += s) {
2477
- const w = Math.min(s, maxX - x);
2478
- const h = Math.min(s, maxY - y);
2479
- if (w > 1e-12 && h > 1e-12) paths.push(createRect(x, y, w, h));
3314
+ // src/contour/straightSkeleton.ts
3315
+ function straightSkeleton(shape) {
3316
+ const parts = straightSkeletonParts(shape);
3317
+ return group([
3318
+ ...parts.faces.paths,
3319
+ ...parts.branches.paths,
3320
+ ...parts.bones.paths
3321
+ ]);
3322
+ }
3323
+ function straightSkeletonParts(shape) {
3324
+ const empty = {
3325
+ faces: group([]),
3326
+ branches: group([]),
3327
+ bones: group([])
3328
+ };
3329
+ if (!shape.closed || !shape.rings[0] || shape.rings[0].length < 3) {
3330
+ return empty;
3331
+ }
3332
+ const b = bounds(shape);
3333
+ const span = Math.max(b.maxX - b.minX, b.maxY - b.minY, 1);
3334
+ const delta = Math.max(span / 40, 0.5);
3335
+ const densified = densify(shape, Math.max(span / 40, 1e-3));
3336
+ const levels = [densified];
3337
+ let current = densified;
3338
+ for (let i = 0; i < 40; i++) {
3339
+ const next = buffer(current, -delta);
3340
+ if (next.paths.length === 0) break;
3341
+ let best = next.paths[0];
3342
+ let bestA = -1;
3343
+ for (const p of next.paths) {
3344
+ const bb = bounds(p);
3345
+ const a = (bb.maxX - bb.minX) * (bb.maxY - bb.minY);
3346
+ if (a > bestA) {
3347
+ bestA = a;
3348
+ best = p;
3349
+ }
3350
+ }
3351
+ if (!best.rings[0] || best.rings[0].length < 3) break;
3352
+ levels.push(best);
3353
+ current = best;
3354
+ }
3355
+ const boneSegs = [];
3356
+ for (let i = 0; i < levels.length - 1; i++) {
3357
+ const a = levels[i];
3358
+ const bb = levels[i + 1];
3359
+ const ca = centroid(a);
3360
+ const cb = centroid(bb);
3361
+ if (containsPoint(shape, mid2(ca, cb))) {
3362
+ boneSegs.push(segment(ca, cb));
3363
+ }
3364
+ for (const ring2 of a.rings) {
3365
+ for (const p of ring2) {
3366
+ const q = nearestOnPath(bb, p);
3367
+ if (q && containsPoint(shape, mid2(p, q))) {
3368
+ boneSegs.push(segment(p, q));
3369
+ }
3370
+ }
2480
3371
  }
2481
3372
  }
2482
- return group(paths);
2483
- }
2484
- function hexTiling(spacing, minX, minY, maxX, maxY) {
2485
- const s = Math.max(spacing, 1e-9);
2486
- const r = s / 2;
2487
- const dx = s * 0.75;
2488
- const dy = s * Math.sqrt(3) * 0.5;
2489
- const paths = [];
2490
- let row = 0;
2491
- for (let y = minY + r; y <= maxY + r; y += dy, row++) {
2492
- const x0 = minX + r + (row % 2 === 0 ? 0 : dx * 0.5);
2493
- for (let x = x0; x <= maxX + r; x += dx) {
2494
- paths.push(hexagon(x, y, r));
3373
+ const branchSegs = [];
3374
+ const outer = densified.rings[0];
3375
+ const target = levels[1] ?? levels[0];
3376
+ for (let i = 0; i < outer.length; i++) {
3377
+ const a = outer[i];
3378
+ const bb = outer[(i + 1) % outer.length];
3379
+ const m = mid2(a, bb);
3380
+ const q = nearestOnPath(target, m);
3381
+ if (q) branchSegs.push(segment(m, q));
3382
+ }
3383
+ const faces = [];
3384
+ if (levels.length >= 2) {
3385
+ const inner = levels[1].rings[0] ?? [];
3386
+ for (let i = 0; i < outer.length; i++) {
3387
+ const a = outer[i];
3388
+ const bb = outer[(i + 1) % outer.length];
3389
+ const ia = nearestOnRing(inner, a);
3390
+ const ib = nearestOnRing(inner, bb);
3391
+ if (ia && ib) {
3392
+ const face = [a, bb, ib, ia];
3393
+ const c = {
3394
+ x: (a.x + bb.x + ib.x + ia.x) / 4,
3395
+ y: (a.y + bb.y + ib.y + ia.y) / 4
3396
+ };
3397
+ if (containsPoint(shape, c)) faces.push(polygon(face));
3398
+ }
2495
3399
  }
2496
3400
  }
2497
- return group(paths);
3401
+ return {
3402
+ faces: group(faces),
3403
+ branches: dissolveSegments(branchSegs),
3404
+ bones: dissolveSegments(boneSegs)
3405
+ };
2498
3406
  }
2499
- function hexagon(cx, cy, r) {
2500
- const ring2 = [];
2501
- for (let i = 0; i < 6; i++) {
2502
- const t = Math.PI / 180 * (60 * i - 30);
2503
- ring2.push({ x: cx + Math.cos(t) * r, y: cy + Math.sin(t) * r });
2504
- }
2505
- return polygon(ring2);
3407
+ function mid2(a, b) {
3408
+ return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
2506
3409
  }
2507
- function rectSubdivision(p, nx, ny) {
2508
- const b = bounds(p);
2509
- const cols = Math.max(1, Math.floor(nx));
2510
- const rows = Math.max(1, Math.floor(ny));
2511
- const dw = (b.maxX - b.minX) / cols;
2512
- const dh = (b.maxY - b.minY) / rows;
2513
- const paths = [];
2514
- for (let i = 0; i < cols; i++) {
2515
- for (let j = 0; j < rows; j++) {
2516
- const cell = createRect(
2517
- b.minX + i * dw,
2518
- b.minY + j * dh,
2519
- dw,
2520
- dh
2521
- );
2522
- paths.push(...intersect(p, cell).paths);
3410
+ function nearestOnPath(p, q) {
3411
+ let best = null;
3412
+ let bestD = Infinity;
3413
+ for (const ring2 of p.rings) {
3414
+ for (const v of ring2) {
3415
+ const d = Math.hypot(v.x - q.x, v.y - q.y);
3416
+ if (d < bestD) {
3417
+ bestD = d;
3418
+ best = v;
3419
+ }
2523
3420
  }
2524
3421
  }
2525
- return group(paths);
3422
+ return best;
2526
3423
  }
2527
- function quadSubdivision(faces, depth = 1) {
2528
- let cur = faces;
2529
- const d = Math.max(0, Math.floor(depth));
2530
- for (let i = 0; i < d; i++) {
2531
- const next = [];
2532
- for (const f of cur) {
2533
- next.push(...subdivideQuad(f));
3424
+ function nearestOnRing(ring2, q) {
3425
+ if (ring2.length === 0) return null;
3426
+ let best = ring2[0];
3427
+ let bestD = Infinity;
3428
+ for (const v of ring2) {
3429
+ const d = Math.hypot(v.x - q.x, v.y - q.y);
3430
+ if (d < bestD) {
3431
+ bestD = d;
3432
+ best = v;
2534
3433
  }
2535
- cur = next;
2536
3434
  }
2537
- return group(cur);
2538
- }
2539
- function subdivideQuad(p) {
2540
- const ring2 = p.rings[0];
2541
- if (!ring2 || ring2.length < 4) return [p];
2542
- const [a, b, c, d] = ring2;
2543
- if (!a || !b || !c || !d) return [p];
2544
- const mab = mid(a, b);
2545
- const mbc = mid(b, c);
2546
- const mcd = mid(c, d);
2547
- const mda = mid(d, a);
2548
- const cen = mid(mid(a, c), mid(b, d));
2549
- return [
2550
- polygon([a, mab, cen, mda]),
2551
- polygon([mab, b, mbc, cen]),
2552
- polygon([cen, mbc, c, mcd]),
2553
- polygon([mda, cen, mcd, d])
2554
- ];
3435
+ return best;
2555
3436
  }
2556
- function triangleSubdivision(faces, depth = 1) {
2557
- let cur = faces;
2558
- const d = Math.max(0, Math.floor(depth));
2559
- for (let i = 0; i < d; i++) {
2560
- const next = [];
2561
- for (const f of cur) {
2562
- next.push(...subdivideTriangle(f));
3437
+
3438
+ // src/contour/marchingSquares.ts
3439
+ function isolinesFromFunction(bounds2, sampleSpacing, contourInterval, fn, isolineMin = Number.NaN, isolineMax = Number.NaN) {
3440
+ const [xmin, ymin, xmax, ymax] = bounds2;
3441
+ const dx = Math.max(sampleSpacing, 1e-9);
3442
+ const dy = dx;
3443
+ const nx = Math.max(2, Math.ceil((xmax - xmin) / dx) + 1);
3444
+ const ny = Math.max(2, Math.ceil((ymax - ymin) / dy) + 1);
3445
+ const grid = [];
3446
+ let gmin = Infinity;
3447
+ let gmax = -Infinity;
3448
+ for (let j = 0; j < ny; j++) {
3449
+ const row = [];
3450
+ const y = ymin + j * ((ymax - ymin) / (ny - 1));
3451
+ for (let i = 0; i < nx; i++) {
3452
+ const x = xmin + i * ((xmax - xmin) / (nx - 1));
3453
+ let v = fn(x, y);
3454
+ if (!Number.isFinite(v)) v = Number.NEGATIVE_INFINITY;
3455
+ row.push(v);
3456
+ if (Number.isFinite(v)) {
3457
+ gmin = Math.min(gmin, v);
3458
+ gmax = Math.max(gmax, v);
3459
+ }
2563
3460
  }
2564
- cur = next;
3461
+ grid.push(row);
2565
3462
  }
2566
- return group(cur);
3463
+ const z0 = Number.isFinite(isolineMin) ? isolineMin : gmin;
3464
+ const z1 = Number.isFinite(isolineMax) ? isolineMax : gmax;
3465
+ if (!Number.isFinite(z0) || !Number.isFinite(z1) || contourInterval <= 0) {
3466
+ return group([]);
3467
+ }
3468
+ const levels = [];
3469
+ const start = Math.ceil(z0 / contourInterval) * contourInterval;
3470
+ for (let z = start; z <= z1 + 1e-12; z += contourInterval) {
3471
+ levels.push(z);
3472
+ }
3473
+ const segs = [];
3474
+ for (const level of levels) {
3475
+ segs.push(...marchLevel(grid, xmin, ymin, xmax, ymax, nx, ny, level));
3476
+ }
3477
+ return dissolveSegments(segs);
2567
3478
  }
2568
- function subdivideTriangle(p) {
2569
- const ring2 = p.rings[0];
2570
- if (!ring2 || ring2.length < 3) return [p];
2571
- const a = ring2[0];
2572
- const b = ring2[1];
2573
- const c = ring2[2];
2574
- const mab = mid(a, b);
2575
- const mbc = mid(b, c);
2576
- const mca = mid(c, a);
2577
- return [
2578
- polygon([a, mab, mca]),
2579
- polygon([mab, b, mbc]),
2580
- polygon([mca, mbc, c]),
2581
- polygon([mab, mbc, mca])
2582
- ];
3479
+ function isolineZeroFromFunction(bounds2, sampleSpacing, fn) {
3480
+ return isolinesFromFunction(bounds2, sampleSpacing, 1, fn, 0, 0);
2583
3481
  }
2584
- function sliceDivision(p, spacing, angle = 0) {
2585
- const s = Math.max(spacing, 1e-9);
2586
- const b = bounds(p);
2587
- const cx = (b.minX + b.maxX) / 2;
2588
- const cy = (b.minY + b.maxY) / 2;
2589
- const diag = Math.hypot(b.maxX - b.minX, b.maxY - b.minY) + s;
2590
- const ux = Math.cos(angle);
2591
- const uy = Math.sin(angle);
2592
- const nx = -uy;
2593
- const ny = ux;
2594
- const parts = [p];
2595
- for (let t = -diag; t <= diag; t += s) {
2596
- const a = { x: cx + nx * t - ux * diag, y: cy + ny * t - uy * diag };
2597
- const bb = { x: cx + nx * t + ux * diag, y: cy + ny * t + uy * diag };
2598
- const next = [];
2599
- for (const piece of parts) {
2600
- const g = slice(piece, a, bb);
2601
- next.push(...g.paths.length ? g.paths : [piece]);
3482
+ function marchLevel(grid, xmin, ymin, xmax, ymax, nx, ny, level) {
3483
+ const segs = [];
3484
+ const xAt = (i) => xmin + i / (nx - 1) * (xmax - xmin);
3485
+ const yAt = (j) => ymin + j / (ny - 1) * (ymax - ymin);
3486
+ const lerp2 = (x0, y0, v0, x1, y1, v1) => {
3487
+ const t = (level - v0) / (v1 - v0 || 1e-15);
3488
+ return { x: x0 + t * (x1 - x0), y: y0 + t * (y1 - y0) };
3489
+ };
3490
+ for (let j = 0; j < ny - 1; j++) {
3491
+ for (let i = 0; i < nx - 1; i++) {
3492
+ const v00 = grid[j][i];
3493
+ const v10 = grid[j][i + 1];
3494
+ const v11 = grid[j + 1][i + 1];
3495
+ const v01 = grid[j + 1][i];
3496
+ const x0 = xAt(i);
3497
+ const x1 = xAt(i + 1);
3498
+ const y0 = yAt(j);
3499
+ const y1 = yAt(j + 1);
3500
+ let code = 0;
3501
+ if (v00 >= level) code |= 1;
3502
+ if (v10 >= level) code |= 2;
3503
+ if (v11 >= level) code |= 4;
3504
+ if (v01 >= level) code |= 8;
3505
+ if (code === 0 || code === 15) continue;
3506
+ const bottom = () => lerp2(x0, y0, v00, x1, y0, v10);
3507
+ const right = () => lerp2(x1, y0, v10, x1, y1, v11);
3508
+ const top = () => lerp2(x0, y1, v01, x1, y1, v11);
3509
+ const left = () => lerp2(x0, y0, v00, x0, y1, v01);
3510
+ const add = (a, b) => segs.push(segment(a, b));
3511
+ switch (code) {
3512
+ case 1:
3513
+ case 14:
3514
+ add(left(), bottom());
3515
+ break;
3516
+ case 2:
3517
+ case 13:
3518
+ add(bottom(), right());
3519
+ break;
3520
+ case 3:
3521
+ case 12:
3522
+ add(left(), right());
3523
+ break;
3524
+ case 4:
3525
+ case 11:
3526
+ add(right(), top());
3527
+ break;
3528
+ case 6:
3529
+ case 9:
3530
+ add(bottom(), top());
3531
+ break;
3532
+ case 7:
3533
+ case 8:
3534
+ add(left(), top());
3535
+ break;
3536
+ case 5: {
3537
+ const avg = (v00 + v10 + v11 + v01) / 4;
3538
+ if (avg >= level) {
3539
+ add(left(), top());
3540
+ add(bottom(), right());
3541
+ } else {
3542
+ add(left(), bottom());
3543
+ add(right(), top());
3544
+ }
3545
+ break;
3546
+ }
3547
+ case 10: {
3548
+ const avg = (v00 + v10 + v11 + v01) / 4;
3549
+ if (avg >= level) {
3550
+ add(left(), bottom());
3551
+ add(right(), top());
3552
+ } else {
3553
+ add(left(), top());
3554
+ add(bottom(), right());
3555
+ }
3556
+ break;
3557
+ }
3558
+ }
2602
3559
  }
2603
- parts.length = 0;
2604
- parts.push(...next);
2605
3560
  }
2606
- return group(parts);
2607
- }
2608
- function hatchSubdivision(p, spacing, angle = 0) {
2609
- return sliceDivision(p, spacing, angle);
2610
- }
2611
- function mid(a, b) {
2612
- return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
3561
+ return segs;
2613
3562
  }
2614
- var tiling = {
2615
- squareGrid: squareGrid2,
2616
- hexTiling,
2617
- rectSubdivision,
2618
- quadSubdivision,
2619
- triangleSubdivision,
2620
- sliceDivision,
2621
- hatchSubdivision
2622
- };
2623
-
2624
- // src/polygonisation/index.ts
2625
- function maxArea(points) {
2626
- return convexHull(points);
2627
- }
2628
- function minArea(points) {
2629
- const hull2 = convexHull(points).rings[0] ?? [];
2630
- if (hull2.length < 3) return convexHull(points);
2631
- if (hull2.length === 3) return polygon(hull2);
2632
- let best = null;
2633
- let bestA = Infinity;
2634
- const h = hull2;
2635
- const n = h.length;
2636
- for (let i = 0; i < n; i++) {
2637
- for (let j = i + 1; j < n; j++) {
2638
- for (let k = j + 1; k < n; k++) {
2639
- const tri = polygon([h[i], h[j], h[k]]);
2640
- if (!coversPoints(tri, points)) continue;
2641
- const a = area(tri);
2642
- if (a < bestA) {
2643
- bestA = a;
2644
- best = tri;
2645
- }
2646
- }
2647
- }
2648
- }
2649
- return best ?? convexHull(points);
2650
- }
2651
- function minPerimeter(points) {
2652
- if (points.length === 0) return polygon([]);
2653
- if (points.length < 3) return polygon(points);
2654
- const remaining = points.map((p) => ({ ...p }));
2655
- const ordered = [remaining.shift()];
2656
- while (remaining.length) {
2657
- const last = ordered[ordered.length - 1];
2658
- let bestI = 0;
2659
- let bestD = Infinity;
2660
- for (let i = 0; i < remaining.length; i++) {
2661
- const d = Math.hypot(
2662
- remaining[i].x - last.x,
2663
- remaining[i].y - last.y
2664
- );
2665
- if (d < bestD) {
2666
- bestD = d;
2667
- bestI = i;
2668
- }
2669
- }
2670
- ordered.push(remaining.splice(bestI, 1)[0]);
2671
- }
2672
- return polygon(ordered);
2673
- }
2674
- function horizontal(points) {
2675
- const pts = [...points].sort(
2676
- (a, b) => a.y === b.y ? a.x - b.x : a.y - b.y
2677
- );
2678
- return polygon(pts);
2679
- }
2680
- function vertical(points) {
2681
- const pts = [...points].sort(
2682
- (a, b) => a.x === b.x ? a.y - b.y : a.x - b.x
2683
- );
2684
- return polygon(pts);
2685
- }
2686
- function angular(points) {
2687
- if (points.length === 0) return polygon([]);
2688
- const c = centroidOf(points);
2689
- const pts = [...points].sort(
2690
- (a, b) => Math.atan2(a.y - c.y, a.x - c.x) - Math.atan2(b.y - c.y, b.x - c.x)
2691
- );
2692
- return polygon(pts);
2693
- }
2694
- function circular(points) {
2695
- return angular(points);
2696
- }
2697
- function onion(points) {
2698
- const layers = onionLayers(points);
2699
- return layers[0] ?? polygon([]);
2700
- }
2701
- function onionLayers(points) {
2702
- let remaining = points.map((p) => ({ ...p }));
2703
- const layers = [];
2704
- while (remaining.length >= 3) {
2705
- const hull2 = convexHull(remaining);
2706
- const ring2 = hull2.rings[0] ?? [];
2707
- if (ring2.length < 3) break;
2708
- layers.push(hull2);
2709
- const onHull = new Set(ring2.map((v) => `${v.x},${v.y}`));
2710
- remaining = remaining.filter((p) => !onHull.has(`${p.x},${p.y}`));
3563
+ function isolinesFromPoints(points, values, intervals, _smoothing = 0) {
3564
+ if (points.length < 3 || intervals < 1) return group([]);
3565
+ let minZ = Infinity;
3566
+ let maxZ = -Infinity;
3567
+ for (const v of values) {
3568
+ minZ = Math.min(minZ, v);
3569
+ maxZ = Math.max(maxZ, v);
2711
3570
  }
2712
- return layers;
2713
- }
2714
- function hilbert(points) {
2715
- if (points.length === 0) return polygon([]);
3571
+ if (!Number.isFinite(minZ) || maxZ - minZ < 1e-15) return group([]);
2716
3572
  let minX = Infinity;
2717
3573
  let minY = Infinity;
2718
3574
  let maxX = -Infinity;
@@ -2723,135 +3579,371 @@ function hilbert(points) {
2723
3579
  maxX = Math.max(maxX, p.x);
2724
3580
  maxY = Math.max(maxY, p.y);
2725
3581
  }
2726
- const span = Math.max(maxX - minX, maxY - minY, 1e-9);
2727
- const order = 10;
2728
- const n = 1 << order;
2729
- const scored = points.map((p) => {
2730
- const x = Math.min(
2731
- n - 1,
2732
- Math.max(0, Math.floor((p.x - minX) / span * (n - 1)))
2733
- );
2734
- const y = Math.min(
2735
- n - 1,
2736
- Math.max(0, Math.floor((p.y - minY) / span * (n - 1)))
2737
- );
2738
- return { p, h: xy2d(order, x, y) };
2739
- });
2740
- scored.sort((a, b) => a.h - b.h);
2741
- return polygon(scored.map((s) => s.p));
3582
+ const span = Math.max(maxX - minX, maxY - minY, 1);
3583
+ const sampleSpacing = span / Math.max(20, Math.sqrt(points.length));
3584
+ const interval = (maxZ - minZ) / intervals;
3585
+ const fn = (x, y) => {
3586
+ let num2 = 0;
3587
+ let den = 0;
3588
+ for (let i = 0; i < points.length; i++) {
3589
+ const p = points[i];
3590
+ const d2 = (x - p.x) ** 2 + (y - p.y) ** 2;
3591
+ const w = 1 / Math.max(d2, 1e-12);
3592
+ num2 += w * values[i];
3593
+ den += w;
3594
+ }
3595
+ return num2 / den;
3596
+ };
3597
+ return isolinesFromFunction(
3598
+ [minX, minY, maxX, maxY],
3599
+ sampleSpacing,
3600
+ interval,
3601
+ fn,
3602
+ minZ,
3603
+ maxZ
3604
+ );
2742
3605
  }
2743
- function coversPoints(poly, points) {
2744
- const r = poly.rings[0];
2745
- if (!r || r.length < 3) return false;
2746
- const a = r[0];
2747
- const b = r[1];
2748
- const c = r[2];
2749
- for (const p of points) {
2750
- if (!pointInTriangle(p, a, b, c)) return false;
2751
- }
2752
- return true;
3606
+
3607
+ // src/contour/fields.ts
3608
+ function distanceField(shape, spacing, pole) {
3609
+ if (!shape.closed || shape.rings.length === 0 || spacing <= 0) return group([]);
3610
+ const b = bounds(shape);
3611
+ const span = Math.max(b.maxX - b.minX, b.maxY - b.minY, 1);
3612
+ let polePt = pole;
3613
+ if (!polePt) {
3614
+ const mic = maximumInscribedCircle(shape, Math.max(span * 0.01, 0.1));
3615
+ polePt = mic ? { x: mic.x, y: mic.y } : { x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 };
3616
+ }
3617
+ const pad = Math.max(spacing, 1);
3618
+ const box = [
3619
+ b.minX - pad,
3620
+ b.minY - pad,
3621
+ b.maxX + pad,
3622
+ b.maxY + pad
3623
+ ];
3624
+ const sampleSpacing = Math.max(spacing / 10, span / 80);
3625
+ const fn = (x, y) => {
3626
+ const p = { x, y };
3627
+ if (!insideSafe(shape, p)) return Number.NEGATIVE_INFINITY;
3628
+ const dGeo = distanceToBoundary2(shape, p);
3629
+ const dPoint = Math.hypot(x - polePt.x, y - polePt.y);
3630
+ return dGeo - dPoint;
3631
+ };
3632
+ const raw = isolinesFromFunction(box, sampleSpacing, spacing, fn);
3633
+ return clipPolylinesToPath(raw, shape);
2753
3634
  }
2754
- function pointInTriangle(p, a, b, c) {
2755
- const v0x = c.x - a.x;
2756
- const v0y = c.y - a.y;
2757
- const v1x = b.x - a.x;
2758
- const v1y = b.y - a.y;
2759
- const v2x = p.x - a.x;
2760
- const v2y = p.y - a.y;
2761
- const dot00 = v0x * v0x + v0y * v0y;
2762
- const dot01 = v0x * v1x + v0y * v1y;
2763
- const dot02 = v0x * v2x + v0y * v2y;
2764
- const dot11 = v1x * v1x + v1y * v1y;
2765
- const dot12 = v1x * v2x + v1y * v2y;
2766
- const inv = 1 / (dot00 * dot11 - dot01 * dot01 || 1e-18);
2767
- const u = (dot11 * dot02 - dot01 * dot12) * inv;
2768
- const v = (dot00 * dot12 - dot01 * dot02) * inv;
2769
- return u >= -1e-9 && v >= -1e-9 && u + v <= 1 + 1e-9;
3635
+ function contrastField(shape, intervals, reference) {
3636
+ if (!shape.closed || shape.rings.length === 0 || intervals < 1) {
3637
+ return group([]);
3638
+ }
3639
+ const b = bounds(shape);
3640
+ const areaApprox = (b.maxX - b.minX) * (b.maxY - b.minY);
3641
+ const count = Math.max(100, Math.floor(areaApprox / 100));
3642
+ const minDist = Math.sqrt(areaApprox / (count * 1.5));
3643
+ const samples = poisson(
3644
+ Math.max(minDist, 1e-3),
3645
+ b.minX,
3646
+ b.minY,
3647
+ b.maxX,
3648
+ b.maxY,
3649
+ 1337
3650
+ ).filter((p) => containsPoint(shape, p));
3651
+ if (samples.length < 3) return group([]);
3652
+ const values = samples.map((p) => {
3653
+ const dB = distanceToBoundary2(shape, p);
3654
+ const dR = Math.hypot(p.x - reference.x, p.y - reference.y);
3655
+ return Math.abs(dB - dR);
3656
+ });
3657
+ const lines = isolinesFromPoints(samples, values, Math.max(1, intervals), 0);
3658
+ return clipPolylinesToPath(lines, shape);
3659
+ }
3660
+ function isolines(shape, highPoint, intervalSpacing) {
3661
+ if (!shape.closed || intervalSpacing <= 0) return group([]);
3662
+ const b = bounds(shape);
3663
+ const span = Math.max(b.maxX - b.minX, b.maxY - b.minY, 1);
3664
+ const pad = intervalSpacing;
3665
+ const box = [
3666
+ b.minX - pad,
3667
+ b.minY - pad,
3668
+ b.maxX + pad,
3669
+ b.maxY + pad
3670
+ ];
3671
+ const fn = (x, y) => {
3672
+ if (!containsPoint(shape, { x, y })) return -1;
3673
+ return Math.hypot(x - highPoint.x, y - highPoint.y);
3674
+ };
3675
+ const raw = isolinesFromFunction(
3676
+ box,
3677
+ Math.max(intervalSpacing / 8, span / 100),
3678
+ intervalSpacing,
3679
+ fn,
3680
+ 0,
3681
+ span
3682
+ );
3683
+ return clipPolylinesToPath(raw, shape);
2770
3684
  }
2771
- function centroidOf(points) {
2772
- let sx = 0;
2773
- let sy = 0;
2774
- for (const p of points) {
2775
- sx += p.x;
2776
- sy += p.y;
3685
+ function clipPolylinesToPath(g, shape) {
3686
+ const paths = [];
3687
+ for (const p of g.paths) {
3688
+ const ring2 = p.rings[0];
3689
+ if (!ring2 || ring2.length < 2) continue;
3690
+ let current = [];
3691
+ const flush = () => {
3692
+ if (current.length >= 2) {
3693
+ paths.push({ rings: [current], closed: false });
3694
+ }
3695
+ current = [];
3696
+ };
3697
+ for (let i = 0; i < ring2.length - 1; i++) {
3698
+ const a = ring2[i];
3699
+ const b = ring2[i + 1];
3700
+ if (!finitePt(a) || !finitePt(b)) {
3701
+ flush();
3702
+ continue;
3703
+ }
3704
+ const mid5 = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
3705
+ if (insideSafe(shape, mid5)) {
3706
+ if (current.length === 0) current.push(a);
3707
+ current.push(b);
3708
+ } else {
3709
+ flush();
3710
+ }
3711
+ }
3712
+ flush();
2777
3713
  }
2778
- return { x: sx / points.length, y: sy / points.length };
3714
+ return group(paths);
2779
3715
  }
2780
- function xy2d(order, x, y) {
2781
- let rx;
2782
- let ry;
2783
- let s;
2784
- let d = 0;
2785
- for (s = 1 << order - 1; s > 0; s >>= 1) {
2786
- rx = (x & s) > 0 ? 1 : 0;
2787
- ry = (y & s) > 0 ? 1 : 0;
2788
- d += s * s * (3 * rx ^ ry);
2789
- ({ x, y } = rot(s, x, y, rx, ry));
3716
+ function finitePt(p) {
3717
+ return Number.isFinite(p.x) && Number.isFinite(p.y);
3718
+ }
3719
+ function insideSafe(shape, point) {
3720
+ if (!finitePt(point)) return false;
3721
+ try {
3722
+ return containsPoint(shape, point);
3723
+ } catch {
3724
+ return pointInPoly2(point, shape.rings[0]) && shape.rings.slice(1).every((h) => !pointInPoly2(point, h));
2790
3725
  }
2791
- return d;
2792
3726
  }
2793
- function rot(n, x, y, rx, ry) {
2794
- if (ry === 0) {
2795
- if (rx === 1) {
2796
- x = n - 1 - x;
2797
- y = n - 1 - y;
3727
+ function pointInPoly2(point, ring2) {
3728
+ let inside = false;
3729
+ for (let i = 0, j = ring2.length - 1; i < ring2.length; j = i++) {
3730
+ const pi = ring2[i];
3731
+ const pj = ring2[j];
3732
+ if (pi.y > point.y !== pj.y > point.y && point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y + 1e-15) + pi.x) {
3733
+ inside = !inside;
2798
3734
  }
2799
- return { x: y, y: x };
2800
3735
  }
2801
- return { x, y };
3736
+ return inside;
2802
3737
  }
2803
- var polygonisation = {
2804
- maxArea,
2805
- minArea,
2806
- minPerimeter,
2807
- horizontal,
2808
- vertical,
2809
- angular,
2810
- circular,
2811
- onion,
2812
- onionLayers,
2813
- hilbert
2814
- };
2815
3738
 
2816
- // src/meshing/index.ts
2817
- function edgeKey(a, b) {
2818
- const aKey = `${a.x},${a.y}`;
2819
- const bKey = `${b.x},${b.y}`;
2820
- return aKey < bKey ? `${aKey}|${bKey}` : `${bKey}|${aKey}`;
2821
- }
2822
- function vertKey(v) {
2823
- return `${v.x},${v.y}`;
2824
- }
2825
- function same(a, b, eps = 1e-12) {
2826
- return Math.abs(a.x - b.x) < eps && Math.abs(a.y - b.y) < eps;
2827
- }
2828
- function dist(a, b) {
2829
- return Math.hypot(a.x - b.x, a.y - b.y);
2830
- }
2831
- function mid2(a, b) {
2832
- return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
2833
- }
2834
- function lerp(a, b, t) {
2835
- return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
3739
+ // src/contour/distanceTree.ts
3740
+ function key3(v) {
3741
+ return `${v.x.toFixed(8)},${v.y.toFixed(8)}`;
2836
3742
  }
2837
- function unique(points) {
2838
- const seen = /* @__PURE__ */ new Set();
2839
- const out = [];
2840
- for (const p of points) {
2841
- const k = vertKey(p);
2842
- if (seen.has(k)) continue;
2843
- seen.add(k);
2844
- out.push({ ...p });
3743
+ function distanceTree(mesh, source, flatten2) {
3744
+ const adj = /* @__PURE__ */ new Map();
3745
+ const pts = /* @__PURE__ */ new Map();
3746
+ const addEdge = (a, b) => {
3747
+ const ka = key3(a);
3748
+ const kb = key3(b);
3749
+ if (ka === kb) return;
3750
+ pts.set(ka, a);
3751
+ pts.set(kb, b);
3752
+ if (!adj.has(ka)) adj.set(ka, []);
3753
+ if (!adj.has(kb)) adj.set(kb, []);
3754
+ adj.get(ka).push({ to: kb, pt: b });
3755
+ adj.get(kb).push({ to: ka, pt: a });
3756
+ };
3757
+ for (const face of mesh.paths) {
3758
+ for (const ring2 of face.rings) {
3759
+ if (ring2.length < 2) continue;
3760
+ const n = ring2.length;
3761
+ const closed = face.closed;
3762
+ const limit = closed ? n : n - 1;
3763
+ for (let i = 0; i < limit; i++) {
3764
+ addEdge(ring2[i], ring2[(i + 1) % n]);
3765
+ }
3766
+ }
2845
3767
  }
2846
- return out;
3768
+ if (pts.size === 0) return group([]);
3769
+ let sourceKey = "";
3770
+ let best = Infinity;
3771
+ for (const [k, p] of pts) {
3772
+ const d = Math.hypot(p.x - source.x, p.y - source.y);
3773
+ if (d < best) {
3774
+ best = d;
3775
+ sourceKey = k;
3776
+ }
3777
+ }
3778
+ const parent = /* @__PURE__ */ new Map();
3779
+ parent.set(sourceKey, null);
3780
+ const q = [sourceKey];
3781
+ while (q.length) {
3782
+ const u = q.shift();
3783
+ for (const { to } of adj.get(u) ?? []) {
3784
+ if (parent.has(to)) continue;
3785
+ parent.set(to, u);
3786
+ q.push(to);
3787
+ }
3788
+ }
3789
+ if (flatten2) {
3790
+ const segs = [];
3791
+ const seen = /* @__PURE__ */ new Set();
3792
+ for (const [v, p] of parent) {
3793
+ if (p === null) continue;
3794
+ const ek = v < p ? `${v}|${p}` : `${p}|${v}`;
3795
+ if (seen.has(ek)) continue;
3796
+ seen.add(ek);
3797
+ segs.push(segment(pts.get(p), pts.get(v)));
3798
+ }
3799
+ return dissolveSegments(segs);
3800
+ }
3801
+ const paths = [];
3802
+ for (const [v] of parent) {
3803
+ if (v === sourceKey) continue;
3804
+ const chain = [];
3805
+ let cur = v;
3806
+ while (cur !== null) {
3807
+ chain.push(pts.get(cur));
3808
+ cur = parent.get(cur) ?? null;
3809
+ }
3810
+ chain.reverse();
3811
+ if (chain.length >= 2) paths.push(polyline(chain));
3812
+ }
3813
+ return group(paths);
2847
3814
  }
2848
- function cross3(o, a, b) {
2849
- return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
3815
+
3816
+ // src/contour/index.ts
3817
+ function offsetCurvesOutward(p, distance) {
3818
+ return buffer(p, Math.abs(distance));
2850
3819
  }
2851
- function ringOf(face) {
2852
- return face.rings[0] ?? [];
3820
+ function offsetCurvesInward(p, distance) {
3821
+ return buffer(p, -Math.abs(distance));
2853
3822
  }
2854
- function buildEdgeMap(faces) {
3823
+ var contour = {
3824
+ offsetCurvesOutward,
3825
+ offsetCurvesInward,
3826
+ medialAxis,
3827
+ chordalAxis,
3828
+ straightSkeleton,
3829
+ straightSkeletonParts,
3830
+ centerLine,
3831
+ distanceField,
3832
+ contrastField,
3833
+ distanceTree,
3834
+ isolines,
3835
+ isolinesFromFunction,
3836
+ isolineZeroFromFunction,
3837
+ isolinesFromPoints,
3838
+ dissolveSegments
3839
+ };
3840
+ function compoundVoronoi(sites, options = {}) {
3841
+ if (sites.length === 0) return group([]);
3842
+ if (sites.length === 1) {
3843
+ const b2 = options.bounds ?? padBounds(sites, 1);
3844
+ return group([
3845
+ polygon([
3846
+ { x: b2.minX, y: b2.minY },
3847
+ { x: b2.maxX, y: b2.minY },
3848
+ { x: b2.maxX, y: b2.maxY },
3849
+ { x: b2.minX, y: b2.maxY }
3850
+ ])
3851
+ ]);
3852
+ }
3853
+ const b = options.bounds ?? padBounds(sites, 0.1);
3854
+ const delaunay = Delaunay.from(
3855
+ sites,
3856
+ (d) => d.x,
3857
+ (d) => d.y
3858
+ );
3859
+ const voronoi2 = delaunay.voronoi([b.minX, b.minY, b.maxX, b.maxY]);
3860
+ const paths = [];
3861
+ for (let i = 0; i < sites.length; i++) {
3862
+ const poly = voronoi2.cellPolygon(i);
3863
+ if (!poly || poly.length < 3) continue;
3864
+ const ring2 = poly.slice(0, -1).map(([x, y]) => ({ x, y }));
3865
+ if (ring2.length >= 3) paths.push(polygon(ring2));
3866
+ }
3867
+ return group(paths);
3868
+ }
3869
+ function innerVoronoi(sites, container, options = {}) {
3870
+ const b = options.bounds ?? {
3871
+ ...bounds(container)
3872
+ };
3873
+ const cells = compoundVoronoi(sites, { bounds: b });
3874
+ const clipped = [];
3875
+ for (const cell of cells.paths) {
3876
+ clipped.push(...intersect(cell, container).paths);
3877
+ }
3878
+ return group(clipped);
3879
+ }
3880
+ function innerVoronoiRaw(sites, options = {}) {
3881
+ return compoundVoronoi(sites, options);
3882
+ }
3883
+ function padBounds(sites, padRatio) {
3884
+ let minX = Infinity;
3885
+ let minY = Infinity;
3886
+ let maxX = -Infinity;
3887
+ let maxY = -Infinity;
3888
+ for (const s of sites) {
3889
+ minX = Math.min(minX, s.x);
3890
+ minY = Math.min(minY, s.y);
3891
+ maxX = Math.max(maxX, s.x);
3892
+ maxY = Math.max(maxY, s.y);
3893
+ }
3894
+ const pad = Math.max(maxX - minX, maxY - minY, 1) * padRatio + 1e-6;
3895
+ return {
3896
+ minX: minX - pad,
3897
+ minY: minY - pad,
3898
+ maxX: maxX + pad,
3899
+ maxY: maxY + pad
3900
+ };
3901
+ }
3902
+ var voronoi = {
3903
+ compoundVoronoi,
3904
+ innerVoronoi,
3905
+ innerVoronoiRaw
3906
+ };
3907
+
3908
+ // src/meshing/index.ts
3909
+ function edgeKey3(a, b) {
3910
+ const aKey = `${a.x},${a.y}`;
3911
+ const bKey = `${b.x},${b.y}`;
3912
+ return aKey < bKey ? `${aKey}|${bKey}` : `${bKey}|${aKey}`;
3913
+ }
3914
+ function vertKey(v) {
3915
+ return `${v.x},${v.y}`;
3916
+ }
3917
+ function same2(a, b, eps = 1e-12) {
3918
+ return Math.abs(a.x - b.x) < eps && Math.abs(a.y - b.y) < eps;
3919
+ }
3920
+ function dist(a, b) {
3921
+ return Math.hypot(a.x - b.x, a.y - b.y);
3922
+ }
3923
+ function mid3(a, b) {
3924
+ return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
3925
+ }
3926
+ function lerp(a, b, t) {
3927
+ return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
3928
+ }
3929
+ function unique(points) {
3930
+ const seen = /* @__PURE__ */ new Set();
3931
+ const out = [];
3932
+ for (const p of points) {
3933
+ const k = vertKey(p);
3934
+ if (seen.has(k)) continue;
3935
+ seen.add(k);
3936
+ out.push({ ...p });
3937
+ }
3938
+ return out;
3939
+ }
3940
+ function cross3(o, a, b) {
3941
+ return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
3942
+ }
3943
+ function ringOf(face) {
3944
+ return face.rings[0] ?? [];
3945
+ }
3946
+ function buildEdgeMap(faces) {
2855
3947
  const edges = /* @__PURE__ */ new Map();
2856
3948
  faces.forEach((f, fi) => {
2857
3949
  const ring2 = ringOf(f);
@@ -2862,10 +3954,10 @@ function buildEdgeMap(faces) {
2862
3954
  for (let i = 0; i < lim; i++) {
2863
3955
  const a = ring2[i];
2864
3956
  const b = ring2[(i + 1) % n];
2865
- const key = edgeKey(a, b);
2866
- const rec = edges.get(key);
3957
+ const key4 = edgeKey3(a, b);
3958
+ const rec = edges.get(key4);
2867
3959
  if (rec) rec.faceIds.push(fi);
2868
- else edges.set(key, { a: { ...a }, b: { ...b }, faceIds: [fi] });
3960
+ else edges.set(key4, { a: { ...a }, b: { ...b }, faceIds: [fi] });
2869
3961
  }
2870
3962
  });
2871
3963
  return edges;
@@ -2909,8 +4001,8 @@ function facesFromKeptEdges(tris, kept) {
2909
4001
  if (ra !== rb) parent[rb] = ra;
2910
4002
  };
2911
4003
  const edgeMap = buildEdgeMap(tris);
2912
- for (const [key, e] of edgeMap) {
2913
- if (e.faceIds.length === 2 && !kept.has(key)) {
4004
+ for (const [key4, e] of edgeMap) {
4005
+ if (e.faceIds.length === 2 && !kept.has(key4)) {
2914
4006
  unite(e.faceIds[0], e.faceIds[1]);
2915
4007
  }
2916
4008
  }
@@ -2936,10 +4028,10 @@ function boundaryRingOfComponent(tris, members) {
2936
4028
  for (let i = 0; i < ring2.length; i++) {
2937
4029
  const a = ring2[i];
2938
4030
  const b = ring2[(i + 1) % ring2.length];
2939
- const key = edgeKey(a, b);
2940
- const rec = edgeCount.get(key);
4031
+ const key4 = edgeKey3(a, b);
4032
+ const rec = edgeCount.get(key4);
2941
4033
  if (rec) rec.n++;
2942
- else edgeCount.set(key, { a: { ...a }, b: { ...b }, n: 1 });
4034
+ else edgeCount.set(key4, { a: { ...a }, b: { ...b }, n: 1 });
2943
4035
  }
2944
4036
  }
2945
4037
  const boundary = [];
@@ -2969,14 +4061,14 @@ function orderEdgesIntoRing(edges) {
2969
4061
  const maxSteps = edges.length + 2;
2970
4062
  for (let step = 0; step < maxSteps; step++) {
2971
4063
  ring2.push({ ...curr });
2972
- if (same(curr, start) && ring2.length > 2) {
4064
+ if (same2(curr, start) && ring2.length > 2) {
2973
4065
  ring2.pop();
2974
4066
  return ring2;
2975
4067
  }
2976
4068
  const nbrs = adj.get(vertKey(curr)) ?? [];
2977
4069
  let next = null;
2978
4070
  for (const n of nbrs) {
2979
- if (!same(n, prev)) {
4071
+ if (!same2(n, prev)) {
2980
4072
  next = n;
2981
4073
  break;
2982
4074
  }
@@ -2991,13 +4083,13 @@ function applyEdgeFilter(tris, shouldKeep, preservePerimeter) {
2991
4083
  const edgeMap = buildEdgeMap(tris);
2992
4084
  const peri = perimeterKeys(edgeMap);
2993
4085
  const kept = /* @__PURE__ */ new Set();
2994
- for (const [key, e] of edgeMap) {
2995
- const isPeri = peri.has(key);
4086
+ for (const [key4, e] of edgeMap) {
4087
+ const isPeri = peri.has(key4);
2996
4088
  if (isPeri && preservePerimeter) {
2997
- kept.add(key);
4089
+ kept.add(key4);
2998
4090
  continue;
2999
4091
  }
3000
- if (shouldKeep(e.a, e.b, isPeri)) kept.add(key);
4092
+ if (shouldKeep(e.a, e.b, isPeri)) kept.add(key4);
3001
4093
  }
3002
4094
  return facesFromKeptEdges(tris, kept);
3003
4095
  }
@@ -3025,8 +4117,8 @@ function extractInnerVertices(faces) {
3025
4117
  const edgeMap = buildEdgeMap(faces);
3026
4118
  const peri = perimeterKeys(edgeMap);
3027
4119
  const periVerts = /* @__PURE__ */ new Set();
3028
- for (const key of peri) {
3029
- const e = edgeMap.get(key);
4120
+ for (const key4 of peri) {
4121
+ const e = edgeMap.get(key4);
3030
4122
  periVerts.add(vertKey(e.a));
3031
4123
  periVerts.add(vertKey(e.b));
3032
4124
  }
@@ -3076,10 +4168,10 @@ function densifyRing2(ring2, maxLen) {
3076
4168
  return out;
3077
4169
  }
3078
4170
  function isGabriel(a, b, points) {
3079
- const m = mid2(a, b);
4171
+ const m = mid3(a, b);
3080
4172
  const r = dist(a, b) / 2;
3081
4173
  for (const p of points) {
3082
- if (same(p, a) || same(p, b)) continue;
4174
+ if (same2(p, a) || same2(p, b)) continue;
3083
4175
  if (dist(p, m) < r - 1e-9) return false;
3084
4176
  }
3085
4177
  return true;
@@ -3087,7 +4179,7 @@ function isGabriel(a, b, points) {
3087
4179
  function isRelativeNeighbor(a, b, points) {
3088
4180
  const dab = dist(a, b);
3089
4181
  for (const p of points) {
3090
- if (same(p, a) || same(p, b)) continue;
4182
+ if (same2(p, a) || same2(p, b)) continue;
3091
4183
  if (Math.max(dist(p, a), dist(p, b)) < dab - 1e-9) return false;
3092
4184
  }
3093
4185
  return true;
@@ -3108,7 +4200,7 @@ function urquhartFaces(input, preservePerimeter = false) {
3108
4200
  const d = dist(a, b);
3109
4201
  if (d > longestLen) {
3110
4202
  longestLen = d;
3111
- longestKey = edgeKey(a, b);
4203
+ longestKey = edgeKey3(a, b);
3112
4204
  }
3113
4205
  }
3114
4206
  if (longestKey && !(preservePerimeter && peri.has(longestKey))) {
@@ -3116,8 +4208,8 @@ function urquhartFaces(input, preservePerimeter = false) {
3116
4208
  }
3117
4209
  }
3118
4210
  const kept = /* @__PURE__ */ new Set();
3119
- for (const key of edgeMap.keys()) {
3120
- if (!removed.has(key)) kept.add(key);
4211
+ for (const key4 of edgeMap.keys()) {
4212
+ if (!removed.has(key4)) kept.add(key4);
3121
4213
  }
3122
4214
  return group(facesFromKeptEdges(tris, kept));
3123
4215
  }
@@ -3148,12 +4240,12 @@ function spannerFaces(input, k = 1, preservePerimeter = false) {
3148
4240
  const edgeMap = buildEdgeMap(tris);
3149
4241
  const peri = perimeterKeys(edgeMap);
3150
4242
  const stretch = Math.max(1, k);
3151
- const edges = [...edgeMap.entries()].map(([key, e]) => ({
3152
- key,
4243
+ const edges = [...edgeMap.entries()].map(([key4, e]) => ({
4244
+ key: key4,
3153
4245
  a: e.a,
3154
4246
  b: e.b,
3155
4247
  len: dist(e.a, e.b),
3156
- peri: peri.has(key)
4248
+ peri: peri.has(key4)
3157
4249
  }));
3158
4250
  edges.sort((a, b) => a.len - b.len);
3159
4251
  const adj = /* @__PURE__ */ new Map();
@@ -3208,8 +4300,8 @@ function dualFaces(faces) {
3208
4300
  const edgeMap = buildEdgeMap(faces);
3209
4301
  const peri = perimeterKeys(edgeMap);
3210
4302
  const periVerts = /* @__PURE__ */ new Set();
3211
- for (const key of peri) {
3212
- const e = edgeMap.get(key);
4303
+ for (const key4 of peri) {
4304
+ const e = edgeMap.get(key4);
3213
4305
  periVerts.add(vertKey(e.a));
3214
4306
  periVerts.add(vertKey(e.b));
3215
4307
  }
@@ -3245,8 +4337,8 @@ function triVerts(t) {
3245
4337
  function midpointMap(faces) {
3246
4338
  const mids = /* @__PURE__ */ new Map();
3247
4339
  const edgeMap = buildEdgeMap(faces);
3248
- for (const [key, e] of edgeMap) {
3249
- mids.set(key, mid2(e.a, e.b));
4340
+ for (const [key4, e] of edgeMap) {
4341
+ mids.set(key4, mid3(e.a, e.b));
3250
4342
  }
3251
4343
  return mids;
3252
4344
  }
@@ -3256,7 +4348,7 @@ function centroidQuadrangulation(input, preservePerimeter = true) {
3256
4348
  const centers = tris.map((t) => centroid(t));
3257
4349
  const out = [];
3258
4350
  const peri = perimeterKeys(edgeMap);
3259
- for (const [key, e] of edgeMap) {
4351
+ for (const [key4, e] of edgeMap) {
3260
4352
  const ids = e.faceIds;
3261
4353
  if (ids.length === 2) {
3262
4354
  const c0 = centers[ids[0]];
@@ -3265,7 +4357,7 @@ function centroidQuadrangulation(input, preservePerimeter = true) {
3265
4357
  } else if (ids.length === 1 && preservePerimeter) {
3266
4358
  const c0 = centers[ids[0]];
3267
4359
  out.push(polygon([e.a, e.b, c0]));
3268
- } else if (ids.length === 1 && !preservePerimeter && peri.has(key)) ;
4360
+ } else if (ids.length === 1 && !preservePerimeter && peri.has(key4)) ;
3269
4361
  }
3270
4362
  return group(out);
3271
4363
  }
@@ -3278,9 +4370,9 @@ function splitQuadrangulation(input) {
3278
4370
  if (!v) continue;
3279
4371
  const [a, b, c] = v;
3280
4372
  const g = centroid(t);
3281
- const mab = mids.get(edgeKey(a, b)) ?? mid2(a, b);
3282
- const mbc = mids.get(edgeKey(b, c)) ?? mid2(b, c);
3283
- const mca = mids.get(edgeKey(c, a)) ?? mid2(c, a);
4373
+ const mab = mids.get(edgeKey3(a, b)) ?? mid3(a, b);
4374
+ const mbc = mids.get(edgeKey3(b, c)) ?? mid3(b, c);
4375
+ const mca = mids.get(edgeKey3(c, a)) ?? mid3(c, a);
3284
4376
  out.push(polygon([a, mab, g, mca]));
3285
4377
  out.push(polygon([b, mbc, g, mab]));
3286
4378
  out.push(polygon([c, mca, g, mbc]));
@@ -3291,13 +4383,13 @@ function edgeCollapseQuadrangulation(input, preservePerimeter = true) {
3291
4383
  const tris = asTriFaces(input);
3292
4384
  const edgeMap = buildEdgeMap(tris);
3293
4385
  const candidates = [];
3294
- for (const [key, e] of edgeMap) {
4386
+ for (const [key4, e] of edgeMap) {
3295
4387
  if (e.faceIds.length !== 2) continue;
3296
4388
  const [i, j] = e.faceIds;
3297
4389
  const quad = mergeTriPair(tris[i], tris[j], e.a, e.b);
3298
4390
  if (!quad) continue;
3299
4391
  candidates.push({
3300
- key,
4392
+ key: key4,
3301
4393
  a: e.a,
3302
4394
  b: e.b,
3303
4395
  i,
@@ -3333,7 +4425,7 @@ function mergeTriPair(t0, t1, ea, eb) {
3333
4425
  }
3334
4426
  function oppositeVertex(t, ea, eb) {
3335
4427
  for (const v of ringOf(t)) {
3336
- if (!same(v, ea) && !same(v, eb)) return v;
4428
+ if (!same2(v, ea) && !same2(v, eb)) return v;
3337
4429
  }
3338
4430
  return null;
3339
4431
  }
@@ -3395,8 +4487,8 @@ function vertexNeighborhood(faces) {
3395
4487
  const edgeMap = buildEdgeMap(faces);
3396
4488
  const peri = perimeterKeys(edgeMap);
3397
4489
  const perimeter = /* @__PURE__ */ new Set();
3398
- for (const key of peri) {
3399
- const e = edgeMap.get(key);
4490
+ for (const key4 of peri) {
4491
+ const e = edgeMap.get(key4);
3400
4492
  perimeter.add(vertKey(e.a));
3401
4493
  perimeter.add(vertKey(e.b));
3402
4494
  }
@@ -3519,8 +4611,8 @@ function simplifyMesh(faces, tolerance, preservePerimeter = true) {
3519
4611
  };
3520
4612
  const periVerts = /* @__PURE__ */ new Set();
3521
4613
  if (preservePerimeter) {
3522
- for (const key of peri) {
3523
- const e = edgeMap.get(key);
4614
+ for (const key4 of peri) {
4615
+ const e = edgeMap.get(key4);
3524
4616
  periVerts.add(vertKey(e.a));
3525
4617
  periVerts.add(vertKey(e.b));
3526
4618
  }
@@ -3564,7 +4656,7 @@ function clusterSnap(points, tol) {
3564
4656
  function stochasticMerge(faces, nClasses, seed = 1) {
3565
4657
  if (faces.length === 0) return group([]);
3566
4658
  const classes = Math.max(1, Math.floor(nClasses));
3567
- const rng = mulberry324(seed >>> 0);
4659
+ const rng = mulberry325(seed >>> 0);
3568
4660
  const label = faces.map(() => Math.floor(rng() * classes));
3569
4661
  const adj = faceAdjacency(faces);
3570
4662
  const parent = faces.map((_, i) => i);
@@ -3704,7 +4796,7 @@ function convexHullOf(points) {
3704
4796
  upper.pop();
3705
4797
  return polygon([...lower, ...upper]);
3706
4798
  }
3707
- function mulberry324(a) {
4799
+ function mulberry325(a) {
3708
4800
  return () => {
3709
4801
  a |= 0;
3710
4802
  a = a + 1831565813 | 0;
@@ -3741,8 +4833,8 @@ function findBreaks(faces) {
3741
4833
  function nearDuplicateEdge(a, b, tol) {
3742
4834
  const d1 = dist(a.a, b.a) + dist(a.b, b.b);
3743
4835
  const d2 = dist(a.a, b.b) + dist(a.b, b.a);
3744
- const len = dist(a.a, a.b);
3745
- return Math.min(d1, d2) < tol * 2 + len * 0.05 && Math.min(d1, d2) > 1e-9;
4836
+ const len2 = dist(a.a, a.b);
4837
+ return Math.min(d1, d2) < tol * 2 + len2 * 0.05 && Math.min(d1, d2) > 1e-9;
3746
4838
  }
3747
4839
  function fixBreaks(faces, maxGapWidth) {
3748
4840
  if (faces.length === 0) return group([]);
@@ -3851,7 +4943,7 @@ function fixBrokenFaces(coverage, tolerance, polygonise = true) {
3851
4943
  out.push(p);
3852
4944
  continue;
3853
4945
  }
3854
- if (ring2.length >= 3 && same(ring2[0], ring2[ring2.length - 1], tol * 2)) {
4946
+ if (ring2.length >= 3 && same2(ring2[0], ring2[ring2.length - 1], tol * 2)) {
3855
4947
  out.push(polygon(ring2.slice(0, -1)));
3856
4948
  } else if (ring2.length >= 3 && dist(ring2[0], ring2[ring2.length - 1]) <= tol * 2) {
3857
4949
  out.push(polygon(ring2));
@@ -3928,6 +5020,510 @@ var meshing = {
3928
5020
  findIslands
3929
5021
  };
3930
5022
 
3931
- export { angular, area, areaGroup, areaMerge, areaSimilarity, assertAreaClose, boundingBox, bounds, buffer, centroid, centroidQuadrangulation, centroidSortFaces, centroidSplit, chaikinCut, circle, circleCenter, circleContainedInPath, circleOverlapsPath, circlePacking, circular, clipSegmentToPath, clipSegmentsToPath, cloneGroup, clonePath, cloneVec2, closestPoint, closestPointPair, closestVertex, complement, compoundVoronoi, construction, containmentAgreement, containsPoint, contour, conversion, convexHull, convexHullPath, copy, createArc, createCircle, createKochSnowflake, createRect, createRegularPolygon, createRing, createStar, delaunayTriangulation, delaunayTriangulationPoints, densify, dilationErosion, dissolve, dualFaces, earCutTriangulation, edgeCollapseQuadrangulation, envelope, equalsVec2, erosionDilation, extractBoundary, extractHoles, extractInnerEdges, extractInnerVertices, extractPerimeter, farthestPointPair, filterAxisAligned, filterByMinLength, findBreaks, findContainingFace, findIslands, fixBreaks, fixBrokenFaces, flatten, flipHorizontal, flipVertical, fromArray, fromContours, frontChainPack, gabrielFaces, generateRandomGridPoints, generateRandomPoints, group, groupFromPaths, hatch, cross as hatchCross, parallel as hatchParallel, hatchSubdivision, height, hexGrid, hexLatticePack, hexTiling, hilbert as hilbertPolygonise, hull, innerVoronoi, innerVoronoiRaw, intersect, intersectionPoints, largestEmptyCircle, largestEmptyCircles, matchingQuadrangulation, maxArea, maximumInscribedAARectangle, maximumInscribedCircle, maximumInscribedPack, maximumInscribedPackUntil, meshing, minArea, minPerimeter, minimumBoundingCircle, minkDifference, minkSum, morphology, normalizeRing, obstaclePack, occlusionSubtract, offsetCurvesInward, offsetCurvesOutward, onion, onionLayers, optimisation, oraclePathsToGroup, originScale, overlapRegions, parallelSegments, parsePathData, parseSvg, path, ring as pointRing, pointSet, pointsOnExterior, poisson, poissonTriangulation, poissonTriangulationPoints, polygon, polygonisation, horizontal as polygoniseHorizontal, vertical as polygoniseVertical, polyline, predicates, processing, prunePointsWithinDistance, quadSubdivision, radialSortFaces, radialWarp, random as randomPoints, rectSubdivision, reducePrecision, refine, relativeAreaError, relativeNeighborFaces, removeSmallHoles, repulsionPack, resizeByHeight, resizeByWidth, ringArea, ringsToPath, rotate, rotateAroundCenter, roundVertexCoords, scale, segment, segmentLength, segmentSet, segmentsOnExterior, segmentsToOpenPaths, serializePathData, shapeBoolean, shear, simplify, simplifyMesh, sineWarp, slice, sliceDivision, smooth, smoothMesh, spannerFaces, spiralQuadrangulation, splitEdges, splitQuadrangulation, squareGrid, squareLatticePack, squareGrid2 as squareTiling, stochasticMerge, stochasticPack, subdivideMesh, subtract, subtractAll, symDifference, tiling, toArray, toContours, toSvg, transformation, translate, translateCentroidTo, translateCornerTo, translateToOrigin, triangleSubdivision, triangulation, union, unionAll, urquhartFaces, vec2, voronoi, width };
5023
+ // src/construction/index.ts
5024
+ function createCircle(cx, cy, radius, segments = 64) {
5025
+ return createRegularPolygon(cx, cy, radius, Math.max(3, Math.floor(segments)));
5026
+ }
5027
+ function createRect(x, y, width2, height2) {
5028
+ return polygon([
5029
+ { x, y },
5030
+ { x: x + width2, y },
5031
+ { x: x + width2, y: y + height2 },
5032
+ { x, y: y + height2 }
5033
+ ]);
5034
+ }
5035
+ function createRegularPolygon(cx, cy, radius, sides, rotation = -Math.PI / 2) {
5036
+ const n = Math.max(3, Math.floor(sides));
5037
+ const ring2 = [];
5038
+ for (let i = 0; i < n; i++) {
5039
+ const t = rotation + i / n * Math.PI * 2;
5040
+ ring2.push({ x: cx + Math.cos(t) * radius, y: cy + Math.sin(t) * radius });
5041
+ }
5042
+ return polygon(ring2);
5043
+ }
5044
+ function createRing(cx, cy, outerRadius, innerRadius, segments = 64) {
5045
+ const outer = createCircle(cx, cy, outerRadius, segments).rings[0];
5046
+ const inner = createCircle(cx, cy, Math.min(innerRadius, outerRadius), segments).rings[0];
5047
+ return path([outer, [...inner].reverse()], true);
5048
+ }
5049
+ function createArc(cx, cy, radius, startAngle, endAngle, segments = 32) {
5050
+ const n = Math.max(2, Math.floor(segments));
5051
+ const pts = [];
5052
+ for (let i = 0; i <= n; i++) {
5053
+ const t = startAngle + (endAngle - startAngle) * i / n;
5054
+ pts.push({ x: cx + Math.cos(t) * radius, y: cy + Math.sin(t) * radius });
5055
+ }
5056
+ return polyline(pts);
5057
+ }
5058
+ function createStar(cx, cy, outerRadius, innerRadius, points = 5, rotation = -Math.PI / 2) {
5059
+ const n = Math.max(3, Math.floor(points));
5060
+ const ring2 = [];
5061
+ for (let i = 0; i < n * 2; i++) {
5062
+ const r = i % 2 === 0 ? outerRadius : innerRadius;
5063
+ const t = rotation + i / (n * 2) * Math.PI * 2;
5064
+ ring2.push({ x: cx + Math.cos(t) * r, y: cy + Math.sin(t) * r });
5065
+ }
5066
+ return polygon(ring2);
5067
+ }
5068
+ function createKochSnowflake(cx, cy, radius, iterations = 3) {
5069
+ const tri = createRegularPolygon(cx, cy, radius, 3);
5070
+ let ring2 = tri.rings[0];
5071
+ const iters = Math.max(0, Math.min(6, Math.floor(iterations)));
5072
+ for (let i = 0; i < iters; i++) {
5073
+ ring2 = kochRefine(ring2);
5074
+ }
5075
+ return polygon(ring2);
5076
+ }
5077
+ function createSponge(width2, height2, generators, thickness, smoothing, classes, seed = 1) {
5078
+ const w = Math.max(width2, 1e-9);
5079
+ const h = Math.max(height2, 1e-9);
5080
+ const nGen = Math.max(6, Math.floor(generators));
5081
+ const nClasses = Math.max(1, Math.floor(classes));
5082
+ const smoothIters = Math.max(0, Math.floor(smoothing));
5083
+ const wall = Math.max(0, thickness);
5084
+ const sites = randomInBox(nGen, 0, 0, w, h, seed);
5085
+ const cells = compoundVoronoi(sites, {
5086
+ bounds: { minX: 0, minY: 0, maxX: w, maxY: h }
5087
+ });
5088
+ const merged = stochasticMerge(cells.paths, nClasses, seed + 1);
5089
+ const pores = [];
5090
+ for (let p of merged.paths) {
5091
+ if (smoothIters > 0) p = smooth(p, smoothIters);
5092
+ if (wall > 1e-12) {
5093
+ pores.push(...buffer(p, -wall * 0.5).paths);
5094
+ } else {
5095
+ pores.push(p);
5096
+ }
5097
+ }
5098
+ const frame = createRect(0, 0, w, h);
5099
+ if (pores.length === 0) return group([frame]);
5100
+ return subtractAll(frame, pores);
5101
+ }
5102
+ function randomInBox(count, minX, minY, maxX, maxY, seed) {
5103
+ const rng = mulberry326(seed >>> 0);
5104
+ const out = [];
5105
+ for (let i = 0; i < count; i++) {
5106
+ out.push({
5107
+ x: minX + rng() * (maxX - minX),
5108
+ y: minY + rng() * (maxY - minY)
5109
+ });
5110
+ }
5111
+ return out;
5112
+ }
5113
+ function mulberry326(a) {
5114
+ return function() {
5115
+ let t = a += 1831565813;
5116
+ t = Math.imul(t ^ t >>> 15, t | 1);
5117
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
5118
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
5119
+ };
5120
+ }
5121
+ function kochRefine(ring2) {
5122
+ const out = [];
5123
+ const n = ring2.length;
5124
+ for (let i = 0; i < n; i++) {
5125
+ const a = ring2[i];
5126
+ const b = ring2[(i + 1) % n];
5127
+ const dx = b.x - a.x;
5128
+ const dy = b.y - a.y;
5129
+ const p1 = { x: a.x + dx / 3, y: a.y + dy / 3 };
5130
+ const p2 = { x: a.x + 2 * dx / 3, y: a.y + 2 * dy / 3 };
5131
+ const px = a.x + dx / 2 - dy * (Math.sqrt(3) / 6);
5132
+ const py = a.y + dy / 2 + dx * (Math.sqrt(3) / 6);
5133
+ out.push(a, p1, { x: px, y: py }, p2);
5134
+ }
5135
+ return out;
5136
+ }
5137
+ var construction = {
5138
+ createCircle,
5139
+ createRect,
5140
+ createRegularPolygon,
5141
+ createRing,
5142
+ createArc,
5143
+ createStar,
5144
+ createKochSnowflake,
5145
+ createSponge
5146
+ };
5147
+
5148
+ // src/tiling/index.ts
5149
+ function squareGrid2(spacing, minX, minY, maxX, maxY) {
5150
+ const s = Math.max(spacing, 1e-9);
5151
+ const paths = [];
5152
+ for (let x = minX; x < maxX - 1e-12; x += s) {
5153
+ for (let y = minY; y < maxY - 1e-12; y += s) {
5154
+ const w = Math.min(s, maxX - x);
5155
+ const h = Math.min(s, maxY - y);
5156
+ if (w > 1e-12 && h > 1e-12) paths.push(createRect(x, y, w, h));
5157
+ }
5158
+ }
5159
+ return group(paths);
5160
+ }
5161
+ function hexTiling(spacing, minX, minY, maxX, maxY) {
5162
+ const s = Math.max(spacing, 1e-9);
5163
+ const r = s / 2;
5164
+ const dx = s * 0.75;
5165
+ const dy = s * Math.sqrt(3) * 0.5;
5166
+ const paths = [];
5167
+ let row = 0;
5168
+ for (let y = minY + r; y <= maxY + r; y += dy, row++) {
5169
+ const x0 = minX + r + (row % 2 === 0 ? 0 : dx * 0.5);
5170
+ for (let x = x0; x <= maxX + r; x += dx) {
5171
+ paths.push(hexagon(x, y, r));
5172
+ }
5173
+ }
5174
+ return group(paths);
5175
+ }
5176
+ function hexagon(cx, cy, r) {
5177
+ const ring2 = [];
5178
+ for (let i = 0; i < 6; i++) {
5179
+ const t = Math.PI / 180 * (60 * i - 30);
5180
+ ring2.push({ x: cx + Math.cos(t) * r, y: cy + Math.sin(t) * r });
5181
+ }
5182
+ return polygon(ring2);
5183
+ }
5184
+ function rectSubdivision(p, nx, ny) {
5185
+ const b = bounds(p);
5186
+ const cols = Math.max(1, Math.floor(nx));
5187
+ const rows = Math.max(1, Math.floor(ny));
5188
+ const dw = (b.maxX - b.minX) / cols;
5189
+ const dh = (b.maxY - b.minY) / rows;
5190
+ const paths = [];
5191
+ for (let i = 0; i < cols; i++) {
5192
+ for (let j = 0; j < rows; j++) {
5193
+ const cell = createRect(
5194
+ b.minX + i * dw,
5195
+ b.minY + j * dh,
5196
+ dw,
5197
+ dh
5198
+ );
5199
+ paths.push(...intersect(p, cell).paths);
5200
+ }
5201
+ }
5202
+ return group(paths);
5203
+ }
5204
+ function quadSubdivision(faces, depth = 1) {
5205
+ let cur = faces;
5206
+ const d = Math.max(0, Math.floor(depth));
5207
+ for (let i = 0; i < d; i++) {
5208
+ const next = [];
5209
+ for (const f of cur) {
5210
+ next.push(...subdivideQuad(f));
5211
+ }
5212
+ cur = next;
5213
+ }
5214
+ return group(cur);
5215
+ }
5216
+ function subdivideQuad(p) {
5217
+ const ring2 = p.rings[0];
5218
+ if (!ring2 || ring2.length < 4) return [p];
5219
+ const [a, b, c, d] = ring2;
5220
+ if (!a || !b || !c || !d) return [p];
5221
+ const mab = mid4(a, b);
5222
+ const mbc = mid4(b, c);
5223
+ const mcd = mid4(c, d);
5224
+ const mda = mid4(d, a);
5225
+ const cen = mid4(mid4(a, c), mid4(b, d));
5226
+ return [
5227
+ polygon([a, mab, cen, mda]),
5228
+ polygon([mab, b, mbc, cen]),
5229
+ polygon([cen, mbc, c, mcd]),
5230
+ polygon([mda, cen, mcd, d])
5231
+ ];
5232
+ }
5233
+ function triangleSubdivision(faces, depth = 1) {
5234
+ let cur = faces;
5235
+ const d = Math.max(0, Math.floor(depth));
5236
+ for (let i = 0; i < d; i++) {
5237
+ const next = [];
5238
+ for (const f of cur) {
5239
+ next.push(...subdivideTriangle(f));
5240
+ }
5241
+ cur = next;
5242
+ }
5243
+ return group(cur);
5244
+ }
5245
+ function subdivideTriangle(p) {
5246
+ const ring2 = p.rings[0];
5247
+ if (!ring2 || ring2.length < 3) return [p];
5248
+ const a = ring2[0];
5249
+ const b = ring2[1];
5250
+ const c = ring2[2];
5251
+ const mab = mid4(a, b);
5252
+ const mbc = mid4(b, c);
5253
+ const mca = mid4(c, a);
5254
+ return [
5255
+ polygon([a, mab, mca]),
5256
+ polygon([mab, b, mbc]),
5257
+ polygon([mca, mbc, c]),
5258
+ polygon([mab, mbc, mca])
5259
+ ];
5260
+ }
5261
+ function sliceDivision(p, spacing, angle = 0) {
5262
+ const s = Math.max(spacing, 1e-9);
5263
+ const b = bounds(p);
5264
+ const cx = (b.minX + b.maxX) / 2;
5265
+ const cy = (b.minY + b.maxY) / 2;
5266
+ const diag = Math.hypot(b.maxX - b.minX, b.maxY - b.minY) + s;
5267
+ const ux = Math.cos(angle);
5268
+ const uy = Math.sin(angle);
5269
+ const nx = -uy;
5270
+ const ny = ux;
5271
+ const parts = [p];
5272
+ for (let t = -diag; t <= diag; t += s) {
5273
+ const a = { x: cx + nx * t - ux * diag, y: cy + ny * t - uy * diag };
5274
+ const bb = { x: cx + nx * t + ux * diag, y: cy + ny * t + uy * diag };
5275
+ const next = [];
5276
+ for (const piece of parts) {
5277
+ const g = slice(piece, a, bb);
5278
+ next.push(...g.paths.length ? g.paths : [piece]);
5279
+ }
5280
+ parts.length = 0;
5281
+ parts.push(...next);
5282
+ }
5283
+ return group(parts);
5284
+ }
5285
+ function hatchSubdivision(p, spacing, angle = 0) {
5286
+ return sliceDivision(p, spacing, angle);
5287
+ }
5288
+ function arcDivision(width2, height2, arcs, seed = 1, circlePoints = 64) {
5289
+ const w = Math.max(width2, 1e-9);
5290
+ const h = Math.max(height2, 1e-9);
5291
+ const n = Math.max(0, Math.floor(arcs));
5292
+ const segs = Math.max(8, Math.floor(circlePoints));
5293
+ const rect = createRect(0, 0, w, h);
5294
+ if (n === 0) return group([rect]);
5295
+ const rng = mulberry327(seed >>> 0);
5296
+ let faces = [rect];
5297
+ const minArea2 = w * h * 1e-8;
5298
+ for (let i = 0; i < n; i++) {
5299
+ const { cx, cy, r } = sampleBoundaryCircle(w, h, rng);
5300
+ const disk = createCircle(cx, cy, r, segs);
5301
+ const next = [];
5302
+ for (const face of faces) {
5303
+ for (const piece of intersect(face, disk).paths) {
5304
+ if (area(piece) > minArea2) next.push(piece);
5305
+ }
5306
+ for (const piece of subtract(face, disk).paths) {
5307
+ if (area(piece) > minArea2) next.push(piece);
5308
+ }
5309
+ }
5310
+ if (next.length > 0) faces = next;
5311
+ }
5312
+ return group(faces);
5313
+ }
5314
+ function sampleBoundaryCircle(w, h, rng) {
5315
+ const peri = 2 * (w + h);
5316
+ let t = rng() * peri;
5317
+ let cx;
5318
+ let cy;
5319
+ let side;
5320
+ if (t < w) {
5321
+ side = 0;
5322
+ cx = t;
5323
+ cy = 0;
5324
+ } else {
5325
+ t -= w;
5326
+ if (t < h) {
5327
+ side = 1;
5328
+ cx = w;
5329
+ cy = t;
5330
+ } else {
5331
+ t -= h;
5332
+ if (t < w) {
5333
+ side = 2;
5334
+ cx = w - t;
5335
+ cy = h;
5336
+ } else {
5337
+ side = 3;
5338
+ cx = 0;
5339
+ cy = h - (t - w);
5340
+ }
5341
+ }
5342
+ }
5343
+ const distOther = [
5344
+ cy,
5345
+ // to top
5346
+ w - cx,
5347
+ // to right
5348
+ h - cy,
5349
+ // to bottom
5350
+ cx
5351
+ // to left
5352
+ ];
5353
+ let minSecond = Infinity;
5354
+ for (let s = 0; s < 4; s++) {
5355
+ if (s === side) continue;
5356
+ minSecond = Math.min(minSecond, distOther[s]);
5357
+ }
5358
+ const maxR = Math.hypot(w, h);
5359
+ const r = minSecond + rng() * Math.max(0, maxR - minSecond);
5360
+ return { cx, cy, r: Math.max(r, minSecond + 1e-6) };
5361
+ }
5362
+ function mulberry327(a) {
5363
+ return function() {
5364
+ let t = a += 1831565813;
5365
+ t = Math.imul(t ^ t >>> 15, t | 1);
5366
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
5367
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
5368
+ };
5369
+ }
5370
+ function mid4(a, b) {
5371
+ return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
5372
+ }
5373
+ var tiling = {
5374
+ squareGrid: squareGrid2,
5375
+ hexTiling,
5376
+ rectSubdivision,
5377
+ quadSubdivision,
5378
+ triangleSubdivision,
5379
+ sliceDivision,
5380
+ hatchSubdivision,
5381
+ arcDivision
5382
+ };
5383
+
5384
+ // src/polygonisation/index.ts
5385
+ function maxArea(points) {
5386
+ return convexHull(points);
5387
+ }
5388
+ function minArea(points) {
5389
+ const hull2 = convexHull(points).rings[0] ?? [];
5390
+ if (hull2.length < 3) return convexHull(points);
5391
+ if (hull2.length === 3) return polygon(hull2);
5392
+ let best = null;
5393
+ let bestA = Infinity;
5394
+ const h = hull2;
5395
+ const n = h.length;
5396
+ for (let i = 0; i < n; i++) {
5397
+ for (let j = i + 1; j < n; j++) {
5398
+ for (let k = j + 1; k < n; k++) {
5399
+ const tri = polygon([h[i], h[j], h[k]]);
5400
+ if (!coversPoints(tri, points)) continue;
5401
+ const a = area(tri);
5402
+ if (a < bestA) {
5403
+ bestA = a;
5404
+ best = tri;
5405
+ }
5406
+ }
5407
+ }
5408
+ }
5409
+ return best ?? convexHull(points);
5410
+ }
5411
+ function minPerimeter(points) {
5412
+ if (points.length === 0) return polygon([]);
5413
+ if (points.length < 3) return polygon(points);
5414
+ const remaining = points.map((p) => ({ ...p }));
5415
+ const ordered = [remaining.shift()];
5416
+ while (remaining.length) {
5417
+ const last = ordered[ordered.length - 1];
5418
+ let bestI = 0;
5419
+ let bestD = Infinity;
5420
+ for (let i = 0; i < remaining.length; i++) {
5421
+ const d = Math.hypot(
5422
+ remaining[i].x - last.x,
5423
+ remaining[i].y - last.y
5424
+ );
5425
+ if (d < bestD) {
5426
+ bestD = d;
5427
+ bestI = i;
5428
+ }
5429
+ }
5430
+ ordered.push(remaining.splice(bestI, 1)[0]);
5431
+ }
5432
+ return polygon(ordered);
5433
+ }
5434
+ function horizontal(points) {
5435
+ const pts = [...points].sort(
5436
+ (a, b) => a.y === b.y ? a.x - b.x : a.y - b.y
5437
+ );
5438
+ return polygon(pts);
5439
+ }
5440
+ function vertical(points) {
5441
+ const pts = [...points].sort(
5442
+ (a, b) => a.x === b.x ? a.y - b.y : a.x - b.x
5443
+ );
5444
+ return polygon(pts);
5445
+ }
5446
+ function angular(points) {
5447
+ if (points.length === 0) return polygon([]);
5448
+ const c = centroidOf(points);
5449
+ const pts = [...points].sort(
5450
+ (a, b) => Math.atan2(a.y - c.y, a.x - c.x) - Math.atan2(b.y - c.y, b.x - c.x)
5451
+ );
5452
+ return polygon(pts);
5453
+ }
5454
+ function circular(points) {
5455
+ return angular(points);
5456
+ }
5457
+ function onion(points) {
5458
+ const layers = onionLayers(points);
5459
+ return layers[0] ?? polygon([]);
5460
+ }
5461
+ function onionLayers(points) {
5462
+ let remaining = points.map((p) => ({ ...p }));
5463
+ const layers = [];
5464
+ while (remaining.length >= 3) {
5465
+ const hull2 = convexHull(remaining);
5466
+ const ring2 = hull2.rings[0] ?? [];
5467
+ if (ring2.length < 3) break;
5468
+ layers.push(hull2);
5469
+ const onHull = new Set(ring2.map((v) => `${v.x},${v.y}`));
5470
+ remaining = remaining.filter((p) => !onHull.has(`${p.x},${p.y}`));
5471
+ }
5472
+ return layers;
5473
+ }
5474
+ function hilbert(points) {
5475
+ return polygon(hilbertSort(points));
5476
+ }
5477
+ function coversPoints(poly, points) {
5478
+ const r = poly.rings[0];
5479
+ if (!r || r.length < 3) return false;
5480
+ const a = r[0];
5481
+ const b = r[1];
5482
+ const c = r[2];
5483
+ for (const p of points) {
5484
+ if (!pointInTriangle(p, a, b, c)) return false;
5485
+ }
5486
+ return true;
5487
+ }
5488
+ function pointInTriangle(p, a, b, c) {
5489
+ const v0x = c.x - a.x;
5490
+ const v0y = c.y - a.y;
5491
+ const v1x = b.x - a.x;
5492
+ const v1y = b.y - a.y;
5493
+ const v2x = p.x - a.x;
5494
+ const v2y = p.y - a.y;
5495
+ const dot00 = v0x * v0x + v0y * v0y;
5496
+ const dot01 = v0x * v1x + v0y * v1y;
5497
+ const dot02 = v0x * v2x + v0y * v2y;
5498
+ const dot11 = v1x * v1x + v1y * v1y;
5499
+ const dot12 = v1x * v2x + v1y * v2y;
5500
+ const inv = 1 / (dot00 * dot11 - dot01 * dot01 || 1e-18);
5501
+ const u = (dot11 * dot02 - dot01 * dot12) * inv;
5502
+ const v = (dot00 * dot12 - dot01 * dot02) * inv;
5503
+ return u >= -1e-9 && v >= -1e-9 && u + v <= 1 + 1e-9;
5504
+ }
5505
+ function centroidOf(points) {
5506
+ let sx = 0;
5507
+ let sy = 0;
5508
+ for (const p of points) {
5509
+ sx += p.x;
5510
+ sy += p.y;
5511
+ }
5512
+ return { x: sx / points.length, y: sy / points.length };
5513
+ }
5514
+ var polygonisation = {
5515
+ maxArea,
5516
+ minArea,
5517
+ minPerimeter,
5518
+ horizontal,
5519
+ vertical,
5520
+ angular,
5521
+ circular,
5522
+ onion,
5523
+ onionLayers,
5524
+ hilbert
5525
+ };
5526
+
5527
+ export { angular, arcDivision, area, areaGroup, areaMerge, areaSimilarity, assertAreaClose, boundingBox, bounds, buffer, centerLine, centroid, centroidQuadrangulation, centroidSortFaces, centroidSplit, chaikinCut, chordalAxis, circle, circleCenter, circleContainedInPath, circleOverlapsPath, circlePacking, circular, clipSegmentToPath, clipSegmentsToPath, cloneGroup, clonePath, cloneVec2, closestPoint, closestPointPair, closestVertex, complement, compoundVoronoi, construction, containmentAgreement, containsPoint, contour, contrastField, conversion, convexHull, convexHullPath, copy, createArc, createCircle, createKochSnowflake, createRect, createRegularPolygon, createRing, createSponge, createStar, delaunayTriangulation, delaunayTriangulationPoints, densify, dilationErosion, dissolve, dissolveSegments, distanceField, distanceTree, dualFaces, earCutTriangulation, edgeCollapseQuadrangulation, envelope, equalsVec2, erosionDilation, extractBoundary, extractHoles, extractInnerEdges, extractInnerVertices, extractPerimeter, farthestPointPair, filterAxisAligned, filterByMinLength, findBreaks, findContainingFace, findIslands, findShortestTour, fixBreaks, fixBrokenFaces, flatten, flipHorizontal, flipVertical, fromArray, fromContours, frontChainPack, gabrielFaces, generateRandomGridPoints, generateRandomPoints, group, groupFromPaths, hatch, concentric as hatchConcentric, cross as hatchCross, parallel as hatchParallel, perpendicular as hatchPerpendicular, stochastic as hatchStochastic, hatchSubdivision, weave as hatchWeave, height, hexGrid, hexLatticePack, hexTiling, hilbert as hilbertPolygonise, hilbertSort, hull, innerVoronoi, innerVoronoiRaw, intersect, intersectionPoints, isolineZeroFromFunction, isolines, isolinesFromFunction, isolinesFromPoints, largestEmptyCircle, largestEmptyCircles, matchingQuadrangulation, maxArea, maximumInscribedAARectangle, maximumInscribedCircle, maximumInscribedPack, maximumInscribedPackUntil, medialAxis, meshing, minArea, minPerimeter, minimumBoundingCircle, minkDifference, minkSum, morphology, normalizeRing, obstaclePack, occlusionSubtract, offsetCurvesInward, offsetCurvesOutward, onion, onionLayers, optimisation, oraclePathsToGroup, originScale, overlapRegions, parallelSegments, parsePathData, parseSvg, path, perpendicularPathSegments, ring as pointRing, pointSet, pointsOnExterior, poisson, poissonTriangulation, poissonTriangulationPoints, polygon, polygonisation, horizontal as polygoniseHorizontal, vertical as polygoniseVertical, polyline, predicates, processing, prunePointsWithinDistance, quadSubdivision, radialSortFaces, radialWarp, random as randomPoints, rectSubdivision, reducePrecision, refine, relativeAreaError, relativeNeighborFaces, removeSmallHoles, repulsionPack, resizeByHeight, resizeByWidth, ringArea, ringsToPath, rotate, rotateAroundCenter, roundVertexCoords, scale, segment, segmentLength, segmentSet, segmentsOnExterior, segmentsToOpenPaths, serializePathData, shapeBoolean, shear, simplify, simplifyMesh, sineWarp, slice, sliceDivision, smooth, smoothMesh, spannerFaces, spiralQuadrangulation, splitEdges, splitQuadrangulation, squareGrid, squareLatticePack, squareGrid2 as squareTiling, stochasticMerge, stochasticPack, stochasticSegments, straightSkeleton, straightSkeletonParts, subdivideMesh, subtract, subtractAll, symDifference, tiling, toArray, toContours, toSvg, transformation, translate, translateCentroidTo, translateCornerTo, translateToOrigin, triangleSubdivision, triangulation, union, unionAll, urquhartFaces, vec2, voronoi, weaveSegments, width };
3932
5528
  //# sourceMappingURL=index.js.map
3933
5529
  //# sourceMappingURL=index.js.map