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.js
CHANGED
|
@@ -31,11 +31,11 @@ import {
|
|
|
31
31
|
luminance,
|
|
32
32
|
srgbToLinear,
|
|
33
33
|
timesPaperWhite
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-S74YZAXE.js";
|
|
35
35
|
import {
|
|
36
36
|
DAMAGE_CHANNELS,
|
|
37
37
|
DAMAGE_LOOK_DEFAULTS
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-BIFUJCUA.js";
|
|
39
39
|
|
|
40
40
|
// src/fx/particles.ts
|
|
41
41
|
var particlePresets = {
|
|
@@ -47,7 +47,7 @@ var particlePresets = {
|
|
|
47
47
|
* caught the bug in the version before this one. That version said "the hot
|
|
48
48
|
* end is now ~5.6, well over the bloom threshold" — and 5.6 was an ABSOLUTE
|
|
49
49
|
* luminance, while paper white under `window` is 1.6. So the hottest spark
|
|
50
|
-
* in the frame was **3.5× paper, under the
|
|
50
|
+
* in the frame was **3.5× paper, under the 4× floor**, and its
|
|
51
51
|
* own comment said otherwise. A number that cannot be compared to anything
|
|
52
52
|
* is a number nobody can check.
|
|
53
53
|
*
|
|
@@ -67,12 +67,12 @@ var particlePresets = {
|
|
|
67
67
|
drag: 0.8,
|
|
68
68
|
jitter: 1.5,
|
|
69
69
|
windCatch: 0.8,
|
|
70
|
-
// 0.3-1 mm of core
|
|
70
|
+
// 0.3-1 mm of core. A sheet is one world unit across, 210 mm, so
|
|
71
71
|
// 0.0035 is 0.7 mm. It was 0.005 — a full millimetre wide before the
|
|
72
72
|
// streak stretched it, which is where "wide bars" started.
|
|
73
73
|
size: [35e-4, 12e-4],
|
|
74
74
|
color: [
|
|
75
|
-
// 6× paper white: inside
|
|
75
|
+
// 6× paper white: inside the 4–8× band, so it blooms.
|
|
76
76
|
emitHex("#FFC271", 6),
|
|
77
77
|
// 0.7× — UNDER the bloom threshold on purpose, so a dying spark stops
|
|
78
78
|
// glowing rather than merely fading.
|
|
@@ -90,7 +90,13 @@ var particlePresets = {
|
|
|
90
90
|
* simulation, on purpose; see the fx plan.
|
|
91
91
|
*/
|
|
92
92
|
smoke: {
|
|
93
|
-
|
|
93
|
+
// Shorter, and smaller below: these are the thread beside the
|
|
94
|
+
// simulator's smoke, and the simulator's is gone within a second or two
|
|
95
|
+
// of the last flame. Left to live three and a half seconds and grow to
|
|
96
|
+
// seven centimetres, the last few of them overlapped into a flat wash
|
|
97
|
+
// over the hole with nothing left to move them — the one moment of a
|
|
98
|
+
// burn where the smoke looked wrong.
|
|
99
|
+
life: [1.4, 2.6],
|
|
94
100
|
speed: [0.05, 0.15],
|
|
95
101
|
direction: [0, 1, 0],
|
|
96
102
|
spread: 0.3,
|
|
@@ -98,13 +104,13 @@ var particlePresets = {
|
|
|
98
104
|
drag: 0.9,
|
|
99
105
|
jitter: 0.3,
|
|
100
106
|
windCatch: 1,
|
|
101
|
-
size: [0.05, 0.
|
|
107
|
+
size: [0.05, 0.18],
|
|
102
108
|
// #6B6560 grey-brown, linear, and thin — the background stays clear.
|
|
103
109
|
color: [
|
|
104
110
|
[0.147, 0.13, 0.117],
|
|
105
111
|
[0.2, 0.19, 0.18]
|
|
106
112
|
],
|
|
107
|
-
alpha: [0.
|
|
113
|
+
alpha: [0.07, 0],
|
|
108
114
|
spin: 0.4,
|
|
109
115
|
blend: "normal",
|
|
110
116
|
shape: "soft",
|
|
@@ -116,7 +122,9 @@ var particlePresets = {
|
|
|
116
122
|
* burn that proves paper was there.
|
|
117
123
|
*/
|
|
118
124
|
ash: {
|
|
119
|
-
|
|
125
|
+
// A second or two: ash that hung in the air for five read as grey
|
|
126
|
+
// confetti over the hole, dust or stars rather than something falling.
|
|
127
|
+
life: [1.2, 2.6],
|
|
120
128
|
// Thrown UP by the heat it came off, before it is heavier than the air.
|
|
121
129
|
// It used to leave at almost nothing (0.02–0.1) with a lift of −0.35, so
|
|
122
130
|
// every flake began falling the moment it was born — straight through the
|
|
@@ -124,17 +132,21 @@ var particlePresets = {
|
|
|
124
132
|
speed: [0.14, 0.3],
|
|
125
133
|
direction: [0, 1, 0],
|
|
126
134
|
spread: 0.55,
|
|
127
|
-
|
|
135
|
+
// Heavier than the air once the throw is spent: most flakes tumble down,
|
|
136
|
+
// only the lightest ride the heat up a little first.
|
|
137
|
+
lift: -0.42,
|
|
128
138
|
drag: 1.6,
|
|
129
139
|
jitter: 1.2,
|
|
130
140
|
windCatch: 1,
|
|
131
|
-
// 5–10 mm across
|
|
141
|
+
// 5–10 mm across. A sheet is 210 mm, so 0.03 is 6.3 mm. They were
|
|
132
142
|
// 3.8 mm and there were far too many of them: a shower of specks rather
|
|
133
143
|
// than a few flakes you would notice.
|
|
134
144
|
size: [0.03, 0.026],
|
|
145
|
+
// Charred paper, near black: under a bright key the old value read as
|
|
146
|
+
// pale grey against the black stage, which is the confetti.
|
|
135
147
|
color: [
|
|
136
|
-
[0.
|
|
137
|
-
[0.
|
|
148
|
+
[0.04, 0.037, 0.034],
|
|
149
|
+
[0.07, 0.066, 0.062]
|
|
138
150
|
],
|
|
139
151
|
alpha: [0.95, 0],
|
|
140
152
|
spin: 4,
|
|
@@ -358,13 +370,14 @@ var ParticlePool = class {
|
|
|
358
370
|
|
|
359
371
|
// src/fx/fire.ts
|
|
360
372
|
var fireEmitterDefaults = {
|
|
361
|
-
embers: 0.
|
|
362
|
-
smoke: 0,
|
|
363
|
-
ash: 0.
|
|
373
|
+
embers: 0.39,
|
|
374
|
+
smoke: 0.135,
|
|
375
|
+
ash: 0.36,
|
|
364
376
|
seed: 7,
|
|
365
377
|
caps: {}
|
|
366
378
|
};
|
|
367
379
|
var PER_UPDATE = 24;
|
|
380
|
+
var CRACKLE_SPARKS = 0.06;
|
|
368
381
|
var FireEmitter = class {
|
|
369
382
|
field;
|
|
370
383
|
pool;
|
|
@@ -401,7 +414,10 @@ var FireEmitter = class {
|
|
|
401
414
|
heat /= front;
|
|
402
415
|
const struggle = 0.35 + 1.6 * Math.max(0, 0.7 - heat) + air * 0.8;
|
|
403
416
|
const oxygen = 1 + air * 1.5;
|
|
404
|
-
this.emberDebt = Math.min(
|
|
417
|
+
this.emberDebt = Math.min(
|
|
418
|
+
this.emberDebt + front * o.embers * oxygen * dt + field.lastStats.charred * o.embers * CRACKLE_SPARKS,
|
|
419
|
+
PER_UPDATE
|
|
420
|
+
);
|
|
405
421
|
this.smokeDebt = Math.min(this.smokeDebt + front * o.smoke * struggle * dt, PER_UPDATE);
|
|
406
422
|
while (this.emberDebt >= 1) {
|
|
407
423
|
this.emberDebt -= 1;
|
|
@@ -533,7 +549,7 @@ void main() {
|
|
|
533
549
|
// It used to be flat-topped across (smoothstep from 0.35, so the middle
|
|
534
550
|
// seventy per cent of the width was all at full brightness) and to keep a
|
|
535
551
|
// quarter of that brightness all the way to the end of the quad. Both
|
|
536
|
-
// together drew a wide bar with a squared-off end, which is what
|
|
552
|
+
// together drew a wide bar with a squared-off end, which is what a visual review
|
|
537
553
|
// saw up close. A spark is a point of light smeared by its own motion: it
|
|
538
554
|
// is brightest on its centre line and it runs out.
|
|
539
555
|
float across = abs(vQuad.x);
|
|
@@ -653,14 +669,14 @@ void main() {
|
|
|
653
669
|
float edge = 0.62 + 0.3 * flNoise(vec2(a * 1.6 + vSeed * 31.0, vSeed * 7.0));
|
|
654
670
|
float mask = 1.0 - smoothstep(edge - 0.06, edge, r);
|
|
655
671
|
if (mask <= 0.0) discard;
|
|
656
|
-
// Char in the middle, pale brittle ash at the edge
|
|
672
|
+
// Char in the middle, pale brittle ash at the edge \u2014 in patches
|
|
657
673
|
// along it, not all the way round: a flake is a few pixels across, and a
|
|
658
674
|
// continuous pale rim round a dark middle drew it as a hollow ring.
|
|
659
675
|
float rim = smoothstep(edge - 0.32, edge - 0.04, r);
|
|
660
676
|
float patches = smoothstep(0.45, 0.75, flNoise(vec2(a * 2.2 + vSeed * 17.0, vSeed * 13.0)));
|
|
661
677
|
float grain = flNoise(vQuad * 3.0 + vSeed * 23.0);
|
|
662
678
|
vec3 charC = vec3(0.05, 0.043, 0.038) * (0.7 + 0.6 * grain);
|
|
663
|
-
// #A49E9D,
|
|
679
|
+
// #A49E9D, sampled from the reference ash, in linear.
|
|
664
680
|
vec3 ashC = vec3(0.372, 0.344, 0.340);
|
|
665
681
|
// Pale ash with char under it, not char with a pale rim.
|
|
666
682
|
//
|
|
@@ -757,7 +773,7 @@ function FxParticles({ pool }) {
|
|
|
757
773
|
import { Suspense, lazy } from "react";
|
|
758
774
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
759
775
|
var Pass = lazy(
|
|
760
|
-
() => import("./FxPostPass-
|
|
776
|
+
() => import("./FxPostPass-ACFSHY3S.js").then((m) => ({ default: m.FxPostPass })).catch((error) => {
|
|
761
777
|
warnOnce(error);
|
|
762
778
|
return { default: (() => null) };
|
|
763
779
|
})
|
|
@@ -767,7 +783,7 @@ function warnOnce(cause) {
|
|
|
767
783
|
if (warned) return;
|
|
768
784
|
warned = true;
|
|
769
785
|
console.warn(
|
|
770
|
-
"[paperlab/fx] Rendering without FxPost
|
|
786
|
+
"[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.",
|
|
771
787
|
...cause === void 0 ? [] : [cause]
|
|
772
788
|
);
|
|
773
789
|
}
|
|
@@ -818,7 +834,7 @@ var Afterglow = class {
|
|
|
818
834
|
}
|
|
819
835
|
/**
|
|
820
836
|
* Advance by `dt`, after the field has stepped. `blow` is the breath on the
|
|
821
|
-
* sheet, 0..1: it flares what is left (
|
|
837
|
+
* sheet, 0..1: it flares what is left ("on smoulder: beads flare,
|
|
822
838
|
* then fade").
|
|
823
839
|
*/
|
|
824
840
|
step(dt, blow = 0) {
|
|
@@ -1742,10 +1758,17 @@ void main() {
|
|
|
1742
1758
|
float rootMask = smoothstep(mix(0.4, 0.02, uRootReach), mix(0.6, 0.12, uRootReach), fuel) * (1.0 - smoothstep(0.25, 0.6, t));
|
|
1743
1759
|
fire += uRootColor * rootMask * uRootAmount * uPaperWhite * uGlow * 0.6;
|
|
1744
1760
|
|
|
1745
|
-
// Smoke:
|
|
1746
|
-
//
|
|
1761
|
+
// Smoke: grey-brown, warm (a cool grey over cream reads lavender), and LIT
|
|
1762
|
+
// BY THE FIRE UNDER IT rather than by nothing.
|
|
1763
|
+
//
|
|
1764
|
+
// The base used to be bright enough to read on its own \u2014 beige \u2014 which is
|
|
1765
|
+
// right while flames are lighting it and wrong the moment they stop: the
|
|
1766
|
+
// last of the smoke hung over a black stage as flat beige slabs, the same
|
|
1767
|
+
// colour everywhere, which reads as a filled shape rather than as smoke.
|
|
1768
|
+
// Dim at the base and warm where the fire is: smoke by
|
|
1769
|
+
// the flames looks as it did, smoke after them goes to a dim grey.
|
|
1747
1770
|
float smokeAlpha = 1.0 - exp(-smoke * uSmokeDensity);
|
|
1748
|
-
vec3 smokeColor = vec3(0.
|
|
1771
|
+
vec3 smokeColor = vec3(0.075, 0.068, 0.062) + vec3(0.3, 0.14, 0.045) * smoothstep(0.12, 0.9, t);
|
|
1749
1772
|
float alpha = smokeAlpha + fireAlpha - smokeAlpha * fireAlpha;
|
|
1750
1773
|
// No square edge: the domain fades out before its borders.
|
|
1751
1774
|
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);
|
|
@@ -2131,7 +2154,7 @@ var fireFluidDefaults = {
|
|
|
2131
2154
|
smoke: 0.7,
|
|
2132
2155
|
// Was 5 — a full unit a second, 210 mm/s, of gas pushed sideways out of the
|
|
2133
2156
|
// rim. Against a buoyancy of 1.7 units/s² that is what rolled the flames
|
|
2134
|
-
// into mushroom caps: the grey spirals over the text that
|
|
2157
|
+
// into mushroom caps: the grey spirals over the text that a visual review called
|
|
2135
2158
|
// the most synthetic thing in the frame. A flame leaves paper going UP.
|
|
2136
2159
|
radialImpulse: 0.8,
|
|
2137
2160
|
// In world units a second now (see `solverUniforms`): 1.4 is ~0.3 m/s, the
|
|
@@ -2164,7 +2187,7 @@ var fireFluidDefaults = {
|
|
|
2164
2187
|
// reached the text (~150 mm), 1.6 ~100 mm, 2.1 short licks (~60 mm);
|
|
2165
2188
|
// Hero.png's tallest is ~70 mm.
|
|
2166
2189
|
cooling: 1.8,
|
|
2167
|
-
// Was 1.4, which made a thick grey column, then 0.5.
|
|
2190
|
+
// Was 1.4, which made a thick grey column, then 0.5. The direction is
|
|
2168
2191
|
// light smoke, and with MacCormack advection keeping the smoke's fine
|
|
2169
2192
|
// structure it needs less of it to read: at 0.5 it veiled the upper sheet.
|
|
2170
2193
|
smokeProduction: 0.25,
|
|
@@ -2544,55 +2567,261 @@ function FxFireFluid({
|
|
|
2544
2567
|
import * as THREE6 from "three";
|
|
2545
2568
|
import { useFrame as useFrame5 } from "@react-three/fiber";
|
|
2546
2569
|
import { useRef as useRef4 } from "react";
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2570
|
+
|
|
2571
|
+
// src/fx/fireState.ts
|
|
2572
|
+
var FLAMES = 32;
|
|
2573
|
+
var FIRE_CLUSTERS = 4;
|
|
2574
|
+
var CLUSTER_REACH = 25 / 210;
|
|
2575
|
+
var FireState = class {
|
|
2576
|
+
constructor(field, locate) {
|
|
2577
|
+
this.field = field;
|
|
2578
|
+
this.locate = locate;
|
|
2579
|
+
}
|
|
2580
|
+
field;
|
|
2581
|
+
locate;
|
|
2582
|
+
/** The flames it was taken over. Only the first {@link count} are current. */
|
|
2583
|
+
anchors = [];
|
|
2584
|
+
count = 0;
|
|
2585
|
+
/** Where the fire is, as a whole: its flames' mean. Zero with none. */
|
|
2586
|
+
x = 0;
|
|
2587
|
+
y = 0;
|
|
2588
|
+
z = 0;
|
|
2589
|
+
/** Its flames' mean height. */
|
|
2590
|
+
height = 0;
|
|
2591
|
+
/** Its flames' mean puff. */
|
|
2592
|
+
flicker = 0;
|
|
2593
|
+
/** Where it is gathered. Only the first {@link clusterCount} are current. */
|
|
2594
|
+
clusters = [];
|
|
2595
|
+
clusterCount = 0;
|
|
2596
|
+
frame = Number.NaN;
|
|
2597
|
+
order = [];
|
|
2598
|
+
root = new Float64Array(FIRE_CLUSTERS * 3);
|
|
2599
|
+
weight = new Float64Array(FIRE_CLUSTERS);
|
|
2600
|
+
/**
|
|
2601
|
+
* Bring it up to date for `frame` — any number that is the same for every
|
|
2602
|
+
* reader in one frame and different in the next; R3F's `clock.elapsedTime`
|
|
2603
|
+
* is the one in hand. Free the second time it is asked in a frame.
|
|
2604
|
+
*/
|
|
2605
|
+
update(frame) {
|
|
2606
|
+
if (frame === this.frame) return this;
|
|
2607
|
+
this.frame = frame;
|
|
2608
|
+
const { field, anchors } = this;
|
|
2609
|
+
const n = flameAnchors(field, this.locate, FLAMES, anchors);
|
|
2610
|
+
this.count = n;
|
|
2562
2611
|
let x = 0;
|
|
2563
2612
|
let y = 0;
|
|
2564
2613
|
let z = 0;
|
|
2565
2614
|
let h = 0;
|
|
2566
2615
|
let flicker = 0;
|
|
2567
2616
|
for (let i = 0; i < n; i++) {
|
|
2568
|
-
const f = anchors
|
|
2617
|
+
const f = anchors[i];
|
|
2569
2618
|
x += f.x;
|
|
2570
2619
|
y += f.y;
|
|
2571
2620
|
z += f.z;
|
|
2572
2621
|
h += f.height;
|
|
2573
2622
|
flicker += flamePuff(f.seed, field.time);
|
|
2574
2623
|
}
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2624
|
+
if (n > 0) {
|
|
2625
|
+
x /= n;
|
|
2626
|
+
y /= n;
|
|
2627
|
+
z /= n;
|
|
2628
|
+
h /= n;
|
|
2629
|
+
flicker /= n;
|
|
2630
|
+
}
|
|
2631
|
+
this.x = x;
|
|
2632
|
+
this.y = y;
|
|
2633
|
+
this.z = z;
|
|
2634
|
+
this.height = h;
|
|
2635
|
+
this.flicker = flicker;
|
|
2636
|
+
this.gather();
|
|
2637
|
+
return this;
|
|
2638
|
+
}
|
|
2639
|
+
/**
|
|
2640
|
+
* Group the flames into at most {@link FIRE_CLUSTERS} clusters.
|
|
2641
|
+
*
|
|
2642
|
+
* Tallest first, so each cluster is named by its tallest tongue and stands
|
|
2643
|
+
* where that one stands: a flame joins the nearest cluster within
|
|
2644
|
+
* {@link CLUSTER_REACH} of it, or starts one while there is room, or joins
|
|
2645
|
+
* the nearest once there is not. Pure arithmetic over the flames, so the
|
|
2646
|
+
* same burn gathers the same way every time.
|
|
2647
|
+
*/
|
|
2648
|
+
gather() {
|
|
2649
|
+
const anchors = this.anchors;
|
|
2650
|
+
const clusters = this.clusters;
|
|
2651
|
+
const order = this.order;
|
|
2652
|
+
const root = this.root;
|
|
2653
|
+
const weight = this.weight;
|
|
2654
|
+
const n = this.count;
|
|
2655
|
+
order.length = 0;
|
|
2656
|
+
for (let i = 0; i < n; i++) order.push(i);
|
|
2657
|
+
order.sort((i, j) => anchors[j].height - anchors[i].height || i - j);
|
|
2658
|
+
let k = 0;
|
|
2659
|
+
for (const i of order) {
|
|
2660
|
+
const f = anchors[i];
|
|
2661
|
+
let best = -1;
|
|
2662
|
+
let near = Number.POSITIVE_INFINITY;
|
|
2663
|
+
for (let c = 0; c < k; c++) {
|
|
2664
|
+
const d = Math.hypot(f.x - root[c * 3], f.y - root[c * 3 + 1], f.z - root[c * 3 + 2]);
|
|
2665
|
+
if (d < near) {
|
|
2666
|
+
near = d;
|
|
2667
|
+
best = c;
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
if (best < 0 || near > CLUSTER_REACH && k < FIRE_CLUSTERS) {
|
|
2671
|
+
best = k++;
|
|
2672
|
+
root[best * 3] = f.x;
|
|
2673
|
+
root[best * 3 + 1] = f.y;
|
|
2674
|
+
root[best * 3 + 2] = f.z;
|
|
2675
|
+
weight[best] = 0;
|
|
2676
|
+
let cluster2 = clusters[best];
|
|
2677
|
+
if (!cluster2) {
|
|
2678
|
+
cluster2 = { x: 0, y: 0, z: 0, height: 0, tallest: 0, flicker: 0, heat: 0, count: 0 };
|
|
2679
|
+
clusters[best] = cluster2;
|
|
2680
|
+
}
|
|
2681
|
+
cluster2.x = 0;
|
|
2682
|
+
cluster2.y = 0;
|
|
2683
|
+
cluster2.z = 0;
|
|
2684
|
+
cluster2.height = 0;
|
|
2685
|
+
cluster2.tallest = f.height;
|
|
2686
|
+
cluster2.flicker = 0;
|
|
2687
|
+
cluster2.heat = 0;
|
|
2688
|
+
cluster2.count = 0;
|
|
2689
|
+
}
|
|
2690
|
+
const cluster = clusters[best];
|
|
2691
|
+
const w = f.height;
|
|
2692
|
+
cluster.x += f.x * w;
|
|
2693
|
+
cluster.y += f.y * w;
|
|
2694
|
+
cluster.z += f.z * w;
|
|
2695
|
+
weight[best] = weight[best] + w;
|
|
2696
|
+
cluster.height += f.height;
|
|
2697
|
+
cluster.flicker += flamePuff(f.seed, this.field.time);
|
|
2698
|
+
cluster.heat += f.heat;
|
|
2699
|
+
cluster.count++;
|
|
2700
|
+
}
|
|
2701
|
+
for (let c = 0; c < k; c++) {
|
|
2702
|
+
const cluster = clusters[c];
|
|
2703
|
+
const w = weight[c];
|
|
2704
|
+
if (w > 0) {
|
|
2705
|
+
cluster.x /= w;
|
|
2706
|
+
cluster.y /= w;
|
|
2707
|
+
cluster.z /= w;
|
|
2708
|
+
} else {
|
|
2709
|
+
cluster.x = root[c * 3];
|
|
2710
|
+
cluster.y = root[c * 3 + 1];
|
|
2711
|
+
cluster.z = root[c * 3 + 2];
|
|
2712
|
+
}
|
|
2713
|
+
cluster.height /= cluster.count;
|
|
2714
|
+
cluster.flicker /= cluster.count;
|
|
2715
|
+
cluster.heat /= cluster.count;
|
|
2716
|
+
}
|
|
2717
|
+
this.clusterCount = k;
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
var shared = /* @__PURE__ */ new WeakMap();
|
|
2721
|
+
function fireStateOf(field, locate) {
|
|
2722
|
+
let byLocator = shared.get(field);
|
|
2723
|
+
if (!byLocator) {
|
|
2724
|
+
byLocator = /* @__PURE__ */ new WeakMap();
|
|
2725
|
+
shared.set(field, byLocator);
|
|
2726
|
+
}
|
|
2727
|
+
let state = byLocator.get(locate);
|
|
2728
|
+
if (!state) {
|
|
2729
|
+
state = new FireState(field, locate);
|
|
2730
|
+
byLocator.set(locate, state);
|
|
2731
|
+
}
|
|
2732
|
+
return state;
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
// src/fx/FxFireLight.tsx
|
|
2736
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2737
|
+
function FxFireLight({ field, locate, gain = FIRE_LIGHT_GAIN, shadows = false }) {
|
|
2738
|
+
const front = useRef4([]);
|
|
2739
|
+
const through = useRef4(null);
|
|
2740
|
+
const spot = useRef4(null);
|
|
2741
|
+
useFrame5(({ camera, clock }) => {
|
|
2742
|
+
const b = through.current;
|
|
2743
|
+
if (!b) return;
|
|
2744
|
+
const fire = fireStateOf(field, locate).update(clock.elapsedTime);
|
|
2745
|
+
const level = field.lastStats.front * gain;
|
|
2746
|
+
let total = 0;
|
|
2747
|
+
let brightest = -1;
|
|
2748
|
+
let most = 0;
|
|
2749
|
+
for (let c = 0; c < fire.clusterCount; c++) {
|
|
2750
|
+
const cluster = fire.clusters[c];
|
|
2751
|
+
const share = cluster.count * cluster.height;
|
|
2752
|
+
total += share;
|
|
2753
|
+
if (share > most) {
|
|
2754
|
+
most = share;
|
|
2755
|
+
brightest = c;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
const s = shadows ? spot.current : null;
|
|
2759
|
+
if (s) s.intensity = 0;
|
|
2760
|
+
for (let i = 0; i < FIRE_CLUSTERS; i++) {
|
|
2761
|
+
const light = front.current[i];
|
|
2762
|
+
if (!light) continue;
|
|
2763
|
+
const cluster = i < fire.clusterCount ? fire.clusters[i] : null;
|
|
2764
|
+
if (!cluster || !(total > 0)) {
|
|
2765
|
+
light.intensity = 0;
|
|
2766
|
+
continue;
|
|
2767
|
+
}
|
|
2768
|
+
toward.set(camera.position.x - cluster.x, 0, camera.position.z - cluster.z).normalize().multiplyScalar(0.035);
|
|
2769
|
+
light.position.set(cluster.x + toward.x, cluster.y + cluster.height * 0.5, cluster.z + toward.z);
|
|
2770
|
+
light.intensity = level * cluster.flicker * (cluster.count * cluster.height / total);
|
|
2771
|
+
light.color.copy(COOLING).lerp(FIRE_COLOR, Math.min(1, Math.max(0, cluster.heat)));
|
|
2772
|
+
if (s && i === brightest) {
|
|
2773
|
+
s.position.copy(light.position);
|
|
2774
|
+
s.target.position.set(cluster.x, cluster.y, cluster.z);
|
|
2775
|
+
s.target.updateMatrixWorld();
|
|
2776
|
+
s.intensity = light.intensity;
|
|
2777
|
+
s.color.copy(light.color);
|
|
2778
|
+
light.intensity = 0;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
if (fire.count === 0) {
|
|
2782
|
+
b.intensity = 0;
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
toward.set(camera.position.x - fire.x, 0, camera.position.z - fire.z).normalize().multiplyScalar(0.035);
|
|
2786
|
+
b.position.set(fire.x - toward.x, fire.y + fire.height * 0.5, fire.z - toward.z);
|
|
2787
|
+
b.intensity = level * fire.flicker * 0.35;
|
|
2588
2788
|
});
|
|
2589
2789
|
return /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
2590
|
-
|
|
2790
|
+
Array.from({ length: FIRE_CLUSTERS }, (_, i) => /* @__PURE__ */ jsx6(
|
|
2791
|
+
"pointLight",
|
|
2792
|
+
{
|
|
2793
|
+
ref: (light) => {
|
|
2794
|
+
front.current[i] = light;
|
|
2795
|
+
},
|
|
2796
|
+
color: FIRE_COLOR,
|
|
2797
|
+
intensity: 0,
|
|
2798
|
+
decay: 0.9,
|
|
2799
|
+
distance: 0
|
|
2800
|
+
},
|
|
2801
|
+
i
|
|
2802
|
+
)),
|
|
2803
|
+
shadows && /* @__PURE__ */ jsx6(
|
|
2804
|
+
"spotLight",
|
|
2805
|
+
{
|
|
2806
|
+
ref: spot,
|
|
2807
|
+
color: FIRE_COLOR,
|
|
2808
|
+
intensity: 0,
|
|
2809
|
+
decay: 0.9,
|
|
2810
|
+
distance: 0,
|
|
2811
|
+
angle: 1.3,
|
|
2812
|
+
penumbra: 0.8,
|
|
2813
|
+
castShadow: true,
|
|
2814
|
+
"shadow-mapSize": [512, 512],
|
|
2815
|
+
"shadow-bias": -4e-4
|
|
2816
|
+
}
|
|
2817
|
+
),
|
|
2591
2818
|
/* @__PURE__ */ jsx6("pointLight", { ref: through, color: FIRE_COLOR, intensity: 0, decay: 0.9, distance: 0 })
|
|
2592
2819
|
] });
|
|
2593
2820
|
}
|
|
2821
|
+
var toward = new THREE6.Vector3();
|
|
2594
2822
|
var FIRE_COLOR = new THREE6.Color("#ffa24c");
|
|
2595
|
-
var
|
|
2823
|
+
var COOLING = new THREE6.Color("#ff8a33");
|
|
2824
|
+
var FIRE_LIGHT_GAIN = 26;
|
|
2596
2825
|
|
|
2597
2826
|
// src/fx/FxMatchFlame.tsx
|
|
2598
2827
|
import * as THREE7 from "three";
|
|
@@ -2736,7 +2965,7 @@ var FxAudio = class {
|
|
|
2736
2965
|
constructor(options) {
|
|
2737
2966
|
const { quality = "auto", context, volume = 0.8 } = options;
|
|
2738
2967
|
this.quality = typeof quality === "string" ? fxQualityFor(quality) : quality;
|
|
2739
|
-
if (!context) throw new Error("FxAudio needs a context
|
|
2968
|
+
if (!context) throw new Error("FxAudio needs a context. See createAudioContext()");
|
|
2740
2969
|
this.ctx = context;
|
|
2741
2970
|
this.master = this.ctx.createGain();
|
|
2742
2971
|
this.master.gain.value = volume;
|
|
@@ -2996,18 +3225,18 @@ var FireSound = class {
|
|
|
2996
3225
|
}
|
|
2997
3226
|
/**
|
|
2998
3227
|
* A match struck: the scratch of the head across the box, then the hiss of
|
|
2999
|
-
* it flaring as the head burns off
|
|
3228
|
+
* it flaring as the head burns off. Two bursts of the shared
|
|
3000
3229
|
* noise, one bright and short, one breathier and longer.
|
|
3001
3230
|
*/
|
|
3002
3231
|
strike() {
|
|
3003
3232
|
this.burst("fire-strike", 0.9, { type: "bandpass", frequency: 3200, q: 1.1 }, 4e-3, 0.07, 0.8);
|
|
3004
3233
|
this.burst("fire-flare", 0.8, { type: "highpass", frequency: 1800, q: 0.7 }, 0.03, 0.45, 0.45, 0.05);
|
|
3005
3234
|
}
|
|
3006
|
-
/** Blown out: a soft, low breath of noise
|
|
3235
|
+
/** Blown out: a soft, low breath of noise. */
|
|
3007
3236
|
puff() {
|
|
3008
3237
|
this.burst("fire-puff", 0.6, { type: "lowpass", frequency: 520, q: 0.6 }, 0.02, 0.28, 0.55);
|
|
3009
3238
|
}
|
|
3010
|
-
/** One ember popping in the air — a tiny click on the frame it flashes
|
|
3239
|
+
/** One ember popping in the air — a tiny click on the frame it flashes. */
|
|
3011
3240
|
pop() {
|
|
3012
3241
|
const f = 3e3 + this.next() * 3e3;
|
|
3013
3242
|
this.burst(
|
|
@@ -3148,6 +3377,7 @@ export {
|
|
|
3148
3377
|
FIELD_SIZE,
|
|
3149
3378
|
FIRE_GLOW,
|
|
3150
3379
|
FIRE_HEAT_SCALE,
|
|
3380
|
+
FIRE_LIGHT_GAIN,
|
|
3151
3381
|
FIRE_SOOT_SCALE,
|
|
3152
3382
|
FIRE_ZONES,
|
|
3153
3383
|
FIXED_DT,
|