@wave3d/core 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -6
- package/dist/config/model.d.ts +97 -9
- package/dist/config/model.js +165 -61
- package/dist/config/model.js.map +1 -1
- package/dist/core-loader.d.ts +0 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/presets.d.ts +8 -3
- package/dist/presets.js +542 -1
- package/dist/presets.js.map +1 -1
- package/dist/renderer/WaveGeometry.d.ts +22 -4
- package/dist/renderer/WaveGeometry.js +22 -3
- package/dist/renderer/WaveGeometry.js.map +1 -1
- package/dist/renderer/WaveRenderer.d.ts +22 -2
- package/dist/renderer/WaveRenderer.js +118 -3
- package/dist/renderer/WaveRenderer.js.map +1 -1
- package/dist/renderer/heroPalette.d.ts +0 -1
- package/dist/renderer/interaction.d.ts +0 -2
- package/dist/renderer/interaction.js +9 -0
- package/dist/renderer/interaction.js.map +1 -1
- package/dist/renderer/palette.d.ts +1 -2
- package/dist/renderer/palette.js +7 -5
- package/dist/renderer/palette.js.map +1 -1
- package/dist/renderer/particleField.d.ts +50 -0
- package/dist/renderer/particleField.js +220 -0
- package/dist/renderer/particleField.js.map +1 -0
- package/dist/renderer/shaders.js +271 -67
- package/dist/renderer/shaders.js.map +1 -1
- package/dist/shell/createWave.d.ts +0 -1
- package/dist/standalone/wave3d.standalone.js +2868 -1950
- package/dist/standalone.d.ts +2 -3
- package/dist/standalone.js +2 -2
- package/dist/studio/StudioWaveRenderer.d.ts +0 -1
- package/dist/studio/randomize.d.ts +0 -1
- package/dist/studio/thumbnail.d.ts +2 -3
- package/dist/studio/thumbnail.js +16 -3
- package/dist/studio/thumbnail.js.map +1 -1
- package/package.json +9 -5
- package/skills/wave3d/SKILL.md +30 -12
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { particleFragmentShader, particleVertexShader } from "./shaders.js";
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
//#region src/renderer/particleField.ts
|
|
4
|
+
/** Deterministic PRNG (mulberry32): a pure function of the seed, so a `(count, seed)` layout
|
|
5
|
+
* reproduces exactly. NEVER Math.random — that would desync timeOffset scrub / loop / paused. */
|
|
6
|
+
function mulberry32(seed) {
|
|
7
|
+
let a = seed >>> 0;
|
|
8
|
+
return () => {
|
|
9
|
+
a |= 0;
|
|
10
|
+
a = a + 1831565813 | 0;
|
|
11
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
12
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
13
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const HEX_SCRATCH = new THREE.Color();
|
|
17
|
+
function setLinear(target, hex) {
|
|
18
|
+
const c = HEX_SCRATCH.set(hex);
|
|
19
|
+
target.set(c.r, c.g, c.b);
|
|
20
|
+
}
|
|
21
|
+
const DEFAULT_COLOR = "#ffcf8a";
|
|
22
|
+
/** Sprite-shape name → the int the fragment shader branches on (see particleFragmentShader). */
|
|
23
|
+
const SHAPE_INDEX = {
|
|
24
|
+
glitter: 0,
|
|
25
|
+
soft: 1,
|
|
26
|
+
ring: 2,
|
|
27
|
+
star: 3,
|
|
28
|
+
streak: 4
|
|
29
|
+
};
|
|
30
|
+
/** The owning wave's shape uniforms mirrored onto the particle material so the dust rides the same
|
|
31
|
+
* deform as the ribbon. Names match the wave material's uniforms 1:1 (copied by value in configure). */
|
|
32
|
+
const SHAPE_UNIFORMS = [
|
|
33
|
+
"uDispFreqX",
|
|
34
|
+
"uDispFreqZ",
|
|
35
|
+
"uDispAmount",
|
|
36
|
+
"uDetailFreq",
|
|
37
|
+
"uDetailAmount",
|
|
38
|
+
"uTwFreqX",
|
|
39
|
+
"uTwFreqY",
|
|
40
|
+
"uTwFreqZ",
|
|
41
|
+
"uTwPowX",
|
|
42
|
+
"uTwPowY",
|
|
43
|
+
"uTwPowZ",
|
|
44
|
+
"uHelixTurns",
|
|
45
|
+
"uHelixRadius",
|
|
46
|
+
"uHelixRoll",
|
|
47
|
+
"uHelixPhase",
|
|
48
|
+
"uRadialAmount",
|
|
49
|
+
"uRadialArc",
|
|
50
|
+
"uRadialSpread",
|
|
51
|
+
"uRadialRadius",
|
|
52
|
+
"uRadialCenter"
|
|
53
|
+
];
|
|
54
|
+
/** Build the seeded per-particle attribute buffers. Pure function of `(count, seed, edgeBias, bias)` —
|
|
55
|
+
* exported so a unit test can assert reproducibility without a GPU. */
|
|
56
|
+
function buildParticleAttributes(count, seed, edgeBias = 1, bias = 0) {
|
|
57
|
+
const rand = mulberry32(seed >>> 0 || 1);
|
|
58
|
+
const position = new Float32Array(count * 3);
|
|
59
|
+
const aSeed = new Float32Array(count);
|
|
60
|
+
const aRnd = new Float32Array(count * 4);
|
|
61
|
+
const aUv = new Float32Array(count * 2);
|
|
62
|
+
for (let i = 0; i < count; i++) {
|
|
63
|
+
aSeed[i] = rand();
|
|
64
|
+
aRnd[i * 4 + 0] = rand();
|
|
65
|
+
aRnd[i * 4 + 1] = rand();
|
|
66
|
+
aRnd[i * 4 + 2] = rand();
|
|
67
|
+
aRnd[i * 4 + 3] = rand();
|
|
68
|
+
}
|
|
69
|
+
const pexp = bias === 0 ? 1 : Math.exp(-bias * 2);
|
|
70
|
+
const eb = Math.min(Math.max(edgeBias, 0), 1);
|
|
71
|
+
for (let i = 0; i < count; i++) {
|
|
72
|
+
const ux = rand();
|
|
73
|
+
aUv[i * 2 + 0] = bias === 0 ? ux : Math.pow(ux, pexp);
|
|
74
|
+
const e = rand();
|
|
75
|
+
const rim = 1 - e * e * .45;
|
|
76
|
+
aUv[i * 2 + 1] = e + (rim - e) * eb;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
position,
|
|
80
|
+
aSeed,
|
|
81
|
+
aRnd,
|
|
82
|
+
aUv
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
var ParticleField = class {
|
|
86
|
+
points;
|
|
87
|
+
geometry = new THREE.BufferGeometry();
|
|
88
|
+
material;
|
|
89
|
+
/** Layout signature — only these rebuild the seeded buffers; the rest are live uniforms. */
|
|
90
|
+
sig = "";
|
|
91
|
+
constructor() {
|
|
92
|
+
this.material = new THREE.ShaderMaterial({
|
|
93
|
+
uniforms: {
|
|
94
|
+
uTime: { value: 0 },
|
|
95
|
+
uLoopSeconds: { value: 0 },
|
|
96
|
+
uLife: { value: 6 },
|
|
97
|
+
uPartSpeed: { value: 1 },
|
|
98
|
+
uSize: { value: 2 },
|
|
99
|
+
uSizeJitter: { value: 0 },
|
|
100
|
+
uTwinkle: { value: 0 },
|
|
101
|
+
uPixelRatio: { value: 1 },
|
|
102
|
+
uColor: { value: new THREE.Vector3(1, .81, .54) },
|
|
103
|
+
uColor2: { value: new THREE.Vector3(1, .81, .54) },
|
|
104
|
+
uCenter: { value: new THREE.Vector3() },
|
|
105
|
+
uRight: { value: new THREE.Vector3(1, 0, 0) },
|
|
106
|
+
uUp: { value: new THREE.Vector3(0, 1, 0) },
|
|
107
|
+
uDrift: { value: 0 },
|
|
108
|
+
uRise: { value: 0 },
|
|
109
|
+
uSwirl: { value: 0 },
|
|
110
|
+
uWander: { value: 0 },
|
|
111
|
+
uShape: { value: 0 },
|
|
112
|
+
uDispFreqX: { value: 0 },
|
|
113
|
+
uDispFreqZ: { value: 0 },
|
|
114
|
+
uDispAmount: { value: 0 },
|
|
115
|
+
uDetailFreq: { value: 0 },
|
|
116
|
+
uDetailAmount: { value: 0 },
|
|
117
|
+
uTwFreqX: { value: 0 },
|
|
118
|
+
uTwFreqY: { value: 0 },
|
|
119
|
+
uTwFreqZ: { value: 0 },
|
|
120
|
+
uTwPowX: { value: 0 },
|
|
121
|
+
uTwPowY: { value: 0 },
|
|
122
|
+
uTwPowZ: { value: 0 },
|
|
123
|
+
uHelixTurns: { value: 0 },
|
|
124
|
+
uHelixRadius: { value: 0 },
|
|
125
|
+
uHelixRoll: { value: 0 },
|
|
126
|
+
uHelixPhase: { value: 0 },
|
|
127
|
+
uRadialAmount: { value: 0 },
|
|
128
|
+
uRadialArc: { value: 0 },
|
|
129
|
+
uRadialSpread: { value: 0 },
|
|
130
|
+
uRadialRadius: { value: 0 },
|
|
131
|
+
uRadialCenter: { value: 0 },
|
|
132
|
+
uShedModel: { value: new THREE.Matrix4() },
|
|
133
|
+
uShedSpeed: { value: 0 },
|
|
134
|
+
uShedSeed: { value: 0 }
|
|
135
|
+
},
|
|
136
|
+
vertexShader: particleVertexShader,
|
|
137
|
+
fragmentShader: particleFragmentShader,
|
|
138
|
+
transparent: true,
|
|
139
|
+
depthTest: false,
|
|
140
|
+
depthWrite: false,
|
|
141
|
+
blending: THREE.AdditiveBlending
|
|
142
|
+
});
|
|
143
|
+
this.points = new THREE.Points(this.geometry, this.material);
|
|
144
|
+
this.points.frustumCulled = false;
|
|
145
|
+
this.points.renderOrder = 10;
|
|
146
|
+
}
|
|
147
|
+
/** Reconcile to `cfg`: rebuild the seeded buffers if the layout signature changed, then push the
|
|
148
|
+
* frame-independent uniforms. `loopSeconds` is scene-level (passed in). Called from refresh(). */
|
|
149
|
+
sync(cfg, loopSeconds) {
|
|
150
|
+
const count = Math.max(0, Math.floor(cfg.count));
|
|
151
|
+
const edgeBias = cfg.edgeBias ?? 1;
|
|
152
|
+
const bias = cfg.bias ?? 0;
|
|
153
|
+
const sig = `${count}|${cfg.seed}|${edgeBias}|${bias}`;
|
|
154
|
+
if (sig !== this.sig) {
|
|
155
|
+
this.sig = sig;
|
|
156
|
+
const { position, aSeed, aRnd, aUv } = buildParticleAttributes(count, cfg.seed, edgeBias, bias);
|
|
157
|
+
this.geometry.setAttribute("position", new THREE.BufferAttribute(position, 3));
|
|
158
|
+
this.geometry.setAttribute("aSeed", new THREE.BufferAttribute(aSeed, 1));
|
|
159
|
+
this.geometry.setAttribute("aRnd", new THREE.BufferAttribute(aRnd, 4));
|
|
160
|
+
this.geometry.setAttribute("aUv", new THREE.BufferAttribute(aUv, 2));
|
|
161
|
+
this.geometry.setDrawRange(0, count);
|
|
162
|
+
}
|
|
163
|
+
const u = this.material.uniforms;
|
|
164
|
+
u.uLoopSeconds.value = loopSeconds;
|
|
165
|
+
u.uLife.value = cfg.life ?? 6;
|
|
166
|
+
u.uPartSpeed.value = cfg.speed ?? 1;
|
|
167
|
+
u.uSize.value = cfg.size;
|
|
168
|
+
u.uSizeJitter.value = cfg.sizeJitter ?? 0;
|
|
169
|
+
u.uTwinkle.value = cfg.twinkle ?? 0;
|
|
170
|
+
u.uDrift.value = cfg.drift ?? 0;
|
|
171
|
+
u.uRise.value = cfg.rise ?? 0;
|
|
172
|
+
u.uSwirl.value = cfg.swirl ?? 0;
|
|
173
|
+
u.uWander.value = cfg.wander ?? 0;
|
|
174
|
+
u.uShape.value = SHAPE_INDEX[cfg.shape ?? "glitter"] ?? 0;
|
|
175
|
+
setLinear(u.uColor.value, cfg.color ?? DEFAULT_COLOR);
|
|
176
|
+
setLinear(u.uColor2.value, cfg.color2 ?? cfg.color ?? DEFAULT_COLOR);
|
|
177
|
+
}
|
|
178
|
+
/** Push the per-frame frame basis (the camera can move, so this runs every frame). */
|
|
179
|
+
frame(f, pixelRatio) {
|
|
180
|
+
const u = this.material.uniforms;
|
|
181
|
+
u.uCenter.value.copy(f.center);
|
|
182
|
+
u.uRight.value.copy(f.right);
|
|
183
|
+
u.uUp.value.copy(f.up);
|
|
184
|
+
u.uPixelRatio.value = pixelRatio;
|
|
185
|
+
}
|
|
186
|
+
/** Bind the OWNING wave's shape: mirror its shape #defines + shape uniforms + world matrix so the
|
|
187
|
+
* dust rides the SAME deform as the ribbon. Recompiles the point program only when the define set
|
|
188
|
+
* changes. Called from refresh() each frame with the wave's live state. */
|
|
189
|
+
configure(shape) {
|
|
190
|
+
const want = shape.defines;
|
|
191
|
+
const cur = this.material.defines ?? {};
|
|
192
|
+
if (Object.keys(want).sort().join(",") !== Object.keys(cur).sort().join(",")) {
|
|
193
|
+
this.material.defines = { ...want };
|
|
194
|
+
this.material.needsUpdate = true;
|
|
195
|
+
}
|
|
196
|
+
const u = this.material.uniforms;
|
|
197
|
+
for (const name of SHAPE_UNIFORMS) {
|
|
198
|
+
const src = shape.uniforms[name];
|
|
199
|
+
if (!src || u[name] === void 0) continue;
|
|
200
|
+
const dst = u[name].value;
|
|
201
|
+
if (typeof src.value === "number") u[name].value = src.value;
|
|
202
|
+
else if (dst && typeof dst.copy === "function") dst.copy(src.value);
|
|
203
|
+
}
|
|
204
|
+
u.uShedModel.value.copy(shape.matrixWorld);
|
|
205
|
+
u.uShedSpeed.value = shape.speed;
|
|
206
|
+
u.uShedSeed.value = shape.seed;
|
|
207
|
+
}
|
|
208
|
+
/** Advance the field to scene time `t` (= the same `t` the waves get). */
|
|
209
|
+
setTime(t) {
|
|
210
|
+
this.material.uniforms.uTime.value = t;
|
|
211
|
+
}
|
|
212
|
+
dispose() {
|
|
213
|
+
this.geometry.dispose();
|
|
214
|
+
this.material.dispose();
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
//#endregion
|
|
218
|
+
export { ParticleField, buildParticleAttributes };
|
|
219
|
+
|
|
220
|
+
//# sourceMappingURL=particleField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"particleField.js","names":[],"sources":["../../src/renderer/particleField.ts"],"sourcesContent":["import * as THREE from \"three\";\nimport type { ParticlesConfig } from \"../config/model\";\nimport { particleFragmentShader, particleVertexShader } from \"./shaders\";\n\n/**\n * A WAVE's additive particle / dust field: a single {@link THREE.Points} whose every particle is placed\n * and animated ENTIRELY in the vertex shader from `uTime` + baked per-particle attributes, so the field\n * is deterministic — the same `(count, seed, edgeBias, bias)` yields byte-identical buffers, and all\n * motion is a pure function of the scene time `t` (so timeOffset scrub / loopSeconds / paused reproduce).\n *\n * One field belongs to ONE wave (created/disposed alongside it, like {@link WavePalette}). Every particle\n * spawns on that wave's DEFORMED surface / edge (via the shared waveShape chunk, riding the exact deform\n * the ribbon uses) and drifts outward from the wave centre. Byte-identical when off: absent\n * `wave.particles` ⇒ the renderer never creates a field.\n */\nexport interface ParticleFrame {\n /** The owning wave's world-space centre — drift radiates from here. */\n center: THREE.Vector3;\n /** Screen-right / screen-up unit vectors (world space) for screen-relative motion. */\n right: THREE.Vector3;\n up: THREE.Vector3;\n}\n\n/** Deterministic PRNG (mulberry32): a pure function of the seed, so a `(count, seed)` layout\n * reproduces exactly. NEVER Math.random — that would desync timeOffset scrub / loop / paused. */\nfunction mulberry32(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a |= 0;\n a = (a + 0x6d2b79f5) | 0;\n let t = Math.imul(a ^ (a >>> 15), 1 | a);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n// sRGB hex → linear RGB. Replicates WaveRenderer.hexToLinearVec3 locally to avoid a circular import\n// (the renderer imports this module): three's ColorManagement linearizes on parse, so read .r/.g/.b\n// directly — a second convertSRGBToLinear() would double-linearize.\nconst HEX_SCRATCH = new THREE.Color();\nfunction setLinear(target: THREE.Vector3, hex: string): void {\n const c = HEX_SCRATCH.set(hex);\n target.set(c.r, c.g, c.b);\n}\n\nconst DEFAULT_COLOR = \"#ffcf8a\"; // warm gold\n\n/** Sprite-shape name → the int the fragment shader branches on (see particleFragmentShader). */\nconst SHAPE_INDEX: Record<string, number> = { glitter: 0, soft: 1, ring: 2, star: 3, streak: 4 };\n\n/** The owning wave's shape uniforms mirrored onto the particle material so the dust rides the same\n * deform as the ribbon. Names match the wave material's uniforms 1:1 (copied by value in configure). */\nconst SHAPE_UNIFORMS = [\n \"uDispFreqX\",\n \"uDispFreqZ\",\n \"uDispAmount\",\n \"uDetailFreq\",\n \"uDetailAmount\",\n \"uTwFreqX\",\n \"uTwFreqY\",\n \"uTwFreqZ\",\n \"uTwPowX\",\n \"uTwPowY\",\n \"uTwPowZ\",\n \"uHelixTurns\",\n \"uHelixRadius\",\n \"uHelixRoll\",\n \"uHelixPhase\",\n \"uRadialAmount\",\n \"uRadialArc\",\n \"uRadialSpread\",\n \"uRadialRadius\",\n \"uRadialCenter\",\n] as const;\n\n/** Build the seeded per-particle attribute buffers. Pure function of `(count, seed, edgeBias, bias)` —\n * exported so a unit test can assert reproducibility without a GPU. */\nexport function buildParticleAttributes(\n count: number,\n seed: number,\n edgeBias = 1,\n bias = 0,\n): {\n position: Float32Array;\n aSeed: Float32Array;\n aRnd: Float32Array;\n aUv: Float32Array;\n} {\n const rand = mulberry32(seed >>> 0 || 1);\n const position = new Float32Array(count * 3); // dummy — the vertex shader computes real positions\n const aSeed = new Float32Array(count);\n const aRnd = new Float32Array(count * 4);\n const aUv = new Float32Array(count * 2);\n for (let i = 0; i < count; i++) {\n aSeed[i] = rand();\n aRnd[i * 4 + 0] = rand();\n aRnd[i * 4 + 1] = rand();\n aRnd[i * 4 + 2] = rand();\n aRnd[i * 4 + 3] = rand();\n }\n // aUv in a SEPARATE pass so adding/changing it never shifts the aSeed/aRnd RNG sequence above. aUv.x\n // = the flank position across the ribbon width; `bias` skews it toward one side (`u^p`, p<1 crowds\n // →1, p>1 crowds →0; p=1 / bias 0 leaves it untouched). aUv.y = WHERE ALONG the ribbon the particle\n // spawns, interpolated by `edgeBias`: 0 → uniform across the whole SURFACE, 1 → crowded to the outer\n // rim/EDGE (`1 − rand²·0.45`, the silk-dissolving-into-glitter look).\n const pexp = bias === 0 ? 1 : Math.exp(-bias * 2);\n const eb = Math.min(Math.max(edgeBias, 0), 1);\n for (let i = 0; i < count; i++) {\n const ux = rand();\n aUv[i * 2 + 0] = bias === 0 ? ux : Math.pow(ux, pexp);\n const e = rand();\n const rim = 1.0 - e * e * 0.45; // outer-rim biased\n aUv[i * 2 + 1] = e + (rim - e) * eb; // mix(surface, edge) by edgeBias\n }\n return { position, aSeed, aRnd, aUv };\n}\n\nexport class ParticleField {\n readonly points: THREE.Points;\n private readonly geometry = new THREE.BufferGeometry();\n private readonly material: THREE.ShaderMaterial;\n /** Layout signature — only these rebuild the seeded buffers; the rest are live uniforms. */\n private sig = \"\";\n\n constructor() {\n this.material = new THREE.ShaderMaterial({\n uniforms: {\n uTime: { value: 0 },\n uLoopSeconds: { value: 0 },\n uLife: { value: 6 },\n uPartSpeed: { value: 1 },\n uSize: { value: 2 },\n uSizeJitter: { value: 0 },\n uTwinkle: { value: 0 },\n uPixelRatio: { value: 1 },\n uColor: { value: new THREE.Vector3(1, 0.81, 0.54) },\n uColor2: { value: new THREE.Vector3(1, 0.81, 0.54) },\n uCenter: { value: new THREE.Vector3() },\n uRight: { value: new THREE.Vector3(1, 0, 0) },\n uUp: { value: new THREE.Vector3(0, 1, 0) },\n uDrift: { value: 0 },\n uRise: { value: 0 },\n uSwirl: { value: 0 },\n uWander: { value: 0 },\n uShape: { value: 0 },\n // The owning wave's shape uniforms + world matrix, mirrored in configure() so the dust rides\n // the same deform as the ribbon. The nested HELIX/RADIAL uniforms are only declared (and\n // uploaded) when the matching #define is set — the byte-identity precedent from the wave material.\n uDispFreqX: { value: 0 },\n uDispFreqZ: { value: 0 },\n uDispAmount: { value: 0 },\n uDetailFreq: { value: 0 },\n uDetailAmount: { value: 0 },\n uTwFreqX: { value: 0 },\n uTwFreqY: { value: 0 },\n uTwFreqZ: { value: 0 },\n uTwPowX: { value: 0 },\n uTwPowY: { value: 0 },\n uTwPowZ: { value: 0 },\n uHelixTurns: { value: 0 },\n uHelixRadius: { value: 0 },\n uHelixRoll: { value: 0 },\n uHelixPhase: { value: 0 },\n uRadialAmount: { value: 0 },\n uRadialArc: { value: 0 },\n uRadialSpread: { value: 0 },\n uRadialRadius: { value: 0 },\n uRadialCenter: { value: 0 },\n uShedModel: { value: new THREE.Matrix4() },\n uShedSpeed: { value: 0 },\n uShedSeed: { value: 0 },\n },\n vertexShader: particleVertexShader,\n fragmentShader: particleFragmentShader,\n transparent: true,\n depthTest: false, // always composite OVER the waves...\n depthWrite: false, // ...and never occlude anything (additive glints)\n blending: THREE.AdditiveBlending,\n });\n this.points = new THREE.Points(this.geometry, this.material);\n this.points.frustumCulled = false; // positions are shader-computed; the base geometry is dummy\n this.points.renderOrder = 10; // after the waves (0..5)\n }\n\n /** Reconcile to `cfg`: rebuild the seeded buffers if the layout signature changed, then push the\n * frame-independent uniforms. `loopSeconds` is scene-level (passed in). Called from refresh(). */\n sync(cfg: ParticlesConfig, loopSeconds: number): void {\n const count = Math.max(0, Math.floor(cfg.count));\n const edgeBias = cfg.edgeBias ?? 1;\n const bias = cfg.bias ?? 0;\n const sig = `${count}|${cfg.seed}|${edgeBias}|${bias}`;\n if (sig !== this.sig) {\n this.sig = sig;\n const { position, aSeed, aRnd, aUv } = buildParticleAttributes(\n count,\n cfg.seed,\n edgeBias,\n bias,\n );\n this.geometry.setAttribute(\"position\", new THREE.BufferAttribute(position, 3));\n this.geometry.setAttribute(\"aSeed\", new THREE.BufferAttribute(aSeed, 1));\n this.geometry.setAttribute(\"aRnd\", new THREE.BufferAttribute(aRnd, 4));\n this.geometry.setAttribute(\"aUv\", new THREE.BufferAttribute(aUv, 2));\n this.geometry.setDrawRange(0, count);\n }\n const u = this.material.uniforms;\n u.uLoopSeconds.value = loopSeconds;\n u.uLife.value = cfg.life ?? 6;\n u.uPartSpeed.value = cfg.speed ?? 1;\n u.uSize.value = cfg.size;\n u.uSizeJitter.value = cfg.sizeJitter ?? 0;\n u.uTwinkle.value = cfg.twinkle ?? 0;\n u.uDrift.value = cfg.drift ?? 0;\n u.uRise.value = cfg.rise ?? 0;\n u.uSwirl.value = cfg.swirl ?? 0;\n u.uWander.value = cfg.wander ?? 0;\n u.uShape.value = SHAPE_INDEX[cfg.shape ?? \"glitter\"] ?? 0;\n setLinear(u.uColor.value as THREE.Vector3, cfg.color ?? DEFAULT_COLOR);\n setLinear(u.uColor2.value as THREE.Vector3, cfg.color2 ?? cfg.color ?? DEFAULT_COLOR);\n }\n\n /** Push the per-frame frame basis (the camera can move, so this runs every frame). */\n frame(f: ParticleFrame, pixelRatio: number): void {\n const u = this.material.uniforms;\n (u.uCenter.value as THREE.Vector3).copy(f.center);\n (u.uRight.value as THREE.Vector3).copy(f.right);\n (u.uUp.value as THREE.Vector3).copy(f.up);\n u.uPixelRatio.value = pixelRatio;\n }\n\n /** Bind the OWNING wave's shape: mirror its shape #defines + shape uniforms + world matrix so the\n * dust rides the SAME deform as the ribbon. Recompiles the point program only when the define set\n * changes. Called from refresh() each frame with the wave's live state. */\n configure(shape: {\n defines: Record<string, string>;\n uniforms: Record<string, THREE.IUniform>;\n matrixWorld: THREE.Matrix4;\n speed: number;\n seed: number;\n }): void {\n const want = shape.defines;\n const cur = (this.material.defines ?? {}) as Record<string, string>;\n if (Object.keys(want).sort().join(\",\") !== Object.keys(cur).sort().join(\",\")) {\n this.material.defines = { ...want };\n this.material.needsUpdate = true; // define set changed → recompile the point program\n }\n const u = this.material.uniforms;\n for (const name of SHAPE_UNIFORMS) {\n const src = shape.uniforms[name];\n if (!src || u[name] === undefined) continue;\n const dst = u[name].value;\n if (typeof src.value === \"number\") u[name].value = src.value;\n else if (dst && typeof (dst as { copy?: unknown }).copy === \"function\") {\n (dst as THREE.Vector3).copy(src.value as THREE.Vector3);\n }\n }\n (u.uShedModel.value as THREE.Matrix4).copy(shape.matrixWorld);\n u.uShedSpeed.value = shape.speed;\n u.uShedSeed.value = shape.seed;\n }\n\n /** Advance the field to scene time `t` (= the same `t` the waves get). */\n setTime(t: number): void {\n this.material.uniforms.uTime.value = t;\n }\n\n dispose(): void {\n this.geometry.dispose();\n this.material.dispose();\n }\n}\n"],"mappings":"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"}
|