paperlab 0.7.0 → 0.7.1

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.
@@ -4936,6 +4936,8 @@ var ClothSim = class {
4936
4936
  height;
4937
4937
  positions;
4938
4938
  prev;
4939
+ /** Where the constructor laid every particle out — what {@link restore} puts back. */
4940
+ laidOut;
4939
4941
  pinned;
4940
4942
  pinTargets;
4941
4943
  /**
@@ -5069,6 +5071,7 @@ var ClothSim = class {
5069
5071
  }
5070
5072
  }
5071
5073
  this.prev.set(this.positions);
5074
+ this.laidOut = this.positions.slice();
5072
5075
  const idx = (r, c) => r * cols + c;
5073
5076
  const links = [];
5074
5077
  const link = (a, b, kind) => {
@@ -5186,6 +5189,22 @@ var ClothSim = class {
5186
5189
  this.asleep = false;
5187
5190
  this.stillFrames = 0;
5188
5191
  }
5192
+ /**
5193
+ * Back to the sheet as it was laid out: flat, still, every particle where
5194
+ * the constructor put it. Pins, a grab and every lever are left alone.
5195
+ *
5196
+ * For a history that has been rewound — a burn played back from an earlier
5197
+ * moment, say, which hands back paper that had burnt away and paper that
5198
+ * had FALLEN, lying on the floor with its springs to the sheet suddenly
5199
+ * whole again. Solved from there, the solve hauls it up through the sheet
5200
+ * in a tangle. Laid out afresh, it is simply the sheet it is again.
5201
+ */
5202
+ restore() {
5203
+ this.positions.set(this.laidOut);
5204
+ this.prev.set(this.laidOut);
5205
+ this.accumulator = 0;
5206
+ this.wake();
5207
+ }
5189
5208
  /**
5190
5209
  * Take hold of the sheet at one particle.
5191
5210
  *
@@ -5525,6 +5544,7 @@ var CHAR_SHRINK = 0.12;
5525
5544
  var CHAR_CURL = 6;
5526
5545
  var FRONT_GRADIENT = 16 * 16;
5527
5546
  var WET_MASS = 2;
5547
+ var TEAR_STRETCH = 1.5;
5528
5548
  var DamageCoupling = class {
5529
5549
  sim;
5530
5550
  source = null;
@@ -5534,8 +5554,24 @@ var DamageCoupling = class {
5534
5554
  particleTexel = new Int32Array(0);
5535
5555
  /** Gone, per particle, as of the last read. */
5536
5556
  gone;
5557
+ /** Whether any particle has ever been gone since the last reset — nothing can tear before. */
5558
+ holed = false;
5537
5559
  /** The gone particles with live paper beside them. See {@link follow}. */
5538
5560
  followers = [];
5561
+ /**
5562
+ * Which piece of paper each particle belongs to, named by the lowest index
5563
+ * in it — or -1 in the middle of a hole.
5564
+ *
5565
+ * A piece is whatever the sim's unbroken springs still hold together, which
5566
+ * is exactly what moves as one: two pieces are two things the solve lets
5567
+ * part. A gone particle on a cut takes the piece it borders MOST — a cut a
5568
+ * single particle wide borders two, and a particle averaged between them
5569
+ * would be dragged across the gap as one piece fell, stretching both edges
5570
+ * into it.
5571
+ */
5572
+ piece;
5573
+ /** Union-find over the particles, reused on every read. */
5574
+ parent;
5539
5575
  /**
5540
5576
  * Per bend spring, how squarely it lies ACROSS the burn front, 0..1 — and
5541
5577
  * remembered once it has been.
@@ -5553,9 +5589,20 @@ var DamageCoupling = class {
5553
5589
  * but the paper stays rolled the way it rolled.
5554
5590
  */
5555
5591
  curlWeight;
5592
+ /** The mesh's index as it was built — what {@link tear} hides triangles from and mends them back to. */
5593
+ original = null;
5594
+ /** Per triangle of that index: torn, and hidden until the history is rewound. */
5595
+ torn = new Uint8Array(0);
5596
+ /** The triangles with a gone corner — the only ones that can tear. */
5597
+ rim = [];
5598
+ rimStale = true;
5599
+ /** Every torn triangle has to be put back: the history was rewound, or the source went. */
5600
+ mend = false;
5556
5601
  constructor(sim) {
5557
5602
  this.sim = sim;
5558
5603
  this.gone = new Uint8Array(sim.count);
5604
+ this.piece = new Int32Array(sim.count);
5605
+ this.parent = new Int32Array(sim.count);
5559
5606
  this.curlWeight = new Float32Array(sim.constraintCount);
5560
5607
  }
5561
5608
  /**
@@ -5567,7 +5614,13 @@ var DamageCoupling = class {
5567
5614
  update(source) {
5568
5615
  const next = source ?? null;
5569
5616
  if (next === this.source && (next === null || next.version === this.version)) return false;
5570
- if (next !== this.source) this.curlWeight.fill(0);
5617
+ if (next !== this.source) {
5618
+ this.curlWeight.fill(0);
5619
+ if (next && this.holed) {
5620
+ this.rewind();
5621
+ this.gone.fill(0);
5622
+ }
5623
+ }
5571
5624
  this.source = next;
5572
5625
  if (!next) {
5573
5626
  this.reset();
@@ -5587,12 +5640,13 @@ var DamageCoupling = class {
5587
5640
  * laid out flat around them — their positions plus its rest offset from
5588
5641
  * each, averaged. The triangles that straddle the burnt edge then keep
5589
5642
  * roughly their shape instead of stretching back to wherever the particle
5590
- * was when it burnt. Only a band of a single particle's width, with live
5591
- * paper pulling it both ways, is left averaging between them.
5643
+ * was when it burnt. Only the neighbours in its own {@link piece} count, so
5644
+ * on a cut between two pieces it leaves with one of them.
5592
5645
  */
5593
5646
  follow() {
5594
5647
  if (this.followers.length === 0) return;
5595
5648
  const { gone, sim } = this;
5649
+ const piece = this.piece;
5596
5650
  const p = sim.positions;
5597
5651
  const { cols, rows } = sim;
5598
5652
  const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
@@ -5600,6 +5654,7 @@ var DamageCoupling = class {
5600
5654
  for (const g of this.followers) {
5601
5655
  const r = g / cols | 0;
5602
5656
  const c = g % cols;
5657
+ const own = piece[g];
5603
5658
  let sx = 0;
5604
5659
  let sy = 0;
5605
5660
  let sz = 0;
@@ -5611,7 +5666,7 @@ var DamageCoupling = class {
5611
5666
  const nc = c + dc;
5612
5667
  if (dr === 0 && dc === 0 || nc < 0 || nc >= cols) continue;
5613
5668
  const j = nr * cols + nc;
5614
- if (gone[j]) continue;
5669
+ if (gone[j] || piece[j] !== own) continue;
5615
5670
  const j3 = j * 3;
5616
5671
  sx += p[j3] - dc * cellX;
5617
5672
  sy += p[j3 + 1] + dr * cellY;
@@ -5626,6 +5681,70 @@ var DamageCoupling = class {
5626
5681
  p[g3 + 2] = sz / n;
5627
5682
  }
5628
5683
  }
5684
+ /**
5685
+ * Hide the triangles a piece has torn away from. Call after {@link follow},
5686
+ * with the sheet's index buffer: it is rewritten in place, and the return
5687
+ * says whether it changed, so the caller knows to upload it.
5688
+ *
5689
+ * A triangle tears when one of its edges has stretched past
5690
+ * {@link TEAR_STRETCH} — which, among triangles with a burnt-away corner,
5691
+ * only ever happens across a gap that is opening. It is then collapsed to a
5692
+ * point, which draws nothing, casts no shadow and adds nothing to its
5693
+ * corners' normals, and it stays hidden: torn paper does not mend because
5694
+ * the two sides swing close again. Only a rewound history puts it back.
5695
+ *
5696
+ * A sheet in one piece, held, never stretches one that far — so a burn that
5697
+ * cuts nothing loose leaves this index exactly as it was built.
5698
+ */
5699
+ tear(index) {
5700
+ if (!this.holed && !this.mend) return false;
5701
+ if (!this.original || this.original.length !== index.length) {
5702
+ this.original = index.slice();
5703
+ this.torn = new Uint8Array(index.length / 3);
5704
+ this.rimStale = true;
5705
+ }
5706
+ const { original, torn, gone, sim } = this;
5707
+ let changed = false;
5708
+ if (this.mend) {
5709
+ this.mend = false;
5710
+ index.set(original);
5711
+ changed = true;
5712
+ }
5713
+ if (this.rimStale) {
5714
+ this.rimStale = false;
5715
+ const rim = [];
5716
+ for (let t = 0; t < torn.length; t++) {
5717
+ if (gone[original[t * 3]] || gone[original[t * 3 + 1]] || gone[original[t * 3 + 2]]) rim.push(t);
5718
+ }
5719
+ this.rim = rim;
5720
+ }
5721
+ const p = sim.positions;
5722
+ const { cols, rows } = sim;
5723
+ const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
5724
+ const cellY = rows > 1 ? sim.height / (rows - 1) : 0;
5725
+ const limit = TEAR_STRETCH * TEAR_STRETCH;
5726
+ const stretched = (i, j) => {
5727
+ const dc = i % cols - j % cols;
5728
+ const dr = (i / cols | 0) - (j / cols | 0);
5729
+ const rest = (dc * cellX) ** 2 + (dr * cellY) ** 2;
5730
+ const i3 = i * 3;
5731
+ const j3 = j * 3;
5732
+ const now = (p[i3] - p[j3]) ** 2 + (p[i3 + 1] - p[j3 + 1]) ** 2 + (p[i3 + 2] - p[j3 + 2]) ** 2;
5733
+ return now > rest * limit;
5734
+ };
5735
+ for (const t of this.rim) {
5736
+ if (torn[t]) continue;
5737
+ const a = original[t * 3];
5738
+ const b = original[t * 3 + 1];
5739
+ const c = original[t * 3 + 2];
5740
+ if (!stretched(a, b) && !stretched(b, c) && !stretched(c, a)) continue;
5741
+ torn[t] = 1;
5742
+ index[t * 3 + 1] = a;
5743
+ index[t * 3 + 2] = a;
5744
+ changed = true;
5745
+ }
5746
+ return changed;
5747
+ }
5629
5748
  /** Map each particle to its texel. The damage grid's row 0 is v = 0, the sheet's BOTTOM. */
5630
5749
  layout(size) {
5631
5750
  const { cols, rows, count } = this.sim;
@@ -5653,12 +5772,19 @@ var DamageCoupling = class {
5653
5772
  constraintKind,
5654
5773
  constraintMiddle
5655
5774
  } = sim;
5775
+ let rewound = false;
5776
+ let holed = false;
5656
5777
  for (let i = 0; i < sim.count; i++) {
5657
5778
  const t = particleTexel[i] * 4;
5658
5779
  const isGone = pixels[t + PRESENCE] < GONE_BELOW;
5780
+ if (gone[i] && !isGone) rewound = true;
5781
+ if (isGone) holed = true;
5659
5782
  gone[i] = isGone ? 1 : 0;
5660
5783
  invMass[i] = isGone ? 0 : 1 / (1 + WET_MASS * pixels[t + SATURATION] / 255);
5661
5784
  }
5785
+ if (rewound) this.rewind();
5786
+ this.holed = holed;
5787
+ this.rimStale = true;
5662
5788
  for (let k = 0; k < sim.constraintCount; k++) {
5663
5789
  const a = constraintA[k];
5664
5790
  const b = constraintB[k];
@@ -5691,31 +5817,95 @@ var DamageCoupling = class {
5691
5817
  restBend[k] = CHAR_CURL * char * curlWeight[k] * span * span / 8;
5692
5818
  }
5693
5819
  }
5820
+ this.findPieces();
5821
+ }
5822
+ /**
5823
+ * Which piece each particle is in ({@link piece}), and which gone ones
5824
+ * follow a piece at all ({@link followers}).
5825
+ *
5826
+ * Pieces by union-find over the springs that still hold, rooted at the
5827
+ * lowest index so the same burn names the same pieces every time. Then each
5828
+ * gone particle with live paper beside it joins the piece it has the most
5829
+ * neighbours in, the lower-named on a tie.
5830
+ */
5831
+ findPieces() {
5832
+ const { sim, gone } = this;
5833
+ const parent = this.parent;
5834
+ const piece = this.piece;
5835
+ const { broken, constraintA, constraintB } = sim;
5836
+ for (let i = 0; i < sim.count; i++) parent[i] = i;
5837
+ const find = (i) => {
5838
+ let r = i;
5839
+ while (parent[r] !== r) r = parent[r];
5840
+ while (parent[i] !== r) {
5841
+ const up = parent[i];
5842
+ parent[i] = r;
5843
+ i = up;
5844
+ }
5845
+ return r;
5846
+ };
5847
+ for (let k = 0; k < sim.constraintCount; k++) {
5848
+ if (broken[k]) continue;
5849
+ const ra = find(constraintA[k]);
5850
+ const rb = find(constraintB[k]);
5851
+ if (ra < rb) parent[rb] = ra;
5852
+ else if (rb < ra) parent[ra] = rb;
5853
+ }
5854
+ for (let i = 0; i < sim.count; i++) piece[i] = gone[i] ? -1 : find(i);
5694
5855
  const followers = [];
5695
5856
  const { cols, rows } = sim;
5857
+ const seen = [];
5858
+ const votes = [];
5696
5859
  for (let i = 0; i < sim.count; i++) {
5697
5860
  if (!gone[i]) continue;
5698
5861
  const r = i / cols | 0;
5699
5862
  const c = i % cols;
5700
- let beside = false;
5701
- for (let dr = -1; dr <= 1 && !beside; dr++) {
5863
+ seen.length = 0;
5864
+ votes.length = 0;
5865
+ for (let dr = -1; dr <= 1; dr++) {
5702
5866
  const nr = r + dr;
5703
5867
  if (nr < 0 || nr >= rows) continue;
5704
5868
  for (let dc = -1; dc <= 1; dc++) {
5705
5869
  const nc = c + dc;
5706
5870
  if (nc < 0 || nc >= cols) continue;
5707
- if (!gone[nr * cols + nc]) {
5708
- beside = true;
5709
- break;
5871
+ const j = nr * cols + nc;
5872
+ if (gone[j]) continue;
5873
+ const at = seen.indexOf(piece[j]);
5874
+ if (at < 0) {
5875
+ seen.push(piece[j]);
5876
+ votes.push(1);
5877
+ } else {
5878
+ votes[at]++;
5710
5879
  }
5711
5880
  }
5712
5881
  }
5713
- if (beside) followers.push(i);
5882
+ if (seen.length === 0) continue;
5883
+ let best = 0;
5884
+ for (let k = 1; k < seen.length; k++) {
5885
+ if (votes[k] > votes[best] || votes[k] === votes[best] && seen[k] < seen[best]) best = k;
5886
+ }
5887
+ piece[i] = seen[best];
5888
+ followers.push(i);
5714
5889
  }
5715
5890
  this.followers = followers;
5716
5891
  }
5892
+ /**
5893
+ * A rewound history: the sheet starts over.
5894
+ *
5895
+ * Paper that had fallen away is paper again, with every spring back to the
5896
+ * sheet it was cut from — solved from where it lies, the solve would haul it
5897
+ * up off the floor through the sheet in a tangle. And the curl it rolled
5898
+ * into belonged to a front that has not passed yet.
5899
+ */
5900
+ rewind() {
5901
+ this.sim.restore();
5902
+ this.curlWeight.fill(0);
5903
+ this.torn.fill(0);
5904
+ this.mend = true;
5905
+ }
5717
5906
  reset() {
5718
5907
  const { sim } = this;
5908
+ if (this.holed) this.rewind();
5719
5909
  sim.invMass.fill(1);
5720
5910
  sim.restBend.fill(0);
5721
5911
  sim.broken.fill(0);
@@ -5723,6 +5913,7 @@ var DamageCoupling = class {
5723
5913
  this.gone.fill(0);
5724
5914
  this.curlWeight.fill(0);
5725
5915
  this.followers = [];
5916
+ this.holed = false;
5726
5917
  this.version = -1;
5727
5918
  sim.wake();
5728
5919
  }
@@ -7392,7 +7583,11 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
7392
7583
  coupling?.update(props.damage);
7393
7584
  sim.step(delta);
7394
7585
  const moved = !sim.asleep;
7395
- if (moved) coupling?.follow();
7586
+ if (moved && coupling) {
7587
+ coupling.follow();
7588
+ const index = geometry.index;
7589
+ if (index && coupling.tear(index.array)) index.needsUpdate = true;
7590
+ }
7396
7591
  if (!applyShape(sim.positions, moved) && moved) {
7397
7592
  const position = geometry.attributes.position;
7398
7593
  position.array.set(sim.positions);
@@ -10220,4 +10415,4 @@ export {
10220
10415
  describeConfig,
10221
10416
  buildAgentPayload
10222
10417
  };
10223
- //# sourceMappingURL=chunk-MZCAN3AR.js.map
10418
+ //# sourceMappingURL=chunk-NTDVOOBA.js.map