@weasel-js/geom 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +6 -2
  2. package/dist/3d/index.d.ts +8 -1
  3. package/dist/3d/index.js +25 -1
  4. package/dist/3d/index.js.map +1 -1
  5. package/dist/booleans/index.d.ts +6 -5
  6. package/dist/booleans/index.js +583 -73
  7. package/dist/booleans/index.js.map +1 -1
  8. package/dist/{chunk-CE7IY7TB.js → chunk-2N6OWA7E.js} +3 -3
  9. package/dist/{chunk-CE7IY7TB.js.map → chunk-2N6OWA7E.js.map} +1 -1
  10. package/dist/{chunk-Q66NA2FU.js → chunk-3HQJGI63.js} +8 -5
  11. package/dist/chunk-3HQJGI63.js.map +1 -0
  12. package/dist/{chunk-YPWFGFQX.js → chunk-3YH5XU3H.js} +125 -8
  13. package/dist/chunk-3YH5XU3H.js.map +1 -0
  14. package/dist/{chunk-UI3FUWXM.js → chunk-42TZDN5C.js} +4 -4
  15. package/dist/{chunk-UI3FUWXM.js.map → chunk-42TZDN5C.js.map} +1 -1
  16. package/dist/{chunk-B53BERKG.js → chunk-B3D6KWW3.js} +110 -8
  17. package/dist/chunk-B3D6KWW3.js.map +1 -0
  18. package/dist/{chunk-2PKPKU4P.js → chunk-EURWZUNY.js} +25 -22
  19. package/dist/chunk-EURWZUNY.js.map +1 -0
  20. package/dist/{chunk-VHXOCDZ2.js → chunk-FROQPADA.js} +6 -6
  21. package/dist/{chunk-VHXOCDZ2.js.map → chunk-FROQPADA.js.map} +1 -1
  22. package/dist/{chunk-UGE57OTX.js → chunk-GWSJQSQE.js} +11 -8
  23. package/dist/chunk-GWSJQSQE.js.map +1 -0
  24. package/dist/chunk-XXNK4N7Z.js +683 -0
  25. package/dist/chunk-XXNK4N7Z.js.map +1 -0
  26. package/dist/chunk-ZOMUTU7J.js +249 -0
  27. package/dist/chunk-ZOMUTU7J.js.map +1 -0
  28. package/dist/{curvature-PkoLduGc.d.ts → curvature-CDOI32hv.d.ts} +46 -3
  29. package/dist/curves/index.d.ts +2 -2
  30. package/dist/curves/index.js +4 -4
  31. package/dist/index.d.ts +172 -45
  32. package/dist/index.js +244 -178
  33. package/dist/index.js.map +1 -1
  34. package/dist/nd/index.d.ts +28 -1
  35. package/dist/nd/index.js +27 -675
  36. package/dist/nd/index.js.map +1 -1
  37. package/dist/{path-BW8d_2RY.d.ts → path-t80OoJnM.d.ts} +11 -0
  38. package/dist/tessellate/index.d.ts +11 -4
  39. package/dist/tessellate/index.js +4 -4
  40. package/dist/warp/index.d.ts +1 -1
  41. package/dist/warp/index.js +238 -64
  42. package/dist/warp/index.js.map +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-2PKPKU4P.js.map +0 -1
  45. package/dist/chunk-B53BERKG.js.map +0 -1
  46. package/dist/chunk-Q66NA2FU.js.map +0 -1
  47. package/dist/chunk-STGSBIOL.js +0 -152
  48. package/dist/chunk-STGSBIOL.js.map +0 -1
  49. package/dist/chunk-UGE57OTX.js.map +0 -1
  50. package/dist/chunk-YPWFGFQX.js.map +0 -1
@@ -1,10 +1,10 @@
1
- import { boundsOfPath, keepCorner, fitPointRun } from '../chunk-STGSBIOL.js';
2
- import { pathToAnchors, anchorsToPath } from '../chunk-Q66NA2FU.js';
3
- import '../chunk-CE7IY7TB.js';
4
- import { nestRings, flattenPolygon, resolveCrossings } from '../chunk-UGE57OTX.js';
5
- import { pointInPolygon } from '../chunk-2PKPKU4P.js';
1
+ import { boundsOfPath, keepCorner, fitPointRun } from '../chunk-ZOMUTU7J.js';
2
+ import { pathToAnchors, anchorsToPath } from '../chunk-3HQJGI63.js';
3
+ import { nearestOnLine } from '../chunk-2N6OWA7E.js';
4
+ import { nestRings, flattenPolygon, resolveCrossings } from '../chunk-GWSJQSQE.js';
5
+ import { pointInPolygon } from '../chunk-EURWZUNY.js';
6
6
  import '../chunk-755Y4YZS.js';
7
- import { cubicPointAt } from '../chunk-B53BERKG.js';
7
+ import { cubicPointAt, addOrigin } from '../chunk-B3D6KWW3.js';
8
8
  import earcut from 'earcut';
9
9
 
10
10
  // src/warp/quad.ts
@@ -225,6 +225,41 @@ function factorSymmetric(rows) {
225
225
  const x = new Float64Array(n);
226
226
  for (let k = 0; k < n; k++) x[order[k]] = y[k];
227
227
  return x;
228
+ },
229
+ solvePair(b) {
230
+ const y = new Float64Array(2 * n);
231
+ for (let k = 0; k < n; k++) {
232
+ y[2 * k] = b[2 * order[k]];
233
+ y[2 * k + 1] = b[2 * order[k] + 1];
234
+ }
235
+ for (let i = 0; i < n; i++) {
236
+ const fi = first[i], si = start[i];
237
+ let sx = y[2 * i], sy = y[2 * i + 1];
238
+ for (let k = fi; k < i; k++) {
239
+ const l = L[si + k - fi];
240
+ sx -= l * y[2 * k];
241
+ sy -= l * y[2 * k + 1];
242
+ }
243
+ const d = L[si + i - fi];
244
+ y[2 * i] = sx / d;
245
+ y[2 * i + 1] = sy / d;
246
+ }
247
+ for (let i = n - 1; i >= 0; i--) {
248
+ const fi = first[i], si = start[i];
249
+ const d = L[si + i - fi];
250
+ const yx = y[2 * i] /= d, yy = y[2 * i + 1] /= d;
251
+ for (let k = fi; k < i; k++) {
252
+ const l = L[si + k - fi];
253
+ y[2 * k] -= l * yx;
254
+ y[2 * k + 1] -= l * yy;
255
+ }
256
+ }
257
+ const x = new Float64Array(2 * n);
258
+ for (let k = 0; k < n; k++) {
259
+ x[2 * order[k]] = y[2 * k];
260
+ x[2 * order[k] + 1] = y[2 * k + 1];
261
+ }
262
+ return x;
228
263
  }
229
264
  };
230
265
  }
@@ -293,10 +328,78 @@ function edgeEnergy(tris, n) {
293
328
  return rows;
294
329
  }
295
330
 
331
+ // src/warp/lbfgs.ts
332
+ var DEPTH = 6;
333
+ function stepMemory() {
334
+ return { steps: [], turns: [], bentTurns: [], rho: new Float64Array(DEPTH), count: 0, newest: -1, lastGrad: null, lastStep: null, lastBent: null };
335
+ }
336
+ function forget(m) {
337
+ m.count = 0;
338
+ m.newest = -1;
339
+ m.lastGrad = null;
340
+ m.lastStep = null;
341
+ }
342
+ function dot(a, b) {
343
+ let s = 0;
344
+ for (let i = 0; i < a.length; i++) s += a[i] * b[i];
345
+ return s;
346
+ }
347
+ function remember(m, g, bent) {
348
+ const { lastGrad, lastStep, lastBent } = m;
349
+ if (!lastGrad || !lastStep || !lastBent) return;
350
+ const slot = (m.newest + 1) % DEPTH;
351
+ if (m.steps.length <= slot || m.steps[slot].length !== g.length) {
352
+ m.steps[slot] = new Float64Array(g.length);
353
+ m.turns[slot] = new Float64Array(g.length);
354
+ m.bentTurns[slot] = new Float64Array(g.length);
355
+ }
356
+ const s = m.steps[slot], y = m.turns[slot], py = m.bentTurns[slot];
357
+ let sy = 0;
358
+ for (let i = 0; i < g.length; i++) {
359
+ s[i] = lastStep[i];
360
+ y[i] = g[i] - lastGrad[i];
361
+ py[i] = bent[i] - lastBent[i];
362
+ sy += y[i] * s[i];
363
+ }
364
+ if (!(sy > 1e-12)) return;
365
+ m.rho[slot] = 1 / sy;
366
+ m.newest = slot;
367
+ m.count = Math.min(DEPTH, m.count + 1);
368
+ }
369
+ function descent(m, g, precondition, scaled = false) {
370
+ const r = precondition(Float64Array.from(g));
371
+ remember(m, g, r);
372
+ m.lastBent = Float64Array.from(r);
373
+ const q = Float64Array.from(g);
374
+ const n = q.length;
375
+ const alpha = new Float64Array(m.count);
376
+ for (let k = 0; k < m.count; k++) {
377
+ const slot = (m.newest - k + DEPTH) % DEPTH;
378
+ const s = m.steps[slot], y = m.turns[slot], py = m.bentTurns[slot];
379
+ const a = alpha[k] = m.rho[slot] * dot(s, q);
380
+ for (let j = 0; j < n; j++) {
381
+ q[j] -= a * y[j];
382
+ r[j] -= a * py[j];
383
+ }
384
+ }
385
+ if (scaled && m.count > 0) {
386
+ const scale = 1 / (m.rho[m.newest] * dot(m.turns[m.newest], m.bentTurns[m.newest]));
387
+ for (let j = 0; j < n; j++) r[j] *= scale;
388
+ }
389
+ for (let k = m.count - 1; k >= 0; k--) {
390
+ const slot = (m.newest - k + DEPTH) % DEPTH;
391
+ const s = m.steps[slot], y = m.turns[slot];
392
+ const c = alpha[k] - m.rho[slot] * dot(y, r);
393
+ for (let j = 0; j < n; j++) r[j] += c * s[j];
394
+ }
395
+ for (let j = 0; j < n; j++) r[j] = -r[j];
396
+ return r;
397
+ }
398
+
296
399
  // src/warp/injective.ts
297
400
  function restFrames(rest, tris) {
298
401
  const nt = tris.length / 3;
299
- const inv = new Float64Array(4 * nt), area = new Float64Array(nt);
402
+ const inv = new Float64Array(4 * nt), area = new Float64Array(nt), side = new Int8Array(nt);
300
403
  for (let t = 0; t < nt; t++) {
301
404
  const a = tris[3 * t], b = tris[3 * t + 1], c = tris[3 * t + 2];
302
405
  const ux = rest[2 * b] - rest[2 * a], uy = rest[2 * b + 1] - rest[2 * a + 1];
@@ -305,8 +408,9 @@ function restFrames(rest, tris) {
305
408
  if (det === 0) continue;
306
409
  inv.set([vy / det, -vx / det, -uy / det, ux / det], 4 * t);
307
410
  area[t] = Math.abs(det) / 2;
411
+ side[t] = Math.sign(det);
308
412
  }
309
- return { inv, area };
413
+ return { inv, area, side };
310
414
  }
311
415
  var FLIP_MARGIN = 0.8;
312
416
  var ARMIJO = 1e-4;
@@ -326,11 +430,11 @@ function jacobian(piece, pos, t, J) {
326
430
  return true;
327
431
  }
328
432
  function flipBound(piece, pos, d) {
329
- const { tris, rest } = piece;
433
+ const { tris, frames } = piece;
330
434
  let bound = Infinity;
331
435
  for (let t = 0; t < tris.length; t += 3) {
332
436
  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]));
437
+ const side = frames.side[t / 3];
334
438
  if (side === 0) continue;
335
439
  const ux = pos[2 * b] - pos[2 * a], uy = pos[2 * b + 1] - pos[2 * a + 1];
336
440
  const vx = pos[2 * c] - pos[2 * a], vy = pos[2 * c + 1] - pos[2 * a + 1];
@@ -349,23 +453,54 @@ function smallestPositiveRoot(c0, c1, c2) {
349
453
  const disc = c1 * c1 - 4 * c2 * c0;
350
454
  if (disc < 0) return Infinity;
351
455
  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;
456
+ const r1 = q / c2, r2 = c0 / q;
457
+ let least = Infinity;
458
+ if (r1 > 0 && r1 < least) least = r1;
459
+ if (r2 > 0 && r2 < least) least = r2;
460
+ return least;
354
461
  }
355
462
  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;
463
+ function energyTerms(piece) {
464
+ const n = piece.frames.area.length;
465
+ return { shape: new Float64Array(n), det: new Float64Array(n).fill(Infinity) };
466
+ }
467
+ function untangleEnergy(piece, pos, eps, terms) {
468
+ const { tris, frames: { area, inv: m } } = piece;
359
469
  let E = 0;
360
470
  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];
471
+ if (area[t] === 0) continue;
472
+ const a = tris[3 * t], b = tris[3 * t + 1], cc = tris[3 * t + 2], i = 4 * t;
473
+ const ux = pos[2 * b] - pos[2 * a], uy = pos[2 * b + 1] - pos[2 * a + 1];
474
+ const vx = pos[2 * cc] - pos[2 * a], vy = pos[2 * cc + 1] - pos[2 * a + 1];
475
+ const j0 = ux * m[i] + vx * m[i + 2], j1 = ux * m[i + 1] + vx * m[i + 3];
476
+ const j2 = uy * m[i] + vy * m[i + 2], j3 = uy * m[i + 1] + vy * m[i + 3];
477
+ const d = j0 * j3 - j1 * j2;
363
478
  const c = chi(d, eps);
364
479
  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;
480
+ const shape = area[t] * (j0 * j0 + j1 * j1 + j2 * j2 + j3 * j3 + AREA * (d * d + 1));
481
+ if (terms) {
482
+ terms.shape[t] = shape;
483
+ terms.det[t] = d;
484
+ }
485
+ E += shape / c;
366
486
  }
367
487
  return E;
368
488
  }
489
+ function energyAt(terms, eps) {
490
+ let E = 0;
491
+ for (let t = 0; t < terms.shape.length; t++) if (terms.shape[t] > 0) E += terms.shape[t] / chi(terms.det[t], eps);
492
+ return E;
493
+ }
494
+ function restEnergy(piece) {
495
+ let E = 0;
496
+ for (const a of piece.frames.area) E += a * (2 + 2 * AREA);
497
+ return E;
498
+ }
499
+ function leastOf(terms) {
500
+ let least = Infinity;
501
+ for (let t = 0; t < terms.det.length; t++) if (terms.det[t] < least) least = terms.det[t];
502
+ return least;
503
+ }
369
504
  function leastArea(piece, pos) {
370
505
  const J = new Float64Array(4);
371
506
  let least = Infinity;
@@ -375,23 +510,25 @@ function leastArea(piece, pos) {
375
510
  return least;
376
511
  }
377
512
  function gradient(piece, pos, eps) {
378
- const { tris, frames } = piece;
513
+ const { tris, frames: { area, inv: m } } = piece;
379
514
  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];
515
+ for (let t = 0; t < area.length; t++) {
516
+ if (area[t] === 0) continue;
517
+ const a = tris[3 * t], b = tris[3 * t + 1], cc = tris[3 * t + 2], i = 4 * t;
518
+ const ux = pos[2 * b] - pos[2 * a], uy = pos[2 * b + 1] - pos[2 * a + 1];
519
+ const vx = pos[2 * cc] - pos[2 * a], vy = pos[2 * cc + 1] - pos[2 * a + 1];
520
+ const j0 = ux * m[i] + vx * m[i + 2], j1 = ux * m[i + 1] + vx * m[i + 3];
521
+ const j2 = uy * m[i] + vy * m[i + 2], j3 = uy * m[i + 1] + vy * m[i + 3];
522
+ const d = j0 * j3 - j1 * j2;
384
523
  const root = Math.sqrt(eps * eps + d * d);
385
524
  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);
525
+ const A = area[t];
526
+ const top = j0 * j0 + j1 * j1 + j2 * j2 + j3 * j3 + AREA * (d * d + 1);
388
527
  const dc = root === 0 ? 0.5 : (1 + d / root) / 2;
389
528
  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;
529
+ const g0 = s * j0 + r * j3, g1 = s * j1 - r * j2, g2 = s * j2 - r * j1, g3 = s * j3 + r * j0;
392
530
  const gux = g0 * m[i] + g1 * m[i + 1], gvx = g0 * m[i + 2] + g1 * m[i + 3];
393
531
  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
532
  g[2 * b] += gux;
396
533
  g[2 * b + 1] += guy;
397
534
  g[2 * cc] += gvx;
@@ -407,46 +544,59 @@ function turnedOver(piece, pos) {
407
544
  function startingEps(piece, pos) {
408
545
  return Math.max(1e-3, 2 * Math.abs(Math.min(0, leastArea(piece, pos))));
409
546
  }
410
- function nextEps(piece, pos, eps, before, after) {
411
- const least = leastArea(piece, pos);
412
- const sigma = Math.max(1 - after / before, 0.1);
547
+ var SHRINK_TURNED = 0.2;
548
+ var SHRINK_CLEAR = 0.05;
549
+ function nextEps(least, eps, before, after) {
550
+ const sigma = Math.max(1 - after / before, least > 0 ? SHRINK_CLEAR : SHRINK_TURNED);
413
551
  const mu = (1 - sigma) * chi(least, eps);
414
552
  return least < mu ? 2 * Math.sqrt(mu * (mu - least)) : 0;
415
553
  }
416
- function untangleRound(piece, pos, eps, energy) {
554
+ function untangleRound(piece, pos, eps, energy, memory) {
417
555
  const { pinOf, freeOf, step2 } = piece;
556
+ const terms = energyTerms(piece);
418
557
  const g = gradient(piece, pos, eps);
419
558
  const nf = step2.coupling.length;
420
- const bx = new Float64Array(nf), by = new Float64Array(nf);
559
+ const gf = new Float64Array(2 * nf);
421
560
  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];
561
+ const f = freeOf[i];
562
+ if (f >= 0) {
563
+ gf[2 * f] = g[2 * i];
564
+ gf[2 * f + 1] = g[2 * i + 1];
425
565
  }
426
566
  }
427
- const dx = solveSplit(step2, () => 0, bx);
428
- const dy = solveSplit(step2, () => 0, by);
567
+ const precondition = (q) => step2.factored.solvePair(q);
568
+ let df = descent(memory, gf, precondition, eps === 0);
569
+ let slope = dot(gf, df);
570
+ if (!(slope < 0)) {
571
+ forget(memory);
572
+ df = memory.lastBent.map((v) => -v);
573
+ slope = dot(gf, df);
574
+ }
429
575
  const d = new Float64Array(pos.length);
430
- let slope = 0;
431
576
  for (let i = 0; i < pinOf.length; i++) {
432
577
  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];
578
+ if (f >= 0) {
579
+ d[2 * i] = df[2 * f];
580
+ d[2 * i + 1] = df[2 * f + 1];
581
+ }
437
582
  }
438
583
  let s = eps > 0 ? 1 : Math.min(1, FLIP_MARGIN * flipBound(piece, pos, d));
439
584
  const next = new Float64Array(pos.length);
440
585
  for (; s > 1e-12; s /= 2) {
441
586
  for (let i = 0; i < next.length; i++) next[i] = pos[i] + s * d[i];
442
- const e = untangleEnergy(piece, next, eps);
587
+ const e = untangleEnergy(piece, next, eps, terms);
443
588
  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 };
589
+ let far = 0;
590
+ for (let i = 0; i < d.length; i += 2) far = Math.max(far, d[i] * d[i] + d[i + 1] * d[i + 1]);
591
+ const moved = s * Math.sqrt(far);
592
+ memory.lastGrad = gf;
593
+ memory.lastStep = df.map((v) => s * v);
594
+ return { pos: next, energy: e, terms, moved };
447
595
  }
448
596
  }
449
- return { pos, energy, moved: 0 };
597
+ forget(memory);
598
+ untangleEnergy(piece, pos, eps, terms);
599
+ return { pos, energy, terms, moved: 0 };
450
600
  }
451
601
 
452
602
  // src/warp/arapPiece.ts
@@ -497,18 +647,18 @@ function rigidRound(piece, targets, pos) {
497
647
  const bx = new Float64Array(nf), by = new Float64Array(nf);
498
648
  let energy = 0;
499
649
  for (let t = 0; t < tris.length; t += 3) {
500
- let dot = 0, cross = 0, sq = 0;
650
+ let dot2 = 0, cross = 0, sq = 0;
501
651
  for (let e = 0; e < 3; e++) {
502
652
  const i = tris[t + e], j = tris[t + (e + 1) % 3];
503
653
  const px = rest[2 * i] - rest[2 * j], py = rest[2 * i + 1] - rest[2 * j + 1];
504
654
  const qx = pos[2 * i] - pos[2 * j], qy = pos[2 * i + 1] - pos[2 * j + 1];
505
- dot += px * qx + py * qy;
655
+ dot2 += px * qx + py * qy;
506
656
  cross += px * qy - py * qx;
507
657
  sq += px * px + py * py + qx * qx + qy * qy;
508
658
  }
509
- const len = Math.hypot(dot, cross);
659
+ const len = Math.hypot(dot2, cross);
510
660
  energy += sq - 2 * len;
511
- const cos = len === 0 ? 1 : dot / len, sin = len === 0 ? 0 : cross / len;
661
+ const cos = len === 0 ? 1 : dot2 / len, sin = len === 0 ? 0 : cross / len;
512
662
  for (let e = 0; e < 3; e++) {
513
663
  const i = tris[t + e], j = tris[t + (e + 1) % 3];
514
664
  const ex = rest[2 * i] - rest[2 * j], ey = rest[2 * i + 1] - rest[2 * j + 1];
@@ -544,6 +694,8 @@ function placePiece(piece, pos, out) {
544
694
  var ARAP_ROUNDS = 5;
545
695
  var ARAP_INJECTIVE_ROUNDS = 100;
546
696
  var SETTLED = 1e-4;
697
+ var SETTLED_ENERGY = 3e-3;
698
+ var CALM_ROUNDS = 2;
547
699
  function piecesOf(mesh) {
548
700
  const n = mesh.vertices.length >> 1;
549
701
  const parent = Int32Array.from({ length: n }, (_, i) => i);
@@ -594,15 +746,15 @@ function rigidFit(from, to) {
594
746
  pcy /= k;
595
747
  qcx /= k;
596
748
  qcy /= k;
597
- let dot = 0, cross = 0;
749
+ let dot2 = 0, cross = 0;
598
750
  for (let i = 0; i < k; i++) {
599
751
  const px = from[2 * i] - pcx, py = from[2 * i + 1] - pcy;
600
752
  const qx = to[2 * i] - qcx, qy = to[2 * i + 1] - qcy;
601
- dot += px * qx + py * qy;
753
+ dot2 += px * qx + py * qy;
602
754
  cross += px * qy - py * qx;
603
755
  }
604
- const len = Math.hypot(dot, cross);
605
- const cos = len === 0 ? 1 : dot / len, sin = len === 0 ? 0 : cross / len;
756
+ const len = Math.hypot(dot2, cross);
757
+ const cos = len === 0 ? 1 : dot2 / len, sin = len === 0 ? 0 : cross / len;
606
758
  return (x, y) => [cos * (x - pcx) - sin * (y - pcy) + qcx, sin * (x - pcx) + cos * (y - pcy) + qcy];
607
759
  }
608
760
  function createArapSolver(mesh, pinned) {
@@ -663,22 +815,23 @@ function createArapSolver(mesh, pinned) {
663
815
  if (tangled.some(Boolean)) {
664
816
  const state = solved.map((piece, k) => {
665
817
  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] };
818
+ return { pos: pos[k], eps, energy: tangled[k] ? untangleEnergy(piece, pos[k], eps) : 0, live: tangled[k], memory: stepMemory(), calm: 0, rest: restEnergy(piece) };
667
819
  });
668
820
  let r = 1;
669
821
  for (; r <= injective && state.some((s) => s.live); r++) {
670
822
  solved.forEach((piece, k) => {
671
823
  const s = state[k];
672
824
  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;
825
+ const step = untangleRound(piece, s.pos, s.eps, s.energy, s.memory);
826
+ const settled = step.moved <= SETTLED * piece.size || s.energy - step.energy <= SETTLED_ENERGY * (step.energy - s.rest);
675
827
  s.pos = step.pos;
676
828
  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);
829
+ s.eps = nextEps(leastOf(step.terms), s.eps, s.energy, step.energy);
830
+ s.energy = energyAt(step.terms, s.eps);
679
831
  } else {
680
832
  s.energy = step.energy;
681
- s.live = !settled;
833
+ s.calm = settled ? s.calm + 1 : 0;
834
+ s.live = s.calm < CALM_ROUNDS;
682
835
  }
683
836
  placePiece(piece, s.pos, out);
684
837
  });
@@ -1061,6 +1214,7 @@ function refineTriangulation(coords, triangles, firstInner) {
1061
1214
  // src/warp/puppetMesh.ts
1062
1215
  function ringsOf(path, tolerance) {
1063
1216
  const flat = flattenPolygon(path, tolerance);
1217
+ addOrigin(path, flat.coords);
1064
1218
  if (path.fillRule === "evenodd") return { flat, filled: (w) => w % 2 !== 0 };
1065
1219
  const crossed = resolveCrossings(flat);
1066
1220
  const nonzero = (w) => w !== 0;
@@ -1310,6 +1464,8 @@ function unwarpPoint(map, target, guess = target, tolerance = 1e-3) {
1310
1464
  // src/warp/warpPath.ts
1311
1465
  var MIN_SAMPLES = 8;
1312
1466
  var MAX_SAMPLES = 64;
1467
+ var FLATNESS = 0.25;
1468
+ var MAX_HALVINGS = 6;
1313
1469
  var TANGENT_STEP = 1e-3;
1314
1470
  function warpPath(path, map, opts = {}) {
1315
1471
  const tol = Math.max(opts.tolerance ?? 0.5, 1e-6);
@@ -1328,8 +1484,7 @@ function warpSubpath(sub, closed, map, tol) {
1328
1484
  const b = sub[(i + 1) % n];
1329
1485
  const at = segmentAt(a, b);
1330
1486
  const count = sampleCount(a, b, tol);
1331
- const pts = [];
1332
- for (let k = 0; k <= count; k++) pts.push(map(at(k / count)));
1487
+ const pts = sampleWarped((t) => map(at(t)), count, tol);
1333
1488
  const start = pts[0];
1334
1489
  const end = pts[pts.length - 1];
1335
1490
  const tStart = unit(sub2(map(at(TANGENT_STEP)), start));
@@ -1362,6 +1517,25 @@ function sampleCount(a, b, tol) {
1362
1517
  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);
1363
1518
  return Math.min(MAX_SAMPLES, Math.max(MIN_SAMPLES, Math.ceil(len / (8 * tol))));
1364
1519
  }
1520
+ function sampleWarped(curve, count, tol) {
1521
+ const flat = FLATNESS * tol;
1522
+ const out = [curve(0)];
1523
+ const span = (t0, p0, t1, p1, depth) => {
1524
+ const tm = (t0 + t1) / 2;
1525
+ const pm = curve(tm);
1526
+ if (depth < MAX_HALVINGS && fromChord(pm, p0, p1) > flat) {
1527
+ span(t0, p0, tm, pm, depth + 1);
1528
+ span(tm, pm, t1, p1, depth + 1);
1529
+ } else {
1530
+ out.push(p1);
1531
+ }
1532
+ };
1533
+ for (let k = 0; k < count; k++) span(k / count, out[out.length - 1], (k + 1) / count, curve((k + 1) / count), 0);
1534
+ return out;
1535
+ }
1536
+ function fromChord(p, a, b) {
1537
+ return nearestOnLine(p.x, p.y, a.x, a.y, b.x, b.y).dist;
1538
+ }
1365
1539
  function dedupe(pts) {
1366
1540
  return pts.filter((p, i) => i === 0 || p.x !== pts[i - 1].x || p.y !== pts[i - 1].y);
1367
1541
  }