@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
package/dist/renderer/shaders.js
CHANGED
|
@@ -157,6 +157,107 @@ vec3 applyColorGrade(vec3 c){
|
|
|
157
157
|
return hueShift(c, radians(uHueShift));
|
|
158
158
|
}
|
|
159
159
|
`;
|
|
160
|
+
const waveShapeChunk = `
|
|
161
|
+
// expStep: a falloff from 1 (at x=0) toward 0, sharpness set by n. The
|
|
162
|
+
// max() guards pow(0, n) (= Infinity → NaN) so negative n is safe — negative n
|
|
163
|
+
// just concentrates the twist toward the OTHER end instead.
|
|
164
|
+
float expStep(float x, float n){ return exp2(-exp2(n) * pow(max(x, 1.0e-3), n)); }
|
|
165
|
+
|
|
166
|
+
// rotationMatrix (mat4), used row-vector style: pos = (vec4(pos,1) * R).xyz
|
|
167
|
+
mat4 rotationMatrix(vec3 axis, float angle){
|
|
168
|
+
axis = normalize(axis);
|
|
169
|
+
float s = sin(angle), c = cos(angle), oc = 1.0 - c;
|
|
170
|
+
return mat4(
|
|
171
|
+
oc*axis.x*axis.x + c, oc*axis.x*axis.y - axis.z*s, oc*axis.z*axis.x + axis.y*s, 0.0,
|
|
172
|
+
oc*axis.x*axis.y + axis.z*s, oc*axis.y*axis.y + c, oc*axis.y*axis.z - axis.x*s, 0.0,
|
|
173
|
+
oc*axis.z*axis.x - axis.y*s, oc*axis.y*axis.z + axis.x*s, oc*axis.z*axis.z + c, 0.0,
|
|
174
|
+
0.0, 0.0, 0.0, 1.0
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// The wave SHAPE deform, shared by the wave vertex shader (below) and the particle SHED emitter
|
|
179
|
+
// (particleVertexShader): a base hairpin position + its uv → the displaced / helixed / twisted /
|
|
180
|
+
// fanned LOCAL position, plus the three twist matrices (the pointer field reads them). Every branch
|
|
181
|
+
// sits behind the SAME #ifdef gates as the code it replaces, so a given compiled program is
|
|
182
|
+
// byte-identical to the former inline version. t / loopOff are the linear / orbit time the
|
|
183
|
+
// caller computed; only the one selected by LOOP_MOTION is read (the other is a dead argument).
|
|
184
|
+
struct WaveShape { vec3 pos; mat4 rotA; mat4 rotB; mat4 rotC; };
|
|
185
|
+
WaveShape waveShape(vec3 position, vec2 uv, float t, vec2 loopOff){
|
|
186
|
+
// Displacement lifts Y by simplex noise of the (x,z) position.
|
|
187
|
+
vec3 pos = position;
|
|
188
|
+
#ifdef LOOP_MOTION
|
|
189
|
+
pos.y += uDispAmount * simplexNoise(vec2(pos.x * uDispFreqX, pos.z * uDispFreqZ) + loopOff);
|
|
190
|
+
#else
|
|
191
|
+
pos.y += uDispAmount * simplexNoise(vec2(pos.x * uDispFreqX + t, pos.z * uDispFreqZ + t));
|
|
192
|
+
#endif
|
|
193
|
+
#ifdef DETAIL_OCTAVE
|
|
194
|
+
// A second, finer octave riding on the broad swell (loop-orbit shared so it stays periodic).
|
|
195
|
+
#ifdef LOOP_MOTION
|
|
196
|
+
pos.y += uDetailAmount * simplexNoise(vec2(pos.x * uDetailFreq, pos.z * uDetailFreq) + loopOff);
|
|
197
|
+
#else
|
|
198
|
+
pos.y += uDetailAmount * simplexNoise(vec2(pos.x * uDetailFreq + t, pos.z * uDetailFreq + t));
|
|
199
|
+
#endif
|
|
200
|
+
#endif
|
|
201
|
+
|
|
202
|
+
#ifdef HELIX
|
|
203
|
+
// Helix — the periodic sweep the three twists (monotone falloffs) can't reach. Runs AFTER the
|
|
204
|
+
// displacement (so the noise still samples undeformed pos) and BEFORE the twist (so they compose).
|
|
205
|
+
float hAng = 6.28318530718 * uHelixTurns * uv.y + radians(uHelixPhase);
|
|
206
|
+
// Roll about the ribbon's width centre, not the origin — see RIBBON_Z_CENTER in WaveGeometry.
|
|
207
|
+
float rollA = hAng * uHelixRoll;
|
|
208
|
+
float rollC = cos(rollA), rollS = sin(rollA);
|
|
209
|
+
vec2 rel = vec2(pos.y, pos.z - ${(-8).toFixed(1)});
|
|
210
|
+
pos.y = rel.x * rollC - rel.y * rollS;
|
|
211
|
+
pos.z = ${(-8).toFixed(1)} + rel.x * rollS + rel.y * rollC;
|
|
212
|
+
pos.y += uHelixRadius * cos(hAng);
|
|
213
|
+
pos.z += uHelixRadius * sin(hAng);
|
|
214
|
+
#endif
|
|
215
|
+
|
|
216
|
+
// The X-twist frequency feeding rotB; the TWIST_MOTION variant modulates it with simplex noise
|
|
217
|
+
// indexed along the ribbon (uv.y) so the twist breathes over time.
|
|
218
|
+
float twistXFreq = uTwFreqX;
|
|
219
|
+
#ifdef TWIST_MOTION
|
|
220
|
+
#ifdef LOOP_MOTION
|
|
221
|
+
float twistXNoise = simplexNoise(vec2(uv.y * 2.0, 0.0) + loopOff);
|
|
222
|
+
#else
|
|
223
|
+
float twistXNoise = simplexNoise(vec2(uv.y * 2.0, t));
|
|
224
|
+
#endif
|
|
225
|
+
twistXFreq = uTwFreqX - twistXNoise * 0.1;
|
|
226
|
+
#endif
|
|
227
|
+
|
|
228
|
+
// Three-axis twist (see the falloff-axis note: rotA keys off uv.x/WIDTH, rotB/rotC off uv.y/LENGTH).
|
|
229
|
+
mat4 rotA = rotationMatrix(vec3(0.5, 0.0, 0.5), uTwFreqY * expStep(uv.x, uTwPowY));
|
|
230
|
+
mat4 rotB = rotationMatrix(vec3(0.0, 0.5, 0.5), twistXFreq * expStep(uv.y, uTwPowX));
|
|
231
|
+
mat4 rotC = rotationMatrix(vec3(0.5, 0.0, 0.5), uTwFreqZ * expStep(uv.y, uTwPowZ));
|
|
232
|
+
pos = (vec4(pos, 1.0) * rotA).xyz;
|
|
233
|
+
pos = (vec4(pos, 1.0) * rotB).xyz;
|
|
234
|
+
pos = (vec4(pos, 1.0) * rotC).xyz;
|
|
235
|
+
|
|
236
|
+
#ifdef RADIAL
|
|
237
|
+
// Radial fan: remap the ribbon to polar around the LOCAL origin so its LENGTH fans into a plume.
|
|
238
|
+
// uv.x (folded WIDTH) → fan ANGLE across uRadialArc; uv.y (LENGTH) → RADIUS, so a constant-uv.x
|
|
239
|
+
// combed fiber becomes a constant-angle radial spoke. mix(pos, fanned, 0) is identity → off is
|
|
240
|
+
// byte-identical. (Placement is the wave's position transform — the fan has no separate pivot.)
|
|
241
|
+
{
|
|
242
|
+
float rAng = radians(uRadialCenter) + (clamp(uv.x, 0.0, 1.0) - 0.5) * radians(uRadialArc);
|
|
243
|
+
float rRho = uRadialRadius + uv.y * 400.0 * uRadialSpread; // 400 = native ribbon length
|
|
244
|
+
vec3 rEr = vec3(cos(rAng), sin(rAng), 0.0); // radial dir, in local X–Y (screen plane)
|
|
245
|
+
vec3 rEt = vec3(-sin(rAng), cos(rAng), 0.0); // tangential
|
|
246
|
+
vec3 fanned = rEr * rRho
|
|
247
|
+
+ rEt * (pos.z - ${(-8).toFixed(1)}) * 0.5
|
|
248
|
+
+ vec3(0.0, 0.0, pos.y);
|
|
249
|
+
pos = mix(pos, fanned, clamp(uRadialAmount, 0.0, 1.0));
|
|
250
|
+
}
|
|
251
|
+
#endif
|
|
252
|
+
|
|
253
|
+
WaveShape s;
|
|
254
|
+
s.pos = pos;
|
|
255
|
+
s.rotA = rotA;
|
|
256
|
+
s.rotB = rotB;
|
|
257
|
+
s.rotC = rotC;
|
|
258
|
+
return s;
|
|
259
|
+
}
|
|
260
|
+
`;
|
|
160
261
|
const vertexShader = `
|
|
161
262
|
${simplex2d}
|
|
162
263
|
|
|
@@ -166,6 +267,25 @@ uniform float uDetailFreq, uDetailAmount; // 2nd displacement octave (only read
|
|
|
166
267
|
uniform float uTwFreqX, uTwFreqY, uTwFreqZ, uTwPowX, uTwPowY, uTwPowZ;
|
|
167
268
|
uniform float uLoopSeconds; // seamless-loop period (only read under LOOP_MOTION)
|
|
168
269
|
|
|
270
|
+
// Helix (optional). Behind HELIX so a wave without one compiles the exact same program — same
|
|
271
|
+
// byte-identity contract as the pointer block below.
|
|
272
|
+
#ifdef HELIX
|
|
273
|
+
uniform float uHelixTurns; // full turns from one end of the ribbon to the other
|
|
274
|
+
uniform float uHelixRadius; // orbit radius: carries the whole ribbon around the axis
|
|
275
|
+
uniform float uHelixRoll; // cross-section roll, as a fraction of the turns (1 = rigid ladder)
|
|
276
|
+
uniform float uHelixPhase; // degrees
|
|
277
|
+
#endif
|
|
278
|
+
|
|
279
|
+
// Radial fan (optional). Behind RADIAL so a wave without one compiles the exact same program (same
|
|
280
|
+
// byte-identity contract as HELIX / POINTER_FX above).
|
|
281
|
+
#ifdef RADIAL
|
|
282
|
+
uniform float uRadialAmount; // 0..1 blend (0 = identity)
|
|
283
|
+
uniform float uRadialArc; // fan spread, degrees
|
|
284
|
+
uniform float uRadialSpread; // length → radius scale
|
|
285
|
+
uniform float uRadialRadius; // source / inner radius
|
|
286
|
+
uniform float uRadialCenter; // base angle, degrees
|
|
287
|
+
#endif
|
|
288
|
+
|
|
169
289
|
varying vec2 vUv;
|
|
170
290
|
varying vec3 vWorldPos;
|
|
171
291
|
varying vec3 vViewDir;
|
|
@@ -199,27 +319,13 @@ const float RIPPLE_MAX_R = 1.2; // reach where the crest has fully left th
|
|
|
199
319
|
#endif
|
|
200
320
|
#endif
|
|
201
321
|
|
|
202
|
-
|
|
203
|
-
// max() guards pow(0, n) (= Infinity → NaN) so negative n is safe — negative n
|
|
204
|
-
// just concentrates the twist toward the OTHER end instead.
|
|
205
|
-
float expStep(float x, float n){ return exp2(-exp2(n) * pow(max(x, 1.0e-3), n)); }
|
|
206
|
-
|
|
207
|
-
// rotationMatrix (mat4), used row-vector style: pos = (vec4(pos,1) * R).xyz
|
|
208
|
-
mat4 rotationMatrix(vec3 axis, float angle){
|
|
209
|
-
axis = normalize(axis);
|
|
210
|
-
float s = sin(angle), c = cos(angle), oc = 1.0 - c;
|
|
211
|
-
return mat4(
|
|
212
|
-
oc*axis.x*axis.x + c, oc*axis.x*axis.y - axis.z*s, oc*axis.z*axis.x + axis.y*s, 0.0,
|
|
213
|
-
oc*axis.x*axis.y + axis.z*s, oc*axis.y*axis.y + c, oc*axis.y*axis.z - axis.x*s, 0.0,
|
|
214
|
-
oc*axis.z*axis.x - axis.y*s, oc*axis.y*axis.z + axis.x*s, oc*axis.z*axis.z + c, 0.0,
|
|
215
|
-
0.0, 0.0, 0.0, 1.0
|
|
216
|
-
);
|
|
217
|
-
}
|
|
322
|
+
${waveShapeChunk}
|
|
218
323
|
|
|
219
324
|
void main(){
|
|
220
325
|
vUv = uv;
|
|
221
326
|
#ifndef LOOP_MOTION
|
|
222
327
|
float t = uTime * uSpeed + uSeed;
|
|
328
|
+
vec2 loopOff = vec2(0.0); // unused under linear time; kept so waveShape's signature is uniform
|
|
223
329
|
#endif
|
|
224
330
|
|
|
225
331
|
#ifdef LOOP_MOTION
|
|
@@ -232,51 +338,14 @@ void main(){
|
|
|
232
338
|
float loopTheta = uTime * (6.28318530718 / uLoopSeconds) + uSeed;
|
|
233
339
|
float loopR = uSpeed * uLoopSeconds * 0.159154943092; // = uSpeed·uLoopSeconds / (2π)
|
|
234
340
|
vec2 loopOff = loopR * vec2(cos(loopTheta), sin(loopTheta));
|
|
341
|
+
float t = 0.0; // unused under loop time
|
|
235
342
|
#endif
|
|
236
343
|
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
pos.y += uDispAmount * simplexNoise(vec2(pos.x * uDispFreqX, pos.z * uDispFreqZ) + loopOff);
|
|
243
|
-
#else
|
|
244
|
-
pos.y += uDispAmount * simplexNoise(vec2(pos.x * uDispFreqX + t, pos.z * uDispFreqZ + t));
|
|
245
|
-
#endif
|
|
246
|
-
#ifdef DETAIL_OCTAVE
|
|
247
|
-
// A second, finer octave riding on the broad swell — fine ripples on top of the big shape, a
|
|
248
|
-
// shape vocabulary single-octave displacement can't reach. Shares the loop orbit so it stays
|
|
249
|
-
// periodic when looping.
|
|
250
|
-
#ifdef LOOP_MOTION
|
|
251
|
-
pos.y += uDetailAmount * simplexNoise(vec2(pos.x * uDetailFreq, pos.z * uDetailFreq) + loopOff);
|
|
252
|
-
#else
|
|
253
|
-
pos.y += uDetailAmount * simplexNoise(vec2(pos.x * uDetailFreq + t, pos.z * uDetailFreq + t));
|
|
254
|
-
#endif
|
|
255
|
-
#endif
|
|
256
|
-
|
|
257
|
-
// The X-twist frequency feeding rotB. Two modes: by default uTwFreqX is used
|
|
258
|
-
// directly; the variant (used by the Wave 4 preset) modulates it with
|
|
259
|
-
// simplex noise indexed along the ribbon (uv.y) so the twist breathes over time.
|
|
260
|
-
// We gate the wobble with a #define so the compiled program is unchanged when off.
|
|
261
|
-
float twistXFreq = uTwFreqX;
|
|
262
|
-
#ifdef TWIST_MOTION
|
|
263
|
-
#ifdef LOOP_MOTION
|
|
264
|
-
float twistXNoise = simplexNoise(vec2(vUv.y * 2.0, 0.0) + loopOff);
|
|
265
|
-
#else
|
|
266
|
-
float twistXNoise = simplexNoise(vec2(vUv.y * 2.0, t));
|
|
267
|
-
#endif
|
|
268
|
-
twistXFreq = uTwFreqX - twistXNoise * 0.1;
|
|
269
|
-
#endif
|
|
270
|
-
|
|
271
|
-
// Three-axis twist: expStep falloff sets how
|
|
272
|
-
// sharply each rotation concentrates toward an edge. rotA keys off uv.x, rotB/rotC
|
|
273
|
-
// off uv.y; axes (0.5,0,0.5) and (0,0.5,0.5) are normalised inside rotationMatrix.
|
|
274
|
-
mat4 rotA = rotationMatrix(vec3(0.5, 0.0, 0.5), uTwFreqY * expStep(uv.x, uTwPowY));
|
|
275
|
-
mat4 rotB = rotationMatrix(vec3(0.0, 0.5, 0.5), twistXFreq * expStep(uv.y, uTwPowX));
|
|
276
|
-
mat4 rotC = rotationMatrix(vec3(0.5, 0.0, 0.5), uTwFreqZ * expStep(uv.y, uTwPowZ));
|
|
277
|
-
pos = (vec4(pos, 1.0) * rotA).xyz;
|
|
278
|
-
pos = (vec4(pos, 1.0) * rotB).xyz;
|
|
279
|
-
pos = (vec4(pos, 1.0) * rotC).xyz;
|
|
344
|
+
// Deform the baked hairpin via the shared waveShape chunk (displacement + helix + twist + radial),
|
|
345
|
+
// which also drives the particle shed emitter. It returns the deformed local pos + the twist
|
|
346
|
+
// matrices the pointer field reads below.
|
|
347
|
+
WaveShape ws = waveShape(position, uv, t, loopOff);
|
|
348
|
+
vec3 pos = ws.pos;
|
|
280
349
|
|
|
281
350
|
#ifdef POINTER_FX
|
|
282
351
|
// Pointer field: displace along the wave's own (post-twist) up-axis, weighted by a screen-space
|
|
@@ -299,7 +368,7 @@ void main(){
|
|
|
299
368
|
// point-projection and no perspective divide. (A true per-pixel uv would need GPU picking — the
|
|
300
369
|
// visible surface is shader-displaced, so a CPU raycast of the base geometry misses.)
|
|
301
370
|
if (uShapeFlow > 0.0) {
|
|
302
|
-
vec3 tangentLocal = (((vec4(1.0, 0.0, 0.0, 0.0) * rotA) * rotB) * rotC).xyz;
|
|
371
|
+
vec3 tangentLocal = (((vec4(1.0, 0.0, 0.0, 0.0) * ws.rotA) * ws.rotB) * ws.rotC).xyz;
|
|
303
372
|
vec2 tang = (mvp * vec4(tangentLocal, 0.0)).xy * vec2(uPointerAspect, 1.0);
|
|
304
373
|
float tl = length(tang);
|
|
305
374
|
if (tl > 1.0e-6) {
|
|
@@ -312,7 +381,7 @@ void main(){
|
|
|
312
381
|
vPointerFall = fall * uPointerActive;
|
|
313
382
|
// Displacement axis = local +Y carried through the SAME three twist rotations as pos (row-vector
|
|
314
383
|
// convention). Rotations are linear, so post-twist axis displacement equals pre-twist Y displacement.
|
|
315
|
-
vec3 dispAxis = (((vec4(0.0, 1.0, 0.0, 0.0) * rotA) * rotB) * rotC).xyz;
|
|
384
|
+
vec3 dispAxis = (((vec4(0.0, 1.0, 0.0, 0.0) * ws.rotA) * ws.rotB) * ws.rotC).xyz;
|
|
316
385
|
// Agitation: a fast churn octave near the cursor (additive — never rewrites base noise t, which
|
|
317
386
|
// would force restructuring the shared path). Loop-safe under both time variants.
|
|
318
387
|
#ifdef LOOP_MOTION
|
|
@@ -403,7 +472,7 @@ uniform vec3 uDepthTintColor;
|
|
|
403
472
|
varying vec4 vClipPosition; // clip-space depth (written by the vertex shader for both programs)
|
|
404
473
|
#endif
|
|
405
474
|
#ifdef EDGE_FEATHER
|
|
406
|
-
uniform float uEdgeFeather; // ribbon
|
|
475
|
+
uniform float uEdgeFeather; // softness of the ribbon's two ENDS (only when it differs from 0.1)
|
|
407
476
|
#endif
|
|
408
477
|
#ifdef POINTER_FX
|
|
409
478
|
uniform float uPointerThin; // 0..1 local translucency near the cursor
|
|
@@ -443,8 +512,10 @@ vec3 surfaceStreaks(vec2 uv, vec3 color, float crease){
|
|
|
443
512
|
colorAtten = mix(colorAtten, prm.w, blend);
|
|
444
513
|
paraPow = mix(paraPow, uNoiseBandParaPow[i], blend);
|
|
445
514
|
}
|
|
446
|
-
// The high frequency runs along uv.x (the
|
|
447
|
-
//
|
|
515
|
+
// The high frequency runs along uv.x (the folded WIDTH — see WaveGeometry's UV AXES note),
|
|
516
|
+
// packing many thin stripes across the cross-section while uv.y is barely scaled, so each
|
|
517
|
+
// one stretches out into a fine LENGTHWISE fiber. 1 - parabola(uv.x) then weights them
|
|
518
|
+
// toward the two long edges and away from the width centreline.
|
|
448
519
|
float p = 1.0 - parabola(uv.x, paraPow);
|
|
449
520
|
float n0 = simplexNoise(vec2(uv.x * 0.1, uv.y * 0.5));
|
|
450
521
|
float n1 = simplexNoise(vec2(uv.x * (freq + freq * 0.5 * n0), uv.y * 4.0 * n0));
|
|
@@ -533,7 +604,8 @@ void main(){
|
|
|
533
604
|
|
|
534
605
|
if (uTexture > 0.001) col *= 1.0 + (grainHash(vUv * 850.0) - 0.5) * uTexture * 0.25;
|
|
535
606
|
|
|
536
|
-
// Soft
|
|
607
|
+
// Soft ribbon ENDS (it fades on vUv.y, the length) + optional viewport-edge fade. The edge
|
|
608
|
+
// softness is the hardcoded 0.1 by
|
|
537
609
|
// default (literal branch → byte-identical); EDGE_FEATHER swaps in the uEdgeFeather knob only
|
|
538
610
|
// when it differs, so razor-crisp or vapor-soft edges are both reachable.
|
|
539
611
|
#ifdef EDGE_FEATHER
|
|
@@ -580,6 +652,11 @@ uniform float uLineAmount; // default 425
|
|
|
580
652
|
uniform float uLineThickness; // default 1
|
|
581
653
|
uniform float uLineDerivativePower; // default 0.95
|
|
582
654
|
uniform float uMaxWidth; // default 1232
|
|
655
|
+
// Cross-wise rungs (optional) — behind RUNGS so a wave without them compiles the same program.
|
|
656
|
+
#ifdef RUNGS
|
|
657
|
+
uniform float uRungAmount; // frequency across the ribbon (rungs ≈ amount / π)
|
|
658
|
+
uniform float uRungThickness; // rung width in pixels
|
|
659
|
+
#endif
|
|
583
660
|
uniform vec3 uClearColor; // = page background colour (shown between the lines)
|
|
584
661
|
|
|
585
662
|
varying vec2 vUv;
|
|
@@ -602,7 +679,7 @@ void main(){
|
|
|
602
679
|
color *= 1.0 + uPointerLighten * vPointerFall;
|
|
603
680
|
#endif
|
|
604
681
|
|
|
605
|
-
// Carve into fine
|
|
682
|
+
// Carve into fine lengthwise strands; thickness from the screen-space uv derivative.
|
|
606
683
|
vec2 dy = dFdy(vUv);
|
|
607
684
|
float lineThickness = uLineThickness * pow(abs(dy.x * uMaxWidth), uLineDerivativePower);
|
|
608
685
|
#ifdef POINTER_FX
|
|
@@ -611,6 +688,16 @@ void main(){
|
|
|
611
688
|
float a = abs(sin(vUv.x * uLineAmount));
|
|
612
689
|
a = smoothstep(lineThickness, 0.0, a);
|
|
613
690
|
|
|
691
|
+
#ifdef RUNGS
|
|
692
|
+
// Rungs: the same carve at constant uv.y instead of uv.x, so this family runs ACROSS the ribbon
|
|
693
|
+
// where the one above runs along it — together they read as a ladder. Width comes from fwidth()
|
|
694
|
+
// rather than the lengthwise term's dFdy(vUv).x, which is the derivative of the wrong axis for
|
|
695
|
+
// this direction: |sin| climbs by ~uRungAmount·fwidth(vUv.y) per pixel, so scaling by that keeps
|
|
696
|
+
// a rung uRungThickness pixels wide at any zoom or ribbon scale.
|
|
697
|
+
float rung = abs(sin(vUv.y * uRungAmount));
|
|
698
|
+
a = max(a, smoothstep(uRungThickness * uRungAmount * fwidth(vUv.y), 0.0, rung));
|
|
699
|
+
#endif
|
|
700
|
+
|
|
614
701
|
// Depth fade: the wave recedes into the background colour with depth. Watch the
|
|
615
702
|
// argument order: clamp(0.0, 1.0, z*6) is a swapped-args trap — it clamps the
|
|
616
703
|
// constant 0.0 into [1.0, z*6], i.e. min(1.0, z*6), which (with our ortho clip.z
|
|
@@ -851,7 +938,124 @@ void main(){
|
|
|
851
938
|
gl_FragColor = vec4(mix(src.rgb, outc, clamp(uHalftoneCmyk, 0.0, 1.0)), src.a);
|
|
852
939
|
}
|
|
853
940
|
`;
|
|
941
|
+
const particleVertexShader = `
|
|
942
|
+
attribute float aSeed;
|
|
943
|
+
attribute vec4 aRnd;
|
|
944
|
+
attribute vec2 aUv; // where this particle spawns on the ribbon (x = flank, y = along length; edge-biased at build)
|
|
945
|
+
|
|
946
|
+
uniform float uTime, uLoopSeconds, uLife, uSize, uSizeJitter, uTwinkle, uPixelRatio;
|
|
947
|
+
uniform float uPartSpeed;
|
|
948
|
+
uniform vec3 uColor, uColor2, uCenter, uRight, uUp;
|
|
949
|
+
uniform float uDrift, uRise, uSwirl, uWander;
|
|
950
|
+
|
|
951
|
+
// The owning wave's shape, mirrored in configure() so the dust rides the SAME deform as the ribbon.
|
|
952
|
+
// The HELIX/RADIAL uniform blocks are declared only when the matching #define is set.
|
|
953
|
+
${simplex2d}
|
|
954
|
+
uniform float uDispFreqX, uDispFreqZ, uDispAmount;
|
|
955
|
+
uniform float uDetailFreq, uDetailAmount;
|
|
956
|
+
uniform float uTwFreqX, uTwFreqY, uTwFreqZ, uTwPowX, uTwPowY, uTwPowZ;
|
|
957
|
+
#ifdef HELIX
|
|
958
|
+
uniform float uHelixTurns, uHelixRadius, uHelixRoll, uHelixPhase;
|
|
959
|
+
#endif
|
|
960
|
+
#ifdef RADIAL
|
|
961
|
+
uniform float uRadialAmount, uRadialArc, uRadialSpread, uRadialRadius, uRadialCenter;
|
|
962
|
+
#endif
|
|
963
|
+
uniform mat4 uShedModel; // the wave's matrixWorld (deformed LOCAL → world)
|
|
964
|
+
uniform float uShedSpeed, uShedSeed;
|
|
965
|
+
${waveShapeChunk}
|
|
966
|
+
|
|
967
|
+
varying float vAlpha;
|
|
968
|
+
varying vec3 vColor;
|
|
969
|
+
varying vec2 vDir; // screen-space motion direction (for the streak sprite)
|
|
970
|
+
|
|
971
|
+
const float TAU = 6.28318530718;
|
|
972
|
+
|
|
973
|
+
void main(){
|
|
974
|
+
// Deterministic life: age 0..1 from uTime + a per-particle seed. Advances once per loop period when
|
|
975
|
+
// looping (so the whole field repeats seamlessly), else once per uLife seconds. uPartSpeed scales the
|
|
976
|
+
// cadence (motion speed); under a loop it snaps to a whole number of cycles so the seam stays seamless.
|
|
977
|
+
float cyc = max(1.0, floor(uPartSpeed + 0.5));
|
|
978
|
+
float rate = (uLoopSeconds > 0.0) ? (uTime / uLoopSeconds * cyc) : (uTime * uPartSpeed / max(uLife, 0.001));
|
|
979
|
+
float age = fract(rate + aSeed);
|
|
980
|
+
float fade = sin(3.14159265 * age); // 0 at birth/death, 1 mid-life
|
|
981
|
+
|
|
982
|
+
// Spawn on the owning wave's DEFORMED surface / edge at aUv (via the shared waveShape), then peel
|
|
983
|
+
// outward from the wave centre as the particle ages — silk dissolving into glitter.
|
|
984
|
+
float ts = uTime * uShedSpeed + uShedSeed;
|
|
985
|
+
vec2 loopOff = vec2(0.0);
|
|
986
|
+
#ifdef LOOP_MOTION
|
|
987
|
+
float loopTheta = uTime * (TAU / uLoopSeconds) + uShedSeed;
|
|
988
|
+
float loopR = uShedSpeed * uLoopSeconds * 0.159154943092;
|
|
989
|
+
loopOff = loopR * vec2(cos(loopTheta), sin(loopTheta));
|
|
990
|
+
ts = 0.0;
|
|
991
|
+
#endif
|
|
992
|
+
// Approximate the base hairpin point for this uv (length from uv.y; width centre), then deform it
|
|
993
|
+
// exactly as the wave does. Good enough for dust — the fan / displacement dominate.
|
|
994
|
+
vec3 base = vec3((aUv.y - 0.5) * 400.0, 0.0, ${(-8).toFixed(1)});
|
|
995
|
+
vec3 origin = (uShedModel * vec4(waveShape(base, aUv, ts, loopOff).pos, 1.0)).xyz;
|
|
996
|
+
vec3 outward = normalize(origin - uCenter + vec3(1e-4));
|
|
997
|
+
vec3 p = origin + outward * age * uDrift + (aRnd.xyz - 0.5) * age * uDrift * 0.35;
|
|
998
|
+
|
|
999
|
+
// Motion styles, each 0 = off, all riding age so they stay loop-safe (the age wrap is hidden by
|
|
1000
|
+
// fade→0 at birth/death). rise = screen-vertical buoyancy (embers up / snow down); swirl = orbit
|
|
1001
|
+
// around the wave centre in the screen plane; wander = curl-noise turbulence (fireflies / motes).
|
|
1002
|
+
p += uUp * age * uRise;
|
|
1003
|
+
if (uSwirl != 0.0) {
|
|
1004
|
+
vec3 nrm = cross(uRight, uUp);
|
|
1005
|
+
vec3 rel = p - uCenter;
|
|
1006
|
+
float rx = dot(rel, uRight), ry = dot(rel, uUp), rz = dot(rel, nrm);
|
|
1007
|
+
float a = age * uSwirl * TAU;
|
|
1008
|
+
float ca = cos(a), sa = sin(a);
|
|
1009
|
+
p = uCenter + uRight * (rx * ca - ry * sa) + uUp * (rx * sa + ry * ca) + nrm * rz;
|
|
1010
|
+
}
|
|
1011
|
+
if (uWander != 0.0) {
|
|
1012
|
+
vec2 wan = vec2(simplexNoise(vec2(aSeed * 17.0, age * 3.0)),
|
|
1013
|
+
simplexNoise(vec2(age * 3.0, aSeed * 23.0)));
|
|
1014
|
+
p += (uRight * wan.x + uUp * wan.y) * uWander;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
float tw = 0.5 + 0.5 * sin((age * 9.0 + aSeed) * TAU); // loop-safe flicker (rides age)
|
|
1018
|
+
vAlpha = fade * mix(1.0, tw, clamp(uTwinkle, 0.0, 1.0));
|
|
1019
|
+
vColor = mix(uColor, uColor2, aRnd.w); // two-tone dust: per-particle blend of the two colours
|
|
1020
|
+
vDir = normalize(vec2(dot(outward, uRight), dot(outward, uUp)) + vec2(1e-4)); // outward, in screen space
|
|
1021
|
+
gl_Position = projectionMatrix * viewMatrix * vec4(p, 1.0);
|
|
1022
|
+
// Orthographic camera → point size is constant in device pixels (no perspective depth divide).
|
|
1023
|
+
float jitter = 1.0 + uSizeJitter * (aSeed - 0.5) * 2.0;
|
|
1024
|
+
gl_PointSize = max(uSize * uPixelRatio * jitter * fade, 0.0);
|
|
1025
|
+
}
|
|
1026
|
+
`;
|
|
1027
|
+
const particleFragmentShader = `
|
|
1028
|
+
precision highp float;
|
|
1029
|
+
uniform float uShape; // 0 glitter · 1 soft · 2 ring · 3 star · 4 streak
|
|
1030
|
+
varying float vAlpha;
|
|
1031
|
+
varying vec3 vColor;
|
|
1032
|
+
varying vec2 vDir;
|
|
1033
|
+
void main(){
|
|
1034
|
+
vec2 pc = gl_PointCoord - 0.5;
|
|
1035
|
+
float d = length(pc);
|
|
1036
|
+
int s = int(uShape + 0.5);
|
|
1037
|
+
float a;
|
|
1038
|
+
if (s == 1) { // soft: a diffuse gaussian blob (motes / pollen)
|
|
1039
|
+
a = exp(-d * d * 7.0);
|
|
1040
|
+
} else if (s == 2) { // ring: a hollow band (bubbles)
|
|
1041
|
+
a = smoothstep(0.09, 0.0, abs(d - 0.34));
|
|
1042
|
+
} else if (s == 3) { // star: a 4-point sparkle
|
|
1043
|
+
float ang = atan(pc.y, pc.x);
|
|
1044
|
+
float spike = pow(abs(cos(ang * 2.0)), 6.0);
|
|
1045
|
+
a = smoothstep(1.0, 0.0, d / (0.14 + 0.5 * spike));
|
|
1046
|
+
} else if (s == 4) { // streak: an elongated comet along the motion direction
|
|
1047
|
+
float along = dot(pc, vDir);
|
|
1048
|
+
float perp = dot(pc, vec2(-vDir.y, vDir.x));
|
|
1049
|
+
a = smoothstep(0.5, 0.0, length(vec2(along * 0.42, perp * 2.2)));
|
|
1050
|
+
} else { // glitter (0): the soft round additive disc
|
|
1051
|
+
a = smoothstep(0.5, 0.0, d);
|
|
1052
|
+
}
|
|
1053
|
+
a *= vAlpha;
|
|
1054
|
+
if (a <= 0.0) discard;
|
|
1055
|
+
gl_FragColor = vec4(vColor, a); // AdditiveBlending (src = SrcAlpha) → adds vColor·a
|
|
1056
|
+
}
|
|
1057
|
+
`;
|
|
854
1058
|
//#endregion
|
|
855
|
-
export { ditherFragmentShader, fragmentShader, halftoneCmykFragmentShader, halftoneFragmentShader, heatmapFragmentShader, innerLightFragmentShader, lineFragmentShader, paperTextureFragmentShader, postFragmentShader, postVertexShader, vertexShader };
|
|
1059
|
+
export { ditherFragmentShader, fragmentShader, halftoneCmykFragmentShader, halftoneFragmentShader, heatmapFragmentShader, innerLightFragmentShader, lineFragmentShader, paperTextureFragmentShader, particleFragmentShader, particleVertexShader, postFragmentShader, postVertexShader, vertexShader };
|
|
856
1060
|
|
|
857
1061
|
//# sourceMappingURL=shaders.js.map
|