pattapatta 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +0 -35
- package/dist/contour/index.d.ts +89 -2
- package/dist/contour/index.js +1326 -3
- package/dist/contour/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1184 -144
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -825,8 +825,8 @@ function clipSegmentToPath(seg, clip) {
|
|
|
825
825
|
const h0 = hits[i];
|
|
826
826
|
const h1 = hits[i + 1];
|
|
827
827
|
if (h1.t - h0.t < 1e-9) continue;
|
|
828
|
-
const
|
|
829
|
-
if (containsPoint(clip,
|
|
828
|
+
const mid5 = vec2((h0.p.x + h1.p.x) / 2, (h0.p.y + h1.p.y) / 2);
|
|
829
|
+
if (containsPoint(clip, mid5)) {
|
|
830
830
|
out.push(segment(h0.p, h1.p));
|
|
831
831
|
}
|
|
832
832
|
}
|
|
@@ -1569,10 +1569,10 @@ function segmentsOnExterior(p) {
|
|
|
1569
1569
|
function slice(p, a, b) {
|
|
1570
1570
|
const dx = b.x - a.x;
|
|
1571
1571
|
const dy = b.y - a.y;
|
|
1572
|
-
const
|
|
1573
|
-
if (
|
|
1574
|
-
const nx = -dy /
|
|
1575
|
-
const ny = dx /
|
|
1572
|
+
const len2 = Math.hypot(dx, dy);
|
|
1573
|
+
if (len2 < 1e-12) return group([p]);
|
|
1574
|
+
const nx = -dy / len2;
|
|
1575
|
+
const ny = dx / len2;
|
|
1576
1576
|
const bb = bounds(p);
|
|
1577
1577
|
const span = Math.hypot(bb.maxX - bb.minX, bb.maxY - bb.minY) * 4 + 10;
|
|
1578
1578
|
const left = halfPlanePoly(a, nx, ny, span);
|
|
@@ -1601,9 +1601,9 @@ function intersectionPoints(a, b) {
|
|
|
1601
1601
|
for (const sb of segsB) {
|
|
1602
1602
|
const hit = segmentIntersection2(sa.a, sa.b, sb.a, sb.b);
|
|
1603
1603
|
if (!hit) continue;
|
|
1604
|
-
const
|
|
1605
|
-
if (seen.has(
|
|
1606
|
-
seen.add(
|
|
1604
|
+
const key4 = `${hit.x.toFixed(9)},${hit.y.toFixed(9)}`;
|
|
1605
|
+
if (seen.has(key4)) continue;
|
|
1606
|
+
seen.add(key4);
|
|
1607
1607
|
out.push(hit);
|
|
1608
1608
|
}
|
|
1609
1609
|
}
|
|
@@ -1762,76 +1762,6 @@ var conversion = {
|
|
|
1762
1762
|
group
|
|
1763
1763
|
};
|
|
1764
1764
|
|
|
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);
|
|
1788
|
-
}
|
|
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();
|
|
1794
|
-
}
|
|
1795
|
-
upper.push(p);
|
|
1796
|
-
}
|
|
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);
|
|
1817
|
-
}
|
|
1818
|
-
function uniquePoints(points) {
|
|
1819
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1820
|
-
const out = [];
|
|
1821
|
-
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 });
|
|
1826
|
-
}
|
|
1827
|
-
return out;
|
|
1828
|
-
}
|
|
1829
|
-
var hull = {
|
|
1830
|
-
convexHull,
|
|
1831
|
-
convexHullPath,
|
|
1832
|
-
boundingBox
|
|
1833
|
-
};
|
|
1834
|
-
|
|
1835
1765
|
// src/pointSet/index.ts
|
|
1836
1766
|
function random(count, minX, minY, maxX, maxY, seed = 1) {
|
|
1837
1767
|
const rng = mulberry323(seed >>> 0);
|
|
@@ -2121,9 +2051,9 @@ function longestEdgeMidpoint(a, b, c) {
|
|
|
2121
2051
|
let best = edges[0];
|
|
2122
2052
|
let bestLen = -1;
|
|
2123
2053
|
for (const e of edges) {
|
|
2124
|
-
const
|
|
2125
|
-
if (
|
|
2126
|
-
bestLen =
|
|
2054
|
+
const len2 = Math.hypot(e[0].x - e[1].x, e[0].y - e[1].y);
|
|
2055
|
+
if (len2 > bestLen) {
|
|
2056
|
+
bestLen = len2;
|
|
2127
2057
|
best = e;
|
|
2128
2058
|
}
|
|
2129
2059
|
}
|
|
@@ -2138,6 +2068,205 @@ var triangulation = {
|
|
|
2138
2068
|
refine
|
|
2139
2069
|
};
|
|
2140
2070
|
|
|
2071
|
+
// src/contour/dissolve.ts
|
|
2072
|
+
function key(v, digits = 9) {
|
|
2073
|
+
return `${v.x.toFixed(digits)},${v.y.toFixed(digits)}`;
|
|
2074
|
+
}
|
|
2075
|
+
function same(a, b, eps = 1e-9) {
|
|
2076
|
+
return Math.abs(a.x - b.x) <= eps && Math.abs(a.y - b.y) <= eps;
|
|
2077
|
+
}
|
|
2078
|
+
function dissolveSegments(segments) {
|
|
2079
|
+
const segs = segments.filter((s) => !same(s.a, s.b));
|
|
2080
|
+
if (segs.length === 0) return group([]);
|
|
2081
|
+
const adj = /* @__PURE__ */ new Map();
|
|
2082
|
+
const pts = /* @__PURE__ */ new Map();
|
|
2083
|
+
const add = (a, b, segIdx) => {
|
|
2084
|
+
const ka = key(a);
|
|
2085
|
+
const kb = key(b);
|
|
2086
|
+
pts.set(ka, a);
|
|
2087
|
+
pts.set(kb, b);
|
|
2088
|
+
if (!adj.has(ka)) adj.set(ka, []);
|
|
2089
|
+
if (!adj.has(kb)) adj.set(kb, []);
|
|
2090
|
+
adj.get(ka).push({ segIdx, other: kb });
|
|
2091
|
+
adj.get(kb).push({ segIdx, other: ka });
|
|
2092
|
+
};
|
|
2093
|
+
for (let i = 0; i < segs.length; i++) {
|
|
2094
|
+
add(segs[i].a, segs[i].b, i);
|
|
2095
|
+
}
|
|
2096
|
+
const used = /* @__PURE__ */ new Set();
|
|
2097
|
+
const paths = [];
|
|
2098
|
+
const unusedDegree = (k) => (adj.get(k) ?? []).filter((e) => !used.has(e.segIdx)).length;
|
|
2099
|
+
const nextUnused = (k) => (adj.get(k) ?? []).find((e) => !used.has(e.segIdx));
|
|
2100
|
+
const starts = [...adj.keys()].sort(
|
|
2101
|
+
(a, b) => unusedDegree(a) - unusedDegree(b)
|
|
2102
|
+
);
|
|
2103
|
+
for (const start of starts) {
|
|
2104
|
+
let link = nextUnused(start);
|
|
2105
|
+
while (link) {
|
|
2106
|
+
const chain = [pts.get(start)];
|
|
2107
|
+
used.add(link.segIdx);
|
|
2108
|
+
let next = link.other;
|
|
2109
|
+
chain.push(pts.get(next));
|
|
2110
|
+
while (unusedDegree(next) === 1) {
|
|
2111
|
+
const e = nextUnused(next);
|
|
2112
|
+
if (!e) break;
|
|
2113
|
+
used.add(e.segIdx);
|
|
2114
|
+
next = e.other;
|
|
2115
|
+
chain.push(pts.get(next));
|
|
2116
|
+
if (next === start) break;
|
|
2117
|
+
}
|
|
2118
|
+
if (chain.length >= 2) paths.push(polyline(chain));
|
|
2119
|
+
link = nextUnused(start);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
for (let i = 0; i < segs.length; i++) {
|
|
2123
|
+
if (used.has(i)) continue;
|
|
2124
|
+
const s = segs[i];
|
|
2125
|
+
paths.push(polyline([s.a, s.b]));
|
|
2126
|
+
}
|
|
2127
|
+
return group(paths);
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
// src/contour/chordalAxis.ts
|
|
2131
|
+
function mid(a, b) {
|
|
2132
|
+
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
2133
|
+
}
|
|
2134
|
+
function len(a, b) {
|
|
2135
|
+
return Math.hypot(b.x - a.x, b.y - a.y);
|
|
2136
|
+
}
|
|
2137
|
+
function edgeKey(a, b) {
|
|
2138
|
+
const ka = `${a.x.toFixed(8)},${a.y.toFixed(8)}`;
|
|
2139
|
+
const kb = `${b.x.toFixed(8)},${b.y.toFixed(8)}`;
|
|
2140
|
+
return ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`;
|
|
2141
|
+
}
|
|
2142
|
+
function chordalAxis(shape) {
|
|
2143
|
+
if (!shape.closed || shape.rings.length === 0) return dissolveSegments([]);
|
|
2144
|
+
const b = boundsSpan(shape);
|
|
2145
|
+
const densified = densify(shape, Math.max(b / 40, 1e-3));
|
|
2146
|
+
const tris = delaunayTriangulation(densified);
|
|
2147
|
+
if (tris.length === 0) return dissolveSegments([]);
|
|
2148
|
+
const edgeCount = /* @__PURE__ */ new Map();
|
|
2149
|
+
const triEdges = [];
|
|
2150
|
+
for (const t of tris) {
|
|
2151
|
+
const r = t.rings[0];
|
|
2152
|
+
if (!r || r.length < 3) continue;
|
|
2153
|
+
const a = r[0];
|
|
2154
|
+
const b0 = r[1];
|
|
2155
|
+
const c = r[2];
|
|
2156
|
+
triEdges.push([a, b0, c]);
|
|
2157
|
+
for (const [u, v] of [
|
|
2158
|
+
[a, b0],
|
|
2159
|
+
[b0, c],
|
|
2160
|
+
[c, a]
|
|
2161
|
+
]) {
|
|
2162
|
+
const k = edgeKey(u, v);
|
|
2163
|
+
edgeCount.set(k, (edgeCount.get(k) ?? 0) + 1);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
const isBoundary = (u, v) => (edgeCount.get(edgeKey(u, v)) ?? 0) <= 1;
|
|
2167
|
+
const segs = [];
|
|
2168
|
+
for (const [a, b0, c] of triEdges) {
|
|
2169
|
+
const edges = [
|
|
2170
|
+
[a, b0],
|
|
2171
|
+
[b0, c],
|
|
2172
|
+
[c, a]
|
|
2173
|
+
];
|
|
2174
|
+
const interior = edges.filter(([u, v]) => !isBoundary(u, v));
|
|
2175
|
+
const degree = interior.length;
|
|
2176
|
+
if (degree === 1) {
|
|
2177
|
+
const [u, v] = interior[0];
|
|
2178
|
+
const cen = centroid({ rings: [[a, b0, c]]});
|
|
2179
|
+
segs.push(segment(cen, mid(u, v)));
|
|
2180
|
+
} else if (degree === 2) {
|
|
2181
|
+
const m0 = mid(interior[0][0], interior[0][1]);
|
|
2182
|
+
const m1 = mid(interior[1][0], interior[1][1]);
|
|
2183
|
+
segs.push(segment(m0, m1));
|
|
2184
|
+
} else if (degree === 3) {
|
|
2185
|
+
const ranked = edges.map(([u, v]) => ({ u, v, l: len(u, v) })).sort((x, y) => y.l - x.l);
|
|
2186
|
+
const longest = ranked[0];
|
|
2187
|
+
const shortA = ranked[1];
|
|
2188
|
+
const shortB = ranked[2];
|
|
2189
|
+
const mL = mid(longest.u, longest.v);
|
|
2190
|
+
segs.push(segment(mid(shortA.u, shortA.v), mL));
|
|
2191
|
+
segs.push(segment(mid(shortB.u, shortB.v), mL));
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
return dissolveSegments(segs);
|
|
2195
|
+
}
|
|
2196
|
+
function boundsSpan(p) {
|
|
2197
|
+
let minX = Infinity;
|
|
2198
|
+
let minY = Infinity;
|
|
2199
|
+
let maxX = -Infinity;
|
|
2200
|
+
let maxY = -Infinity;
|
|
2201
|
+
for (const ring2 of p.rings) {
|
|
2202
|
+
for (const v of ring2) {
|
|
2203
|
+
minX = Math.min(minX, v.x);
|
|
2204
|
+
minY = Math.min(minY, v.y);
|
|
2205
|
+
maxX = Math.max(maxX, v.x);
|
|
2206
|
+
maxY = Math.max(maxY, v.y);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
return Math.max(maxX - minX, maxY - minY, 1);
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
// src/hull/index.ts
|
|
2213
|
+
function convexHull(points) {
|
|
2214
|
+
const pts = uniquePoints(points);
|
|
2215
|
+
if (pts.length < 3) return polygon(pts);
|
|
2216
|
+
pts.sort((a, b) => a.x === b.x ? a.y - b.y : a.x - b.x);
|
|
2217
|
+
const lower = [];
|
|
2218
|
+
for (const p of pts) {
|
|
2219
|
+
while (lower.length >= 2 && cross2(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
|
|
2220
|
+
lower.pop();
|
|
2221
|
+
}
|
|
2222
|
+
lower.push(p);
|
|
2223
|
+
}
|
|
2224
|
+
const upper = [];
|
|
2225
|
+
for (let i = pts.length - 1; i >= 0; i--) {
|
|
2226
|
+
const p = pts[i];
|
|
2227
|
+
while (upper.length >= 2 && cross2(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
|
|
2228
|
+
upper.pop();
|
|
2229
|
+
}
|
|
2230
|
+
upper.push(p);
|
|
2231
|
+
}
|
|
2232
|
+
lower.pop();
|
|
2233
|
+
upper.pop();
|
|
2234
|
+
return polygon([...lower, ...upper]);
|
|
2235
|
+
}
|
|
2236
|
+
function convexHullPath(p) {
|
|
2237
|
+
const pts = [];
|
|
2238
|
+
for (const ring2 of p.rings) pts.push(...ring2);
|
|
2239
|
+
return convexHull(pts);
|
|
2240
|
+
}
|
|
2241
|
+
function boundingBox(p) {
|
|
2242
|
+
const b = bounds(p);
|
|
2243
|
+
return polygon([
|
|
2244
|
+
{ x: b.minX, y: b.minY },
|
|
2245
|
+
{ x: b.maxX, y: b.minY },
|
|
2246
|
+
{ x: b.maxX, y: b.maxY },
|
|
2247
|
+
{ x: b.minX, y: b.maxY }
|
|
2248
|
+
]);
|
|
2249
|
+
}
|
|
2250
|
+
function cross2(o, a, b) {
|
|
2251
|
+
return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
2252
|
+
}
|
|
2253
|
+
function uniquePoints(points) {
|
|
2254
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2255
|
+
const out = [];
|
|
2256
|
+
for (const p of points) {
|
|
2257
|
+
const key4 = `${p.x},${p.y}`;
|
|
2258
|
+
if (seen.has(key4)) continue;
|
|
2259
|
+
seen.add(key4);
|
|
2260
|
+
out.push({ x: p.x, y: p.y });
|
|
2261
|
+
}
|
|
2262
|
+
return out;
|
|
2263
|
+
}
|
|
2264
|
+
var hull = {
|
|
2265
|
+
convexHull,
|
|
2266
|
+
convexHullPath,
|
|
2267
|
+
boundingBox
|
|
2268
|
+
};
|
|
2269
|
+
|
|
2141
2270
|
// src/optimisation/index.ts
|
|
2142
2271
|
function envelope(p) {
|
|
2143
2272
|
return boundingBox(p);
|
|
@@ -2170,12 +2299,12 @@ function maximumInscribedAARectangle(p) {
|
|
|
2170
2299
|
let hi = Math.min(w, h) / 2;
|
|
2171
2300
|
let best = hi;
|
|
2172
2301
|
for (let i = 0; i < 40; i++) {
|
|
2173
|
-
const
|
|
2174
|
-
if (fits(
|
|
2175
|
-
best =
|
|
2176
|
-
hi =
|
|
2302
|
+
const mid5 = (lo + hi) / 2;
|
|
2303
|
+
if (fits(mid5)) {
|
|
2304
|
+
best = mid5;
|
|
2305
|
+
hi = mid5;
|
|
2177
2306
|
} else {
|
|
2178
|
-
lo =
|
|
2307
|
+
lo = mid5;
|
|
2179
2308
|
}
|
|
2180
2309
|
}
|
|
2181
2310
|
const m = best;
|
|
@@ -2320,6 +2449,917 @@ var optimisation = {
|
|
|
2320
2449
|
farthestPointPair,
|
|
2321
2450
|
minimumBoundingCircle
|
|
2322
2451
|
};
|
|
2452
|
+
|
|
2453
|
+
// src/contour/boundaryDistance.ts
|
|
2454
|
+
function distanceToBoundary2(path3, p) {
|
|
2455
|
+
let best = Infinity;
|
|
2456
|
+
for (const ring2 of path3.rings) {
|
|
2457
|
+
if (ring2.length < 2) continue;
|
|
2458
|
+
const n = ring2.length;
|
|
2459
|
+
const closed = path3.closed;
|
|
2460
|
+
const limit = closed ? n : n - 1;
|
|
2461
|
+
for (let i = 0; i < limit; i++) {
|
|
2462
|
+
const a = ring2[i];
|
|
2463
|
+
const b = ring2[(i + 1) % n];
|
|
2464
|
+
best = Math.min(best, distPointSeg(p, a, b));
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
return best;
|
|
2468
|
+
}
|
|
2469
|
+
function sampleBoundary(path3, maxSegLen) {
|
|
2470
|
+
const pts = [];
|
|
2471
|
+
const lim = Math.max(maxSegLen, 1e-9);
|
|
2472
|
+
for (const ring2 of path3.rings) {
|
|
2473
|
+
if (ring2.length < 2) continue;
|
|
2474
|
+
const n = ring2.length;
|
|
2475
|
+
const limit = path3.closed ? n : n - 1;
|
|
2476
|
+
for (let i = 0; i < limit; i++) {
|
|
2477
|
+
const a = ring2[i];
|
|
2478
|
+
const b = ring2[(i + 1) % n];
|
|
2479
|
+
const len2 = Math.hypot(b.x - a.x, b.y - a.y);
|
|
2480
|
+
const steps = Math.max(1, Math.ceil(len2 / lim));
|
|
2481
|
+
for (let s = 0; s < steps; s++) {
|
|
2482
|
+
const t = s / steps;
|
|
2483
|
+
pts.push({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
return pts;
|
|
2488
|
+
}
|
|
2489
|
+
function distPointSeg(p, a, b) {
|
|
2490
|
+
const dx = b.x - a.x;
|
|
2491
|
+
const dy = b.y - a.y;
|
|
2492
|
+
const len2 = dx * dx + dy * dy;
|
|
2493
|
+
if (len2 < 1e-18) return Math.hypot(p.x - a.x, p.y - a.y);
|
|
2494
|
+
let t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;
|
|
2495
|
+
t = Math.max(0, Math.min(1, t));
|
|
2496
|
+
return Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy));
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
// src/contour/medialAxis.ts
|
|
2500
|
+
function medialAxis(shape, axialThreshold = 0, distanceThreshold = 0, areaThreshold = 0) {
|
|
2501
|
+
const g = buildMedialAxisGraph(shape);
|
|
2502
|
+
if (!g) return dissolveSegments([]);
|
|
2503
|
+
const kept = pruneEdges(g, axialThreshold, distanceThreshold, areaThreshold);
|
|
2504
|
+
const segs = kept.map((e) => {
|
|
2505
|
+
const a = g.nodes[e.head].position;
|
|
2506
|
+
const b = g.nodes[e.tail].position;
|
|
2507
|
+
return segment(a, b);
|
|
2508
|
+
});
|
|
2509
|
+
return dissolveSegments(segs);
|
|
2510
|
+
}
|
|
2511
|
+
function buildMedialAxisGraph(shape) {
|
|
2512
|
+
if (!shape.closed || shape.rings.length === 0) return null;
|
|
2513
|
+
const b = bounds(shape);
|
|
2514
|
+
const span = Math.max(b.maxX - b.minX, b.maxY - b.minY, 1);
|
|
2515
|
+
const densified = densify(shape, Math.max(span / 60, 1e-3));
|
|
2516
|
+
const samples = sampleBoundary(densified, Math.max(span / 80, 1e-3));
|
|
2517
|
+
if (samples.length < 3) return null;
|
|
2518
|
+
const uniq = [];
|
|
2519
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2520
|
+
for (const p of samples) {
|
|
2521
|
+
const k = `${p.x.toFixed(6)},${p.y.toFixed(6)}`;
|
|
2522
|
+
if (seen.has(k)) continue;
|
|
2523
|
+
seen.add(k);
|
|
2524
|
+
uniq.push(p);
|
|
2525
|
+
}
|
|
2526
|
+
if (uniq.length < 3) return null;
|
|
2527
|
+
const delaunay = Delaunay.from(
|
|
2528
|
+
uniq,
|
|
2529
|
+
(d) => d.x,
|
|
2530
|
+
(d) => d.y
|
|
2531
|
+
);
|
|
2532
|
+
const pad = span * 0.5;
|
|
2533
|
+
const voronoi2 = delaunay.voronoi([
|
|
2534
|
+
b.minX - pad,
|
|
2535
|
+
b.minY - pad,
|
|
2536
|
+
b.maxX + pad,
|
|
2537
|
+
b.maxY + pad
|
|
2538
|
+
]);
|
|
2539
|
+
const raw = [];
|
|
2540
|
+
const edgeSeen = /* @__PURE__ */ new Set();
|
|
2541
|
+
for (let i = 0; i < uniq.length; i++) {
|
|
2542
|
+
const poly = voronoi2.cellPolygon(i);
|
|
2543
|
+
if (!poly || poly.length < 2) continue;
|
|
2544
|
+
for (let j = 0; j < poly.length - 1; j++) {
|
|
2545
|
+
const p0 = poly[j];
|
|
2546
|
+
const p1 = poly[j + 1];
|
|
2547
|
+
const a = { x: p0[0], y: p0[1] };
|
|
2548
|
+
const bb = { x: p1[0], y: p1[1] };
|
|
2549
|
+
if (!containsPoint(shape, a) || !containsPoint(shape, bb)) continue;
|
|
2550
|
+
const ek = edgeKey2(a, bb);
|
|
2551
|
+
if (edgeSeen.has(ek)) continue;
|
|
2552
|
+
edgeSeen.add(ek);
|
|
2553
|
+
const mid5 = { x: (a.x + bb.x) / 2, y: (a.y + bb.y) / 2 };
|
|
2554
|
+
const site = uniq[i];
|
|
2555
|
+
const radius = Math.hypot(mid5.x - site.x, mid5.y - site.y);
|
|
2556
|
+
raw.push({ a, b: bb, radius });
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
if (raw.length === 0) return null;
|
|
2560
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
2561
|
+
const nodes = [];
|
|
2562
|
+
const ensure = (p, radius) => {
|
|
2563
|
+
const k = key2(p);
|
|
2564
|
+
let id = nodeIndex.get(k);
|
|
2565
|
+
if (id !== void 0) {
|
|
2566
|
+
nodes[id].radius = Math.max(nodes[id].radius, radius);
|
|
2567
|
+
return id;
|
|
2568
|
+
}
|
|
2569
|
+
id = nodes.length;
|
|
2570
|
+
nodeIndex.set(k, id);
|
|
2571
|
+
nodes.push({
|
|
2572
|
+
id,
|
|
2573
|
+
position: p,
|
|
2574
|
+
radius,
|
|
2575
|
+
parent: null,
|
|
2576
|
+
children: [],
|
|
2577
|
+
rootDist: 0,
|
|
2578
|
+
featureArea: Math.PI * radius * radius
|
|
2579
|
+
});
|
|
2580
|
+
return id;
|
|
2581
|
+
};
|
|
2582
|
+
const undirected = [];
|
|
2583
|
+
for (const e of raw) {
|
|
2584
|
+
const midR = e.radius;
|
|
2585
|
+
const u = ensure(e.a, midR);
|
|
2586
|
+
const v = ensure(e.b, midR);
|
|
2587
|
+
if (u === v) continue;
|
|
2588
|
+
const length = Math.hypot(
|
|
2589
|
+
nodes[u].position.x - nodes[v].position.x,
|
|
2590
|
+
nodes[u].position.y - nodes[v].position.y
|
|
2591
|
+
);
|
|
2592
|
+
const axial = length > 1e-12 ? Math.abs(nodes[u].radius - nodes[v].radius) / length : 0;
|
|
2593
|
+
undirected.push({ u, v, axial, length });
|
|
2594
|
+
}
|
|
2595
|
+
const mic = maximumInscribedCircle(shape, Math.max(span * 0.01, 0.1));
|
|
2596
|
+
const rootPos = mic ? { x: mic.x, y: mic.y } : {
|
|
2597
|
+
x: (b.minX + b.maxX) / 2,
|
|
2598
|
+
y: (b.minY + b.maxY) / 2
|
|
2599
|
+
};
|
|
2600
|
+
let root = 0;
|
|
2601
|
+
let bestD = Infinity;
|
|
2602
|
+
for (const n of nodes) {
|
|
2603
|
+
const d = Math.hypot(n.position.x - rootPos.x, n.position.y - rootPos.y);
|
|
2604
|
+
if (d < bestD) {
|
|
2605
|
+
bestD = d;
|
|
2606
|
+
root = n.id;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
const adj = /* @__PURE__ */ new Map();
|
|
2610
|
+
for (const e of undirected) {
|
|
2611
|
+
if (!adj.has(e.u)) adj.set(e.u, []);
|
|
2612
|
+
if (!adj.has(e.v)) adj.set(e.v, []);
|
|
2613
|
+
adj.get(e.u).push({ to: e.v, axial: e.axial, length: e.length });
|
|
2614
|
+
adj.get(e.v).push({ to: e.u, axial: e.axial, length: e.length });
|
|
2615
|
+
}
|
|
2616
|
+
const edges = [];
|
|
2617
|
+
const visited = /* @__PURE__ */ new Set([root]);
|
|
2618
|
+
const queue = [root];
|
|
2619
|
+
nodes[root].rootDist = 0;
|
|
2620
|
+
while (queue.length) {
|
|
2621
|
+
const u = queue.shift();
|
|
2622
|
+
for (const link of adj.get(u) ?? []) {
|
|
2623
|
+
if (visited.has(link.to)) continue;
|
|
2624
|
+
visited.add(link.to);
|
|
2625
|
+
nodes[link.to].parent = u;
|
|
2626
|
+
nodes[u].children.push(link.to);
|
|
2627
|
+
nodes[link.to].rootDist = nodes[u].rootDist + link.length;
|
|
2628
|
+
edges.push({
|
|
2629
|
+
head: u,
|
|
2630
|
+
tail: link.to,
|
|
2631
|
+
axial: link.axial,
|
|
2632
|
+
length: link.length
|
|
2633
|
+
});
|
|
2634
|
+
queue.push(link.to);
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
const post = [];
|
|
2638
|
+
const stack = [root];
|
|
2639
|
+
const seenN = /* @__PURE__ */ new Set();
|
|
2640
|
+
while (stack.length) {
|
|
2641
|
+
const u = stack.pop();
|
|
2642
|
+
if (seenN.has(u)) {
|
|
2643
|
+
post.push(u);
|
|
2644
|
+
continue;
|
|
2645
|
+
}
|
|
2646
|
+
seenN.add(u);
|
|
2647
|
+
stack.push(u);
|
|
2648
|
+
for (const c of nodes[u].children) stack.push(c);
|
|
2649
|
+
}
|
|
2650
|
+
for (const u of post) {
|
|
2651
|
+
let area2 = nodes[u].featureArea;
|
|
2652
|
+
for (const c of nodes[u].children) area2 += nodes[c].featureArea;
|
|
2653
|
+
nodes[u].featureArea = area2;
|
|
2654
|
+
}
|
|
2655
|
+
return { nodes, edges, root };
|
|
2656
|
+
}
|
|
2657
|
+
function pruneEdges(g, axialT, distT, areaT) {
|
|
2658
|
+
const maxDist = Math.max(...g.nodes.map((n) => n.rootDist), 1e-9);
|
|
2659
|
+
const maxArea2 = Math.max(...g.nodes.map((n) => n.featureArea), 1e-9);
|
|
2660
|
+
const maxAxial = Math.max(...g.edges.map((e) => e.axial), 1e-9);
|
|
2661
|
+
const aT = clamp01(axialT);
|
|
2662
|
+
const dT = clamp01(distT);
|
|
2663
|
+
const arT = clamp01(areaT);
|
|
2664
|
+
return g.edges.filter((e) => {
|
|
2665
|
+
const tail = g.nodes[e.tail];
|
|
2666
|
+
if (aT > 0 && e.axial / maxAxial < aT * 0.15 && aT > 0.05) {
|
|
2667
|
+
if (tail.children.length === 0 && e.axial / maxAxial < aT) return false;
|
|
2668
|
+
}
|
|
2669
|
+
if (dT > 0) {
|
|
2670
|
+
const nd = tail.rootDist / maxDist;
|
|
2671
|
+
if (tail.children.length === 0 && nd > 1 - dT && dT > 0) ;
|
|
2672
|
+
if (tail.children.length === 0 && 1 - nd < dT * 0.5 && dT >= 0.5) {
|
|
2673
|
+
return false;
|
|
2674
|
+
}
|
|
2675
|
+
if (tail.children.length === 0 && nd * dT > 0.85) return false;
|
|
2676
|
+
}
|
|
2677
|
+
if (arT > 0) {
|
|
2678
|
+
const na = tail.featureArea / maxArea2;
|
|
2679
|
+
if (na < arT) return false;
|
|
2680
|
+
}
|
|
2681
|
+
if (aT > 0 && e.axial / maxAxial > 1 - aT * 0.5 && tail.children.length === 0) {
|
|
2682
|
+
return false;
|
|
2683
|
+
}
|
|
2684
|
+
return true;
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
function centerLine(shape, straightnessWeighting = 0.7, smoothing = 50) {
|
|
2688
|
+
const g = buildMedialAxisGraph(shape);
|
|
2689
|
+
if (!g || g.nodes.length < 2) {
|
|
2690
|
+
return polyline([]);
|
|
2691
|
+
}
|
|
2692
|
+
const root = g.nodes[g.root];
|
|
2693
|
+
const childRoots = root.children;
|
|
2694
|
+
const leavesOf = (start) => {
|
|
2695
|
+
const out = [];
|
|
2696
|
+
const stack = [start];
|
|
2697
|
+
while (stack.length) {
|
|
2698
|
+
const u = stack.pop();
|
|
2699
|
+
const n = g.nodes[u];
|
|
2700
|
+
if (n.children.length === 0) out.push(n);
|
|
2701
|
+
else stack.push(...n.children);
|
|
2702
|
+
}
|
|
2703
|
+
return out;
|
|
2704
|
+
};
|
|
2705
|
+
let bestPath = [];
|
|
2706
|
+
if (childRoots.length <= 1) {
|
|
2707
|
+
bestPath = longestPathNodes(g);
|
|
2708
|
+
} else if (childRoots.length === 2) {
|
|
2709
|
+
bestPath = pathBetweenLeaves(
|
|
2710
|
+
g,
|
|
2711
|
+
leavesOf(childRoots[0]),
|
|
2712
|
+
leavesOf(childRoots[1]),
|
|
2713
|
+
root,
|
|
2714
|
+
straightnessWeighting
|
|
2715
|
+
);
|
|
2716
|
+
} else {
|
|
2717
|
+
const groups = childRoots.map(leavesOf);
|
|
2718
|
+
let bestW = -Infinity;
|
|
2719
|
+
for (let i = 0; i < groups.length; i++) {
|
|
2720
|
+
for (let j = i + 1; j < groups.length; j++) {
|
|
2721
|
+
const { path: p, weight } = bestLeafPair(
|
|
2722
|
+
g,
|
|
2723
|
+
groups[i],
|
|
2724
|
+
groups[j],
|
|
2725
|
+
root,
|
|
2726
|
+
straightnessWeighting
|
|
2727
|
+
);
|
|
2728
|
+
if (weight > bestW) {
|
|
2729
|
+
bestW = weight;
|
|
2730
|
+
bestPath = p;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
if (bestPath.length < 2) bestPath = longestPathNodes(g);
|
|
2736
|
+
const pts = bestPath.map((id) => g.nodes[id].position);
|
|
2737
|
+
return polyline(gaussianSmooth(pts, smoothing));
|
|
2738
|
+
}
|
|
2739
|
+
function bestLeafPair(g, a, b, root, straightness) {
|
|
2740
|
+
let bestW = -Infinity;
|
|
2741
|
+
let best = [];
|
|
2742
|
+
for (const d1 of a) {
|
|
2743
|
+
for (const d2 of b) {
|
|
2744
|
+
const angle = angleBetween(d1.position, root.position, d2.position);
|
|
2745
|
+
const aw = Math.pow(1 + angle, straightness);
|
|
2746
|
+
const weight = (d1.rootDist + d2.rootDist) * Math.max(aw - 1, 1);
|
|
2747
|
+
if (weight > bestW) {
|
|
2748
|
+
bestW = weight;
|
|
2749
|
+
best = [...pathToRoot(g, d1.id), ...pathToRoot(g, d2.id).reverse().slice(1)];
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
return { path: best, weight: bestW };
|
|
2754
|
+
}
|
|
2755
|
+
function pathBetweenLeaves(g, a, b, root, straightness) {
|
|
2756
|
+
return bestLeafPair(g, a, b, root, straightness).path;
|
|
2757
|
+
}
|
|
2758
|
+
function pathToRoot(g, id) {
|
|
2759
|
+
const path3 = [id];
|
|
2760
|
+
let cur = id;
|
|
2761
|
+
while (g.nodes[cur].parent !== null) {
|
|
2762
|
+
cur = g.nodes[cur].parent;
|
|
2763
|
+
path3.push(cur);
|
|
2764
|
+
}
|
|
2765
|
+
return path3;
|
|
2766
|
+
}
|
|
2767
|
+
function longestPathNodes(g) {
|
|
2768
|
+
const leaves = g.nodes.filter((n) => n.children.length === 0 && n.id !== g.root);
|
|
2769
|
+
if (leaves.length === 0) return g.nodes.map((n) => n.id);
|
|
2770
|
+
let farthest = leaves[0];
|
|
2771
|
+
let best = -1;
|
|
2772
|
+
for (const L of leaves) {
|
|
2773
|
+
if (L.rootDist > best) {
|
|
2774
|
+
best = L.rootDist;
|
|
2775
|
+
farthest = L;
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
let bestPath = pathToRoot(g, farthest.id);
|
|
2779
|
+
let bestLen = farthest.rootDist;
|
|
2780
|
+
for (const L of leaves) {
|
|
2781
|
+
if (L.id === farthest.id) continue;
|
|
2782
|
+
const p = [
|
|
2783
|
+
...pathToRoot(g, farthest.id),
|
|
2784
|
+
...pathToRoot(g, L.id).reverse().slice(1)
|
|
2785
|
+
];
|
|
2786
|
+
const len2 = farthest.rootDist + L.rootDist;
|
|
2787
|
+
if (len2 > bestLen) {
|
|
2788
|
+
bestLen = len2;
|
|
2789
|
+
bestPath = p;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
return bestPath;
|
|
2793
|
+
}
|
|
2794
|
+
function angleBetween(a, apex, b) {
|
|
2795
|
+
const ax = a.x - apex.x;
|
|
2796
|
+
const ay = a.y - apex.y;
|
|
2797
|
+
const bx = b.x - apex.x;
|
|
2798
|
+
const by = b.y - apex.y;
|
|
2799
|
+
const la = Math.hypot(ax, ay) || 1;
|
|
2800
|
+
const lb = Math.hypot(bx, by) || 1;
|
|
2801
|
+
const cos = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
|
|
2802
|
+
return Math.acos(cos);
|
|
2803
|
+
}
|
|
2804
|
+
function gaussianSmooth(pts, sigma) {
|
|
2805
|
+
if (pts.length < 3 || sigma <= 0) return pts;
|
|
2806
|
+
const radius = Math.max(1, Math.round(Math.min(pts.length / 4, sigma / 10)));
|
|
2807
|
+
const out = [];
|
|
2808
|
+
for (let i = 0; i < pts.length; i++) {
|
|
2809
|
+
let sx = 0;
|
|
2810
|
+
let sy = 0;
|
|
2811
|
+
let w = 0;
|
|
2812
|
+
for (let k = -radius; k <= radius; k++) {
|
|
2813
|
+
const j = Math.max(0, Math.min(pts.length - 1, i + k));
|
|
2814
|
+
const wk = Math.exp(-(k * k) / (2 * (radius * 0.5) ** 2 + 1e-9));
|
|
2815
|
+
sx += pts[j].x * wk;
|
|
2816
|
+
sy += pts[j].y * wk;
|
|
2817
|
+
w += wk;
|
|
2818
|
+
}
|
|
2819
|
+
out.push({ x: sx / w, y: sy / w });
|
|
2820
|
+
}
|
|
2821
|
+
out[0] = pts[0];
|
|
2822
|
+
out[out.length - 1] = pts[pts.length - 1];
|
|
2823
|
+
return out;
|
|
2824
|
+
}
|
|
2825
|
+
function key2(p) {
|
|
2826
|
+
return `${p.x.toFixed(7)},${p.y.toFixed(7)}`;
|
|
2827
|
+
}
|
|
2828
|
+
function edgeKey2(a, b) {
|
|
2829
|
+
const ka = key2(a);
|
|
2830
|
+
const kb = key2(b);
|
|
2831
|
+
return ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`;
|
|
2832
|
+
}
|
|
2833
|
+
function clamp01(v) {
|
|
2834
|
+
return Math.max(0, Math.min(1, v));
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
// src/contour/straightSkeleton.ts
|
|
2838
|
+
function straightSkeleton(shape) {
|
|
2839
|
+
const parts = straightSkeletonParts(shape);
|
|
2840
|
+
return group([
|
|
2841
|
+
...parts.faces.paths,
|
|
2842
|
+
...parts.branches.paths,
|
|
2843
|
+
...parts.bones.paths
|
|
2844
|
+
]);
|
|
2845
|
+
}
|
|
2846
|
+
function straightSkeletonParts(shape) {
|
|
2847
|
+
const empty = {
|
|
2848
|
+
faces: group([]),
|
|
2849
|
+
branches: group([]),
|
|
2850
|
+
bones: group([])
|
|
2851
|
+
};
|
|
2852
|
+
if (!shape.closed || !shape.rings[0] || shape.rings[0].length < 3) {
|
|
2853
|
+
return empty;
|
|
2854
|
+
}
|
|
2855
|
+
const b = bounds(shape);
|
|
2856
|
+
const span = Math.max(b.maxX - b.minX, b.maxY - b.minY, 1);
|
|
2857
|
+
const delta = Math.max(span / 40, 0.5);
|
|
2858
|
+
const densified = densify(shape, Math.max(span / 40, 1e-3));
|
|
2859
|
+
const levels = [densified];
|
|
2860
|
+
let current = densified;
|
|
2861
|
+
for (let i = 0; i < 40; i++) {
|
|
2862
|
+
const next = buffer(current, -delta);
|
|
2863
|
+
if (next.paths.length === 0) break;
|
|
2864
|
+
let best = next.paths[0];
|
|
2865
|
+
let bestA = -1;
|
|
2866
|
+
for (const p of next.paths) {
|
|
2867
|
+
const bb = bounds(p);
|
|
2868
|
+
const a = (bb.maxX - bb.minX) * (bb.maxY - bb.minY);
|
|
2869
|
+
if (a > bestA) {
|
|
2870
|
+
bestA = a;
|
|
2871
|
+
best = p;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
if (!best.rings[0] || best.rings[0].length < 3) break;
|
|
2875
|
+
levels.push(best);
|
|
2876
|
+
current = best;
|
|
2877
|
+
}
|
|
2878
|
+
const boneSegs = [];
|
|
2879
|
+
for (let i = 0; i < levels.length - 1; i++) {
|
|
2880
|
+
const a = levels[i];
|
|
2881
|
+
const bb = levels[i + 1];
|
|
2882
|
+
const ca = centroid(a);
|
|
2883
|
+
const cb = centroid(bb);
|
|
2884
|
+
if (containsPoint(shape, mid2(ca, cb))) {
|
|
2885
|
+
boneSegs.push(segment(ca, cb));
|
|
2886
|
+
}
|
|
2887
|
+
for (const ring2 of a.rings) {
|
|
2888
|
+
for (const p of ring2) {
|
|
2889
|
+
const q = nearestOnPath(bb, p);
|
|
2890
|
+
if (q && containsPoint(shape, mid2(p, q))) {
|
|
2891
|
+
boneSegs.push(segment(p, q));
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
const branchSegs = [];
|
|
2897
|
+
const outer = densified.rings[0];
|
|
2898
|
+
const target = levels[1] ?? levels[0];
|
|
2899
|
+
for (let i = 0; i < outer.length; i++) {
|
|
2900
|
+
const a = outer[i];
|
|
2901
|
+
const bb = outer[(i + 1) % outer.length];
|
|
2902
|
+
const m = mid2(a, bb);
|
|
2903
|
+
const q = nearestOnPath(target, m);
|
|
2904
|
+
if (q) branchSegs.push(segment(m, q));
|
|
2905
|
+
}
|
|
2906
|
+
const faces = [];
|
|
2907
|
+
if (levels.length >= 2) {
|
|
2908
|
+
const inner = levels[1].rings[0] ?? [];
|
|
2909
|
+
for (let i = 0; i < outer.length; i++) {
|
|
2910
|
+
const a = outer[i];
|
|
2911
|
+
const bb = outer[(i + 1) % outer.length];
|
|
2912
|
+
const ia = nearestOnRing(inner, a);
|
|
2913
|
+
const ib = nearestOnRing(inner, bb);
|
|
2914
|
+
if (ia && ib) {
|
|
2915
|
+
const face = [a, bb, ib, ia];
|
|
2916
|
+
const c = {
|
|
2917
|
+
x: (a.x + bb.x + ib.x + ia.x) / 4,
|
|
2918
|
+
y: (a.y + bb.y + ib.y + ia.y) / 4
|
|
2919
|
+
};
|
|
2920
|
+
if (containsPoint(shape, c)) faces.push(polygon(face));
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
return {
|
|
2925
|
+
faces: group(faces),
|
|
2926
|
+
branches: dissolveSegments(branchSegs),
|
|
2927
|
+
bones: dissolveSegments(boneSegs)
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
function mid2(a, b) {
|
|
2931
|
+
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
2932
|
+
}
|
|
2933
|
+
function nearestOnPath(p, q) {
|
|
2934
|
+
let best = null;
|
|
2935
|
+
let bestD = Infinity;
|
|
2936
|
+
for (const ring2 of p.rings) {
|
|
2937
|
+
for (const v of ring2) {
|
|
2938
|
+
const d = Math.hypot(v.x - q.x, v.y - q.y);
|
|
2939
|
+
if (d < bestD) {
|
|
2940
|
+
bestD = d;
|
|
2941
|
+
best = v;
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
return best;
|
|
2946
|
+
}
|
|
2947
|
+
function nearestOnRing(ring2, q) {
|
|
2948
|
+
if (ring2.length === 0) return null;
|
|
2949
|
+
let best = ring2[0];
|
|
2950
|
+
let bestD = Infinity;
|
|
2951
|
+
for (const v of ring2) {
|
|
2952
|
+
const d = Math.hypot(v.x - q.x, v.y - q.y);
|
|
2953
|
+
if (d < bestD) {
|
|
2954
|
+
bestD = d;
|
|
2955
|
+
best = v;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
return best;
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
// src/contour/marchingSquares.ts
|
|
2962
|
+
function isolinesFromFunction(bounds2, sampleSpacing, contourInterval, fn, isolineMin = Number.NaN, isolineMax = Number.NaN) {
|
|
2963
|
+
const [xmin, ymin, xmax, ymax] = bounds2;
|
|
2964
|
+
const dx = Math.max(sampleSpacing, 1e-9);
|
|
2965
|
+
const dy = dx;
|
|
2966
|
+
const nx = Math.max(2, Math.ceil((xmax - xmin) / dx) + 1);
|
|
2967
|
+
const ny = Math.max(2, Math.ceil((ymax - ymin) / dy) + 1);
|
|
2968
|
+
const grid = [];
|
|
2969
|
+
let gmin = Infinity;
|
|
2970
|
+
let gmax = -Infinity;
|
|
2971
|
+
for (let j = 0; j < ny; j++) {
|
|
2972
|
+
const row = [];
|
|
2973
|
+
const y = ymin + j * ((ymax - ymin) / (ny - 1));
|
|
2974
|
+
for (let i = 0; i < nx; i++) {
|
|
2975
|
+
const x = xmin + i * ((xmax - xmin) / (nx - 1));
|
|
2976
|
+
let v = fn(x, y);
|
|
2977
|
+
if (!Number.isFinite(v)) v = Number.NEGATIVE_INFINITY;
|
|
2978
|
+
row.push(v);
|
|
2979
|
+
if (Number.isFinite(v)) {
|
|
2980
|
+
gmin = Math.min(gmin, v);
|
|
2981
|
+
gmax = Math.max(gmax, v);
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
grid.push(row);
|
|
2985
|
+
}
|
|
2986
|
+
const z0 = Number.isFinite(isolineMin) ? isolineMin : gmin;
|
|
2987
|
+
const z1 = Number.isFinite(isolineMax) ? isolineMax : gmax;
|
|
2988
|
+
if (!Number.isFinite(z0) || !Number.isFinite(z1) || contourInterval <= 0) {
|
|
2989
|
+
return group([]);
|
|
2990
|
+
}
|
|
2991
|
+
const levels = [];
|
|
2992
|
+
const start = Math.ceil(z0 / contourInterval) * contourInterval;
|
|
2993
|
+
for (let z = start; z <= z1 + 1e-12; z += contourInterval) {
|
|
2994
|
+
levels.push(z);
|
|
2995
|
+
}
|
|
2996
|
+
const segs = [];
|
|
2997
|
+
for (const level of levels) {
|
|
2998
|
+
segs.push(...marchLevel(grid, xmin, ymin, xmax, ymax, nx, ny, level));
|
|
2999
|
+
}
|
|
3000
|
+
return dissolveSegments(segs);
|
|
3001
|
+
}
|
|
3002
|
+
function isolineZeroFromFunction(bounds2, sampleSpacing, fn) {
|
|
3003
|
+
return isolinesFromFunction(bounds2, sampleSpacing, 1, fn, 0, 0);
|
|
3004
|
+
}
|
|
3005
|
+
function marchLevel(grid, xmin, ymin, xmax, ymax, nx, ny, level) {
|
|
3006
|
+
const segs = [];
|
|
3007
|
+
const xAt = (i) => xmin + i / (nx - 1) * (xmax - xmin);
|
|
3008
|
+
const yAt = (j) => ymin + j / (ny - 1) * (ymax - ymin);
|
|
3009
|
+
const lerp2 = (x0, y0, v0, x1, y1, v1) => {
|
|
3010
|
+
const t = (level - v0) / (v1 - v0 || 1e-15);
|
|
3011
|
+
return { x: x0 + t * (x1 - x0), y: y0 + t * (y1 - y0) };
|
|
3012
|
+
};
|
|
3013
|
+
for (let j = 0; j < ny - 1; j++) {
|
|
3014
|
+
for (let i = 0; i < nx - 1; i++) {
|
|
3015
|
+
const v00 = grid[j][i];
|
|
3016
|
+
const v10 = grid[j][i + 1];
|
|
3017
|
+
const v11 = grid[j + 1][i + 1];
|
|
3018
|
+
const v01 = grid[j + 1][i];
|
|
3019
|
+
const x0 = xAt(i);
|
|
3020
|
+
const x1 = xAt(i + 1);
|
|
3021
|
+
const y0 = yAt(j);
|
|
3022
|
+
const y1 = yAt(j + 1);
|
|
3023
|
+
let code = 0;
|
|
3024
|
+
if (v00 >= level) code |= 1;
|
|
3025
|
+
if (v10 >= level) code |= 2;
|
|
3026
|
+
if (v11 >= level) code |= 4;
|
|
3027
|
+
if (v01 >= level) code |= 8;
|
|
3028
|
+
if (code === 0 || code === 15) continue;
|
|
3029
|
+
const bottom = () => lerp2(x0, y0, v00, x1, y0, v10);
|
|
3030
|
+
const right = () => lerp2(x1, y0, v10, x1, y1, v11);
|
|
3031
|
+
const top = () => lerp2(x0, y1, v01, x1, y1, v11);
|
|
3032
|
+
const left = () => lerp2(x0, y0, v00, x0, y1, v01);
|
|
3033
|
+
const add = (a, b) => segs.push(segment(a, b));
|
|
3034
|
+
switch (code) {
|
|
3035
|
+
case 1:
|
|
3036
|
+
case 14:
|
|
3037
|
+
add(left(), bottom());
|
|
3038
|
+
break;
|
|
3039
|
+
case 2:
|
|
3040
|
+
case 13:
|
|
3041
|
+
add(bottom(), right());
|
|
3042
|
+
break;
|
|
3043
|
+
case 3:
|
|
3044
|
+
case 12:
|
|
3045
|
+
add(left(), right());
|
|
3046
|
+
break;
|
|
3047
|
+
case 4:
|
|
3048
|
+
case 11:
|
|
3049
|
+
add(right(), top());
|
|
3050
|
+
break;
|
|
3051
|
+
case 6:
|
|
3052
|
+
case 9:
|
|
3053
|
+
add(bottom(), top());
|
|
3054
|
+
break;
|
|
3055
|
+
case 7:
|
|
3056
|
+
case 8:
|
|
3057
|
+
add(left(), top());
|
|
3058
|
+
break;
|
|
3059
|
+
case 5: {
|
|
3060
|
+
const avg = (v00 + v10 + v11 + v01) / 4;
|
|
3061
|
+
if (avg >= level) {
|
|
3062
|
+
add(left(), top());
|
|
3063
|
+
add(bottom(), right());
|
|
3064
|
+
} else {
|
|
3065
|
+
add(left(), bottom());
|
|
3066
|
+
add(right(), top());
|
|
3067
|
+
}
|
|
3068
|
+
break;
|
|
3069
|
+
}
|
|
3070
|
+
case 10: {
|
|
3071
|
+
const avg = (v00 + v10 + v11 + v01) / 4;
|
|
3072
|
+
if (avg >= level) {
|
|
3073
|
+
add(left(), bottom());
|
|
3074
|
+
add(right(), top());
|
|
3075
|
+
} else {
|
|
3076
|
+
add(left(), top());
|
|
3077
|
+
add(bottom(), right());
|
|
3078
|
+
}
|
|
3079
|
+
break;
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
return segs;
|
|
3085
|
+
}
|
|
3086
|
+
function isolinesFromPoints(points, values, intervals, _smoothing = 0) {
|
|
3087
|
+
if (points.length < 3 || intervals < 1) return group([]);
|
|
3088
|
+
let minZ = Infinity;
|
|
3089
|
+
let maxZ = -Infinity;
|
|
3090
|
+
for (const v of values) {
|
|
3091
|
+
minZ = Math.min(minZ, v);
|
|
3092
|
+
maxZ = Math.max(maxZ, v);
|
|
3093
|
+
}
|
|
3094
|
+
if (!Number.isFinite(minZ) || maxZ - minZ < 1e-15) return group([]);
|
|
3095
|
+
let minX = Infinity;
|
|
3096
|
+
let minY = Infinity;
|
|
3097
|
+
let maxX = -Infinity;
|
|
3098
|
+
let maxY = -Infinity;
|
|
3099
|
+
for (const p of points) {
|
|
3100
|
+
minX = Math.min(minX, p.x);
|
|
3101
|
+
minY = Math.min(minY, p.y);
|
|
3102
|
+
maxX = Math.max(maxX, p.x);
|
|
3103
|
+
maxY = Math.max(maxY, p.y);
|
|
3104
|
+
}
|
|
3105
|
+
const span = Math.max(maxX - minX, maxY - minY, 1);
|
|
3106
|
+
const sampleSpacing = span / Math.max(20, Math.sqrt(points.length));
|
|
3107
|
+
const interval = (maxZ - minZ) / intervals;
|
|
3108
|
+
const fn = (x, y) => {
|
|
3109
|
+
let num2 = 0;
|
|
3110
|
+
let den = 0;
|
|
3111
|
+
for (let i = 0; i < points.length; i++) {
|
|
3112
|
+
const p = points[i];
|
|
3113
|
+
const d2 = (x - p.x) ** 2 + (y - p.y) ** 2;
|
|
3114
|
+
const w = 1 / Math.max(d2, 1e-12);
|
|
3115
|
+
num2 += w * values[i];
|
|
3116
|
+
den += w;
|
|
3117
|
+
}
|
|
3118
|
+
return num2 / den;
|
|
3119
|
+
};
|
|
3120
|
+
return isolinesFromFunction(
|
|
3121
|
+
[minX, minY, maxX, maxY],
|
|
3122
|
+
sampleSpacing,
|
|
3123
|
+
interval,
|
|
3124
|
+
fn,
|
|
3125
|
+
minZ,
|
|
3126
|
+
maxZ
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
// src/contour/fields.ts
|
|
3131
|
+
function distanceField(shape, spacing, pole) {
|
|
3132
|
+
if (!shape.closed || shape.rings.length === 0 || spacing <= 0) return group([]);
|
|
3133
|
+
const b = bounds(shape);
|
|
3134
|
+
const span = Math.max(b.maxX - b.minX, b.maxY - b.minY, 1);
|
|
3135
|
+
let polePt = pole;
|
|
3136
|
+
if (!polePt) {
|
|
3137
|
+
const mic = maximumInscribedCircle(shape, Math.max(span * 0.01, 0.1));
|
|
3138
|
+
polePt = mic ? { x: mic.x, y: mic.y } : { x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 };
|
|
3139
|
+
}
|
|
3140
|
+
const pad = Math.max(spacing, 1);
|
|
3141
|
+
const box = [
|
|
3142
|
+
b.minX - pad,
|
|
3143
|
+
b.minY - pad,
|
|
3144
|
+
b.maxX + pad,
|
|
3145
|
+
b.maxY + pad
|
|
3146
|
+
];
|
|
3147
|
+
const sampleSpacing = Math.max(spacing / 10, span / 80);
|
|
3148
|
+
const fn = (x, y) => {
|
|
3149
|
+
const p = { x, y };
|
|
3150
|
+
if (!insideSafe(shape, p)) return Number.NEGATIVE_INFINITY;
|
|
3151
|
+
const dGeo = distanceToBoundary2(shape, p);
|
|
3152
|
+
const dPoint = Math.hypot(x - polePt.x, y - polePt.y);
|
|
3153
|
+
return dGeo - dPoint;
|
|
3154
|
+
};
|
|
3155
|
+
const raw = isolinesFromFunction(box, sampleSpacing, spacing, fn);
|
|
3156
|
+
return clipPolylinesToPath(raw, shape);
|
|
3157
|
+
}
|
|
3158
|
+
function contrastField(shape, intervals, reference) {
|
|
3159
|
+
if (!shape.closed || shape.rings.length === 0 || intervals < 1) {
|
|
3160
|
+
return group([]);
|
|
3161
|
+
}
|
|
3162
|
+
const b = bounds(shape);
|
|
3163
|
+
const areaApprox = (b.maxX - b.minX) * (b.maxY - b.minY);
|
|
3164
|
+
const count = Math.max(100, Math.floor(areaApprox / 100));
|
|
3165
|
+
const minDist = Math.sqrt(areaApprox / (count * 1.5));
|
|
3166
|
+
const samples = poisson(
|
|
3167
|
+
Math.max(minDist, 1e-3),
|
|
3168
|
+
b.minX,
|
|
3169
|
+
b.minY,
|
|
3170
|
+
b.maxX,
|
|
3171
|
+
b.maxY,
|
|
3172
|
+
1337
|
|
3173
|
+
).filter((p) => containsPoint(shape, p));
|
|
3174
|
+
if (samples.length < 3) return group([]);
|
|
3175
|
+
const values = samples.map((p) => {
|
|
3176
|
+
const dB = distanceToBoundary2(shape, p);
|
|
3177
|
+
const dR = Math.hypot(p.x - reference.x, p.y - reference.y);
|
|
3178
|
+
return Math.abs(dB - dR);
|
|
3179
|
+
});
|
|
3180
|
+
const lines = isolinesFromPoints(samples, values, Math.max(1, intervals), 0);
|
|
3181
|
+
return clipPolylinesToPath(lines, shape);
|
|
3182
|
+
}
|
|
3183
|
+
function isolines(shape, highPoint, intervalSpacing) {
|
|
3184
|
+
if (!shape.closed || intervalSpacing <= 0) return group([]);
|
|
3185
|
+
const b = bounds(shape);
|
|
3186
|
+
const span = Math.max(b.maxX - b.minX, b.maxY - b.minY, 1);
|
|
3187
|
+
const pad = intervalSpacing;
|
|
3188
|
+
const box = [
|
|
3189
|
+
b.minX - pad,
|
|
3190
|
+
b.minY - pad,
|
|
3191
|
+
b.maxX + pad,
|
|
3192
|
+
b.maxY + pad
|
|
3193
|
+
];
|
|
3194
|
+
const fn = (x, y) => {
|
|
3195
|
+
if (!containsPoint(shape, { x, y })) return -1;
|
|
3196
|
+
return Math.hypot(x - highPoint.x, y - highPoint.y);
|
|
3197
|
+
};
|
|
3198
|
+
const raw = isolinesFromFunction(
|
|
3199
|
+
box,
|
|
3200
|
+
Math.max(intervalSpacing / 8, span / 100),
|
|
3201
|
+
intervalSpacing,
|
|
3202
|
+
fn,
|
|
3203
|
+
0,
|
|
3204
|
+
span
|
|
3205
|
+
);
|
|
3206
|
+
return clipPolylinesToPath(raw, shape);
|
|
3207
|
+
}
|
|
3208
|
+
function clipPolylinesToPath(g, shape) {
|
|
3209
|
+
const paths = [];
|
|
3210
|
+
for (const p of g.paths) {
|
|
3211
|
+
const ring2 = p.rings[0];
|
|
3212
|
+
if (!ring2 || ring2.length < 2) continue;
|
|
3213
|
+
let current = [];
|
|
3214
|
+
const flush = () => {
|
|
3215
|
+
if (current.length >= 2) {
|
|
3216
|
+
paths.push({ rings: [current], closed: false });
|
|
3217
|
+
}
|
|
3218
|
+
current = [];
|
|
3219
|
+
};
|
|
3220
|
+
for (let i = 0; i < ring2.length - 1; i++) {
|
|
3221
|
+
const a = ring2[i];
|
|
3222
|
+
const b = ring2[i + 1];
|
|
3223
|
+
if (!finitePt(a) || !finitePt(b)) {
|
|
3224
|
+
flush();
|
|
3225
|
+
continue;
|
|
3226
|
+
}
|
|
3227
|
+
const mid5 = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
3228
|
+
if (insideSafe(shape, mid5)) {
|
|
3229
|
+
if (current.length === 0) current.push(a);
|
|
3230
|
+
current.push(b);
|
|
3231
|
+
} else {
|
|
3232
|
+
flush();
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
flush();
|
|
3236
|
+
}
|
|
3237
|
+
return group(paths);
|
|
3238
|
+
}
|
|
3239
|
+
function finitePt(p) {
|
|
3240
|
+
return Number.isFinite(p.x) && Number.isFinite(p.y);
|
|
3241
|
+
}
|
|
3242
|
+
function insideSafe(shape, point) {
|
|
3243
|
+
if (!finitePt(point)) return false;
|
|
3244
|
+
try {
|
|
3245
|
+
return containsPoint(shape, point);
|
|
3246
|
+
} catch {
|
|
3247
|
+
return pointInPoly2(point, shape.rings[0]) && shape.rings.slice(1).every((h) => !pointInPoly2(point, h));
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
function pointInPoly2(point, ring2) {
|
|
3251
|
+
let inside = false;
|
|
3252
|
+
for (let i = 0, j = ring2.length - 1; i < ring2.length; j = i++) {
|
|
3253
|
+
const pi = ring2[i];
|
|
3254
|
+
const pj = ring2[j];
|
|
3255
|
+
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) {
|
|
3256
|
+
inside = !inside;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
return inside;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
// src/contour/distanceTree.ts
|
|
3263
|
+
function key3(v) {
|
|
3264
|
+
return `${v.x.toFixed(8)},${v.y.toFixed(8)}`;
|
|
3265
|
+
}
|
|
3266
|
+
function distanceTree(mesh, source, flatten2) {
|
|
3267
|
+
const adj = /* @__PURE__ */ new Map();
|
|
3268
|
+
const pts = /* @__PURE__ */ new Map();
|
|
3269
|
+
const addEdge = (a, b) => {
|
|
3270
|
+
const ka = key3(a);
|
|
3271
|
+
const kb = key3(b);
|
|
3272
|
+
if (ka === kb) return;
|
|
3273
|
+
pts.set(ka, a);
|
|
3274
|
+
pts.set(kb, b);
|
|
3275
|
+
if (!adj.has(ka)) adj.set(ka, []);
|
|
3276
|
+
if (!adj.has(kb)) adj.set(kb, []);
|
|
3277
|
+
adj.get(ka).push({ to: kb, pt: b });
|
|
3278
|
+
adj.get(kb).push({ to: ka, pt: a });
|
|
3279
|
+
};
|
|
3280
|
+
for (const face of mesh.paths) {
|
|
3281
|
+
for (const ring2 of face.rings) {
|
|
3282
|
+
if (ring2.length < 2) continue;
|
|
3283
|
+
const n = ring2.length;
|
|
3284
|
+
const closed = face.closed;
|
|
3285
|
+
const limit = closed ? n : n - 1;
|
|
3286
|
+
for (let i = 0; i < limit; i++) {
|
|
3287
|
+
addEdge(ring2[i], ring2[(i + 1) % n]);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
if (pts.size === 0) return group([]);
|
|
3292
|
+
let sourceKey = "";
|
|
3293
|
+
let best = Infinity;
|
|
3294
|
+
for (const [k, p] of pts) {
|
|
3295
|
+
const d = Math.hypot(p.x - source.x, p.y - source.y);
|
|
3296
|
+
if (d < best) {
|
|
3297
|
+
best = d;
|
|
3298
|
+
sourceKey = k;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
const parent = /* @__PURE__ */ new Map();
|
|
3302
|
+
parent.set(sourceKey, null);
|
|
3303
|
+
const q = [sourceKey];
|
|
3304
|
+
while (q.length) {
|
|
3305
|
+
const u = q.shift();
|
|
3306
|
+
for (const { to } of adj.get(u) ?? []) {
|
|
3307
|
+
if (parent.has(to)) continue;
|
|
3308
|
+
parent.set(to, u);
|
|
3309
|
+
q.push(to);
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
if (flatten2) {
|
|
3313
|
+
const segs = [];
|
|
3314
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3315
|
+
for (const [v, p] of parent) {
|
|
3316
|
+
if (p === null) continue;
|
|
3317
|
+
const ek = v < p ? `${v}|${p}` : `${p}|${v}`;
|
|
3318
|
+
if (seen.has(ek)) continue;
|
|
3319
|
+
seen.add(ek);
|
|
3320
|
+
segs.push(segment(pts.get(p), pts.get(v)));
|
|
3321
|
+
}
|
|
3322
|
+
return dissolveSegments(segs);
|
|
3323
|
+
}
|
|
3324
|
+
const paths = [];
|
|
3325
|
+
for (const [v] of parent) {
|
|
3326
|
+
if (v === sourceKey) continue;
|
|
3327
|
+
const chain = [];
|
|
3328
|
+
let cur = v;
|
|
3329
|
+
while (cur !== null) {
|
|
3330
|
+
chain.push(pts.get(cur));
|
|
3331
|
+
cur = parent.get(cur) ?? null;
|
|
3332
|
+
}
|
|
3333
|
+
chain.reverse();
|
|
3334
|
+
if (chain.length >= 2) paths.push(polyline(chain));
|
|
3335
|
+
}
|
|
3336
|
+
return group(paths);
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
// src/contour/index.ts
|
|
3340
|
+
function offsetCurvesOutward(p, distance) {
|
|
3341
|
+
return buffer(p, Math.abs(distance));
|
|
3342
|
+
}
|
|
3343
|
+
function offsetCurvesInward(p, distance) {
|
|
3344
|
+
return buffer(p, -Math.abs(distance));
|
|
3345
|
+
}
|
|
3346
|
+
var contour = {
|
|
3347
|
+
offsetCurvesOutward,
|
|
3348
|
+
offsetCurvesInward,
|
|
3349
|
+
medialAxis,
|
|
3350
|
+
chordalAxis,
|
|
3351
|
+
straightSkeleton,
|
|
3352
|
+
straightSkeletonParts,
|
|
3353
|
+
centerLine,
|
|
3354
|
+
distanceField,
|
|
3355
|
+
contrastField,
|
|
3356
|
+
distanceTree,
|
|
3357
|
+
isolines,
|
|
3358
|
+
isolinesFromFunction,
|
|
3359
|
+
isolineZeroFromFunction,
|
|
3360
|
+
isolinesFromPoints,
|
|
3361
|
+
dissolveSegments
|
|
3362
|
+
};
|
|
2323
3363
|
function compoundVoronoi(sites, options = {}) {
|
|
2324
3364
|
if (sites.length === 0) return group([]);
|
|
2325
3365
|
if (sites.length === 1) {
|
|
@@ -2541,11 +3581,11 @@ function subdivideQuad(p) {
|
|
|
2541
3581
|
if (!ring2 || ring2.length < 4) return [p];
|
|
2542
3582
|
const [a, b, c, d] = ring2;
|
|
2543
3583
|
if (!a || !b || !c || !d) return [p];
|
|
2544
|
-
const mab =
|
|
2545
|
-
const mbc =
|
|
2546
|
-
const mcd =
|
|
2547
|
-
const mda =
|
|
2548
|
-
const cen =
|
|
3584
|
+
const mab = mid3(a, b);
|
|
3585
|
+
const mbc = mid3(b, c);
|
|
3586
|
+
const mcd = mid3(c, d);
|
|
3587
|
+
const mda = mid3(d, a);
|
|
3588
|
+
const cen = mid3(mid3(a, c), mid3(b, d));
|
|
2549
3589
|
return [
|
|
2550
3590
|
polygon([a, mab, cen, mda]),
|
|
2551
3591
|
polygon([mab, b, mbc, cen]),
|
|
@@ -2571,9 +3611,9 @@ function subdivideTriangle(p) {
|
|
|
2571
3611
|
const a = ring2[0];
|
|
2572
3612
|
const b = ring2[1];
|
|
2573
3613
|
const c = ring2[2];
|
|
2574
|
-
const mab =
|
|
2575
|
-
const mbc =
|
|
2576
|
-
const mca =
|
|
3614
|
+
const mab = mid3(a, b);
|
|
3615
|
+
const mbc = mid3(b, c);
|
|
3616
|
+
const mca = mid3(c, a);
|
|
2577
3617
|
return [
|
|
2578
3618
|
polygon([a, mab, mca]),
|
|
2579
3619
|
polygon([mab, b, mbc]),
|
|
@@ -2608,7 +3648,7 @@ function sliceDivision(p, spacing, angle = 0) {
|
|
|
2608
3648
|
function hatchSubdivision(p, spacing, angle = 0) {
|
|
2609
3649
|
return sliceDivision(p, spacing, angle);
|
|
2610
3650
|
}
|
|
2611
|
-
function
|
|
3651
|
+
function mid3(a, b) {
|
|
2612
3652
|
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
2613
3653
|
}
|
|
2614
3654
|
var tiling = {
|
|
@@ -2814,7 +3854,7 @@ var polygonisation = {
|
|
|
2814
3854
|
};
|
|
2815
3855
|
|
|
2816
3856
|
// src/meshing/index.ts
|
|
2817
|
-
function
|
|
3857
|
+
function edgeKey3(a, b) {
|
|
2818
3858
|
const aKey = `${a.x},${a.y}`;
|
|
2819
3859
|
const bKey = `${b.x},${b.y}`;
|
|
2820
3860
|
return aKey < bKey ? `${aKey}|${bKey}` : `${bKey}|${aKey}`;
|
|
@@ -2822,13 +3862,13 @@ function edgeKey(a, b) {
|
|
|
2822
3862
|
function vertKey(v) {
|
|
2823
3863
|
return `${v.x},${v.y}`;
|
|
2824
3864
|
}
|
|
2825
|
-
function
|
|
3865
|
+
function same2(a, b, eps = 1e-12) {
|
|
2826
3866
|
return Math.abs(a.x - b.x) < eps && Math.abs(a.y - b.y) < eps;
|
|
2827
3867
|
}
|
|
2828
3868
|
function dist(a, b) {
|
|
2829
3869
|
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
2830
3870
|
}
|
|
2831
|
-
function
|
|
3871
|
+
function mid4(a, b) {
|
|
2832
3872
|
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
2833
3873
|
}
|
|
2834
3874
|
function lerp(a, b, t) {
|
|
@@ -2862,10 +3902,10 @@ function buildEdgeMap(faces) {
|
|
|
2862
3902
|
for (let i = 0; i < lim; i++) {
|
|
2863
3903
|
const a = ring2[i];
|
|
2864
3904
|
const b = ring2[(i + 1) % n];
|
|
2865
|
-
const
|
|
2866
|
-
const rec = edges.get(
|
|
3905
|
+
const key4 = edgeKey3(a, b);
|
|
3906
|
+
const rec = edges.get(key4);
|
|
2867
3907
|
if (rec) rec.faceIds.push(fi);
|
|
2868
|
-
else edges.set(
|
|
3908
|
+
else edges.set(key4, { a: { ...a }, b: { ...b }, faceIds: [fi] });
|
|
2869
3909
|
}
|
|
2870
3910
|
});
|
|
2871
3911
|
return edges;
|
|
@@ -2909,8 +3949,8 @@ function facesFromKeptEdges(tris, kept) {
|
|
|
2909
3949
|
if (ra !== rb) parent[rb] = ra;
|
|
2910
3950
|
};
|
|
2911
3951
|
const edgeMap = buildEdgeMap(tris);
|
|
2912
|
-
for (const [
|
|
2913
|
-
if (e.faceIds.length === 2 && !kept.has(
|
|
3952
|
+
for (const [key4, e] of edgeMap) {
|
|
3953
|
+
if (e.faceIds.length === 2 && !kept.has(key4)) {
|
|
2914
3954
|
unite(e.faceIds[0], e.faceIds[1]);
|
|
2915
3955
|
}
|
|
2916
3956
|
}
|
|
@@ -2936,10 +3976,10 @@ function boundaryRingOfComponent(tris, members) {
|
|
|
2936
3976
|
for (let i = 0; i < ring2.length; i++) {
|
|
2937
3977
|
const a = ring2[i];
|
|
2938
3978
|
const b = ring2[(i + 1) % ring2.length];
|
|
2939
|
-
const
|
|
2940
|
-
const rec = edgeCount.get(
|
|
3979
|
+
const key4 = edgeKey3(a, b);
|
|
3980
|
+
const rec = edgeCount.get(key4);
|
|
2941
3981
|
if (rec) rec.n++;
|
|
2942
|
-
else edgeCount.set(
|
|
3982
|
+
else edgeCount.set(key4, { a: { ...a }, b: { ...b }, n: 1 });
|
|
2943
3983
|
}
|
|
2944
3984
|
}
|
|
2945
3985
|
const boundary = [];
|
|
@@ -2969,14 +4009,14 @@ function orderEdgesIntoRing(edges) {
|
|
|
2969
4009
|
const maxSteps = edges.length + 2;
|
|
2970
4010
|
for (let step = 0; step < maxSteps; step++) {
|
|
2971
4011
|
ring2.push({ ...curr });
|
|
2972
|
-
if (
|
|
4012
|
+
if (same2(curr, start) && ring2.length > 2) {
|
|
2973
4013
|
ring2.pop();
|
|
2974
4014
|
return ring2;
|
|
2975
4015
|
}
|
|
2976
4016
|
const nbrs = adj.get(vertKey(curr)) ?? [];
|
|
2977
4017
|
let next = null;
|
|
2978
4018
|
for (const n of nbrs) {
|
|
2979
|
-
if (!
|
|
4019
|
+
if (!same2(n, prev)) {
|
|
2980
4020
|
next = n;
|
|
2981
4021
|
break;
|
|
2982
4022
|
}
|
|
@@ -2991,13 +4031,13 @@ function applyEdgeFilter(tris, shouldKeep, preservePerimeter) {
|
|
|
2991
4031
|
const edgeMap = buildEdgeMap(tris);
|
|
2992
4032
|
const peri = perimeterKeys(edgeMap);
|
|
2993
4033
|
const kept = /* @__PURE__ */ new Set();
|
|
2994
|
-
for (const [
|
|
2995
|
-
const isPeri = peri.has(
|
|
4034
|
+
for (const [key4, e] of edgeMap) {
|
|
4035
|
+
const isPeri = peri.has(key4);
|
|
2996
4036
|
if (isPeri && preservePerimeter) {
|
|
2997
|
-
kept.add(
|
|
4037
|
+
kept.add(key4);
|
|
2998
4038
|
continue;
|
|
2999
4039
|
}
|
|
3000
|
-
if (shouldKeep(e.a, e.b, isPeri)) kept.add(
|
|
4040
|
+
if (shouldKeep(e.a, e.b, isPeri)) kept.add(key4);
|
|
3001
4041
|
}
|
|
3002
4042
|
return facesFromKeptEdges(tris, kept);
|
|
3003
4043
|
}
|
|
@@ -3025,8 +4065,8 @@ function extractInnerVertices(faces) {
|
|
|
3025
4065
|
const edgeMap = buildEdgeMap(faces);
|
|
3026
4066
|
const peri = perimeterKeys(edgeMap);
|
|
3027
4067
|
const periVerts = /* @__PURE__ */ new Set();
|
|
3028
|
-
for (const
|
|
3029
|
-
const e = edgeMap.get(
|
|
4068
|
+
for (const key4 of peri) {
|
|
4069
|
+
const e = edgeMap.get(key4);
|
|
3030
4070
|
periVerts.add(vertKey(e.a));
|
|
3031
4071
|
periVerts.add(vertKey(e.b));
|
|
3032
4072
|
}
|
|
@@ -3076,10 +4116,10 @@ function densifyRing2(ring2, maxLen) {
|
|
|
3076
4116
|
return out;
|
|
3077
4117
|
}
|
|
3078
4118
|
function isGabriel(a, b, points) {
|
|
3079
|
-
const m =
|
|
4119
|
+
const m = mid4(a, b);
|
|
3080
4120
|
const r = dist(a, b) / 2;
|
|
3081
4121
|
for (const p of points) {
|
|
3082
|
-
if (
|
|
4122
|
+
if (same2(p, a) || same2(p, b)) continue;
|
|
3083
4123
|
if (dist(p, m) < r - 1e-9) return false;
|
|
3084
4124
|
}
|
|
3085
4125
|
return true;
|
|
@@ -3087,7 +4127,7 @@ function isGabriel(a, b, points) {
|
|
|
3087
4127
|
function isRelativeNeighbor(a, b, points) {
|
|
3088
4128
|
const dab = dist(a, b);
|
|
3089
4129
|
for (const p of points) {
|
|
3090
|
-
if (
|
|
4130
|
+
if (same2(p, a) || same2(p, b)) continue;
|
|
3091
4131
|
if (Math.max(dist(p, a), dist(p, b)) < dab - 1e-9) return false;
|
|
3092
4132
|
}
|
|
3093
4133
|
return true;
|
|
@@ -3108,7 +4148,7 @@ function urquhartFaces(input, preservePerimeter = false) {
|
|
|
3108
4148
|
const d = dist(a, b);
|
|
3109
4149
|
if (d > longestLen) {
|
|
3110
4150
|
longestLen = d;
|
|
3111
|
-
longestKey =
|
|
4151
|
+
longestKey = edgeKey3(a, b);
|
|
3112
4152
|
}
|
|
3113
4153
|
}
|
|
3114
4154
|
if (longestKey && !(preservePerimeter && peri.has(longestKey))) {
|
|
@@ -3116,8 +4156,8 @@ function urquhartFaces(input, preservePerimeter = false) {
|
|
|
3116
4156
|
}
|
|
3117
4157
|
}
|
|
3118
4158
|
const kept = /* @__PURE__ */ new Set();
|
|
3119
|
-
for (const
|
|
3120
|
-
if (!removed.has(
|
|
4159
|
+
for (const key4 of edgeMap.keys()) {
|
|
4160
|
+
if (!removed.has(key4)) kept.add(key4);
|
|
3121
4161
|
}
|
|
3122
4162
|
return group(facesFromKeptEdges(tris, kept));
|
|
3123
4163
|
}
|
|
@@ -3148,12 +4188,12 @@ function spannerFaces(input, k = 1, preservePerimeter = false) {
|
|
|
3148
4188
|
const edgeMap = buildEdgeMap(tris);
|
|
3149
4189
|
const peri = perimeterKeys(edgeMap);
|
|
3150
4190
|
const stretch = Math.max(1, k);
|
|
3151
|
-
const edges = [...edgeMap.entries()].map(([
|
|
3152
|
-
key,
|
|
4191
|
+
const edges = [...edgeMap.entries()].map(([key4, e]) => ({
|
|
4192
|
+
key: key4,
|
|
3153
4193
|
a: e.a,
|
|
3154
4194
|
b: e.b,
|
|
3155
4195
|
len: dist(e.a, e.b),
|
|
3156
|
-
peri: peri.has(
|
|
4196
|
+
peri: peri.has(key4)
|
|
3157
4197
|
}));
|
|
3158
4198
|
edges.sort((a, b) => a.len - b.len);
|
|
3159
4199
|
const adj = /* @__PURE__ */ new Map();
|
|
@@ -3208,8 +4248,8 @@ function dualFaces(faces) {
|
|
|
3208
4248
|
const edgeMap = buildEdgeMap(faces);
|
|
3209
4249
|
const peri = perimeterKeys(edgeMap);
|
|
3210
4250
|
const periVerts = /* @__PURE__ */ new Set();
|
|
3211
|
-
for (const
|
|
3212
|
-
const e = edgeMap.get(
|
|
4251
|
+
for (const key4 of peri) {
|
|
4252
|
+
const e = edgeMap.get(key4);
|
|
3213
4253
|
periVerts.add(vertKey(e.a));
|
|
3214
4254
|
periVerts.add(vertKey(e.b));
|
|
3215
4255
|
}
|
|
@@ -3245,8 +4285,8 @@ function triVerts(t) {
|
|
|
3245
4285
|
function midpointMap(faces) {
|
|
3246
4286
|
const mids = /* @__PURE__ */ new Map();
|
|
3247
4287
|
const edgeMap = buildEdgeMap(faces);
|
|
3248
|
-
for (const [
|
|
3249
|
-
mids.set(
|
|
4288
|
+
for (const [key4, e] of edgeMap) {
|
|
4289
|
+
mids.set(key4, mid4(e.a, e.b));
|
|
3250
4290
|
}
|
|
3251
4291
|
return mids;
|
|
3252
4292
|
}
|
|
@@ -3256,7 +4296,7 @@ function centroidQuadrangulation(input, preservePerimeter = true) {
|
|
|
3256
4296
|
const centers = tris.map((t) => centroid(t));
|
|
3257
4297
|
const out = [];
|
|
3258
4298
|
const peri = perimeterKeys(edgeMap);
|
|
3259
|
-
for (const [
|
|
4299
|
+
for (const [key4, e] of edgeMap) {
|
|
3260
4300
|
const ids = e.faceIds;
|
|
3261
4301
|
if (ids.length === 2) {
|
|
3262
4302
|
const c0 = centers[ids[0]];
|
|
@@ -3265,7 +4305,7 @@ function centroidQuadrangulation(input, preservePerimeter = true) {
|
|
|
3265
4305
|
} else if (ids.length === 1 && preservePerimeter) {
|
|
3266
4306
|
const c0 = centers[ids[0]];
|
|
3267
4307
|
out.push(polygon([e.a, e.b, c0]));
|
|
3268
|
-
} else if (ids.length === 1 && !preservePerimeter && peri.has(
|
|
4308
|
+
} else if (ids.length === 1 && !preservePerimeter && peri.has(key4)) ;
|
|
3269
4309
|
}
|
|
3270
4310
|
return group(out);
|
|
3271
4311
|
}
|
|
@@ -3278,9 +4318,9 @@ function splitQuadrangulation(input) {
|
|
|
3278
4318
|
if (!v) continue;
|
|
3279
4319
|
const [a, b, c] = v;
|
|
3280
4320
|
const g = centroid(t);
|
|
3281
|
-
const mab = mids.get(
|
|
3282
|
-
const mbc = mids.get(
|
|
3283
|
-
const mca = mids.get(
|
|
4321
|
+
const mab = mids.get(edgeKey3(a, b)) ?? mid4(a, b);
|
|
4322
|
+
const mbc = mids.get(edgeKey3(b, c)) ?? mid4(b, c);
|
|
4323
|
+
const mca = mids.get(edgeKey3(c, a)) ?? mid4(c, a);
|
|
3284
4324
|
out.push(polygon([a, mab, g, mca]));
|
|
3285
4325
|
out.push(polygon([b, mbc, g, mab]));
|
|
3286
4326
|
out.push(polygon([c, mca, g, mbc]));
|
|
@@ -3291,13 +4331,13 @@ function edgeCollapseQuadrangulation(input, preservePerimeter = true) {
|
|
|
3291
4331
|
const tris = asTriFaces(input);
|
|
3292
4332
|
const edgeMap = buildEdgeMap(tris);
|
|
3293
4333
|
const candidates = [];
|
|
3294
|
-
for (const [
|
|
4334
|
+
for (const [key4, e] of edgeMap) {
|
|
3295
4335
|
if (e.faceIds.length !== 2) continue;
|
|
3296
4336
|
const [i, j] = e.faceIds;
|
|
3297
4337
|
const quad = mergeTriPair(tris[i], tris[j], e.a, e.b);
|
|
3298
4338
|
if (!quad) continue;
|
|
3299
4339
|
candidates.push({
|
|
3300
|
-
key,
|
|
4340
|
+
key: key4,
|
|
3301
4341
|
a: e.a,
|
|
3302
4342
|
b: e.b,
|
|
3303
4343
|
i,
|
|
@@ -3333,7 +4373,7 @@ function mergeTriPair(t0, t1, ea, eb) {
|
|
|
3333
4373
|
}
|
|
3334
4374
|
function oppositeVertex(t, ea, eb) {
|
|
3335
4375
|
for (const v of ringOf(t)) {
|
|
3336
|
-
if (!
|
|
4376
|
+
if (!same2(v, ea) && !same2(v, eb)) return v;
|
|
3337
4377
|
}
|
|
3338
4378
|
return null;
|
|
3339
4379
|
}
|
|
@@ -3395,8 +4435,8 @@ function vertexNeighborhood(faces) {
|
|
|
3395
4435
|
const edgeMap = buildEdgeMap(faces);
|
|
3396
4436
|
const peri = perimeterKeys(edgeMap);
|
|
3397
4437
|
const perimeter = /* @__PURE__ */ new Set();
|
|
3398
|
-
for (const
|
|
3399
|
-
const e = edgeMap.get(
|
|
4438
|
+
for (const key4 of peri) {
|
|
4439
|
+
const e = edgeMap.get(key4);
|
|
3400
4440
|
perimeter.add(vertKey(e.a));
|
|
3401
4441
|
perimeter.add(vertKey(e.b));
|
|
3402
4442
|
}
|
|
@@ -3519,8 +4559,8 @@ function simplifyMesh(faces, tolerance, preservePerimeter = true) {
|
|
|
3519
4559
|
};
|
|
3520
4560
|
const periVerts = /* @__PURE__ */ new Set();
|
|
3521
4561
|
if (preservePerimeter) {
|
|
3522
|
-
for (const
|
|
3523
|
-
const e = edgeMap.get(
|
|
4562
|
+
for (const key4 of peri) {
|
|
4563
|
+
const e = edgeMap.get(key4);
|
|
3524
4564
|
periVerts.add(vertKey(e.a));
|
|
3525
4565
|
periVerts.add(vertKey(e.b));
|
|
3526
4566
|
}
|
|
@@ -3741,8 +4781,8 @@ function findBreaks(faces) {
|
|
|
3741
4781
|
function nearDuplicateEdge(a, b, tol) {
|
|
3742
4782
|
const d1 = dist(a.a, b.a) + dist(a.b, b.b);
|
|
3743
4783
|
const d2 = dist(a.a, b.b) + dist(a.b, b.a);
|
|
3744
|
-
const
|
|
3745
|
-
return Math.min(d1, d2) < tol * 2 +
|
|
4784
|
+
const len2 = dist(a.a, a.b);
|
|
4785
|
+
return Math.min(d1, d2) < tol * 2 + len2 * 0.05 && Math.min(d1, d2) > 1e-9;
|
|
3746
4786
|
}
|
|
3747
4787
|
function fixBreaks(faces, maxGapWidth) {
|
|
3748
4788
|
if (faces.length === 0) return group([]);
|
|
@@ -3851,7 +4891,7 @@ function fixBrokenFaces(coverage, tolerance, polygonise = true) {
|
|
|
3851
4891
|
out.push(p);
|
|
3852
4892
|
continue;
|
|
3853
4893
|
}
|
|
3854
|
-
if (ring2.length >= 3 &&
|
|
4894
|
+
if (ring2.length >= 3 && same2(ring2[0], ring2[ring2.length - 1], tol * 2)) {
|
|
3855
4895
|
out.push(polygon(ring2.slice(0, -1)));
|
|
3856
4896
|
} else if (ring2.length >= 3 && dist(ring2[0], ring2[ring2.length - 1]) <= tol * 2) {
|
|
3857
4897
|
out.push(polygon(ring2));
|
|
@@ -3928,6 +4968,6 @@ var meshing = {
|
|
|
3928
4968
|
findIslands
|
|
3929
4969
|
};
|
|
3930
4970
|
|
|
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 };
|
|
4971
|
+
export { angular, 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, 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, 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, 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, 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, straightSkeleton, straightSkeletonParts, subdivideMesh, subtract, subtractAll, symDifference, tiling, toArray, toContours, toSvg, transformation, translate, translateCentroidTo, translateCornerTo, translateToOrigin, triangleSubdivision, triangulation, union, unionAll, urquhartFaces, vec2, voronoi, width };
|
|
3932
4972
|
//# sourceMappingURL=index.js.map
|
|
3933
4973
|
//# sourceMappingURL=index.js.map
|