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/README.md +27 -7
- package/dist/{chunk-2AFVQ65S.js → chunk-VY22O2II.js} +324 -33
- package/dist/chunk-VY22O2II.js.map +1 -0
- package/dist/{dynamics-7Bq3LLds.d.cts → dynamics-DqGk_f1n.d.cts} +157 -13
- package/dist/{dynamics-7Bq3LLds.d.ts → dynamics-DqGk_f1n.d.ts} +157 -13
- package/dist/index.cjs +291 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -4
- package/dist/index.d.ts +19 -4
- package/dist/index.js +1 -1
- package/dist/web/index.cjs +693 -9
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +63 -2
- package/dist/web/index.d.ts +63 -2
- package/dist/web/index.js +449 -7
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2AFVQ65S.js.map +0 -1
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-
|
|
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 {
|
|
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 {
|
|
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
|