vireuikit 0.3.0 → 0.4.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/dist/web/index.js CHANGED
@@ -1,7 +1,373 @@
1
- import { MEDIUM_VAPOR_FORWARD_SHADER, MEDIUM_VAPOR_CORRECT_SHADER, MEDIUM_VAPOR_REACT_SHADER, MEDIUM_CONDENSATE_FORWARD_SHADER, MEDIUM_CONDENSATE_CORRECT_SHADER, MEDIUM_CONDENSATE_REACT_SHADER, MEDIUM_TRACK_ADVECT_SHADER, MEDIUM_COMPOSITE_SHADER, MEDIUM_SEED_SHADER, MEDIUM_EMIT_SHADER, MEDIUM_RESAMPLE_SHADER, MEDIUM_DEFAULTS, computeMediumSpatialPeriods, srgbToOklab, depthFarOffsetPx, MEDIUM_TIME_PERIOD } from '../chunk-2AFVQ65S.js';
1
+ import { MEDIUM_TRACK_LAYER_FADE_GAMMA, MEDIUM_TRACK_LAYER_LIFE_SECONDS, MEDIUM_TRACK_LAYER_LOOK, MEDIUM_VAPOR_FORWARD_SHADER, MEDIUM_VAPOR_CORRECT_SHADER, MEDIUM_VAPOR_REACT_SHADER, MEDIUM_CONDENSATE_FORWARD_SHADER, MEDIUM_CONDENSATE_CORRECT_SHADER, MEDIUM_CONDENSATE_REACT_SHADER, MEDIUM_TRACK_ADVECT_SHADER, MEDIUM_COMPOSITE_SHADER, MEDIUM_SEED_SHADER, MEDIUM_EMIT_SHADER, MEDIUM_RESAMPLE_SHADER, MEDIUM_SHADOW_SHADER, MEDIUM_DEFAULTS, computeMediumSpatialPeriods, srgbToOklab, depthFarOffsetPx, MEDIUM_MAX_LIGHTS, MEDIUM_TRACK_LAYER_CURL_PUSH, MEDIUM_TRACK_LAYER_GRAVITY_SAG, MEDIUM_TRACK_LAYER_BURST_ACCENT, MEDIUM_TRACK_LAYER_BURST_SPREAD, MEDIUM_TRACK_LAYER_MAX_TRACKS, MEDIUM_TIME_PERIOD, vgPotentialJS } from '../chunk-VY22O2II.js';
2
2
  import { toGLSL } from 'vireglass';
3
- import { createProgram, locationCache, FULLSCREEN_TRIANGLE_VERTEX_SOURCE, bindTextureAt, drawFullscreenTriangle, setUniform, createTexture, createFramebuffer } from 'vireglass/web';
3
+ import { locationCache, createProgram, FULLSCREEN_TRIANGLE_VERTEX_SOURCE, bindTextureAt, drawFullscreenTriangle, setUniform, createTexture, createFramebuffer } from 'vireglass/web';
4
4
 
5
+ // src/web/track-layer.ts
6
+ function trackLayerFade(age, life) {
7
+ if (life <= 0) return 0;
8
+ const t = Math.min(Math.max(age / life, 0), 1);
9
+ return (1 - t) ** MEDIUM_TRACK_LAYER_FADE_GAMMA;
10
+ }
11
+ function trackLayerSigmaMul(age, broaden) {
12
+ return Math.sqrt(1 + broaden * Math.max(age, 0));
13
+ }
14
+ function hash01(n) {
15
+ const s = Math.sin(n * 91.3458) * 47453.5453;
16
+ return s - Math.floor(s);
17
+ }
18
+ function curlVelocityJS(px, py, phase) {
19
+ const e = 0.6;
20
+ const px1 = vgPotentialJS(px + e, py, phase);
21
+ const px0 = vgPotentialJS(px - e, py, phase);
22
+ const py1 = vgPotentialJS(px, py + e, phase);
23
+ const py0 = vgPotentialJS(px, py - e, phase);
24
+ return [(py1 - py0) / (2 * e), -(px1 - px0) / (2 * e)];
25
+ }
26
+ var CURL_SAMPLE_SCALE = 40;
27
+ var PHASE_WRAP = 1e4;
28
+ function createTrackLayer() {
29
+ let tracks = [];
30
+ let phase = 0;
31
+ function step(dt, emissions) {
32
+ phase = (phase + dt) % PHASE_WRAP;
33
+ const next = [];
34
+ for (const track of tracks) {
35
+ const life = MEDIUM_TRACK_LAYER_LIFE_SECONDS[track.emission.kind];
36
+ const age = track.age + dt;
37
+ if (age >= life) continue;
38
+ if (age <= 0) {
39
+ next.push({ ...track, age });
40
+ continue;
41
+ }
42
+ const midX = track.emission.source[0] + Math.cos(track.emission.angle) * track.emission.lengthFrac * 0.5;
43
+ const midY = track.emission.source[1] + Math.sin(track.emission.angle) * track.emission.lengthFrac * 0.5;
44
+ const [curlX, curlY] = curlVelocityJS(midX * CURL_SAMPLE_SCALE, midY * CURL_SAMPLE_SCALE, phase);
45
+ const activeDt = Math.min(dt, age);
46
+ next.push({
47
+ emission: track.emission,
48
+ age,
49
+ driftX: track.driftX + curlX * MEDIUM_TRACK_LAYER_CURL_PUSH * activeDt,
50
+ driftY: track.driftY + (curlY * MEDIUM_TRACK_LAYER_CURL_PUSH - MEDIUM_TRACK_LAYER_GRAVITY_SAG) * activeDt
51
+ });
52
+ }
53
+ let burstIndex = 0;
54
+ for (const emission of emissions) {
55
+ let age = 0;
56
+ if (emission.kind === "alpha") {
57
+ if (burstIndex >= MEDIUM_TRACK_LAYER_BURST_ACCENT) {
58
+ age = -hash01(emission.seed * 3.17 + burstIndex) * MEDIUM_TRACK_LAYER_BURST_SPREAD;
59
+ }
60
+ burstIndex += 1;
61
+ }
62
+ next.push({ emission, age, driftX: 0, driftY: 0 });
63
+ }
64
+ tracks = next.length > MEDIUM_TRACK_LAYER_MAX_TRACKS ? next.slice(next.length - MEDIUM_TRACK_LAYER_MAX_TRACKS) : next;
65
+ return tracks;
66
+ }
67
+ return { step };
68
+ }
69
+ var TRACK_LAYER_VERTEX_SOURCE = `#version 300 es
70
+ layout(location = 0) in vec2 a_corner;
71
+ layout(location = 1) in vec4 a_geom;
72
+ layout(location = 2) in vec4 a_span;
73
+ layout(location = 3) in vec4 a_path;
74
+ layout(location = 4) in vec4 a_grain;
75
+
76
+ uniform vec2 u_resolution;
77
+ uniform float u_trackLightFloor;
78
+ uniform sampler2D u_shadow;
79
+ uniform vec2 u_light0Pos;
80
+ uniform vec2 u_light0Dir;
81
+ uniform float u_light0CosCone;
82
+ uniform float u_light0Falloff;
83
+ uniform vec3 u_light0Color;
84
+ uniform float u_light0Intensity;
85
+ uniform vec2 u_light1Pos;
86
+ uniform vec2 u_light1Dir;
87
+ uniform float u_light1CosCone;
88
+ uniform float u_light1Falloff;
89
+ uniform vec3 u_light1Color;
90
+ uniform float u_light1Intensity;
91
+ uniform vec2 u_light2Pos;
92
+ uniform vec2 u_light2Dir;
93
+ uniform float u_light2CosCone;
94
+ uniform float u_light2Falloff;
95
+ uniform vec3 u_light2Color;
96
+ uniform float u_light2Intensity;
97
+
98
+ out vec2 v_local;
99
+ flat out vec4 v_span;
100
+ flat out vec4 v_path;
101
+ flat out vec4 v_grain;
102
+ flat out float v_reach;
103
+ out vec3 v_illum;
104
+
105
+ float vgHash21(vec2 p) {
106
+ float h = dot(p, vec2(127.1, 311.7));
107
+ return fract(sin(h) * 43758.5453123);
108
+ }
109
+
110
+ float vgValueNoise(vec2 p) {
111
+ vec2 i = floor(p);
112
+ vec2 f = fract(p);
113
+ float a = vgHash21(i);
114
+ float b = vgHash21(i + vec2(1.0, 0.0));
115
+ float c = vgHash21(i + vec2(0.0, 1.0));
116
+ float d = vgHash21(i + vec2(1.0, 1.0));
117
+ vec2 u = f * f * (3.0 - 2.0 * f);
118
+ return mix(mix(a, b, u.x), mix(c, d, u.x), u.y) * 2.0 - 1.0;
119
+ }
120
+
121
+ // Keep in step with vgLightAmount in composite-shader.ts.
122
+ float vgLightAmount(vec2 xy, vec2 lightPosFrac, vec2 lightDir, float cosCone, float falloff, vec2 resolution, float seed) {
123
+ vec2 rel = xy - lightPosFrac * resolution;
124
+ if (cosCone <= -0.999) {
125
+ float depth = max(dot(rel, lightDir), 0.0);
126
+ return exp(-(depth / max(resolution.y, 1.0)) * falloff);
127
+ }
128
+ float dist = length(rel);
129
+ vec2 dirN = dist > 1e-4 ? rel / dist : lightDir;
130
+ float cosAngle = dot(dirN, lightDir);
131
+ float lobe = log(0.5) / log(clamp(cosCone, 0.05, 0.999));
132
+ float edge = pow(max(cosAngle, 0.0), lobe);
133
+ float atten = exp(-(dist / max(resolution.y, 1.0)) * falloff);
134
+ if (edge * atten < 0.001) return 0.0;
135
+ float angle = atan(dirN.x * lightDir.y - dirN.y * lightDir.x, cosAngle);
136
+ float shafts = 0.6 + 0.32 * vgValueNoise(vec2(angle * 14.0, seed)) + 0.18 * vgValueNoise(vec2(angle * 33.0, seed + 5.3));
137
+ return edge * atten * clamp(shafts, 0.0, 1.0);
138
+ }
139
+
140
+ void main() {
141
+ vec2 dir = a_geom.zw;
142
+ vec2 side = vec2(-dir.y, dir.x);
143
+ // Beads widen sigma by up to 20%; 3.5 sigma leaves under 1% of the core outside the quad.
144
+ float reach = max(a_span.z, a_span.w) * 1.2 * 3.5 + 1.5;
145
+ float margin = reach + a_path.x * 1.7;
146
+ float u = mix(a_span.y - reach, a_span.x + reach, a_corner.y);
147
+ float s = a_corner.x * margin;
148
+ vec2 pos = a_geom.xy + dir * u + side * s;
149
+ v_local = vec2(u, s);
150
+ v_span = a_span;
151
+ v_path = a_path;
152
+ v_grain = a_grain;
153
+ v_reach = reach;
154
+ // Lit per corner: light varies over hundreds of px, a track is a few px wide. The backdrop
155
+ // target stores the scene top row at y = 0; lights use y up.
156
+ vec2 xy = vec2(pos.x, u_resolution.y - pos.y);
157
+ // The same gas shadow the composite applies to the spotlights (shadow-shader.ts, y up).
158
+ vec4 shadow = texture(u_shadow, xy / u_resolution);
159
+ float light0 = vgLightAmount(xy, u_light0Pos, u_light0Dir, u_light0CosCone, u_light0Falloff, u_resolution, 1.7) * u_light0Intensity;
160
+ float light1 = vgLightAmount(xy, u_light1Pos, u_light1Dir, u_light1CosCone, u_light1Falloff, u_resolution, 4.1) * u_light1Intensity * shadow.r;
161
+ float light2 = vgLightAmount(xy, u_light2Pos, u_light2Dir, u_light2CosCone, u_light2Falloff, u_resolution, 8.3) * u_light2Intensity * shadow.g;
162
+ v_illum = u_light0Color * light0 + u_light1Color * light1 + u_light2Color * light2 + vec3(u_trackLightFloor);
163
+ gl_Position = vec4((pos / u_resolution) * 2.0 - 1.0, 0.0, 1.0);
164
+ }
165
+ `;
166
+ var TRACK_LAYER_FRAGMENT_SOURCE = `#version 300 es
167
+ precision highp float;
168
+
169
+ in vec2 v_local;
170
+ flat in vec4 v_span;
171
+ flat in vec4 v_path;
172
+ flat in vec4 v_grain;
173
+ flat in float v_reach;
174
+ in vec3 v_illum;
175
+
176
+ out vec4 fragColor;
177
+
178
+ float vgHash(float a, float b) {
179
+ uint x = floatBitsToUint(a) * 0x9E3779B1u ^ (floatBitsToUint(b) + 0x7F4A7C15u);
180
+ x ^= x >> 16;
181
+ x *= 0x7FEB352Du;
182
+ x ^= x >> 15;
183
+ x *= 0x846CA68Bu;
184
+ x ^= x >> 16;
185
+ return float(x) * (1.0 / 4294967295.0);
186
+ }
187
+
188
+ float vgNoise1(float x, float salt) {
189
+ float i = floor(x);
190
+ float f = fract(x);
191
+ float u = f * f * (3.0 - 2.0 * f);
192
+ return mix(vgHash(i, salt), vgHash(i + 1.0, salt), u);
193
+ }
194
+
195
+ void main() {
196
+ float len = v_span.x;
197
+ float start = v_span.y;
198
+ float sigma0 = v_span.z;
199
+ float sigma1 = v_span.w;
200
+ float wiggleAmp = v_path.x;
201
+ float wiggleFreq = v_path.y;
202
+ float amp = v_path.z;
203
+ float seed = v_path.w;
204
+ float beadPx = v_grain.x;
205
+ float dropPx = v_grain.y;
206
+ float dropChance = v_grain.z;
207
+ float blur = v_grain.w;
208
+
209
+ float u = v_local.x;
210
+ float s = v_local.y;
211
+ float t = clamp((u - start) / max(len - start, 1.0), 0.0, 1.0);
212
+
213
+ float wiggle = 0.0;
214
+ if (wiggleAmp > 0.0) {
215
+ float x = u * wiggleFreq;
216
+ wiggle = wiggleAmp * smoothstep(0.0, 0.2, t) *
217
+ ((vgNoise1(x, seed) - 0.5) * 2.0 + (vgNoise1(x * 3.1, seed + 1.0) - 0.5) * 0.7);
218
+ }
219
+ float across = s - wiggle;
220
+ if (abs(across) > v_reach) discard;
221
+
222
+ float bead = vgNoise1(u / beadPx, seed + 2.0);
223
+ // Blur (age, defocus) washes the beads out rather than stretching them into ribs.
224
+ float sigma = mix(sigma0, sigma1, t * t) * (1.0 + (bead - 0.5) * 0.4 / blur);
225
+ float beyond = u < start ? start - u : max(u - len, 0.0);
226
+ float core = exp(-0.5 * (across * across + beyond * beyond) / (sigma * sigma));
227
+ float along = 1.0 - 0.35 / blur * vgNoise1(u / (beadPx * 2.3), seed + 3.0);
228
+
229
+ float drops = 0.0;
230
+ float cell0 = floor(u / dropPx);
231
+ for (int k = -1; k <= 1; k++) {
232
+ float c = cell0 + float(k);
233
+ if (vgHash(c, seed + 4.0) > dropChance) continue;
234
+ float cu = (c + vgHash(c, seed + 5.0)) * dropPx;
235
+ if (cu < start || cu > len) continue;
236
+ float cs = (vgHash(c, seed + 6.0) + vgHash(c, seed + 7.0) - 1.0) * 1.7 * sigma + wiggle;
237
+ float r = max(0.7, sigma * mix(0.35, 0.8, vgHash(c, seed + 8.0)));
238
+ float dx = u - cu;
239
+ float dy = s - cs;
240
+ drops += mix(0.35, 1.0, vgHash(c, seed + 9.0)) * exp(-0.5 * (dx * dx + dy * dy) / (r * r));
241
+ }
242
+
243
+ float lum = amp * (core * along + drops * 0.7 / (blur * blur));
244
+ if (lum < 0.002) discard;
245
+
246
+ vec3 glow = lum * v_illum;
247
+ fragColor = vec4(vec3(1.0) - exp(-glow), 1.0);
248
+ }
249
+ `;
250
+ var FLOATS_PER_INSTANCE = 16;
251
+ var DEFOCUS_FROM_DEPTH = 0.7;
252
+ var DEFOCUS_MAX = 2.4;
253
+ function buildTrackLayerInstances(tracks, contentWidth, contentHeight, amount) {
254
+ const minDim = Math.min(contentWidth, contentHeight);
255
+ const data = new Float32Array(tracks.length * FLOATS_PER_INSTANCE);
256
+ let count = 0;
257
+ if (amount <= 0) return { data, count };
258
+ for (const track of tracks) {
259
+ if (track.age < 0) continue;
260
+ const e = track.emission;
261
+ const fade = trackLayerFade(track.age, MEDIUM_TRACK_LAYER_LIFE_SECONDS[e.kind]);
262
+ if (fade <= 1e-4) continue;
263
+ const look = MEDIUM_TRACK_LAYER_LOOK[e.kind];
264
+ const sigmaMul = trackLayerSigmaMul(track.age, look.broaden);
265
+ const near = Math.max(0, (e.depth - DEFOCUS_FROM_DEPTH) / (1 - DEFOCUS_FROM_DEPTH));
266
+ const defocus = 1 + (DEFOCUS_MAX - 1) * near * near;
267
+ const sigma0 = Math.max(look.sigmaFrac * minDim, 0.6) * sigmaMul * defocus;
268
+ const o = count * FLOATS_PER_INSTANCE;
269
+ data[o + 0] = e.source[0] * contentWidth + track.driftX * minDim;
270
+ data[o + 1] = e.source[1] * contentHeight - track.driftY * minDim;
271
+ data[o + 2] = Math.cos(e.angle);
272
+ data[o + 3] = Math.sin(e.angle);
273
+ const len = e.lengthFrac * minDim;
274
+ data[o + 4] = len;
275
+ data[o + 5] = Math.min(look.startFrac * minDim, len * 0.5);
276
+ data[o + 6] = sigma0;
277
+ data[o + 7] = sigma0 * look.endSigmaMul;
278
+ data[o + 8] = e.raggedFrac > 0 ? look.wiggleFrac * minDim : 0;
279
+ data[o + 9] = e.raggedFreq / Math.max(len, 1);
280
+ data[o + 10] = e.intensity * look.gain * fade * amount / (sigmaMul ** 0.7 * defocus);
281
+ data[o + 11] = e.seed;
282
+ data[o + 12] = look.beadFrac * minDim;
283
+ data[o + 13] = look.dropletFrac * minDim;
284
+ data[o + 14] = look.dropletChance;
285
+ data[o + 15] = sigmaMul * defocus;
286
+ count += 1;
287
+ }
288
+ return { data, count };
289
+ }
290
+ function compile(gl, type, source, label) {
291
+ const shader = gl.createShader(type);
292
+ if (!shader) throw new Error(`vireuikit: track layer ${label} shader creation failed`);
293
+ gl.shaderSource(shader, source);
294
+ gl.compileShader(shader);
295
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
296
+ throw new Error(`vireuikit: track layer ${label} shader failed:
297
+ ${gl.getShaderInfoLog(shader) ?? ""}`);
298
+ }
299
+ return shader;
300
+ }
301
+ function createTrackLayerProgram(gl) {
302
+ const vertex = compile(gl, gl.VERTEX_SHADER, TRACK_LAYER_VERTEX_SOURCE, "vertex");
303
+ const fragment = compile(gl, gl.FRAGMENT_SHADER, TRACK_LAYER_FRAGMENT_SOURCE, "fragment");
304
+ const program = gl.createProgram();
305
+ if (!program) throw new Error("vireuikit: track layer program creation failed");
306
+ gl.attachShader(program, vertex);
307
+ gl.attachShader(program, fragment);
308
+ gl.linkProgram(program);
309
+ gl.deleteShader(vertex);
310
+ gl.deleteShader(fragment);
311
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
312
+ throw new Error(`vireuikit: track layer program failed to link:
313
+ ${gl.getProgramInfoLog(program) ?? ""}`);
314
+ }
315
+ const loc = locationCache(gl, program);
316
+ const quadBuffer = gl.createBuffer();
317
+ gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
318
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, 0, 1, 0, -1, 1, 1, 1]), gl.STATIC_DRAW);
319
+ const instanceBuffer = gl.createBuffer();
320
+ const vao = gl.createVertexArray();
321
+ gl.bindVertexArray(vao);
322
+ gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
323
+ gl.enableVertexAttribArray(0);
324
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
325
+ const stride = FLOATS_PER_INSTANCE * 4;
326
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
327
+ for (let location = 1; location <= 4; location += 1) {
328
+ gl.enableVertexAttribArray(location);
329
+ gl.vertexAttribPointer(location, 4, gl.FLOAT, false, stride, (location - 1) * 16);
330
+ gl.vertexAttribDivisor(location, 1);
331
+ }
332
+ gl.bindVertexArray(null);
333
+ function bindLight(slot, light) {
334
+ const position = light?.position ?? [0, 0];
335
+ const direction = light?.direction ?? [0, 1];
336
+ const dirLen = Math.hypot(direction[0], direction[1]) || 1;
337
+ setUniform(gl, loc(`u_light${slot}Pos`), position);
338
+ setUniform(gl, loc(`u_light${slot}Dir`), [direction[0] / dirLen, direction[1] / dirLen]);
339
+ setUniform(gl, loc(`u_light${slot}CosCone`), Math.cos(light?.coneAngle ?? 0));
340
+ setUniform(gl, loc(`u_light${slot}Falloff`), light?.falloff ?? 0);
341
+ setUniform(gl, loc(`u_light${slot}Color`), light?.color ?? [0, 0, 0]);
342
+ setUniform(gl, loc(`u_light${slot}Intensity`), light?.intensity ?? 0);
343
+ }
344
+ function draw(contentWidth, contentHeight, data, count, trackLightFloor, lights, shadow) {
345
+ if (count <= 0) return;
346
+ gl.useProgram(program);
347
+ gl.bindVertexArray(vao);
348
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
349
+ gl.bufferData(gl.ARRAY_BUFFER, data.subarray(0, count * FLOATS_PER_INSTANCE), gl.DYNAMIC_DRAW);
350
+ setUniform(gl, loc("u_resolution"), [contentWidth, contentHeight]);
351
+ setUniform(gl, loc("u_trackLightFloor"), trackLightFloor);
352
+ for (let slot = 0; slot < 3; slot += 1) bindLight(slot, lights[slot]);
353
+ bindTextureAt(gl, 0, shadow, program, "u_shadow");
354
+ gl.enable(gl.BLEND);
355
+ gl.blendFunc(gl.ONE, gl.ONE);
356
+ gl.disable(gl.DEPTH_TEST);
357
+ gl.viewport(0, 0, contentWidth, contentHeight);
358
+ gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, count);
359
+ gl.bindVertexArray(null);
360
+ }
361
+ function destroy() {
362
+ gl.deleteBuffer(quadBuffer);
363
+ gl.deleteBuffer(instanceBuffer);
364
+ gl.deleteVertexArray(vao);
365
+ gl.deleteProgram(program);
366
+ }
367
+ return { draw, destroy };
368
+ }
369
+
370
+ // src/web/medium.ts
5
371
  function pickDyeFormat(gl) {
6
372
  const hasFloat = gl.getExtension("EXT_color_buffer_float") !== null;
7
373
  return hasFloat ? { internalFormat: gl.RGBA16F, type: gl.HALF_FLOAT } : { internalFormat: gl.RGBA8, type: gl.UNSIGNED_BYTE };
@@ -19,6 +385,10 @@ function createMediumRuntime(gl, vertexSource) {
19
385
  const seedProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_SEED_SHADER));
20
386
  const emitProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_EMIT_SHADER));
21
387
  const resampleProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_RESAMPLE_SHADER));
388
+ const shadowProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_SHADOW_SHADER));
389
+ const trackLayerProgram = createTrackLayerProgram(gl);
390
+ const trackLayer = createTrackLayer();
391
+ let liveTracks = [];
22
392
  const vaporForwardLoc = locationCache(gl, vaporForwardProgram);
23
393
  const vaporCorrectLoc = locationCache(gl, vaporCorrectProgram);
24
394
  const vaporReactLoc = locationCache(gl, vaporReactProgram);
@@ -30,6 +400,7 @@ function createMediumRuntime(gl, vertexSource) {
30
400
  const seedLoc = locationCache(gl, seedProgram);
31
401
  const emitLoc = locationCache(gl, emitProgram);
32
402
  const resampleLoc = locationCache(gl, resampleProgram);
403
+ const shadowLoc = locationCache(gl, shadowProgram);
33
404
  let gridW = 0;
34
405
  let gridH = 0;
35
406
  let vapor = null;
@@ -39,6 +410,7 @@ function createMediumRuntime(gl, vertexSource) {
39
410
  let vaporCorrected = null;
40
411
  let condensateForward = null;
41
412
  let condensateCorrected = null;
413
+ let shadow = null;
42
414
  let front = 0;
43
415
  function makeTarget(w, h) {
44
416
  const texture = createTexture(gl, {
@@ -82,6 +454,7 @@ function createMediumRuntime(gl, vertexSource) {
82
454
  destroyTarget(vaporCorrected);
83
455
  destroyTarget(condensateForward);
84
456
  destroyTarget(condensateCorrected);
457
+ destroyTarget(shadow);
85
458
  vapor = null;
86
459
  condensate = null;
87
460
  track = null;
@@ -89,6 +462,7 @@ function createMediumRuntime(gl, vertexSource) {
89
462
  vaporCorrected = null;
90
463
  condensateForward = null;
91
464
  condensateCorrected = null;
465
+ shadow = null;
92
466
  }
93
467
  function seed(w, h) {
94
468
  if (!vapor || !condensate || !track) return;
@@ -142,6 +516,7 @@ function createMediumRuntime(gl, vertexSource) {
142
516
  vaporCorrected = makeTarget(w, h);
143
517
  condensateForward = makeTarget(w, h);
144
518
  condensateCorrected = makeTarget(w, h);
519
+ shadow = makeTarget(w, h);
145
520
  front = 0;
146
521
  const areaRatio = w * h / (oldW * oldH);
147
522
  waterTargetTotal *= areaRatio;
@@ -160,6 +535,7 @@ function createMediumRuntime(gl, vertexSource) {
160
535
  vaporCorrected = makeTarget(w, h);
161
536
  condensateForward = makeTarget(w, h);
162
537
  condensateCorrected = makeTarget(w, h);
538
+ shadow = makeTarget(w, h);
163
539
  front = 0;
164
540
  seed(w, h);
165
541
  waterTargetTotal = readTarget(vapor[front]) + readTarget(condensate[front]);
@@ -170,6 +546,17 @@ function createMediumRuntime(gl, vertexSource) {
170
546
  }
171
547
  resizeGrid(w, h);
172
548
  }
549
+ function bindLightUniforms(slot, light) {
550
+ const position = light?.position ?? [0, 0];
551
+ const direction = light?.direction ?? [0, 1];
552
+ const dirLen = Math.hypot(direction[0], direction[1]) || 1;
553
+ setUniform(gl, compositeLoc(`u_light${slot}Pos`), position);
554
+ setUniform(gl, compositeLoc(`u_light${slot}Dir`), [direction[0] / dirLen, direction[1] / dirLen]);
555
+ setUniform(gl, compositeLoc(`u_light${slot}CosCone`), Math.cos(light?.coneAngle ?? 0));
556
+ setUniform(gl, compositeLoc(`u_light${slot}Falloff`), light?.falloff ?? 0);
557
+ setUniform(gl, compositeLoc(`u_light${slot}Color`), light?.color ?? [0, 0, 0]);
558
+ setUniform(gl, compositeLoc(`u_light${slot}Intensity`), light?.intensity ?? 0);
559
+ }
173
560
  function bindVelocityUniforms(loc, dt, phase2, p, spatial) {
174
561
  setUniform(gl, loc("u_dyeSize"), [gridW, gridH]);
175
562
  setUniform(gl, loc("u_resolution"), [gridW, gridH]);
@@ -195,7 +582,7 @@ function createMediumRuntime(gl, vertexSource) {
195
582
  let timeSinceRenorm = 0;
196
583
  let framesSinceRenorm = 0;
197
584
  function step(dt, params = {}) {
198
- if (!vapor || !condensate || !track || !vaporForward || !vaporCorrected || !condensateForward || !condensateCorrected) {
585
+ if (!vapor || !condensate || !track || !vaporForward || !vaporCorrected || !condensateForward || !condensateCorrected || !shadow) {
199
586
  return;
200
587
  }
201
588
  const p = { ...MEDIUM_DEFAULTS, ...params };
@@ -269,6 +656,20 @@ function createMediumRuntime(gl, vertexSource) {
269
656
  bindVelocityUniforms(trackAdvectLoc, clampedDt, phase, p, spatial);
270
657
  setUniform(gl, trackAdvectLoc("u_decay"), p.decay);
271
658
  drawFullscreenTriangle(gl);
659
+ gl.bindFramebuffer(gl.FRAMEBUFFER, shadow.fbo);
660
+ gl.useProgram(shadowProgram);
661
+ bindTextureAt(gl, 0, vapor[back].texture, shadowProgram, "u_vapor");
662
+ bindTextureAt(gl, 1, condensate[back].texture, shadowProgram, "u_condensate");
663
+ setUniform(gl, shadowLoc("u_vaporSize"), gridSize);
664
+ setUniform(gl, shadowLoc("u_condensateSize"), gridSize);
665
+ setUniform(gl, shadowLoc("u_resolution"), gridSize);
666
+ setUniform(gl, shadowLoc("u_condensateGain"), p.condensateGain);
667
+ setUniform(gl, shadowLoc("u_extinction"), p.shadowExtinction);
668
+ for (const slot of [1, 2]) {
669
+ const position = p.lights[slot]?.position ?? [0, 0];
670
+ setUniform(gl, shadowLoc(`u_light${slot}Grid`), [position[0] * gridW, position[1] * gridH]);
671
+ }
672
+ drawFullscreenTriangle(gl);
272
673
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
273
674
  front = back;
274
675
  framesSinceRenorm += 1;
@@ -310,14 +711,16 @@ function createMediumRuntime(gl, vertexSource) {
310
711
  setUniform(gl, emitLoc("u_tailDim"), e.tailDim);
311
712
  setUniform(gl, emitLoc("u_intensity"), e.intensity);
312
713
  setUniform(gl, emitLoc("u_seed"), e.seed);
313
- setUniform(gl, emitLoc("u_channelMask"), e.channel);
314
714
  drawFullscreenTriangle(gl);
315
715
  }
316
716
  gl.disable(gl.BLEND);
317
717
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
318
718
  }
719
+ function stepTrackLayer(dt, emissions) {
720
+ liveTracks = trackLayer.step(dt, emissions);
721
+ }
319
722
  function composite(contentWidth, contentHeight, params = {}) {
320
- if (!vapor || !condensate || !track) return;
723
+ if (!vapor || !condensate || !track || !shadow) return;
321
724
  const p = { ...MEDIUM_DEFAULTS, ...params };
322
725
  gl.viewport(0, 0, contentWidth, contentHeight);
323
726
  gl.disable(gl.BLEND);
@@ -325,9 +728,11 @@ function createMediumRuntime(gl, vertexSource) {
325
728
  bindTextureAt(gl, 0, vapor[front].texture, compositeProgram, "u_vapor");
326
729
  bindTextureAt(gl, 1, condensate[front].texture, compositeProgram, "u_condensate");
327
730
  bindTextureAt(gl, 2, track[front].texture, compositeProgram, "u_track");
731
+ bindTextureAt(gl, 3, shadow.texture, compositeProgram, "u_shadow");
328
732
  setUniform(gl, compositeLoc("u_vaporSize"), [gridW, gridH]);
329
733
  setUniform(gl, compositeLoc("u_condensateSize"), [gridW, gridH]);
330
734
  setUniform(gl, compositeLoc("u_trackSize"), [gridW, gridH]);
735
+ setUniform(gl, compositeLoc("u_shadowSize"), [gridW, gridH]);
331
736
  setUniform(gl, compositeLoc("u_resolution"), [contentWidth, contentHeight]);
332
737
  setUniform(gl, compositeLoc("u_dyeScale"), [gridW / contentWidth, gridH / contentHeight]);
333
738
  setUniform(gl, compositeLoc("u_lab0"), srgbToOklab(p.channelColors[0]));
@@ -344,7 +749,12 @@ function createMediumRuntime(gl, vertexSource) {
344
749
  setUniform(gl, compositeLoc("u_farWeight"), p.depthFarWeight);
345
750
  setUniform(gl, compositeLoc("u_gravityBoost"), p.gravityBottomBoost);
346
751
  setUniform(gl, compositeLoc("u_gravityBoostStart"), p.gravityBottomBoostStart);
752
+ setUniform(gl, compositeLoc("u_channelScatter"), p.channelScatter);
753
+ for (let slot = 0; slot < MEDIUM_MAX_LIGHTS; slot += 1) bindLightUniforms(slot, p.lights[slot]);
754
+ setUniform(gl, compositeLoc("u_trackLightFloor"), p.trackLightFloor);
347
755
  drawFullscreenTriangle(gl);
756
+ const { data, count } = buildTrackLayerInstances(liveTracks, contentWidth, contentHeight, p.trackLayerAmount);
757
+ trackLayerProgram.draw(contentWidth, contentHeight, data, count, p.trackLightFloor, p.lights, shadow.texture);
348
758
  }
349
759
  function readTarget(target) {
350
760
  gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
@@ -411,6 +821,23 @@ function createMediumRuntime(gl, vertexSource) {
411
821
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
412
822
  return { cols: gridW, rows: gridH, data };
413
823
  }
824
+ function readTrackGrid() {
825
+ if (!track) return null;
826
+ gl.bindFramebuffer(gl.FRAMEBUFFER, track[front].fbo);
827
+ const cells = gridW * gridH;
828
+ const data = new Array(cells);
829
+ if (format.type === gl.HALF_FLOAT) {
830
+ const buf = new Float32Array(cells * 4);
831
+ gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.FLOAT, buf);
832
+ for (let i = 0; i < cells; i += 1) data[i] = buf[i * 4];
833
+ } else {
834
+ const buf = new Uint8Array(cells * 4);
835
+ gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.UNSIGNED_BYTE, buf);
836
+ for (let i = 0; i < cells; i += 1) data[i] = buf[i * 4] / 255;
837
+ }
838
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
839
+ return { cols: gridW, rows: gridH, data };
840
+ }
414
841
  function destroy() {
415
842
  destroyTargets();
416
843
  gl.deleteProgram(vaporForwardProgram);
@@ -423,9 +850,22 @@ function createMediumRuntime(gl, vertexSource) {
423
850
  gl.deleteProgram(compositeProgram);
424
851
  gl.deleteProgram(seedProgram);
425
852
  gl.deleteProgram(emitProgram);
853
+ gl.deleteProgram(shadowProgram);
426
854
  gl.deleteProgram(resampleProgram);
855
+ trackLayerProgram.destroy();
427
856
  }
428
- return { ensureGrid, step, emit, composite, readTotals, readVaporGrid, readCondensateGrid, destroy };
857
+ return {
858
+ ensureGrid,
859
+ step,
860
+ emit,
861
+ stepTrackLayer,
862
+ composite,
863
+ readTotals,
864
+ readVaporGrid,
865
+ readCondensateGrid,
866
+ readTrackGrid,
867
+ destroy
868
+ };
429
869
  }
430
870
  function createMediumBackdrop() {
431
871
  let runtime = null;
@@ -438,12 +878,14 @@ function createMediumBackdrop() {
438
878
  runtime.ensureGrid(frame.gridWidth, frame.gridHeight);
439
879
  runtime.step(frame.dt, frame.params);
440
880
  runtime.emit(frame.emissions ?? []);
881
+ runtime.stepTrackLayer(frame.dt, frame.emissions ?? []);
441
882
  gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);
442
883
  runtime.composite(target.width, target.height, frame.params);
443
884
  },
444
885
  readTotals: () => runtime?.readTotals() ?? null,
445
886
  readVaporGrid: () => runtime?.readVaporGrid() ?? null,
446
887
  readCondensateGrid: () => runtime?.readCondensateGrid() ?? null,
888
+ readTrackGrid: () => runtime?.readTrackGrid() ?? null,
447
889
  destroy: () => {
448
890
  runtime?.destroy();
449
891
  runtime = null;
@@ -452,6 +894,6 @@ function createMediumBackdrop() {
452
894
  };
453
895
  }
454
896
 
455
- export { createMediumBackdrop, createMediumRuntime };
897
+ export { buildTrackLayerInstances, createMediumBackdrop, createMediumRuntime, createTrackLayer, createTrackLayerProgram, trackLayerFade, trackLayerSigmaMul };
456
898
  //# sourceMappingURL=index.js.map
457
899
  //# sourceMappingURL=index.js.map