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
package/dist/fx.cjs
CHANGED
|
@@ -41,32 +41,24 @@ var init_damageContract = __esm({
|
|
|
41
41
|
"src/surface/damageContract.ts"() {
|
|
42
42
|
"use strict";
|
|
43
43
|
DAMAGE_LOOK_DEFAULTS = {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// underneath is wrong, which in this case was a fire nothing could see.
|
|
47
|
-
emberWidth: 0.9,
|
|
48
|
-
emberIntensity: 1.45,
|
|
44
|
+
emberWidth: 1.25,
|
|
45
|
+
emberIntensity: 1.05,
|
|
49
46
|
emberCoverage: 0.6,
|
|
50
47
|
emberFlicker: 1.65,
|
|
51
48
|
emberGlow: 1.25,
|
|
52
49
|
sparkle: 0.5,
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
lipBrightness: 0.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
scorchReach: 30,
|
|
66
|
-
scorchDarkness: 1.17,
|
|
67
|
-
fingers: 1.25,
|
|
68
|
-
edgeWave: 8.5,
|
|
69
|
-
edgeBite: 3.6
|
|
50
|
+
// As wide as the char band: the lip has to be seen. At
|
|
51
|
+
// 0.95 mm it was a hairline tracing the edge.
|
|
52
|
+
lipWidth: 3.5,
|
|
53
|
+
charWidth: 3.5,
|
|
54
|
+
lipBrightness: 0.76,
|
|
55
|
+
charWarmth: 0.32,
|
|
56
|
+
charCracks: 0.45,
|
|
57
|
+
scorchReach: 9,
|
|
58
|
+
scorchDarkness: 0.86,
|
|
59
|
+
fingers: 1.95,
|
|
60
|
+
edgeWave: 13,
|
|
61
|
+
edgeBite: 6
|
|
70
62
|
};
|
|
71
63
|
DAMAGE_CHANNELS = { char: 0, saturation: 1, heat: 2, presence: 3 };
|
|
72
64
|
}
|
|
@@ -613,27 +605,15 @@ var init_emission = __esm({
|
|
|
613
605
|
FIRE_GLOW = [4, 8];
|
|
614
606
|
FIRE_HEAT_SCALE = 0.65;
|
|
615
607
|
FIRE_SOOT_SCALE = 3;
|
|
616
|
-
FX_BLOOM =
|
|
608
|
+
FX_BLOOM = 1.25;
|
|
617
609
|
FIRE_CONTRAST = 0.9;
|
|
618
610
|
FIRE_OPACITY = 5;
|
|
619
611
|
FIRE_THIN = 0.55;
|
|
620
612
|
FIRE_ZONES = {
|
|
621
|
-
root: { color: "#3b6bff", amount: 0, reach: 0.
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
// budget. Brighter keeps the same small area: 1.47% bloom, near-white 0.8%.
|
|
626
|
-
// Starting it lower (from 0.45) bloomed 6.4% but put near-white back at 4.7%.
|
|
627
|
-
core: { color: "#fff7d4", glow: 6, from: 0.55 },
|
|
628
|
-
// Saturated, because the tone curve takes saturation away from anything
|
|
629
|
-
// above paper white: #ffdd7c at this glow came out pale yellow (s ~0.4),
|
|
630
|
-
// and measured 7.5% yellow against Flame_base.png's 29%.
|
|
631
|
-
body: { color: "#ffcf3a", glow: 1.3 },
|
|
632
|
-
// to: 0.4, measured against Flame_base.png with bloom off (bloom's halo
|
|
633
|
-
// counts as flame in any diff): near-white / pale / yellow / orange came out
|
|
634
|
-
// 0.7 / 21 / 34 / 44% against the reference's 0.9 / 17 / 29 / 43. At 0.32
|
|
635
|
-
// the body took half the flame and it was 51% yellow; at 0.45, 64% orange.
|
|
636
|
-
tip: { color: "#ff9e2c", glow: 0.9, from: 0.08, to: 0.4, softness: 0.15, tearing: 0.35 }
|
|
613
|
+
root: { color: "#3b6bff", amount: 0.01, reach: 0.39 },
|
|
614
|
+
core: { color: "#fff7d4", glow: 2.1, from: 0.63 },
|
|
615
|
+
body: { color: "#ffcf3a", glow: 0.8 },
|
|
616
|
+
tip: { color: "#ff9e2c", glow: 0.21, from: 0, to: 0.5, softness: 0.02, tearing: 0.64 }
|
|
637
617
|
};
|
|
638
618
|
}
|
|
639
619
|
});
|
|
@@ -711,16 +691,16 @@ function flameAnchors(field, locate, max, out, time = field.time) {
|
|
|
711
691
|
const rx = at.x;
|
|
712
692
|
const ry = at.y;
|
|
713
693
|
const rz = at.z;
|
|
714
|
-
const
|
|
694
|
+
const toward2 = locate(u + gx / gl / last, v + gy / gl / last);
|
|
715
695
|
let upper = 0;
|
|
716
696
|
scratchA.x = 0;
|
|
717
697
|
scratchA.y = 0;
|
|
718
698
|
scratchA.z = 0;
|
|
719
|
-
if (
|
|
720
|
-
const l = Math.hypot(
|
|
721
|
-
scratchA.x = (
|
|
722
|
-
scratchA.y = (
|
|
723
|
-
scratchA.z = (
|
|
699
|
+
if (toward2) {
|
|
700
|
+
const l = Math.hypot(toward2.x - rx, toward2.y - ry, toward2.z - rz) || 1;
|
|
701
|
+
scratchA.x = (toward2.x - rx) / l;
|
|
702
|
+
scratchA.y = (toward2.y - ry) / l;
|
|
703
|
+
scratchA.z = (toward2.z - rz) / l;
|
|
724
704
|
upper = scratchA.y;
|
|
725
705
|
}
|
|
726
706
|
const heat = data[cell * 4 + HEAT];
|
|
@@ -866,7 +846,7 @@ void mainUv(inout vec2 uv) {
|
|
|
866
846
|
}
|
|
867
847
|
|
|
868
848
|
// The grade: the whole frame warms as the fire grows \u2014 a few percent, no
|
|
869
|
-
// more
|
|
849
|
+
// more. A multiply, so black stays black.
|
|
870
850
|
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
|
|
871
851
|
outputColor = vec4(inputColor.rgb * mix(vec3(1.0), uWarm, uGrade), inputColor.a);
|
|
872
852
|
}
|
|
@@ -976,12 +956,14 @@ function FxPostPass({
|
|
|
976
956
|
threshold = FX_BLOOM_THRESHOLD,
|
|
977
957
|
field,
|
|
978
958
|
locate,
|
|
979
|
-
haze: hazeOverride
|
|
959
|
+
haze: hazeOverride,
|
|
960
|
+
focus = 0
|
|
980
961
|
}) {
|
|
981
962
|
const { bloomScale, haze: tierHaze } = fxQualityFor(quality);
|
|
982
963
|
const hazePx = hazeOverride ?? tierHaze;
|
|
983
964
|
const haze = (0, import_react2.useMemo)(() => new HazeGradeEffect(), []);
|
|
984
965
|
const anchors = (0, import_react2.useRef)([]);
|
|
966
|
+
const focusTarget = (0, import_react2.useRef)(new THREE3.Vector3());
|
|
985
967
|
(0, import_fiber2.useFrame)(({ camera }) => {
|
|
986
968
|
if (!field || !locate) {
|
|
987
969
|
haze.count = 0;
|
|
@@ -996,6 +978,14 @@ function FxPostPass({
|
|
|
996
978
|
top.set(a.x, a.y + a.height, a.z).project(camera);
|
|
997
979
|
sources[i].set(root.x * 0.5 + 0.5, root.y * 0.5 + 0.5, Math.max(0, (top.y - root.y) * 0.5), a.heat);
|
|
998
980
|
}
|
|
981
|
+
if (focus > 0 && n > 0) {
|
|
982
|
+
focusTarget.current.set(0, 0, 0);
|
|
983
|
+
for (let i = 0; i < n; i++) {
|
|
984
|
+
const a = anchors.current[i];
|
|
985
|
+
focusTarget.current.add(root.set(a.x, a.y, a.z));
|
|
986
|
+
}
|
|
987
|
+
focusTarget.current.multiplyScalar(1 / n);
|
|
988
|
+
}
|
|
999
989
|
haze.count = n;
|
|
1000
990
|
haze.time = field.time;
|
|
1001
991
|
haze.amount = hazePx;
|
|
@@ -1013,6 +1003,14 @@ function FxPostPass({
|
|
|
1013
1003
|
}
|
|
1014
1004
|
) : null,
|
|
1015
1005
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("primitive", { object: haze }),
|
|
1006
|
+
focus > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1007
|
+
import_postprocessing2.DepthOfField,
|
|
1008
|
+
{
|
|
1009
|
+
target: focusTarget.current,
|
|
1010
|
+
focalLength: 0.06 - 0.045 * focus,
|
|
1011
|
+
bokehScale: 1 + 3 * focus
|
|
1012
|
+
}
|
|
1013
|
+
) : null,
|
|
1016
1014
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_postprocessing2.ToneMapping, { mode: modes[film] })
|
|
1017
1015
|
] });
|
|
1018
1016
|
}
|
|
@@ -1051,6 +1049,7 @@ __export(fx_exports, {
|
|
|
1051
1049
|
FIELD_SIZE: () => FIELD_SIZE,
|
|
1052
1050
|
FIRE_GLOW: () => FIRE_GLOW,
|
|
1053
1051
|
FIRE_HEAT_SCALE: () => FIRE_HEAT_SCALE,
|
|
1052
|
+
FIRE_LIGHT_GAIN: () => FIRE_LIGHT_GAIN,
|
|
1054
1053
|
FIRE_SOOT_SCALE: () => FIRE_SOOT_SCALE,
|
|
1055
1054
|
FIRE_ZONES: () => FIRE_ZONES,
|
|
1056
1055
|
FIXED_DT: () => FIXED_DT,
|
|
@@ -1109,7 +1108,7 @@ var particlePresets = {
|
|
|
1109
1108
|
* caught the bug in the version before this one. That version said "the hot
|
|
1110
1109
|
* end is now ~5.6, well over the bloom threshold" — and 5.6 was an ABSOLUTE
|
|
1111
1110
|
* luminance, while paper white under `window` is 1.6. So the hottest spark
|
|
1112
|
-
* in the frame was **3.5× paper, under the
|
|
1111
|
+
* in the frame was **3.5× paper, under the 4× floor**, and its
|
|
1113
1112
|
* own comment said otherwise. A number that cannot be compared to anything
|
|
1114
1113
|
* is a number nobody can check.
|
|
1115
1114
|
*
|
|
@@ -1129,12 +1128,12 @@ var particlePresets = {
|
|
|
1129
1128
|
drag: 0.8,
|
|
1130
1129
|
jitter: 1.5,
|
|
1131
1130
|
windCatch: 0.8,
|
|
1132
|
-
// 0.3-1 mm of core
|
|
1131
|
+
// 0.3-1 mm of core. A sheet is one world unit across, 210 mm, so
|
|
1133
1132
|
// 0.0035 is 0.7 mm. It was 0.005 — a full millimetre wide before the
|
|
1134
1133
|
// streak stretched it, which is where "wide bars" started.
|
|
1135
1134
|
size: [35e-4, 12e-4],
|
|
1136
1135
|
color: [
|
|
1137
|
-
// 6× paper white: inside
|
|
1136
|
+
// 6× paper white: inside the 4–8× band, so it blooms.
|
|
1138
1137
|
emitHex("#FFC271", 6),
|
|
1139
1138
|
// 0.7× — UNDER the bloom threshold on purpose, so a dying spark stops
|
|
1140
1139
|
// glowing rather than merely fading.
|
|
@@ -1152,7 +1151,13 @@ var particlePresets = {
|
|
|
1152
1151
|
* simulation, on purpose; see the fx plan.
|
|
1153
1152
|
*/
|
|
1154
1153
|
smoke: {
|
|
1155
|
-
|
|
1154
|
+
// Shorter, and smaller below: these are the thread beside the
|
|
1155
|
+
// simulator's smoke, and the simulator's is gone within a second or two
|
|
1156
|
+
// of the last flame. Left to live three and a half seconds and grow to
|
|
1157
|
+
// seven centimetres, the last few of them overlapped into a flat wash
|
|
1158
|
+
// over the hole with nothing left to move them — the one moment of a
|
|
1159
|
+
// burn where the smoke looked wrong.
|
|
1160
|
+
life: [1.4, 2.6],
|
|
1156
1161
|
speed: [0.05, 0.15],
|
|
1157
1162
|
direction: [0, 1, 0],
|
|
1158
1163
|
spread: 0.3,
|
|
@@ -1160,13 +1165,13 @@ var particlePresets = {
|
|
|
1160
1165
|
drag: 0.9,
|
|
1161
1166
|
jitter: 0.3,
|
|
1162
1167
|
windCatch: 1,
|
|
1163
|
-
size: [0.05, 0.
|
|
1168
|
+
size: [0.05, 0.18],
|
|
1164
1169
|
// #6B6560 grey-brown, linear, and thin — the background stays clear.
|
|
1165
1170
|
color: [
|
|
1166
1171
|
[0.147, 0.13, 0.117],
|
|
1167
1172
|
[0.2, 0.19, 0.18]
|
|
1168
1173
|
],
|
|
1169
|
-
alpha: [0.
|
|
1174
|
+
alpha: [0.07, 0],
|
|
1170
1175
|
spin: 0.4,
|
|
1171
1176
|
blend: "normal",
|
|
1172
1177
|
shape: "soft",
|
|
@@ -1178,7 +1183,9 @@ var particlePresets = {
|
|
|
1178
1183
|
* burn that proves paper was there.
|
|
1179
1184
|
*/
|
|
1180
1185
|
ash: {
|
|
1181
|
-
|
|
1186
|
+
// A second or two: ash that hung in the air for five read as grey
|
|
1187
|
+
// confetti over the hole, dust or stars rather than something falling.
|
|
1188
|
+
life: [1.2, 2.6],
|
|
1182
1189
|
// Thrown UP by the heat it came off, before it is heavier than the air.
|
|
1183
1190
|
// It used to leave at almost nothing (0.02–0.1) with a lift of −0.35, so
|
|
1184
1191
|
// every flake began falling the moment it was born — straight through the
|
|
@@ -1186,17 +1193,21 @@ var particlePresets = {
|
|
|
1186
1193
|
speed: [0.14, 0.3],
|
|
1187
1194
|
direction: [0, 1, 0],
|
|
1188
1195
|
spread: 0.55,
|
|
1189
|
-
|
|
1196
|
+
// Heavier than the air once the throw is spent: most flakes tumble down,
|
|
1197
|
+
// only the lightest ride the heat up a little first.
|
|
1198
|
+
lift: -0.42,
|
|
1190
1199
|
drag: 1.6,
|
|
1191
1200
|
jitter: 1.2,
|
|
1192
1201
|
windCatch: 1,
|
|
1193
|
-
// 5–10 mm across
|
|
1202
|
+
// 5–10 mm across. A sheet is 210 mm, so 0.03 is 6.3 mm. They were
|
|
1194
1203
|
// 3.8 mm and there were far too many of them: a shower of specks rather
|
|
1195
1204
|
// than a few flakes you would notice.
|
|
1196
1205
|
size: [0.03, 0.026],
|
|
1206
|
+
// Charred paper, near black: under a bright key the old value read as
|
|
1207
|
+
// pale grey against the black stage, which is the confetti.
|
|
1197
1208
|
color: [
|
|
1198
|
-
[0.
|
|
1199
|
-
[0.
|
|
1209
|
+
[0.04, 0.037, 0.034],
|
|
1210
|
+
[0.07, 0.066, 0.062]
|
|
1200
1211
|
],
|
|
1201
1212
|
alpha: [0.95, 0],
|
|
1202
1213
|
spin: 4,
|
|
@@ -1421,13 +1432,14 @@ var ParticlePool = class {
|
|
|
1421
1432
|
// src/fx/fire.ts
|
|
1422
1433
|
init_field();
|
|
1423
1434
|
var fireEmitterDefaults = {
|
|
1424
|
-
embers: 0.
|
|
1425
|
-
smoke: 0,
|
|
1426
|
-
ash: 0.
|
|
1435
|
+
embers: 0.39,
|
|
1436
|
+
smoke: 0.135,
|
|
1437
|
+
ash: 0.36,
|
|
1427
1438
|
seed: 7,
|
|
1428
1439
|
caps: {}
|
|
1429
1440
|
};
|
|
1430
1441
|
var PER_UPDATE = 24;
|
|
1442
|
+
var CRACKLE_SPARKS = 0.06;
|
|
1431
1443
|
var FireEmitter = class {
|
|
1432
1444
|
field;
|
|
1433
1445
|
pool;
|
|
@@ -1464,7 +1476,10 @@ var FireEmitter = class {
|
|
|
1464
1476
|
heat /= front;
|
|
1465
1477
|
const struggle = 0.35 + 1.6 * Math.max(0, 0.7 - heat) + air * 0.8;
|
|
1466
1478
|
const oxygen = 1 + air * 1.5;
|
|
1467
|
-
this.emberDebt = Math.min(
|
|
1479
|
+
this.emberDebt = Math.min(
|
|
1480
|
+
this.emberDebt + front * o.embers * oxygen * dt + field.lastStats.charred * o.embers * CRACKLE_SPARKS,
|
|
1481
|
+
PER_UPDATE
|
|
1482
|
+
);
|
|
1468
1483
|
this.smokeDebt = Math.min(this.smokeDebt + front * o.smoke * struggle * dt, PER_UPDATE);
|
|
1469
1484
|
while (this.emberDebt >= 1) {
|
|
1470
1485
|
this.emberDebt -= 1;
|
|
@@ -1596,7 +1611,7 @@ void main() {
|
|
|
1596
1611
|
// It used to be flat-topped across (smoothstep from 0.35, so the middle
|
|
1597
1612
|
// seventy per cent of the width was all at full brightness) and to keep a
|
|
1598
1613
|
// quarter of that brightness all the way to the end of the quad. Both
|
|
1599
|
-
// together drew a wide bar with a squared-off end, which is what
|
|
1614
|
+
// together drew a wide bar with a squared-off end, which is what a visual review
|
|
1600
1615
|
// saw up close. A spark is a point of light smeared by its own motion: it
|
|
1601
1616
|
// is brightest on its centre line and it runs out.
|
|
1602
1617
|
float across = abs(vQuad.x);
|
|
@@ -1716,14 +1731,14 @@ void main() {
|
|
|
1716
1731
|
float edge = 0.62 + 0.3 * flNoise(vec2(a * 1.6 + vSeed * 31.0, vSeed * 7.0));
|
|
1717
1732
|
float mask = 1.0 - smoothstep(edge - 0.06, edge, r);
|
|
1718
1733
|
if (mask <= 0.0) discard;
|
|
1719
|
-
// Char in the middle, pale brittle ash at the edge
|
|
1734
|
+
// Char in the middle, pale brittle ash at the edge \u2014 in patches
|
|
1720
1735
|
// along it, not all the way round: a flake is a few pixels across, and a
|
|
1721
1736
|
// continuous pale rim round a dark middle drew it as a hollow ring.
|
|
1722
1737
|
float rim = smoothstep(edge - 0.32, edge - 0.04, r);
|
|
1723
1738
|
float patches = smoothstep(0.45, 0.75, flNoise(vec2(a * 2.2 + vSeed * 17.0, vSeed * 13.0)));
|
|
1724
1739
|
float grain = flNoise(vQuad * 3.0 + vSeed * 23.0);
|
|
1725
1740
|
vec3 charC = vec3(0.05, 0.043, 0.038) * (0.7 + 0.6 * grain);
|
|
1726
|
-
// #A49E9D,
|
|
1741
|
+
// #A49E9D, sampled from the reference ash, in linear.
|
|
1727
1742
|
vec3 ashC = vec3(0.372, 0.344, 0.340);
|
|
1728
1743
|
// Pale ash with char under it, not char with a pale rim.
|
|
1729
1744
|
//
|
|
@@ -1830,7 +1845,7 @@ function warnOnce(cause) {
|
|
|
1830
1845
|
if (warned) return;
|
|
1831
1846
|
warned = true;
|
|
1832
1847
|
console.warn(
|
|
1833
|
-
"[paperlab/fx] Rendering without FxPost
|
|
1848
|
+
"[paperlab/fx] Rendering without FxPost, so fire will not bloom.\nIt needs two optional peers:\n npm i @react-three/postprocessing postprocessing\nLeave <FxPost> out to go without it deliberately and silence this.",
|
|
1834
1849
|
...cause === void 0 ? [] : [cause]
|
|
1835
1850
|
);
|
|
1836
1851
|
}
|
|
@@ -1885,7 +1900,7 @@ var Afterglow = class {
|
|
|
1885
1900
|
}
|
|
1886
1901
|
/**
|
|
1887
1902
|
* Advance by `dt`, after the field has stepped. `blow` is the breath on the
|
|
1888
|
-
* sheet, 0..1: it flares what is left (
|
|
1903
|
+
* sheet, 0..1: it flares what is left ("on smoulder: beads flare,
|
|
1889
1904
|
* then fade").
|
|
1890
1905
|
*/
|
|
1891
1906
|
step(dt, blow = 0) {
|
|
@@ -2814,10 +2829,17 @@ void main() {
|
|
|
2814
2829
|
float rootMask = smoothstep(mix(0.4, 0.02, uRootReach), mix(0.6, 0.12, uRootReach), fuel) * (1.0 - smoothstep(0.25, 0.6, t));
|
|
2815
2830
|
fire += uRootColor * rootMask * uRootAmount * uPaperWhite * uGlow * 0.6;
|
|
2816
2831
|
|
|
2817
|
-
// Smoke:
|
|
2818
|
-
//
|
|
2832
|
+
// Smoke: grey-brown, warm (a cool grey over cream reads lavender), and LIT
|
|
2833
|
+
// BY THE FIRE UNDER IT rather than by nothing.
|
|
2834
|
+
//
|
|
2835
|
+
// The base used to be bright enough to read on its own \u2014 beige \u2014 which is
|
|
2836
|
+
// right while flames are lighting it and wrong the moment they stop: the
|
|
2837
|
+
// last of the smoke hung over a black stage as flat beige slabs, the same
|
|
2838
|
+
// colour everywhere, which reads as a filled shape rather than as smoke.
|
|
2839
|
+
// Dim at the base and warm where the fire is: smoke by
|
|
2840
|
+
// the flames looks as it did, smoke after them goes to a dim grey.
|
|
2819
2841
|
float smokeAlpha = 1.0 - exp(-smoke * uSmokeDensity);
|
|
2820
|
-
vec3 smokeColor = vec3(0.
|
|
2842
|
+
vec3 smokeColor = vec3(0.075, 0.068, 0.062) + vec3(0.3, 0.14, 0.045) * smoothstep(0.12, 0.9, t);
|
|
2821
2843
|
float alpha = smokeAlpha + fireAlpha - smokeAlpha * fireAlpha;
|
|
2822
2844
|
// No square edge: the domain fades out before its borders.
|
|
2823
2845
|
float edge = smoothstep(0.0, 0.06, vUv.x) * smoothstep(1.0, 0.94, vUv.x) * smoothstep(0.0, 0.03, vUv.y) * smoothstep(1.0, 0.9, vUv.y);
|
|
@@ -3203,7 +3225,7 @@ var fireFluidDefaults = {
|
|
|
3203
3225
|
smoke: 0.7,
|
|
3204
3226
|
// Was 5 — a full unit a second, 210 mm/s, of gas pushed sideways out of the
|
|
3205
3227
|
// rim. Against a buoyancy of 1.7 units/s² that is what rolled the flames
|
|
3206
|
-
// into mushroom caps: the grey spirals over the text that
|
|
3228
|
+
// into mushroom caps: the grey spirals over the text that a visual review called
|
|
3207
3229
|
// the most synthetic thing in the frame. A flame leaves paper going UP.
|
|
3208
3230
|
radialImpulse: 0.8,
|
|
3209
3231
|
// In world units a second now (see `solverUniforms`): 1.4 is ~0.3 m/s, the
|
|
@@ -3236,7 +3258,7 @@ var fireFluidDefaults = {
|
|
|
3236
3258
|
// reached the text (~150 mm), 1.6 ~100 mm, 2.1 short licks (~60 mm);
|
|
3237
3259
|
// Hero.png's tallest is ~70 mm.
|
|
3238
3260
|
cooling: 1.8,
|
|
3239
|
-
// Was 1.4, which made a thick grey column, then 0.5.
|
|
3261
|
+
// Was 1.4, which made a thick grey column, then 0.5. The direction is
|
|
3240
3262
|
// light smoke, and with MacCormack advection keeping the smoke's fine
|
|
3241
3263
|
// structure it needs less of it to read: at 0.5 it veiled the upper sheet.
|
|
3242
3264
|
smokeProduction: 0.25,
|
|
@@ -3617,56 +3639,262 @@ function FxFireFluid({
|
|
|
3617
3639
|
var THREE8 = __toESM(require("three"), 1);
|
|
3618
3640
|
var import_fiber6 = require("@react-three/fiber");
|
|
3619
3641
|
var import_react7 = require("react");
|
|
3642
|
+
|
|
3643
|
+
// src/fx/fireState.ts
|
|
3620
3644
|
init_flames();
|
|
3621
|
-
var
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3645
|
+
var FLAMES = 32;
|
|
3646
|
+
var FIRE_CLUSTERS = 4;
|
|
3647
|
+
var CLUSTER_REACH = 25 / 210;
|
|
3648
|
+
var FireState = class {
|
|
3649
|
+
constructor(field, locate) {
|
|
3650
|
+
this.field = field;
|
|
3651
|
+
this.locate = locate;
|
|
3652
|
+
}
|
|
3653
|
+
field;
|
|
3654
|
+
locate;
|
|
3655
|
+
/** The flames it was taken over. Only the first {@link count} are current. */
|
|
3656
|
+
anchors = [];
|
|
3657
|
+
count = 0;
|
|
3658
|
+
/** Where the fire is, as a whole: its flames' mean. Zero with none. */
|
|
3659
|
+
x = 0;
|
|
3660
|
+
y = 0;
|
|
3661
|
+
z = 0;
|
|
3662
|
+
/** Its flames' mean height. */
|
|
3663
|
+
height = 0;
|
|
3664
|
+
/** Its flames' mean puff. */
|
|
3665
|
+
flicker = 0;
|
|
3666
|
+
/** Where it is gathered. Only the first {@link clusterCount} are current. */
|
|
3667
|
+
clusters = [];
|
|
3668
|
+
clusterCount = 0;
|
|
3669
|
+
frame = Number.NaN;
|
|
3670
|
+
order = [];
|
|
3671
|
+
root = new Float64Array(FIRE_CLUSTERS * 3);
|
|
3672
|
+
weight = new Float64Array(FIRE_CLUSTERS);
|
|
3673
|
+
/**
|
|
3674
|
+
* Bring it up to date for `frame` — any number that is the same for every
|
|
3675
|
+
* reader in one frame and different in the next; R3F's `clock.elapsedTime`
|
|
3676
|
+
* is the one in hand. Free the second time it is asked in a frame.
|
|
3677
|
+
*/
|
|
3678
|
+
update(frame) {
|
|
3679
|
+
if (frame === this.frame) return this;
|
|
3680
|
+
this.frame = frame;
|
|
3681
|
+
const { field, anchors } = this;
|
|
3682
|
+
const n = flameAnchors(field, this.locate, FLAMES, anchors);
|
|
3683
|
+
this.count = n;
|
|
3636
3684
|
let x = 0;
|
|
3637
3685
|
let y = 0;
|
|
3638
3686
|
let z = 0;
|
|
3639
3687
|
let h = 0;
|
|
3640
3688
|
let flicker = 0;
|
|
3641
3689
|
for (let i = 0; i < n; i++) {
|
|
3642
|
-
const f = anchors
|
|
3690
|
+
const f = anchors[i];
|
|
3643
3691
|
x += f.x;
|
|
3644
3692
|
y += f.y;
|
|
3645
3693
|
z += f.z;
|
|
3646
3694
|
h += f.height;
|
|
3647
3695
|
flicker += flamePuff(f.seed, field.time);
|
|
3648
3696
|
}
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3697
|
+
if (n > 0) {
|
|
3698
|
+
x /= n;
|
|
3699
|
+
y /= n;
|
|
3700
|
+
z /= n;
|
|
3701
|
+
h /= n;
|
|
3702
|
+
flicker /= n;
|
|
3703
|
+
}
|
|
3704
|
+
this.x = x;
|
|
3705
|
+
this.y = y;
|
|
3706
|
+
this.z = z;
|
|
3707
|
+
this.height = h;
|
|
3708
|
+
this.flicker = flicker;
|
|
3709
|
+
this.gather();
|
|
3710
|
+
return this;
|
|
3711
|
+
}
|
|
3712
|
+
/**
|
|
3713
|
+
* Group the flames into at most {@link FIRE_CLUSTERS} clusters.
|
|
3714
|
+
*
|
|
3715
|
+
* Tallest first, so each cluster is named by its tallest tongue and stands
|
|
3716
|
+
* where that one stands: a flame joins the nearest cluster within
|
|
3717
|
+
* {@link CLUSTER_REACH} of it, or starts one while there is room, or joins
|
|
3718
|
+
* the nearest once there is not. Pure arithmetic over the flames, so the
|
|
3719
|
+
* same burn gathers the same way every time.
|
|
3720
|
+
*/
|
|
3721
|
+
gather() {
|
|
3722
|
+
const anchors = this.anchors;
|
|
3723
|
+
const clusters = this.clusters;
|
|
3724
|
+
const order = this.order;
|
|
3725
|
+
const root2 = this.root;
|
|
3726
|
+
const weight = this.weight;
|
|
3727
|
+
const n = this.count;
|
|
3728
|
+
order.length = 0;
|
|
3729
|
+
for (let i = 0; i < n; i++) order.push(i);
|
|
3730
|
+
order.sort((i, j) => anchors[j].height - anchors[i].height || i - j);
|
|
3731
|
+
let k = 0;
|
|
3732
|
+
for (const i of order) {
|
|
3733
|
+
const f = anchors[i];
|
|
3734
|
+
let best = -1;
|
|
3735
|
+
let near = Number.POSITIVE_INFINITY;
|
|
3736
|
+
for (let c = 0; c < k; c++) {
|
|
3737
|
+
const d = Math.hypot(f.x - root2[c * 3], f.y - root2[c * 3 + 1], f.z - root2[c * 3 + 2]);
|
|
3738
|
+
if (d < near) {
|
|
3739
|
+
near = d;
|
|
3740
|
+
best = c;
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
if (best < 0 || near > CLUSTER_REACH && k < FIRE_CLUSTERS) {
|
|
3744
|
+
best = k++;
|
|
3745
|
+
root2[best * 3] = f.x;
|
|
3746
|
+
root2[best * 3 + 1] = f.y;
|
|
3747
|
+
root2[best * 3 + 2] = f.z;
|
|
3748
|
+
weight[best] = 0;
|
|
3749
|
+
let cluster2 = clusters[best];
|
|
3750
|
+
if (!cluster2) {
|
|
3751
|
+
cluster2 = { x: 0, y: 0, z: 0, height: 0, tallest: 0, flicker: 0, heat: 0, count: 0 };
|
|
3752
|
+
clusters[best] = cluster2;
|
|
3753
|
+
}
|
|
3754
|
+
cluster2.x = 0;
|
|
3755
|
+
cluster2.y = 0;
|
|
3756
|
+
cluster2.z = 0;
|
|
3757
|
+
cluster2.height = 0;
|
|
3758
|
+
cluster2.tallest = f.height;
|
|
3759
|
+
cluster2.flicker = 0;
|
|
3760
|
+
cluster2.heat = 0;
|
|
3761
|
+
cluster2.count = 0;
|
|
3762
|
+
}
|
|
3763
|
+
const cluster = clusters[best];
|
|
3764
|
+
const w = f.height;
|
|
3765
|
+
cluster.x += f.x * w;
|
|
3766
|
+
cluster.y += f.y * w;
|
|
3767
|
+
cluster.z += f.z * w;
|
|
3768
|
+
weight[best] = weight[best] + w;
|
|
3769
|
+
cluster.height += f.height;
|
|
3770
|
+
cluster.flicker += flamePuff(f.seed, this.field.time);
|
|
3771
|
+
cluster.heat += f.heat;
|
|
3772
|
+
cluster.count++;
|
|
3773
|
+
}
|
|
3774
|
+
for (let c = 0; c < k; c++) {
|
|
3775
|
+
const cluster = clusters[c];
|
|
3776
|
+
const w = weight[c];
|
|
3777
|
+
if (w > 0) {
|
|
3778
|
+
cluster.x /= w;
|
|
3779
|
+
cluster.y /= w;
|
|
3780
|
+
cluster.z /= w;
|
|
3781
|
+
} else {
|
|
3782
|
+
cluster.x = root2[c * 3];
|
|
3783
|
+
cluster.y = root2[c * 3 + 1];
|
|
3784
|
+
cluster.z = root2[c * 3 + 2];
|
|
3785
|
+
}
|
|
3786
|
+
cluster.height /= cluster.count;
|
|
3787
|
+
cluster.flicker /= cluster.count;
|
|
3788
|
+
cluster.heat /= cluster.count;
|
|
3789
|
+
}
|
|
3790
|
+
this.clusterCount = k;
|
|
3791
|
+
}
|
|
3792
|
+
};
|
|
3793
|
+
var shared = /* @__PURE__ */ new WeakMap();
|
|
3794
|
+
function fireStateOf(field, locate) {
|
|
3795
|
+
let byLocator = shared.get(field);
|
|
3796
|
+
if (!byLocator) {
|
|
3797
|
+
byLocator = /* @__PURE__ */ new WeakMap();
|
|
3798
|
+
shared.set(field, byLocator);
|
|
3799
|
+
}
|
|
3800
|
+
let state = byLocator.get(locate);
|
|
3801
|
+
if (!state) {
|
|
3802
|
+
state = new FireState(field, locate);
|
|
3803
|
+
byLocator.set(locate, state);
|
|
3804
|
+
}
|
|
3805
|
+
return state;
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
// src/fx/FxFireLight.tsx
|
|
3809
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
3810
|
+
function FxFireLight({ field, locate, gain = FIRE_LIGHT_GAIN, shadows = false }) {
|
|
3811
|
+
const front = (0, import_react7.useRef)([]);
|
|
3812
|
+
const through = (0, import_react7.useRef)(null);
|
|
3813
|
+
const spot = (0, import_react7.useRef)(null);
|
|
3814
|
+
(0, import_fiber6.useFrame)(({ camera, clock }) => {
|
|
3815
|
+
const b = through.current;
|
|
3816
|
+
if (!b) return;
|
|
3817
|
+
const fire = fireStateOf(field, locate).update(clock.elapsedTime);
|
|
3818
|
+
const level = field.lastStats.front * gain;
|
|
3819
|
+
let total = 0;
|
|
3820
|
+
let brightest = -1;
|
|
3821
|
+
let most = 0;
|
|
3822
|
+
for (let c = 0; c < fire.clusterCount; c++) {
|
|
3823
|
+
const cluster = fire.clusters[c];
|
|
3824
|
+
const share = cluster.count * cluster.height;
|
|
3825
|
+
total += share;
|
|
3826
|
+
if (share > most) {
|
|
3827
|
+
most = share;
|
|
3828
|
+
brightest = c;
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
const s = shadows ? spot.current : null;
|
|
3832
|
+
if (s) s.intensity = 0;
|
|
3833
|
+
for (let i = 0; i < FIRE_CLUSTERS; i++) {
|
|
3834
|
+
const light = front.current[i];
|
|
3835
|
+
if (!light) continue;
|
|
3836
|
+
const cluster = i < fire.clusterCount ? fire.clusters[i] : null;
|
|
3837
|
+
if (!cluster || !(total > 0)) {
|
|
3838
|
+
light.intensity = 0;
|
|
3839
|
+
continue;
|
|
3840
|
+
}
|
|
3841
|
+
toward.set(camera.position.x - cluster.x, 0, camera.position.z - cluster.z).normalize().multiplyScalar(0.035);
|
|
3842
|
+
light.position.set(cluster.x + toward.x, cluster.y + cluster.height * 0.5, cluster.z + toward.z);
|
|
3843
|
+
light.intensity = level * cluster.flicker * (cluster.count * cluster.height / total);
|
|
3844
|
+
light.color.copy(COOLING).lerp(FIRE_COLOR, Math.min(1, Math.max(0, cluster.heat)));
|
|
3845
|
+
if (s && i === brightest) {
|
|
3846
|
+
s.position.copy(light.position);
|
|
3847
|
+
s.target.position.set(cluster.x, cluster.y, cluster.z);
|
|
3848
|
+
s.target.updateMatrixWorld();
|
|
3849
|
+
s.intensity = light.intensity;
|
|
3850
|
+
s.color.copy(light.color);
|
|
3851
|
+
light.intensity = 0;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
if (fire.count === 0) {
|
|
3855
|
+
b.intensity = 0;
|
|
3856
|
+
return;
|
|
3857
|
+
}
|
|
3858
|
+
toward.set(camera.position.x - fire.x, 0, camera.position.z - fire.z).normalize().multiplyScalar(0.035);
|
|
3859
|
+
b.position.set(fire.x - toward.x, fire.y + fire.height * 0.5, fire.z - toward.z);
|
|
3860
|
+
b.intensity = level * fire.flicker * 0.35;
|
|
3662
3861
|
});
|
|
3663
3862
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
3664
|
-
|
|
3863
|
+
Array.from({ length: FIRE_CLUSTERS }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3864
|
+
"pointLight",
|
|
3865
|
+
{
|
|
3866
|
+
ref: (light) => {
|
|
3867
|
+
front.current[i] = light;
|
|
3868
|
+
},
|
|
3869
|
+
color: FIRE_COLOR,
|
|
3870
|
+
intensity: 0,
|
|
3871
|
+
decay: 0.9,
|
|
3872
|
+
distance: 0
|
|
3873
|
+
},
|
|
3874
|
+
i
|
|
3875
|
+
)),
|
|
3876
|
+
shadows && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3877
|
+
"spotLight",
|
|
3878
|
+
{
|
|
3879
|
+
ref: spot,
|
|
3880
|
+
color: FIRE_COLOR,
|
|
3881
|
+
intensity: 0,
|
|
3882
|
+
decay: 0.9,
|
|
3883
|
+
distance: 0,
|
|
3884
|
+
angle: 1.3,
|
|
3885
|
+
penumbra: 0.8,
|
|
3886
|
+
castShadow: true,
|
|
3887
|
+
"shadow-mapSize": [512, 512],
|
|
3888
|
+
"shadow-bias": -4e-4
|
|
3889
|
+
}
|
|
3890
|
+
),
|
|
3665
3891
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("pointLight", { ref: through, color: FIRE_COLOR, intensity: 0, decay: 0.9, distance: 0 })
|
|
3666
3892
|
] });
|
|
3667
3893
|
}
|
|
3894
|
+
var toward = new THREE8.Vector3();
|
|
3668
3895
|
var FIRE_COLOR = new THREE8.Color("#ffa24c");
|
|
3669
|
-
var
|
|
3896
|
+
var COOLING = new THREE8.Color("#ff8a33");
|
|
3897
|
+
var FIRE_LIGHT_GAIN = 26;
|
|
3670
3898
|
|
|
3671
3899
|
// src/fx/FxMatchFlame.tsx
|
|
3672
3900
|
var THREE9 = __toESM(require("three"), 1);
|
|
@@ -3814,7 +4042,7 @@ var FxAudio = class {
|
|
|
3814
4042
|
constructor(options) {
|
|
3815
4043
|
const { quality = "auto", context, volume = 0.8 } = options;
|
|
3816
4044
|
this.quality = typeof quality === "string" ? fxQualityFor(quality) : quality;
|
|
3817
|
-
if (!context) throw new Error("FxAudio needs a context
|
|
4045
|
+
if (!context) throw new Error("FxAudio needs a context. See createAudioContext()");
|
|
3818
4046
|
this.ctx = context;
|
|
3819
4047
|
this.master = this.ctx.createGain();
|
|
3820
4048
|
this.master.gain.value = volume;
|
|
@@ -4074,18 +4302,18 @@ var FireSound = class {
|
|
|
4074
4302
|
}
|
|
4075
4303
|
/**
|
|
4076
4304
|
* A match struck: the scratch of the head across the box, then the hiss of
|
|
4077
|
-
* it flaring as the head burns off
|
|
4305
|
+
* it flaring as the head burns off. Two bursts of the shared
|
|
4078
4306
|
* noise, one bright and short, one breathier and longer.
|
|
4079
4307
|
*/
|
|
4080
4308
|
strike() {
|
|
4081
4309
|
this.burst("fire-strike", 0.9, { type: "bandpass", frequency: 3200, q: 1.1 }, 4e-3, 0.07, 0.8);
|
|
4082
4310
|
this.burst("fire-flare", 0.8, { type: "highpass", frequency: 1800, q: 0.7 }, 0.03, 0.45, 0.45, 0.05);
|
|
4083
4311
|
}
|
|
4084
|
-
/** Blown out: a soft, low breath of noise
|
|
4312
|
+
/** Blown out: a soft, low breath of noise. */
|
|
4085
4313
|
puff() {
|
|
4086
4314
|
this.burst("fire-puff", 0.6, { type: "lowpass", frequency: 520, q: 0.6 }, 0.02, 0.28, 0.55);
|
|
4087
4315
|
}
|
|
4088
|
-
/** One ember popping in the air — a tiny click on the frame it flashes
|
|
4316
|
+
/** One ember popping in the air — a tiny click on the frame it flashes. */
|
|
4089
4317
|
pop() {
|
|
4090
4318
|
const f = 3e3 + this.next() * 3e3;
|
|
4091
4319
|
this.burst(
|
|
@@ -4230,6 +4458,7 @@ init_quality();
|
|
|
4230
4458
|
FIELD_SIZE,
|
|
4231
4459
|
FIRE_GLOW,
|
|
4232
4460
|
FIRE_HEAT_SCALE,
|
|
4461
|
+
FIRE_LIGHT_GAIN,
|
|
4233
4462
|
FIRE_SOOT_SCALE,
|
|
4234
4463
|
FIRE_ZONES,
|
|
4235
4464
|
FIXED_DT,
|