paperlab 0.7.1 → 0.8.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/README.md +57 -28
- package/dist/{FxPostPass-M66QMVVC.js → FxPostPass-ACFSHY3S.js} +24 -6
- package/dist/FxPostPass-ACFSHY3S.js.map +1 -0
- package/dist/chunk-BIFUJCUA.js +34 -0
- package/dist/chunk-BIFUJCUA.js.map +1 -0
- package/dist/{chunk-NTDVOOBA.js → chunk-LUKNPQ3S.js} +385 -165
- package/dist/chunk-LUKNPQ3S.js.map +1 -0
- package/dist/{chunk-GB6BMHC3.js → chunk-S74YZAXE.js} +7 -19
- package/dist/chunk-S74YZAXE.js.map +1 -0
- package/dist/{damageContract-DPF1YFLZ.d.cts → damageContract-9RC4egJg.d.cts} +31 -3
- package/dist/{damageContract-DPF1YFLZ.d.ts → damageContract-9RC4egJg.d.ts} +31 -3
- package/dist/fx.cjs +336 -107
- package/dist/fx.cjs.map +1 -1
- package/dist/fx.d.cts +113 -89
- package/dist/fx.d.ts +113 -89
- package/dist/fx.js +293 -63
- package/dist/fx.js.map +1 -1
- package/dist/index.cjs +422 -193
- 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 +21 -8
- package/dist/index.js.map +1 -1
- package/dist/{slots-C5PqY0Q5.d.cts → slots-DbJQfwVd.d.cts} +13 -1
- package/dist/{slots-C5PqY0Q5.d.ts → slots-DbJQfwVd.d.ts} +13 -1
- package/dist/stage.cjs +417 -201
- 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 +12 -12
- package/dist/stage.js.map +1 -1
- package/package.json +5 -5
- 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-GB6BMHC3.js.map +0 -1
- package/dist/chunk-NTDVOOBA.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-BIFUJCUA.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
|
/**
|
|
@@ -1533,7 +1549,7 @@ var paperConfigSchema = z15.object({
|
|
|
1533
1549
|
ctx.addIssue({
|
|
1534
1550
|
code: z15.ZodIssueCode.custom,
|
|
1535
1551
|
path: ["physics"],
|
|
1536
|
-
message: "the strip simulation and behavior/deformers
|
|
1552
|
+
message: "the strip simulation and behavior/deformers cannot be used together. The roll owns the vertices, and its rows are chain nodes rather than the sheet's own grid (pick Shape OR Strip)"
|
|
1537
1553
|
});
|
|
1538
1554
|
}
|
|
1539
1555
|
if (config.states) {
|
|
@@ -1555,7 +1571,7 @@ var paperConfigSchema = z15.object({
|
|
|
1555
1571
|
ctx.addIssue({
|
|
1556
1572
|
code: z15.ZodIssueCode.custom,
|
|
1557
1573
|
path: ["states", "states", name, "overrides"],
|
|
1558
|
-
message: `state "${name}" overrides don't validate against the paper schema: ${first ? `${first.path.join(".")}
|
|
1574
|
+
message: `state "${name}" overrides don't validate against the paper schema: ${first ? `${first.path.join(".")}: ${first.message}` : "invalid"}`
|
|
1559
1575
|
});
|
|
1560
1576
|
}
|
|
1561
1577
|
}
|
|
@@ -2146,7 +2162,7 @@ function getPreset(name) {
|
|
|
2146
2162
|
}
|
|
2147
2163
|
function registerPreset(name, input) {
|
|
2148
2164
|
if (name in builtins) {
|
|
2149
|
-
throw new Error(`[paperlab] "${name}" is a built-in preset
|
|
2165
|
+
throw new Error(`[paperlab] "${name}" is a built-in preset. Pick another name.`);
|
|
2150
2166
|
}
|
|
2151
2167
|
paperConfigSchema.parse(input);
|
|
2152
2168
|
userPresets.set(name, input);
|
|
@@ -2695,7 +2711,7 @@ function resolveDeformerStack(raw) {
|
|
|
2695
2711
|
if (!parsed.success) {
|
|
2696
2712
|
const issue = parsed.error.issues[0];
|
|
2697
2713
|
throw new Error(
|
|
2698
|
-
`[paperlab] deformers[${i}] ("${instance.type}"): ${issue ? `${issue.path.join(".") || "options"}
|
|
2714
|
+
`[paperlab] deformers[${i}] ("${instance.type}"): ${issue ? `${issue.path.join(".") || "options"}: ${issue.message}` : "invalid options"}`
|
|
2699
2715
|
);
|
|
2700
2716
|
}
|
|
2701
2717
|
return {
|
|
@@ -5013,6 +5029,16 @@ var ClothSim = class {
|
|
|
5013
5029
|
* cannot do this job: it moves paper, and a uniformly curled sheet in still
|
|
5014
5030
|
* air has no business going anywhere.
|
|
5015
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;
|
|
5016
5042
|
restBend;
|
|
5017
5043
|
/** For each bend spring, the particle between its ends; -1 for every other kind. */
|
|
5018
5044
|
constraintMiddle;
|
|
@@ -5099,6 +5125,7 @@ var ClothSim = class {
|
|
|
5099
5125
|
this.constraintStiffness = new Float64Array(m).fill(1);
|
|
5100
5126
|
this.broken = new Uint8Array(m);
|
|
5101
5127
|
this.restBend = new Float64Array(m);
|
|
5128
|
+
this.draught = new Float64Array(this.count * 3);
|
|
5102
5129
|
this.constraintMiddle = new Int32Array(m).fill(-1);
|
|
5103
5130
|
for (let k = 0; k < m; k++) {
|
|
5104
5131
|
const a = links[k * 3];
|
|
@@ -5437,9 +5464,9 @@ var ClothSim = class {
|
|
|
5437
5464
|
this.prev[i3] = x;
|
|
5438
5465
|
this.prev[i3 + 1] = y;
|
|
5439
5466
|
this.prev[i3 + 2] = z25;
|
|
5440
|
-
p[i3] = x + vx + ax * im * dt2;
|
|
5441
|
-
p[i3 + 1] = y + vy + (ay * im - gravity * 3.2) * dt2;
|
|
5442
|
-
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;
|
|
5443
5470
|
maxTravel = Math.max(maxTravel, vx * vx + vy * vy + vz * vz);
|
|
5444
5471
|
}
|
|
5445
5472
|
for (let k = 0; k < this.grabbed.length; k++) {
|
|
@@ -5538,9 +5565,12 @@ var ClothSim = class {
|
|
|
5538
5565
|
};
|
|
5539
5566
|
|
|
5540
5567
|
// src/physics/damage.ts
|
|
5541
|
-
var { char: CHAR, saturation: SATURATION, presence: PRESENCE } = DAMAGE_CHANNELS;
|
|
5568
|
+
var { char: CHAR, saturation: SATURATION, heat: HEAT, presence: PRESENCE } = DAMAGE_CHANNELS;
|
|
5542
5569
|
var GONE_BELOW = 128;
|
|
5543
5570
|
var CHAR_SHRINK = 0.12;
|
|
5571
|
+
var ACROSS_SHRINK = 0.55;
|
|
5572
|
+
var UPDRAFT = 1.1;
|
|
5573
|
+
var UPDRAFT_OUT = 0.35;
|
|
5544
5574
|
var CHAR_CURL = 6;
|
|
5545
5575
|
var FRONT_GRADIENT = 16 * 16;
|
|
5546
5576
|
var WET_MASS = 2;
|
|
@@ -5573,9 +5603,12 @@ var DamageCoupling = class {
|
|
|
5573
5603
|
/** Union-find over the particles, reused on every read. */
|
|
5574
5604
|
parent;
|
|
5575
5605
|
/**
|
|
5576
|
-
* Per
|
|
5606
|
+
* Per spring, how squarely it lies ACROSS the burn front, 0..1 — and
|
|
5577
5607
|
* remembered once it has been.
|
|
5578
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
|
+
*
|
|
5579
5612
|
* A burnt edge rolls about an axis along the front, like a scroll; it does
|
|
5580
5613
|
* not dish into a bowl. Measured with the curl in every bend spring alike: a
|
|
5581
5614
|
* burnt band settled into a saddle, its corners forward and its middle BACK
|
|
@@ -5588,7 +5621,7 @@ var DamageCoupling = class {
|
|
|
5588
5621
|
* gone by, the char behind it is uniform and says nothing about direction,
|
|
5589
5622
|
* but the paper stays rolled the way it rolled.
|
|
5590
5623
|
*/
|
|
5591
|
-
|
|
5624
|
+
acrossFront;
|
|
5592
5625
|
/** The mesh's index as it was built — what {@link tear} hides triangles from and mends them back to. */
|
|
5593
5626
|
original = null;
|
|
5594
5627
|
/** Per triangle of that index: torn, and hidden until the history is rewound. */
|
|
@@ -5603,7 +5636,7 @@ var DamageCoupling = class {
|
|
|
5603
5636
|
this.gone = new Uint8Array(sim.count);
|
|
5604
5637
|
this.piece = new Int32Array(sim.count);
|
|
5605
5638
|
this.parent = new Int32Array(sim.count);
|
|
5606
|
-
this.
|
|
5639
|
+
this.acrossFront = new Float32Array(sim.constraintCount);
|
|
5607
5640
|
}
|
|
5608
5641
|
/**
|
|
5609
5642
|
* Bring the sim's levers up to date with `source`. Call before stepping.
|
|
@@ -5615,7 +5648,7 @@ var DamageCoupling = class {
|
|
|
5615
5648
|
const next = source ?? null;
|
|
5616
5649
|
if (next === this.source && (next === null || next.version === this.version)) return false;
|
|
5617
5650
|
if (next !== this.source) {
|
|
5618
|
-
this.
|
|
5651
|
+
this.acrossFront.fill(0);
|
|
5619
5652
|
if (next && this.holed) {
|
|
5620
5653
|
this.rewind();
|
|
5621
5654
|
this.gone.fill(0);
|
|
@@ -5760,8 +5793,10 @@ var DamageCoupling = class {
|
|
|
5760
5793
|
}
|
|
5761
5794
|
}
|
|
5762
5795
|
read(pixels) {
|
|
5763
|
-
const { sim, gone, particleTexel,
|
|
5796
|
+
const { sim, gone, particleTexel, acrossFront, size } = this;
|
|
5764
5797
|
const {
|
|
5798
|
+
draught,
|
|
5799
|
+
cols,
|
|
5765
5800
|
invMass,
|
|
5766
5801
|
restLength,
|
|
5767
5802
|
naturalLength,
|
|
@@ -5781,6 +5816,12 @@ var DamageCoupling = class {
|
|
|
5781
5816
|
if (isGone) holed = true;
|
|
5782
5817
|
gone[i] = isGone ? 1 : 0;
|
|
5783
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;
|
|
5784
5825
|
}
|
|
5785
5826
|
if (rewound) this.rewind();
|
|
5786
5827
|
this.holed = holed;
|
|
@@ -5801,20 +5842,25 @@ var DamageCoupling = class {
|
|
|
5801
5842
|
samples = 3;
|
|
5802
5843
|
}
|
|
5803
5844
|
const char = charSum / (samples * 255);
|
|
5804
|
-
|
|
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));
|
|
5805
5861
|
if (constraintKind[k] === 2) {
|
|
5806
|
-
const t = particleTexel[m];
|
|
5807
|
-
const x = t % size;
|
|
5808
|
-
const y = t / size | 0;
|
|
5809
|
-
const gx = pixels[(y * size + Math.min(size - 1, x + 2)) * 4 + CHAR] - pixels[(y * size + Math.max(0, x - 2)) * 4 + CHAR];
|
|
5810
|
-
const gy = pixels[(Math.min(size - 1, y + 2) * size + x) * 4 + CHAR] - pixels[(Math.max(0, y - 2) * size + x) * 4 + CHAR];
|
|
5811
|
-
const steep = gx * gx + gy * gy;
|
|
5812
|
-
if (steep > FRONT_GRADIENT) {
|
|
5813
|
-
const across = (b - a === 2 ? gx * gx : gy * gy) / steep;
|
|
5814
|
-
if (across > curlWeight[k]) curlWeight[k] = across;
|
|
5815
|
-
}
|
|
5816
5862
|
const span = naturalLength[k];
|
|
5817
|
-
restBend[k] = CHAR_CURL * char *
|
|
5863
|
+
restBend[k] = CHAR_CURL * char * across * span * span / 8;
|
|
5818
5864
|
}
|
|
5819
5865
|
}
|
|
5820
5866
|
this.findPieces();
|
|
@@ -5899,19 +5945,20 @@ var DamageCoupling = class {
|
|
|
5899
5945
|
*/
|
|
5900
5946
|
rewind() {
|
|
5901
5947
|
this.sim.restore();
|
|
5902
|
-
this.
|
|
5948
|
+
this.acrossFront.fill(0);
|
|
5903
5949
|
this.torn.fill(0);
|
|
5904
5950
|
this.mend = true;
|
|
5905
5951
|
}
|
|
5906
5952
|
reset() {
|
|
5907
5953
|
const { sim } = this;
|
|
5908
5954
|
if (this.holed) this.rewind();
|
|
5955
|
+
sim.draught.fill(0);
|
|
5909
5956
|
sim.invMass.fill(1);
|
|
5910
5957
|
sim.restBend.fill(0);
|
|
5911
5958
|
sim.broken.fill(0);
|
|
5912
5959
|
sim.restLength.set(sim.naturalLength);
|
|
5913
5960
|
this.gone.fill(0);
|
|
5914
|
-
this.
|
|
5961
|
+
this.acrossFront.fill(0);
|
|
5915
5962
|
this.followers = [];
|
|
5916
5963
|
this.holed = false;
|
|
5917
5964
|
this.version = -1;
|
|
@@ -5925,6 +5972,82 @@ import { useEffect as useEffect5, useMemo as useMemo3 } from "react";
|
|
|
5925
5972
|
import { useFrame as useFrame2 } from "@react-three/fiber";
|
|
5926
5973
|
import CustomShaderMaterial from "three-custom-shader-material";
|
|
5927
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
|
+
|
|
5928
6051
|
// src/surface/compose.ts
|
|
5929
6052
|
import * as THREE5 from "three";
|
|
5930
6053
|
|
|
@@ -6340,7 +6463,7 @@ var DAMAGE_CHUNK = (
|
|
|
6340
6463
|
`
|
|
6341
6464
|
uniform sampler2D uDamage;
|
|
6342
6465
|
uniform float uDamageDetail;
|
|
6343
|
-
// (edgeWave mm, edgeBite mm, sparkle,
|
|
6466
|
+
// (edgeWave mm, edgeBite mm, sparkle, charWidth mm) \u2014 see DamageLook.
|
|
6344
6467
|
uniform vec4 uLook3;
|
|
6345
6468
|
|
|
6346
6469
|
// The damage grid's texels sit ON the sheet \u2014 texel x at u = x / (N - 1), its
|
|
@@ -6477,6 +6600,8 @@ var DAMAGE_SHADE_CHUNK = (
|
|
|
6477
6600
|
/* glsl */
|
|
6478
6601
|
`
|
|
6479
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;
|
|
6480
6605
|
// The look, packed \u2014 see DamageLook for what each number means.
|
|
6481
6606
|
uniform vec4 uLook0; // emberWidth mm, emberIntensity, emberCoverage, emberFlicker
|
|
6482
6607
|
uniform vec4 uLook1; // emberGlow, lipWidth mm, lipBrightness, charWarmth
|
|
@@ -6486,6 +6611,10 @@ vec3 plDamageLight;
|
|
|
6486
6611
|
|
|
6487
6612
|
// World units per millimetre: a default sheet is one unit across, and A4 is 210 mm.
|
|
6488
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)};
|
|
6489
6618
|
|
|
6490
6619
|
vec3 plLinear(vec3 srgb) {
|
|
6491
6620
|
return pow(srgb, vec3(2.2));
|
|
@@ -6542,7 +6671,12 @@ vec3 plScorchTint(float t) {
|
|
|
6542
6671
|
vec3 c3 = plLinear(vec3(0.541, 0.310, 0.141)); // #8A4F24
|
|
6543
6672
|
vec3 c4 = plLinear(vec3(0.471, 0.275, 0.149)); // #784626
|
|
6544
6673
|
vec3 c5 = plLinear(vec3(0.380, 0.216, 0.133)); // #613722
|
|
6545
|
-
|
|
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
|
|
6546
6680
|
float s = clamp(t, 0.0, 1.0) * 6.0;
|
|
6547
6681
|
// Each stop is blended with a SMOOTH step, not a linear one.
|
|
6548
6682
|
//
|
|
@@ -6565,7 +6699,7 @@ vec3 plScorchTint(float t) {
|
|
|
6565
6699
|
return min(c / paper, vec3(1.0));
|
|
6566
6700
|
}
|
|
6567
6701
|
|
|
6568
|
-
/** The ember ramp
|
|
6702
|
+
/** The ember ramp: off \u2192 #870E03 \u2192 #A12108 \u2192 #F77E14 \u2192 #FEFBE0, hottest last. */
|
|
6569
6703
|
vec3 plEmberRamp(float t) {
|
|
6570
6704
|
vec3 c0 = plLinear(vec3(0.529, 0.055, 0.012));
|
|
6571
6705
|
vec3 c1 = plLinear(vec3(0.631, 0.129, 0.031));
|
|
@@ -6593,35 +6727,9 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6593
6727
|
vec2 perWorld = (vec2(textureSize(uDamage, 0)) - 1.0) / uSheetSize;
|
|
6594
6728
|
vec2 gradP = vec2(e.a - w.a, n.a - s.a) * 0.5 * perWorld;
|
|
6595
6729
|
vec2 gradC = vec2(e.r - w.r, n.r - s.r) * 0.5 * perWorld;
|
|
6596
|
-
// Millimetres from the cut, on the paper side \u2014 measured against the field
|
|
6597
|
-
// the cut is DRAWN from (softened, frayed, warped), through its own screen
|
|
6598
|
-
// derivatives. It used to divide by the slope of the raw texels, and since
|
|
6599
|
-
// paper burns through within a texel that slope is a cliff while the drawn
|
|
6600
|
-
// edge is spread over millimetres: a point well into the char computed as a
|
|
6601
|
-
// hair from the cut, and the ember line drew there \u2014 found by the gate.
|
|
6602
|
-
// The slope, though, is the SMOOTH presence's. The fray's fibre noise
|
|
6603
|
-
// changes pixel to pixel, and its slope is a cliff everywhere it touches:
|
|
6604
|
-
// divided by that, a point deep in the char came out a hair from the cut
|
|
6605
|
-
// and the ember glow drew there \u2014 the gate's stray pixels. The fray still
|
|
6606
|
-
// moves where the edge is; it just cannot fake how steep it is.
|
|
6607
|
-
float smoothA = plDamageSmooth;
|
|
6608
|
-
vec2 cutSlope = vec2(dFdx(smoothA), dFdy(smoothA));
|
|
6609
|
-
float pxFromCut = (d.a - 0.5) / max(length(cutSlope), 1e-4);
|
|
6610
|
-
// Millimetres per pixel ALONG the way to the cut, not across the screen's
|
|
6611
|
-
// x: a sheet leaning away is foreshortened vertically, so above and below a
|
|
6612
|
-
// hole a pixel covers more paper than beside it, and a scale taken from x
|
|
6613
|
-
// alone drew the ember zone too many pixels deep on the top and bottom rims.
|
|
6614
|
-
vec2 toCutPx = cutSlope / max(length(cutSlope), 1e-6);
|
|
6615
|
-
float mmPerPx = length(dFdx(-vViewPosition) * toCutPx.x + dFdy(-vViewPosition) * toCutPx.y) / PL_MM;
|
|
6616
|
-
float mm = pxFromCut * mmPerPx;
|
|
6617
|
-
// About a pixel of antialiasing, never more: where the warp makes the
|
|
6618
|
-
// estimate jump between neighbours, fwidth(mm) is millimetres wide, and the
|
|
6619
|
-
// ember's soft tail \u2014 faint, but multiplied by its light \u2014 drew stray glow
|
|
6620
|
-
// well out into the char.
|
|
6621
|
-
float aa = min(fwidth(mm), 1.5 * mmPerPx);
|
|
6622
6730
|
// Scorch reaches ahead of the char, and further UP than anywhere else:
|
|
6623
6731
|
// the char a centimetre below this point, and the char a few millimetres
|
|
6624
|
-
// around it, each lent to the scorch at a discount
|
|
6732
|
+
// around it, each lent to the scorch at a discount.
|
|
6625
6733
|
vec2 mmUv = vec2(PL_MM) / uSheetSize;
|
|
6626
6734
|
float charBelow = max(texture2D(uDamage, uv - upUv * uLook2.y * mmUv).r, texture2D(uDamage, uv - upUv * 0.5 * uLook2.y * mmUv).r);
|
|
6627
6735
|
// A ring of eight, averaged: four diagonal maxima drew the grid back in as
|
|
@@ -6632,38 +6740,61 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6632
6740
|
charWide += texture2D(uDamage, uv + vec2(cos(a), sin(a)) * 7.0 * mmUv).r;
|
|
6633
6741
|
}
|
|
6634
6742
|
charWide /= 8.0;
|
|
6635
|
-
//
|
|
6636
|
-
//
|
|
6637
|
-
//
|
|
6638
|
-
//
|
|
6639
|
-
//
|
|
6640
|
-
//
|
|
6641
|
-
|
|
6642
|
-
//
|
|
6643
|
-
//
|
|
6644
|
-
//
|
|
6645
|
-
//
|
|
6646
|
-
//
|
|
6647
|
-
//
|
|
6648
|
-
//
|
|
6649
|
-
//
|
|
6650
|
-
vec2
|
|
6651
|
-
|
|
6652
|
-
float
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
//
|
|
6656
|
-
float
|
|
6657
|
-
|
|
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);
|
|
6658
6775
|
|
|
6659
6776
|
// An untouched texel is left exactly as it was \u2014 the identity the whole
|
|
6660
6777
|
// seam is built on. Nothing here has happened to this paper.
|
|
6661
|
-
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) {
|
|
6662
6779
|
return;
|
|
6663
6780
|
}
|
|
6664
6781
|
|
|
6665
6782
|
vec2 p = plLocal();
|
|
6666
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 \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
|
+
|
|
6667
6798
|
// Wet paper is darker and smoother: water fills the gaps between fibres
|
|
6668
6799
|
// that scatter light, which is both effects from one cause.
|
|
6669
6800
|
color.rgb *= 1.0 - 0.38 * d.g;
|
|
@@ -6715,27 +6846,64 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6715
6846
|
// being finer than a level it can only ever move a value into the
|
|
6716
6847
|
// neighbouring one.
|
|
6717
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);
|
|
6718
6856
|
// A steep leading edge \u2014 paper to straw in a sliver \u2014 then the long ramp.
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
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);
|
|
6722
6876
|
|
|
6723
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
|
|
6724
6878
|
// A wide, smooth hand-over from scorch to char \u2014 the gradient is the point.
|
|
6725
|
-
|
|
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);
|
|
6726
6889
|
vec2 cells = plVoronoi(p / (3.6 * PL_MM));
|
|
6727
6890
|
// Sparse: a real crack network is broken, not a tiled floor \u2014 most cell
|
|
6728
6891
|
// borders never split.
|
|
6729
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)));
|
|
6730
6893
|
float plate = plHash(floor(p / (3.6 * PL_MM)) + 3.7);
|
|
6731
|
-
//
|
|
6732
|
-
//
|
|
6733
|
-
//
|
|
6734
|
-
//
|
|
6735
|
-
|
|
6736
|
-
|
|
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);
|
|
6737
6903
|
vec3 cracks = plLinear(vec3(0.039, 0.024, 0.016)); // #0A0604
|
|
6738
|
-
|
|
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);
|
|
6739
6907
|
// Mottled at the plate scale and finer, and darker than the sampled body
|
|
6740
6908
|
// under a bright key \u2014 the reference's char reads near black in the frame.
|
|
6741
6909
|
charColor *= (0.75 + 0.35 * plate) * (0.8 + 0.4 * plNoise(p * 260.0));
|
|
@@ -6747,42 +6915,42 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6747
6915
|
plHeight += charZone * ((plNoise(p * 380.0) - 0.5) * 0.00008 + (plate - 0.5) * 0.00018 - crack * 0.00016);
|
|
6748
6916
|
|
|
6749
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
|
|
6750
|
-
//
|
|
6751
|
-
//
|
|
6752
|
-
//
|
|
6753
|
-
//
|
|
6754
|
-
//
|
|
6755
|
-
//
|
|
6756
|
-
//
|
|
6757
|
-
//
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
float
|
|
6761
|
-
//
|
|
6762
|
-
//
|
|
6763
|
-
|
|
6764
|
-
// middle of it, which read as a neon tube round the hole \u2014 the single most
|
|
6765
|
-
// synthetic thing at wide view.
|
|
6766
|
-
float lipAlong = dot(p, tangentP) / (3.2 * PL_MM);
|
|
6767
|
-
float lipBreak = smoothstep(0.34, 0.56, plFbm(vec2(lipAlong, 0.9)));
|
|
6768
|
-
float lip = (1.0 - smoothstep(lipWidth - aa, lipWidth + aa, mm)) * step(d.a, 0.999) * cutNear * lipBreak;
|
|
6769
|
-
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;
|
|
6770
6932
|
color.rgb = mix(color.rgb, ash, lip);
|
|
6771
6933
|
roughness = mix(roughness, 0.99, lip);
|
|
6772
|
-
//
|
|
6773
|
-
//
|
|
6774
|
-
//
|
|
6775
|
-
//
|
|
6776
|
-
// of
|
|
6777
|
-
//
|
|
6778
|
-
|
|
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;
|
|
6779
6947
|
|
|
6780
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
|
|
6781
6949
|
// Beads along the cut: position along the edge, crawling on the burn's
|
|
6782
6950
|
// clock, with a flicker of their own. Only where it is hot \u2014 and as the
|
|
6783
6951
|
// heat goes the threshold rises, so the line breaks into fewer, dimmer
|
|
6784
6952
|
// beads and they go out one by one.
|
|
6785
|
-
float s1 = dot(p,
|
|
6953
|
+
float s1 = dot(p, tangentR) / (2.4 * PL_MM) - uDamageTime * 0.55 * uLook0.w;
|
|
6786
6954
|
float bead = plNoise(vec2(s1, 0.37)) * 0.7 + plNoise(vec2(s1 * 2.3, 5.1)) * 0.3;
|
|
6787
6955
|
float flicker = 0.62 + 0.38 * plNoise(vec2(floor(s1) * 7.13, uDamageTime * 9.0 * uLook0.w));
|
|
6788
6956
|
// Down to a smoulder's last flicker: a spent edge keeps a little heat in
|
|
@@ -6797,7 +6965,8 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6797
6965
|
float lit = smoothstep(mix(0.82, 0.45, hot) - shift, mix(0.9, 0.58, hot) - shift, bead);
|
|
6798
6966
|
// Each bead tapers at its ends \u2014 a bead, not a dash cut from a strip.
|
|
6799
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);
|
|
6800
|
-
|
|
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;
|
|
6801
6970
|
// Mostly orange: a bead is red at its ends and yellow-white only at its
|
|
6802
6971
|
// hottest core, which is what Ember_line.png shows. Mapped low and pushed
|
|
6803
6972
|
// high by a power, so white is the exception rather than the line.
|
|
@@ -6817,13 +6986,15 @@ void plDamage(inout vec4 color, inout float roughness) {
|
|
|
6817
6986
|
// edge is hot, and inside the zone the beads are held to \u2014 the lip plus
|
|
6818
6987
|
// the widest bead, measured from the cut \u2014 so the glow never reaches out
|
|
6819
6988
|
// over paper the gate says heat must not light.
|
|
6820
|
-
float zoneEnd =
|
|
6821
|
-
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;
|
|
6822
6991
|
float patch_ = smoothstep(0.35, 0.72, plFbm(p / (4.0 * PL_MM) + vec2(uDamageTime * 0.15 * uLook0.w, 0.0)));
|
|
6823
6992
|
plDamageLight += vec3(0.55, 0.06, 0.01) * seam * patch_ * hot * uLook1.x;
|
|
6824
6993
|
float speck = step(0.985, plHash(floor(p / (0.35 * PL_MM)) + floor(uDamageTime * 8.0 * uLook0.w)));
|
|
6825
6994
|
plDamageLight += plEmberRamp(0.9) * 1.6 * speck * seam * hot * uLook3.z;
|
|
6826
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));
|
|
6827
6998
|
plDamageCut(color, d);
|
|
6828
6999
|
}
|
|
6829
7000
|
`
|
|
@@ -6934,6 +7105,7 @@ function composeSurface(surface, stock, thickness, maps = { hasFrontMap: false,
|
|
|
6934
7105
|
chunks.push(DAMAGE_CHUNK, DAMAGE_SHADE_CHUNK);
|
|
6935
7106
|
calls.push("plDamage(csm_DiffuseColor, csm_Roughness);");
|
|
6936
7107
|
uniforms.uDamage = { value: null };
|
|
7108
|
+
uniforms.uDamageEdge = { value: null };
|
|
6937
7109
|
uniforms.uDamageDetail = { value: 1 };
|
|
6938
7110
|
uniforms.uDamageTime = { value: 0 };
|
|
6939
7111
|
const look = DAMAGE_LOOK_DEFAULTS;
|
|
@@ -6946,7 +7118,7 @@ function composeSurface(surface, stock, thickness, maps = { hasFrontMap: false,
|
|
|
6946
7118
|
uniforms.uLook2 = {
|
|
6947
7119
|
value: new THREE5.Vector4(look.charCracks, look.scorchReach, look.scorchDarkness, look.fingers)
|
|
6948
7120
|
};
|
|
6949
|
-
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) };
|
|
6950
7122
|
}
|
|
6951
7123
|
const cutting = [];
|
|
6952
7124
|
const cuts = [];
|
|
@@ -7039,35 +7211,65 @@ function pad(values, fill = 0) {
|
|
|
7039
7211
|
import * as THREE6 from "three";
|
|
7040
7212
|
import { useFrame } from "@react-three/fiber";
|
|
7041
7213
|
import { useEffect as useEffect4, useMemo as useMemo2 } from "react";
|
|
7042
|
-
|
|
7043
|
-
|
|
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(() => {
|
|
7044
7218
|
if (!source) return null;
|
|
7045
|
-
const
|
|
7219
|
+
const damage = new THREE6.DataTexture(
|
|
7046
7220
|
source.pixels,
|
|
7047
7221
|
source.size,
|
|
7048
7222
|
source.size,
|
|
7049
7223
|
THREE6.RGBAFormat,
|
|
7050
7224
|
THREE6.UnsignedByteType
|
|
7051
7225
|
);
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
t
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
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
|
+
);
|
|
7064
7255
|
useFrame(() => {
|
|
7065
|
-
if (!source || !
|
|
7066
|
-
if (
|
|
7067
|
-
|
|
7068
|
-
|
|
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);
|
|
7069
7261
|
});
|
|
7070
|
-
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;
|
|
7071
7273
|
}
|
|
7072
7274
|
|
|
7073
7275
|
// src/surface/PaperMaterial.tsx
|
|
@@ -7084,7 +7286,7 @@ function PaperMaterial({
|
|
|
7084
7286
|
damage
|
|
7085
7287
|
}) {
|
|
7086
7288
|
const rig = useLightRig(lighting);
|
|
7087
|
-
const
|
|
7289
|
+
const damageTextures = useDamageTexture(damage, sheet2);
|
|
7088
7290
|
const composed = composeSurface(
|
|
7089
7291
|
surface,
|
|
7090
7292
|
stock,
|
|
@@ -7092,7 +7294,7 @@ function PaperMaterial({
|
|
|
7092
7294
|
{
|
|
7093
7295
|
hasFrontMap: Boolean(texture),
|
|
7094
7296
|
hasBackMap: Boolean(backTexture),
|
|
7095
|
-
hasDamage: Boolean(
|
|
7297
|
+
hasDamage: Boolean(damageTextures)
|
|
7096
7298
|
},
|
|
7097
7299
|
sheet2,
|
|
7098
7300
|
rig,
|
|
@@ -7101,7 +7303,7 @@ function PaperMaterial({
|
|
|
7101
7303
|
const bound = useMemo3(() => composed.uniforms, [composed.structureKey]);
|
|
7102
7304
|
useEffect5(() => {
|
|
7103
7305
|
for (const [key, uniform] of Object.entries(composed.uniforms)) {
|
|
7104
|
-
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")) {
|
|
7105
7307
|
continue;
|
|
7106
7308
|
}
|
|
7107
7309
|
if (bound[key].value instanceof THREE7.Color && uniform.value instanceof THREE7.Color) {
|
|
@@ -7115,8 +7317,9 @@ function PaperMaterial({
|
|
|
7115
7317
|
useEffect5(() => {
|
|
7116
7318
|
if (bound.uFrontMap) bound.uFrontMap.value = texture;
|
|
7117
7319
|
if (bound.uBackMap) bound.uBackMap.value = backTexture ?? null;
|
|
7118
|
-
if (bound.uDamage) bound.uDamage.value =
|
|
7119
|
-
|
|
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]);
|
|
7120
7323
|
useFrame2((state) => {
|
|
7121
7324
|
if (!bound.uDamageDetail) return;
|
|
7122
7325
|
if (bound.uDamageTime) {
|
|
@@ -7149,7 +7352,7 @@ function PaperMaterial({
|
|
|
7149
7352
|
v(look.edgeWave, d.edgeWave),
|
|
7150
7353
|
v(look.edgeBite, d.edgeBite),
|
|
7151
7354
|
v(look.sparkle, d.sparkle),
|
|
7152
|
-
|
|
7355
|
+
v(look.charWidth, d.charWidth)
|
|
7153
7356
|
);
|
|
7154
7357
|
}
|
|
7155
7358
|
const detail = damage?.detail ?? 1;
|
|
@@ -7987,7 +8190,8 @@ function PaperLighting({
|
|
|
7987
8190
|
reducedMotion,
|
|
7988
8191
|
shadowMapSize,
|
|
7989
8192
|
contactShadow = true,
|
|
7990
|
-
environment = true
|
|
8193
|
+
environment = true,
|
|
8194
|
+
damage
|
|
7991
8195
|
}) {
|
|
7992
8196
|
const p = useMemo5(() => rig ?? resolveLighting(preset, light), [rig, preset, light]);
|
|
7993
8197
|
const mapSize = shadowMapSize ?? p.shadow.mapSize;
|
|
@@ -8021,6 +8225,20 @@ function PaperLighting({
|
|
|
8021
8225
|
driftRef.current += delta * p.gobo.drift;
|
|
8022
8226
|
goboMap.offset.set(driftRef.current, driftRef.current * 0.6);
|
|
8023
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
|
+
});
|
|
8024
8242
|
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
8025
8243
|
p.studio > 0 && (environment ? /* @__PURE__ */ jsx5(StudioLight, { rig: p }) : (
|
|
8026
8244
|
// The studio light degrades rather than disappearing. A hemisphere
|
|
@@ -8031,19 +8249,21 @@ function PaperLighting({
|
|
|
8031
8249
|
/* @__PURE__ */ jsx5(
|
|
8032
8250
|
"hemisphereLight",
|
|
8033
8251
|
{
|
|
8252
|
+
ref: hemisphereRef,
|
|
8034
8253
|
color: p.sky.horizon,
|
|
8035
8254
|
groundColor: p.sky.ground,
|
|
8036
8255
|
intensity: p.studio * HEMISPHERE_STAND_IN
|
|
8037
8256
|
}
|
|
8038
8257
|
)
|
|
8039
8258
|
)),
|
|
8040
|
-
/* @__PURE__ */ jsx5("ambientLight", { intensity: p.ambient }),
|
|
8259
|
+
/* @__PURE__ */ jsx5("ambientLight", { ref: ambientRef, intensity: p.ambient }),
|
|
8041
8260
|
p.gobo && goboMap ? /* @__PURE__ */ jsx5(
|
|
8042
8261
|
"spotLight",
|
|
8043
8262
|
{
|
|
8263
|
+
ref: keyRef,
|
|
8044
8264
|
position: p.key.position,
|
|
8045
8265
|
color: p.key.color,
|
|
8046
|
-
intensity:
|
|
8266
|
+
intensity: keyIntensity,
|
|
8047
8267
|
angle: p.gobo.angle,
|
|
8048
8268
|
penumbra: 0.5,
|
|
8049
8269
|
decay: 0,
|
|
@@ -8056,9 +8276,10 @@ function PaperLighting({
|
|
|
8056
8276
|
) : /* @__PURE__ */ jsx5(
|
|
8057
8277
|
"directionalLight",
|
|
8058
8278
|
{
|
|
8279
|
+
ref: keyRef,
|
|
8059
8280
|
position: p.key.position,
|
|
8060
8281
|
color: p.key.color,
|
|
8061
|
-
intensity:
|
|
8282
|
+
intensity: keyIntensity,
|
|
8062
8283
|
castShadow,
|
|
8063
8284
|
"shadow-mapSize": [mapSize || 1, mapSize || 1],
|
|
8064
8285
|
"shadow-radius": p.shadow.radius,
|
|
@@ -8991,7 +9212,7 @@ function buildDisplacementGLSL(stack, sheet2) {
|
|
|
8991
9212
|
const deformer = getDeformer(instance.type);
|
|
8992
9213
|
if (!deformer.glsl) {
|
|
8993
9214
|
throw new Error(
|
|
8994
|
-
`[paperlab] Deformer "${instance.type}" has no GLSL implementation
|
|
9215
|
+
`[paperlab] Deformer "${instance.type}" has no GLSL implementation, so it can't run in field mode.`
|
|
8995
9216
|
);
|
|
8996
9217
|
}
|
|
8997
9218
|
const ns = `u${cap(instance.type)}${i}_`;
|
|
@@ -9899,10 +10120,9 @@ function FieldKeyboardMirror({
|
|
|
9899
10120
|
children: [
|
|
9900
10121
|
paperLabel(slot, i),
|
|
9901
10122
|
carrying?.slot === i && /* @__PURE__ */ jsxs7("span", { "aria-live": "polite", children: [
|
|
9902
|
-
" ",
|
|
9903
|
-
"\u2014 carrying; zone ",
|
|
10123
|
+
", carrying. Zone ",
|
|
9904
10124
|
controller.current?.zoneIds()[carrying.zoneIndex] ?? "none",
|
|
9905
|
-
"
|
|
10125
|
+
". Enter places it, Escape puts it back."
|
|
9906
10126
|
] })
|
|
9907
10127
|
]
|
|
9908
10128
|
},
|
|
@@ -10231,7 +10451,7 @@ function withoutUploads(value) {
|
|
|
10231
10451
|
};
|
|
10232
10452
|
return { value: walk(value), replaced };
|
|
10233
10453
|
}
|
|
10234
|
-
var UPLOAD_NOTE = "// Uploaded pictures cannot travel in a snippet
|
|
10454
|
+
var UPLOAD_NOTE = "// Uploaded pictures cannot travel in a snippet, so the paths below are\n// stand-ins, in order. Point them at your own files.";
|
|
10235
10455
|
function buildJsxSnippet(config) {
|
|
10236
10456
|
const diffed = diffConfig(config);
|
|
10237
10457
|
delete diffed.meta;
|
|
@@ -10259,7 +10479,7 @@ var BEHAVIOR_PHRASES = {
|
|
|
10259
10479
|
settle: (o) => o.relax > 0.7 ? "lying where it landed, flat but for one corner the stiffness kept" : "just come to rest, still holding a little of the shape it fell in",
|
|
10260
10480
|
carry: () => "drooping from a pinched corner, fluttering as if being carried",
|
|
10261
10481
|
flight: (o) => o.path === "loop" ? "tumbling through a seamless airborne loop" : "tumbling across the scene on the wind",
|
|
10262
|
-
crumple: (o) => o.progress < 0.3 ? "lightly handled
|
|
10482
|
+
crumple: (o) => o.progress < 0.3 ? "lightly handled, with a few soft creases across it" : o.progress < 0.7 ? "crushed into irregular creased facets, as if screwed up and flattened out again" : "balled up in a fist"
|
|
10263
10483
|
};
|
|
10264
10484
|
function describeConfig(config) {
|
|
10265
10485
|
const stock = getStock(config.stock);
|
|
@@ -10320,8 +10540,8 @@ export function ${name}() {
|
|
|
10320
10540
|
give the parent an explicit height.
|
|
10321
10541
|
|
|
10322
10542
|
4. Verify: run the dev server. You should see ${describeConfig(config)}.
|
|
10323
|
-
If the canvas is blank, the parent container almost certainly has no height
|
|
10324
|
-
|
|
10543
|
+
If the canvas is blank, the parent container almost certainly has no height.
|
|
10544
|
+
Give it one (this is the classic React Three Fiber integration bug, not a
|
|
10325
10545
|
paperlab bug).
|
|
10326
10546
|
|
|
10327
10547
|
Constraints: don't modify the preset values; three >= 0.160 and React 19 are
|
|
@@ -10415,4 +10635,4 @@ export {
|
|
|
10415
10635
|
describeConfig,
|
|
10416
10636
|
buildAgentPayload
|
|
10417
10637
|
};
|
|
10418
|
-
//# sourceMappingURL=chunk-
|
|
10638
|
+
//# sourceMappingURL=chunk-LUKNPQ3S.js.map
|