vireuikit 0.1.0 → 0.3.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 -0
- package/dist/{chunk-ZFX2MLKK.js → chunk-2AFVQ65S.js} +240 -18
- package/dist/chunk-2AFVQ65S.js.map +1 -0
- package/dist/dynamics-7Bq3LLds.d.cts +271 -0
- package/dist/dynamics-7Bq3LLds.d.ts +271 -0
- package/dist/index.cjs +224 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/web/index.cjs +340 -126
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +1 -1
- package/dist/web/index.d.ts +1 -1
- package/dist/web/index.js +21 -7
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZFX2MLKK.js.map +0 -1
- package/dist/dynamics-BqBoj03x.d.cts +0 -160
- package/dist/dynamics-BqBoj03x.d.ts +0 -160
package/dist/web/index.cjs
CHANGED
|
@@ -3,115 +3,6 @@
|
|
|
3
3
|
var vireglass = require('vireglass');
|
|
4
4
|
var web = require('vireglass/web');
|
|
5
5
|
|
|
6
|
-
// src/medium/oklab.ts
|
|
7
|
-
var LINEAR_TO_LMS = [
|
|
8
|
-
[0.4122214708, 0.5363325363, 0.0514459929],
|
|
9
|
-
[0.2119034982, 0.6806995451, 0.1073969566],
|
|
10
|
-
[0.0883024619, 0.2817188376, 0.6299787005]
|
|
11
|
-
];
|
|
12
|
-
var LMS_TO_OKLAB = [
|
|
13
|
-
[0.2104542553, 0.793617785, -0.0040720468],
|
|
14
|
-
[1.9779984951, -2.428592205, 0.4505937099],
|
|
15
|
-
[0.0259040371, 0.7827717662, -0.808675766]
|
|
16
|
-
];
|
|
17
|
-
var OKLAB_TO_LMS = [
|
|
18
|
-
[1, 0.3963377774, 0.2158037573],
|
|
19
|
-
[1, -0.1055613458, -0.0638541728],
|
|
20
|
-
[1, -0.0894841775, -1.291485548]
|
|
21
|
-
];
|
|
22
|
-
var LMS_TO_LINEAR = [
|
|
23
|
-
[4.0767416621, -3.3077115913, 0.2309699292],
|
|
24
|
-
[-1.2684380046, 2.6097574011, -0.3413193965],
|
|
25
|
-
[-0.0041960863, -0.7034186147, 1.707614701]
|
|
26
|
-
];
|
|
27
|
-
var glsl = (v) => Number.isInteger(v) ? v.toFixed(1) : String(v);
|
|
28
|
-
var row = (m, i, vars) => m[i].map((k, j) => `${glsl(k)} * ${vars[j]}`).join(" + ");
|
|
29
|
-
var VG_OKLAB_TO_SRGB = `
|
|
30
|
-
float3 vgLinearToSrgb(float3 c) {
|
|
31
|
-
float3 lo = c * 12.92;
|
|
32
|
-
float3 hi = 1.055 * pow(max(c, float3(0.0)), float3(1.0 / 2.4)) - 0.055;
|
|
33
|
-
return mix(lo, hi, step(float3(0.0031308), c));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
float3 vgOklabToLinear(float3 lab) {
|
|
37
|
-
float l_ = ${row(OKLAB_TO_LMS, 0, ["lab.x", "lab.y", "lab.z"])};
|
|
38
|
-
float m_ = ${row(OKLAB_TO_LMS, 1, ["lab.x", "lab.y", "lab.z"])};
|
|
39
|
-
float s_ = ${row(OKLAB_TO_LMS, 2, ["lab.x", "lab.y", "lab.z"])};
|
|
40
|
-
float l = l_ * l_ * l_;
|
|
41
|
-
float m = m_ * m_ * m_;
|
|
42
|
-
float s = s_ * s_ * s_;
|
|
43
|
-
return float3(
|
|
44
|
-
${row(LMS_TO_LINEAR, 0, ["l", "m", "s"])},
|
|
45
|
-
${row(LMS_TO_LINEAR, 1, ["l", "m", "s"])},
|
|
46
|
-
${row(LMS_TO_LINEAR, 2, ["l", "m", "s"])}
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
`;
|
|
50
|
-
var apply = (m, v) => [
|
|
51
|
-
m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
|
|
52
|
-
m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
|
|
53
|
-
m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2]
|
|
54
|
-
];
|
|
55
|
-
var srgbChannelToLinear = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
56
|
-
var linearChannelToSrgb = (c) => c <= 31308e-7 ? c * 12.92 : 1.055 * Math.pow(Math.max(c, 0), 1 / 2.4) - 0.055;
|
|
57
|
-
function srgbToOklab(rgb) {
|
|
58
|
-
const linear = rgb.map(srgbChannelToLinear);
|
|
59
|
-
const lms = apply(LINEAR_TO_LMS, linear);
|
|
60
|
-
return apply(LMS_TO_OKLAB, lms.map((v) => Math.cbrt(Math.max(v, 0))));
|
|
61
|
-
}
|
|
62
|
-
function oklabToSrgb(lab) {
|
|
63
|
-
const lms = apply(OKLAB_TO_LMS, lab).map((v) => v * v * v);
|
|
64
|
-
return apply(LMS_TO_LINEAR, lms).map(linearChannelToSrgb);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// src/medium/species.ts
|
|
68
|
-
var MEDIUM_SPECIES_LIGHTNESS = 0.44;
|
|
69
|
-
var MEDIUM_SPECIES_HUE_SPREAD = 32;
|
|
70
|
-
var clamp01 = (v) => Math.min(1, Math.max(0, v));
|
|
71
|
-
function characterOf(color) {
|
|
72
|
-
const [lightness, a, b] = srgbToOklab(color);
|
|
73
|
-
return {
|
|
74
|
-
hue: Math.atan2(b, a) * 180 / Math.PI,
|
|
75
|
-
chroma: Math.hypot(a, b),
|
|
76
|
-
lightness
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
function speciesFamily(character, spreadDeg = MEDIUM_SPECIES_HUE_SPREAD) {
|
|
80
|
-
const { hue, chroma, lightness } = character;
|
|
81
|
-
const tone = (deg) => {
|
|
82
|
-
const rad = deg * Math.PI / 180;
|
|
83
|
-
return oklabToSrgb([lightness, chroma * Math.cos(rad), chroma * Math.sin(rad)]).map(
|
|
84
|
-
clamp01
|
|
85
|
-
);
|
|
86
|
-
};
|
|
87
|
-
return [tone(hue - spreadDeg), tone(hue), tone(hue + spreadDeg)];
|
|
88
|
-
}
|
|
89
|
-
function neutralSpecies(baseColor) {
|
|
90
|
-
const { hue, chroma } = characterOf(baseColor);
|
|
91
|
-
return speciesFamily({ hue, chroma, lightness: MEDIUM_SPECIES_LIGHTNESS });
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// src/medium/params.ts
|
|
95
|
-
var BASE_COLOR = [0.07, 0.08, 0.1];
|
|
96
|
-
var MEDIUM_DEFAULTS = {
|
|
97
|
-
decay: 0.06,
|
|
98
|
-
curlFreq: 0.045,
|
|
99
|
-
curlSpeed: 0.12,
|
|
100
|
-
advectSpeed: 26,
|
|
101
|
-
turbulence: 1,
|
|
102
|
-
condensationRate: 0.18,
|
|
103
|
-
condensationFloor: 0.12,
|
|
104
|
-
evaporationRate: 0.2,
|
|
105
|
-
condensateSettleSpeed: 8,
|
|
106
|
-
condensateSpreadRate: 0.08,
|
|
107
|
-
condensateTint: 0.2,
|
|
108
|
-
condensateGain: 5,
|
|
109
|
-
condensateColor: [0.94, 0.95, 0.97],
|
|
110
|
-
channelColors: neutralSpecies(BASE_COLOR),
|
|
111
|
-
baseColor: BASE_COLOR,
|
|
112
|
-
baseWeight: 0.9
|
|
113
|
-
};
|
|
114
|
-
|
|
115
6
|
// src/medium/noise.ts
|
|
116
7
|
var VG_VALUE_NOISE = `
|
|
117
8
|
float vgHash21(float2 p) {
|
|
@@ -242,7 +133,210 @@ float2 vgCurlVelocity(float2 p, float phase) {
|
|
|
242
133
|
float py0 = vgPotential(p - float2(0.0, e), phase);
|
|
243
134
|
return float2(py1 - py0, -(px1 - px0)) / (2.0 * e);
|
|
244
135
|
}
|
|
136
|
+
|
|
137
|
+
// vgPotential above is periodic in TIME only: the simulation domain is a torus (gl.REPEAT), but
|
|
138
|
+
// the lattice itself never wraps in x/y, so velocity jumps at the domain seam and dye on either
|
|
139
|
+
// side of it is transported differently \u2014 a straight seam, made worse wherever something re-reads
|
|
140
|
+
// the field at a different scale (the far depth plane's tile boundary). vgValueNoise3Seamless
|
|
141
|
+
// wraps ALL THREE lattice axes: xyPeriod/zPeriod fold BOTH neighbors of a cell into their own
|
|
142
|
+
// period before hashing, so the field is EXACTLY the same one period over, in space exactly as it
|
|
143
|
+
// already was in time.
|
|
144
|
+
//
|
|
145
|
+
// The period has to be an integer number of lattice cells, or the wrap would jump instead of
|
|
146
|
+
// closing seamlessly (the same requirement MEDIUM_TIME_OCTAVES/mediumOctavePeriod enforces for
|
|
147
|
+
// time) \u2014 but unlike time, the spatial period depends on the GRID SIZE, which this shader has no
|
|
148
|
+
// notion of. So xyPeriod travels together with a per-octave spatial frequency the CPU computes to
|
|
149
|
+
// make gridSize * frequency land on that exact integer (see computeMediumSpatialPeriods below);
|
|
150
|
+
// both arrive together as one uniform per octave (u_spatialN.xy = frequency, .zw = period),
|
|
151
|
+
// recomputed by web/medium.ts whenever the grid resizes.
|
|
152
|
+
float vgValueNoise3Seamless(float3 p, float2 xyPeriod, float zPeriod) {
|
|
153
|
+
float3 i = floor(p);
|
|
154
|
+
float3 f = fract(p);
|
|
155
|
+
float3 u = f * f * (3.0 - 2.0 * f);
|
|
156
|
+
float x0 = mod(i.x, xyPeriod.x);
|
|
157
|
+
float x1 = mod(i.x + 1.0, xyPeriod.x);
|
|
158
|
+
float y0 = mod(i.y, xyPeriod.y);
|
|
159
|
+
float y1 = mod(i.y + 1.0, xyPeriod.y);
|
|
160
|
+
float z0 = mod(i.z, zPeriod);
|
|
161
|
+
float z1 = mod(i.z + 1.0, zPeriod);
|
|
162
|
+
float n000 = vgHash31(float3(x0, y0, z0));
|
|
163
|
+
float n100 = vgHash31(float3(x1, y0, z0));
|
|
164
|
+
float n010 = vgHash31(float3(x0, y1, z0));
|
|
165
|
+
float n110 = vgHash31(float3(x1, y1, z0));
|
|
166
|
+
float n001 = vgHash31(float3(x0, y0, z1));
|
|
167
|
+
float n101 = vgHash31(float3(x1, y0, z1));
|
|
168
|
+
float n011 = vgHash31(float3(x0, y1, z1));
|
|
169
|
+
float n111 = vgHash31(float3(x1, y1, z1));
|
|
170
|
+
float nx00 = mix(n000, n100, u.x);
|
|
171
|
+
float nx10 = mix(n010, n110, u.x);
|
|
172
|
+
float nx01 = mix(n001, n101, u.x);
|
|
173
|
+
float nx11 = mix(n011, n111, u.x);
|
|
174
|
+
float nxy0 = mix(nx00, nx10, u.y);
|
|
175
|
+
float nxy1 = mix(nx01, nx11, u.y);
|
|
176
|
+
return mix(nxy0, nxy1, u.z) * 2.0 - 1.0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Same four-octave fbm as vgPotential, but every octave reads the seamless lattice above at its
|
|
180
|
+
// OWN (frequency, period) pair \u2014 sN.xy replaces that octave's compile-time \`scale\` constant (it
|
|
181
|
+
// has to be a uniform now: the adjustment depends on the live grid size), sN.zw is the matching
|
|
182
|
+
// integer period. \`p\` is still curlFreq-scaled exactly as vgPotential's caller scales it \u2014 only
|
|
183
|
+
// the per-octave multiplier changed from a constant to a grid-dependent uniform, so the finite
|
|
184
|
+
// difference in vgCurlVelocitySeamless below needs no epsilon change of its own.
|
|
185
|
+
float vgPotentialSeamless(float2 p, float phase, float4 s0, float4 s1, float4 s2, float4 s3) {
|
|
186
|
+
float v = vgValueNoise3Seamless(float3(p * s0.xy, phase * ${VG_OCT0.rate} + ${VG_OCT0.offset}), s0.zw, ${VG_OCT0.period}) * 0.5;
|
|
187
|
+
v += vgValueNoise3Seamless(float3(p * s1.xy, phase * ${VG_OCT1.rate} + ${VG_OCT1.offset}), s1.zw, ${VG_OCT1.period}) * 0.25;
|
|
188
|
+
v += vgValueNoise3Seamless(float3(p * s2.xy, phase * ${VG_OCT2.rate} + ${VG_OCT2.offset}), s2.zw, ${VG_OCT2.period}) * 0.125;
|
|
189
|
+
float macro = vgValueNoise3Seamless(float3(p * s3.xy, phase * ${VG_OCT3.rate} + ${VG_OCT3.offset}), s3.zw, ${VG_OCT3.period});
|
|
190
|
+
return v * (0.7 + 0.3 * macro);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
float2 vgCurlVelocitySeamless(float2 p, float phase, float4 s0, float4 s1, float4 s2, float4 s3) {
|
|
194
|
+
float e = 0.6;
|
|
195
|
+
float px1 = vgPotentialSeamless(p + float2(e, 0.0), phase, s0, s1, s2, s3);
|
|
196
|
+
float px0 = vgPotentialSeamless(p - float2(e, 0.0), phase, s0, s1, s2, s3);
|
|
197
|
+
float py1 = vgPotentialSeamless(p + float2(0.0, e), phase, s0, s1, s2, s3);
|
|
198
|
+
float py0 = vgPotentialSeamless(p - float2(0.0, e), phase, s0, s1, s2, s3);
|
|
199
|
+
return float2(py1 - py0, -(px1 - px0)) / (2.0 * e);
|
|
200
|
+
}
|
|
245
201
|
`;
|
|
202
|
+
var MIN_SPATIAL_PERIOD = 2;
|
|
203
|
+
function computeMediumSpatialPeriods(gridWidth, gridHeight, curlFreq) {
|
|
204
|
+
const freq = Math.max(curlFreq, 1e-6);
|
|
205
|
+
return MEDIUM_TIME_OCTAVES.map((o) => {
|
|
206
|
+
const rawX = gridWidth * freq * o.scale;
|
|
207
|
+
const rawY = gridHeight * freq * o.scale;
|
|
208
|
+
const minRaw = Math.min(rawX, rawY);
|
|
209
|
+
const scaleUp = minRaw > 0 && minRaw < MIN_SPATIAL_PERIOD ? MIN_SPATIAL_PERIOD / minRaw : 1;
|
|
210
|
+
const periodX = Math.max(MIN_SPATIAL_PERIOD, Math.round(rawX * scaleUp));
|
|
211
|
+
const periodY = Math.max(MIN_SPATIAL_PERIOD, Math.round(rawY * scaleUp));
|
|
212
|
+
return {
|
|
213
|
+
freqX: periodX / (gridWidth * freq),
|
|
214
|
+
freqY: periodY / (gridHeight * freq),
|
|
215
|
+
periodX,
|
|
216
|
+
periodY
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/medium/oklab.ts
|
|
222
|
+
var LINEAR_TO_LMS = [
|
|
223
|
+
[0.4122214708, 0.5363325363, 0.0514459929],
|
|
224
|
+
[0.2119034982, 0.6806995451, 0.1073969566],
|
|
225
|
+
[0.0883024619, 0.2817188376, 0.6299787005]
|
|
226
|
+
];
|
|
227
|
+
var LMS_TO_OKLAB = [
|
|
228
|
+
[0.2104542553, 0.793617785, -0.0040720468],
|
|
229
|
+
[1.9779984951, -2.428592205, 0.4505937099],
|
|
230
|
+
[0.0259040371, 0.7827717662, -0.808675766]
|
|
231
|
+
];
|
|
232
|
+
var OKLAB_TO_LMS = [
|
|
233
|
+
[1, 0.3963377774, 0.2158037573],
|
|
234
|
+
[1, -0.1055613458, -0.0638541728],
|
|
235
|
+
[1, -0.0894841775, -1.291485548]
|
|
236
|
+
];
|
|
237
|
+
var LMS_TO_LINEAR = [
|
|
238
|
+
[4.0767416621, -3.3077115913, 0.2309699292],
|
|
239
|
+
[-1.2684380046, 2.6097574011, -0.3413193965],
|
|
240
|
+
[-0.0041960863, -0.7034186147, 1.707614701]
|
|
241
|
+
];
|
|
242
|
+
var glsl = (v) => Number.isInteger(v) ? v.toFixed(1) : String(v);
|
|
243
|
+
var row = (m, i, vars) => m[i].map((k, j) => `${glsl(k)} * ${vars[j]}`).join(" + ");
|
|
244
|
+
var VG_OKLAB_TO_SRGB = `
|
|
245
|
+
float3 vgLinearToSrgb(float3 c) {
|
|
246
|
+
float3 lo = c * 12.92;
|
|
247
|
+
float3 hi = 1.055 * pow(max(c, float3(0.0)), float3(1.0 / 2.4)) - 0.055;
|
|
248
|
+
return mix(lo, hi, step(float3(0.0031308), c));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
float3 vgOklabToLinear(float3 lab) {
|
|
252
|
+
float l_ = ${row(OKLAB_TO_LMS, 0, ["lab.x", "lab.y", "lab.z"])};
|
|
253
|
+
float m_ = ${row(OKLAB_TO_LMS, 1, ["lab.x", "lab.y", "lab.z"])};
|
|
254
|
+
float s_ = ${row(OKLAB_TO_LMS, 2, ["lab.x", "lab.y", "lab.z"])};
|
|
255
|
+
float l = l_ * l_ * l_;
|
|
256
|
+
float m = m_ * m_ * m_;
|
|
257
|
+
float s = s_ * s_ * s_;
|
|
258
|
+
return float3(
|
|
259
|
+
${row(LMS_TO_LINEAR, 0, ["l", "m", "s"])},
|
|
260
|
+
${row(LMS_TO_LINEAR, 1, ["l", "m", "s"])},
|
|
261
|
+
${row(LMS_TO_LINEAR, 2, ["l", "m", "s"])}
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
var apply = (m, v) => [
|
|
266
|
+
m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
|
|
267
|
+
m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
|
|
268
|
+
m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2]
|
|
269
|
+
];
|
|
270
|
+
var srgbChannelToLinear = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
271
|
+
var linearChannelToSrgb = (c) => c <= 31308e-7 ? c * 12.92 : 1.055 * Math.pow(Math.max(c, 0), 1 / 2.4) - 0.055;
|
|
272
|
+
function srgbToOklab(rgb) {
|
|
273
|
+
const linear = rgb.map(srgbChannelToLinear);
|
|
274
|
+
const lms = apply(LINEAR_TO_LMS, linear);
|
|
275
|
+
return apply(LMS_TO_OKLAB, lms.map((v) => Math.cbrt(Math.max(v, 0))));
|
|
276
|
+
}
|
|
277
|
+
function oklabToSrgb(lab) {
|
|
278
|
+
const lms = apply(OKLAB_TO_LMS, lab).map((v) => v * v * v);
|
|
279
|
+
return apply(LMS_TO_LINEAR, lms).map(linearChannelToSrgb);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/medium/species.ts
|
|
283
|
+
var MEDIUM_SPECIES_LIGHTNESS = 0.44;
|
|
284
|
+
var MEDIUM_SPECIES_HUE_SPREAD = 32;
|
|
285
|
+
var clamp01 = (v) => Math.min(1, Math.max(0, v));
|
|
286
|
+
function characterOf(color) {
|
|
287
|
+
const [lightness, a, b] = srgbToOklab(color);
|
|
288
|
+
return {
|
|
289
|
+
hue: Math.atan2(b, a) * 180 / Math.PI,
|
|
290
|
+
chroma: Math.hypot(a, b),
|
|
291
|
+
lightness
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function speciesFamily(character, spreadDeg = MEDIUM_SPECIES_HUE_SPREAD) {
|
|
295
|
+
const { hue, chroma, lightness } = character;
|
|
296
|
+
const tone = (deg) => {
|
|
297
|
+
const rad = deg * Math.PI / 180;
|
|
298
|
+
return oklabToSrgb([lightness, chroma * Math.cos(rad), chroma * Math.sin(rad)]).map(
|
|
299
|
+
clamp01
|
|
300
|
+
);
|
|
301
|
+
};
|
|
302
|
+
return [tone(hue - spreadDeg), tone(hue), tone(hue + spreadDeg)];
|
|
303
|
+
}
|
|
304
|
+
function neutralSpecies(baseColor) {
|
|
305
|
+
const { hue, chroma } = characterOf(baseColor);
|
|
306
|
+
return speciesFamily({ hue, chroma, lightness: MEDIUM_SPECIES_LIGHTNESS });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/medium/params.ts
|
|
310
|
+
var BASE_COLOR = [0.07, 0.08, 0.1];
|
|
311
|
+
var MEDIUM_DEFAULTS = {
|
|
312
|
+
decay: 0.06,
|
|
313
|
+
curlFreq: 0.045,
|
|
314
|
+
curlSpeed: 0.12,
|
|
315
|
+
advectSpeed: 26,
|
|
316
|
+
turbulence: 1,
|
|
317
|
+
condensationRate: 0.18,
|
|
318
|
+
condensationFloor: 0.12,
|
|
319
|
+
evaporationRate: 0.2,
|
|
320
|
+
condensateSettleSpeed: 8,
|
|
321
|
+
condensateSettleWiggle: 0.4,
|
|
322
|
+
condensateSpreadRate: 0.08,
|
|
323
|
+
condensateTint: 0.2,
|
|
324
|
+
condensateGain: 5,
|
|
325
|
+
condensateColor: [0.94, 0.95, 0.97],
|
|
326
|
+
channelColors: neutralSpecies(BASE_COLOR),
|
|
327
|
+
baseColor: BASE_COLOR,
|
|
328
|
+
baseWeight: 0.9,
|
|
329
|
+
depthFarScale: 1.6,
|
|
330
|
+
depthFarOffsetFrac: [0.47, 0.53],
|
|
331
|
+
depthFarBlurRadius: 5,
|
|
332
|
+
depthFarWeight: 0.5,
|
|
333
|
+
gravityVaporDrift: 0.6,
|
|
334
|
+
gravityBottomBoost: 2,
|
|
335
|
+
gravityBottomBoostStart: 0.75
|
|
336
|
+
};
|
|
337
|
+
function depthFarOffsetPx(offsetFrac, gridWidth, gridHeight) {
|
|
338
|
+
return [offsetFrac[0] * gridWidth, offsetFrac[1] * gridHeight];
|
|
339
|
+
}
|
|
246
340
|
|
|
247
341
|
// src/medium/seed-shader.ts
|
|
248
342
|
var MEDIUM_SEED_SHADER = `
|
|
@@ -285,21 +379,36 @@ uniform float u_phase;
|
|
|
285
379
|
uniform float u_curlFreq;
|
|
286
380
|
uniform float u_advectSpeed;
|
|
287
381
|
uniform float u_turbulence;
|
|
382
|
+
// One float4 per MEDIUM_TIME_OCTAVES entry: xy is that octave's seamless spatial frequency, zw its
|
|
383
|
+
// matching integer lattice period \u2014 both grid-size-dependent, so they can't be compile-time
|
|
384
|
+
// constants (see vgPotentialSeamless/computeMediumSpatialPeriods in noise.ts). Recomputed by
|
|
385
|
+
// web/medium.ts's bindVelocityUniforms every frame from the live grid dimensions.
|
|
386
|
+
uniform float4 u_spatial0;
|
|
387
|
+
uniform float4 u_spatial1;
|
|
388
|
+
uniform float4 u_spatial2;
|
|
389
|
+
uniform float4 u_spatial3;
|
|
288
390
|
|
|
289
391
|
${VG_CURL_NOISE}
|
|
290
392
|
`;
|
|
291
393
|
var MEDIUM_ADVECT_VELOCITY = `
|
|
292
|
-
float2
|
|
394
|
+
float2 curl = vgCurlVelocitySeamless(xy * u_curlFreq, u_phase, u_spatial0, u_spatial1, u_spatial2, u_spatial3);
|
|
395
|
+
float2 vel = curl * u_advectSpeed * u_turbulence;
|
|
293
396
|
`;
|
|
294
397
|
var MEDIUM_ADVECT_VELOCITY_SETTLE = `
|
|
295
398
|
${MEDIUM_ADVECT_VELOCITY}
|
|
296
399
|
vel.y += u_settleSpeed;
|
|
400
|
+
vel.x += curl.x * u_advectSpeed * u_settleWiggle;
|
|
401
|
+
`;
|
|
402
|
+
var MEDIUM_ADVECT_VELOCITY_VAPOR = `
|
|
403
|
+
${MEDIUM_ADVECT_VELOCITY}
|
|
404
|
+
vel.y += u_vaporDrift;
|
|
297
405
|
`;
|
|
298
406
|
var MEDIUM_VAPOR_FORWARD_SHADER = `
|
|
299
407
|
${MEDIUM_ADVECT_UNIFORMS}
|
|
408
|
+
uniform float u_vaporDrift;
|
|
300
409
|
|
|
301
410
|
half4 main(float2 xy) {
|
|
302
|
-
${
|
|
411
|
+
${MEDIUM_ADVECT_VELOCITY_VAPOR}
|
|
303
412
|
float2 src = xy - vel * u_dt;
|
|
304
413
|
return u_dye.eval(float2(src.x, u_dyeSize.y - src.y));
|
|
305
414
|
}
|
|
@@ -307,6 +416,7 @@ ${MEDIUM_ADVECT_VELOCITY}
|
|
|
307
416
|
var MEDIUM_CONDENSATE_FORWARD_SHADER = `
|
|
308
417
|
${MEDIUM_ADVECT_UNIFORMS}
|
|
309
418
|
uniform float u_settleSpeed;
|
|
419
|
+
uniform float u_settleWiggle;
|
|
310
420
|
|
|
311
421
|
half4 main(float2 xy) {
|
|
312
422
|
${MEDIUM_ADVECT_VELOCITY_SETTLE}
|
|
@@ -335,9 +445,10 @@ var MEDIUM_MACCORMACK_CORRECT = `
|
|
|
335
445
|
var MEDIUM_VAPOR_CORRECT_SHADER = `
|
|
336
446
|
${MEDIUM_ADVECT_UNIFORMS}
|
|
337
447
|
uniform shader u_forward;
|
|
448
|
+
uniform float u_vaporDrift;
|
|
338
449
|
|
|
339
450
|
half4 main(float2 xy) {
|
|
340
|
-
${
|
|
451
|
+
${MEDIUM_ADVECT_VELOCITY_VAPOR}
|
|
341
452
|
${MEDIUM_MACCORMACK_CORRECT}
|
|
342
453
|
return limited;
|
|
343
454
|
}
|
|
@@ -345,6 +456,7 @@ ${MEDIUM_MACCORMACK_CORRECT}
|
|
|
345
456
|
var MEDIUM_CONDENSATE_CORRECT_SHADER = `
|
|
346
457
|
${MEDIUM_ADVECT_UNIFORMS}
|
|
347
458
|
uniform float u_settleSpeed;
|
|
459
|
+
uniform float u_settleWiggle;
|
|
348
460
|
uniform shader u_forward;
|
|
349
461
|
|
|
350
462
|
half4 main(float2 xy) {
|
|
@@ -444,6 +556,39 @@ ${MEDIUM_ADVECT_VELOCITY}
|
|
|
444
556
|
`;
|
|
445
557
|
|
|
446
558
|
// src/medium/composite-shader.ts
|
|
559
|
+
var VG_CUBIC_WEIGHTS = `
|
|
560
|
+
float4 vgCubicWeights(float v) {
|
|
561
|
+
float4 n = float4(1.0, 2.0, 3.0, 4.0) - v;
|
|
562
|
+
float4 s = n * n * n;
|
|
563
|
+
float x = s.x;
|
|
564
|
+
float y = s.y - 4.0 * s.x;
|
|
565
|
+
float z = s.z - 4.0 * s.y + 6.0 * s.x;
|
|
566
|
+
float w = 6.0 - x - y - z;
|
|
567
|
+
return float4(x, y, z, w) * (1.0 / 6.0);
|
|
568
|
+
}
|
|
569
|
+
`;
|
|
570
|
+
function vgBicubicSampler(fnName, textureName) {
|
|
571
|
+
return `
|
|
572
|
+
half4 ${fnName}(float2 px) {
|
|
573
|
+
float2 shifted = px - 0.5;
|
|
574
|
+
float2 fxy = fract(shifted);
|
|
575
|
+
float2 base = shifted - fxy;
|
|
576
|
+
float4 xw = vgCubicWeights(fxy.x);
|
|
577
|
+
float4 yw = vgCubicWeights(fxy.y);
|
|
578
|
+
float2 sx = float2(xw.x + xw.y, xw.z + xw.w);
|
|
579
|
+
float2 sy = float2(yw.x + yw.y, yw.z + yw.w);
|
|
580
|
+
float2 offsetX = float2(base.x - 0.5 + xw.y / sx.x, base.x + 1.5 + xw.w / sx.y);
|
|
581
|
+
float2 offsetY = float2(base.y - 0.5 + yw.y / sy.x, base.y + 1.5 + yw.w / sy.y);
|
|
582
|
+
half4 s00 = ${textureName}.eval(float2(offsetX.x, offsetY.x));
|
|
583
|
+
half4 s10 = ${textureName}.eval(float2(offsetX.y, offsetY.x));
|
|
584
|
+
half4 s01 = ${textureName}.eval(float2(offsetX.x, offsetY.y));
|
|
585
|
+
half4 s11 = ${textureName}.eval(float2(offsetX.y, offsetY.y));
|
|
586
|
+
float wx = sx.x / (sx.x + sx.y);
|
|
587
|
+
float wy = sy.x / (sy.x + sy.y);
|
|
588
|
+
return mix(mix(s11, s01, wx), mix(s10, s00, wx), wy);
|
|
589
|
+
}
|
|
590
|
+
`;
|
|
591
|
+
}
|
|
447
592
|
var MEDIUM_COMPOSITE_SHADER = `
|
|
448
593
|
uniform shader u_vapor;
|
|
449
594
|
uniform shader u_condensate;
|
|
@@ -457,17 +602,71 @@ uniform float3 u_labCondensate;
|
|
|
457
602
|
uniform float u_baseWeight;
|
|
458
603
|
uniform float u_condensateTint;
|
|
459
604
|
uniform float u_condensateGain;
|
|
605
|
+
uniform float u_farScale;
|
|
606
|
+
uniform float2 u_farOffset;
|
|
607
|
+
uniform float u_farBlurRadius;
|
|
608
|
+
uniform float u_farWeight;
|
|
609
|
+
uniform float u_gravityBoost;
|
|
610
|
+
uniform float u_gravityBoostStart;
|
|
460
611
|
|
|
461
612
|
${VG_OKLAB_TO_SRGB}
|
|
613
|
+
${VG_CUBIC_WEIGHTS}
|
|
614
|
+
${vgBicubicSampler("vgBicubicVapor", "u_vapor")}
|
|
615
|
+
${vgBicubicSampler("vgBicubicCondensate", "u_condensate")}
|
|
462
616
|
|
|
463
617
|
half4 main(float2 xy) {
|
|
464
|
-
float2
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
618
|
+
float2 baseSrc = xy * u_dyeScale;
|
|
619
|
+
|
|
620
|
+
// Near plane \u2014 the calibrated look lives here. Bicubic (see the file header) on vapor and
|
|
621
|
+
// condensate; track stays a plain bilinear read.
|
|
622
|
+
half4 vapor = vgBicubicVapor(baseSrc);
|
|
623
|
+
half4 condensate = vgBicubicCondensate(baseSrc);
|
|
624
|
+
half4 track = u_track.eval(baseSrc);
|
|
625
|
+
|
|
626
|
+
// Far plane \u2014 the identical field, scaled and offset (see the file header). An 8-tap ring, not
|
|
627
|
+
// the near plane's \xB1x/\xB1y box: axis-aligned taps read the SAME column/row on both sides of any
|
|
628
|
+
// residual axis-aligned structure (a seam, a resize artifact), which is exactly how a straight
|
|
629
|
+
// line gets doubled into four parallel copies instead of softened away \u2014 measured directly via
|
|
630
|
+
// check-medium.mjs's isotropy gate (see its report). The ring is rotated a half-step (22.5\xB0) off
|
|
631
|
+
// the axes so no tap direction ever coincides with a grid row or column. Radius stays in the same
|
|
632
|
+
// far-plane texture-space unit as before (u_farBlurRadius, grid-px) \u2014 only the tap PATTERN
|
|
633
|
+
// changed, not what it measures in.
|
|
634
|
+
float2 farSrc = baseSrc * u_farScale + u_farOffset;
|
|
635
|
+
float2 ring0 = float2( 0.9238795, 0.3826834) * u_farBlurRadius;
|
|
636
|
+
float2 ring1 = float2( 0.3826834, 0.9238795) * u_farBlurRadius;
|
|
637
|
+
float2 ring2 = float2(-0.3826834, 0.9238795) * u_farBlurRadius;
|
|
638
|
+
float2 ring3 = float2(-0.9238795, 0.3826834) * u_farBlurRadius;
|
|
639
|
+
float2 ring4 = float2(-0.9238795, -0.3826834) * u_farBlurRadius;
|
|
640
|
+
float2 ring5 = float2(-0.3826834, -0.9238795) * u_farBlurRadius;
|
|
641
|
+
float2 ring6 = float2( 0.3826834, -0.9238795) * u_farBlurRadius;
|
|
642
|
+
float2 ring7 = float2( 0.9238795, -0.3826834) * u_farBlurRadius;
|
|
643
|
+
half4 vaporFar = (u_vapor.eval(farSrc + ring0) + u_vapor.eval(farSrc + ring1) +
|
|
644
|
+
u_vapor.eval(farSrc + ring2) + u_vapor.eval(farSrc + ring3) +
|
|
645
|
+
u_vapor.eval(farSrc + ring4) + u_vapor.eval(farSrc + ring5) +
|
|
646
|
+
u_vapor.eval(farSrc + ring6) + u_vapor.eval(farSrc + ring7)) * half4(0.125);
|
|
647
|
+
half4 condensateFar = (u_condensate.eval(farSrc + ring0) + u_condensate.eval(farSrc + ring1) +
|
|
648
|
+
u_condensate.eval(farSrc + ring2) + u_condensate.eval(farSrc + ring3) +
|
|
649
|
+
u_condensate.eval(farSrc + ring4) + u_condensate.eval(farSrc + ring5) +
|
|
650
|
+
u_condensate.eval(farSrc + ring6) + u_condensate.eval(farSrc + ring7)) * half4(0.125);
|
|
651
|
+
|
|
652
|
+
// Gravity's "denser layer at the bottom of the chamber" \u2014 a compositing-only boost (see
|
|
653
|
+
// MEDIUM_DEFAULTS.gravityBottomBoost): it scales how density already in the conserved buffers is
|
|
654
|
+
// DISPLAYED, never the buffers themselves, so it can't threaten water conservation. Shared by
|
|
655
|
+
// both planes: the chamber's floor is denser everywhere along its depth, not just up close.
|
|
656
|
+
//
|
|
657
|
+
// Verified with a headless render, not assumed: ramping this with xy.y directly lit up the TOP
|
|
658
|
+
// of the frame, not the bottom (contentTexture's own orientation contract flips y once more
|
|
659
|
+
// between this pass and the final scene \u2014 see the file header) \u2014 hence the 1.0 - below.
|
|
660
|
+
float bottomFrac = 1.0 - xy.y / u_resolution.y;
|
|
661
|
+
float bottomT = smoothstep(u_gravityBoostStart, 1.0, bottomFrac);
|
|
662
|
+
float bottomBoost = 1.0 + u_gravityBoost * bottomT;
|
|
663
|
+
|
|
664
|
+
float gasR = (float(vapor.r) + float(vaporFar.r) * u_farWeight) * bottomBoost;
|
|
665
|
+
float gasG = (float(vapor.g) + float(vaporFar.g) * u_farWeight) * bottomBoost;
|
|
666
|
+
float gasB = (float(vapor.b) + float(vaporFar.b) * u_farWeight) * bottomBoost;
|
|
667
|
+
float w0 = gasR + float(track.r);
|
|
668
|
+
float w1 = gasG + float(track.g);
|
|
669
|
+
float w2 = gasB + float(track.b);
|
|
471
670
|
float wBg = u_baseWeight;
|
|
472
671
|
|
|
473
672
|
// A faint condensate hint from the LOCAL vapor, not its own color (droplets scatter light
|
|
@@ -475,7 +674,8 @@ half4 main(float2 xy) {
|
|
|
475
674
|
float vaporLocal = max(w0 + w1 + w2, 1e-4);
|
|
476
675
|
float3 vaporHue = (u_lab0 * w0 + u_lab1 * w1 + u_lab2 * w2) / vaporLocal;
|
|
477
676
|
float3 condensateLab = mix(u_labCondensate, vaporHue, u_condensateTint);
|
|
478
|
-
float wCondensate =
|
|
677
|
+
float wCondensate =
|
|
678
|
+
(float(condensate.r) + float(condensateFar.r) * u_farWeight) * u_condensateGain * bottomBoost;
|
|
479
679
|
|
|
480
680
|
float total = max(w0 + w1 + w2 + wBg + wCondensate, 1e-4);
|
|
481
681
|
float3 mixLab =
|
|
@@ -693,7 +893,7 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
693
893
|
}
|
|
694
894
|
resizeGrid(w, h);
|
|
695
895
|
}
|
|
696
|
-
function bindVelocityUniforms(loc, dt, phase2, p) {
|
|
896
|
+
function bindVelocityUniforms(loc, dt, phase2, p, spatial) {
|
|
697
897
|
web.setUniform(gl, loc("u_dyeSize"), [gridW, gridH]);
|
|
698
898
|
web.setUniform(gl, loc("u_resolution"), [gridW, gridH]);
|
|
699
899
|
web.setUniform(gl, loc("u_dt"), dt);
|
|
@@ -701,6 +901,11 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
701
901
|
web.setUniform(gl, loc("u_curlFreq"), p.curlFreq);
|
|
702
902
|
web.setUniform(gl, loc("u_advectSpeed"), p.advectSpeed);
|
|
703
903
|
web.setUniform(gl, loc("u_turbulence"), p.turbulence);
|
|
904
|
+
web.setUniform(gl, loc("u_vaporDrift"), p.gravityVaporDrift);
|
|
905
|
+
web.setUniform(gl, loc("u_spatial0"), [spatial[0].freqX, spatial[0].freqY, spatial[0].periodX, spatial[0].periodY]);
|
|
906
|
+
web.setUniform(gl, loc("u_spatial1"), [spatial[1].freqX, spatial[1].freqY, spatial[1].periodX, spatial[1].periodY]);
|
|
907
|
+
web.setUniform(gl, loc("u_spatial2"), [spatial[2].freqX, spatial[2].freqY, spatial[2].periodX, spatial[2].periodY]);
|
|
908
|
+
web.setUniform(gl, loc("u_spatial3"), [spatial[3].freqX, spatial[3].freqY, spatial[3].periodX, spatial[3].periodY]);
|
|
704
909
|
}
|
|
705
910
|
let phase = 0;
|
|
706
911
|
function wrapPhase(value) {
|
|
@@ -717,6 +922,7 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
717
922
|
return;
|
|
718
923
|
}
|
|
719
924
|
const p = { ...MEDIUM_DEFAULTS, ...params };
|
|
925
|
+
const spatial = computeMediumSpatialPeriods(gridW, gridH, p.curlFreq);
|
|
720
926
|
const clampedDt = Math.min(Math.max(dt, 0), 1 / 15);
|
|
721
927
|
phase = wrapPhase(phase + clampedDt * p.curlSpeed);
|
|
722
928
|
const back = front === 0 ? 1 : 0;
|
|
@@ -726,28 +932,30 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
726
932
|
gl.bindFramebuffer(gl.FRAMEBUFFER, vaporForward.fbo);
|
|
727
933
|
gl.useProgram(vaporForwardProgram);
|
|
728
934
|
web.bindTextureAt(gl, 0, vapor[front].texture, vaporForwardProgram, "u_dye");
|
|
729
|
-
bindVelocityUniforms(vaporForwardLoc, clampedDt, phase, p);
|
|
935
|
+
bindVelocityUniforms(vaporForwardLoc, clampedDt, phase, p, spatial);
|
|
730
936
|
web.drawFullscreenTriangle(gl);
|
|
731
937
|
gl.bindFramebuffer(gl.FRAMEBUFFER, condensateForward.fbo);
|
|
732
938
|
gl.useProgram(condensateForwardProgram);
|
|
733
939
|
web.bindTextureAt(gl, 0, condensate[front].texture, condensateForwardProgram, "u_dye");
|
|
734
|
-
bindVelocityUniforms(condensateForwardLoc, clampedDt, phase, p);
|
|
940
|
+
bindVelocityUniforms(condensateForwardLoc, clampedDt, phase, p, spatial);
|
|
735
941
|
web.setUniform(gl, condensateForwardLoc("u_settleSpeed"), p.condensateSettleSpeed);
|
|
942
|
+
web.setUniform(gl, condensateForwardLoc("u_settleWiggle"), p.condensateSettleWiggle);
|
|
736
943
|
web.drawFullscreenTriangle(gl);
|
|
737
944
|
gl.bindFramebuffer(gl.FRAMEBUFFER, vaporCorrected.fbo);
|
|
738
945
|
gl.useProgram(vaporCorrectProgram);
|
|
739
946
|
web.bindTextureAt(gl, 0, vapor[front].texture, vaporCorrectProgram, "u_dye");
|
|
740
947
|
web.bindTextureAt(gl, 1, vaporForward.texture, vaporCorrectProgram, "u_forward");
|
|
741
948
|
web.setUniform(gl, vaporCorrectLoc("u_forwardSize"), gridSize);
|
|
742
|
-
bindVelocityUniforms(vaporCorrectLoc, clampedDt, phase, p);
|
|
949
|
+
bindVelocityUniforms(vaporCorrectLoc, clampedDt, phase, p, spatial);
|
|
743
950
|
web.drawFullscreenTriangle(gl);
|
|
744
951
|
gl.bindFramebuffer(gl.FRAMEBUFFER, condensateCorrected.fbo);
|
|
745
952
|
gl.useProgram(condensateCorrectProgram);
|
|
746
953
|
web.bindTextureAt(gl, 0, condensate[front].texture, condensateCorrectProgram, "u_dye");
|
|
747
954
|
web.bindTextureAt(gl, 1, condensateForward.texture, condensateCorrectProgram, "u_forward");
|
|
748
955
|
web.setUniform(gl, condensateCorrectLoc("u_forwardSize"), gridSize);
|
|
749
|
-
bindVelocityUniforms(condensateCorrectLoc, clampedDt, phase, p);
|
|
956
|
+
bindVelocityUniforms(condensateCorrectLoc, clampedDt, phase, p, spatial);
|
|
750
957
|
web.setUniform(gl, condensateCorrectLoc("u_settleSpeed"), p.condensateSettleSpeed);
|
|
958
|
+
web.setUniform(gl, condensateCorrectLoc("u_settleWiggle"), p.condensateSettleWiggle);
|
|
751
959
|
web.drawFullscreenTriangle(gl);
|
|
752
960
|
gl.bindFramebuffer(gl.FRAMEBUFFER, vapor[back].fbo);
|
|
753
961
|
gl.useProgram(vaporReactProgram);
|
|
@@ -781,7 +989,7 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
781
989
|
gl.bindFramebuffer(gl.FRAMEBUFFER, track[back].fbo);
|
|
782
990
|
gl.useProgram(trackAdvectProgram);
|
|
783
991
|
web.bindTextureAt(gl, 0, track[front].texture, trackAdvectProgram, "u_dye");
|
|
784
|
-
bindVelocityUniforms(trackAdvectLoc, clampedDt, phase, p);
|
|
992
|
+
bindVelocityUniforms(trackAdvectLoc, clampedDt, phase, p, spatial);
|
|
785
993
|
web.setUniform(gl, trackAdvectLoc("u_decay"), p.decay);
|
|
786
994
|
web.drawFullscreenTriangle(gl);
|
|
787
995
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
@@ -853,6 +1061,12 @@ function createMediumRuntime(gl, vertexSource) {
|
|
|
853
1061
|
web.setUniform(gl, compositeLoc("u_baseWeight"), p.baseWeight);
|
|
854
1062
|
web.setUniform(gl, compositeLoc("u_condensateTint"), p.condensateTint);
|
|
855
1063
|
web.setUniform(gl, compositeLoc("u_condensateGain"), p.condensateGain);
|
|
1064
|
+
web.setUniform(gl, compositeLoc("u_farScale"), p.depthFarScale);
|
|
1065
|
+
web.setUniform(gl, compositeLoc("u_farOffset"), depthFarOffsetPx(p.depthFarOffsetFrac, gridW, gridH));
|
|
1066
|
+
web.setUniform(gl, compositeLoc("u_farBlurRadius"), p.depthFarBlurRadius);
|
|
1067
|
+
web.setUniform(gl, compositeLoc("u_farWeight"), p.depthFarWeight);
|
|
1068
|
+
web.setUniform(gl, compositeLoc("u_gravityBoost"), p.gravityBottomBoost);
|
|
1069
|
+
web.setUniform(gl, compositeLoc("u_gravityBoostStart"), p.gravityBottomBoostStart);
|
|
856
1070
|
web.drawFullscreenTriangle(gl);
|
|
857
1071
|
}
|
|
858
1072
|
function readTarget(target) {
|