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.cjs
CHANGED
|
@@ -3718,6 +3718,8 @@ var ClothSim = class {
|
|
|
3718
3718
|
height;
|
|
3719
3719
|
positions;
|
|
3720
3720
|
prev;
|
|
3721
|
+
/** Where the constructor laid every particle out — what {@link restore} puts back. */
|
|
3722
|
+
laidOut;
|
|
3721
3723
|
pinned;
|
|
3722
3724
|
pinTargets;
|
|
3723
3725
|
/**
|
|
@@ -3851,6 +3853,7 @@ var ClothSim = class {
|
|
|
3851
3853
|
}
|
|
3852
3854
|
}
|
|
3853
3855
|
this.prev.set(this.positions);
|
|
3856
|
+
this.laidOut = this.positions.slice();
|
|
3854
3857
|
const idx = (r2, c2) => r2 * cols + c2;
|
|
3855
3858
|
const links = [];
|
|
3856
3859
|
const link = (a, b2, kind) => {
|
|
@@ -3968,6 +3971,22 @@ var ClothSim = class {
|
|
|
3968
3971
|
this.asleep = false;
|
|
3969
3972
|
this.stillFrames = 0;
|
|
3970
3973
|
}
|
|
3974
|
+
/**
|
|
3975
|
+
* Back to the sheet as it was laid out: flat, still, every particle where
|
|
3976
|
+
* the constructor put it. Pins, a grab and every lever are left alone.
|
|
3977
|
+
*
|
|
3978
|
+
* For a history that has been rewound — a burn played back from an earlier
|
|
3979
|
+
* moment, say, which hands back paper that had burnt away and paper that
|
|
3980
|
+
* had FALLEN, lying on the floor with its springs to the sheet suddenly
|
|
3981
|
+
* whole again. Solved from there, the solve hauls it up through the sheet
|
|
3982
|
+
* in a tangle. Laid out afresh, it is simply the sheet it is again.
|
|
3983
|
+
*/
|
|
3984
|
+
restore() {
|
|
3985
|
+
this.positions.set(this.laidOut);
|
|
3986
|
+
this.prev.set(this.laidOut);
|
|
3987
|
+
this.accumulator = 0;
|
|
3988
|
+
this.wake();
|
|
3989
|
+
}
|
|
3971
3990
|
/**
|
|
3972
3991
|
* Take hold of the sheet at one particle.
|
|
3973
3992
|
*
|
|
@@ -4338,6 +4357,7 @@ var CHAR_SHRINK = 0.12;
|
|
|
4338
4357
|
var CHAR_CURL = 6;
|
|
4339
4358
|
var FRONT_GRADIENT = 16 * 16;
|
|
4340
4359
|
var WET_MASS = 2;
|
|
4360
|
+
var TEAR_STRETCH = 1.5;
|
|
4341
4361
|
var DamageCoupling = class {
|
|
4342
4362
|
sim;
|
|
4343
4363
|
source = null;
|
|
@@ -4347,8 +4367,24 @@ var DamageCoupling = class {
|
|
|
4347
4367
|
particleTexel = new Int32Array(0);
|
|
4348
4368
|
/** Gone, per particle, as of the last read. */
|
|
4349
4369
|
gone;
|
|
4370
|
+
/** Whether any particle has ever been gone since the last reset — nothing can tear before. */
|
|
4371
|
+
holed = false;
|
|
4350
4372
|
/** The gone particles with live paper beside them. See {@link follow}. */
|
|
4351
4373
|
followers = [];
|
|
4374
|
+
/**
|
|
4375
|
+
* Which piece of paper each particle belongs to, named by the lowest index
|
|
4376
|
+
* in it — or -1 in the middle of a hole.
|
|
4377
|
+
*
|
|
4378
|
+
* A piece is whatever the sim's unbroken springs still hold together, which
|
|
4379
|
+
* is exactly what moves as one: two pieces are two things the solve lets
|
|
4380
|
+
* part. A gone particle on a cut takes the piece it borders MOST — a cut a
|
|
4381
|
+
* single particle wide borders two, and a particle averaged between them
|
|
4382
|
+
* would be dragged across the gap as one piece fell, stretching both edges
|
|
4383
|
+
* into it.
|
|
4384
|
+
*/
|
|
4385
|
+
piece;
|
|
4386
|
+
/** Union-find over the particles, reused on every read. */
|
|
4387
|
+
parent;
|
|
4352
4388
|
/**
|
|
4353
4389
|
* Per bend spring, how squarely it lies ACROSS the burn front, 0..1 — and
|
|
4354
4390
|
* remembered once it has been.
|
|
@@ -4366,9 +4402,20 @@ var DamageCoupling = class {
|
|
|
4366
4402
|
* but the paper stays rolled the way it rolled.
|
|
4367
4403
|
*/
|
|
4368
4404
|
curlWeight;
|
|
4405
|
+
/** The mesh's index as it was built — what {@link tear} hides triangles from and mends them back to. */
|
|
4406
|
+
original = null;
|
|
4407
|
+
/** Per triangle of that index: torn, and hidden until the history is rewound. */
|
|
4408
|
+
torn = new Uint8Array(0);
|
|
4409
|
+
/** The triangles with a gone corner — the only ones that can tear. */
|
|
4410
|
+
rim = [];
|
|
4411
|
+
rimStale = true;
|
|
4412
|
+
/** Every torn triangle has to be put back: the history was rewound, or the source went. */
|
|
4413
|
+
mend = false;
|
|
4369
4414
|
constructor(sim) {
|
|
4370
4415
|
this.sim = sim;
|
|
4371
4416
|
this.gone = new Uint8Array(sim.count);
|
|
4417
|
+
this.piece = new Int32Array(sim.count);
|
|
4418
|
+
this.parent = new Int32Array(sim.count);
|
|
4372
4419
|
this.curlWeight = new Float32Array(sim.constraintCount);
|
|
4373
4420
|
}
|
|
4374
4421
|
/**
|
|
@@ -4380,7 +4427,13 @@ var DamageCoupling = class {
|
|
|
4380
4427
|
update(source) {
|
|
4381
4428
|
const next = source ?? null;
|
|
4382
4429
|
if (next === this.source && (next === null || next.version === this.version)) return false;
|
|
4383
|
-
if (next !== this.source)
|
|
4430
|
+
if (next !== this.source) {
|
|
4431
|
+
this.curlWeight.fill(0);
|
|
4432
|
+
if (next && this.holed) {
|
|
4433
|
+
this.rewind();
|
|
4434
|
+
this.gone.fill(0);
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4384
4437
|
this.source = next;
|
|
4385
4438
|
if (!next) {
|
|
4386
4439
|
this.reset();
|
|
@@ -4400,12 +4453,13 @@ var DamageCoupling = class {
|
|
|
4400
4453
|
* laid out flat around them — their positions plus its rest offset from
|
|
4401
4454
|
* each, averaged. The triangles that straddle the burnt edge then keep
|
|
4402
4455
|
* roughly their shape instead of stretching back to wherever the particle
|
|
4403
|
-
* was when it burnt. Only
|
|
4404
|
-
*
|
|
4456
|
+
* was when it burnt. Only the neighbours in its own {@link piece} count, so
|
|
4457
|
+
* on a cut between two pieces it leaves with one of them.
|
|
4405
4458
|
*/
|
|
4406
4459
|
follow() {
|
|
4407
4460
|
if (this.followers.length === 0) return;
|
|
4408
4461
|
const { gone, sim } = this;
|
|
4462
|
+
const piece = this.piece;
|
|
4409
4463
|
const p2 = sim.positions;
|
|
4410
4464
|
const { cols, rows } = sim;
|
|
4411
4465
|
const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
|
|
@@ -4413,6 +4467,7 @@ var DamageCoupling = class {
|
|
|
4413
4467
|
for (const g of this.followers) {
|
|
4414
4468
|
const r2 = g / cols | 0;
|
|
4415
4469
|
const c2 = g % cols;
|
|
4470
|
+
const own = piece[g];
|
|
4416
4471
|
let sx = 0;
|
|
4417
4472
|
let sy = 0;
|
|
4418
4473
|
let sz = 0;
|
|
@@ -4424,7 +4479,7 @@ var DamageCoupling = class {
|
|
|
4424
4479
|
const nc = c2 + dc;
|
|
4425
4480
|
if (dr === 0 && dc === 0 || nc < 0 || nc >= cols) continue;
|
|
4426
4481
|
const j3 = nr * cols + nc;
|
|
4427
|
-
if (gone[j3]) continue;
|
|
4482
|
+
if (gone[j3] || piece[j3] !== own) continue;
|
|
4428
4483
|
const j32 = j3 * 3;
|
|
4429
4484
|
sx += p2[j32] - dc * cellX;
|
|
4430
4485
|
sy += p2[j32 + 1] + dr * cellY;
|
|
@@ -4439,6 +4494,70 @@ var DamageCoupling = class {
|
|
|
4439
4494
|
p2[g3 + 2] = sz / n;
|
|
4440
4495
|
}
|
|
4441
4496
|
}
|
|
4497
|
+
/**
|
|
4498
|
+
* Hide the triangles a piece has torn away from. Call after {@link follow},
|
|
4499
|
+
* with the sheet's index buffer: it is rewritten in place, and the return
|
|
4500
|
+
* says whether it changed, so the caller knows to upload it.
|
|
4501
|
+
*
|
|
4502
|
+
* A triangle tears when one of its edges has stretched past
|
|
4503
|
+
* {@link TEAR_STRETCH} — which, among triangles with a burnt-away corner,
|
|
4504
|
+
* only ever happens across a gap that is opening. It is then collapsed to a
|
|
4505
|
+
* point, which draws nothing, casts no shadow and adds nothing to its
|
|
4506
|
+
* corners' normals, and it stays hidden: torn paper does not mend because
|
|
4507
|
+
* the two sides swing close again. Only a rewound history puts it back.
|
|
4508
|
+
*
|
|
4509
|
+
* A sheet in one piece, held, never stretches one that far — so a burn that
|
|
4510
|
+
* cuts nothing loose leaves this index exactly as it was built.
|
|
4511
|
+
*/
|
|
4512
|
+
tear(index) {
|
|
4513
|
+
if (!this.holed && !this.mend) return false;
|
|
4514
|
+
if (!this.original || this.original.length !== index.length) {
|
|
4515
|
+
this.original = index.slice();
|
|
4516
|
+
this.torn = new Uint8Array(index.length / 3);
|
|
4517
|
+
this.rimStale = true;
|
|
4518
|
+
}
|
|
4519
|
+
const { original, torn, gone, sim } = this;
|
|
4520
|
+
let changed = false;
|
|
4521
|
+
if (this.mend) {
|
|
4522
|
+
this.mend = false;
|
|
4523
|
+
index.set(original);
|
|
4524
|
+
changed = true;
|
|
4525
|
+
}
|
|
4526
|
+
if (this.rimStale) {
|
|
4527
|
+
this.rimStale = false;
|
|
4528
|
+
const rim = [];
|
|
4529
|
+
for (let t = 0; t < torn.length; t++) {
|
|
4530
|
+
if (gone[original[t * 3]] || gone[original[t * 3 + 1]] || gone[original[t * 3 + 2]]) rim.push(t);
|
|
4531
|
+
}
|
|
4532
|
+
this.rim = rim;
|
|
4533
|
+
}
|
|
4534
|
+
const p2 = sim.positions;
|
|
4535
|
+
const { cols, rows } = sim;
|
|
4536
|
+
const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
|
|
4537
|
+
const cellY = rows > 1 ? sim.height / (rows - 1) : 0;
|
|
4538
|
+
const limit = TEAR_STRETCH * TEAR_STRETCH;
|
|
4539
|
+
const stretched = (i, j3) => {
|
|
4540
|
+
const dc = i % cols - j3 % cols;
|
|
4541
|
+
const dr = (i / cols | 0) - (j3 / cols | 0);
|
|
4542
|
+
const rest = (dc * cellX) ** 2 + (dr * cellY) ** 2;
|
|
4543
|
+
const i3 = i * 3;
|
|
4544
|
+
const j32 = j3 * 3;
|
|
4545
|
+
const now = (p2[i3] - p2[j32]) ** 2 + (p2[i3 + 1] - p2[j32 + 1]) ** 2 + (p2[i3 + 2] - p2[j32 + 2]) ** 2;
|
|
4546
|
+
return now > rest * limit;
|
|
4547
|
+
};
|
|
4548
|
+
for (const t of this.rim) {
|
|
4549
|
+
if (torn[t]) continue;
|
|
4550
|
+
const a = original[t * 3];
|
|
4551
|
+
const b2 = original[t * 3 + 1];
|
|
4552
|
+
const c2 = original[t * 3 + 2];
|
|
4553
|
+
if (!stretched(a, b2) && !stretched(b2, c2) && !stretched(c2, a)) continue;
|
|
4554
|
+
torn[t] = 1;
|
|
4555
|
+
index[t * 3 + 1] = a;
|
|
4556
|
+
index[t * 3 + 2] = a;
|
|
4557
|
+
changed = true;
|
|
4558
|
+
}
|
|
4559
|
+
return changed;
|
|
4560
|
+
}
|
|
4442
4561
|
/** Map each particle to its texel. The damage grid's row 0 is v = 0, the sheet's BOTTOM. */
|
|
4443
4562
|
layout(size) {
|
|
4444
4563
|
const { cols, rows, count } = this.sim;
|
|
@@ -4466,12 +4585,19 @@ var DamageCoupling = class {
|
|
|
4466
4585
|
constraintKind,
|
|
4467
4586
|
constraintMiddle
|
|
4468
4587
|
} = sim;
|
|
4588
|
+
let rewound = false;
|
|
4589
|
+
let holed = false;
|
|
4469
4590
|
for (let i = 0; i < sim.count; i++) {
|
|
4470
4591
|
const t = particleTexel[i] * 4;
|
|
4471
4592
|
const isGone = pixels[t + PRESENCE] < GONE_BELOW;
|
|
4593
|
+
if (gone[i] && !isGone) rewound = true;
|
|
4594
|
+
if (isGone) holed = true;
|
|
4472
4595
|
gone[i] = isGone ? 1 : 0;
|
|
4473
4596
|
invMass[i] = isGone ? 0 : 1 / (1 + WET_MASS * pixels[t + SATURATION] / 255);
|
|
4474
4597
|
}
|
|
4598
|
+
if (rewound) this.rewind();
|
|
4599
|
+
this.holed = holed;
|
|
4600
|
+
this.rimStale = true;
|
|
4475
4601
|
for (let k2 = 0; k2 < sim.constraintCount; k2++) {
|
|
4476
4602
|
const a = constraintA[k2];
|
|
4477
4603
|
const b2 = constraintB[k2];
|
|
@@ -4504,31 +4630,95 @@ var DamageCoupling = class {
|
|
|
4504
4630
|
restBend[k2] = CHAR_CURL * char * curlWeight[k2] * span * span / 8;
|
|
4505
4631
|
}
|
|
4506
4632
|
}
|
|
4633
|
+
this.findPieces();
|
|
4634
|
+
}
|
|
4635
|
+
/**
|
|
4636
|
+
* Which piece each particle is in ({@link piece}), and which gone ones
|
|
4637
|
+
* follow a piece at all ({@link followers}).
|
|
4638
|
+
*
|
|
4639
|
+
* Pieces by union-find over the springs that still hold, rooted at the
|
|
4640
|
+
* lowest index so the same burn names the same pieces every time. Then each
|
|
4641
|
+
* gone particle with live paper beside it joins the piece it has the most
|
|
4642
|
+
* neighbours in, the lower-named on a tie.
|
|
4643
|
+
*/
|
|
4644
|
+
findPieces() {
|
|
4645
|
+
const { sim, gone } = this;
|
|
4646
|
+
const parent = this.parent;
|
|
4647
|
+
const piece = this.piece;
|
|
4648
|
+
const { broken, constraintA, constraintB } = sim;
|
|
4649
|
+
for (let i = 0; i < sim.count; i++) parent[i] = i;
|
|
4650
|
+
const find = (i) => {
|
|
4651
|
+
let r2 = i;
|
|
4652
|
+
while (parent[r2] !== r2) r2 = parent[r2];
|
|
4653
|
+
while (parent[i] !== r2) {
|
|
4654
|
+
const up = parent[i];
|
|
4655
|
+
parent[i] = r2;
|
|
4656
|
+
i = up;
|
|
4657
|
+
}
|
|
4658
|
+
return r2;
|
|
4659
|
+
};
|
|
4660
|
+
for (let k2 = 0; k2 < sim.constraintCount; k2++) {
|
|
4661
|
+
if (broken[k2]) continue;
|
|
4662
|
+
const ra = find(constraintA[k2]);
|
|
4663
|
+
const rb = find(constraintB[k2]);
|
|
4664
|
+
if (ra < rb) parent[rb] = ra;
|
|
4665
|
+
else if (rb < ra) parent[ra] = rb;
|
|
4666
|
+
}
|
|
4667
|
+
for (let i = 0; i < sim.count; i++) piece[i] = gone[i] ? -1 : find(i);
|
|
4507
4668
|
const followers = [];
|
|
4508
4669
|
const { cols, rows } = sim;
|
|
4670
|
+
const seen = [];
|
|
4671
|
+
const votes = [];
|
|
4509
4672
|
for (let i = 0; i < sim.count; i++) {
|
|
4510
4673
|
if (!gone[i]) continue;
|
|
4511
4674
|
const r2 = i / cols | 0;
|
|
4512
4675
|
const c2 = i % cols;
|
|
4513
|
-
|
|
4514
|
-
|
|
4676
|
+
seen.length = 0;
|
|
4677
|
+
votes.length = 0;
|
|
4678
|
+
for (let dr = -1; dr <= 1; dr++) {
|
|
4515
4679
|
const nr = r2 + dr;
|
|
4516
4680
|
if (nr < 0 || nr >= rows) continue;
|
|
4517
4681
|
for (let dc = -1; dc <= 1; dc++) {
|
|
4518
4682
|
const nc = c2 + dc;
|
|
4519
4683
|
if (nc < 0 || nc >= cols) continue;
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4684
|
+
const j3 = nr * cols + nc;
|
|
4685
|
+
if (gone[j3]) continue;
|
|
4686
|
+
const at = seen.indexOf(piece[j3]);
|
|
4687
|
+
if (at < 0) {
|
|
4688
|
+
seen.push(piece[j3]);
|
|
4689
|
+
votes.push(1);
|
|
4690
|
+
} else {
|
|
4691
|
+
votes[at]++;
|
|
4523
4692
|
}
|
|
4524
4693
|
}
|
|
4525
4694
|
}
|
|
4526
|
-
if (
|
|
4695
|
+
if (seen.length === 0) continue;
|
|
4696
|
+
let best = 0;
|
|
4697
|
+
for (let k2 = 1; k2 < seen.length; k2++) {
|
|
4698
|
+
if (votes[k2] > votes[best] || votes[k2] === votes[best] && seen[k2] < seen[best]) best = k2;
|
|
4699
|
+
}
|
|
4700
|
+
piece[i] = seen[best];
|
|
4701
|
+
followers.push(i);
|
|
4527
4702
|
}
|
|
4528
4703
|
this.followers = followers;
|
|
4529
4704
|
}
|
|
4705
|
+
/**
|
|
4706
|
+
* A rewound history: the sheet starts over.
|
|
4707
|
+
*
|
|
4708
|
+
* Paper that had fallen away is paper again, with every spring back to the
|
|
4709
|
+
* sheet it was cut from — solved from where it lies, the solve would haul it
|
|
4710
|
+
* up off the floor through the sheet in a tangle. And the curl it rolled
|
|
4711
|
+
* into belonged to a front that has not passed yet.
|
|
4712
|
+
*/
|
|
4713
|
+
rewind() {
|
|
4714
|
+
this.sim.restore();
|
|
4715
|
+
this.curlWeight.fill(0);
|
|
4716
|
+
this.torn.fill(0);
|
|
4717
|
+
this.mend = true;
|
|
4718
|
+
}
|
|
4530
4719
|
reset() {
|
|
4531
4720
|
const { sim } = this;
|
|
4721
|
+
if (this.holed) this.rewind();
|
|
4532
4722
|
sim.invMass.fill(1);
|
|
4533
4723
|
sim.restBend.fill(0);
|
|
4534
4724
|
sim.broken.fill(0);
|
|
@@ -4536,6 +4726,7 @@ var DamageCoupling = class {
|
|
|
4536
4726
|
this.gone.fill(0);
|
|
4537
4727
|
this.curlWeight.fill(0);
|
|
4538
4728
|
this.followers = [];
|
|
4729
|
+
this.holed = false;
|
|
4539
4730
|
this.version = -1;
|
|
4540
4731
|
sim.wake();
|
|
4541
4732
|
}
|
|
@@ -8188,7 +8379,11 @@ var PaperMesh = (0, import_react8.forwardRef)(function PaperMesh2(props, ref) {
|
|
|
8188
8379
|
coupling?.update(props.damage);
|
|
8189
8380
|
sim.step(delta);
|
|
8190
8381
|
const moved = !sim.asleep;
|
|
8191
|
-
if (moved
|
|
8382
|
+
if (moved && coupling) {
|
|
8383
|
+
coupling.follow();
|
|
8384
|
+
const index = geometry.index;
|
|
8385
|
+
if (index && coupling.tear(index.array)) index.needsUpdate = true;
|
|
8386
|
+
}
|
|
8192
8387
|
if (!applyShape(sim.positions, moved) && moved) {
|
|
8193
8388
|
const position = geometry.attributes.position;
|
|
8194
8389
|
position.array.set(sim.positions);
|