vireuikit 0.1.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/LICENSE +202 -0
- package/NOTICE +11 -0
- package/README.md +81 -0
- package/dist/chunk-ZFX2MLKK.js +635 -0
- package/dist/chunk-ZFX2MLKK.js.map +1 -0
- package/dist/dynamics-BqBoj03x.d.cts +160 -0
- package/dist/dynamics-BqBoj03x.d.ts +160 -0
- package/dist/index.cjs +874 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +181 -0
- package/dist/index.d.ts +181 -0
- package/dist/index.js +188 -0
- package/dist/index.js.map +1 -0
- package/dist/web/index.cjs +967 -0
- package/dist/web/index.cjs.map +1 -0
- package/dist/web/index.d.cts +91 -0
- package/dist/web/index.d.ts +91 -0
- package/dist/web/index.js +443 -0
- package/dist/web/index.js.map +1 -0
- package/package.json +76 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var vireglass = require('vireglass');
|
|
4
|
+
|
|
5
|
+
// src/kit/atom.ts
|
|
6
|
+
var ATOM_AT_REST = {
|
|
7
|
+
pressed: 0,
|
|
8
|
+
hovered: 0,
|
|
9
|
+
selected: 0,
|
|
10
|
+
disabled: false
|
|
11
|
+
};
|
|
12
|
+
function atomLift(kind) {
|
|
13
|
+
switch (kind) {
|
|
14
|
+
case "iconButton":
|
|
15
|
+
case "switch":
|
|
16
|
+
case "slider":
|
|
17
|
+
return 1;
|
|
18
|
+
default:
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
var DISABLED_PRESENCE = 0.25;
|
|
23
|
+
var HOVER_GAIN = 0.35;
|
|
24
|
+
function atomMaterial(base, kind, state = ATOM_AT_REST) {
|
|
25
|
+
const clamp = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
26
|
+
const on = Math.max(clamp(state.selected), clamp(state.pressed), clamp(state.hovered) * HOVER_GAIN);
|
|
27
|
+
const out = vireglass.activeMaterial(base, state.disabled ? 0 : on);
|
|
28
|
+
if (!state.disabled) return out;
|
|
29
|
+
return { ...out, presence: out.presence * DISABLED_PRESENCE };
|
|
30
|
+
}
|
|
31
|
+
function atomAtRest(state) {
|
|
32
|
+
return state.pressed === 0 && state.hovered === 0 && state.selected === 0 && state.disabled === false;
|
|
33
|
+
}
|
|
34
|
+
function stepSpring(s, dt, response, damping, instant = false) {
|
|
35
|
+
if (instant) {
|
|
36
|
+
const moved = s.x !== s.target || s.v !== 0;
|
|
37
|
+
s.x = s.target;
|
|
38
|
+
s.v = 0;
|
|
39
|
+
return moved;
|
|
40
|
+
}
|
|
41
|
+
const stiffness = (2 * Math.PI / response) ** 2;
|
|
42
|
+
const friction = 4 * Math.PI * damping / response;
|
|
43
|
+
const h = 1 / 240;
|
|
44
|
+
for (let left = Math.min(dt, 0.05); left > 0; left -= h) {
|
|
45
|
+
const step = Math.min(left, h);
|
|
46
|
+
s.v += (stiffness * (s.target - s.x) - friction * s.v) * step;
|
|
47
|
+
s.x += s.v * step;
|
|
48
|
+
}
|
|
49
|
+
if (Math.abs(s.target - s.x) < 1e-3 && Math.abs(s.v) < 0.01) {
|
|
50
|
+
s.x = s.target;
|
|
51
|
+
s.v = 0;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
var smoothstep = (e0, e1, x) => {
|
|
57
|
+
const t = Math.min(Math.max((x - e0) / (e1 - e0), 0), 1);
|
|
58
|
+
return t * t * (3 - 2 * t);
|
|
59
|
+
};
|
|
60
|
+
var mix = (a, b, t) => a + (b - a) * t;
|
|
61
|
+
var DROP_DIAMETER = 1.1;
|
|
62
|
+
var DROP_LIFT = 0.5;
|
|
63
|
+
var NECK_GAIN = 0.3;
|
|
64
|
+
var NECK_RAMP = 4;
|
|
65
|
+
var CORNER_EASE_START = 0.55;
|
|
66
|
+
var INK_OUT_END = 0.4;
|
|
67
|
+
var INK_IN_START = 0.45;
|
|
68
|
+
var INK_IN_END = 0.95;
|
|
69
|
+
function growthInk(phase) {
|
|
70
|
+
const a = Math.min(Math.max(phase.shrink, 0), 1);
|
|
71
|
+
const b = Math.max(phase.grow, 0);
|
|
72
|
+
return {
|
|
73
|
+
outgoing: 1 - smoothstep(0, INK_OUT_END, a),
|
|
74
|
+
incoming: smoothstep(INK_IN_START, INK_IN_END, b)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function largerOf(a, b) {
|
|
78
|
+
return vireglass.halfMinDp(a.geometry) >= vireglass.halfMinDp(b.geometry) ? { main: a, other: b } : { main: b, other: a };
|
|
79
|
+
}
|
|
80
|
+
function growthFrame(source, target, anchor, phase) {
|
|
81
|
+
const dx = target.center.x - source.center.x;
|
|
82
|
+
const dy = target.center.y - source.center.y;
|
|
83
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
84
|
+
const sourceSize = vireglass.halfMinDp(source.geometry) * 2;
|
|
85
|
+
const lift = DROP_LIFT * sourceSize;
|
|
86
|
+
const drop = {
|
|
87
|
+
x: source.center.x + dx / len * lift,
|
|
88
|
+
y: source.center.y + dy / len * lift
|
|
89
|
+
};
|
|
90
|
+
const a = Math.min(Math.max(phase.shrink, 0), 1);
|
|
91
|
+
const b = Math.max(phase.grow, 0);
|
|
92
|
+
const keep = Math.max(1 - b, 0);
|
|
93
|
+
const d = DROP_DIAMETER * sourceSize;
|
|
94
|
+
const shrunkGeometry = {
|
|
95
|
+
width: mix(source.geometry.width, 0.6 * d, a) * keep,
|
|
96
|
+
height: mix(source.geometry.height, 0.6 * d, a) * keep,
|
|
97
|
+
cornerRadius: 0
|
|
98
|
+
};
|
|
99
|
+
shrunkGeometry.cornerRadius = Math.min(
|
|
100
|
+
mix(source.geometry.cornerRadius, vireglass.halfMinDp(shrunkGeometry), a),
|
|
101
|
+
vireglass.halfMinDp(shrunkGeometry)
|
|
102
|
+
);
|
|
103
|
+
const shrunk = {
|
|
104
|
+
geometry: shrunkGeometry,
|
|
105
|
+
center: { x: mix(source.center.x, drop.x, a), y: mix(source.center.y, drop.y, a) }
|
|
106
|
+
};
|
|
107
|
+
const seed = d * Math.sqrt(a);
|
|
108
|
+
const cornerX = Math.sign(anchor.x - source.center.x);
|
|
109
|
+
const cornerY = Math.sign(anchor.y - source.center.y);
|
|
110
|
+
const edgeX = mix(drop.x + cornerX * (seed / 2), anchor.x, Math.min(b, 1));
|
|
111
|
+
const edgeY = mix(drop.y + cornerY * (seed / 2), anchor.y, Math.min(b, 1));
|
|
112
|
+
const width = mix(seed, target.geometry.width, b);
|
|
113
|
+
const height = mix(seed, target.geometry.height, b);
|
|
114
|
+
const grownGeometry = { width, height, cornerRadius: 0 };
|
|
115
|
+
grownGeometry.cornerRadius = Math.min(
|
|
116
|
+
mix(vireglass.halfMinDp(grownGeometry), target.geometry.cornerRadius, smoothstep(CORNER_EASE_START, 1, b)),
|
|
117
|
+
vireglass.halfMinDp(grownGeometry)
|
|
118
|
+
);
|
|
119
|
+
const grown = {
|
|
120
|
+
geometry: grownGeometry,
|
|
121
|
+
center: { x: edgeX - cornerX * (width / 2), y: edgeY - cornerY * (height / 2) }
|
|
122
|
+
};
|
|
123
|
+
const { main, other } = largerOf(grown, shrunk);
|
|
124
|
+
const neck = NECK_GAIN * sourceSize * Math.min(a * NECK_RAMP, 1) * keep;
|
|
125
|
+
const united = neck > 0.01 && other.geometry.width > 0.5 && other.geometry.height > 0.5;
|
|
126
|
+
return {
|
|
127
|
+
body: main,
|
|
128
|
+
grown,
|
|
129
|
+
merge: united ? {
|
|
130
|
+
offsetX: other.center.x - main.center.x,
|
|
131
|
+
offsetY: other.center.y - main.center.y,
|
|
132
|
+
width: other.geometry.width,
|
|
133
|
+
height: other.geometry.height,
|
|
134
|
+
cornerRadius: other.geometry.cornerRadius,
|
|
135
|
+
smoothing: neck
|
|
136
|
+
} : void 0,
|
|
137
|
+
ink: growthInk(phase)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
var createGrowthState = () => ({
|
|
141
|
+
shrink: { x: 0, v: 0, target: 0 },
|
|
142
|
+
grow: { x: 0, v: 0, target: 0 }
|
|
143
|
+
});
|
|
144
|
+
var growthPhase = (state) => ({
|
|
145
|
+
shrink: state.shrink.x,
|
|
146
|
+
grow: state.grow.x
|
|
147
|
+
});
|
|
148
|
+
var DEFAULT_GROWTH_TIMING = {
|
|
149
|
+
shrinkOpen: 0.38,
|
|
150
|
+
shrinkClose: 0.3,
|
|
151
|
+
growOpen: 0.46,
|
|
152
|
+
growClose: 0.4,
|
|
153
|
+
growDampingOpen: 0.72
|
|
154
|
+
};
|
|
155
|
+
var GROW_DAMPING_CLOSE = 1;
|
|
156
|
+
var SHRINK_DAMPING = 1;
|
|
157
|
+
var GROW_STARTS_AT = 0.8;
|
|
158
|
+
var SHRINK_RETURNS_AT = 0.12;
|
|
159
|
+
function stepGrowth(state, dt, open, reduceMotion = false, timing = DEFAULT_GROWTH_TIMING) {
|
|
160
|
+
const { shrink, grow } = state;
|
|
161
|
+
if (reduceMotion) {
|
|
162
|
+
shrink.target = open ? 1 : 0;
|
|
163
|
+
grow.target = open ? 1 : 0;
|
|
164
|
+
} else if (open) {
|
|
165
|
+
shrink.target = 1;
|
|
166
|
+
if (shrink.x > GROW_STARTS_AT) grow.target = 1;
|
|
167
|
+
} else {
|
|
168
|
+
grow.target = 0;
|
|
169
|
+
if (grow.x < SHRINK_RETURNS_AT) shrink.target = 0;
|
|
170
|
+
}
|
|
171
|
+
const movedShrink = stepSpring(
|
|
172
|
+
shrink,
|
|
173
|
+
dt,
|
|
174
|
+
open ? timing.shrinkOpen : timing.shrinkClose,
|
|
175
|
+
SHRINK_DAMPING,
|
|
176
|
+
reduceMotion
|
|
177
|
+
);
|
|
178
|
+
const movedGrow = stepSpring(
|
|
179
|
+
grow,
|
|
180
|
+
dt,
|
|
181
|
+
open ? timing.growOpen : timing.growClose,
|
|
182
|
+
open ? timing.growDampingOpen : GROW_DAMPING_CLOSE,
|
|
183
|
+
reduceMotion
|
|
184
|
+
);
|
|
185
|
+
return movedShrink || movedGrow;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/medium/oklab.ts
|
|
189
|
+
var LINEAR_TO_LMS = [
|
|
190
|
+
[0.4122214708, 0.5363325363, 0.0514459929],
|
|
191
|
+
[0.2119034982, 0.6806995451, 0.1073969566],
|
|
192
|
+
[0.0883024619, 0.2817188376, 0.6299787005]
|
|
193
|
+
];
|
|
194
|
+
var LMS_TO_OKLAB = [
|
|
195
|
+
[0.2104542553, 0.793617785, -0.0040720468],
|
|
196
|
+
[1.9779984951, -2.428592205, 0.4505937099],
|
|
197
|
+
[0.0259040371, 0.7827717662, -0.808675766]
|
|
198
|
+
];
|
|
199
|
+
var OKLAB_TO_LMS = [
|
|
200
|
+
[1, 0.3963377774, 0.2158037573],
|
|
201
|
+
[1, -0.1055613458, -0.0638541728],
|
|
202
|
+
[1, -0.0894841775, -1.291485548]
|
|
203
|
+
];
|
|
204
|
+
var LMS_TO_LINEAR = [
|
|
205
|
+
[4.0767416621, -3.3077115913, 0.2309699292],
|
|
206
|
+
[-1.2684380046, 2.6097574011, -0.3413193965],
|
|
207
|
+
[-0.0041960863, -0.7034186147, 1.707614701]
|
|
208
|
+
];
|
|
209
|
+
var glsl = (v) => Number.isInteger(v) ? v.toFixed(1) : String(v);
|
|
210
|
+
var row = (m, i, vars) => m[i].map((k, j) => `${glsl(k)} * ${vars[j]}`).join(" + ");
|
|
211
|
+
var VG_OKLAB_TO_SRGB = `
|
|
212
|
+
float3 vgLinearToSrgb(float3 c) {
|
|
213
|
+
float3 lo = c * 12.92;
|
|
214
|
+
float3 hi = 1.055 * pow(max(c, float3(0.0)), float3(1.0 / 2.4)) - 0.055;
|
|
215
|
+
return mix(lo, hi, step(float3(0.0031308), c));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
float3 vgOklabToLinear(float3 lab) {
|
|
219
|
+
float l_ = ${row(OKLAB_TO_LMS, 0, ["lab.x", "lab.y", "lab.z"])};
|
|
220
|
+
float m_ = ${row(OKLAB_TO_LMS, 1, ["lab.x", "lab.y", "lab.z"])};
|
|
221
|
+
float s_ = ${row(OKLAB_TO_LMS, 2, ["lab.x", "lab.y", "lab.z"])};
|
|
222
|
+
float l = l_ * l_ * l_;
|
|
223
|
+
float m = m_ * m_ * m_;
|
|
224
|
+
float s = s_ * s_ * s_;
|
|
225
|
+
return float3(
|
|
226
|
+
${row(LMS_TO_LINEAR, 0, ["l", "m", "s"])},
|
|
227
|
+
${row(LMS_TO_LINEAR, 1, ["l", "m", "s"])},
|
|
228
|
+
${row(LMS_TO_LINEAR, 2, ["l", "m", "s"])}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
`;
|
|
232
|
+
var apply = (m, v) => [
|
|
233
|
+
m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
|
|
234
|
+
m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
|
|
235
|
+
m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2]
|
|
236
|
+
];
|
|
237
|
+
var srgbChannelToLinear = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
238
|
+
var linearChannelToSrgb = (c) => c <= 31308e-7 ? c * 12.92 : 1.055 * Math.pow(Math.max(c, 0), 1 / 2.4) - 0.055;
|
|
239
|
+
function srgbToOklab(rgb) {
|
|
240
|
+
const linear = rgb.map(srgbChannelToLinear);
|
|
241
|
+
const lms = apply(LINEAR_TO_LMS, linear);
|
|
242
|
+
return apply(LMS_TO_OKLAB, lms.map((v) => Math.cbrt(Math.max(v, 0))));
|
|
243
|
+
}
|
|
244
|
+
function oklabToSrgb(lab) {
|
|
245
|
+
const lms = apply(OKLAB_TO_LMS, lab).map((v) => v * v * v);
|
|
246
|
+
return apply(LMS_TO_LINEAR, lms).map(linearChannelToSrgb);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/medium/species.ts
|
|
250
|
+
var MEDIUM_SPECIES_LIGHTNESS = 0.44;
|
|
251
|
+
var MEDIUM_SPECIES_HUE_SPREAD = 32;
|
|
252
|
+
var clamp01 = (v) => Math.min(1, Math.max(0, v));
|
|
253
|
+
function characterOf(color) {
|
|
254
|
+
const [lightness, a, b] = srgbToOklab(color);
|
|
255
|
+
return {
|
|
256
|
+
hue: Math.atan2(b, a) * 180 / Math.PI,
|
|
257
|
+
chroma: Math.hypot(a, b),
|
|
258
|
+
lightness
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function speciesFamily(character, spreadDeg = MEDIUM_SPECIES_HUE_SPREAD) {
|
|
262
|
+
const { hue, chroma, lightness } = character;
|
|
263
|
+
const tone = (deg) => {
|
|
264
|
+
const rad = deg * Math.PI / 180;
|
|
265
|
+
return oklabToSrgb([lightness, chroma * Math.cos(rad), chroma * Math.sin(rad)]).map(
|
|
266
|
+
clamp01
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
return [tone(hue - spreadDeg), tone(hue), tone(hue + spreadDeg)];
|
|
270
|
+
}
|
|
271
|
+
function neutralSpecies(baseColor) {
|
|
272
|
+
const { hue, chroma } = characterOf(baseColor);
|
|
273
|
+
return speciesFamily({ hue, chroma, lightness: MEDIUM_SPECIES_LIGHTNESS });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/medium/params.ts
|
|
277
|
+
var BASE_COLOR = [0.07, 0.08, 0.1];
|
|
278
|
+
var MEDIUM_DEFAULTS = {
|
|
279
|
+
decay: 0.06,
|
|
280
|
+
curlFreq: 0.045,
|
|
281
|
+
curlSpeed: 0.12,
|
|
282
|
+
advectSpeed: 26,
|
|
283
|
+
turbulence: 1,
|
|
284
|
+
condensationRate: 0.18,
|
|
285
|
+
condensationFloor: 0.12,
|
|
286
|
+
evaporationRate: 0.2,
|
|
287
|
+
condensateSettleSpeed: 8,
|
|
288
|
+
condensateSpreadRate: 0.08,
|
|
289
|
+
condensateTint: 0.2,
|
|
290
|
+
condensateGain: 5,
|
|
291
|
+
condensateColor: [0.94, 0.95, 0.97],
|
|
292
|
+
channelColors: neutralSpecies(BASE_COLOR),
|
|
293
|
+
baseColor: BASE_COLOR,
|
|
294
|
+
baseWeight: 0.9
|
|
295
|
+
};
|
|
296
|
+
var MEDIUM_PROBE_FLOOR_PX = 22;
|
|
297
|
+
var MEDIUM_DEFAULT_CELL_PX = 26;
|
|
298
|
+
var MEDIUM_TRACK_PRESETS = {
|
|
299
|
+
alpha: {
|
|
300
|
+
lengthFrac: 0.22,
|
|
301
|
+
headWidthFrac: 0.05,
|
|
302
|
+
tailWidthFrac: 0.12,
|
|
303
|
+
raggedFrac: 0,
|
|
304
|
+
raggedFreq: 0,
|
|
305
|
+
settleFrac: 0.02,
|
|
306
|
+
tailDim: 0.55,
|
|
307
|
+
intensity: 1.15
|
|
308
|
+
},
|
|
309
|
+
beta: {
|
|
310
|
+
lengthFrac: 0.42,
|
|
311
|
+
headWidthFrac: 0.018,
|
|
312
|
+
tailWidthFrac: 0.05,
|
|
313
|
+
raggedFrac: 0.045,
|
|
314
|
+
raggedFreq: 5,
|
|
315
|
+
settleFrac: 0.05,
|
|
316
|
+
tailDim: 0.45,
|
|
317
|
+
intensity: 0.85
|
|
318
|
+
},
|
|
319
|
+
natural: {
|
|
320
|
+
lengthFrac: 0.13,
|
|
321
|
+
headWidthFrac: 0.02,
|
|
322
|
+
tailWidthFrac: 0.04,
|
|
323
|
+
raggedFrac: 0.02,
|
|
324
|
+
raggedFreq: 4,
|
|
325
|
+
settleFrac: 0.02,
|
|
326
|
+
tailDim: 0.5,
|
|
327
|
+
intensity: 0.4
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var MEDIUM_DRIFT_TURBULENCE = 0.045;
|
|
331
|
+
var MEDIUM_PLAYING_TURBULENCE_RANGE = [0.55, 1.15];
|
|
332
|
+
var MEDIUM_TURBULENCE_TAU = 2.5;
|
|
333
|
+
var MEDIUM_EMISSION_BEATS = 4;
|
|
334
|
+
var MEDIUM_EMISSION_PROBABILITY = 0.55;
|
|
335
|
+
var MEDIUM_MIN_EMISSION_INTERVAL = 1.2;
|
|
336
|
+
var MEDIUM_NATURAL_INTERVAL = 25;
|
|
337
|
+
var MEDIUM_NATURAL_JITTER = 35;
|
|
338
|
+
var MEDIUM_SENSITIVE_TOP = 0.55;
|
|
339
|
+
var MEDIUM_SENSITIVE_BOTTOM = 0.95;
|
|
340
|
+
|
|
341
|
+
// src/medium/noise.ts
|
|
342
|
+
var VG_VALUE_NOISE = `
|
|
343
|
+
float vgHash21(float2 p) {
|
|
344
|
+
float h = dot(p, float2(127.1, 311.7));
|
|
345
|
+
return fract(sin(h) * 43758.5453123);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
float vgValueNoise(float2 p) {
|
|
349
|
+
float2 i = floor(p);
|
|
350
|
+
float2 f = fract(p);
|
|
351
|
+
float a = vgHash21(i);
|
|
352
|
+
float b = vgHash21(i + float2(1.0, 0.0));
|
|
353
|
+
float c = vgHash21(i + float2(0.0, 1.0));
|
|
354
|
+
float d = vgHash21(i + float2(1.0, 1.0));
|
|
355
|
+
float2 u = f * f * (3.0 - 2.0 * f);
|
|
356
|
+
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y) * 2.0 - 1.0;
|
|
357
|
+
}
|
|
358
|
+
`;
|
|
359
|
+
var glslFloat = (v) => Number.isInteger(v) ? v.toFixed(1) : String(v);
|
|
360
|
+
var MEDIUM_TIME_PERIOD = 1e3;
|
|
361
|
+
var MEDIUM_TIME_OCTAVES = [
|
|
362
|
+
{ rate: 0.6, offset: 0, scale: 1 },
|
|
363
|
+
{ rate: 1.7, offset: 11, scale: 2.03 },
|
|
364
|
+
{ rate: 0.9, offset: 37, scale: 4.11 },
|
|
365
|
+
{ rate: 0.15, offset: 5, scale: 0.35 }
|
|
366
|
+
];
|
|
367
|
+
function mediumOctavePeriod(rate) {
|
|
368
|
+
const period = rate * MEDIUM_TIME_PERIOD;
|
|
369
|
+
if (!Number.isInteger(period)) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
`medium noise: octave rate ${rate} * MEDIUM_TIME_PERIOD ${MEDIUM_TIME_PERIOD} = ${period}, not an integer \u2014 the phase wrap would not be seamless`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
return period;
|
|
375
|
+
}
|
|
376
|
+
var glslOctave = (o) => ({
|
|
377
|
+
rate: glslFloat(o.rate),
|
|
378
|
+
offset: glslFloat(o.offset),
|
|
379
|
+
scale: glslFloat(o.scale),
|
|
380
|
+
period: glslFloat(mediumOctavePeriod(o.rate))
|
|
381
|
+
});
|
|
382
|
+
var [VG_OCT0, VG_OCT1, VG_OCT2, VG_OCT3] = MEDIUM_TIME_OCTAVES.map(glslOctave);
|
|
383
|
+
var VG_CURL_NOISE = `
|
|
384
|
+
${VG_VALUE_NOISE}
|
|
385
|
+
|
|
386
|
+
float vgHash31(float3 p) {
|
|
387
|
+
float h = dot(p, float3(127.1, 311.7, 74.7));
|
|
388
|
+
return fract(sin(h) * 43758.5453123);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 3D value-noise: time is a third grid axis, not a coordinate shift. A shift would just SLIDE the
|
|
392
|
+
// pattern (a "traveling" look, when motion is meant to read as random), while trilinear
|
|
393
|
+
// interpolation over t CHANGES the pattern itself between grid nodes \u2014 nearby moments look alike,
|
|
394
|
+
// distant ones don't, and no repeat ever shows up.
|
|
395
|
+
float vgValueNoise3(float3 p) {
|
|
396
|
+
float3 i = floor(p);
|
|
397
|
+
float3 f = fract(p);
|
|
398
|
+
float3 u = f * f * (3.0 - 2.0 * f);
|
|
399
|
+
float n000 = vgHash31(i);
|
|
400
|
+
float n100 = vgHash31(i + float3(1.0, 0.0, 0.0));
|
|
401
|
+
float n010 = vgHash31(i + float3(0.0, 1.0, 0.0));
|
|
402
|
+
float n110 = vgHash31(i + float3(1.0, 1.0, 0.0));
|
|
403
|
+
float n001 = vgHash31(i + float3(0.0, 0.0, 1.0));
|
|
404
|
+
float n101 = vgHash31(i + float3(1.0, 0.0, 1.0));
|
|
405
|
+
float n011 = vgHash31(i + float3(0.0, 1.0, 1.0));
|
|
406
|
+
float n111 = vgHash31(i + float3(1.0, 1.0, 1.0));
|
|
407
|
+
float nx00 = mix(n000, n100, u.x);
|
|
408
|
+
float nx10 = mix(n010, n110, u.x);
|
|
409
|
+
float nx01 = mix(n001, n101, u.x);
|
|
410
|
+
float nx11 = mix(n011, n111, u.x);
|
|
411
|
+
float nxy0 = mix(nx00, nx10, u.y);
|
|
412
|
+
float nxy1 = mix(nx01, nx11, u.y);
|
|
413
|
+
return mix(nxy0, nxy1, u.z) * 2.0 - 1.0;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Same as vgValueNoise3, but the z axis wraps at \`period\`: BOTH z-lattice neighbors (i.z and
|
|
417
|
+
// i.z + 1.0) are folded into [0, period) before hashing, so this is EXACTLY the same function of
|
|
418
|
+
// z and of z + period \u2014 not just close. vgPotential below calls this once per octave, each with
|
|
419
|
+
// the period matching its own rate (MEDIUM_TIME_OCTAVES), so a phase that wraps at
|
|
420
|
+
// MEDIUM_TIME_PERIOD reproduces the identical field instead of jumping to a new one.
|
|
421
|
+
float vgValueNoise3Periodic(float3 p, float period) {
|
|
422
|
+
float3 i = floor(p);
|
|
423
|
+
float3 f = fract(p);
|
|
424
|
+
float3 u = f * f * (3.0 - 2.0 * f);
|
|
425
|
+
float z0 = mod(i.z, period);
|
|
426
|
+
float z1 = mod(i.z + 1.0, period);
|
|
427
|
+
float n000 = vgHash31(float3(i.xy, z0));
|
|
428
|
+
float n100 = vgHash31(float3(i.xy + float2(1.0, 0.0), z0));
|
|
429
|
+
float n010 = vgHash31(float3(i.xy + float2(0.0, 1.0), z0));
|
|
430
|
+
float n110 = vgHash31(float3(i.xy + float2(1.0, 1.0), z0));
|
|
431
|
+
float n001 = vgHash31(float3(i.xy, z1));
|
|
432
|
+
float n101 = vgHash31(float3(i.xy + float2(1.0, 0.0), z1));
|
|
433
|
+
float n011 = vgHash31(float3(i.xy + float2(0.0, 1.0), z1));
|
|
434
|
+
float n111 = vgHash31(float3(i.xy + float2(1.0, 1.0), z1));
|
|
435
|
+
float nx00 = mix(n000, n100, u.x);
|
|
436
|
+
float nx10 = mix(n010, n110, u.x);
|
|
437
|
+
float nx01 = mix(n001, n101, u.x);
|
|
438
|
+
float nx11 = mix(n011, n111, u.x);
|
|
439
|
+
float nxy0 = mix(nx00, nx10, u.y);
|
|
440
|
+
float nxy1 = mix(nx01, nx11, u.y);
|
|
441
|
+
return mix(nxy0, nxy1, u.z) * 2.0 - 1.0;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// The potential is an fbm of three octaves, each evolving over the field's PHASE at its own speed
|
|
445
|
+
// (unrelated to the others, plus a phase offset): no single phase shift can align them all at
|
|
446
|
+
// once, so no traveling pattern remains. Each octave wraps its own z-lattice at the period that
|
|
447
|
+
// matches its rate (see MEDIUM_TIME_OCTAVES/vgValueNoise3Periodic), so
|
|
448
|
+
// vgPotential(p, phase) === vgPotential(p, phase + ${glslFloat(MEDIUM_TIME_PERIOD)}) exactly \u2014 a
|
|
449
|
+
// CPU-side phase accumulator can wrap at that period with no visible jump (see web/medium.ts). The
|
|
450
|
+
// large-scale factor is a slow non-uniformity layered over the sum \u2014 without it, a field that's
|
|
451
|
+
// uniform by statistics still reads as a regular cell pattern.
|
|
452
|
+
float vgPotential(float2 p, float phase) {
|
|
453
|
+
float v = vgValueNoise3Periodic(float3(p * ${VG_OCT0.scale}, phase * ${VG_OCT0.rate} + ${VG_OCT0.offset}), ${VG_OCT0.period}) * 0.5;
|
|
454
|
+
v += vgValueNoise3Periodic(float3(p * ${VG_OCT1.scale}, phase * ${VG_OCT1.rate} + ${VG_OCT1.offset}), ${VG_OCT1.period}) * 0.25;
|
|
455
|
+
v += vgValueNoise3Periodic(float3(p * ${VG_OCT2.scale}, phase * ${VG_OCT2.rate} + ${VG_OCT2.offset}), ${VG_OCT2.period}) * 0.125;
|
|
456
|
+
float macro = vgValueNoise3Periodic(float3(p * ${VG_OCT3.scale}, phase * ${VG_OCT3.rate} + ${VG_OCT3.offset}), ${VG_OCT3.period});
|
|
457
|
+
return v * (0.7 + 0.3 * macro);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Velocity = (dPsi/dy, -dPsi/dx) \u2014 the curl of a scalar field, solenoidal for any Psi. Central
|
|
461
|
+
// differences instead of an analytic derivative: the potential is a sum of three value-noise
|
|
462
|
+
// calls, and there is no cheaper way to differentiate it by hand than four extra samples.
|
|
463
|
+
float2 vgCurlVelocity(float2 p, float phase) {
|
|
464
|
+
float e = 0.6;
|
|
465
|
+
float px1 = vgPotential(p + float2(e, 0.0), phase);
|
|
466
|
+
float px0 = vgPotential(p - float2(e, 0.0), phase);
|
|
467
|
+
float py1 = vgPotential(p + float2(0.0, e), phase);
|
|
468
|
+
float py0 = vgPotential(p - float2(0.0, e), phase);
|
|
469
|
+
return float2(py1 - py0, -(px1 - px0)) / (2.0 * e);
|
|
470
|
+
}
|
|
471
|
+
`;
|
|
472
|
+
|
|
473
|
+
// src/medium/seed-shader.ts
|
|
474
|
+
var MEDIUM_SEED_SHADER = `
|
|
475
|
+
uniform float2 u_spot0;
|
|
476
|
+
uniform float2 u_spot1;
|
|
477
|
+
uniform float2 u_spot2;
|
|
478
|
+
uniform float u_spotRadius;
|
|
479
|
+
|
|
480
|
+
float vgSpot(float2 p, float2 c, float r) {
|
|
481
|
+
float d = length(p - c) / max(r, 1.0);
|
|
482
|
+
return exp(-d * d * 2.0);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
half4 main(float2 xy) {
|
|
486
|
+
float3 dye = float3(
|
|
487
|
+
vgSpot(xy, u_spot0, u_spotRadius),
|
|
488
|
+
vgSpot(xy, u_spot1, u_spotRadius),
|
|
489
|
+
vgSpot(xy, u_spot2, u_spotRadius)
|
|
490
|
+
);
|
|
491
|
+
return half4(half3(dye), half(1.0));
|
|
492
|
+
}
|
|
493
|
+
`;
|
|
494
|
+
|
|
495
|
+
// src/medium/resample-shader.ts
|
|
496
|
+
var MEDIUM_RESAMPLE_SHADER = `
|
|
497
|
+
uniform shader u_src;
|
|
498
|
+
|
|
499
|
+
half4 main(float2 xy) {
|
|
500
|
+
float2 uv = xy / u_resolution;
|
|
501
|
+
float2 src = uv * u_srcSize;
|
|
502
|
+
return u_src.eval(float2(src.x, u_srcSize.y - src.y));
|
|
503
|
+
}
|
|
504
|
+
`;
|
|
505
|
+
|
|
506
|
+
// src/medium/advect-shader.ts
|
|
507
|
+
var MEDIUM_ADVECT_UNIFORMS = `
|
|
508
|
+
uniform shader u_dye;
|
|
509
|
+
uniform float u_dt;
|
|
510
|
+
uniform float u_phase;
|
|
511
|
+
uniform float u_curlFreq;
|
|
512
|
+
uniform float u_advectSpeed;
|
|
513
|
+
uniform float u_turbulence;
|
|
514
|
+
|
|
515
|
+
${VG_CURL_NOISE}
|
|
516
|
+
`;
|
|
517
|
+
var MEDIUM_ADVECT_VELOCITY = `
|
|
518
|
+
float2 vel = vgCurlVelocity(xy * u_curlFreq, u_phase) * u_advectSpeed * u_turbulence;
|
|
519
|
+
`;
|
|
520
|
+
var MEDIUM_ADVECT_VELOCITY_SETTLE = `
|
|
521
|
+
${MEDIUM_ADVECT_VELOCITY}
|
|
522
|
+
vel.y += u_settleSpeed;
|
|
523
|
+
`;
|
|
524
|
+
var MEDIUM_VAPOR_FORWARD_SHADER = `
|
|
525
|
+
${MEDIUM_ADVECT_UNIFORMS}
|
|
526
|
+
|
|
527
|
+
half4 main(float2 xy) {
|
|
528
|
+
${MEDIUM_ADVECT_VELOCITY}
|
|
529
|
+
float2 src = xy - vel * u_dt;
|
|
530
|
+
return u_dye.eval(float2(src.x, u_dyeSize.y - src.y));
|
|
531
|
+
}
|
|
532
|
+
`;
|
|
533
|
+
var MEDIUM_CONDENSATE_FORWARD_SHADER = `
|
|
534
|
+
${MEDIUM_ADVECT_UNIFORMS}
|
|
535
|
+
uniform float u_settleSpeed;
|
|
536
|
+
|
|
537
|
+
half4 main(float2 xy) {
|
|
538
|
+
${MEDIUM_ADVECT_VELOCITY_SETTLE}
|
|
539
|
+
float2 src = xy - vel * u_dt;
|
|
540
|
+
return u_dye.eval(float2(src.x, u_dyeSize.y - src.y));
|
|
541
|
+
}
|
|
542
|
+
`;
|
|
543
|
+
var MEDIUM_MACCORMACK_CORRECT = `
|
|
544
|
+
float2 srcForward = xy - vel * u_dt;
|
|
545
|
+
float2 srcReverse = xy + vel * u_dt;
|
|
546
|
+
half4 forwardSelf = u_forward.eval(float2(xy.x, u_forwardSize.y - xy.y));
|
|
547
|
+
half4 forwardReverse = u_forward.eval(float2(srcReverse.x, u_forwardSize.y - srcReverse.y));
|
|
548
|
+
half4 prevSelf = u_dye.eval(float2(xy.x, u_dyeSize.y - xy.y));
|
|
549
|
+
half4 corrected = forwardSelf + half4(0.5) * (prevSelf - forwardReverse);
|
|
550
|
+
|
|
551
|
+
float2 src = float2(srcForward.x, u_dyeSize.y - srcForward.y);
|
|
552
|
+
float2 base = floor(src - 0.5);
|
|
553
|
+
half4 c00 = u_dye.eval(base + float2(0.5, 0.5));
|
|
554
|
+
half4 c10 = u_dye.eval(base + float2(1.5, 0.5));
|
|
555
|
+
half4 c01 = u_dye.eval(base + float2(0.5, 1.5));
|
|
556
|
+
half4 c11 = u_dye.eval(base + float2(1.5, 1.5));
|
|
557
|
+
half4 lo = min(min(c00, c10), min(c01, c11));
|
|
558
|
+
half4 hi = max(max(c00, c10), max(c01, c11));
|
|
559
|
+
half4 limited = clamp(corrected, lo, hi);
|
|
560
|
+
`;
|
|
561
|
+
var MEDIUM_VAPOR_CORRECT_SHADER = `
|
|
562
|
+
${MEDIUM_ADVECT_UNIFORMS}
|
|
563
|
+
uniform shader u_forward;
|
|
564
|
+
|
|
565
|
+
half4 main(float2 xy) {
|
|
566
|
+
${MEDIUM_ADVECT_VELOCITY}
|
|
567
|
+
${MEDIUM_MACCORMACK_CORRECT}
|
|
568
|
+
return limited;
|
|
569
|
+
}
|
|
570
|
+
`;
|
|
571
|
+
var MEDIUM_CONDENSATE_CORRECT_SHADER = `
|
|
572
|
+
${MEDIUM_ADVECT_UNIFORMS}
|
|
573
|
+
uniform float u_settleSpeed;
|
|
574
|
+
uniform shader u_forward;
|
|
575
|
+
|
|
576
|
+
half4 main(float2 xy) {
|
|
577
|
+
${MEDIUM_ADVECT_VELOCITY_SETTLE}
|
|
578
|
+
${MEDIUM_MACCORMACK_CORRECT}
|
|
579
|
+
return limited;
|
|
580
|
+
}
|
|
581
|
+
`;
|
|
582
|
+
var MEDIUM_VAPOR_REACT_SHADER = `
|
|
583
|
+
uniform shader u_transported;
|
|
584
|
+
uniform shader u_condensateTransported;
|
|
585
|
+
uniform float u_dt;
|
|
586
|
+
uniform float u_condensationRate;
|
|
587
|
+
uniform float u_condensationFloor;
|
|
588
|
+
uniform float u_evaporationRate;
|
|
589
|
+
uniform float u_totalScale;
|
|
590
|
+
|
|
591
|
+
half4 main(float2 xy) {
|
|
592
|
+
float3 prevVapor = float3(u_transported.eval(float2(xy.x, u_transportedSize.y - xy.y)).rgb);
|
|
593
|
+
float condensateAtXy = float(u_condensateTransported.eval(float2(xy.x, u_condensateTransportedSize.y - xy.y)).r);
|
|
594
|
+
float vaporTotal = prevVapor.r + prevVapor.g + prevVapor.b;
|
|
595
|
+
|
|
596
|
+
// Only the EXCESS of vapor above the background floor condenses, not any nonzero density \u2014
|
|
597
|
+
// otherwise condensate would exactly retrace the vapor spot's shape (the same outline, or
|
|
598
|
+
// softer from blur), leaving the two phases nowhere to visually diverge: vapor is already soft,
|
|
599
|
+
// and condensate needs to read brighter and sharper than it. The floor concentrates condensate
|
|
600
|
+
// in dense turbulence cores, leaving the sparse vapor around it uncondensed \u2014 that's where the
|
|
601
|
+
// contrast comes from.
|
|
602
|
+
float excess = max(0.0, vaporTotal - u_condensationFloor);
|
|
603
|
+
float condensedAmount = excess * (1.0 - exp(-u_condensationRate * u_dt));
|
|
604
|
+
float lossFraction = vaporTotal > 1e-6 ? condensedAmount / vaporTotal : 0.0;
|
|
605
|
+
float3 afterLoss = prevVapor * (1.0 - lossFraction);
|
|
606
|
+
|
|
607
|
+
float condKeep = exp(-u_evaporationRate * u_dt);
|
|
608
|
+
float returned = condensateAtXy * (1.0 - condKeep);
|
|
609
|
+
|
|
610
|
+
// Evaporated water is distributed by the CURRENT vapor proportion \u2014 the condensation/
|
|
611
|
+
// evaporation cycle doesn't shift the hue, water only oscillates between phases (color belongs
|
|
612
|
+
// to vapor).
|
|
613
|
+
float sumAfterLoss = afterLoss.r + afterLoss.g + afterLoss.b;
|
|
614
|
+
float3 hueShare = sumAfterLoss > 1e-6 ? afterLoss / sumAfterLoss : float3(1.0 / 3.0);
|
|
615
|
+
float3 vapor = (afterLoss + hueShare * returned) * u_totalScale;
|
|
616
|
+
return half4(half3(vapor), half(1.0));
|
|
617
|
+
}
|
|
618
|
+
`;
|
|
619
|
+
var MEDIUM_CONDENSATE_REACT_SHADER = `
|
|
620
|
+
uniform shader u_transported;
|
|
621
|
+
uniform shader u_vaporTransported;
|
|
622
|
+
uniform shader u_dye;
|
|
623
|
+
uniform float u_dt;
|
|
624
|
+
uniform float u_condensationRate;
|
|
625
|
+
uniform float u_condensationFloor;
|
|
626
|
+
uniform float u_evaporationRate;
|
|
627
|
+
uniform float u_spreadRate;
|
|
628
|
+
uniform float u_totalScale;
|
|
629
|
+
|
|
630
|
+
half4 main(float2 xy) {
|
|
631
|
+
float prevCondensate = float(u_transported.eval(float2(xy.x, u_transportedSize.y - xy.y)).r);
|
|
632
|
+
float3 vaporAtXy = float3(u_vaporTransported.eval(float2(xy.x, u_vaporTransportedSize.y - xy.y)).rgb);
|
|
633
|
+
float vaporTotal = vaporAtXy.r + vaporAtXy.g + vaporAtXy.b;
|
|
634
|
+
|
|
635
|
+
float excess = max(0.0, vaporTotal - u_condensationFloor);
|
|
636
|
+
float condensed = excess * (1.0 - exp(-u_condensationRate * u_dt));
|
|
637
|
+
float condKeep = exp(-u_evaporationRate * u_dt);
|
|
638
|
+
float afterReaction = prevCondensate * condKeep + condensed;
|
|
639
|
+
|
|
640
|
+
// Growth is an Euler step separate from transport: the neighbors are sampled at THIS cell's own
|
|
641
|
+
// xy in the ORIGINAL (pre-transport-this-frame) field u_dye, not the already
|
|
642
|
+
// transported/corrected value. On a non-uniform flow, warping compresses the domain in some
|
|
643
|
+
// places and stretches it in others, and blurring at a warped point stacks those distortions \u2014
|
|
644
|
+
// the error accumulates frame over frame (measured: condensate steadily gained about 5% extra
|
|
645
|
+
// water alongside its own mass growth). Right on the grid, with no warp, the same blur is an
|
|
646
|
+
// honest averaging kernel that conserves the sum on a periodic domain.
|
|
647
|
+
float2 selfSrc = float2(xy.x, u_dyeSize.y - xy.y);
|
|
648
|
+
float n0 = float(u_dye.eval(float2(selfSrc.x + 1.0, selfSrc.y)).r);
|
|
649
|
+
float n1 = float(u_dye.eval(float2(selfSrc.x - 1.0, selfSrc.y)).r);
|
|
650
|
+
float n2 = float(u_dye.eval(float2(selfSrc.x, selfSrc.y - 1.0)).r);
|
|
651
|
+
float n3 = float(u_dye.eval(float2(selfSrc.x, selfSrc.y + 1.0)).r);
|
|
652
|
+
float neighborAvg = (n0 + n1 + n2 + n3) * 0.25;
|
|
653
|
+
float spreadWeight = 1.0 - exp(-u_spreadRate * u_dt);
|
|
654
|
+
float grown = mix(afterReaction, neighborAvg, spreadWeight) * u_totalScale;
|
|
655
|
+
|
|
656
|
+
return half4(half3(grown, 0.0, 0.0), half(1.0));
|
|
657
|
+
}
|
|
658
|
+
`;
|
|
659
|
+
var MEDIUM_TRACK_ADVECT_SHADER = `
|
|
660
|
+
${MEDIUM_ADVECT_UNIFORMS}
|
|
661
|
+
uniform float u_decay;
|
|
662
|
+
|
|
663
|
+
half4 main(float2 xy) {
|
|
664
|
+
${MEDIUM_ADVECT_VELOCITY}
|
|
665
|
+
float2 back = xy - vel * u_dt;
|
|
666
|
+
half4 prev = u_dye.eval(float2(back.x, u_dyeSize.y - back.y));
|
|
667
|
+
float3 decayed = float3(prev.rgb) * exp(-u_decay * u_dt);
|
|
668
|
+
return half4(half3(decayed), half(1.0));
|
|
669
|
+
}
|
|
670
|
+
`;
|
|
671
|
+
|
|
672
|
+
// src/medium/composite-shader.ts
|
|
673
|
+
var MEDIUM_COMPOSITE_SHADER = `
|
|
674
|
+
uniform shader u_vapor;
|
|
675
|
+
uniform shader u_condensate;
|
|
676
|
+
uniform shader u_track;
|
|
677
|
+
uniform float2 u_dyeScale;
|
|
678
|
+
uniform float3 u_lab0;
|
|
679
|
+
uniform float3 u_lab1;
|
|
680
|
+
uniform float3 u_lab2;
|
|
681
|
+
uniform float3 u_labBg;
|
|
682
|
+
uniform float3 u_labCondensate;
|
|
683
|
+
uniform float u_baseWeight;
|
|
684
|
+
uniform float u_condensateTint;
|
|
685
|
+
uniform float u_condensateGain;
|
|
686
|
+
|
|
687
|
+
${VG_OKLAB_TO_SRGB}
|
|
688
|
+
|
|
689
|
+
half4 main(float2 xy) {
|
|
690
|
+
float2 src = xy * u_dyeScale;
|
|
691
|
+
half4 vapor = u_vapor.eval(src);
|
|
692
|
+
half4 condensate = u_condensate.eval(src);
|
|
693
|
+
half4 track = u_track.eval(src);
|
|
694
|
+
float w0 = float(vapor.r) + float(track.r);
|
|
695
|
+
float w1 = float(vapor.g) + float(track.g);
|
|
696
|
+
float w2 = float(vapor.b) + float(track.b);
|
|
697
|
+
float wBg = u_baseWeight;
|
|
698
|
+
|
|
699
|
+
// A faint condensate hint from the LOCAL vapor, not its own color (droplets scatter light
|
|
700
|
+
// rather than tint it) \u2014 a fraction of the way from white to the vapor tone sampled right here.
|
|
701
|
+
float vaporLocal = max(w0 + w1 + w2, 1e-4);
|
|
702
|
+
float3 vaporHue = (u_lab0 * w0 + u_lab1 * w1 + u_lab2 * w2) / vaporLocal;
|
|
703
|
+
float3 condensateLab = mix(u_labCondensate, vaporHue, u_condensateTint);
|
|
704
|
+
float wCondensate = float(condensate.r) * u_condensateGain;
|
|
705
|
+
|
|
706
|
+
float total = max(w0 + w1 + w2 + wBg + wCondensate, 1e-4);
|
|
707
|
+
float3 mixLab =
|
|
708
|
+
(u_labBg * wBg + u_lab0 * w0 + u_lab1 * w1 + u_lab2 * w2 + condensateLab * wCondensate) / total;
|
|
709
|
+
float3 rgbLinear = clamp(vgOklabToLinear(mixLab), float3(0.0), float3(1.0));
|
|
710
|
+
float3 srgb = vgLinearToSrgb(rgbLinear);
|
|
711
|
+
return half4(half3(srgb), half(1.0));
|
|
712
|
+
}
|
|
713
|
+
`;
|
|
714
|
+
|
|
715
|
+
// src/medium/emit-shader.ts
|
|
716
|
+
var MEDIUM_EMIT_SHADER = `
|
|
717
|
+
uniform float2 u_source;
|
|
718
|
+
uniform float2 u_head;
|
|
719
|
+
uniform float u_headWidth;
|
|
720
|
+
uniform float u_tailWidth;
|
|
721
|
+
uniform float u_raggedAmp;
|
|
722
|
+
uniform float u_raggedFreq;
|
|
723
|
+
uniform float u_settle;
|
|
724
|
+
uniform float u_tailDim;
|
|
725
|
+
uniform float u_intensity;
|
|
726
|
+
uniform float u_seed;
|
|
727
|
+
uniform float3 u_channelMask;
|
|
728
|
+
|
|
729
|
+
${VG_VALUE_NOISE}
|
|
730
|
+
|
|
731
|
+
half4 main(float2 xy) {
|
|
732
|
+
float2 dir = u_head - u_source;
|
|
733
|
+
float len = max(length(dir), 1.0);
|
|
734
|
+
float2 side = float2(-dir.y, dir.x) / len;
|
|
735
|
+
|
|
736
|
+
float best = 0.0;
|
|
737
|
+
const int STEPS = 12;
|
|
738
|
+
for (int i = 0; i < STEPS; i++) {
|
|
739
|
+
float s = float(i) / float(STEPS - 1);
|
|
740
|
+
float tailness = 1.0 - s;
|
|
741
|
+
float ragged = u_raggedAmp * tailness * vgValueNoise(float2(s * u_raggedFreq + u_seed, u_seed * 3.7));
|
|
742
|
+
float2 p = mix(u_source, u_head, s) + side * ragged + float2(0.0, u_settle * tailness * tailness);
|
|
743
|
+
float w = max(mix(u_tailWidth, u_headWidth, s), 0.5);
|
|
744
|
+
float amp = mix(u_tailDim, 1.0, s);
|
|
745
|
+
float d = length(xy - p);
|
|
746
|
+
float v = amp * exp(-(d * d) / (2.0 * w * w));
|
|
747
|
+
best = max(best, v);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
float3 dye = u_channelMask * (best * u_intensity);
|
|
751
|
+
return half4(half3(dye), half(1.0));
|
|
752
|
+
}
|
|
753
|
+
`;
|
|
754
|
+
|
|
755
|
+
// src/medium/dynamics.ts
|
|
756
|
+
function hash01(n) {
|
|
757
|
+
const s = Math.sin(n * 12.9898) * 43758.5453;
|
|
758
|
+
return s - Math.floor(s);
|
|
759
|
+
}
|
|
760
|
+
function channelFromSeed(seed) {
|
|
761
|
+
const idx = Math.floor(hash01(seed * 1.37) * 3) % 3;
|
|
762
|
+
return idx === 0 ? [1, 0, 0] : idx === 1 ? [0, 1, 0] : [0, 0, 1];
|
|
763
|
+
}
|
|
764
|
+
function buildEmission(presetName, source, seed, intensityScale) {
|
|
765
|
+
const preset = MEDIUM_TRACK_PRESETS[presetName];
|
|
766
|
+
return {
|
|
767
|
+
...preset,
|
|
768
|
+
intensity: preset.intensity * intensityScale,
|
|
769
|
+
source,
|
|
770
|
+
angle: hash01(seed * 7.31) * Math.PI * 2,
|
|
771
|
+
seed,
|
|
772
|
+
channel: channelFromSeed(seed)
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function createMediumDynamics() {
|
|
776
|
+
let turbulence = MEDIUM_DRIFT_TURBULENCE;
|
|
777
|
+
let lastBar = -1;
|
|
778
|
+
let idleClock = 0;
|
|
779
|
+
let naturalIndex = 0;
|
|
780
|
+
let nextNaturalAt = MEDIUM_NATURAL_INTERVAL * 0.5;
|
|
781
|
+
let simTime = 0;
|
|
782
|
+
let lastEmissionAt = -Infinity;
|
|
783
|
+
function step(input) {
|
|
784
|
+
const { state, bpm, amplitude, positionSeconds, sourcePoint, dt } = input;
|
|
785
|
+
simTime += dt;
|
|
786
|
+
const amp = Math.max(0, Math.min(1, amplitude));
|
|
787
|
+
const target = state === "playing" ? MEDIUM_PLAYING_TURBULENCE_RANGE[0] + (MEDIUM_PLAYING_TURBULENCE_RANGE[1] - MEDIUM_PLAYING_TURBULENCE_RANGE[0]) * amp : MEDIUM_DRIFT_TURBULENCE;
|
|
788
|
+
turbulence += (target - turbulence) * (1 - Math.exp(-dt / MEDIUM_TURBULENCE_TAU));
|
|
789
|
+
const emissions = [];
|
|
790
|
+
if (state === "playing" && bpm > 0) {
|
|
791
|
+
const beats = positionSeconds * bpm / 60;
|
|
792
|
+
const bar = Math.floor(beats / MEDIUM_EMISSION_BEATS);
|
|
793
|
+
if (bar !== lastBar) {
|
|
794
|
+
lastBar = bar;
|
|
795
|
+
const eligible = hash01(bar * 0.618) < MEDIUM_EMISSION_PROBABILITY && simTime - lastEmissionAt >= MEDIUM_MIN_EMISSION_INTERVAL;
|
|
796
|
+
if (eligible) {
|
|
797
|
+
lastEmissionAt = simTime;
|
|
798
|
+
const presetName = hash01(bar * 3.14) < 0.55 ? "alpha" : "beta";
|
|
799
|
+
emissions.push(buildEmission(presetName, sourcePoint, bar, 0.6 + 0.4 * amp));
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
} else if (state === "idle") {
|
|
803
|
+
idleClock += dt;
|
|
804
|
+
if (idleClock >= nextNaturalAt) {
|
|
805
|
+
const idx = naturalIndex;
|
|
806
|
+
naturalIndex += 1;
|
|
807
|
+
const randomSource = [
|
|
808
|
+
hash01(idx * 2.1 + 0.5),
|
|
809
|
+
MEDIUM_SENSITIVE_TOP + hash01(idx * 5.9 + 1.7) * (MEDIUM_SENSITIVE_BOTTOM - MEDIUM_SENSITIVE_TOP)
|
|
810
|
+
];
|
|
811
|
+
emissions.push(buildEmission("natural", randomSource, idx, 1));
|
|
812
|
+
nextNaturalAt = idleClock + MEDIUM_NATURAL_INTERVAL + hash01(idx * 1.7) * MEDIUM_NATURAL_JITTER;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return { turbulence, emissions };
|
|
816
|
+
}
|
|
817
|
+
return { step };
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
exports.ATOM_AT_REST = ATOM_AT_REST;
|
|
821
|
+
exports.DEFAULT_GROWTH_TIMING = DEFAULT_GROWTH_TIMING;
|
|
822
|
+
exports.LINEAR_TO_LMS = LINEAR_TO_LMS;
|
|
823
|
+
exports.LMS_TO_LINEAR = LMS_TO_LINEAR;
|
|
824
|
+
exports.LMS_TO_OKLAB = LMS_TO_OKLAB;
|
|
825
|
+
exports.MEDIUM_COMPOSITE_SHADER = MEDIUM_COMPOSITE_SHADER;
|
|
826
|
+
exports.MEDIUM_CONDENSATE_CORRECT_SHADER = MEDIUM_CONDENSATE_CORRECT_SHADER;
|
|
827
|
+
exports.MEDIUM_CONDENSATE_FORWARD_SHADER = MEDIUM_CONDENSATE_FORWARD_SHADER;
|
|
828
|
+
exports.MEDIUM_CONDENSATE_REACT_SHADER = MEDIUM_CONDENSATE_REACT_SHADER;
|
|
829
|
+
exports.MEDIUM_DEFAULTS = MEDIUM_DEFAULTS;
|
|
830
|
+
exports.MEDIUM_DEFAULT_CELL_PX = MEDIUM_DEFAULT_CELL_PX;
|
|
831
|
+
exports.MEDIUM_DRIFT_TURBULENCE = MEDIUM_DRIFT_TURBULENCE;
|
|
832
|
+
exports.MEDIUM_EMISSION_BEATS = MEDIUM_EMISSION_BEATS;
|
|
833
|
+
exports.MEDIUM_EMISSION_PROBABILITY = MEDIUM_EMISSION_PROBABILITY;
|
|
834
|
+
exports.MEDIUM_EMIT_SHADER = MEDIUM_EMIT_SHADER;
|
|
835
|
+
exports.MEDIUM_MIN_EMISSION_INTERVAL = MEDIUM_MIN_EMISSION_INTERVAL;
|
|
836
|
+
exports.MEDIUM_NATURAL_INTERVAL = MEDIUM_NATURAL_INTERVAL;
|
|
837
|
+
exports.MEDIUM_NATURAL_JITTER = MEDIUM_NATURAL_JITTER;
|
|
838
|
+
exports.MEDIUM_PLAYING_TURBULENCE_RANGE = MEDIUM_PLAYING_TURBULENCE_RANGE;
|
|
839
|
+
exports.MEDIUM_PROBE_FLOOR_PX = MEDIUM_PROBE_FLOOR_PX;
|
|
840
|
+
exports.MEDIUM_RESAMPLE_SHADER = MEDIUM_RESAMPLE_SHADER;
|
|
841
|
+
exports.MEDIUM_SEED_SHADER = MEDIUM_SEED_SHADER;
|
|
842
|
+
exports.MEDIUM_SENSITIVE_BOTTOM = MEDIUM_SENSITIVE_BOTTOM;
|
|
843
|
+
exports.MEDIUM_SENSITIVE_TOP = MEDIUM_SENSITIVE_TOP;
|
|
844
|
+
exports.MEDIUM_SPECIES_HUE_SPREAD = MEDIUM_SPECIES_HUE_SPREAD;
|
|
845
|
+
exports.MEDIUM_SPECIES_LIGHTNESS = MEDIUM_SPECIES_LIGHTNESS;
|
|
846
|
+
exports.MEDIUM_TIME_PERIOD = MEDIUM_TIME_PERIOD;
|
|
847
|
+
exports.MEDIUM_TRACK_ADVECT_SHADER = MEDIUM_TRACK_ADVECT_SHADER;
|
|
848
|
+
exports.MEDIUM_TRACK_PRESETS = MEDIUM_TRACK_PRESETS;
|
|
849
|
+
exports.MEDIUM_TURBULENCE_TAU = MEDIUM_TURBULENCE_TAU;
|
|
850
|
+
exports.MEDIUM_VAPOR_CORRECT_SHADER = MEDIUM_VAPOR_CORRECT_SHADER;
|
|
851
|
+
exports.MEDIUM_VAPOR_FORWARD_SHADER = MEDIUM_VAPOR_FORWARD_SHADER;
|
|
852
|
+
exports.MEDIUM_VAPOR_REACT_SHADER = MEDIUM_VAPOR_REACT_SHADER;
|
|
853
|
+
exports.OKLAB_TO_LMS = OKLAB_TO_LMS;
|
|
854
|
+
exports.VG_CURL_NOISE = VG_CURL_NOISE;
|
|
855
|
+
exports.VG_OKLAB_TO_SRGB = VG_OKLAB_TO_SRGB;
|
|
856
|
+
exports.VG_VALUE_NOISE = VG_VALUE_NOISE;
|
|
857
|
+
exports.atomAtRest = atomAtRest;
|
|
858
|
+
exports.atomLift = atomLift;
|
|
859
|
+
exports.atomMaterial = atomMaterial;
|
|
860
|
+
exports.characterOf = characterOf;
|
|
861
|
+
exports.createGrowthState = createGrowthState;
|
|
862
|
+
exports.createMediumDynamics = createMediumDynamics;
|
|
863
|
+
exports.growthFrame = growthFrame;
|
|
864
|
+
exports.growthInk = growthInk;
|
|
865
|
+
exports.growthPhase = growthPhase;
|
|
866
|
+
exports.neutralSpecies = neutralSpecies;
|
|
867
|
+
exports.oklabToSrgb = oklabToSrgb;
|
|
868
|
+
exports.smoothstep = smoothstep;
|
|
869
|
+
exports.speciesFamily = speciesFamily;
|
|
870
|
+
exports.srgbToOklab = srgbToOklab;
|
|
871
|
+
exports.stepGrowth = stepGrowth;
|
|
872
|
+
exports.stepSpring = stepSpring;
|
|
873
|
+
//# sourceMappingURL=index.cjs.map
|
|
874
|
+
//# sourceMappingURL=index.cjs.map
|