vireuikit 0.2.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 +47 -15
- package/dist/{chunk-BV5P6MF7.js → chunk-VY22O2II.js} +485 -46
- package/dist/chunk-VY22O2II.js.map +1 -0
- package/dist/{dynamics-DelasYER.d.cts → dynamics-DqGk_f1n.d.cts} +170 -14
- package/dist/{dynamics-DelasYER.d.ts → dynamics-DqGk_f1n.d.ts} +170 -14
- package/dist/index.cjs +434 -43
- 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 +984 -145
- 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 +462 -13
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-BV5P6MF7.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, 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,7 +546,18 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
170
546
|
}
|
|
171
547
|
resizeGrid(w, h);
|
|
172
548
|
}
|
|
173
|
-
function
|
|
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
|
+
}
|
|
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]);
|
|
176
563
|
setUniform(gl, loc("u_dt"), dt);
|
|
@@ -179,6 +566,10 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
179
566
|
setUniform(gl, loc("u_advectSpeed"), p.advectSpeed);
|
|
180
567
|
setUniform(gl, loc("u_turbulence"), p.turbulence);
|
|
181
568
|
setUniform(gl, loc("u_vaporDrift"), p.gravityVaporDrift);
|
|
569
|
+
setUniform(gl, loc("u_spatial0"), [spatial[0].freqX, spatial[0].freqY, spatial[0].periodX, spatial[0].periodY]);
|
|
570
|
+
setUniform(gl, loc("u_spatial1"), [spatial[1].freqX, spatial[1].freqY, spatial[1].periodX, spatial[1].periodY]);
|
|
571
|
+
setUniform(gl, loc("u_spatial2"), [spatial[2].freqX, spatial[2].freqY, spatial[2].periodX, spatial[2].periodY]);
|
|
572
|
+
setUniform(gl, loc("u_spatial3"), [spatial[3].freqX, spatial[3].freqY, spatial[3].periodX, spatial[3].periodY]);
|
|
182
573
|
}
|
|
183
574
|
let phase = 0;
|
|
184
575
|
function wrapPhase(value) {
|
|
@@ -191,10 +582,11 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
191
582
|
let timeSinceRenorm = 0;
|
|
192
583
|
let framesSinceRenorm = 0;
|
|
193
584
|
function step(dt, params = {}) {
|
|
194
|
-
if (!vapor || !condensate || !track || !vaporForward || !vaporCorrected || !condensateForward || !condensateCorrected) {
|
|
585
|
+
if (!vapor || !condensate || !track || !vaporForward || !vaporCorrected || !condensateForward || !condensateCorrected || !shadow) {
|
|
195
586
|
return;
|
|
196
587
|
}
|
|
197
588
|
const p = { ...MEDIUM_DEFAULTS, ...params };
|
|
589
|
+
const spatial = computeMediumSpatialPeriods(gridW, gridH, p.curlFreq);
|
|
198
590
|
const clampedDt = Math.min(Math.max(dt, 0), 1 / 15);
|
|
199
591
|
phase = wrapPhase(phase + clampedDt * p.curlSpeed);
|
|
200
592
|
const back = front === 0 ? 1 : 0;
|
|
@@ -204,28 +596,30 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
204
596
|
gl.bindFramebuffer(gl.FRAMEBUFFER, vaporForward.fbo);
|
|
205
597
|
gl.useProgram(vaporForwardProgram);
|
|
206
598
|
bindTextureAt(gl, 0, vapor[front].texture, vaporForwardProgram, "u_dye");
|
|
207
|
-
bindVelocityUniforms(vaporForwardLoc, clampedDt, phase, p);
|
|
599
|
+
bindVelocityUniforms(vaporForwardLoc, clampedDt, phase, p, spatial);
|
|
208
600
|
drawFullscreenTriangle(gl);
|
|
209
601
|
gl.bindFramebuffer(gl.FRAMEBUFFER, condensateForward.fbo);
|
|
210
602
|
gl.useProgram(condensateForwardProgram);
|
|
211
603
|
bindTextureAt(gl, 0, condensate[front].texture, condensateForwardProgram, "u_dye");
|
|
212
|
-
bindVelocityUniforms(condensateForwardLoc, clampedDt, phase, p);
|
|
604
|
+
bindVelocityUniforms(condensateForwardLoc, clampedDt, phase, p, spatial);
|
|
213
605
|
setUniform(gl, condensateForwardLoc("u_settleSpeed"), p.condensateSettleSpeed);
|
|
606
|
+
setUniform(gl, condensateForwardLoc("u_settleWiggle"), p.condensateSettleWiggle);
|
|
214
607
|
drawFullscreenTriangle(gl);
|
|
215
608
|
gl.bindFramebuffer(gl.FRAMEBUFFER, vaporCorrected.fbo);
|
|
216
609
|
gl.useProgram(vaporCorrectProgram);
|
|
217
610
|
bindTextureAt(gl, 0, vapor[front].texture, vaporCorrectProgram, "u_dye");
|
|
218
611
|
bindTextureAt(gl, 1, vaporForward.texture, vaporCorrectProgram, "u_forward");
|
|
219
612
|
setUniform(gl, vaporCorrectLoc("u_forwardSize"), gridSize);
|
|
220
|
-
bindVelocityUniforms(vaporCorrectLoc, clampedDt, phase, p);
|
|
613
|
+
bindVelocityUniforms(vaporCorrectLoc, clampedDt, phase, p, spatial);
|
|
221
614
|
drawFullscreenTriangle(gl);
|
|
222
615
|
gl.bindFramebuffer(gl.FRAMEBUFFER, condensateCorrected.fbo);
|
|
223
616
|
gl.useProgram(condensateCorrectProgram);
|
|
224
617
|
bindTextureAt(gl, 0, condensate[front].texture, condensateCorrectProgram, "u_dye");
|
|
225
618
|
bindTextureAt(gl, 1, condensateForward.texture, condensateCorrectProgram, "u_forward");
|
|
226
619
|
setUniform(gl, condensateCorrectLoc("u_forwardSize"), gridSize);
|
|
227
|
-
bindVelocityUniforms(condensateCorrectLoc, clampedDt, phase, p);
|
|
620
|
+
bindVelocityUniforms(condensateCorrectLoc, clampedDt, phase, p, spatial);
|
|
228
621
|
setUniform(gl, condensateCorrectLoc("u_settleSpeed"), p.condensateSettleSpeed);
|
|
622
|
+
setUniform(gl, condensateCorrectLoc("u_settleWiggle"), p.condensateSettleWiggle);
|
|
229
623
|
drawFullscreenTriangle(gl);
|
|
230
624
|
gl.bindFramebuffer(gl.FRAMEBUFFER, vapor[back].fbo);
|
|
231
625
|
gl.useProgram(vaporReactProgram);
|
|
@@ -259,9 +653,23 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
259
653
|
gl.bindFramebuffer(gl.FRAMEBUFFER, track[back].fbo);
|
|
260
654
|
gl.useProgram(trackAdvectProgram);
|
|
261
655
|
bindTextureAt(gl, 0, track[front].texture, trackAdvectProgram, "u_dye");
|
|
262
|
-
bindVelocityUniforms(trackAdvectLoc, clampedDt, phase, p);
|
|
656
|
+
bindVelocityUniforms(trackAdvectLoc, clampedDt, phase, p, spatial);
|
|
263
657
|
setUniform(gl, trackAdvectLoc("u_decay"), p.decay);
|
|
264
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);
|
|
265
673
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
266
674
|
front = back;
|
|
267
675
|
framesSinceRenorm += 1;
|
|
@@ -303,14 +711,16 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
303
711
|
setUniform(gl, emitLoc("u_tailDim"), e.tailDim);
|
|
304
712
|
setUniform(gl, emitLoc("u_intensity"), e.intensity);
|
|
305
713
|
setUniform(gl, emitLoc("u_seed"), e.seed);
|
|
306
|
-
setUniform(gl, emitLoc("u_channelMask"), e.channel);
|
|
307
714
|
drawFullscreenTriangle(gl);
|
|
308
715
|
}
|
|
309
716
|
gl.disable(gl.BLEND);
|
|
310
717
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
311
718
|
}
|
|
719
|
+
function stepTrackLayer(dt, emissions) {
|
|
720
|
+
liveTracks = trackLayer.step(dt, emissions);
|
|
721
|
+
}
|
|
312
722
|
function composite(contentWidth, contentHeight, params = {}) {
|
|
313
|
-
if (!vapor || !condensate || !track) return;
|
|
723
|
+
if (!vapor || !condensate || !track || !shadow) return;
|
|
314
724
|
const p = { ...MEDIUM_DEFAULTS, ...params };
|
|
315
725
|
gl.viewport(0, 0, contentWidth, contentHeight);
|
|
316
726
|
gl.disable(gl.BLEND);
|
|
@@ -318,9 +728,11 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
318
728
|
bindTextureAt(gl, 0, vapor[front].texture, compositeProgram, "u_vapor");
|
|
319
729
|
bindTextureAt(gl, 1, condensate[front].texture, compositeProgram, "u_condensate");
|
|
320
730
|
bindTextureAt(gl, 2, track[front].texture, compositeProgram, "u_track");
|
|
731
|
+
bindTextureAt(gl, 3, shadow.texture, compositeProgram, "u_shadow");
|
|
321
732
|
setUniform(gl, compositeLoc("u_vaporSize"), [gridW, gridH]);
|
|
322
733
|
setUniform(gl, compositeLoc("u_condensateSize"), [gridW, gridH]);
|
|
323
734
|
setUniform(gl, compositeLoc("u_trackSize"), [gridW, gridH]);
|
|
735
|
+
setUniform(gl, compositeLoc("u_shadowSize"), [gridW, gridH]);
|
|
324
736
|
setUniform(gl, compositeLoc("u_resolution"), [contentWidth, contentHeight]);
|
|
325
737
|
setUniform(gl, compositeLoc("u_dyeScale"), [gridW / contentWidth, gridH / contentHeight]);
|
|
326
738
|
setUniform(gl, compositeLoc("u_lab0"), srgbToOklab(p.channelColors[0]));
|
|
@@ -337,7 +749,12 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
337
749
|
setUniform(gl, compositeLoc("u_farWeight"), p.depthFarWeight);
|
|
338
750
|
setUniform(gl, compositeLoc("u_gravityBoost"), p.gravityBottomBoost);
|
|
339
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);
|
|
340
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);
|
|
341
758
|
}
|
|
342
759
|
function readTarget(target) {
|
|
343
760
|
gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
|
|
@@ -404,6 +821,23 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
404
821
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
405
822
|
return { cols: gridW, rows: gridH, data };
|
|
406
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
|
+
}
|
|
407
841
|
function destroy() {
|
|
408
842
|
destroyTargets();
|
|
409
843
|
gl.deleteProgram(vaporForwardProgram);
|
|
@@ -416,9 +850,22 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
416
850
|
gl.deleteProgram(compositeProgram);
|
|
417
851
|
gl.deleteProgram(seedProgram);
|
|
418
852
|
gl.deleteProgram(emitProgram);
|
|
853
|
+
gl.deleteProgram(shadowProgram);
|
|
419
854
|
gl.deleteProgram(resampleProgram);
|
|
855
|
+
trackLayerProgram.destroy();
|
|
420
856
|
}
|
|
421
|
-
return {
|
|
857
|
+
return {
|
|
858
|
+
ensureGrid,
|
|
859
|
+
step,
|
|
860
|
+
emit,
|
|
861
|
+
stepTrackLayer,
|
|
862
|
+
composite,
|
|
863
|
+
readTotals,
|
|
864
|
+
readVaporGrid,
|
|
865
|
+
readCondensateGrid,
|
|
866
|
+
readTrackGrid,
|
|
867
|
+
destroy
|
|
868
|
+
};
|
|
422
869
|
}
|
|
423
870
|
function createMediumBackdrop() {
|
|
424
871
|
let runtime = null;
|
|
@@ -431,12 +878,14 @@ function createMediumBackdrop() {
|
|
|
431
878
|
runtime.ensureGrid(frame.gridWidth, frame.gridHeight);
|
|
432
879
|
runtime.step(frame.dt, frame.params);
|
|
433
880
|
runtime.emit(frame.emissions ?? []);
|
|
881
|
+
runtime.stepTrackLayer(frame.dt, frame.emissions ?? []);
|
|
434
882
|
gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);
|
|
435
883
|
runtime.composite(target.width, target.height, frame.params);
|
|
436
884
|
},
|
|
437
885
|
readTotals: () => runtime?.readTotals() ?? null,
|
|
438
886
|
readVaporGrid: () => runtime?.readVaporGrid() ?? null,
|
|
439
887
|
readCondensateGrid: () => runtime?.readCondensateGrid() ?? null,
|
|
888
|
+
readTrackGrid: () => runtime?.readTrackGrid() ?? null,
|
|
440
889
|
destroy: () => {
|
|
441
890
|
runtime?.destroy();
|
|
442
891
|
runtime = null;
|
|
@@ -445,6 +894,6 @@ function createMediumBackdrop() {
|
|
|
445
894
|
};
|
|
446
895
|
}
|
|
447
896
|
|
|
448
|
-
export { createMediumBackdrop, createMediumRuntime };
|
|
897
|
+
export { buildTrackLayerInstances, createMediumBackdrop, createMediumRuntime, createTrackLayer, createTrackLayerProgram, trackLayerFade, trackLayerSigmaMul };
|
|
449
898
|
//# sourceMappingURL=index.js.map
|
|
450
899
|
//# sourceMappingURL=index.js.map
|