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.
- package/dist/{chunk-MZCAN3AR.js → chunk-NTDVOOBA.js} +207 -12
- package/dist/chunk-NTDVOOBA.js.map +1 -0
- package/dist/index.cjs +206 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/stage.cjs +206 -11
- package/dist/stage.cjs.map +1 -1
- package/dist/stage.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-MZCAN3AR.js.map +0 -1
package/dist/index.js
CHANGED
package/dist/stage.cjs
CHANGED
|
@@ -4360,6 +4360,8 @@ var ClothSim = class {
|
|
|
4360
4360
|
height;
|
|
4361
4361
|
positions;
|
|
4362
4362
|
prev;
|
|
4363
|
+
/** Where the constructor laid every particle out — what {@link restore} puts back. */
|
|
4364
|
+
laidOut;
|
|
4363
4365
|
pinned;
|
|
4364
4366
|
pinTargets;
|
|
4365
4367
|
/**
|
|
@@ -4493,6 +4495,7 @@ var ClothSim = class {
|
|
|
4493
4495
|
}
|
|
4494
4496
|
}
|
|
4495
4497
|
this.prev.set(this.positions);
|
|
4498
|
+
this.laidOut = this.positions.slice();
|
|
4496
4499
|
const idx = (r2, c2) => r2 * cols + c2;
|
|
4497
4500
|
const links = [];
|
|
4498
4501
|
const link = (a, b2, kind) => {
|
|
@@ -4610,6 +4613,22 @@ var ClothSim = class {
|
|
|
4610
4613
|
this.asleep = false;
|
|
4611
4614
|
this.stillFrames = 0;
|
|
4612
4615
|
}
|
|
4616
|
+
/**
|
|
4617
|
+
* Back to the sheet as it was laid out: flat, still, every particle where
|
|
4618
|
+
* the constructor put it. Pins, a grab and every lever are left alone.
|
|
4619
|
+
*
|
|
4620
|
+
* For a history that has been rewound — a burn played back from an earlier
|
|
4621
|
+
* moment, say, which hands back paper that had burnt away and paper that
|
|
4622
|
+
* had FALLEN, lying on the floor with its springs to the sheet suddenly
|
|
4623
|
+
* whole again. Solved from there, the solve hauls it up through the sheet
|
|
4624
|
+
* in a tangle. Laid out afresh, it is simply the sheet it is again.
|
|
4625
|
+
*/
|
|
4626
|
+
restore() {
|
|
4627
|
+
this.positions.set(this.laidOut);
|
|
4628
|
+
this.prev.set(this.laidOut);
|
|
4629
|
+
this.accumulator = 0;
|
|
4630
|
+
this.wake();
|
|
4631
|
+
}
|
|
4613
4632
|
/**
|
|
4614
4633
|
* Take hold of the sheet at one particle.
|
|
4615
4634
|
*
|
|
@@ -4980,6 +4999,7 @@ var CHAR_SHRINK = 0.12;
|
|
|
4980
4999
|
var CHAR_CURL = 6;
|
|
4981
5000
|
var FRONT_GRADIENT = 16 * 16;
|
|
4982
5001
|
var WET_MASS = 2;
|
|
5002
|
+
var TEAR_STRETCH = 1.5;
|
|
4983
5003
|
var DamageCoupling = class {
|
|
4984
5004
|
sim;
|
|
4985
5005
|
source = null;
|
|
@@ -4989,8 +5009,24 @@ var DamageCoupling = class {
|
|
|
4989
5009
|
particleTexel = new Int32Array(0);
|
|
4990
5010
|
/** Gone, per particle, as of the last read. */
|
|
4991
5011
|
gone;
|
|
5012
|
+
/** Whether any particle has ever been gone since the last reset — nothing can tear before. */
|
|
5013
|
+
holed = false;
|
|
4992
5014
|
/** The gone particles with live paper beside them. See {@link follow}. */
|
|
4993
5015
|
followers = [];
|
|
5016
|
+
/**
|
|
5017
|
+
* Which piece of paper each particle belongs to, named by the lowest index
|
|
5018
|
+
* in it — or -1 in the middle of a hole.
|
|
5019
|
+
*
|
|
5020
|
+
* A piece is whatever the sim's unbroken springs still hold together, which
|
|
5021
|
+
* is exactly what moves as one: two pieces are two things the solve lets
|
|
5022
|
+
* part. A gone particle on a cut takes the piece it borders MOST — a cut a
|
|
5023
|
+
* single particle wide borders two, and a particle averaged between them
|
|
5024
|
+
* would be dragged across the gap as one piece fell, stretching both edges
|
|
5025
|
+
* into it.
|
|
5026
|
+
*/
|
|
5027
|
+
piece;
|
|
5028
|
+
/** Union-find over the particles, reused on every read. */
|
|
5029
|
+
parent;
|
|
4994
5030
|
/**
|
|
4995
5031
|
* Per bend spring, how squarely it lies ACROSS the burn front, 0..1 — and
|
|
4996
5032
|
* remembered once it has been.
|
|
@@ -5008,9 +5044,20 @@ var DamageCoupling = class {
|
|
|
5008
5044
|
* but the paper stays rolled the way it rolled.
|
|
5009
5045
|
*/
|
|
5010
5046
|
curlWeight;
|
|
5047
|
+
/** The mesh's index as it was built — what {@link tear} hides triangles from and mends them back to. */
|
|
5048
|
+
original = null;
|
|
5049
|
+
/** Per triangle of that index: torn, and hidden until the history is rewound. */
|
|
5050
|
+
torn = new Uint8Array(0);
|
|
5051
|
+
/** The triangles with a gone corner — the only ones that can tear. */
|
|
5052
|
+
rim = [];
|
|
5053
|
+
rimStale = true;
|
|
5054
|
+
/** Every torn triangle has to be put back: the history was rewound, or the source went. */
|
|
5055
|
+
mend = false;
|
|
5011
5056
|
constructor(sim) {
|
|
5012
5057
|
this.sim = sim;
|
|
5013
5058
|
this.gone = new Uint8Array(sim.count);
|
|
5059
|
+
this.piece = new Int32Array(sim.count);
|
|
5060
|
+
this.parent = new Int32Array(sim.count);
|
|
5014
5061
|
this.curlWeight = new Float32Array(sim.constraintCount);
|
|
5015
5062
|
}
|
|
5016
5063
|
/**
|
|
@@ -5022,7 +5069,13 @@ var DamageCoupling = class {
|
|
|
5022
5069
|
update(source) {
|
|
5023
5070
|
const next = source ?? null;
|
|
5024
5071
|
if (next === this.source && (next === null || next.version === this.version)) return false;
|
|
5025
|
-
if (next !== this.source)
|
|
5072
|
+
if (next !== this.source) {
|
|
5073
|
+
this.curlWeight.fill(0);
|
|
5074
|
+
if (next && this.holed) {
|
|
5075
|
+
this.rewind();
|
|
5076
|
+
this.gone.fill(0);
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5026
5079
|
this.source = next;
|
|
5027
5080
|
if (!next) {
|
|
5028
5081
|
this.reset();
|
|
@@ -5042,12 +5095,13 @@ var DamageCoupling = class {
|
|
|
5042
5095
|
* laid out flat around them — their positions plus its rest offset from
|
|
5043
5096
|
* each, averaged. The triangles that straddle the burnt edge then keep
|
|
5044
5097
|
* roughly their shape instead of stretching back to wherever the particle
|
|
5045
|
-
* was when it burnt. Only
|
|
5046
|
-
*
|
|
5098
|
+
* was when it burnt. Only the neighbours in its own {@link piece} count, so
|
|
5099
|
+
* on a cut between two pieces it leaves with one of them.
|
|
5047
5100
|
*/
|
|
5048
5101
|
follow() {
|
|
5049
5102
|
if (this.followers.length === 0) return;
|
|
5050
5103
|
const { gone, sim } = this;
|
|
5104
|
+
const piece = this.piece;
|
|
5051
5105
|
const p2 = sim.positions;
|
|
5052
5106
|
const { cols, rows } = sim;
|
|
5053
5107
|
const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
|
|
@@ -5055,6 +5109,7 @@ var DamageCoupling = class {
|
|
|
5055
5109
|
for (const g of this.followers) {
|
|
5056
5110
|
const r2 = g / cols | 0;
|
|
5057
5111
|
const c2 = g % cols;
|
|
5112
|
+
const own = piece[g];
|
|
5058
5113
|
let sx = 0;
|
|
5059
5114
|
let sy = 0;
|
|
5060
5115
|
let sz = 0;
|
|
@@ -5066,7 +5121,7 @@ var DamageCoupling = class {
|
|
|
5066
5121
|
const nc = c2 + dc;
|
|
5067
5122
|
if (dr === 0 && dc === 0 || nc < 0 || nc >= cols) continue;
|
|
5068
5123
|
const j3 = nr * cols + nc;
|
|
5069
|
-
if (gone[j3]) continue;
|
|
5124
|
+
if (gone[j3] || piece[j3] !== own) continue;
|
|
5070
5125
|
const j32 = j3 * 3;
|
|
5071
5126
|
sx += p2[j32] - dc * cellX;
|
|
5072
5127
|
sy += p2[j32 + 1] + dr * cellY;
|
|
@@ -5081,6 +5136,70 @@ var DamageCoupling = class {
|
|
|
5081
5136
|
p2[g3 + 2] = sz / n;
|
|
5082
5137
|
}
|
|
5083
5138
|
}
|
|
5139
|
+
/**
|
|
5140
|
+
* Hide the triangles a piece has torn away from. Call after {@link follow},
|
|
5141
|
+
* with the sheet's index buffer: it is rewritten in place, and the return
|
|
5142
|
+
* says whether it changed, so the caller knows to upload it.
|
|
5143
|
+
*
|
|
5144
|
+
* A triangle tears when one of its edges has stretched past
|
|
5145
|
+
* {@link TEAR_STRETCH} — which, among triangles with a burnt-away corner,
|
|
5146
|
+
* only ever happens across a gap that is opening. It is then collapsed to a
|
|
5147
|
+
* point, which draws nothing, casts no shadow and adds nothing to its
|
|
5148
|
+
* corners' normals, and it stays hidden: torn paper does not mend because
|
|
5149
|
+
* the two sides swing close again. Only a rewound history puts it back.
|
|
5150
|
+
*
|
|
5151
|
+
* A sheet in one piece, held, never stretches one that far — so a burn that
|
|
5152
|
+
* cuts nothing loose leaves this index exactly as it was built.
|
|
5153
|
+
*/
|
|
5154
|
+
tear(index) {
|
|
5155
|
+
if (!this.holed && !this.mend) return false;
|
|
5156
|
+
if (!this.original || this.original.length !== index.length) {
|
|
5157
|
+
this.original = index.slice();
|
|
5158
|
+
this.torn = new Uint8Array(index.length / 3);
|
|
5159
|
+
this.rimStale = true;
|
|
5160
|
+
}
|
|
5161
|
+
const { original, torn, gone, sim } = this;
|
|
5162
|
+
let changed = false;
|
|
5163
|
+
if (this.mend) {
|
|
5164
|
+
this.mend = false;
|
|
5165
|
+
index.set(original);
|
|
5166
|
+
changed = true;
|
|
5167
|
+
}
|
|
5168
|
+
if (this.rimStale) {
|
|
5169
|
+
this.rimStale = false;
|
|
5170
|
+
const rim = [];
|
|
5171
|
+
for (let t = 0; t < torn.length; t++) {
|
|
5172
|
+
if (gone[original[t * 3]] || gone[original[t * 3 + 1]] || gone[original[t * 3 + 2]]) rim.push(t);
|
|
5173
|
+
}
|
|
5174
|
+
this.rim = rim;
|
|
5175
|
+
}
|
|
5176
|
+
const p2 = sim.positions;
|
|
5177
|
+
const { cols, rows } = sim;
|
|
5178
|
+
const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
|
|
5179
|
+
const cellY = rows > 1 ? sim.height / (rows - 1) : 0;
|
|
5180
|
+
const limit = TEAR_STRETCH * TEAR_STRETCH;
|
|
5181
|
+
const stretched = (i, j3) => {
|
|
5182
|
+
const dc = i % cols - j3 % cols;
|
|
5183
|
+
const dr = (i / cols | 0) - (j3 / cols | 0);
|
|
5184
|
+
const rest = (dc * cellX) ** 2 + (dr * cellY) ** 2;
|
|
5185
|
+
const i3 = i * 3;
|
|
5186
|
+
const j32 = j3 * 3;
|
|
5187
|
+
const now = (p2[i3] - p2[j32]) ** 2 + (p2[i3 + 1] - p2[j32 + 1]) ** 2 + (p2[i3 + 2] - p2[j32 + 2]) ** 2;
|
|
5188
|
+
return now > rest * limit;
|
|
5189
|
+
};
|
|
5190
|
+
for (const t of this.rim) {
|
|
5191
|
+
if (torn[t]) continue;
|
|
5192
|
+
const a = original[t * 3];
|
|
5193
|
+
const b2 = original[t * 3 + 1];
|
|
5194
|
+
const c2 = original[t * 3 + 2];
|
|
5195
|
+
if (!stretched(a, b2) && !stretched(b2, c2) && !stretched(c2, a)) continue;
|
|
5196
|
+
torn[t] = 1;
|
|
5197
|
+
index[t * 3 + 1] = a;
|
|
5198
|
+
index[t * 3 + 2] = a;
|
|
5199
|
+
changed = true;
|
|
5200
|
+
}
|
|
5201
|
+
return changed;
|
|
5202
|
+
}
|
|
5084
5203
|
/** Map each particle to its texel. The damage grid's row 0 is v = 0, the sheet's BOTTOM. */
|
|
5085
5204
|
layout(size) {
|
|
5086
5205
|
const { cols, rows, count } = this.sim;
|
|
@@ -5108,12 +5227,19 @@ var DamageCoupling = class {
|
|
|
5108
5227
|
constraintKind,
|
|
5109
5228
|
constraintMiddle
|
|
5110
5229
|
} = sim;
|
|
5230
|
+
let rewound = false;
|
|
5231
|
+
let holed = false;
|
|
5111
5232
|
for (let i = 0; i < sim.count; i++) {
|
|
5112
5233
|
const t = particleTexel[i] * 4;
|
|
5113
5234
|
const isGone = pixels[t + PRESENCE] < GONE_BELOW;
|
|
5235
|
+
if (gone[i] && !isGone) rewound = true;
|
|
5236
|
+
if (isGone) holed = true;
|
|
5114
5237
|
gone[i] = isGone ? 1 : 0;
|
|
5115
5238
|
invMass[i] = isGone ? 0 : 1 / (1 + WET_MASS * pixels[t + SATURATION] / 255);
|
|
5116
5239
|
}
|
|
5240
|
+
if (rewound) this.rewind();
|
|
5241
|
+
this.holed = holed;
|
|
5242
|
+
this.rimStale = true;
|
|
5117
5243
|
for (let k2 = 0; k2 < sim.constraintCount; k2++) {
|
|
5118
5244
|
const a = constraintA[k2];
|
|
5119
5245
|
const b2 = constraintB[k2];
|
|
@@ -5146,31 +5272,95 @@ var DamageCoupling = class {
|
|
|
5146
5272
|
restBend[k2] = CHAR_CURL * char * curlWeight[k2] * span * span / 8;
|
|
5147
5273
|
}
|
|
5148
5274
|
}
|
|
5275
|
+
this.findPieces();
|
|
5276
|
+
}
|
|
5277
|
+
/**
|
|
5278
|
+
* Which piece each particle is in ({@link piece}), and which gone ones
|
|
5279
|
+
* follow a piece at all ({@link followers}).
|
|
5280
|
+
*
|
|
5281
|
+
* Pieces by union-find over the springs that still hold, rooted at the
|
|
5282
|
+
* lowest index so the same burn names the same pieces every time. Then each
|
|
5283
|
+
* gone particle with live paper beside it joins the piece it has the most
|
|
5284
|
+
* neighbours in, the lower-named on a tie.
|
|
5285
|
+
*/
|
|
5286
|
+
findPieces() {
|
|
5287
|
+
const { sim, gone } = this;
|
|
5288
|
+
const parent = this.parent;
|
|
5289
|
+
const piece = this.piece;
|
|
5290
|
+
const { broken, constraintA, constraintB } = sim;
|
|
5291
|
+
for (let i = 0; i < sim.count; i++) parent[i] = i;
|
|
5292
|
+
const find = (i) => {
|
|
5293
|
+
let r2 = i;
|
|
5294
|
+
while (parent[r2] !== r2) r2 = parent[r2];
|
|
5295
|
+
while (parent[i] !== r2) {
|
|
5296
|
+
const up = parent[i];
|
|
5297
|
+
parent[i] = r2;
|
|
5298
|
+
i = up;
|
|
5299
|
+
}
|
|
5300
|
+
return r2;
|
|
5301
|
+
};
|
|
5302
|
+
for (let k2 = 0; k2 < sim.constraintCount; k2++) {
|
|
5303
|
+
if (broken[k2]) continue;
|
|
5304
|
+
const ra = find(constraintA[k2]);
|
|
5305
|
+
const rb = find(constraintB[k2]);
|
|
5306
|
+
if (ra < rb) parent[rb] = ra;
|
|
5307
|
+
else if (rb < ra) parent[ra] = rb;
|
|
5308
|
+
}
|
|
5309
|
+
for (let i = 0; i < sim.count; i++) piece[i] = gone[i] ? -1 : find(i);
|
|
5149
5310
|
const followers = [];
|
|
5150
5311
|
const { cols, rows } = sim;
|
|
5312
|
+
const seen = [];
|
|
5313
|
+
const votes = [];
|
|
5151
5314
|
for (let i = 0; i < sim.count; i++) {
|
|
5152
5315
|
if (!gone[i]) continue;
|
|
5153
5316
|
const r2 = i / cols | 0;
|
|
5154
5317
|
const c2 = i % cols;
|
|
5155
|
-
|
|
5156
|
-
|
|
5318
|
+
seen.length = 0;
|
|
5319
|
+
votes.length = 0;
|
|
5320
|
+
for (let dr = -1; dr <= 1; dr++) {
|
|
5157
5321
|
const nr = r2 + dr;
|
|
5158
5322
|
if (nr < 0 || nr >= rows) continue;
|
|
5159
5323
|
for (let dc = -1; dc <= 1; dc++) {
|
|
5160
5324
|
const nc = c2 + dc;
|
|
5161
5325
|
if (nc < 0 || nc >= cols) continue;
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5326
|
+
const j3 = nr * cols + nc;
|
|
5327
|
+
if (gone[j3]) continue;
|
|
5328
|
+
const at = seen.indexOf(piece[j3]);
|
|
5329
|
+
if (at < 0) {
|
|
5330
|
+
seen.push(piece[j3]);
|
|
5331
|
+
votes.push(1);
|
|
5332
|
+
} else {
|
|
5333
|
+
votes[at]++;
|
|
5165
5334
|
}
|
|
5166
5335
|
}
|
|
5167
5336
|
}
|
|
5168
|
-
if (
|
|
5337
|
+
if (seen.length === 0) continue;
|
|
5338
|
+
let best = 0;
|
|
5339
|
+
for (let k2 = 1; k2 < seen.length; k2++) {
|
|
5340
|
+
if (votes[k2] > votes[best] || votes[k2] === votes[best] && seen[k2] < seen[best]) best = k2;
|
|
5341
|
+
}
|
|
5342
|
+
piece[i] = seen[best];
|
|
5343
|
+
followers.push(i);
|
|
5169
5344
|
}
|
|
5170
5345
|
this.followers = followers;
|
|
5171
5346
|
}
|
|
5347
|
+
/**
|
|
5348
|
+
* A rewound history: the sheet starts over.
|
|
5349
|
+
*
|
|
5350
|
+
* Paper that had fallen away is paper again, with every spring back to the
|
|
5351
|
+
* sheet it was cut from — solved from where it lies, the solve would haul it
|
|
5352
|
+
* up off the floor through the sheet in a tangle. And the curl it rolled
|
|
5353
|
+
* into belonged to a front that has not passed yet.
|
|
5354
|
+
*/
|
|
5355
|
+
rewind() {
|
|
5356
|
+
this.sim.restore();
|
|
5357
|
+
this.curlWeight.fill(0);
|
|
5358
|
+
this.torn.fill(0);
|
|
5359
|
+
this.mend = true;
|
|
5360
|
+
}
|
|
5172
5361
|
reset() {
|
|
5173
5362
|
const { sim } = this;
|
|
5363
|
+
if (this.holed) this.rewind();
|
|
5174
5364
|
sim.invMass.fill(1);
|
|
5175
5365
|
sim.restBend.fill(0);
|
|
5176
5366
|
sim.broken.fill(0);
|
|
@@ -5178,6 +5368,7 @@ var DamageCoupling = class {
|
|
|
5178
5368
|
this.gone.fill(0);
|
|
5179
5369
|
this.curlWeight.fill(0);
|
|
5180
5370
|
this.followers = [];
|
|
5371
|
+
this.holed = false;
|
|
5181
5372
|
this.version = -1;
|
|
5182
5373
|
sim.wake();
|
|
5183
5374
|
}
|
|
@@ -8491,7 +8682,11 @@ var PaperMesh = (0, import_react12.forwardRef)(function PaperMesh2(props, ref) {
|
|
|
8491
8682
|
coupling?.update(props.damage);
|
|
8492
8683
|
sim.step(delta);
|
|
8493
8684
|
const moved = !sim.asleep;
|
|
8494
|
-
if (moved
|
|
8685
|
+
if (moved && coupling) {
|
|
8686
|
+
coupling.follow();
|
|
8687
|
+
const index = geometry.index;
|
|
8688
|
+
if (index && coupling.tear(index.array)) index.needsUpdate = true;
|
|
8689
|
+
}
|
|
8495
8690
|
if (!applyShape(sim.positions, moved) && moved) {
|
|
8496
8691
|
const position = geometry.attributes.position;
|
|
8497
8692
|
position.array.set(sim.positions);
|