paperlab 0.7.0 → 0.8.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 +7 -5
- package/dist/{FxPostPass-M66QMVVC.js → FxPostPass-V6YDK4H2.js} +23 -5
- package/dist/FxPostPass-V6YDK4H2.js.map +1 -0
- package/dist/chunk-232SU3AQ.js +34 -0
- package/dist/chunk-232SU3AQ.js.map +1 -0
- package/dist/{chunk-GB6BMHC3.js → chunk-7WOGU2D4.js} +7 -19
- package/dist/{chunk-GB6BMHC3.js.map → chunk-7WOGU2D4.js.map} +1 -1
- package/dist/{chunk-MZCAN3AR.js → chunk-SVWWC5UL.js} +576 -160
- package/dist/chunk-SVWWC5UL.js.map +1 -0
- package/dist/{damageContract-DPF1YFLZ.d.cts → damageContract-nF1fgK7P.d.cts} +31 -3
- package/dist/{damageContract-DPF1YFLZ.d.ts → damageContract-nF1fgK7P.d.ts} +31 -3
- package/dist/fx.cjs +320 -91
- package/dist/fx.cjs.map +1 -1
- package/dist/fx.d.cts +81 -55
- package/dist/fx.d.ts +81 -55
- package/dist/fx.js +278 -48
- package/dist/fx.js.map +1 -1
- package/dist/index.cjs +606 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -5
- package/dist/index.d.ts +13 -5
- package/dist/index.js +17 -4
- package/dist/index.js.map +1 -1
- package/dist/{slots-C5PqY0Q5.d.cts → slots-D_EE8GXn.d.cts} +12 -0
- package/dist/{slots-C5PqY0Q5.d.ts → slots-D_EE8GXn.d.ts} +12 -0
- package/dist/stage.cjs +600 -188
- package/dist/stage.cjs.map +1 -1
- package/dist/stage.d.cts +1 -1
- package/dist/stage.d.ts +1 -1
- package/dist/stage.js +2 -2
- package/package.json +1 -1
- package/dist/FxPostPass-M66QMVVC.js.map +0 -1
- package/dist/chunk-7TLIWPGM.js +0 -36
- package/dist/chunk-7TLIWPGM.js.map +0 -1
- package/dist/chunk-MZCAN3AR.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DAMAGE_CHANNELS,
|
|
3
|
-
DAMAGE_LOOK_DEFAULTS
|
|
4
|
-
|
|
3
|
+
DAMAGE_LOOK_DEFAULTS,
|
|
4
|
+
roomLight
|
|
5
|
+
} from "./chunk-232SU3AQ.js";
|
|
5
6
|
|
|
6
7
|
// src/config/merge.ts
|
|
7
8
|
function mergeConfig(base, override) {
|
|
@@ -1437,8 +1438,23 @@ var backdropSchema = z15.object({
|
|
|
1437
1438
|
/** Out of focus, for the same reason. */
|
|
1438
1439
|
blur: z15.number().min(0).max(1).default(0.2)
|
|
1439
1440
|
});
|
|
1441
|
+
var floorSchema = z15.object({
|
|
1442
|
+
enabled: z15.boolean().default(false),
|
|
1443
|
+
/** Dark and matte: it is the ground, not the subject. */
|
|
1444
|
+
color: z15.string().default("#1a1917").describe("color"),
|
|
1445
|
+
/**
|
|
1446
|
+
* Where it lies, in the sheet's own space. The contact shadow falls here
|
|
1447
|
+
* too, and a simulated sheet stops here — one floor, one height, so paper
|
|
1448
|
+
* cannot land through the thing its shadow is on.
|
|
1449
|
+
*/
|
|
1450
|
+
y: z15.number().min(-4).max(0).default(-1.05),
|
|
1451
|
+
/** How rough it is, 0 mirror to 1 chalk. Slate, by default. */
|
|
1452
|
+
roughness: z15.number().min(0).max(1).default(0.92)
|
|
1453
|
+
});
|
|
1440
1454
|
var sceneSchema = z15.object({
|
|
1441
1455
|
lighting: z15.enum(lightingNames).default("studio"),
|
|
1456
|
+
/** The ground under the sheet — see `floorSchema`. Off unless asked for. */
|
|
1457
|
+
floor: floorSchema.prefault({}),
|
|
1442
1458
|
/** What is behind the sheet. Unset leaves the canvas alone. */
|
|
1443
1459
|
backdrop: backdropSchema.optional(),
|
|
1444
1460
|
/**
|
|
@@ -4936,6 +4952,8 @@ var ClothSim = class {
|
|
|
4936
4952
|
height;
|
|
4937
4953
|
positions;
|
|
4938
4954
|
prev;
|
|
4955
|
+
/** Where the constructor laid every particle out — what {@link restore} puts back. */
|
|
4956
|
+
laidOut;
|
|
4939
4957
|
pinned;
|
|
4940
4958
|
pinTargets;
|
|
4941
4959
|
/**
|
|
@@ -5011,6 +5029,16 @@ var ClothSim = class {
|
|
|
5011
5029
|
* cannot do this job: it moves paper, and a uniformly curled sheet in still
|
|
5012
5030
|
* air has no business going anywhere.
|
|
5013
5031
|
*/
|
|
5032
|
+
/**
|
|
5033
|
+
* An acceleration on each particle from whatever is in the air around the
|
|
5034
|
+
* sheet, xyz — the fire's updraft, so far (`DamageCoupling`). Not the wind:
|
|
5035
|
+
* the wind is one thing blowing on the whole sheet, and this is local, a
|
|
5036
|
+
* hot column over a burning rim and nothing a hand's width away.
|
|
5037
|
+
*
|
|
5038
|
+
* An acceleration rather than a force, so it lifts a soaked sheet no
|
|
5039
|
+
* differently from a dry one: hot air does not know what paper weighs.
|
|
5040
|
+
*/
|
|
5041
|
+
draught;
|
|
5014
5042
|
restBend;
|
|
5015
5043
|
/** For each bend spring, the particle between its ends; -1 for every other kind. */
|
|
5016
5044
|
constraintMiddle;
|
|
@@ -5069,6 +5097,7 @@ var ClothSim = class {
|
|
|
5069
5097
|
}
|
|
5070
5098
|
}
|
|
5071
5099
|
this.prev.set(this.positions);
|
|
5100
|
+
this.laidOut = this.positions.slice();
|
|
5072
5101
|
const idx = (r, c) => r * cols + c;
|
|
5073
5102
|
const links = [];
|
|
5074
5103
|
const link = (a, b, kind) => {
|
|
@@ -5096,6 +5125,7 @@ var ClothSim = class {
|
|
|
5096
5125
|
this.constraintStiffness = new Float64Array(m).fill(1);
|
|
5097
5126
|
this.broken = new Uint8Array(m);
|
|
5098
5127
|
this.restBend = new Float64Array(m);
|
|
5128
|
+
this.draught = new Float64Array(this.count * 3);
|
|
5099
5129
|
this.constraintMiddle = new Int32Array(m).fill(-1);
|
|
5100
5130
|
for (let k = 0; k < m; k++) {
|
|
5101
5131
|
const a = links[k * 3];
|
|
@@ -5186,6 +5216,22 @@ var ClothSim = class {
|
|
|
5186
5216
|
this.asleep = false;
|
|
5187
5217
|
this.stillFrames = 0;
|
|
5188
5218
|
}
|
|
5219
|
+
/**
|
|
5220
|
+
* Back to the sheet as it was laid out: flat, still, every particle where
|
|
5221
|
+
* the constructor put it. Pins, a grab and every lever are left alone.
|
|
5222
|
+
*
|
|
5223
|
+
* For a history that has been rewound — a burn played back from an earlier
|
|
5224
|
+
* moment, say, which hands back paper that had burnt away and paper that
|
|
5225
|
+
* had FALLEN, lying on the floor with its springs to the sheet suddenly
|
|
5226
|
+
* whole again. Solved from there, the solve hauls it up through the sheet
|
|
5227
|
+
* in a tangle. Laid out afresh, it is simply the sheet it is again.
|
|
5228
|
+
*/
|
|
5229
|
+
restore() {
|
|
5230
|
+
this.positions.set(this.laidOut);
|
|
5231
|
+
this.prev.set(this.laidOut);
|
|
5232
|
+
this.accumulator = 0;
|
|
5233
|
+
this.wake();
|
|
5234
|
+
}
|
|
5189
5235
|
/**
|
|
5190
5236
|
* Take hold of the sheet at one particle.
|
|
5191
5237
|
*
|
|
@@ -5418,9 +5464,9 @@ var ClothSim = class {
|
|
|
5418
5464
|
this.prev[i3] = x;
|
|
5419
5465
|
this.prev[i3 + 1] = y;
|
|
5420
5466
|
this.prev[i3 + 2] = z25;
|
|
5421
|
-
p[i3] = x + vx + ax * im * dt2;
|
|
5422
|
-
p[i3 + 1] = y + vy + (ay * im - gravity * 3.2) * dt2;
|
|
5423
|
-
p[i3 + 2] = z25 + vz + az * im * dt2;
|
|
5467
|
+
p[i3] = x + vx + (ax * im + this.draught[i3]) * dt2;
|
|
5468
|
+
p[i3 + 1] = y + vy + (ay * im - gravity * 3.2 + this.draught[i3 + 1]) * dt2;
|
|
5469
|
+
p[i3 + 2] = z25 + vz + (az * im + this.draught[i3 + 2]) * dt2;
|
|
5424
5470
|
maxTravel = Math.max(maxTravel, vx * vx + vy * vy + vz * vz);
|
|
5425
5471
|
}
|
|
5426
5472
|
for (let k = 0; k < this.grabbed.length; k++) {
|
|
@@ -5519,12 +5565,16 @@ var ClothSim = class {
|
|
|
5519
5565
|
};
|
|
5520
5566
|
|
|
5521
5567
|
// src/physics/damage.ts
|
|
5522
|
-
var { char: CHAR, saturation: SATURATION, presence: PRESENCE } = DAMAGE_CHANNELS;
|
|
5568
|
+
var { char: CHAR, saturation: SATURATION, heat: HEAT, presence: PRESENCE } = DAMAGE_CHANNELS;
|
|
5523
5569
|
var GONE_BELOW = 128;
|
|
5524
5570
|
var CHAR_SHRINK = 0.12;
|
|
5571
|
+
var ACROSS_SHRINK = 0.55;
|
|
5572
|
+
var UPDRAFT = 1.1;
|
|
5573
|
+
var UPDRAFT_OUT = 0.35;
|
|
5525
5574
|
var CHAR_CURL = 6;
|
|
5526
5575
|
var FRONT_GRADIENT = 16 * 16;
|
|
5527
5576
|
var WET_MASS = 2;
|
|
5577
|
+
var TEAR_STRETCH = 1.5;
|
|
5528
5578
|
var DamageCoupling = class {
|
|
5529
5579
|
sim;
|
|
5530
5580
|
source = null;
|
|
@@ -5534,12 +5584,31 @@ var DamageCoupling = class {
|
|
|
5534
5584
|
particleTexel = new Int32Array(0);
|
|
5535
5585
|
/** Gone, per particle, as of the last read. */
|
|
5536
5586
|
gone;
|
|
5587
|
+
/** Whether any particle has ever been gone since the last reset — nothing can tear before. */
|
|
5588
|
+
holed = false;
|
|
5537
5589
|
/** The gone particles with live paper beside them. See {@link follow}. */
|
|
5538
5590
|
followers = [];
|
|
5539
5591
|
/**
|
|
5540
|
-
*
|
|
5592
|
+
* Which piece of paper each particle belongs to, named by the lowest index
|
|
5593
|
+
* in it — or -1 in the middle of a hole.
|
|
5594
|
+
*
|
|
5595
|
+
* A piece is whatever the sim's unbroken springs still hold together, which
|
|
5596
|
+
* is exactly what moves as one: two pieces are two things the solve lets
|
|
5597
|
+
* part. A gone particle on a cut takes the piece it borders MOST — a cut a
|
|
5598
|
+
* single particle wide borders two, and a particle averaged between them
|
|
5599
|
+
* would be dragged across the gap as one piece fell, stretching both edges
|
|
5600
|
+
* into it.
|
|
5601
|
+
*/
|
|
5602
|
+
piece;
|
|
5603
|
+
/** Union-find over the particles, reused on every read. */
|
|
5604
|
+
parent;
|
|
5605
|
+
/**
|
|
5606
|
+
* Per spring, how squarely it lies ACROSS the burn front, 0..1 — and
|
|
5541
5607
|
* remembered once it has been.
|
|
5542
5608
|
*
|
|
5609
|
+
* It decides two things: how far the spring shrinks ({@link ACROSS_SHRINK})
|
|
5610
|
+
* and, for a bend spring, which way the paper rolls ({@link CHAR_CURL}).
|
|
5611
|
+
*
|
|
5543
5612
|
* A burnt edge rolls about an axis along the front, like a scroll; it does
|
|
5544
5613
|
* not dish into a bowl. Measured with the curl in every bend spring alike: a
|
|
5545
5614
|
* burnt band settled into a saddle, its corners forward and its middle BACK
|
|
@@ -5552,11 +5621,22 @@ var DamageCoupling = class {
|
|
|
5552
5621
|
* gone by, the char behind it is uniform and says nothing about direction,
|
|
5553
5622
|
* but the paper stays rolled the way it rolled.
|
|
5554
5623
|
*/
|
|
5555
|
-
|
|
5624
|
+
acrossFront;
|
|
5625
|
+
/** The mesh's index as it was built — what {@link tear} hides triangles from and mends them back to. */
|
|
5626
|
+
original = null;
|
|
5627
|
+
/** Per triangle of that index: torn, and hidden until the history is rewound. */
|
|
5628
|
+
torn = new Uint8Array(0);
|
|
5629
|
+
/** The triangles with a gone corner — the only ones that can tear. */
|
|
5630
|
+
rim = [];
|
|
5631
|
+
rimStale = true;
|
|
5632
|
+
/** Every torn triangle has to be put back: the history was rewound, or the source went. */
|
|
5633
|
+
mend = false;
|
|
5556
5634
|
constructor(sim) {
|
|
5557
5635
|
this.sim = sim;
|
|
5558
5636
|
this.gone = new Uint8Array(sim.count);
|
|
5559
|
-
this.
|
|
5637
|
+
this.piece = new Int32Array(sim.count);
|
|
5638
|
+
this.parent = new Int32Array(sim.count);
|
|
5639
|
+
this.acrossFront = new Float32Array(sim.constraintCount);
|
|
5560
5640
|
}
|
|
5561
5641
|
/**
|
|
5562
5642
|
* Bring the sim's levers up to date with `source`. Call before stepping.
|
|
@@ -5567,7 +5647,13 @@ var DamageCoupling = class {
|
|
|
5567
5647
|
update(source) {
|
|
5568
5648
|
const next = source ?? null;
|
|
5569
5649
|
if (next === this.source && (next === null || next.version === this.version)) return false;
|
|
5570
|
-
if (next !== this.source)
|
|
5650
|
+
if (next !== this.source) {
|
|
5651
|
+
this.acrossFront.fill(0);
|
|
5652
|
+
if (next && this.holed) {
|
|
5653
|
+
this.rewind();
|
|
5654
|
+
this.gone.fill(0);
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
5571
5657
|
this.source = next;
|
|
5572
5658
|
if (!next) {
|
|
5573
5659
|
this.reset();
|
|
@@ -5587,12 +5673,13 @@ var DamageCoupling = class {
|
|
|
5587
5673
|
* laid out flat around them — their positions plus its rest offset from
|
|
5588
5674
|
* each, averaged. The triangles that straddle the burnt edge then keep
|
|
5589
5675
|
* roughly their shape instead of stretching back to wherever the particle
|
|
5590
|
-
* was when it burnt. Only
|
|
5591
|
-
*
|
|
5676
|
+
* was when it burnt. Only the neighbours in its own {@link piece} count, so
|
|
5677
|
+
* on a cut between two pieces it leaves with one of them.
|
|
5592
5678
|
*/
|
|
5593
5679
|
follow() {
|
|
5594
5680
|
if (this.followers.length === 0) return;
|
|
5595
5681
|
const { gone, sim } = this;
|
|
5682
|
+
const piece = this.piece;
|
|
5596
5683
|
const p = sim.positions;
|
|
5597
5684
|
const { cols, rows } = sim;
|
|
5598
5685
|
const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
|
|
@@ -5600,6 +5687,7 @@ var DamageCoupling = class {
|
|
|
5600
5687
|
for (const g of this.followers) {
|
|
5601
5688
|
const r = g / cols | 0;
|
|
5602
5689
|
const c = g % cols;
|
|
5690
|
+
const own = piece[g];
|
|
5603
5691
|
let sx = 0;
|
|
5604
5692
|
let sy = 0;
|
|
5605
5693
|
let sz = 0;
|
|
@@ -5611,7 +5699,7 @@ var DamageCoupling = class {
|
|
|
5611
5699
|
const nc = c + dc;
|
|
5612
5700
|
if (dr === 0 && dc === 0 || nc < 0 || nc >= cols) continue;
|
|
5613
5701
|
const j = nr * cols + nc;
|
|
5614
|
-
if (gone[j]) continue;
|
|
5702
|
+
if (gone[j] || piece[j] !== own) continue;
|
|
5615
5703
|
const j3 = j * 3;
|
|
5616
5704
|
sx += p[j3] - dc * cellX;
|
|
5617
5705
|
sy += p[j3 + 1] + dr * cellY;
|
|
@@ -5626,6 +5714,70 @@ var DamageCoupling = class {
|
|
|
5626
5714
|
p[g3 + 2] = sz / n;
|
|
5627
5715
|
}
|
|
5628
5716
|
}
|
|
5717
|
+
/**
|
|
5718
|
+
* Hide the triangles a piece has torn away from. Call after {@link follow},
|
|
5719
|
+
* with the sheet's index buffer: it is rewritten in place, and the return
|
|
5720
|
+
* says whether it changed, so the caller knows to upload it.
|
|
5721
|
+
*
|
|
5722
|
+
* A triangle tears when one of its edges has stretched past
|
|
5723
|
+
* {@link TEAR_STRETCH} — which, among triangles with a burnt-away corner,
|
|
5724
|
+
* only ever happens across a gap that is opening. It is then collapsed to a
|
|
5725
|
+
* point, which draws nothing, casts no shadow and adds nothing to its
|
|
5726
|
+
* corners' normals, and it stays hidden: torn paper does not mend because
|
|
5727
|
+
* the two sides swing close again. Only a rewound history puts it back.
|
|
5728
|
+
*
|
|
5729
|
+
* A sheet in one piece, held, never stretches one that far — so a burn that
|
|
5730
|
+
* cuts nothing loose leaves this index exactly as it was built.
|
|
5731
|
+
*/
|
|
5732
|
+
tear(index) {
|
|
5733
|
+
if (!this.holed && !this.mend) return false;
|
|
5734
|
+
if (!this.original || this.original.length !== index.length) {
|
|
5735
|
+
this.original = index.slice();
|
|
5736
|
+
this.torn = new Uint8Array(index.length / 3);
|
|
5737
|
+
this.rimStale = true;
|
|
5738
|
+
}
|
|
5739
|
+
const { original, torn, gone, sim } = this;
|
|
5740
|
+
let changed = false;
|
|
5741
|
+
if (this.mend) {
|
|
5742
|
+
this.mend = false;
|
|
5743
|
+
index.set(original);
|
|
5744
|
+
changed = true;
|
|
5745
|
+
}
|
|
5746
|
+
if (this.rimStale) {
|
|
5747
|
+
this.rimStale = false;
|
|
5748
|
+
const rim = [];
|
|
5749
|
+
for (let t = 0; t < torn.length; t++) {
|
|
5750
|
+
if (gone[original[t * 3]] || gone[original[t * 3 + 1]] || gone[original[t * 3 + 2]]) rim.push(t);
|
|
5751
|
+
}
|
|
5752
|
+
this.rim = rim;
|
|
5753
|
+
}
|
|
5754
|
+
const p = sim.positions;
|
|
5755
|
+
const { cols, rows } = sim;
|
|
5756
|
+
const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
|
|
5757
|
+
const cellY = rows > 1 ? sim.height / (rows - 1) : 0;
|
|
5758
|
+
const limit = TEAR_STRETCH * TEAR_STRETCH;
|
|
5759
|
+
const stretched = (i, j) => {
|
|
5760
|
+
const dc = i % cols - j % cols;
|
|
5761
|
+
const dr = (i / cols | 0) - (j / cols | 0);
|
|
5762
|
+
const rest = (dc * cellX) ** 2 + (dr * cellY) ** 2;
|
|
5763
|
+
const i3 = i * 3;
|
|
5764
|
+
const j3 = j * 3;
|
|
5765
|
+
const now = (p[i3] - p[j3]) ** 2 + (p[i3 + 1] - p[j3 + 1]) ** 2 + (p[i3 + 2] - p[j3 + 2]) ** 2;
|
|
5766
|
+
return now > rest * limit;
|
|
5767
|
+
};
|
|
5768
|
+
for (const t of this.rim) {
|
|
5769
|
+
if (torn[t]) continue;
|
|
5770
|
+
const a = original[t * 3];
|
|
5771
|
+
const b = original[t * 3 + 1];
|
|
5772
|
+
const c = original[t * 3 + 2];
|
|
5773
|
+
if (!stretched(a, b) && !stretched(b, c) && !stretched(c, a)) continue;
|
|
5774
|
+
torn[t] = 1;
|
|
5775
|
+
index[t * 3 + 1] = a;
|
|
5776
|
+
index[t * 3 + 2] = a;
|
|
5777
|
+
changed = true;
|
|
5778
|
+
}
|
|
5779
|
+
return changed;
|
|
5780
|
+
}
|
|
5629
5781
|
/** Map each particle to its texel. The damage grid's row 0 is v = 0, the sheet's BOTTOM. */
|
|
5630
5782
|
layout(size) {
|
|
5631
5783
|
const { cols, rows, count } = this.sim;
|
|
@@ -5641,8 +5793,10 @@ var DamageCoupling = class {
|
|
|
5641
5793
|
}
|
|
5642
5794
|
}
|
|
5643
5795
|
read(pixels) {
|
|
5644
|
-
const { sim, gone, particleTexel,
|
|
5796
|
+
const { sim, gone, particleTexel, acrossFront, size } = this;
|
|
5645
5797
|
const {
|
|
5798
|
+
draught,
|
|
5799
|
+
cols,
|
|
5646
5800
|
invMass,
|
|
5647
5801
|
restLength,
|
|
5648
5802
|
naturalLength,
|
|
@@ -5653,12 +5807,25 @@ var DamageCoupling = class {
|
|
|
5653
5807
|
constraintKind,
|
|
5654
5808
|
constraintMiddle
|
|
5655
5809
|
} = sim;
|
|
5810
|
+
let rewound = false;
|
|
5811
|
+
let holed = false;
|
|
5656
5812
|
for (let i = 0; i < sim.count; i++) {
|
|
5657
5813
|
const t = particleTexel[i] * 4;
|
|
5658
5814
|
const isGone = pixels[t + PRESENCE] < GONE_BELOW;
|
|
5815
|
+
if (gone[i] && !isGone) rewound = true;
|
|
5816
|
+
if (isGone) holed = true;
|
|
5659
5817
|
gone[i] = isGone ? 1 : 0;
|
|
5660
5818
|
invMass[i] = isGone ? 0 : 1 / (1 + WET_MASS * pixels[t + SATURATION] / 255);
|
|
5819
|
+
const heat = isGone ? 0 : pixels[t + HEAT] / 255;
|
|
5820
|
+
const lift = heat > 0.15 ? (heat - 0.15) / 0.85 : 0;
|
|
5821
|
+
const i3 = i * 3;
|
|
5822
|
+
draught[i3] = 0;
|
|
5823
|
+
draught[i3 + 1] = UPDRAFT * lift;
|
|
5824
|
+
draught[i3 + 2] = UPDRAFT * UPDRAFT_OUT * lift;
|
|
5661
5825
|
}
|
|
5826
|
+
if (rewound) this.rewind();
|
|
5827
|
+
this.holed = holed;
|
|
5828
|
+
this.rimStale = true;
|
|
5662
5829
|
for (let k = 0; k < sim.constraintCount; k++) {
|
|
5663
5830
|
const a = constraintA[k];
|
|
5664
5831
|
const b = constraintB[k];
|
|
@@ -5675,54 +5842,125 @@ var DamageCoupling = class {
|
|
|
5675
5842
|
samples = 3;
|
|
5676
5843
|
}
|
|
5677
5844
|
const char = charSum / (samples * 255);
|
|
5678
|
-
|
|
5845
|
+
const t = particleTexel[m >= 0 ? m : a];
|
|
5846
|
+
const x = t % size;
|
|
5847
|
+
const y = t / size | 0;
|
|
5848
|
+
const gx = pixels[(y * size + Math.min(size - 1, x + 2)) * 4 + CHAR] - pixels[(y * size + Math.max(0, x - 2)) * 4 + CHAR];
|
|
5849
|
+
const gy = pixels[(Math.min(size - 1, y + 2) * size + x) * 4 + CHAR] - pixels[(Math.max(0, y - 2) * size + x) * 4 + CHAR];
|
|
5850
|
+
const steep = gx * gx + gy * gy;
|
|
5851
|
+
if (steep > FRONT_GRADIENT) {
|
|
5852
|
+
const dc = b % cols - a % cols;
|
|
5853
|
+
const dr = (b / cols | 0) - (a / cols | 0);
|
|
5854
|
+
const along = dc * gx - dr * gy;
|
|
5855
|
+
const span = dc * dc + dr * dr || 1;
|
|
5856
|
+
const across2 = along * along / (span * steep);
|
|
5857
|
+
if (across2 > acrossFront[k]) acrossFront[k] = across2;
|
|
5858
|
+
}
|
|
5859
|
+
const across = acrossFront[k];
|
|
5860
|
+
restLength[k] = naturalLength[k] * (1 - CHAR_SHRINK * char * (1 - (1 - ACROSS_SHRINK) * across));
|
|
5679
5861
|
if (constraintKind[k] === 2) {
|
|
5680
|
-
const t = particleTexel[m];
|
|
5681
|
-
const x = t % size;
|
|
5682
|
-
const y = t / size | 0;
|
|
5683
|
-
const gx = pixels[(y * size + Math.min(size - 1, x + 2)) * 4 + CHAR] - pixels[(y * size + Math.max(0, x - 2)) * 4 + CHAR];
|
|
5684
|
-
const gy = pixels[(Math.min(size - 1, y + 2) * size + x) * 4 + CHAR] - pixels[(Math.max(0, y - 2) * size + x) * 4 + CHAR];
|
|
5685
|
-
const steep = gx * gx + gy * gy;
|
|
5686
|
-
if (steep > FRONT_GRADIENT) {
|
|
5687
|
-
const across = (b - a === 2 ? gx * gx : gy * gy) / steep;
|
|
5688
|
-
if (across > curlWeight[k]) curlWeight[k] = across;
|
|
5689
|
-
}
|
|
5690
5862
|
const span = naturalLength[k];
|
|
5691
|
-
restBend[k] = CHAR_CURL * char *
|
|
5863
|
+
restBend[k] = CHAR_CURL * char * across * span * span / 8;
|
|
5864
|
+
}
|
|
5865
|
+
}
|
|
5866
|
+
this.findPieces();
|
|
5867
|
+
}
|
|
5868
|
+
/**
|
|
5869
|
+
* Which piece each particle is in ({@link piece}), and which gone ones
|
|
5870
|
+
* follow a piece at all ({@link followers}).
|
|
5871
|
+
*
|
|
5872
|
+
* Pieces by union-find over the springs that still hold, rooted at the
|
|
5873
|
+
* lowest index so the same burn names the same pieces every time. Then each
|
|
5874
|
+
* gone particle with live paper beside it joins the piece it has the most
|
|
5875
|
+
* neighbours in, the lower-named on a tie.
|
|
5876
|
+
*/
|
|
5877
|
+
findPieces() {
|
|
5878
|
+
const { sim, gone } = this;
|
|
5879
|
+
const parent = this.parent;
|
|
5880
|
+
const piece = this.piece;
|
|
5881
|
+
const { broken, constraintA, constraintB } = sim;
|
|
5882
|
+
for (let i = 0; i < sim.count; i++) parent[i] = i;
|
|
5883
|
+
const find = (i) => {
|
|
5884
|
+
let r = i;
|
|
5885
|
+
while (parent[r] !== r) r = parent[r];
|
|
5886
|
+
while (parent[i] !== r) {
|
|
5887
|
+
const up = parent[i];
|
|
5888
|
+
parent[i] = r;
|
|
5889
|
+
i = up;
|
|
5692
5890
|
}
|
|
5891
|
+
return r;
|
|
5892
|
+
};
|
|
5893
|
+
for (let k = 0; k < sim.constraintCount; k++) {
|
|
5894
|
+
if (broken[k]) continue;
|
|
5895
|
+
const ra = find(constraintA[k]);
|
|
5896
|
+
const rb = find(constraintB[k]);
|
|
5897
|
+
if (ra < rb) parent[rb] = ra;
|
|
5898
|
+
else if (rb < ra) parent[ra] = rb;
|
|
5693
5899
|
}
|
|
5900
|
+
for (let i = 0; i < sim.count; i++) piece[i] = gone[i] ? -1 : find(i);
|
|
5694
5901
|
const followers = [];
|
|
5695
5902
|
const { cols, rows } = sim;
|
|
5903
|
+
const seen = [];
|
|
5904
|
+
const votes = [];
|
|
5696
5905
|
for (let i = 0; i < sim.count; i++) {
|
|
5697
5906
|
if (!gone[i]) continue;
|
|
5698
5907
|
const r = i / cols | 0;
|
|
5699
5908
|
const c = i % cols;
|
|
5700
|
-
|
|
5701
|
-
|
|
5909
|
+
seen.length = 0;
|
|
5910
|
+
votes.length = 0;
|
|
5911
|
+
for (let dr = -1; dr <= 1; dr++) {
|
|
5702
5912
|
const nr = r + dr;
|
|
5703
5913
|
if (nr < 0 || nr >= rows) continue;
|
|
5704
5914
|
for (let dc = -1; dc <= 1; dc++) {
|
|
5705
5915
|
const nc = c + dc;
|
|
5706
5916
|
if (nc < 0 || nc >= cols) continue;
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5917
|
+
const j = nr * cols + nc;
|
|
5918
|
+
if (gone[j]) continue;
|
|
5919
|
+
const at = seen.indexOf(piece[j]);
|
|
5920
|
+
if (at < 0) {
|
|
5921
|
+
seen.push(piece[j]);
|
|
5922
|
+
votes.push(1);
|
|
5923
|
+
} else {
|
|
5924
|
+
votes[at]++;
|
|
5710
5925
|
}
|
|
5711
5926
|
}
|
|
5712
5927
|
}
|
|
5713
|
-
if (
|
|
5928
|
+
if (seen.length === 0) continue;
|
|
5929
|
+
let best = 0;
|
|
5930
|
+
for (let k = 1; k < seen.length; k++) {
|
|
5931
|
+
if (votes[k] > votes[best] || votes[k] === votes[best] && seen[k] < seen[best]) best = k;
|
|
5932
|
+
}
|
|
5933
|
+
piece[i] = seen[best];
|
|
5934
|
+
followers.push(i);
|
|
5714
5935
|
}
|
|
5715
5936
|
this.followers = followers;
|
|
5716
5937
|
}
|
|
5938
|
+
/**
|
|
5939
|
+
* A rewound history: the sheet starts over.
|
|
5940
|
+
*
|
|
5941
|
+
* Paper that had fallen away is paper again, with every spring back to the
|
|
5942
|
+
* sheet it was cut from — solved from where it lies, the solve would haul it
|
|
5943
|
+
* up off the floor through the sheet in a tangle. And the curl it rolled
|
|
5944
|
+
* into belonged to a front that has not passed yet.
|
|
5945
|
+
*/
|
|
5946
|
+
rewind() {
|
|
5947
|
+
this.sim.restore();
|
|
5948
|
+
this.acrossFront.fill(0);
|
|
5949
|
+
this.torn.fill(0);
|
|
5950
|
+
this.mend = true;
|
|
5951
|
+
}
|
|
5717
5952
|
reset() {
|
|
5718
5953
|
const { sim } = this;
|
|
5954
|
+
if (this.holed) this.rewind();
|
|
5955
|
+
sim.draught.fill(0);
|
|
5719
5956
|
sim.invMass.fill(1);
|
|
5720
5957
|
sim.restBend.fill(0);
|
|
5721
5958
|
sim.broken.fill(0);
|
|
5722
5959
|
sim.restLength.set(sim.naturalLength);
|
|
5723
5960
|
this.gone.fill(0);
|
|
5724
|
-
this.
|
|
5961
|
+
this.acrossFront.fill(0);
|
|
5725
5962
|
this.followers = [];
|
|
5963
|
+
this.holed = false;
|
|
5726
5964
|
this.version = -1;
|
|
5727
5965
|
sim.wake();
|
|
5728
5966
|
}
|
|
@@ -5734,6 +5972,82 @@ import { useEffect as useEffect5, useMemo as useMemo3 } from "react";
|
|
|
5734
5972
|
import { useFrame as useFrame2 } from "@react-three/fiber";
|
|
5735
5973
|
import CustomShaderMaterial from "three-custom-shader-material";
|
|
5736
5974
|
|
|
5975
|
+
// src/surface/cutDistance.ts
|
|
5976
|
+
var CUT_REACH_MM = 16;
|
|
5977
|
+
var SPECK_TEXELS = 3;
|
|
5978
|
+
var GONE_BELOW2 = 128;
|
|
5979
|
+
var scratch = null;
|
|
5980
|
+
function cutDistance(pixels, size, texelMm, out) {
|
|
5981
|
+
const n = size * size;
|
|
5982
|
+
if (!scratch || scratch.size !== size) {
|
|
5983
|
+
scratch = { size, dist: new Float32Array(n), kept: new Uint8Array(n), stack: new Int32Array(n) };
|
|
5984
|
+
}
|
|
5985
|
+
const { dist, kept, stack } = scratch;
|
|
5986
|
+
const gone = (i) => pixels[i * 4 + 3] < GONE_BELOW2;
|
|
5987
|
+
kept.fill(0);
|
|
5988
|
+
const seen = dist;
|
|
5989
|
+
seen.fill(0);
|
|
5990
|
+
for (let start = 0; start < n; start++) {
|
|
5991
|
+
if (seen[start] || !gone(start)) continue;
|
|
5992
|
+
let top = 0;
|
|
5993
|
+
let count = 0;
|
|
5994
|
+
stack[top++] = start;
|
|
5995
|
+
seen[start] = 1;
|
|
5996
|
+
const first = top;
|
|
5997
|
+
let head = 0;
|
|
5998
|
+
while (head < top) {
|
|
5999
|
+
const i = stack[head++];
|
|
6000
|
+
count++;
|
|
6001
|
+
const x = i % size;
|
|
6002
|
+
const y = i / size | 0;
|
|
6003
|
+
const next = [
|
|
6004
|
+
x > 0 ? i - 1 : -1,
|
|
6005
|
+
x < size - 1 ? i + 1 : -1,
|
|
6006
|
+
y > 0 ? i - size : -1,
|
|
6007
|
+
y < size - 1 ? i + size : -1
|
|
6008
|
+
];
|
|
6009
|
+
for (const j of next) {
|
|
6010
|
+
if (j < 0 || seen[j] || !gone(j)) continue;
|
|
6011
|
+
seen[j] = 1;
|
|
6012
|
+
stack[top++] = j;
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
if (count > SPECK_TEXELS) for (let k = first - 1; k < top; k++) kept[stack[k]] = 1;
|
|
6016
|
+
}
|
|
6017
|
+
const [dx, dy] = texelMm;
|
|
6018
|
+
const dd = Math.hypot(dx, dy);
|
|
6019
|
+
for (let i = 0; i < n; i++) dist[i] = kept[i] ? 0 : Number.POSITIVE_INFINITY;
|
|
6020
|
+
for (let y = 0; y < size; y++) {
|
|
6021
|
+
for (let x = 0; x < size; x++) {
|
|
6022
|
+
const i = y * size + x;
|
|
6023
|
+
let v = dist[i];
|
|
6024
|
+
if (v === 0) continue;
|
|
6025
|
+
if (x > 0) v = Math.min(v, dist[i - 1] + dx);
|
|
6026
|
+
if (y > 0) {
|
|
6027
|
+
v = Math.min(v, dist[i - size] + dy);
|
|
6028
|
+
if (x > 0) v = Math.min(v, dist[i - size - 1] + dd);
|
|
6029
|
+
if (x < size - 1) v = Math.min(v, dist[i - size + 1] + dd);
|
|
6030
|
+
}
|
|
6031
|
+
dist[i] = v;
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
for (let y = size - 1; y >= 0; y--) {
|
|
6035
|
+
for (let x = size - 1; x >= 0; x--) {
|
|
6036
|
+
const i = y * size + x;
|
|
6037
|
+
let v = dist[i];
|
|
6038
|
+
if (v === 0) continue;
|
|
6039
|
+
if (x < size - 1) v = Math.min(v, dist[i + 1] + dx);
|
|
6040
|
+
if (y < size - 1) {
|
|
6041
|
+
v = Math.min(v, dist[i + size] + dy);
|
|
6042
|
+
if (x < size - 1) v = Math.min(v, dist[i + size + 1] + dd);
|
|
6043
|
+
if (x > 0) v = Math.min(v, dist[i + size - 1] + dd);
|
|
6044
|
+
}
|
|
6045
|
+
dist[i] = v;
|
|
6046
|
+
}
|
|
6047
|
+
}
|
|
6048
|
+
for (let i = 0; i < n; i++) out[i] = Math.round(Math.min(dist[i], CUT_REACH_MM) / CUT_REACH_MM * 255);
|
|
6049
|
+
}
|
|
6050
|
+
|
|
5737
6051
|
// src/surface/compose.ts
|
|
5738
6052
|
import * as THREE5 from "three";
|
|
5739
6053
|
|
|
@@ -6149,7 +6463,7 @@ var DAMAGE_CHUNK = (
|
|
|
6149
6463
|
`
|
|
6150
6464
|
uniform sampler2D uDamage;
|
|
6151
6465
|
uniform float uDamageDetail;
|
|
6152
|
-
// (edgeWave mm, edgeBite mm, sparkle,
|
|
6466
|
+
// (edgeWave mm, edgeBite mm, sparkle, charWidth mm) \u2014 see DamageLook.
|
|
6153
6467
|
uniform vec4 uLook3;
|
|
6154
6468
|
|
|
6155
6469
|
// The damage grid's texels sit ON the sheet \u2014 texel x at u = x / (N - 1), its
|
|
@@ -6286,6 +6600,8 @@ var DAMAGE_SHADE_CHUNK = (
|
|
|
6286
6600
|
/* glsl */
|
|
6287
6601
|
`
|
|
6288
6602
|
uniform float uDamageTime;
|
|
6603
|
+
// How far each texel is from the cut, as a fraction of PL_CUT_REACH \u2014 see cutDistance.
|
|
6604
|
+
uniform sampler2D uDamageEdge;
|
|
6289
6605
|
// The look, packed \u2014 see DamageLook for what each number means.
|
|
6290
6606
|
uniform vec4 uLook0; // emberWidth mm, emberIntensity, emberCoverage, emberFlicker
|
|
6291
6607
|
uniform vec4 uLook1; // emberGlow, lipWidth mm, lipBrightness, charWarmth
|
|
@@ -6295,6 +6611,10 @@ vec3 plDamageLight;
|
|
|
6295
6611
|
|
|
6296
6612
|
// World units per millimetre: a default sheet is one unit across, and A4 is 210 mm.
|
|
6297
6613
|
const float PL_MM = 1.0 / 210.0;
|
|
6614
|
+
// How far out a burnt edge's zones can be measured from the cut, mm: past
|
|
6615
|
+
// the widest lip, ember line, char and scorch the look allows by default.
|
|
6616
|
+
// The same number cutDistance encodes with, so the two cannot drift.
|
|
6617
|
+
const float PL_CUT_REACH = ${CUT_REACH_MM.toFixed(1)};
|
|
6298
6618
|
|
|
6299
6619
|
vec3 plLinear(vec3 srgb) {
|
|
6300
6620
|
return pow(srgb, vec3(2.2));
|
|
@@ -6351,7 +6671,12 @@ vec3 plScorchTint(float t) {
|
|
|
6351
6671
|
vec3 c3 = plLinear(vec3(0.541, 0.310, 0.141)); // #8A4F24
|
|
6352
6672
|
vec3 c4 = plLinear(vec3(0.471, 0.275, 0.149)); // #784626
|
|
6353
6673
|
vec3 c5 = plLinear(vec3(0.380, 0.216, 0.133)); // #613722
|
|
6354
|
-
|
|
6674
|
+
// The dark end hands over to the char's black: a nearly neutral umber, not
|
|
6675
|
+
// the saturated brown (#4F3323) it used to stop at. Beside black char that
|
|
6676
|
+
// brown showed through wherever the band thinned, and turned the black
|
|
6677
|
+
// brown \u2014 measured, the cold char's saturation went from 0.43 to 0.11 with
|
|
6678
|
+
// the scorch's darkening switched off.
|
|
6679
|
+
vec3 c6 = plLinear(vec3(0.169, 0.133, 0.110)); // #2B221C
|
|
6355
6680
|
float s = clamp(t, 0.0, 1.0) * 6.0;
|
|
6356
6681
|
// Each stop is blended with a SMOOTH step, not a linear one.
|
|
6357
6682
|
//
|
|
@@ -6402,32 +6727,6 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6402
6727
|
vec2 perWorld = (vec2(textureSize(uDamage, 0)) - 1.0) / uSheetSize;
|
|
6403
6728
|
vec2 gradP = vec2(e.a - w.a, n.a - s.a) * 0.5 * perWorld;
|
|
6404
6729
|
vec2 gradC = vec2(e.r - w.r, n.r - s.r) * 0.5 * perWorld;
|
|
6405
|
-
// Millimetres from the cut, on the paper side \u2014 measured against the field
|
|
6406
|
-
// the cut is DRAWN from (softened, frayed, warped), through its own screen
|
|
6407
|
-
// derivatives. It used to divide by the slope of the raw texels, and since
|
|
6408
|
-
// paper burns through within a texel that slope is a cliff while the drawn
|
|
6409
|
-
// edge is spread over millimetres: a point well into the char computed as a
|
|
6410
|
-
// hair from the cut, and the ember line drew there \u2014 found by the gate.
|
|
6411
|
-
// The slope, though, is the SMOOTH presence's. The fray's fibre noise
|
|
6412
|
-
// changes pixel to pixel, and its slope is a cliff everywhere it touches:
|
|
6413
|
-
// divided by that, a point deep in the char came out a hair from the cut
|
|
6414
|
-
// and the ember glow drew there \u2014 the gate's stray pixels. The fray still
|
|
6415
|
-
// moves where the edge is; it just cannot fake how steep it is.
|
|
6416
|
-
float smoothA = plDamageSmooth;
|
|
6417
|
-
vec2 cutSlope = vec2(dFdx(smoothA), dFdy(smoothA));
|
|
6418
|
-
float pxFromCut = (d.a - 0.5) / max(length(cutSlope), 1e-4);
|
|
6419
|
-
// Millimetres per pixel ALONG the way to the cut, not across the screen's
|
|
6420
|
-
// x: a sheet leaning away is foreshortened vertically, so above and below a
|
|
6421
|
-
// hole a pixel covers more paper than beside it, and a scale taken from x
|
|
6422
|
-
// alone drew the ember zone too many pixels deep on the top and bottom rims.
|
|
6423
|
-
vec2 toCutPx = cutSlope / max(length(cutSlope), 1e-6);
|
|
6424
|
-
float mmPerPx = length(dFdx(-vViewPosition) * toCutPx.x + dFdy(-vViewPosition) * toCutPx.y) / PL_MM;
|
|
6425
|
-
float mm = pxFromCut * mmPerPx;
|
|
6426
|
-
// About a pixel of antialiasing, never more: where the warp makes the
|
|
6427
|
-
// estimate jump between neighbours, fwidth(mm) is millimetres wide, and the
|
|
6428
|
-
// ember's soft tail \u2014 faint, but multiplied by its light \u2014 drew stray glow
|
|
6429
|
-
// well out into the char.
|
|
6430
|
-
float aa = min(fwidth(mm), 1.5 * mmPerPx);
|
|
6431
6730
|
// Scorch reaches ahead of the char, and further UP than anywhere else:
|
|
6432
6731
|
// the char a centimetre below this point, and the char a few millimetres
|
|
6433
6732
|
// around it, each lent to the scorch at a discount (\xA74.2, \xA74.5).
|
|
@@ -6441,38 +6740,61 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6441
6740
|
charWide += texture2D(uDamage, uv + vec2(cos(a), sin(a)) * 7.0 * mmUv).r;
|
|
6442
6741
|
}
|
|
6443
6742
|
charWide /= 8.0;
|
|
6444
|
-
//
|
|
6445
|
-
//
|
|
6446
|
-
//
|
|
6447
|
-
//
|
|
6448
|
-
//
|
|
6449
|
-
//
|
|
6450
|
-
|
|
6451
|
-
//
|
|
6452
|
-
//
|
|
6453
|
-
//
|
|
6454
|
-
//
|
|
6455
|
-
//
|
|
6456
|
-
//
|
|
6457
|
-
//
|
|
6458
|
-
//
|
|
6459
|
-
vec2
|
|
6460
|
-
|
|
6461
|
-
float
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
//
|
|
6465
|
-
float
|
|
6466
|
-
|
|
6743
|
+
// \u2500\u2500 The cut, measured \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6744
|
+
// How far this point is from the cut, in millimetres, out to PL_CUT_REACH \u2014
|
|
6745
|
+
// the one measure every zone of a burnt edge is laid out on: the ash lip,
|
|
6746
|
+
// the ember line, the char and the scorch (Ember_line annotated). Worked
|
|
6747
|
+
// out on the CPU once per change of the field (cutDistance), where a speck
|
|
6748
|
+
// of burnt-through paper can be told from a cut: measured here per pixel,
|
|
6749
|
+
// every speck near the rim grew its own lip, char and scorch, and the bands
|
|
6750
|
+
// came apart into blotches.
|
|
6751
|
+
//
|
|
6752
|
+
// Burnt AWAY, not charred: the black and the lip belong to paper beside a
|
|
6753
|
+
// cut, so a burn toasts before it blackens.
|
|
6754
|
+
//
|
|
6755
|
+
// Read through the same warp as the cut, so the zones follow the edge that
|
|
6756
|
+
// is drawn. Bilinear between a gone texel and the paper beside it, the
|
|
6757
|
+
// drawn edge reads half a texel, which comes back off.
|
|
6758
|
+
vec2 edgeTexel = 1.0 / vec2(textureSize(uDamageEdge, 0));
|
|
6759
|
+
vec2 edgeWorld = uSheetSize / (vec2(textureSize(uDamageEdge, 0)) - 1.0);
|
|
6760
|
+
float edgeRaw = texture2D(uDamageEdge, uv).r;
|
|
6761
|
+
float edgeHalf = 0.5 * min(edgeWorld.x, edgeWorld.y) / PL_MM;
|
|
6762
|
+
float xCut = max(edgeRaw * PL_CUT_REACH - edgeHalf, 0.0);
|
|
6763
|
+
// A pixel or so of antialiasing, measured on the same measure.
|
|
6764
|
+
float aa = clamp(fwidth(xCut), 0.05, 1.0);
|
|
6765
|
+
// Which way the cut lies, from how the distance falls \u2014 and along the edge,
|
|
6766
|
+
// across that. In the sheet's own space, so noise laid along it follows the
|
|
6767
|
+
// rim wherever the rim goes.
|
|
6768
|
+
vec2 toward = -vec2(
|
|
6769
|
+
(texture2D(uDamageEdge, uv + vec2(edgeTexel.x, 0.0)).r - texture2D(uDamageEdge, uv - vec2(edgeTexel.x, 0.0)).r) / edgeWorld.x,
|
|
6770
|
+
(texture2D(uDamageEdge, uv + vec2(0.0, edgeTexel.y)).r - texture2D(uDamageEdge, uv - vec2(0.0, edgeTexel.y)).r) / edgeWorld.y
|
|
6771
|
+
);
|
|
6772
|
+
vec2 tangentR = length(toward) > 1e-4 ? normalize(vec2(-toward.y, toward.x)) : vec2(1.0, 0.0);
|
|
6773
|
+
// Paper with a cut within reach, eased out toward the reach.
|
|
6774
|
+
float cutZone = 1.0 - smoothstep(PL_CUT_REACH - 2.0, PL_CUT_REACH - 0.5, xCut);
|
|
6467
6775
|
|
|
6468
6776
|
// An untouched texel is left exactly as it was \u2014 the identity the whole
|
|
6469
6777
|
// seam is built on. Nothing here has happened to this paper.
|
|
6470
|
-
if (d.r <= 0.0 && d.g <= 0.0 && d.a >= 1.0 && charBelow <= 0.0 && charWide <= 0.0 && e.a >= 1.0 && w.a >= 1.0 && n.a >= 1.0 && s.a >= 1.0) {
|
|
6778
|
+
if (d.r <= 0.0 && d.g <= 0.0 && d.a >= 1.0 && charBelow <= 0.0 && charWide <= 0.0 && edgeRaw >= 1.0 && e.a >= 1.0 && w.a >= 1.0 && n.a >= 1.0 && s.a >= 1.0) {
|
|
6471
6779
|
return;
|
|
6472
6780
|
}
|
|
6473
6781
|
|
|
6474
6782
|
vec2 p = plLocal();
|
|
6475
6783
|
|
|
6784
|
+
// The zones, out from the cut: the ash lip, the ember line on its outer
|
|
6785
|
+
// edge, the char, then the scorch \u2014 each as wide as the look says, and
|
|
6786
|
+
// uneven along the rim so none of them is a ribbon. The lip's unevenness
|
|
6787
|
+
// stays inside the zone the ember gate allows it (lip plus bead).
|
|
6788
|
+
float lipEdge = uLook1.y * mix(0.7, 1.1, plFbm(vec2(dot(p, tangentR) / (5.0 * PL_MM), 1.7)));
|
|
6789
|
+
float charEnd = lipEdge + uLook0.x + uLook3.w * mix(0.8, 1.2, plFbm(vec2(dot(p, tangentR) / (7.0 * PL_MM), 4.3)));
|
|
6790
|
+
// Further above the burn than below it \u2014 hot gas rises and cooks the paper
|
|
6791
|
+
// over it (\xA74.5) \u2014 out to the scorch's full reach straight up, and about
|
|
6792
|
+
// half of it below and beside.
|
|
6793
|
+
vec2 upMm = normalize(upUv * uSheetSize);
|
|
6794
|
+
float above = length(toward) > 1e-3 ? max(0.0, dot(-normalize(toward), upMm)) : 0.0;
|
|
6795
|
+
float scorchEnd = charEnd + uLook2.y * mix(0.45, 1.0, above)
|
|
6796
|
+
+ (plFbm(vec2(dot(p, tangentR) / (4.0 * PL_MM), 3.1)) - 0.5) * uLook2.w * 2.5;
|
|
6797
|
+
|
|
6476
6798
|
// Wet paper is darker and smoother: water fills the gaps between fibres
|
|
6477
6799
|
// that scatter light, which is both effects from one cause.
|
|
6478
6800
|
color.rgb *= 1.0 - 0.38 * d.g;
|
|
@@ -6524,27 +6846,64 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6524
6846
|
// being finer than a level it can only ever move a value into the
|
|
6525
6847
|
// neighbouring one.
|
|
6526
6848
|
c = clamp(c + (plNoise(p * 900.0) - 0.5) * (1.5 / 255.0), 0.0, 1.0);
|
|
6849
|
+
// The colour INSIDE the scorch reads a smoothed c: the same char and the
|
|
6850
|
+
// same reaches, without the fingers. The fingers belong to the front's
|
|
6851
|
+
// silhouette (lead, below); carried into the ramp as well, a strong fingers
|
|
6852
|
+
// setting swung the scorch between tan and near-black umber a few
|
|
6853
|
+
// millimetres apart \u2014 a leopard print, not a toast.
|
|
6854
|
+
float cSmooth = 1.0 - (1.0 - clamp(mix(d.r, soft, 0.65), 0.0, 1.0)) * (1.0 - clamp(0.62 * charBelow * 0.75, 0.0, 1.0)) * (1.0 - clamp(0.4 * charWide * 0.75, 0.0, 1.0));
|
|
6855
|
+
cSmooth = clamp(cSmooth + ((plNoise(p * 300.0) - 0.5) * 0.05 + (plNoise(p * 90.0) - 0.5) * 0.04) * smoothstep(0.0, 0.1, cSmooth), 0.0, 1.0);
|
|
6527
6856
|
// A steep leading edge \u2014 paper to straw in a sliver \u2014 then the long ramp.
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6857
|
+
// The front's teeth from the noisy c; filled in behind them from the smooth
|
|
6858
|
+
// one, so a dip in the noise cannot open a patch of clean paper inside it.
|
|
6859
|
+
float lead = max(smoothstep(0.02, 0.05, c), smoothstep(0.1, 0.2, cSmooth));
|
|
6860
|
+
// The browns are done by 0.45, where the char's umber hand-over takes them:
|
|
6861
|
+
// the ramp used to run on to 0.7, over the char, and most of what read as
|
|
6862
|
+
// "char" was this ramp's darkest brown.
|
|
6863
|
+
float ramp = smoothstep(0.05, 0.45, cSmooth);
|
|
6864
|
+
// That toast is for paper with no cut near it \u2014 the first stage of a burn,
|
|
6865
|
+
// before anything has gone through.
|
|
6866
|
+
color.rgb *= mix(vec3(1.0), pow(plScorchTint(ramp), vec3(uLook2.z)), lead * (1.0 - cutZone));
|
|
6867
|
+
// Round a cut, the scorch is a band of its own: dark, only a little lighter
|
|
6868
|
+
// than the char it leads from, a shade warmer toward its outer edge, then a
|
|
6869
|
+
// steep leading edge into clean paper (Ember_line annotated, zone 5: "steep
|
|
6870
|
+
// leading edge \xB7 albedo only"). A black band beside a pale tan one was the
|
|
6871
|
+
// contrast that read as wrong.
|
|
6872
|
+
float scorchT = smoothstep(charEnd, scorchEnd, xCut);
|
|
6873
|
+
vec3 scorchTint = mix(plLinear(vec3(0.24, 0.17, 0.12)), plLinear(vec3(0.42, 0.28, 0.17)), scorchT) / plLinear(vec3(0.90, 0.885, 0.855));
|
|
6874
|
+
float scorchZone = (1.0 - smoothstep(scorchEnd - 0.35, scorchEnd + 0.35, xCut)) * cutZone;
|
|
6875
|
+
color.rgb *= mix(vec3(1.0), pow(min(scorchTint, vec3(1.0)), vec3(uLook2.z)), scorchZone);
|
|
6531
6876
|
|
|
6532
6877
|
// \u2500\u2500 Char \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6533
6878
|
// A wide, smooth hand-over from scorch to char \u2014 the gradient is the point.
|
|
6534
|
-
|
|
6879
|
+
// From 0.3: the field carries char in about one texel beside the cut, so
|
|
6880
|
+
// past a couple of millimetres c is mostly the scorch borrowed from below,
|
|
6881
|
+
// and a zone that began at 0.5 was hardly ever char at all.
|
|
6882
|
+
// Away from a cut, c only makes char where it is really high: at 0.3\u20130.6
|
|
6883
|
+
// the finger noise pushed it across the line all through the scorch, and
|
|
6884
|
+
// every crossing was a black island in the orange \u2014 a leopard print.
|
|
6885
|
+
// Round a cut, the band from the lip out to charEnd; away from one, only
|
|
6886
|
+
// where the toast has gone all the way to char.
|
|
6887
|
+
float charBand = (1.0 - smoothstep(charEnd - 0.5, charEnd + 0.5, xCut)) * cutZone;
|
|
6888
|
+
float charZone = max(smoothstep(0.6, 0.85, cSmooth) * (1.0 - cutZone), charBand);
|
|
6535
6889
|
vec2 cells = plVoronoi(p / (3.6 * PL_MM));
|
|
6536
6890
|
// Sparse: a real crack network is broken, not a tiled floor \u2014 most cell
|
|
6537
6891
|
// borders never split.
|
|
6538
6892
|
float crack = (1.0 - smoothstep(0.008, 0.03, cells.y - cells.x)) * smoothstep(0.45, 0.65, plNoise(p / (7.0 * PL_MM)));
|
|
6539
6893
|
float plate = plHash(floor(p / (3.6 * PL_MM)) + 3.7);
|
|
6540
|
-
//
|
|
6541
|
-
//
|
|
6542
|
-
//
|
|
6543
|
-
//
|
|
6544
|
-
|
|
6545
|
-
|
|
6894
|
+
// Black. Real char is a near-neutral black \u2014 (22, 20, 18) on the cold
|
|
6895
|
+
// macro references, about 11% of the paper's value. The warm brown this
|
|
6896
|
+
// used to be was Hero.png's char LIT BY ITS FLAMES, painted into the paper,
|
|
6897
|
+
// which is why ours stayed orange on a frame with no fire in it. Warmth on
|
|
6898
|
+
// char comes from the fire's light now, and goes when the fire does.
|
|
6899
|
+
// charWarmth adds only a trace of brown to the black, and a warmer umber
|
|
6900
|
+
// where the char hands over to the scorch.
|
|
6901
|
+
vec3 body = mix(plLinear(vec3(0.086, 0.078, 0.071)), plLinear(vec3(0.11, 0.08, 0.06)), uLook1.w);
|
|
6902
|
+
vec3 under = mix(plLinear(vec3(0.20, 0.15, 0.11)), plLinear(vec3(0.28, 0.16, 0.08)), uLook1.w);
|
|
6546
6903
|
vec3 cracks = plLinear(vec3(0.039, 0.024, 0.016)); // #0A0604
|
|
6547
|
-
|
|
6904
|
+
// Black through the band; the umber only in its outer millimetre.
|
|
6905
|
+
float toUnder = mix(1.0 - smoothstep(0.45, 0.8, cSmooth), smoothstep(charEnd - 1.2, charEnd + 0.3, xCut), cutZone);
|
|
6906
|
+
vec3 charColor = mix(body, under, toUnder);
|
|
6548
6907
|
// Mottled at the plate scale and finer, and darker than the sampled body
|
|
6549
6908
|
// under a bright key \u2014 the reference's char reads near black in the frame.
|
|
6550
6909
|
charColor *= (0.75 + 0.35 * plate) * (0.8 + 0.4 * plNoise(p * 260.0));
|
|
@@ -6556,42 +6915,42 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6556
6915
|
plHeight += charZone * ((plNoise(p * 380.0) - 0.5) * 0.00008 + (plate - 0.5) * 0.00018 - crack * 0.00016);
|
|
6557
6916
|
|
|
6558
6917
|
// \u2500\u2500 Ash lip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6559
|
-
//
|
|
6560
|
-
//
|
|
6561
|
-
//
|
|
6562
|
-
//
|
|
6563
|
-
//
|
|
6564
|
-
//
|
|
6565
|
-
//
|
|
6566
|
-
//
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
float
|
|
6570
|
-
//
|
|
6571
|
-
//
|
|
6572
|
-
|
|
6573
|
-
// middle of it, which read as a neon tube round the hole \u2014 the single most
|
|
6574
|
-
// synthetic thing at wide view.
|
|
6575
|
-
float lipAlong = dot(p, tangentP) / (3.2 * PL_MM);
|
|
6576
|
-
float lipBreak = smoothstep(0.34, 0.56, plFbm(vec2(lipAlong, 0.9)));
|
|
6577
|
-
float lip = (1.0 - smoothstep(lipWidth - aa, lipWidth + aa, mm)) * step(d.a, 0.999) * cutNear * lipBreak;
|
|
6578
|
-
vec3 ash = plLinear(mix(vec3(0.525, 0.498, 0.490), vec3(0.741, 0.725, 0.725), plNoise(p * 520.0) * 0.6 + plNoise(p * 90.0) * 0.4)) * uLook1.z;
|
|
6918
|
+
// A crust as wide as the char band, from the cut to lipEdge \u2014 the pale
|
|
6919
|
+
// thing that makes a hole read as a hole on a black stage (Ember_line
|
|
6920
|
+
// annotated, zone 2). It used to be held to about a millimetre, which at a
|
|
6921
|
+
// reading distance is a hairline tracing the edge, not ash.
|
|
6922
|
+
//
|
|
6923
|
+
// Broken, not a hem: ash flakes off a cooling edge in pieces, so the lip
|
|
6924
|
+
// comes and goes along the rim and wanders in width. Mostly there, though \u2014
|
|
6925
|
+
// it has to be seen.
|
|
6926
|
+
float lipAlong = dot(p, tangentR) / (3.2 * PL_MM);
|
|
6927
|
+
float lipBreak = smoothstep(0.1, 0.3, plFbm(vec2(lipAlong, 0.9)));
|
|
6928
|
+
float lip = (1.0 - smoothstep(lipEdge - aa, lipEdge + aa, xCut)) * cutZone * lipBreak;
|
|
6929
|
+
// Pale: the reference's #A49E9D up to a near-white grey, so it reads white
|
|
6930
|
+
// beside the char. Still matte and still dimmer than the paper.
|
|
6931
|
+
vec3 ash = plLinear(mix(vec3(0.643, 0.620, 0.616), vec3(0.84, 0.82, 0.81), plNoise(p * 520.0) * 0.6 + plNoise(p * 90.0) * 0.4)) * uLook1.z;
|
|
6579
6932
|
color.rgb = mix(color.rgb, ash, lip);
|
|
6580
6933
|
roughness = mix(roughness, 0.99, lip);
|
|
6581
|
-
//
|
|
6582
|
-
//
|
|
6583
|
-
//
|
|
6584
|
-
//
|
|
6585
|
-
// of
|
|
6586
|
-
//
|
|
6587
|
-
|
|
6934
|
+
// And the crust the ragged edge is made of. The cut is frayed at fibre
|
|
6935
|
+
// scale, and the fray opened specks of void all through the first few
|
|
6936
|
+
// millimetres \u2014 exactly where the lip lies \u2014 so at a reading distance the
|
|
6937
|
+
// lip was grey specks in black. Paper inside the fray's band is kept, as
|
|
6938
|
+
// ash, instead of cut away: the lip overhangs the hole a little, broken
|
|
6939
|
+
// where the lip is broken, the way the reference's crust does.
|
|
6940
|
+
float crust = lipBreak * cutZone * smoothstep(0.18, 0.3, d.a) * (1.0 - step(0.5, d.a));
|
|
6941
|
+
color.rgb = mix(color.rgb, ash, crust);
|
|
6942
|
+
roughness = mix(roughness, 0.99, crust);
|
|
6943
|
+
// Raised, and crumbly rather than rounded: a smooth ridge round the hole is
|
|
6944
|
+
// a cylinder, and a cylinder under a key light has a specular line down it
|
|
6945
|
+
// however rough it is \u2014 the neon tube this once was.
|
|
6946
|
+
plHeight += lip * (0.55 + 0.45 * plNoise(p * 700.0)) * 0.00016;
|
|
6588
6947
|
|
|
6589
6948
|
// \u2500\u2500 The ember line \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6590
6949
|
// Beads along the cut: position along the edge, crawling on the burn's
|
|
6591
6950
|
// clock, with a flicker of their own. Only where it is hot \u2014 and as the
|
|
6592
6951
|
// heat goes the threshold rises, so the line breaks into fewer, dimmer
|
|
6593
6952
|
// beads and they go out one by one.
|
|
6594
|
-
float s1 = dot(p,
|
|
6953
|
+
float s1 = dot(p, tangentR) / (2.4 * PL_MM) - uDamageTime * 0.55 * uLook0.w;
|
|
6595
6954
|
float bead = plNoise(vec2(s1, 0.37)) * 0.7 + plNoise(vec2(s1 * 2.3, 5.1)) * 0.3;
|
|
6596
6955
|
float flicker = 0.62 + 0.38 * plNoise(vec2(floor(s1) * 7.13, uDamageTime * 9.0 * uLook0.w));
|
|
6597
6956
|
// Down to a smoulder's last flicker: a spent edge keeps a little heat in
|
|
@@ -6606,7 +6965,8 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6606
6965
|
float lit = smoothstep(mix(0.82, 0.45, hot) - shift, mix(0.9, 0.58, hot) - shift, bead);
|
|
6607
6966
|
// Each bead tapers at its ends \u2014 a bead, not a dash cut from a strip.
|
|
6608
6967
|
float width = max(mix(0.4, max(uLook0.x, 0.41), plNoise(vec2(s1 * 0.7, 9.0))) * (0.35 + 0.65 * lit), 1.4 * aa);
|
|
6609
|
-
|
|
6968
|
+
// On the lip's outer edge, between the ash and the char.
|
|
6969
|
+
float band = smoothstep(lipEdge - aa, lipEdge + aa, xCut) * (1.0 - smoothstep(lipEdge + width - aa, lipEdge + width + aa, xCut)) * cutZone;
|
|
6610
6970
|
// Mostly orange: a bead is red at its ends and yellow-white only at its
|
|
6611
6971
|
// hottest core, which is what Ember_line.png shows. Mapped low and pushed
|
|
6612
6972
|
// high by a power, so white is the exception rather than the line.
|
|
@@ -6626,13 +6986,15 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6626
6986
|
// edge is hot, and inside the zone the beads are held to \u2014 the lip plus
|
|
6627
6987
|
// the widest bead, measured from the cut \u2014 so the glow never reaches out
|
|
6628
6988
|
// over paper the gate says heat must not light.
|
|
6629
|
-
float zoneEnd =
|
|
6630
|
-
float seam = smoothstep(
|
|
6989
|
+
float zoneEnd = lipEdge + uLook0.x;
|
|
6990
|
+
float seam = smoothstep(lipEdge - aa, lipEdge + aa, xCut) * (1.0 - smoothstep(zoneEnd - 0.6, zoneEnd, xCut)) * cutZone;
|
|
6631
6991
|
float patch_ = smoothstep(0.35, 0.72, plFbm(p / (4.0 * PL_MM) + vec2(uDamageTime * 0.15 * uLook0.w, 0.0)));
|
|
6632
6992
|
plDamageLight += vec3(0.55, 0.06, 0.01) * seam * patch_ * hot * uLook1.x;
|
|
6633
6993
|
float speck = step(0.985, plHash(floor(p / (0.35 * PL_MM)) + floor(uDamageTime * 8.0 * uLook0.w)));
|
|
6634
6994
|
plDamageLight += plEmberRamp(0.9) * 1.6 * speck * seam * hot * uLook3.z;
|
|
6635
6995
|
|
|
6996
|
+
// The crust stays, however the fray cut it.
|
|
6997
|
+
d.a = mix(d.a, max(d.a, 0.5), step(0.5, crust));
|
|
6636
6998
|
plDamageCut(color, d);
|
|
6637
6999
|
}
|
|
6638
7000
|
`
|
|
@@ -6743,6 +7105,7 @@ function composeSurface(surface, stock, thickness, maps = { hasFrontMap: false,
|
|
|
6743
7105
|
chunks.push(DAMAGE_CHUNK, DAMAGE_SHADE_CHUNK);
|
|
6744
7106
|
calls.push("plDamage(csm_DiffuseColor, csm_Roughness);");
|
|
6745
7107
|
uniforms.uDamage = { value: null };
|
|
7108
|
+
uniforms.uDamageEdge = { value: null };
|
|
6746
7109
|
uniforms.uDamageDetail = { value: 1 };
|
|
6747
7110
|
uniforms.uDamageTime = { value: 0 };
|
|
6748
7111
|
const look = DAMAGE_LOOK_DEFAULTS;
|
|
@@ -6755,7 +7118,7 @@ function composeSurface(surface, stock, thickness, maps = { hasFrontMap: false,
|
|
|
6755
7118
|
uniforms.uLook2 = {
|
|
6756
7119
|
value: new THREE5.Vector4(look.charCracks, look.scorchReach, look.scorchDarkness, look.fingers)
|
|
6757
7120
|
};
|
|
6758
|
-
uniforms.uLook3 = { value: new THREE5.Vector4(look.edgeWave, look.edgeBite, look.sparkle,
|
|
7121
|
+
uniforms.uLook3 = { value: new THREE5.Vector4(look.edgeWave, look.edgeBite, look.sparkle, look.charWidth) };
|
|
6759
7122
|
}
|
|
6760
7123
|
const cutting = [];
|
|
6761
7124
|
const cuts = [];
|
|
@@ -6848,35 +7211,65 @@ function pad(values, fill = 0) {
|
|
|
6848
7211
|
import * as THREE6 from "three";
|
|
6849
7212
|
import { useFrame } from "@react-three/fiber";
|
|
6850
7213
|
import { useEffect as useEffect4, useMemo as useMemo2 } from "react";
|
|
6851
|
-
|
|
6852
|
-
|
|
7214
|
+
var MM_PER_WORLD = 210;
|
|
7215
|
+
function useDamageTexture(source, sheet2 = { width: 1, height: 1.4 }) {
|
|
7216
|
+
const { width, height } = sheet2;
|
|
7217
|
+
const textures = useMemo2(() => {
|
|
6853
7218
|
if (!source) return null;
|
|
6854
|
-
const
|
|
7219
|
+
const damage = new THREE6.DataTexture(
|
|
6855
7220
|
source.pixels,
|
|
6856
7221
|
source.size,
|
|
6857
7222
|
source.size,
|
|
6858
7223
|
THREE6.RGBAFormat,
|
|
6859
7224
|
THREE6.UnsignedByteType
|
|
6860
7225
|
);
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
t
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
7226
|
+
const edge = new THREE6.DataTexture(
|
|
7227
|
+
new Uint8Array(source.size * source.size),
|
|
7228
|
+
source.size,
|
|
7229
|
+
source.size,
|
|
7230
|
+
THREE6.RedFormat,
|
|
7231
|
+
THREE6.UnsignedByteType
|
|
7232
|
+
);
|
|
7233
|
+
for (const t of [damage, edge]) {
|
|
7234
|
+
t.magFilter = THREE6.LinearFilter;
|
|
7235
|
+
t.minFilter = THREE6.LinearFilter;
|
|
7236
|
+
t.wrapS = THREE6.ClampToEdgeWrapping;
|
|
7237
|
+
t.wrapT = THREE6.ClampToEdgeWrapping;
|
|
7238
|
+
t.generateMipmaps = false;
|
|
7239
|
+
t.colorSpace = THREE6.NoColorSpace;
|
|
7240
|
+
t.flipY = false;
|
|
7241
|
+
t.unpackAlignment = 1;
|
|
7242
|
+
}
|
|
7243
|
+
measureEdge(source, edge, width, height);
|
|
7244
|
+
damage.userData.version = source.version;
|
|
7245
|
+
damage.needsUpdate = true;
|
|
7246
|
+
return { damage, edge };
|
|
7247
|
+
}, [source, width, height]);
|
|
7248
|
+
useEffect4(
|
|
7249
|
+
() => () => {
|
|
7250
|
+
textures?.damage.dispose();
|
|
7251
|
+
textures?.edge.dispose();
|
|
7252
|
+
},
|
|
7253
|
+
[textures]
|
|
7254
|
+
);
|
|
6873
7255
|
useFrame(() => {
|
|
6874
|
-
if (!source || !
|
|
6875
|
-
if (
|
|
6876
|
-
|
|
6877
|
-
|
|
7256
|
+
if (!source || !textures) return;
|
|
7257
|
+
if (textures.damage.userData.version === source.version) return;
|
|
7258
|
+
textures.damage.userData.version = source.version;
|
|
7259
|
+
textures.damage.needsUpdate = true;
|
|
7260
|
+
measureEdge(source, textures.edge, width, height);
|
|
6878
7261
|
});
|
|
6879
|
-
return
|
|
7262
|
+
return textures;
|
|
7263
|
+
}
|
|
7264
|
+
function measureEdge(source, edge, width, height) {
|
|
7265
|
+
const last = Math.max(1, source.size - 1);
|
|
7266
|
+
cutDistance(
|
|
7267
|
+
source.pixels,
|
|
7268
|
+
source.size,
|
|
7269
|
+
[width * MM_PER_WORLD / last, height * MM_PER_WORLD / last],
|
|
7270
|
+
edge.image.data
|
|
7271
|
+
);
|
|
7272
|
+
edge.needsUpdate = true;
|
|
6880
7273
|
}
|
|
6881
7274
|
|
|
6882
7275
|
// src/surface/PaperMaterial.tsx
|
|
@@ -6893,7 +7286,7 @@ function PaperMaterial({
|
|
|
6893
7286
|
damage
|
|
6894
7287
|
}) {
|
|
6895
7288
|
const rig = useLightRig(lighting);
|
|
6896
|
-
const
|
|
7289
|
+
const damageTextures = useDamageTexture(damage, sheet2);
|
|
6897
7290
|
const composed = composeSurface(
|
|
6898
7291
|
surface,
|
|
6899
7292
|
stock,
|
|
@@ -6901,7 +7294,7 @@ function PaperMaterial({
|
|
|
6901
7294
|
{
|
|
6902
7295
|
hasFrontMap: Boolean(texture),
|
|
6903
7296
|
hasBackMap: Boolean(backTexture),
|
|
6904
|
-
hasDamage: Boolean(
|
|
7297
|
+
hasDamage: Boolean(damageTextures)
|
|
6905
7298
|
},
|
|
6906
7299
|
sheet2,
|
|
6907
7300
|
rig,
|
|
@@ -6910,7 +7303,7 @@ function PaperMaterial({
|
|
|
6910
7303
|
const bound = useMemo3(() => composed.uniforms, [composed.structureKey]);
|
|
6911
7304
|
useEffect5(() => {
|
|
6912
7305
|
for (const [key, uniform] of Object.entries(composed.uniforms)) {
|
|
6913
|
-
if (!bound[key] || key === "uFrontMap" || key === "uBackMap" || key === "uDamage" || key === "uDamageDetail" || key === "uDamageTime" || key.startsWith("uLook")) {
|
|
7306
|
+
if (!bound[key] || key === "uFrontMap" || key === "uBackMap" || key === "uDamage" || key === "uDamageEdge" || key === "uDamageDetail" || key === "uDamageTime" || key.startsWith("uLook")) {
|
|
6914
7307
|
continue;
|
|
6915
7308
|
}
|
|
6916
7309
|
if (bound[key].value instanceof THREE7.Color && uniform.value instanceof THREE7.Color) {
|
|
@@ -6924,8 +7317,9 @@ function PaperMaterial({
|
|
|
6924
7317
|
useEffect5(() => {
|
|
6925
7318
|
if (bound.uFrontMap) bound.uFrontMap.value = texture;
|
|
6926
7319
|
if (bound.uBackMap) bound.uBackMap.value = backTexture ?? null;
|
|
6927
|
-
if (bound.uDamage) bound.uDamage.value =
|
|
6928
|
-
|
|
7320
|
+
if (bound.uDamage) bound.uDamage.value = damageTextures?.damage ?? null;
|
|
7321
|
+
if (bound.uDamageEdge) bound.uDamageEdge.value = damageTextures?.edge ?? null;
|
|
7322
|
+
}, [bound, texture, backTexture, damageTextures]);
|
|
6929
7323
|
useFrame2((state) => {
|
|
6930
7324
|
if (!bound.uDamageDetail) return;
|
|
6931
7325
|
if (bound.uDamageTime) {
|
|
@@ -6958,7 +7352,7 @@ function PaperMaterial({
|
|
|
6958
7352
|
v(look.edgeWave, d.edgeWave),
|
|
6959
7353
|
v(look.edgeBite, d.edgeBite),
|
|
6960
7354
|
v(look.sparkle, d.sparkle),
|
|
6961
|
-
|
|
7355
|
+
v(look.charWidth, d.charWidth)
|
|
6962
7356
|
);
|
|
6963
7357
|
}
|
|
6964
7358
|
const detail = damage?.detail ?? 1;
|
|
@@ -7392,7 +7786,11 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
7392
7786
|
coupling?.update(props.damage);
|
|
7393
7787
|
sim.step(delta);
|
|
7394
7788
|
const moved = !sim.asleep;
|
|
7395
|
-
if (moved
|
|
7789
|
+
if (moved && coupling) {
|
|
7790
|
+
coupling.follow();
|
|
7791
|
+
const index = geometry.index;
|
|
7792
|
+
if (index && coupling.tear(index.array)) index.needsUpdate = true;
|
|
7793
|
+
}
|
|
7396
7794
|
if (!applyShape(sim.positions, moved) && moved) {
|
|
7397
7795
|
const position = geometry.attributes.position;
|
|
7398
7796
|
position.array.set(sim.positions);
|
|
@@ -7792,7 +8190,8 @@ function PaperLighting({
|
|
|
7792
8190
|
reducedMotion,
|
|
7793
8191
|
shadowMapSize,
|
|
7794
8192
|
contactShadow = true,
|
|
7795
|
-
environment = true
|
|
8193
|
+
environment = true,
|
|
8194
|
+
damage
|
|
7796
8195
|
}) {
|
|
7797
8196
|
const p = useMemo5(() => rig ?? resolveLighting(preset, light), [rig, preset, light]);
|
|
7798
8197
|
const mapSize = shadowMapSize ?? p.shadow.mapSize;
|
|
@@ -7826,6 +8225,20 @@ function PaperLighting({
|
|
|
7826
8225
|
driftRef.current += delta * p.gobo.drift;
|
|
7827
8226
|
goboMap.offset.set(driftRef.current, driftRef.current * 0.6);
|
|
7828
8227
|
});
|
|
8228
|
+
const keyIntensity = p.gobo && goboMap ? p.key.intensity * 3.2 : p.key.intensity;
|
|
8229
|
+
const keyRef = useRef4(null);
|
|
8230
|
+
const ambientRef = useRef4(null);
|
|
8231
|
+
const hemisphereRef = useRef4(null);
|
|
8232
|
+
const roomRef = useRef4(1);
|
|
8233
|
+
useFrame4(() => {
|
|
8234
|
+
const room = roomLight(damage);
|
|
8235
|
+
if (room === 1 && roomRef.current === 1) return;
|
|
8236
|
+
roomRef.current = room;
|
|
8237
|
+
if (keyRef.current) keyRef.current.intensity = keyIntensity * room;
|
|
8238
|
+
if (ambientRef.current) ambientRef.current.intensity = p.ambient * room;
|
|
8239
|
+
if (hemisphereRef.current) hemisphereRef.current.intensity = p.studio * HEMISPHERE_STAND_IN * room;
|
|
8240
|
+
if (p.studio > 0 && environment) scene.environmentIntensity = p.studio * room;
|
|
8241
|
+
});
|
|
7829
8242
|
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
7830
8243
|
p.studio > 0 && (environment ? /* @__PURE__ */ jsx5(StudioLight, { rig: p }) : (
|
|
7831
8244
|
// The studio light degrades rather than disappearing. A hemisphere
|
|
@@ -7836,19 +8249,21 @@ function PaperLighting({
|
|
|
7836
8249
|
/* @__PURE__ */ jsx5(
|
|
7837
8250
|
"hemisphereLight",
|
|
7838
8251
|
{
|
|
8252
|
+
ref: hemisphereRef,
|
|
7839
8253
|
color: p.sky.horizon,
|
|
7840
8254
|
groundColor: p.sky.ground,
|
|
7841
8255
|
intensity: p.studio * HEMISPHERE_STAND_IN
|
|
7842
8256
|
}
|
|
7843
8257
|
)
|
|
7844
8258
|
)),
|
|
7845
|
-
/* @__PURE__ */ jsx5("ambientLight", { intensity: p.ambient }),
|
|
8259
|
+
/* @__PURE__ */ jsx5("ambientLight", { ref: ambientRef, intensity: p.ambient }),
|
|
7846
8260
|
p.gobo && goboMap ? /* @__PURE__ */ jsx5(
|
|
7847
8261
|
"spotLight",
|
|
7848
8262
|
{
|
|
8263
|
+
ref: keyRef,
|
|
7849
8264
|
position: p.key.position,
|
|
7850
8265
|
color: p.key.color,
|
|
7851
|
-
intensity:
|
|
8266
|
+
intensity: keyIntensity,
|
|
7852
8267
|
angle: p.gobo.angle,
|
|
7853
8268
|
penumbra: 0.5,
|
|
7854
8269
|
decay: 0,
|
|
@@ -7861,9 +8276,10 @@ function PaperLighting({
|
|
|
7861
8276
|
) : /* @__PURE__ */ jsx5(
|
|
7862
8277
|
"directionalLight",
|
|
7863
8278
|
{
|
|
8279
|
+
ref: keyRef,
|
|
7864
8280
|
position: p.key.position,
|
|
7865
8281
|
color: p.key.color,
|
|
7866
|
-
intensity:
|
|
8282
|
+
intensity: keyIntensity,
|
|
7867
8283
|
castShadow,
|
|
7868
8284
|
"shadow-mapSize": [mapSize || 1, mapSize || 1],
|
|
7869
8285
|
"shadow-radius": p.shadow.radius,
|
|
@@ -10220,4 +10636,4 @@ export {
|
|
|
10220
10636
|
describeConfig,
|
|
10221
10637
|
buildAgentPayload
|
|
10222
10638
|
};
|
|
10223
|
-
//# sourceMappingURL=chunk-
|
|
10639
|
+
//# sourceMappingURL=chunk-SVWWC5UL.js.map
|