@weasel-js/geom 1.10.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/3d/index.d.ts +29 -2
- package/dist/3d/index.js +49 -1
- package/dist/3d/index.js.map +1 -1
- package/dist/booleans/index.d.ts +15 -10
- package/dist/booleans/index.js +306 -353
- package/dist/booleans/index.js.map +1 -1
- package/dist/{chunk-7WFFCHRL.js → chunk-6XSJY3TC.js} +6 -37
- package/dist/chunk-6XSJY3TC.js.map +1 -0
- package/dist/chunk-7KLJZFBY.js +573 -0
- package/dist/chunk-7KLJZFBY.js.map +1 -0
- package/dist/chunk-B53BERKG.js +386 -0
- package/dist/chunk-B53BERKG.js.map +1 -0
- package/dist/chunk-CE7IY7TB.js +104 -0
- package/dist/chunk-CE7IY7TB.js.map +1 -0
- package/dist/chunk-HGF5OVEN.js +129 -0
- package/dist/chunk-HGF5OVEN.js.map +1 -0
- package/dist/chunk-J2O2B54M.js +800 -0
- package/dist/chunk-J2O2B54M.js.map +1 -0
- package/dist/chunk-Q66NA2FU.js +174 -0
- package/dist/chunk-Q66NA2FU.js.map +1 -0
- package/dist/chunk-SASMXDBE.js +443 -0
- package/dist/chunk-SASMXDBE.js.map +1 -0
- package/dist/chunk-UI3FUWXM.js +533 -0
- package/dist/chunk-UI3FUWXM.js.map +1 -0
- package/dist/curvature-PkoLduGc.d.ts +454 -0
- package/dist/curves/index.d.ts +14 -5
- package/dist/curves/index.js +33 -111
- package/dist/curves/index.js.map +1 -1
- package/dist/index.d.ts +1468 -284
- package/dist/index.js +5450 -2672
- package/dist/index.js.map +1 -1
- package/dist/nd/index.d.ts +91 -27
- package/dist/nd/index.js +282 -0
- package/dist/nd/index.js.map +1 -1
- package/dist/path-BW8d_2RY.d.ts +157 -0
- package/dist/portCurve-C5HDWuuV.d.ts +30 -0
- package/dist/tessellate/index.d.ts +15 -4
- package/dist/tessellate/index.js +21 -277
- package/dist/tessellate/index.js.map +1 -1
- package/dist/warp/index.d.ts +252 -0
- package/dist/warp/index.js +1316 -0
- package/dist/warp/index.js.map +1 -0
- package/package.json +5 -1
- package/dist/chunk-7WFFCHRL.js.map +0 -1
- package/dist/chunk-IDXY2NCA.js +0 -177
- package/dist/chunk-IDXY2NCA.js.map +0 -1
- package/dist/chunk-NJ3IGAKR.js +0 -16
- package/dist/chunk-NJ3IGAKR.js.map +0 -1
- package/dist/chunk-OXH3NOXC.js +0 -62
- package/dist/chunk-OXH3NOXC.js.map +0 -1
- package/dist/chunk-URQMDR6Q.js +0 -80
- package/dist/chunk-URQMDR6Q.js.map +0 -1
- package/dist/cubicMath-BW-HJrCE.d.ts +0 -16
- package/dist/path-ljHxobyu.d.ts +0 -44
|
@@ -0,0 +1,1316 @@
|
|
|
1
|
+
import { boundsOfPath, keepCorner, fitPointRun } from '../chunk-HGF5OVEN.js';
|
|
2
|
+
import { pathToAnchors, anchorsToPath } from '../chunk-Q66NA2FU.js';
|
|
3
|
+
import '../chunk-CE7IY7TB.js';
|
|
4
|
+
import { nestRings, flattenPolygon, resolveCrossings } from '../chunk-SASMXDBE.js';
|
|
5
|
+
import { pointInPolygon } from '../chunk-J2O2B54M.js';
|
|
6
|
+
import '../chunk-755Y4YZS.js';
|
|
7
|
+
import { cubicPointAt } from '../chunk-B53BERKG.js';
|
|
8
|
+
import earcut from 'earcut';
|
|
9
|
+
|
|
10
|
+
// src/warp/quad.ts
|
|
11
|
+
function bilinearQuad(q) {
|
|
12
|
+
const [a, b, c, d] = q;
|
|
13
|
+
return (u, v) => {
|
|
14
|
+
const w00 = (1 - u) * (1 - v);
|
|
15
|
+
const w10 = u * (1 - v);
|
|
16
|
+
const w11 = u * v;
|
|
17
|
+
const w01 = (1 - u) * v;
|
|
18
|
+
return {
|
|
19
|
+
x: w00 * a.x + w10 * b.x + w11 * c.x + w01 * d.x,
|
|
20
|
+
y: w00 * a.y + w10 * b.y + w11 * c.y + w01 * d.y
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function squareToQuadHomography(q) {
|
|
25
|
+
if (!isConvex(q)) return null;
|
|
26
|
+
const [p0, p1, p2, p3] = q;
|
|
27
|
+
const sx = p0.x - p1.x + p2.x - p3.x;
|
|
28
|
+
const sy = p0.y - p1.y + p2.y - p3.y;
|
|
29
|
+
const dx1 = p1.x - p2.x;
|
|
30
|
+
const dx2 = p3.x - p2.x;
|
|
31
|
+
const dy1 = p1.y - p2.y;
|
|
32
|
+
const dy2 = p3.y - p2.y;
|
|
33
|
+
const den = dx1 * dy2 - dx2 * dy1;
|
|
34
|
+
if (den === 0) return null;
|
|
35
|
+
const g = (sx * dy2 - dx2 * sy) / den;
|
|
36
|
+
const h = (dx1 * sy - sx * dy1) / den;
|
|
37
|
+
return [
|
|
38
|
+
p1.x - p0.x + g * p1.x,
|
|
39
|
+
p3.x - p0.x + h * p3.x,
|
|
40
|
+
p0.x,
|
|
41
|
+
p1.y - p0.y + g * p1.y,
|
|
42
|
+
p3.y - p0.y + h * p3.y,
|
|
43
|
+
p0.y,
|
|
44
|
+
g,
|
|
45
|
+
h
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
function projectiveQuad(q) {
|
|
49
|
+
const m = squareToQuadHomography(q);
|
|
50
|
+
if (!m) return null;
|
|
51
|
+
const [a, b, c, d, e, f, g, h] = m;
|
|
52
|
+
return (u, v) => {
|
|
53
|
+
const w = g * u + h * v + 1;
|
|
54
|
+
return { x: (a * u + b * v + c) / w, y: (d * u + e * v + f) / w };
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function isConvex(q) {
|
|
58
|
+
let sign = 0;
|
|
59
|
+
for (let i = 0; i < 4; i++) {
|
|
60
|
+
const a = q[i];
|
|
61
|
+
const b = q[(i + 1) % 4];
|
|
62
|
+
const c = q[(i + 2) % 4];
|
|
63
|
+
const cross = (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
|
|
64
|
+
if (cross === 0) return false;
|
|
65
|
+
if (sign === 0) sign = Math.sign(cross);
|
|
66
|
+
else if (Math.sign(cross) !== sign) return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/warp/mesh.ts
|
|
72
|
+
function meshGridOver(box, rows, cols) {
|
|
73
|
+
return meshGridFrom((u, v) => ({ x: box.x + u * box.width, y: box.y + v * box.height }), rows, cols);
|
|
74
|
+
}
|
|
75
|
+
function meshGridFrom(map, rows, cols) {
|
|
76
|
+
const r = Math.max(1, Math.round(rows));
|
|
77
|
+
const c = Math.max(1, Math.round(cols));
|
|
78
|
+
const points = [];
|
|
79
|
+
for (let i = 0; i <= r; i++) {
|
|
80
|
+
for (let j = 0; j <= c; j++) points.push(map(j / c, i / r));
|
|
81
|
+
}
|
|
82
|
+
return { rows: r, cols: c, points };
|
|
83
|
+
}
|
|
84
|
+
function gridPoint(grid, i, j) {
|
|
85
|
+
if (i < 0) return extend(gridPoint(grid, 0, j), gridPoint(grid, 1, j));
|
|
86
|
+
if (i > grid.rows) return extend(gridPoint(grid, grid.rows, j), gridPoint(grid, grid.rows - 1, j));
|
|
87
|
+
if (j < 0) return extend(gridPoint(grid, i, 0), gridPoint(grid, i, 1));
|
|
88
|
+
if (j > grid.cols) return extend(gridPoint(grid, i, grid.cols), gridPoint(grid, i, grid.cols - 1));
|
|
89
|
+
return grid.points[i * (grid.cols + 1) + j];
|
|
90
|
+
}
|
|
91
|
+
var extend = (edge, inner) => ({ x: 2 * edge.x - inner.x, y: 2 * edge.y - inner.y });
|
|
92
|
+
function catmullRom(p0, p1, p2, p3, t) {
|
|
93
|
+
const t2 = t * t;
|
|
94
|
+
return 0.5 * (2 * p1 + (p2 - p0) * t + (2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 + (3 * p1 - p0 - 3 * p2 + p3) * t2 * t);
|
|
95
|
+
}
|
|
96
|
+
function catmullRomPoint(p, t) {
|
|
97
|
+
return {
|
|
98
|
+
x: catmullRom(p[0].x, p[1].x, p[2].x, p[3].x, t),
|
|
99
|
+
y: catmullRom(p[0].y, p[1].y, p[2].y, p[3].y, t)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function cellOf(s, n) {
|
|
103
|
+
const k = Math.min(n - 1, Math.max(0, Math.floor(s * n)));
|
|
104
|
+
return [k, s * n - k];
|
|
105
|
+
}
|
|
106
|
+
function bicubicMesh(grid) {
|
|
107
|
+
return (u, v) => {
|
|
108
|
+
const [j, s] = cellOf(u, grid.cols);
|
|
109
|
+
const [i, t] = cellOf(v, grid.rows);
|
|
110
|
+
const across = [];
|
|
111
|
+
for (let di = -1; di <= 2; di++) {
|
|
112
|
+
across.push(catmullRomPoint([-1, 0, 1, 2].map((dj) => gridPoint(grid, i + di, j + dj)), s));
|
|
113
|
+
}
|
|
114
|
+
return catmullRomPoint(across, t);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function coonsMesh(grid) {
|
|
118
|
+
const row = (i, j, s) => catmullRomPoint([-1, 0, 1, 2].map((dj) => gridPoint(grid, i, j + dj)), s);
|
|
119
|
+
const col = (i, j, t) => catmullRomPoint([-1, 0, 1, 2].map((di) => gridPoint(grid, i + di, j)), t);
|
|
120
|
+
return (u, v) => {
|
|
121
|
+
const [j, s] = cellOf(u, grid.cols);
|
|
122
|
+
const [i, t] = cellOf(v, grid.rows);
|
|
123
|
+
const top = row(i, j, s);
|
|
124
|
+
const bottom = row(i + 1, j, s);
|
|
125
|
+
const left = col(i, j, t);
|
|
126
|
+
const right = col(i, j + 1, t);
|
|
127
|
+
const p00 = gridPoint(grid, i, j);
|
|
128
|
+
const p10 = gridPoint(grid, i, j + 1);
|
|
129
|
+
const p01 = gridPoint(grid, i + 1, j);
|
|
130
|
+
const p11 = gridPoint(grid, i + 1, j + 1);
|
|
131
|
+
const blend = (k) => (1 - t) * top[k] + t * bottom[k] + (1 - s) * left[k] + s * right[k] - ((1 - s) * (1 - t) * p00[k] + s * (1 - t) * p10[k] + (1 - s) * t * p01[k] + s * t * p11[k]);
|
|
132
|
+
return { x: blend("x"), y: blend("y") };
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/warp/profileCholesky.ts
|
|
137
|
+
function symmetricRows(n) {
|
|
138
|
+
return Array.from({ length: n }, () => /* @__PURE__ */ new Map());
|
|
139
|
+
}
|
|
140
|
+
function addSymmetric(rows, i, j, v) {
|
|
141
|
+
const [r, c] = i >= j ? [i, j] : [j, i];
|
|
142
|
+
const row = rows[r];
|
|
143
|
+
row.set(c, (row.get(c) ?? 0) + v);
|
|
144
|
+
}
|
|
145
|
+
function reverseCuthillMcKee(rows) {
|
|
146
|
+
const n = rows.length;
|
|
147
|
+
const adj = Array.from({ length: n }, () => []);
|
|
148
|
+
rows.forEach((row, i) => row.forEach((_, j) => {
|
|
149
|
+
if (j !== i) {
|
|
150
|
+
adj[i].push(j);
|
|
151
|
+
adj[j].push(i);
|
|
152
|
+
}
|
|
153
|
+
}));
|
|
154
|
+
const degree = adj.map((a) => a.length);
|
|
155
|
+
const seen = new Uint8Array(n);
|
|
156
|
+
const order = [];
|
|
157
|
+
const byDegree = [...Array(n).keys()].sort((a, b) => degree[a] - degree[b]);
|
|
158
|
+
for (const root of byDegree) {
|
|
159
|
+
if (seen[root]) continue;
|
|
160
|
+
seen[root] = 1;
|
|
161
|
+
const queue = [root];
|
|
162
|
+
for (let head = 0; head < queue.length; head++) {
|
|
163
|
+
const v = queue[head];
|
|
164
|
+
order.push(v);
|
|
165
|
+
const next = adj[v].filter((w) => !seen[w]).sort((a, b) => degree[a] - degree[b]);
|
|
166
|
+
for (const w of next) {
|
|
167
|
+
seen[w] = 1;
|
|
168
|
+
queue.push(w);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return Int32Array.from(order.reverse());
|
|
173
|
+
}
|
|
174
|
+
function factorSymmetric(rows) {
|
|
175
|
+
const n = rows.length;
|
|
176
|
+
const order = reverseCuthillMcKee(rows);
|
|
177
|
+
const at = new Int32Array(n);
|
|
178
|
+
order.forEach((old, k) => {
|
|
179
|
+
at[old] = k;
|
|
180
|
+
});
|
|
181
|
+
const first = Int32Array.from({ length: n }, (_, k) => k);
|
|
182
|
+
rows.forEach((row, i) => row.forEach((_, j) => {
|
|
183
|
+
const a = at[i], b = at[j];
|
|
184
|
+
const r = Math.max(a, b), c = Math.min(a, b);
|
|
185
|
+
if (c < first[r]) first[r] = c;
|
|
186
|
+
}));
|
|
187
|
+
const start = new Int32Array(n + 1);
|
|
188
|
+
for (let k = 0; k < n; k++) start[k + 1] = start[k] + (k - first[k] + 1);
|
|
189
|
+
const L = new Float64Array(start[n]);
|
|
190
|
+
rows.forEach((row, i) => row.forEach((v, j) => {
|
|
191
|
+
const a = at[i], b = at[j];
|
|
192
|
+
const r = Math.max(a, b), c = Math.min(a, b);
|
|
193
|
+
L[start[r] + c - first[r]] += v;
|
|
194
|
+
}));
|
|
195
|
+
for (let i = 0; i < n; i++) {
|
|
196
|
+
const fi = first[i], si = start[i];
|
|
197
|
+
for (let j = fi; j <= i; j++) {
|
|
198
|
+
const fj = first[j], sj = start[j];
|
|
199
|
+
let sum = L[si + j - fi];
|
|
200
|
+
for (let k = Math.max(fi, fj); k < j; k++) sum -= L[si + k - fi] * L[sj + k - fj];
|
|
201
|
+
if (j === i) {
|
|
202
|
+
if (!(sum > 0)) return null;
|
|
203
|
+
L[si + i - fi] = Math.sqrt(sum);
|
|
204
|
+
} else {
|
|
205
|
+
L[si + j - fi] = sum / L[sj + j - fj];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
solve(b) {
|
|
211
|
+
const y = new Float64Array(n);
|
|
212
|
+
for (let k = 0; k < n; k++) y[k] = b[order[k]];
|
|
213
|
+
for (let i = 0; i < n; i++) {
|
|
214
|
+
const fi = first[i], si = start[i];
|
|
215
|
+
let sum = y[i];
|
|
216
|
+
for (let k = fi; k < i; k++) sum -= L[si + k - fi] * y[k];
|
|
217
|
+
y[i] = sum / L[si + i - fi];
|
|
218
|
+
}
|
|
219
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
220
|
+
const fi = first[i], si = start[i];
|
|
221
|
+
y[i] /= L[si + i - fi];
|
|
222
|
+
const yi = y[i];
|
|
223
|
+
for (let k = fi; k < i; k++) y[k] -= L[si + k - fi] * yi;
|
|
224
|
+
}
|
|
225
|
+
const x = new Float64Array(n);
|
|
226
|
+
for (let k = 0; k < n; k++) x[order[k]] = y[k];
|
|
227
|
+
return x;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/warp/arapSystem.ts
|
|
233
|
+
function split(full, freeIndex) {
|
|
234
|
+
const nFree = freeIndex.reduce((n, f) => f >= 0 ? n + 1 : n, 0);
|
|
235
|
+
const rows = symmetricRows(nFree);
|
|
236
|
+
const coupling = Array.from({ length: nFree }, () => ({ at: [], by: [] }));
|
|
237
|
+
full.forEach((row, i) => row.forEach((v, j) => {
|
|
238
|
+
const fi = freeIndex[i] >= 0, fj = freeIndex[j] >= 0;
|
|
239
|
+
if (fi && fj) addSymmetric(rows, freeIndex[i], freeIndex[j], v);
|
|
240
|
+
else if (fi) {
|
|
241
|
+
coupling[freeIndex[i]].at.push(j);
|
|
242
|
+
coupling[freeIndex[i]].by.push(v);
|
|
243
|
+
} else if (fj) {
|
|
244
|
+
coupling[freeIndex[j]].at.push(i);
|
|
245
|
+
coupling[freeIndex[j]].by.push(v);
|
|
246
|
+
}
|
|
247
|
+
}));
|
|
248
|
+
const factored = factorSymmetric(rows);
|
|
249
|
+
if (!factored) return null;
|
|
250
|
+
return { factored, coupling: coupling.map((c) => ({ at: Int32Array.from(c.at), by: Float64Array.from(c.by) })) };
|
|
251
|
+
}
|
|
252
|
+
function solveSplit(s, fixedValue, b) {
|
|
253
|
+
for (let f = 0; f < s.coupling.length; f++) {
|
|
254
|
+
const { at, by } = s.coupling[f];
|
|
255
|
+
for (let k = 0; k < at.length; k++) b[f] -= by[k] * fixedValue(at[k]);
|
|
256
|
+
}
|
|
257
|
+
return s.factored.solve(b);
|
|
258
|
+
}
|
|
259
|
+
function similarityEnergy(rest, tris, n) {
|
|
260
|
+
const rows = symmetricRows(2 * n);
|
|
261
|
+
const coef = new Float64Array(6);
|
|
262
|
+
const idx = new Int32Array(6);
|
|
263
|
+
for (let t = 0; t < tris.length; t += 3) {
|
|
264
|
+
for (let e = 0; e < 3; e++) {
|
|
265
|
+
const a = tris[t + e], b = tris[t + (e + 1) % 3], c = tris[t + (e + 2) % 3];
|
|
266
|
+
const dx = rest[2 * b] - rest[2 * a], dy = rest[2 * b + 1] - rest[2 * a + 1];
|
|
267
|
+
const ex = rest[2 * c] - rest[2 * a], ey = rest[2 * c + 1] - rest[2 * a + 1];
|
|
268
|
+
const d2 = dx * dx + dy * dy;
|
|
269
|
+
if (d2 === 0) continue;
|
|
270
|
+
const x = (ex * dx + ey * dy) / d2;
|
|
271
|
+
const y = (ey * dx - ex * dy) / d2;
|
|
272
|
+
idx.set([2 * a, 2 * a + 1, 2 * b, 2 * b + 1, 2 * c, 2 * c + 1]);
|
|
273
|
+
for (const r of [[-(1 - x), -y, -x, y, 1, 0], [y, -(1 - x), -y, -x, 0, 1]]) {
|
|
274
|
+
coef.set(r);
|
|
275
|
+
for (let i = 0; i < 6; i++) {
|
|
276
|
+
for (let j = 0; j <= i; j++) if (coef[i] !== 0 && coef[j] !== 0) addSymmetric(rows, idx[i], idx[j], coef[i] * coef[j]);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return rows;
|
|
282
|
+
}
|
|
283
|
+
function edgeEnergy(tris, n) {
|
|
284
|
+
const rows = symmetricRows(n);
|
|
285
|
+
for (let t = 0; t < tris.length; t += 3) {
|
|
286
|
+
for (let e = 0; e < 3; e++) {
|
|
287
|
+
const i = tris[t + e], j = tris[t + (e + 1) % 3];
|
|
288
|
+
addSymmetric(rows, i, i, 1);
|
|
289
|
+
addSymmetric(rows, j, j, 1);
|
|
290
|
+
addSymmetric(rows, i, j, -1);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return rows;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/warp/injective.ts
|
|
297
|
+
function restFrames(rest, tris) {
|
|
298
|
+
const nt = tris.length / 3;
|
|
299
|
+
const inv = new Float64Array(4 * nt), area = new Float64Array(nt);
|
|
300
|
+
for (let t = 0; t < nt; t++) {
|
|
301
|
+
const a = tris[3 * t], b = tris[3 * t + 1], c = tris[3 * t + 2];
|
|
302
|
+
const ux = rest[2 * b] - rest[2 * a], uy = rest[2 * b + 1] - rest[2 * a + 1];
|
|
303
|
+
const vx = rest[2 * c] - rest[2 * a], vy = rest[2 * c + 1] - rest[2 * a + 1];
|
|
304
|
+
const det = ux * vy - uy * vx;
|
|
305
|
+
if (det === 0) continue;
|
|
306
|
+
inv.set([vy / det, -vx / det, -uy / det, ux / det], 4 * t);
|
|
307
|
+
area[t] = Math.abs(det) / 2;
|
|
308
|
+
}
|
|
309
|
+
return { inv, area };
|
|
310
|
+
}
|
|
311
|
+
var FLIP_MARGIN = 0.8;
|
|
312
|
+
var ARMIJO = 1e-4;
|
|
313
|
+
var AREA = 1;
|
|
314
|
+
function jacobian(piece, pos, t, J) {
|
|
315
|
+
const { tris, frames } = piece;
|
|
316
|
+
if (frames.area[t] === 0) return false;
|
|
317
|
+
const a = tris[3 * t], b = tris[3 * t + 1], c = tris[3 * t + 2];
|
|
318
|
+
const ux = pos[2 * b] - pos[2 * a], uy = pos[2 * b + 1] - pos[2 * a + 1];
|
|
319
|
+
const vx = pos[2 * c] - pos[2 * a], vy = pos[2 * c + 1] - pos[2 * a + 1];
|
|
320
|
+
const m = frames.inv;
|
|
321
|
+
const i = 4 * t;
|
|
322
|
+
J[0] = ux * m[i] + vx * m[i + 2];
|
|
323
|
+
J[1] = ux * m[i + 1] + vx * m[i + 3];
|
|
324
|
+
J[2] = uy * m[i] + vy * m[i + 2];
|
|
325
|
+
J[3] = uy * m[i + 1] + vy * m[i + 3];
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
function flipBound(piece, pos, d) {
|
|
329
|
+
const { tris, rest } = piece;
|
|
330
|
+
let bound = Infinity;
|
|
331
|
+
for (let t = 0; t < tris.length; t += 3) {
|
|
332
|
+
const a = tris[t], b = tris[t + 1], c = tris[t + 2];
|
|
333
|
+
const side = Math.sign((rest[2 * b] - rest[2 * a]) * (rest[2 * c + 1] - rest[2 * a + 1]) - (rest[2 * b + 1] - rest[2 * a + 1]) * (rest[2 * c] - rest[2 * a]));
|
|
334
|
+
if (side === 0) continue;
|
|
335
|
+
const ux = pos[2 * b] - pos[2 * a], uy = pos[2 * b + 1] - pos[2 * a + 1];
|
|
336
|
+
const vx = pos[2 * c] - pos[2 * a], vy = pos[2 * c + 1] - pos[2 * a + 1];
|
|
337
|
+
const dux = d[2 * b] - d[2 * a], duy = d[2 * b + 1] - d[2 * a + 1];
|
|
338
|
+
const dvx = d[2 * c] - d[2 * a], dvy = d[2 * c + 1] - d[2 * a + 1];
|
|
339
|
+
const c0 = side * (ux * vy - uy * vx);
|
|
340
|
+
const c1 = side * (ux * dvy - uy * dvx + dux * vy - duy * vx);
|
|
341
|
+
const c2 = side * (dux * dvy - duy * dvx);
|
|
342
|
+
if (c0 <= 0) return 0;
|
|
343
|
+
bound = Math.min(bound, smallestPositiveRoot(c0, c1, c2));
|
|
344
|
+
}
|
|
345
|
+
return bound;
|
|
346
|
+
}
|
|
347
|
+
function smallestPositiveRoot(c0, c1, c2) {
|
|
348
|
+
if (Math.abs(c2) < 1e-14 * (Math.abs(c1) + Math.abs(c0))) return c1 < 0 ? -c0 / c1 : Infinity;
|
|
349
|
+
const disc = c1 * c1 - 4 * c2 * c0;
|
|
350
|
+
if (disc < 0) return Infinity;
|
|
351
|
+
const q = -0.5 * (c1 + Math.sign(c1 || 1) * Math.sqrt(disc));
|
|
352
|
+
const roots = [q / c2, c0 / q].filter((s) => s > 0 && Number.isFinite(s));
|
|
353
|
+
return roots.length ? Math.min(...roots) : Infinity;
|
|
354
|
+
}
|
|
355
|
+
var chi = (d, eps) => (d + Math.sqrt(eps * eps + d * d)) / 2;
|
|
356
|
+
function untangleEnergy(piece, pos, eps) {
|
|
357
|
+
const J = new Float64Array(4);
|
|
358
|
+
const { area } = piece.frames;
|
|
359
|
+
let E = 0;
|
|
360
|
+
for (let t = 0; t < area.length; t++) {
|
|
361
|
+
if (!jacobian(piece, pos, t, J)) continue;
|
|
362
|
+
const d = J[0] * J[3] - J[1] * J[2];
|
|
363
|
+
const c = chi(d, eps);
|
|
364
|
+
if (!(c > 0)) return Infinity;
|
|
365
|
+
E += area[t] * (J[0] * J[0] + J[1] * J[1] + J[2] * J[2] + J[3] * J[3] + AREA * (d * d + 1)) / c;
|
|
366
|
+
}
|
|
367
|
+
return E;
|
|
368
|
+
}
|
|
369
|
+
function leastArea(piece, pos) {
|
|
370
|
+
const J = new Float64Array(4);
|
|
371
|
+
let least = Infinity;
|
|
372
|
+
for (let t = 0; t < piece.frames.area.length; t++) {
|
|
373
|
+
if (jacobian(piece, pos, t, J)) least = Math.min(least, J[0] * J[3] - J[1] * J[2]);
|
|
374
|
+
}
|
|
375
|
+
return least;
|
|
376
|
+
}
|
|
377
|
+
function gradient(piece, pos, eps) {
|
|
378
|
+
const { tris, frames } = piece;
|
|
379
|
+
const g = new Float64Array(pos.length);
|
|
380
|
+
const J = new Float64Array(4);
|
|
381
|
+
for (let t = 0; t < frames.area.length; t++) {
|
|
382
|
+
if (!jacobian(piece, pos, t, J)) continue;
|
|
383
|
+
const d = J[0] * J[3] - J[1] * J[2];
|
|
384
|
+
const root = Math.sqrt(eps * eps + d * d);
|
|
385
|
+
const c = (d + root) / 2;
|
|
386
|
+
const A = frames.area[t];
|
|
387
|
+
const top = J[0] * J[0] + J[1] * J[1] + J[2] * J[2] + J[3] * J[3] + AREA * (d * d + 1);
|
|
388
|
+
const dc = root === 0 ? 0.5 : (1 + d / root) / 2;
|
|
389
|
+
const s = 2 * A / c, r = A * (2 * AREA * d / c - top * dc / (c * c));
|
|
390
|
+
const g0 = s * J[0] + r * J[3], g1 = s * J[1] - r * J[2], g2 = s * J[2] - r * J[1], g3 = s * J[3] + r * J[0];
|
|
391
|
+
const m = frames.inv, i = 4 * t;
|
|
392
|
+
const gux = g0 * m[i] + g1 * m[i + 1], gvx = g0 * m[i + 2] + g1 * m[i + 3];
|
|
393
|
+
const guy = g2 * m[i] + g3 * m[i + 1], gvy = g2 * m[i + 2] + g3 * m[i + 3];
|
|
394
|
+
const a = tris[3 * t], b = tris[3 * t + 1], cc = tris[3 * t + 2];
|
|
395
|
+
g[2 * b] += gux;
|
|
396
|
+
g[2 * b + 1] += guy;
|
|
397
|
+
g[2 * cc] += gvx;
|
|
398
|
+
g[2 * cc + 1] += gvy;
|
|
399
|
+
g[2 * a] -= gux + gvx;
|
|
400
|
+
g[2 * a + 1] -= guy + gvy;
|
|
401
|
+
}
|
|
402
|
+
return g;
|
|
403
|
+
}
|
|
404
|
+
function turnedOver(piece, pos) {
|
|
405
|
+
return flipBound(piece, pos, new Float64Array(pos.length)) === 0;
|
|
406
|
+
}
|
|
407
|
+
function startingEps(piece, pos) {
|
|
408
|
+
return Math.max(1e-3, 2 * Math.abs(Math.min(0, leastArea(piece, pos))));
|
|
409
|
+
}
|
|
410
|
+
function nextEps(piece, pos, eps, before, after) {
|
|
411
|
+
const least = leastArea(piece, pos);
|
|
412
|
+
const sigma = Math.max(1 - after / before, 0.1);
|
|
413
|
+
const mu = (1 - sigma) * chi(least, eps);
|
|
414
|
+
return least < mu ? 2 * Math.sqrt(mu * (mu - least)) : 0;
|
|
415
|
+
}
|
|
416
|
+
function untangleRound(piece, pos, eps, energy) {
|
|
417
|
+
const { pinOf, freeOf, step2 } = piece;
|
|
418
|
+
const g = gradient(piece, pos, eps);
|
|
419
|
+
const nf = step2.coupling.length;
|
|
420
|
+
const bx = new Float64Array(nf), by = new Float64Array(nf);
|
|
421
|
+
for (let i = 0; i < pinOf.length; i++) {
|
|
422
|
+
if (freeOf[i] >= 0) {
|
|
423
|
+
bx[freeOf[i]] = -g[2 * i];
|
|
424
|
+
by[freeOf[i]] = -g[2 * i + 1];
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const dx = solveSplit(step2, () => 0, bx);
|
|
428
|
+
const dy = solveSplit(step2, () => 0, by);
|
|
429
|
+
const d = new Float64Array(pos.length);
|
|
430
|
+
let slope = 0;
|
|
431
|
+
for (let i = 0; i < pinOf.length; i++) {
|
|
432
|
+
const f = freeOf[i];
|
|
433
|
+
if (f < 0) continue;
|
|
434
|
+
d[2 * i] = dx[f];
|
|
435
|
+
d[2 * i + 1] = dy[f];
|
|
436
|
+
slope += g[2 * i] * dx[f] + g[2 * i + 1] * dy[f];
|
|
437
|
+
}
|
|
438
|
+
let s = eps > 0 ? 1 : Math.min(1, FLIP_MARGIN * flipBound(piece, pos, d));
|
|
439
|
+
const next = new Float64Array(pos.length);
|
|
440
|
+
for (; s > 1e-12; s /= 2) {
|
|
441
|
+
for (let i = 0; i < next.length; i++) next[i] = pos[i] + s * d[i];
|
|
442
|
+
const e = untangleEnergy(piece, next, eps);
|
|
443
|
+
if (e <= energy + ARMIJO * s * slope) {
|
|
444
|
+
let moved = 0;
|
|
445
|
+
for (let i = 0; i < d.length; i += 2) moved = Math.max(moved, s * Math.hypot(d[i], d[i + 1]));
|
|
446
|
+
return { pos: next, energy: e, moved };
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return { pos, energy, moved: 0 };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/warp/arapPiece.ts
|
|
453
|
+
function buildPiece(mesh, verts, tris, pinAt, local) {
|
|
454
|
+
const n = verts.length;
|
|
455
|
+
verts.forEach((v, i) => {
|
|
456
|
+
local[v] = i;
|
|
457
|
+
});
|
|
458
|
+
const rest = new Float64Array(2 * n);
|
|
459
|
+
verts.forEach((v, i) => {
|
|
460
|
+
rest[2 * i] = mesh.vertices[2 * v];
|
|
461
|
+
rest[2 * i + 1] = mesh.vertices[2 * v + 1];
|
|
462
|
+
});
|
|
463
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
464
|
+
for (let i = 0; i < 2 * n; i += 2) {
|
|
465
|
+
x0 = Math.min(x0, rest[i]);
|
|
466
|
+
x1 = Math.max(x1, rest[i]);
|
|
467
|
+
y0 = Math.min(y0, rest[i + 1]);
|
|
468
|
+
y1 = Math.max(y1, rest[i + 1]);
|
|
469
|
+
}
|
|
470
|
+
const T = mesh.triangles;
|
|
471
|
+
const lt = new Int32Array(3 * tris.length);
|
|
472
|
+
tris.forEach((t, k) => {
|
|
473
|
+
for (let c = 0; c < 3; c++) lt[3 * k + c] = local[T[3 * t + c]];
|
|
474
|
+
});
|
|
475
|
+
const pinOf = Int32Array.from(verts, (v) => pinAt.get(v) ?? -1);
|
|
476
|
+
const freeOf = new Int32Array(n).fill(-1);
|
|
477
|
+
let nf = 0;
|
|
478
|
+
for (let i = 0; i < n; i++) if (pinOf[i] < 0) freeOf[i] = nf++;
|
|
479
|
+
const free2 = Int32Array.from({ length: 2 * n }, (_, u) => freeOf[u >> 1] < 0 ? -1 : 2 * freeOf[u >> 1] + (u & 1));
|
|
480
|
+
const step1 = split(similarityEnergy(rest, lt, n), free2);
|
|
481
|
+
const step2 = split(edgeEnergy(lt, n), freeOf);
|
|
482
|
+
if (!step1 || !step2) return null;
|
|
483
|
+
return { verts: Int32Array.from(verts), rest, size: Math.max(x1 - x0, y1 - y0), tris: lt, pinOf, freeOf, step1, step2, frames: restFrames(rest, lt) };
|
|
484
|
+
}
|
|
485
|
+
function similaritySolve(piece, targets) {
|
|
486
|
+
const { pinOf, freeOf, step1 } = piece;
|
|
487
|
+
const x = solveSplit(step1, (u) => targets[2 * pinOf[u >> 1] + (u & 1)], new Float64Array(step1.coupling.length));
|
|
488
|
+
const pos = new Float64Array(2 * pinOf.length);
|
|
489
|
+
for (let i = 0; i < pinOf.length; i++) {
|
|
490
|
+
for (let c = 0; c < 2; c++) pos[2 * i + c] = pinOf[i] >= 0 ? targets[2 * pinOf[i] + c] : x[2 * freeOf[i] + c];
|
|
491
|
+
}
|
|
492
|
+
return pos;
|
|
493
|
+
}
|
|
494
|
+
function rigidRound(piece, targets, pos) {
|
|
495
|
+
const { rest, tris, pinOf, freeOf, step2 } = piece;
|
|
496
|
+
const nf = step2.coupling.length;
|
|
497
|
+
const bx = new Float64Array(nf), by = new Float64Array(nf);
|
|
498
|
+
let energy = 0;
|
|
499
|
+
for (let t = 0; t < tris.length; t += 3) {
|
|
500
|
+
let dot = 0, cross = 0, sq = 0;
|
|
501
|
+
for (let e = 0; e < 3; e++) {
|
|
502
|
+
const i = tris[t + e], j = tris[t + (e + 1) % 3];
|
|
503
|
+
const px = rest[2 * i] - rest[2 * j], py = rest[2 * i + 1] - rest[2 * j + 1];
|
|
504
|
+
const qx = pos[2 * i] - pos[2 * j], qy = pos[2 * i + 1] - pos[2 * j + 1];
|
|
505
|
+
dot += px * qx + py * qy;
|
|
506
|
+
cross += px * qy - py * qx;
|
|
507
|
+
sq += px * px + py * py + qx * qx + qy * qy;
|
|
508
|
+
}
|
|
509
|
+
const len = Math.hypot(dot, cross);
|
|
510
|
+
energy += sq - 2 * len;
|
|
511
|
+
const cos = len === 0 ? 1 : dot / len, sin = len === 0 ? 0 : cross / len;
|
|
512
|
+
for (let e = 0; e < 3; e++) {
|
|
513
|
+
const i = tris[t + e], j = tris[t + (e + 1) % 3];
|
|
514
|
+
const ex = rest[2 * i] - rest[2 * j], ey = rest[2 * i + 1] - rest[2 * j + 1];
|
|
515
|
+
const fx = cos * ex - sin * ey, fy = sin * ex + cos * ey;
|
|
516
|
+
if (freeOf[i] >= 0) {
|
|
517
|
+
bx[freeOf[i]] += fx;
|
|
518
|
+
by[freeOf[i]] += fy;
|
|
519
|
+
}
|
|
520
|
+
if (freeOf[j] >= 0) {
|
|
521
|
+
bx[freeOf[j]] -= fx;
|
|
522
|
+
by[freeOf[j]] -= fy;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const x = solveSplit(step2, (u) => targets[2 * pinOf[u]], bx);
|
|
527
|
+
const y = solveSplit(step2, (u) => targets[2 * pinOf[u] + 1], by);
|
|
528
|
+
const out = new Float64Array(pos.length);
|
|
529
|
+
for (let i = 0; i < pinOf.length; i++) {
|
|
530
|
+
const p = pinOf[i];
|
|
531
|
+
out[2 * i] = p >= 0 ? targets[2 * p] : x[freeOf[i]];
|
|
532
|
+
out[2 * i + 1] = p >= 0 ? targets[2 * p + 1] : y[freeOf[i]];
|
|
533
|
+
}
|
|
534
|
+
return { pos: out, energy };
|
|
535
|
+
}
|
|
536
|
+
function placePiece(piece, pos, out) {
|
|
537
|
+
piece.verts.forEach((v, i) => {
|
|
538
|
+
out[2 * v] = pos[2 * i];
|
|
539
|
+
out[2 * v + 1] = pos[2 * i + 1];
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/warp/arap.ts
|
|
544
|
+
var ARAP_ROUNDS = 5;
|
|
545
|
+
var ARAP_INJECTIVE_ROUNDS = 100;
|
|
546
|
+
var SETTLED = 1e-4;
|
|
547
|
+
function piecesOf(mesh) {
|
|
548
|
+
const n = mesh.vertices.length >> 1;
|
|
549
|
+
const parent = Int32Array.from({ length: n }, (_, i) => i);
|
|
550
|
+
const find = (i) => {
|
|
551
|
+
while (parent[i] !== i) {
|
|
552
|
+
parent[i] = parent[parent[i]];
|
|
553
|
+
i = parent[i];
|
|
554
|
+
}
|
|
555
|
+
return i;
|
|
556
|
+
};
|
|
557
|
+
const T = mesh.triangles;
|
|
558
|
+
for (let t = 0; t + 2 < T.length; t += 3) {
|
|
559
|
+
parent[find(T[t + 1])] = find(T[t]);
|
|
560
|
+
parent[find(T[t + 2])] = find(T[t]);
|
|
561
|
+
}
|
|
562
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
563
|
+
const used = new Uint8Array(n);
|
|
564
|
+
for (let t = 0; t + 2 < T.length; t += 3) {
|
|
565
|
+
const r = find(T[t]);
|
|
566
|
+
let p = byRoot.get(r);
|
|
567
|
+
if (!p) {
|
|
568
|
+
p = { verts: [], tris: [] };
|
|
569
|
+
byRoot.set(r, p);
|
|
570
|
+
}
|
|
571
|
+
p.tris.push(t / 3);
|
|
572
|
+
for (let k = 0; k < 3; k++) if (!used[T[t + k]]) {
|
|
573
|
+
used[T[t + k]] = 1;
|
|
574
|
+
p.verts.push(T[t + k]);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return [...byRoot.values()];
|
|
578
|
+
}
|
|
579
|
+
function largestMove(a, b) {
|
|
580
|
+
let m = 0;
|
|
581
|
+
for (let i = 0; i < a.length; i += 2) m = Math.max(m, Math.hypot(b[i] - a[i], b[i + 1] - a[i + 1]));
|
|
582
|
+
return m;
|
|
583
|
+
}
|
|
584
|
+
function rigidFit(from, to) {
|
|
585
|
+
const k = from.length >> 1;
|
|
586
|
+
let pcx = 0, pcy = 0, qcx = 0, qcy = 0;
|
|
587
|
+
for (let i = 0; i < k; i++) {
|
|
588
|
+
pcx += from[2 * i];
|
|
589
|
+
pcy += from[2 * i + 1];
|
|
590
|
+
qcx += to[2 * i];
|
|
591
|
+
qcy += to[2 * i + 1];
|
|
592
|
+
}
|
|
593
|
+
pcx /= k;
|
|
594
|
+
pcy /= k;
|
|
595
|
+
qcx /= k;
|
|
596
|
+
qcy /= k;
|
|
597
|
+
let dot = 0, cross = 0;
|
|
598
|
+
for (let i = 0; i < k; i++) {
|
|
599
|
+
const px = from[2 * i] - pcx, py = from[2 * i + 1] - pcy;
|
|
600
|
+
const qx = to[2 * i] - qcx, qy = to[2 * i + 1] - qcy;
|
|
601
|
+
dot += px * qx + py * qy;
|
|
602
|
+
cross += px * qy - py * qx;
|
|
603
|
+
}
|
|
604
|
+
const len = Math.hypot(dot, cross);
|
|
605
|
+
const cos = len === 0 ? 1 : dot / len, sin = len === 0 ? 0 : cross / len;
|
|
606
|
+
return (x, y) => [cos * (x - pcx) - sin * (y - pcy) + qcx, sin * (x - pcx) + cos * (y - pcy) + qcy];
|
|
607
|
+
}
|
|
608
|
+
function createArapSolver(mesh, pinned) {
|
|
609
|
+
const n = mesh.vertices.length >> 1;
|
|
610
|
+
const pinAt = /* @__PURE__ */ new Map();
|
|
611
|
+
pinned.forEach((v, k) => {
|
|
612
|
+
if (!pinAt.has(v)) pinAt.set(v, k);
|
|
613
|
+
});
|
|
614
|
+
const local = new Int32Array(n);
|
|
615
|
+
const pinFrom = pinned.flatMap((v) => [mesh.vertices[2 * v], mesh.vertices[2 * v + 1]]);
|
|
616
|
+
const solved = [];
|
|
617
|
+
const carried = [];
|
|
618
|
+
for (const p of piecesOf(mesh)) {
|
|
619
|
+
const pins = [...new Set(p.verts.filter((v) => pinAt.has(v)).map((v) => pinAt.get(v)))];
|
|
620
|
+
const piece = pins.length >= 2 ? buildPiece(mesh, p.verts, p.tris, pinAt, local) : null;
|
|
621
|
+
if (piece) solved.push(piece);
|
|
622
|
+
else carried.push({ verts: p.verts, pin: pins.length === 1 ? pins[0] : -1 });
|
|
623
|
+
}
|
|
624
|
+
return {
|
|
625
|
+
solve(targets, opts = {}) {
|
|
626
|
+
const out = Float64Array.from(mesh.vertices);
|
|
627
|
+
const whole = carried.some((c) => c.pin < 0) && pinned.length > 0 ? rigidFit(pinFrom, targets) : null;
|
|
628
|
+
for (const { verts, pin } of carried) {
|
|
629
|
+
for (const v of verts) {
|
|
630
|
+
const x = mesh.vertices[2 * v], y = mesh.vertices[2 * v + 1];
|
|
631
|
+
if (pin >= 0) {
|
|
632
|
+
out[2 * v] = x + targets[2 * pin] - pinFrom[2 * pin];
|
|
633
|
+
out[2 * v + 1] = y + targets[2 * pin + 1] - pinFrom[2 * pin + 1];
|
|
634
|
+
} else if (whole) {
|
|
635
|
+
[out[2 * v], out[2 * v + 1]] = whole(x, y);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const { trace } = opts;
|
|
640
|
+
const report = (stage, round) => trace?.({ stage, round, vertices: Float64Array.from(out) });
|
|
641
|
+
const pos = solved.map((piece) => similaritySolve(piece, targets));
|
|
642
|
+
solved.forEach((piece, k) => placePiece(piece, pos[k], out));
|
|
643
|
+
report("similarity", 1);
|
|
644
|
+
solved.forEach((piece, k) => {
|
|
645
|
+
pos[k] = rigidRound(piece, targets, pos[k]).pos;
|
|
646
|
+
placePiece(piece, pos[k], out);
|
|
647
|
+
});
|
|
648
|
+
report("rigid", 1);
|
|
649
|
+
const rounds = Math.max(0, Math.floor(opts.iterations ?? ARAP_ROUNDS));
|
|
650
|
+
const live = solved.map(() => true);
|
|
651
|
+
for (let r = 1; r <= rounds && live.some(Boolean); r++) {
|
|
652
|
+
solved.forEach((piece, k) => {
|
|
653
|
+
if (!live[k]) return;
|
|
654
|
+
const next = rigidRound(piece, targets, pos[k]).pos;
|
|
655
|
+
live[k] = largestMove(pos[k], next) > SETTLED * piece.size;
|
|
656
|
+
pos[k] = next;
|
|
657
|
+
placePiece(piece, next, out);
|
|
658
|
+
});
|
|
659
|
+
report("local-global", r);
|
|
660
|
+
}
|
|
661
|
+
const injective = Math.max(0, Math.floor(opts.injectiveIterations ?? ARAP_INJECTIVE_ROUNDS));
|
|
662
|
+
const tangled = solved.map((piece, k) => injective > 0 && turnedOver(piece, pos[k]));
|
|
663
|
+
if (tangled.some(Boolean)) {
|
|
664
|
+
const state = solved.map((piece, k) => {
|
|
665
|
+
const eps = tangled[k] ? startingEps(piece, pos[k]) : 0;
|
|
666
|
+
return { pos: pos[k], eps, energy: tangled[k] ? untangleEnergy(piece, pos[k], eps) : 0, live: tangled[k] };
|
|
667
|
+
});
|
|
668
|
+
let r = 1;
|
|
669
|
+
for (; r <= injective && state.some((s) => s.live); r++) {
|
|
670
|
+
solved.forEach((piece, k) => {
|
|
671
|
+
const s = state[k];
|
|
672
|
+
if (!s.live) return;
|
|
673
|
+
const step = untangleRound(piece, s.pos, s.eps, s.energy);
|
|
674
|
+
const settled = step.moved <= SETTLED * piece.size || s.energy - step.energy <= SETTLED * step.energy;
|
|
675
|
+
s.pos = step.pos;
|
|
676
|
+
if (s.eps > 0) {
|
|
677
|
+
s.eps = nextEps(piece, s.pos, s.eps, s.energy, step.energy);
|
|
678
|
+
s.energy = untangleEnergy(piece, s.pos, s.eps);
|
|
679
|
+
} else {
|
|
680
|
+
s.energy = step.energy;
|
|
681
|
+
s.live = !settled;
|
|
682
|
+
}
|
|
683
|
+
placePiece(piece, s.pos, out);
|
|
684
|
+
});
|
|
685
|
+
report("injective", r);
|
|
686
|
+
}
|
|
687
|
+
let fell = false;
|
|
688
|
+
solved.forEach((piece, k) => {
|
|
689
|
+
if (!tangled[k] || !turnedOver(piece, state[k].pos)) return;
|
|
690
|
+
placePiece(piece, pos[k], out);
|
|
691
|
+
fell = true;
|
|
692
|
+
});
|
|
693
|
+
if (fell) report("injective", r);
|
|
694
|
+
}
|
|
695
|
+
return out;
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// src/warp/meshMap.ts
|
|
701
|
+
var EPS = 1e-9;
|
|
702
|
+
function weights(V, T, t, x, y) {
|
|
703
|
+
const a = T[3 * t], b = T[3 * t + 1], c = T[3 * t + 2];
|
|
704
|
+
const ax = V[2 * a], ay = V[2 * a + 1];
|
|
705
|
+
const bx = V[2 * b] - ax, by = V[2 * b + 1] - ay;
|
|
706
|
+
const cx = V[2 * c] - ax, cy = V[2 * c + 1] - ay;
|
|
707
|
+
const det = bx * cy - by * cx;
|
|
708
|
+
if (det === 0) return null;
|
|
709
|
+
const px = x - ax, py = y - ay;
|
|
710
|
+
const w1 = (px * cy - py * cx) / det;
|
|
711
|
+
const w2 = (bx * py - by * px) / det;
|
|
712
|
+
return { tri: t, w0: 1 - w1 - w2, w1, w2 };
|
|
713
|
+
}
|
|
714
|
+
var outside = (s) => Math.max(0, -s.w0, -s.w1, -s.w2);
|
|
715
|
+
function meshLocator(mesh) {
|
|
716
|
+
const V = mesh.vertices, T = mesh.triangles;
|
|
717
|
+
const nt = T.length / 3;
|
|
718
|
+
if (nt === 0) return () => null;
|
|
719
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
720
|
+
for (let i = 0; i < V.length; i += 2) {
|
|
721
|
+
x0 = Math.min(x0, V[i]);
|
|
722
|
+
x1 = Math.max(x1, V[i]);
|
|
723
|
+
y0 = Math.min(y0, V[i + 1]);
|
|
724
|
+
y1 = Math.max(y1, V[i + 1]);
|
|
725
|
+
}
|
|
726
|
+
const side = Math.max(1, Math.ceil(Math.sqrt(nt)));
|
|
727
|
+
const cw = (x1 - x0) / side || 1, ch = (y1 - y0) / side || 1;
|
|
728
|
+
const cell = (v, o, s) => Math.min(side - 1, Math.max(0, Math.floor((v - o) / s)));
|
|
729
|
+
const buckets = Array.from({ length: side * side }, () => []);
|
|
730
|
+
for (let t = 0; t < nt; t++) {
|
|
731
|
+
let tx0 = Infinity, ty0 = Infinity, tx1 = -Infinity, ty1 = -Infinity;
|
|
732
|
+
for (let k = 0; k < 3; k++) {
|
|
733
|
+
const v = T[3 * t + k];
|
|
734
|
+
tx0 = Math.min(tx0, V[2 * v]);
|
|
735
|
+
tx1 = Math.max(tx1, V[2 * v]);
|
|
736
|
+
ty0 = Math.min(ty0, V[2 * v + 1]);
|
|
737
|
+
ty1 = Math.max(ty1, V[2 * v + 1]);
|
|
738
|
+
}
|
|
739
|
+
for (let i = cell(tx0, x0, cw); i <= cell(tx1, x0, cw); i++) {
|
|
740
|
+
for (let j = cell(ty0, y0, ch); j <= cell(ty1, y0, ch); j++) buckets[j * side + i].push(t);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return (x, y) => {
|
|
744
|
+
const ci = cell(x, x0, cw), cj = cell(y, y0, ch);
|
|
745
|
+
let best = null;
|
|
746
|
+
let bestOut = Infinity;
|
|
747
|
+
for (let r = 0; r < side; r++) {
|
|
748
|
+
for (let i = ci - r; i <= ci + r; i++) {
|
|
749
|
+
for (let j = cj - r; j <= cj + r; j++) {
|
|
750
|
+
if (i < 0 || j < 0 || i >= side || j >= side) continue;
|
|
751
|
+
if (Math.max(Math.abs(i - ci), Math.abs(j - cj)) !== r) continue;
|
|
752
|
+
for (const t of buckets[j * side + i]) {
|
|
753
|
+
const s = weights(V, T, t, x, y);
|
|
754
|
+
if (!s) continue;
|
|
755
|
+
const o = outside(s);
|
|
756
|
+
if (o <= EPS) return s;
|
|
757
|
+
if (o < bestOut) {
|
|
758
|
+
bestOut = o;
|
|
759
|
+
best = s;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (best && r >= 1) return best;
|
|
765
|
+
}
|
|
766
|
+
return best;
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function placeSpot(T, at, s) {
|
|
770
|
+
const a = T[3 * s.tri], b = T[3 * s.tri + 1], c = T[3 * s.tri + 2];
|
|
771
|
+
return {
|
|
772
|
+
x: s.w0 * at[2 * a] + s.w1 * at[2 * b] + s.w2 * at[2 * c],
|
|
773
|
+
y: s.w0 * at[2 * a + 1] + s.w1 * at[2 * b + 1] + s.w2 * at[2 * c + 1]
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/warp/refine.ts
|
|
778
|
+
function orient(V, a, b, c) {
|
|
779
|
+
return (V[2 * b] - V[2 * a]) * (V[2 * c + 1] - V[2 * a + 1]) - (V[2 * b + 1] - V[2 * a + 1]) * (V[2 * c] - V[2 * a]);
|
|
780
|
+
}
|
|
781
|
+
function inCircle(V, a, b, c, d) {
|
|
782
|
+
const ax = V[2 * a] - V[2 * d], ay = V[2 * a + 1] - V[2 * d + 1];
|
|
783
|
+
const bx = V[2 * b] - V[2 * d], by = V[2 * b + 1] - V[2 * d + 1];
|
|
784
|
+
const cx = V[2 * c] - V[2 * d], cy = V[2 * c + 1] - V[2 * d + 1];
|
|
785
|
+
const det = (ax * ax + ay * ay) * (bx * cy - cx * by) - (bx * bx + by * by) * (ax * cy - cx * ay) + (cx * cx + cy * cy) * (ax * by - bx * ay);
|
|
786
|
+
return det > 1e-12 * (ax * ax + ay * ay + bx * bx + by * by + cx * cx + cy * cy) ** 2;
|
|
787
|
+
}
|
|
788
|
+
var Triangulation = class {
|
|
789
|
+
constructor(V, stride) {
|
|
790
|
+
this.V = V;
|
|
791
|
+
this.stride = stride;
|
|
792
|
+
}
|
|
793
|
+
V;
|
|
794
|
+
stride;
|
|
795
|
+
tris = [];
|
|
796
|
+
/** Directed edge `a→b` to the triangle having it, counter-clockwise. */
|
|
797
|
+
owner = /* @__PURE__ */ new Map();
|
|
798
|
+
free = [];
|
|
799
|
+
/** The triangle made last, where the next point's search starts. */
|
|
800
|
+
last = 0;
|
|
801
|
+
key(a, b) {
|
|
802
|
+
return a * this.stride + b;
|
|
803
|
+
}
|
|
804
|
+
add(a, b, c) {
|
|
805
|
+
const t = this.free.pop() ?? this.tris.length / 3;
|
|
806
|
+
this.last = t;
|
|
807
|
+
this.tris[3 * t] = a;
|
|
808
|
+
this.tris[3 * t + 1] = b;
|
|
809
|
+
this.tris[3 * t + 2] = c;
|
|
810
|
+
this.owner.set(this.key(a, b), t);
|
|
811
|
+
this.owner.set(this.key(b, c), t);
|
|
812
|
+
this.owner.set(this.key(c, a), t);
|
|
813
|
+
return t;
|
|
814
|
+
}
|
|
815
|
+
remove(t) {
|
|
816
|
+
const [a, b, c] = this.corners(t);
|
|
817
|
+
for (const [u, v] of [[a, b], [b, c], [c, a]]) if (this.owner.get(this.key(u, v)) === t) this.owner.delete(this.key(u, v));
|
|
818
|
+
this.tris[3 * t] = -1;
|
|
819
|
+
this.free.push(t);
|
|
820
|
+
}
|
|
821
|
+
corners(t) {
|
|
822
|
+
return [this.tris[3 * t], this.tris[3 * t + 1], this.tris[3 * t + 2]];
|
|
823
|
+
}
|
|
824
|
+
/** The triangle across edge `a→b` and its corner off that edge, if any. */
|
|
825
|
+
across(a, b) {
|
|
826
|
+
const t = this.owner.get(this.key(b, a));
|
|
827
|
+
if (t === void 0) return null;
|
|
828
|
+
const [x, y, z] = this.corners(t);
|
|
829
|
+
return { t, c: x !== a && x !== b ? x : y !== a && y !== b ? y : z };
|
|
830
|
+
}
|
|
831
|
+
/** Flip edge `a→b` of the triangle `(a, b, p)` while the triangle across it disagrees. */
|
|
832
|
+
legalize(a, b, p) {
|
|
833
|
+
const stack = [[a, b, p]];
|
|
834
|
+
while (stack.length > 0) {
|
|
835
|
+
const [u, v, w] = stack.pop();
|
|
836
|
+
const mine = this.owner.get(this.key(u, v));
|
|
837
|
+
const other = this.across(u, v);
|
|
838
|
+
if (mine === void 0 || !other || !inCircle(this.V, u, v, w, other.c)) continue;
|
|
839
|
+
const q = other.c;
|
|
840
|
+
if (orient(this.V, u, q, w) <= 0 || orient(this.V, q, v, w) <= 0) continue;
|
|
841
|
+
this.remove(mine);
|
|
842
|
+
this.remove(other.t);
|
|
843
|
+
this.add(u, q, w);
|
|
844
|
+
this.add(q, v, w);
|
|
845
|
+
stack.push([u, q, w], [q, v, w]);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
/** Whether triangle `t` holds `p`, and on which edge if on one: -1 outside, 3 strictly inside. */
|
|
849
|
+
holds(t, p) {
|
|
850
|
+
const V = this.V;
|
|
851
|
+
const [a, b, c] = this.corners(t);
|
|
852
|
+
const area = orient(V, a, b, c);
|
|
853
|
+
const ws = [orient(V, b, c, p), orient(V, c, a, p), orient(V, a, b, p)];
|
|
854
|
+
if (ws.some((w) => w < -1e-9 * area)) return -1;
|
|
855
|
+
const flat = ws.findIndex((w) => w <= 1e-9 * area);
|
|
856
|
+
return flat < 0 ? 3 : flat;
|
|
857
|
+
}
|
|
858
|
+
/** The triangle holding `p`: walked to from `from` across the edges `p` lies beyond, or
|
|
859
|
+
* found by a scan when the walk leaves the region. */
|
|
860
|
+
locate(p, from) {
|
|
861
|
+
let t = from;
|
|
862
|
+
for (let step = 0; t >= 0 && this.tris[3 * t] >= 0 && step < this.tris.length; step++) {
|
|
863
|
+
const on = this.holds(t, p);
|
|
864
|
+
if (on >= 0) return { t, on };
|
|
865
|
+
const [a, b, c] = this.corners(t);
|
|
866
|
+
const edge = [[b, c], [c, a], [a, b]].find(([u, v]) => orient(this.V, u, v, p) < 0);
|
|
867
|
+
t = this.across(edge[0], edge[1])?.t ?? -1;
|
|
868
|
+
}
|
|
869
|
+
for (let s = 0; s < this.tris.length / 3; s++) {
|
|
870
|
+
if (this.tris[3 * s] < 0) continue;
|
|
871
|
+
const on = this.holds(s, p);
|
|
872
|
+
if (on >= 0) return { t: s, on };
|
|
873
|
+
}
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
/** Split the triangle holding `p` (or the two sharing the edge it lies on). False when none holds it. */
|
|
877
|
+
insert(p) {
|
|
878
|
+
const found = this.locate(p, this.last);
|
|
879
|
+
if (!found) return false;
|
|
880
|
+
const { t } = found;
|
|
881
|
+
const [a, b, c] = this.corners(t);
|
|
882
|
+
const flat = found.on === 3 ? -1 : found.on;
|
|
883
|
+
if (flat < 0) {
|
|
884
|
+
this.remove(t);
|
|
885
|
+
this.add(a, b, p);
|
|
886
|
+
this.add(b, c, p);
|
|
887
|
+
this.add(c, a, p);
|
|
888
|
+
this.legalize(a, b, p);
|
|
889
|
+
this.legalize(b, c, p);
|
|
890
|
+
this.legalize(c, a, p);
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
const [u, v, w] = [[b, c, a], [c, a, b], [a, b, c]][flat];
|
|
894
|
+
const other = this.across(u, v);
|
|
895
|
+
this.remove(t);
|
|
896
|
+
this.add(v, w, p);
|
|
897
|
+
this.add(w, u, p);
|
|
898
|
+
const rim = [[v, w], [w, u]];
|
|
899
|
+
if (other) {
|
|
900
|
+
this.remove(other.t);
|
|
901
|
+
this.add(u, other.c, p);
|
|
902
|
+
this.add(other.c, v, p);
|
|
903
|
+
rim.push([u, other.c], [other.c, v]);
|
|
904
|
+
}
|
|
905
|
+
for (const [x, y] of rim) this.legalize(x, y, p);
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
function refineTriangulation(coords, triangles, firstInner) {
|
|
910
|
+
const n = coords.length / 2;
|
|
911
|
+
const mesh = new Triangulation(coords, n);
|
|
912
|
+
for (let i = 0; i + 2 < triangles.length; i += 3) {
|
|
913
|
+
const [a, b, c] = [triangles[i], triangles[i + 1], triangles[i + 2]];
|
|
914
|
+
const o = orient(coords, a, b, c);
|
|
915
|
+
if (o > 0) mesh.add(a, b, c);
|
|
916
|
+
else if (o < 0) mesh.add(a, c, b);
|
|
917
|
+
}
|
|
918
|
+
const live = () => Array.from({ length: mesh.tris.length / 3 }, (_, t) => t).filter((t) => mesh.tris[3 * t] >= 0);
|
|
919
|
+
for (const t of live()) {
|
|
920
|
+
if (mesh.tris[3 * t] < 0) continue;
|
|
921
|
+
const [a, b, c] = mesh.corners(t);
|
|
922
|
+
mesh.legalize(a, b, c);
|
|
923
|
+
mesh.legalize(b, c, a);
|
|
924
|
+
mesh.legalize(c, a, b);
|
|
925
|
+
}
|
|
926
|
+
for (let p = firstInner; p < n; p++) mesh.insert(p);
|
|
927
|
+
return live().flatMap((t) => mesh.corners(t));
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// src/warp/puppetMesh.ts
|
|
931
|
+
function ringsOf(path, tolerance) {
|
|
932
|
+
const flat = flattenPolygon(path, tolerance);
|
|
933
|
+
if (path.fillRule === "evenodd") return { flat, filled: (w) => w % 2 !== 0 };
|
|
934
|
+
const crossed = resolveCrossings(flat);
|
|
935
|
+
const nonzero = (w) => w !== 0;
|
|
936
|
+
if (crossed) return { flat: crossed, filled: nonzero };
|
|
937
|
+
const simple = nestRings(flat).every((r) => Math.abs(r.winding) <= 1);
|
|
938
|
+
return { flat: simple ? flat : resolveCrossings(flat, true), filled: nonzero };
|
|
939
|
+
}
|
|
940
|
+
function resample(coords, start, end, h) {
|
|
941
|
+
const out = [];
|
|
942
|
+
for (let i = start; i < end; i++) {
|
|
943
|
+
const j = i + 1 === end ? start : i + 1;
|
|
944
|
+
const ax = coords[2 * i], ay = coords[2 * i + 1];
|
|
945
|
+
const steps = Math.max(1, Math.ceil(Math.hypot(coords[2 * j] - ax, coords[2 * j + 1] - ay) / h));
|
|
946
|
+
for (let k = 0; k < steps; k++) {
|
|
947
|
+
const t = k / steps;
|
|
948
|
+
out.push({ x: ax + (coords[2 * j] - ax) * t, y: ay + (coords[2 * j + 1] - ay) * t, pins: [] });
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return out.filter((p, i) => {
|
|
952
|
+
const q = out[(i + 1) % out.length];
|
|
953
|
+
return out.length < 2 || p.x !== q.x || p.y !== q.y;
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
function regionsOf(path, tolerance, h) {
|
|
957
|
+
const { flat, filled } = ringsOf(path, tolerance);
|
|
958
|
+
const rings = nestRings(flat);
|
|
959
|
+
const regions = /* @__PURE__ */ new Map();
|
|
960
|
+
let area = 0;
|
|
961
|
+
rings.forEach((r, i) => {
|
|
962
|
+
if (r.area === 0 || !filled(r.winding)) return;
|
|
963
|
+
if (r.parent >= 0 && filled(rings[r.parent].winding)) return;
|
|
964
|
+
regions.set(i, { rings: [resample(flat.coords, r.start, r.end, h)], inner: [] });
|
|
965
|
+
area += Math.abs(r.area);
|
|
966
|
+
});
|
|
967
|
+
rings.forEach((r) => {
|
|
968
|
+
const outer = r.area !== 0 && !filled(r.winding) ? regions.get(r.parent) : void 0;
|
|
969
|
+
if (!outer) return;
|
|
970
|
+
outer.rings.push(resample(flat.coords, r.start, r.end, h));
|
|
971
|
+
area -= Math.abs(r.area);
|
|
972
|
+
});
|
|
973
|
+
return { regions: [...regions.values()], area };
|
|
974
|
+
}
|
|
975
|
+
var flatRing = (ring) => ring.flatMap((p) => [p.x, p.y]);
|
|
976
|
+
function inside(region, x, y, flats = region.rings.map(flatRing)) {
|
|
977
|
+
return flats.every((ring, i) => pointInPolygon(ring, x, y) === (i === 0));
|
|
978
|
+
}
|
|
979
|
+
function nearestEdge(region, x, y) {
|
|
980
|
+
let best = { d: Infinity, ring: 0, edge: 0, t: 0 };
|
|
981
|
+
region.rings.forEach((ring, r) => ring.forEach((a, i) => {
|
|
982
|
+
const b = ring[(i + 1) % ring.length];
|
|
983
|
+
const dx = b.x - a.x, dy = b.y - a.y;
|
|
984
|
+
const len2 = dx * dx + dy * dy;
|
|
985
|
+
const t = len2 > 0 ? Math.max(0, Math.min(1, ((x - a.x) * dx + (y - a.y) * dy) / len2)) : 0;
|
|
986
|
+
const d = Math.hypot(a.x + dx * t - x, a.y + dy * t - y);
|
|
987
|
+
if (d < best.d) best = { d, ring: r, edge: i, t };
|
|
988
|
+
}));
|
|
989
|
+
return best;
|
|
990
|
+
}
|
|
991
|
+
function placePin(regions, pin, k, h) {
|
|
992
|
+
const home = regions.find((r) => inside(r, pin.x, pin.y));
|
|
993
|
+
const near = (home ? [home] : regions).map((region) => ({ region, ...nearestEdge(region, pin.x, pin.y) })).reduce((a2, b2) => b2.d < a2.d ? b2 : a2);
|
|
994
|
+
const snap = 0.25 * h;
|
|
995
|
+
if (home && near.d > snap) {
|
|
996
|
+
const same = home.inner.find((p) => Math.hypot(p.x - pin.x, p.y - pin.y) < 1e-9 * h);
|
|
997
|
+
if (same) same.pins.push(k);
|
|
998
|
+
else home.inner.push({ x: pin.x, y: pin.y, pins: [k] });
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
const ring = near.region.rings[near.ring];
|
|
1002
|
+
const a = ring[near.edge], b = ring[(near.edge + 1) % ring.length];
|
|
1003
|
+
const at = { x: a.x + (b.x - a.x) * near.t, y: a.y + (b.y - a.y) * near.t };
|
|
1004
|
+
const da = Math.hypot(at.x - a.x, at.y - a.y), db = Math.hypot(at.x - b.x, at.y - b.y);
|
|
1005
|
+
if (Math.min(da, db) <= snap) (da <= db ? a : b).pins.push(k);
|
|
1006
|
+
else ring.splice(near.edge + 1, 0, { ...at, pins: [k] });
|
|
1007
|
+
}
|
|
1008
|
+
function clearanceTest(rings, r) {
|
|
1009
|
+
const cells = /* @__PURE__ */ new Map();
|
|
1010
|
+
const key = (i, j) => `${i},${j}`;
|
|
1011
|
+
for (const ring of rings) {
|
|
1012
|
+
ring.forEach((a, n) => {
|
|
1013
|
+
const b = ring[(n + 1) % ring.length];
|
|
1014
|
+
for (let i = Math.floor(Math.min(a.x, b.x) / r); i <= Math.floor(Math.max(a.x, b.x) / r); i++) {
|
|
1015
|
+
for (let j = Math.floor(Math.min(a.y, b.y) / r); j <= Math.floor(Math.max(a.y, b.y) / r); j++) {
|
|
1016
|
+
const k = key(i, j);
|
|
1017
|
+
const list = cells.get(k);
|
|
1018
|
+
if (list) list.push([a, b]);
|
|
1019
|
+
else cells.set(k, [[a, b]]);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
return (x, y) => {
|
|
1025
|
+
const ci = Math.floor(x / r), cj = Math.floor(y / r);
|
|
1026
|
+
for (let i = ci - 1; i <= ci + 1; i++) {
|
|
1027
|
+
for (let j = cj - 1; j <= cj + 1; j++) {
|
|
1028
|
+
for (const [a, b] of cells.get(key(i, j)) ?? []) {
|
|
1029
|
+
const dx = b.x - a.x, dy = b.y - a.y;
|
|
1030
|
+
const len2 = dx * dx + dy * dy;
|
|
1031
|
+
const t = len2 > 0 ? Math.max(0, Math.min(1, ((x - a.x) * dx + (y - a.y) * dy) / len2)) : 0;
|
|
1032
|
+
if (Math.hypot(a.x + dx * t - x, a.y + dy * t - y) < r) return true;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return false;
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function crossingsAt(rings, y) {
|
|
1040
|
+
const xs = [];
|
|
1041
|
+
for (const ring of rings) {
|
|
1042
|
+
ring.forEach((a, n) => {
|
|
1043
|
+
const b = ring[(n + 1) % ring.length];
|
|
1044
|
+
if (a.y > y !== b.y > y) xs.push(a.x + (y - a.y) * (b.x - a.x) / (b.y - a.y));
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
return xs.sort((p, q) => p - q);
|
|
1048
|
+
}
|
|
1049
|
+
function fillLattice(region, h) {
|
|
1050
|
+
const outer = region.rings[0];
|
|
1051
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
1052
|
+
for (const p of outer) {
|
|
1053
|
+
x0 = Math.min(x0, p.x);
|
|
1054
|
+
y0 = Math.min(y0, p.y);
|
|
1055
|
+
x1 = Math.max(x1, p.x);
|
|
1056
|
+
y1 = Math.max(y1, p.y);
|
|
1057
|
+
}
|
|
1058
|
+
const pins = [...region.inner];
|
|
1059
|
+
const nearOutline = clearanceTest(region.rings, 0.5 * h);
|
|
1060
|
+
const rowH = h * Math.sqrt(3) / 2;
|
|
1061
|
+
for (let row = 0, y = y0 + rowH / 2; y < y1; row++, y += rowH) {
|
|
1062
|
+
const xs = crossingsAt(region.rings, y);
|
|
1063
|
+
let left = 0;
|
|
1064
|
+
for (let x = x0 + (row % 2 ? h / 2 : h / 4); x < x1; x += h) {
|
|
1065
|
+
while (left < xs.length && xs[left] < x) left++;
|
|
1066
|
+
if (left % 2 === 0 || nearOutline(x, y)) continue;
|
|
1067
|
+
if (pins.some((p) => Math.hypot(p.x - x, p.y - y) < 0.5 * h)) continue;
|
|
1068
|
+
region.inner.push({ x, y, pins: [] });
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function puppetMesh(path, pins, opts = {}) {
|
|
1073
|
+
const box = boundsOfPath(path);
|
|
1074
|
+
const size = Math.max(box.width, box.height);
|
|
1075
|
+
const empty = { vertices: new Float64Array(0), triangles: new Uint32Array(0), pinVertex: new Int32Array(pins.length).fill(-1) };
|
|
1076
|
+
if (!(size > 0)) return empty;
|
|
1077
|
+
const tolerance = size / 1e3;
|
|
1078
|
+
const rough = regionsOf(path, tolerance, size);
|
|
1079
|
+
const h = Math.max(Math.sqrt(Math.max(rough.area, 0) / (opts.vertices ?? 400)), size / 400);
|
|
1080
|
+
const { regions } = regionsOf(path, tolerance, h);
|
|
1081
|
+
if (regions.length === 0) return empty;
|
|
1082
|
+
pins.forEach((p, k) => placePin(regions, p, k, h));
|
|
1083
|
+
const vertices = [];
|
|
1084
|
+
const triangles = [];
|
|
1085
|
+
const pinVertex = new Int32Array(pins.length).fill(-1);
|
|
1086
|
+
for (const region of regions) {
|
|
1087
|
+
fillLattice(region, h);
|
|
1088
|
+
const points = [...region.rings.flat(), ...region.inner];
|
|
1089
|
+
const holes = [];
|
|
1090
|
+
let at = 0;
|
|
1091
|
+
for (const ring of region.rings) {
|
|
1092
|
+
if (at > 0) holes.push(at);
|
|
1093
|
+
at += ring.length;
|
|
1094
|
+
}
|
|
1095
|
+
const offset = vertices.length / 2;
|
|
1096
|
+
const coords = points.flatMap((p) => [p.x, p.y]);
|
|
1097
|
+
const outline = earcut(coords.slice(0, 2 * at), holes.length > 0 ? holes : void 0);
|
|
1098
|
+
for (const t of refineTriangulation(coords, outline, at)) triangles.push(t + offset);
|
|
1099
|
+
points.forEach((p, i) => {
|
|
1100
|
+
for (const k of p.pins) pinVertex[k] = offset + i;
|
|
1101
|
+
});
|
|
1102
|
+
vertices.push(...coords);
|
|
1103
|
+
}
|
|
1104
|
+
return { vertices: Float64Array.from(vertices), triangles: Uint32Array.from(triangles), pinVertex };
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// src/warp/puppet.ts
|
|
1108
|
+
var rigs = /* @__PURE__ */ new WeakMap();
|
|
1109
|
+
function rigFor(source, pins) {
|
|
1110
|
+
const key = pins.map((p) => `${p.from.x},${p.from.y}`).join(";");
|
|
1111
|
+
const held = rigs.get(source);
|
|
1112
|
+
if (held?.key === key) return held;
|
|
1113
|
+
const mesh = puppetMesh(source, pins.map((p) => p.from));
|
|
1114
|
+
const pinned = [...new Set(Array.from(mesh.pinVertex).filter((v) => v >= 0))];
|
|
1115
|
+
const rig = { key, mesh, solver: createArapSolver(mesh, pinned), locate: meshLocator(mesh), spots: /* @__PURE__ */ new Map(), remembered: 0 };
|
|
1116
|
+
rigs.set(source, rig);
|
|
1117
|
+
return rig;
|
|
1118
|
+
}
|
|
1119
|
+
function puppetMap(source, pins) {
|
|
1120
|
+
if (pins.length === 0) return (p) => ({ x: p.x, y: p.y });
|
|
1121
|
+
const rig = rigFor(source, pins);
|
|
1122
|
+
const at = solveRig(rig, pins);
|
|
1123
|
+
if (!at) {
|
|
1124
|
+
const dx = pins[0].to.x - pins[0].from.x, dy = pins[0].to.y - pins[0].from.y;
|
|
1125
|
+
return (p) => ({ x: p.x + dx, y: p.y + dy });
|
|
1126
|
+
}
|
|
1127
|
+
return (p) => {
|
|
1128
|
+
const spot = spotOf(rig, p);
|
|
1129
|
+
return spot ? placeSpot(rig.mesh.triangles, at, spot) : { x: p.x, y: p.y };
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
function solveRig(rig, pins, opts) {
|
|
1133
|
+
const targets = [];
|
|
1134
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1135
|
+
pins.forEach((pin, k) => {
|
|
1136
|
+
const v = rig.mesh.pinVertex[k];
|
|
1137
|
+
if (v < 0 || seen.has(v)) return;
|
|
1138
|
+
seen.add(v);
|
|
1139
|
+
targets.push(pin.to.x + rig.mesh.vertices[2 * v] - pin.from.x, pin.to.y + rig.mesh.vertices[2 * v + 1] - pin.from.y);
|
|
1140
|
+
});
|
|
1141
|
+
return targets.length === 0 ? null : rig.solver.solve(targets, opts);
|
|
1142
|
+
}
|
|
1143
|
+
function puppetSolve(source, pins, opts) {
|
|
1144
|
+
if (pins.length === 0) return null;
|
|
1145
|
+
const rig = rigFor(source, pins);
|
|
1146
|
+
const vertices = solveRig(rig, pins, opts);
|
|
1147
|
+
return vertices ? { mesh: rig.mesh, vertices } : null;
|
|
1148
|
+
}
|
|
1149
|
+
var SPOT_CAP = 1 << 16;
|
|
1150
|
+
function spotOf(rig, p) {
|
|
1151
|
+
let row = rig.spots.get(p.x);
|
|
1152
|
+
const hit = row?.get(p.y);
|
|
1153
|
+
if (hit !== void 0) return hit;
|
|
1154
|
+
const spot = rig.locate(p.x, p.y);
|
|
1155
|
+
if (++rig.remembered > SPOT_CAP) {
|
|
1156
|
+
rig.spots.clear();
|
|
1157
|
+
rig.remembered = 1;
|
|
1158
|
+
row = void 0;
|
|
1159
|
+
}
|
|
1160
|
+
if (!row) {
|
|
1161
|
+
row = /* @__PURE__ */ new Map();
|
|
1162
|
+
rig.spots.set(p.x, row);
|
|
1163
|
+
}
|
|
1164
|
+
row.set(p.y, spot);
|
|
1165
|
+
return spot;
|
|
1166
|
+
}
|
|
1167
|
+
function unwarpPoint(map, target, guess = target, tolerance = 1e-3) {
|
|
1168
|
+
let p = { x: guess.x, y: guess.y };
|
|
1169
|
+
for (let k = 0; k < 50; k++) {
|
|
1170
|
+
const at = map(p);
|
|
1171
|
+
const ex = target.x - at.x, ey = target.y - at.y;
|
|
1172
|
+
if (Math.hypot(ex, ey) <= tolerance) break;
|
|
1173
|
+
p = { x: p.x + ex, y: p.y + ey };
|
|
1174
|
+
}
|
|
1175
|
+
return p;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// src/warp/warpPath.ts
|
|
1179
|
+
var MIN_SAMPLES = 8;
|
|
1180
|
+
var MAX_SAMPLES = 64;
|
|
1181
|
+
var TANGENT_STEP = 1e-3;
|
|
1182
|
+
function warpPath(path, map, opts = {}) {
|
|
1183
|
+
const tol = Math.max(opts.tolerance ?? 0.5, 1e-6);
|
|
1184
|
+
const { anchors, closed } = pathToAnchors(path);
|
|
1185
|
+
const out = anchors.map((sub, s) => warpSubpath(sub, closed[s], map, tol));
|
|
1186
|
+
return anchorsToPath(out, closed, path);
|
|
1187
|
+
}
|
|
1188
|
+
function warpSubpath(sub, closed, map, tol) {
|
|
1189
|
+
const n = sub.length;
|
|
1190
|
+
if (n === 0) return [];
|
|
1191
|
+
const out = [];
|
|
1192
|
+
const segments = closed && n > 1 ? n : n - 1;
|
|
1193
|
+
if (segments === 0) return [map(sub[0])];
|
|
1194
|
+
for (let i = 0; i < segments; i++) {
|
|
1195
|
+
const a = sub[i];
|
|
1196
|
+
const b = sub[(i + 1) % n];
|
|
1197
|
+
const at = segmentAt(a, b);
|
|
1198
|
+
const count = sampleCount(a, b, tol);
|
|
1199
|
+
const pts = [];
|
|
1200
|
+
for (let k = 0; k <= count; k++) pts.push(map(at(k / count)));
|
|
1201
|
+
const start = pts[0];
|
|
1202
|
+
const end = pts[pts.length - 1];
|
|
1203
|
+
const tStart = unit(sub2(map(at(TANGENT_STEP)), start));
|
|
1204
|
+
const tEnd = unit(sub2(map(at(1 - TANGENT_STEP)), end));
|
|
1205
|
+
const run = dedupe(pts);
|
|
1206
|
+
const closing = closed && i === segments - 1;
|
|
1207
|
+
if (run.length < 2) {
|
|
1208
|
+
if (out.length === 0) out.push({ x: start.x, y: start.y });
|
|
1209
|
+
if (!closing) {
|
|
1210
|
+
out.push({ x: end.x, y: end.y });
|
|
1211
|
+
keepCorner(out[out.length - 1], b);
|
|
1212
|
+
}
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1215
|
+
fitPointRun(run, tStart, tEnd, tol, out, closing);
|
|
1216
|
+
if (!closing) keepCorner(out[out.length - 1], b);
|
|
1217
|
+
}
|
|
1218
|
+
keepCorner(out[0], sub[0]);
|
|
1219
|
+
return out;
|
|
1220
|
+
}
|
|
1221
|
+
function segmentAt(a, b) {
|
|
1222
|
+
if (!a.outHandle && !b.inHandle) return (t) => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });
|
|
1223
|
+
const c1 = a.outHandle ?? a;
|
|
1224
|
+
const c2 = b.inHandle ?? b;
|
|
1225
|
+
return (t) => cubicPointAt(a, c1, c2, b, t);
|
|
1226
|
+
}
|
|
1227
|
+
function sampleCount(a, b, tol) {
|
|
1228
|
+
const c1 = a.outHandle ?? a;
|
|
1229
|
+
const c2 = b.inHandle ?? b;
|
|
1230
|
+
const len = Math.hypot(c1.x - a.x, c1.y - a.y) + Math.hypot(c2.x - c1.x, c2.y - c1.y) + Math.hypot(b.x - c2.x, b.y - c2.y);
|
|
1231
|
+
return Math.min(MAX_SAMPLES, Math.max(MIN_SAMPLES, Math.ceil(len / (8 * tol))));
|
|
1232
|
+
}
|
|
1233
|
+
function dedupe(pts) {
|
|
1234
|
+
return pts.filter((p, i) => i === 0 || p.x !== pts[i - 1].x || p.y !== pts[i - 1].y);
|
|
1235
|
+
}
|
|
1236
|
+
var sub2 = (p, q) => ({ x: p.x - q.x, y: p.y - q.y });
|
|
1237
|
+
function unit(p) {
|
|
1238
|
+
const len = Math.hypot(p.x, p.y);
|
|
1239
|
+
return len === 0 ? { x: 0, y: 0 } : { x: p.x / len, y: p.y / len };
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// src/warp/pathWarp.ts
|
|
1243
|
+
function distortOver(box, mode = "free") {
|
|
1244
|
+
const { x, y, width: w, height: h } = box;
|
|
1245
|
+
return {
|
|
1246
|
+
kind: "distort",
|
|
1247
|
+
mode,
|
|
1248
|
+
box: { ...box },
|
|
1249
|
+
corners: [{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }]
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
function envelopeOver(box, rows, cols, mapping = "bicubic") {
|
|
1253
|
+
return { kind: "envelope", mapping, box: { ...box }, mesh: meshGridOver(box, rows, cols) };
|
|
1254
|
+
}
|
|
1255
|
+
function resizeEnvelope(warp, rows, cols) {
|
|
1256
|
+
if (warp.mesh.rows === rows && warp.mesh.cols === cols) return warp;
|
|
1257
|
+
return { ...warp, mesh: meshGridFrom(envelopeUvMap(warp), rows, cols) };
|
|
1258
|
+
}
|
|
1259
|
+
function envelopeUvMap(warp) {
|
|
1260
|
+
return warp.mapping === "coons" ? coonsMesh(warp.mesh) : bicubicMesh(warp.mesh);
|
|
1261
|
+
}
|
|
1262
|
+
function boxUv(box, uv) {
|
|
1263
|
+
const sx = box.width === 0 ? 0 : 1 / box.width;
|
|
1264
|
+
const sy = box.height === 0 ? 0 : 1 / box.height;
|
|
1265
|
+
return (p) => uv((p.x - box.x) * sx, (p.y - box.y) * sy);
|
|
1266
|
+
}
|
|
1267
|
+
function pathWarpMap(warp, source) {
|
|
1268
|
+
switch (warp.kind) {
|
|
1269
|
+
case "distort": {
|
|
1270
|
+
const uv = (warp.mode === "perspective" ? projectiveQuad(warp.corners) : null) ?? bilinearQuad(warp.corners);
|
|
1271
|
+
return boxUv(warp.box, uv);
|
|
1272
|
+
}
|
|
1273
|
+
case "envelope":
|
|
1274
|
+
return boxUv(warp.box, envelopeUvMap(warp));
|
|
1275
|
+
case "puppet":
|
|
1276
|
+
return puppetMap(source, warp.pins);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
function applyPathWarp(source, warp, tolerance = 0.5) {
|
|
1280
|
+
return warpPath(source, pathWarpMap(warp, source), { tolerance });
|
|
1281
|
+
}
|
|
1282
|
+
function warpControls(warp) {
|
|
1283
|
+
switch (warp.kind) {
|
|
1284
|
+
case "distort":
|
|
1285
|
+
return warp.corners;
|
|
1286
|
+
case "envelope":
|
|
1287
|
+
return warp.mesh.points;
|
|
1288
|
+
case "puppet":
|
|
1289
|
+
return warp.pins.map((p) => p.to);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
function moveWarpControl(warp, index, to) {
|
|
1293
|
+
const at = { x: to.x, y: to.y };
|
|
1294
|
+
switch (warp.kind) {
|
|
1295
|
+
case "distort": {
|
|
1296
|
+
const corners = [...warp.corners];
|
|
1297
|
+
if (index < 0 || index > 3) return warp;
|
|
1298
|
+
corners[index] = at;
|
|
1299
|
+
return { ...warp, corners };
|
|
1300
|
+
}
|
|
1301
|
+
case "envelope": {
|
|
1302
|
+
if (index < 0 || index >= warp.mesh.points.length) return warp;
|
|
1303
|
+
const points = [...warp.mesh.points];
|
|
1304
|
+
points[index] = at;
|
|
1305
|
+
return { ...warp, mesh: { ...warp.mesh, points } };
|
|
1306
|
+
}
|
|
1307
|
+
case "puppet": {
|
|
1308
|
+
if (index < 0 || index >= warp.pins.length) return warp;
|
|
1309
|
+
return { ...warp, pins: warp.pins.map((p, i) => i === index ? { from: p.from, to: at } : p) };
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
export { ARAP_INJECTIVE_ROUNDS, ARAP_ROUNDS, applyPathWarp, bicubicMesh, bilinearQuad, coonsMesh, createArapSolver, distortOver, envelopeOver, meshGridFrom, meshGridOver, moveWarpControl, pathWarpMap, projectiveQuad, puppetMap, puppetMesh, puppetSolve, resizeEnvelope, squareToQuadHomography, unwarpPoint, warpControls, warpPath };
|
|
1315
|
+
//# sourceMappingURL=index.js.map
|
|
1316
|
+
//# sourceMappingURL=index.js.map
|