vireuikit 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -7
- package/dist/{chunk-2AFVQ65S.js → chunk-VY22O2II.js} +324 -33
- package/dist/chunk-VY22O2II.js.map +1 -0
- package/dist/{dynamics-7Bq3LLds.d.cts → dynamics-DqGk_f1n.d.cts} +157 -13
- package/dist/{dynamics-7Bq3LLds.d.ts → dynamics-DqGk_f1n.d.ts} +157 -13
- package/dist/index.cjs +291 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -4
- package/dist/index.d.ts +19 -4
- package/dist/index.js +1 -1
- package/dist/web/index.cjs +693 -9
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +63 -2
- package/dist/web/index.d.ts +63 -2
- package/dist/web/index.js +449 -7
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2AFVQ65S.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A UI kit built on top of [VireGlass](https://github.com/pruffit/vireglass)'s physically-derived
|
|
4
4
|
glass material: atom state and press/hover/selection behavior for controls, a capsule-to-target
|
|
5
|
-
growth morph, and a live GPU backdrop —
|
|
6
|
-
into VireGlass's WebGL2 renderer as a backdrop pass.
|
|
5
|
+
growth morph, and a live GPU backdrop — a lit cloud-chamber: dark mist, static lights, and
|
|
6
|
+
condensation tracks — that plugs into VireGlass's WebGL2 renderer as a backdrop pass.
|
|
7
7
|
|
|
8
8
|
**Status: 0.x.** The API can still change between minor versions.
|
|
9
9
|
|
|
@@ -44,11 +44,30 @@ const frame = growthFrame(sourceShape, targetShape, anchorPoint, growthPhase(sta
|
|
|
44
44
|
// frame.body: the shape to draw as glass this frame.
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
## GPU backdrop:
|
|
47
|
+
## GPU backdrop: a lit chamber, not colored smoke
|
|
48
48
|
|
|
49
49
|
`createMediumBackdrop()` returns a `VireGlassBackdropPass` factory: a self-contained fluid
|
|
50
|
-
simulation (curl-noise vapor, condensing droplets, decaying
|
|
51
|
-
into the texture VireGlass's lens samples — no offscreen canvas, no readback.
|
|
50
|
+
simulation (curl-noise vapor, condensing droplets, decaying condensation tracks) that composites
|
|
51
|
+
straight into the texture VireGlass's lens samples — no offscreen canvas, no readback.
|
|
52
|
+
|
|
53
|
+
The chamber itself is dark; what's visible is light scattered by vapor, droplets and tracks, not
|
|
54
|
+
the gas's own color. `lights` (`VireUIKitMediumParams`, up to `MEDIUM_MAX_LIGHTS`) are static
|
|
55
|
+
sources — position, direction, cone half-angle, falloff, color, intensity. The defaults are an edge
|
|
56
|
+
strip along the chamber floor and two spotlights placed above the frame, aimed down across it. A
|
|
57
|
+
spotlight has no drawn edge: its beam is a soft lobe with static shafts fanning out from the lamp,
|
|
58
|
+
dimmed by the gas between the lamp and each point (a grid-resolution shadow pass,
|
|
59
|
+
`shadowExtinction`), so beams show where there is vapor to scatter them. `lightRigForCover`/
|
|
60
|
+
`lightRigForCharacter` tint the spotlights toward a cover's hue like colored gels and leave the
|
|
61
|
+
strip near neutral. Vapor species (`channelColors`) fold into a near-neutral ambient base;
|
|
62
|
+
`channelScatter` is how strongly each species scatters light.
|
|
63
|
+
|
|
64
|
+
Tracks come in three kinds (`MEDIUM_TRACK_PRESETS`): `alpha` (short, thick, thickening toward its
|
|
65
|
+
end — the playing source sprays a burst of them from the cover's position, a few on the beat and
|
|
66
|
+
the rest over the next 1.6 s), `electron` (thin, wiggly) and `muon` (long, straight,
|
|
67
|
+
thin). The calm state schedules a lone electron or muon every 8–20 s from no source at all. Each track is drawn twice: as a soft residue stamped into the simulation grid, and
|
|
68
|
+
as a crisp droplet chain in content pixels (`trackLayerAmount`, `MEDIUM_TRACK_LAYER_LOOK`) that
|
|
69
|
+
appears at full length, then broadens, sags and fades over one to two seconds. Tracks are lit by
|
|
70
|
+
the same lights, plus `trackLightFloor` so one is never fully dark.
|
|
52
71
|
|
|
53
72
|
```ts
|
|
54
73
|
import { createMediumBackdrop } from 'vireuikit/web';
|
|
@@ -92,8 +111,9 @@ cannot affect the conserved water total) that actually produces the "denser at t
|
|
|
92
111
|
Condensate settling adds a second, always-on share of the ambient curl field's own sideways push
|
|
93
112
|
(`condensateSettleWiggle`) so a droplet falling at a constant speed doesn't trace a dead-straight
|
|
94
113
|
vertical line when turbulence is otherwise low. Each emitted track also carries a `depth` (0 far …
|
|
95
|
-
1 near) that scales its own width and intensity, so a track reads thinner, dimmer and
|
|
96
|
-
|
|
114
|
+
1 near) that scales its own width and intensity, so a far track reads thinner, dimmer and finer,
|
|
115
|
+
while the nearest tracks read a little LARGER than the base preset, not merely full-size — a
|
|
116
|
+
Gaussian stamp's edge softness scales with its own width, so the same knob gives depth of field too.
|
|
97
117
|
|
|
98
118
|
The curl-noise potential itself is periodic over the simulation grid in space, the same way it
|
|
99
119
|
already was in time: each octave's spatial frequency is adjusted, per grid size, to the nearest
|
|
@@ -87,9 +87,46 @@ function neutralSpecies(baseColor) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// src/medium/params.ts
|
|
90
|
+
var MEDIUM_MAX_LIGHTS = 3;
|
|
90
91
|
var BASE_COLOR = [0.07, 0.08, 0.1];
|
|
92
|
+
var normalize2 = (v) => {
|
|
93
|
+
const len = Math.hypot(v[0], v[1]) || 1;
|
|
94
|
+
return [v[0] / len, v[1] / len];
|
|
95
|
+
};
|
|
96
|
+
var MEDIUM_DEFAULT_LIGHT_COLOR = [0.78, 0.85, 0.94];
|
|
97
|
+
var MEDIUM_DEFAULT_LIGHTS = [
|
|
98
|
+
{
|
|
99
|
+
position: [0.5, 0],
|
|
100
|
+
direction: [0, 1],
|
|
101
|
+
coneAngle: Math.PI,
|
|
102
|
+
falloff: 16,
|
|
103
|
+
color: MEDIUM_DEFAULT_LIGHT_COLOR,
|
|
104
|
+
intensity: 0.28
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
position: [0.12, 1.45],
|
|
108
|
+
direction: normalize2([0.38, -1.03]),
|
|
109
|
+
coneAngle: 9 * Math.PI / 180,
|
|
110
|
+
falloff: 0.7,
|
|
111
|
+
color: MEDIUM_DEFAULT_LIGHT_COLOR,
|
|
112
|
+
intensity: 1.1
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
position: [0.9, 1.4],
|
|
116
|
+
direction: normalize2([-0.4, -0.98]),
|
|
117
|
+
coneAngle: 8 * Math.PI / 180,
|
|
118
|
+
falloff: 0.75,
|
|
119
|
+
color: MEDIUM_DEFAULT_LIGHT_COLOR,
|
|
120
|
+
intensity: 0.9
|
|
121
|
+
}
|
|
122
|
+
];
|
|
91
123
|
var MEDIUM_DEFAULTS = {
|
|
92
|
-
|
|
124
|
+
// A stamped track has to visibly broaden and fade within the reference's "roughly one to two
|
|
125
|
+
// seconds", not the ~11s half-life a 0.06 decay gives — measured against check-medium.mjs's track
|
|
126
|
+
// gate (birth vs +1.5s cross-section width and total density): 1.1 leaves ~30% of the peak at 1s
|
|
127
|
+
// and ~17% at 1.5s, comfortably faded without dying before MacCormack's own diffusion has time to
|
|
128
|
+
// visibly broaden the stamp.
|
|
129
|
+
decay: 1.1,
|
|
93
130
|
curlFreq: 0.045,
|
|
94
131
|
curlSpeed: 0.12,
|
|
95
132
|
advectSpeed: 26,
|
|
@@ -104,6 +141,7 @@ var MEDIUM_DEFAULTS = {
|
|
|
104
141
|
condensateGain: 5,
|
|
105
142
|
condensateColor: [0.94, 0.95, 0.97],
|
|
106
143
|
channelColors: neutralSpecies(BASE_COLOR),
|
|
144
|
+
channelScatter: [1, 1, 1],
|
|
107
145
|
baseColor: BASE_COLOR,
|
|
108
146
|
baseWeight: 0.9,
|
|
109
147
|
depthFarScale: 1.6,
|
|
@@ -112,7 +150,11 @@ var MEDIUM_DEFAULTS = {
|
|
|
112
150
|
depthFarWeight: 0.5,
|
|
113
151
|
gravityVaporDrift: 0.6,
|
|
114
152
|
gravityBottomBoost: 2,
|
|
115
|
-
gravityBottomBoostStart: 0.75
|
|
153
|
+
gravityBottomBoostStart: 0.75,
|
|
154
|
+
lights: MEDIUM_DEFAULT_LIGHTS,
|
|
155
|
+
shadowExtinction: 3,
|
|
156
|
+
trackLightFloor: 0.8,
|
|
157
|
+
trackLayerAmount: 1
|
|
116
158
|
};
|
|
117
159
|
function depthFarOffsetPx(offsetFrac, gridWidth, gridHeight) {
|
|
118
160
|
return [offsetFrac[0] * gridWidth, offsetFrac[1] * gridHeight];
|
|
@@ -134,7 +176,7 @@ var MEDIUM_TRACK_PRESETS = {
|
|
|
134
176
|
tailDim: 0.55,
|
|
135
177
|
intensity: 1.15
|
|
136
178
|
},
|
|
137
|
-
|
|
179
|
+
electron: {
|
|
138
180
|
lengthFrac: 0.42,
|
|
139
181
|
headWidthFrac: 0.018,
|
|
140
182
|
tailWidthFrac: 0.05,
|
|
@@ -142,17 +184,17 @@ var MEDIUM_TRACK_PRESETS = {
|
|
|
142
184
|
raggedFreq: 5,
|
|
143
185
|
settleFrac: 0.05,
|
|
144
186
|
tailDim: 0.45,
|
|
145
|
-
intensity: 0.
|
|
187
|
+
intensity: 0.6
|
|
146
188
|
},
|
|
147
|
-
|
|
148
|
-
lengthFrac: 0.
|
|
149
|
-
headWidthFrac: 0.
|
|
150
|
-
tailWidthFrac: 0.
|
|
151
|
-
raggedFrac: 0
|
|
152
|
-
raggedFreq:
|
|
153
|
-
settleFrac: 0.
|
|
154
|
-
tailDim: 0.
|
|
155
|
-
intensity: 0.
|
|
189
|
+
muon: {
|
|
190
|
+
lengthFrac: 0.7,
|
|
191
|
+
headWidthFrac: 0.012,
|
|
192
|
+
tailWidthFrac: 0.02,
|
|
193
|
+
raggedFrac: 0,
|
|
194
|
+
raggedFreq: 0,
|
|
195
|
+
settleFrac: 0.01,
|
|
196
|
+
tailDim: 0.75,
|
|
197
|
+
intensity: 0.5
|
|
156
198
|
}
|
|
157
199
|
};
|
|
158
200
|
var MEDIUM_DRIFT_TURBULENCE = 0.045;
|
|
@@ -161,12 +203,60 @@ var MEDIUM_TURBULENCE_TAU = 2.5;
|
|
|
161
203
|
var MEDIUM_EMISSION_BEATS = 4;
|
|
162
204
|
var MEDIUM_EMISSION_PROBABILITY = 0.55;
|
|
163
205
|
var MEDIUM_MIN_EMISSION_INTERVAL = 1.2;
|
|
164
|
-
var MEDIUM_NATURAL_INTERVAL =
|
|
165
|
-
var MEDIUM_NATURAL_JITTER =
|
|
206
|
+
var MEDIUM_NATURAL_INTERVAL = 8;
|
|
207
|
+
var MEDIUM_NATURAL_JITTER = 12;
|
|
166
208
|
var MEDIUM_SENSITIVE_TOP = 0.55;
|
|
167
209
|
var MEDIUM_SENSITIVE_BOTTOM = 0.95;
|
|
168
|
-
var
|
|
210
|
+
var MEDIUM_ALPHA_BURST_RAYS = [8, 16];
|
|
211
|
+
var MEDIUM_ALPHA_RAY_LENGTH_SCALE = [0.55, 1];
|
|
212
|
+
var MEDIUM_TRACK_DEPTH_WIDTH_RANGE = [0.55, 1.2];
|
|
169
213
|
var MEDIUM_TRACK_DEPTH_INTENSITY_RANGE = [0.4, 1];
|
|
214
|
+
var MEDIUM_TRACK_LAYER_LIFE_SECONDS = {
|
|
215
|
+
alpha: 1.8,
|
|
216
|
+
electron: 1.4,
|
|
217
|
+
muon: 2.2
|
|
218
|
+
};
|
|
219
|
+
var MEDIUM_TRACK_LAYER_MAX_TRACKS = 64;
|
|
220
|
+
var MEDIUM_TRACK_LAYER_LOOK = {
|
|
221
|
+
alpha: {
|
|
222
|
+
sigmaFrac: 22e-4,
|
|
223
|
+
endSigmaMul: 1.9,
|
|
224
|
+
broaden: 3.2,
|
|
225
|
+
wiggleFrac: 0,
|
|
226
|
+
beadFrac: 6e-3,
|
|
227
|
+
dropletFrac: 35e-4,
|
|
228
|
+
dropletChance: 0.5,
|
|
229
|
+
startFrac: 0.035,
|
|
230
|
+
gain: 1.8
|
|
231
|
+
},
|
|
232
|
+
electron: {
|
|
233
|
+
sigmaFrac: 9e-4,
|
|
234
|
+
endSigmaMul: 1.3,
|
|
235
|
+
broaden: 6,
|
|
236
|
+
wiggleFrac: 0.012,
|
|
237
|
+
beadFrac: 8e-3,
|
|
238
|
+
dropletFrac: 6e-3,
|
|
239
|
+
dropletChance: 0.55,
|
|
240
|
+
startFrac: 0,
|
|
241
|
+
gain: 2.2
|
|
242
|
+
},
|
|
243
|
+
muon: {
|
|
244
|
+
sigmaFrac: 8e-4,
|
|
245
|
+
endSigmaMul: 1,
|
|
246
|
+
broaden: 5,
|
|
247
|
+
wiggleFrac: 0,
|
|
248
|
+
beadFrac: 7e-3,
|
|
249
|
+
dropletFrac: 5e-3,
|
|
250
|
+
dropletChance: 0.5,
|
|
251
|
+
startFrac: 0,
|
|
252
|
+
gain: 2.2
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
var MEDIUM_TRACK_LAYER_BURST_SPREAD = 1.6;
|
|
256
|
+
var MEDIUM_TRACK_LAYER_BURST_ACCENT = 3;
|
|
257
|
+
var MEDIUM_TRACK_LAYER_FADE_GAMMA = 1.6;
|
|
258
|
+
var MEDIUM_TRACK_LAYER_GRAVITY_SAG = 0.018;
|
|
259
|
+
var MEDIUM_TRACK_LAYER_CURL_PUSH = 0.05;
|
|
170
260
|
|
|
171
261
|
// src/medium/noise.ts
|
|
172
262
|
var VG_VALUE_NOISE = `
|
|
@@ -364,6 +454,51 @@ float2 vgCurlVelocitySeamless(float2 p, float phase, float4 s0, float4 s1, float
|
|
|
364
454
|
return float2(py1 - py0, -(px1 - px0)) / (2.0 * e);
|
|
365
455
|
}
|
|
366
456
|
`;
|
|
457
|
+
function hash31JS(x, y, z) {
|
|
458
|
+
const h = x * 127.1 + y * 311.7 + z * 74.7;
|
|
459
|
+
const s = Math.sin(h) * 43758.5453123;
|
|
460
|
+
return s - Math.floor(s);
|
|
461
|
+
}
|
|
462
|
+
var smoothstep3JS = (f) => f * f * (3 - 2 * f);
|
|
463
|
+
var glslMod = (x, y) => x - y * Math.floor(x / y);
|
|
464
|
+
function vgValueNoise3PeriodicJS(px, py, pz, period) {
|
|
465
|
+
const ix = Math.floor(px);
|
|
466
|
+
const iy = Math.floor(py);
|
|
467
|
+
const iz = Math.floor(pz);
|
|
468
|
+
const ux = smoothstep3JS(px - ix);
|
|
469
|
+
const uy = smoothstep3JS(py - iy);
|
|
470
|
+
const uz = smoothstep3JS(pz - iz);
|
|
471
|
+
const z0 = glslMod(iz, period);
|
|
472
|
+
const z1 = glslMod(iz + 1, period);
|
|
473
|
+
const n000 = hash31JS(ix, iy, z0);
|
|
474
|
+
const n100 = hash31JS(ix + 1, iy, z0);
|
|
475
|
+
const n010 = hash31JS(ix, iy + 1, z0);
|
|
476
|
+
const n110 = hash31JS(ix + 1, iy + 1, z0);
|
|
477
|
+
const n001 = hash31JS(ix, iy, z1);
|
|
478
|
+
const n101 = hash31JS(ix + 1, iy, z1);
|
|
479
|
+
const n011 = hash31JS(ix, iy + 1, z1);
|
|
480
|
+
const n111 = hash31JS(ix + 1, iy + 1, z1);
|
|
481
|
+
const nx00 = n000 + (n100 - n000) * ux;
|
|
482
|
+
const nx10 = n010 + (n110 - n010) * ux;
|
|
483
|
+
const nx01 = n001 + (n101 - n001) * ux;
|
|
484
|
+
const nx11 = n011 + (n111 - n011) * ux;
|
|
485
|
+
const nxy0 = nx00 + (nx10 - nx00) * uy;
|
|
486
|
+
const nxy1 = nx01 + (nx11 - nx01) * uy;
|
|
487
|
+
return (nxy0 + (nxy1 - nxy0) * uz) * 2 - 1;
|
|
488
|
+
}
|
|
489
|
+
function vgPotentialJS(px, py, phase) {
|
|
490
|
+
const [o0, o1, o2, o3] = MEDIUM_TIME_OCTAVES;
|
|
491
|
+
let v = vgValueNoise3PeriodicJS(px * o0.scale, py * o0.scale, phase * o0.rate + o0.offset, mediumOctavePeriod(o0.rate)) * 0.5;
|
|
492
|
+
v += vgValueNoise3PeriodicJS(px * o1.scale, py * o1.scale, phase * o1.rate + o1.offset, mediumOctavePeriod(o1.rate)) * 0.25;
|
|
493
|
+
v += vgValueNoise3PeriodicJS(px * o2.scale, py * o2.scale, phase * o2.rate + o2.offset, mediumOctavePeriod(o2.rate)) * 0.125;
|
|
494
|
+
const macro = vgValueNoise3PeriodicJS(
|
|
495
|
+
px * o3.scale,
|
|
496
|
+
py * o3.scale,
|
|
497
|
+
phase * o3.rate + o3.offset,
|
|
498
|
+
mediumOctavePeriod(o3.rate)
|
|
499
|
+
);
|
|
500
|
+
return v * (0.7 + 0.3 * macro);
|
|
501
|
+
}
|
|
367
502
|
var MIN_SPATIAL_PERIOD = 2;
|
|
368
503
|
function computeMediumSpatialPeriods(gridWidth, gridHeight, curlFreq) {
|
|
369
504
|
const freq = Math.max(curlFreq, 1e-6);
|
|
@@ -634,10 +769,35 @@ half4 ${fnName}(float2 px) {
|
|
|
634
769
|
}
|
|
635
770
|
`;
|
|
636
771
|
}
|
|
772
|
+
var VG_LIGHT = `
|
|
773
|
+
float vgLightAmount(float2 xy, float2 lightPosFrac, float2 lightDir, float cosCone, float falloff, float2 resolution, float seed) {
|
|
774
|
+
float2 rel = xy - lightPosFrac * resolution;
|
|
775
|
+
// A cone of 180\xB0 or more is an edge strip: light spreads from a line, not a point.
|
|
776
|
+
if (cosCone <= -0.999) {
|
|
777
|
+
float depth = max(dot(rel, lightDir), 0.0);
|
|
778
|
+
return exp(-(depth / max(resolution.y, 1.0)) * falloff);
|
|
779
|
+
}
|
|
780
|
+
float dist = length(rel);
|
|
781
|
+
float2 dirN = dist > 1e-4 ? rel / dist : lightDir;
|
|
782
|
+
float cosAngle = dot(dirN, lightDir);
|
|
783
|
+
// A soft lobe with half intensity at the cone angle and no boundary at all: a lamp in haze has
|
|
784
|
+
// no drawn edge.
|
|
785
|
+
float lobe = log(0.5) / log(clamp(cosCone, 0.05, 0.999));
|
|
786
|
+
float edge = pow(max(cosAngle, 0.0), lobe);
|
|
787
|
+
float atten = exp(-(dist / max(resolution.y, 1.0)) * falloff);
|
|
788
|
+
if (edge * atten < 0.001) return 0.0;
|
|
789
|
+
// Static shafts inside the beam: brightness varies with the angle around the lamp, so streaks
|
|
790
|
+
// fan out from the source the way light through dusty air does.
|
|
791
|
+
float angle = atan(dirN.x * lightDir.y - dirN.y * lightDir.x, cosAngle);
|
|
792
|
+
float shafts = 0.6 + 0.32 * vgValueNoise(float2(angle * 14.0, seed)) + 0.18 * vgValueNoise(float2(angle * 33.0, seed + 5.3));
|
|
793
|
+
return edge * atten * clamp(shafts, 0.0, 1.0);
|
|
794
|
+
}
|
|
795
|
+
`;
|
|
637
796
|
var MEDIUM_COMPOSITE_SHADER = `
|
|
638
797
|
uniform shader u_vapor;
|
|
639
798
|
uniform shader u_condensate;
|
|
640
799
|
uniform shader u_track;
|
|
800
|
+
uniform shader u_shadow;
|
|
641
801
|
uniform float2 u_dyeScale;
|
|
642
802
|
uniform float3 u_lab0;
|
|
643
803
|
uniform float3 u_lab1;
|
|
@@ -653,11 +813,33 @@ uniform float u_farBlurRadius;
|
|
|
653
813
|
uniform float u_farWeight;
|
|
654
814
|
uniform float u_gravityBoost;
|
|
655
815
|
uniform float u_gravityBoostStart;
|
|
816
|
+
uniform float3 u_channelScatter;
|
|
817
|
+
uniform float2 u_light0Pos;
|
|
818
|
+
uniform float2 u_light0Dir;
|
|
819
|
+
uniform float u_light0CosCone;
|
|
820
|
+
uniform float u_light0Falloff;
|
|
821
|
+
uniform float3 u_light0Color;
|
|
822
|
+
uniform float u_light0Intensity;
|
|
823
|
+
uniform float2 u_light1Pos;
|
|
824
|
+
uniform float2 u_light1Dir;
|
|
825
|
+
uniform float u_light1CosCone;
|
|
826
|
+
uniform float u_light1Falloff;
|
|
827
|
+
uniform float3 u_light1Color;
|
|
828
|
+
uniform float u_light1Intensity;
|
|
829
|
+
uniform float2 u_light2Pos;
|
|
830
|
+
uniform float2 u_light2Dir;
|
|
831
|
+
uniform float u_light2CosCone;
|
|
832
|
+
uniform float u_light2Falloff;
|
|
833
|
+
uniform float3 u_light2Color;
|
|
834
|
+
uniform float u_light2Intensity;
|
|
835
|
+
uniform float u_trackLightFloor;
|
|
656
836
|
|
|
657
837
|
${VG_OKLAB_TO_SRGB}
|
|
658
838
|
${VG_CUBIC_WEIGHTS}
|
|
659
839
|
${vgBicubicSampler("vgBicubicVapor", "u_vapor")}
|
|
660
840
|
${vgBicubicSampler("vgBicubicCondensate", "u_condensate")}
|
|
841
|
+
${VG_VALUE_NOISE}
|
|
842
|
+
${VG_LIGHT}
|
|
661
843
|
|
|
662
844
|
half4 main(float2 xy) {
|
|
663
845
|
float2 baseSrc = xy * u_dyeScale;
|
|
@@ -725,7 +907,34 @@ half4 main(float2 xy) {
|
|
|
725
907
|
float total = max(w0 + w1 + w2 + wBg + wCondensate, 1e-4);
|
|
726
908
|
float3 mixLab =
|
|
727
909
|
(u_labBg * wBg + u_lab0 * w0 + u_lab1 * w1 + u_lab2 * w2 + condensateLab * wCondensate) / total;
|
|
728
|
-
|
|
910
|
+
// The dark ambient base \u2014 this is where the OLD mechanism stops (see the file header on
|
|
911
|
+
// VG_LIGHT): unclamped here, clamped once at the very end, after the lit layer adds in.
|
|
912
|
+
float3 rgbLinearBase = vgOklabToLinear(mixLab);
|
|
913
|
+
|
|
914
|
+
// Spotlights reach this point dimmed by the gas in front of them (shadow-shader.ts).
|
|
915
|
+
half4 shadow = u_shadow.eval(baseSrc);
|
|
916
|
+
float light0 = vgLightAmount(xy, u_light0Pos, u_light0Dir, u_light0CosCone, u_light0Falloff, u_resolution, 1.7) * u_light0Intensity;
|
|
917
|
+
float light1 = vgLightAmount(xy, u_light1Pos, u_light1Dir, u_light1CosCone, u_light1Falloff, u_resolution, 4.1) * u_light1Intensity
|
|
918
|
+
* float(shadow.r);
|
|
919
|
+
float light2 = vgLightAmount(xy, u_light2Pos, u_light2Dir, u_light2CosCone, u_light2Falloff, u_resolution, 8.3) * u_light2Intensity
|
|
920
|
+
* float(shadow.g);
|
|
921
|
+
float3 illum = u_light0Color * light0 + u_light1Color * light1 + u_light2Color * light2;
|
|
922
|
+
|
|
923
|
+
float totalScatter = gasR * u_channelScatter.x + gasG * u_channelScatter.y + gasB * u_channelScatter.z;
|
|
924
|
+
float fogDensity = totalScatter + wCondensate;
|
|
925
|
+
float mist = 0.006 + fogDensity * 0.2;
|
|
926
|
+
|
|
927
|
+
// TRACKS: lit the same way as the mist, plus a floor so a track already on screen never vanishes
|
|
928
|
+
// entirely just because it drifted out of a cone (a fresh ionization trail reads brighter than
|
|
929
|
+
// ambient mist in a real chamber). The floor is added to ITS OWN color, not multiplied into illum:
|
|
930
|
+
// illum is exactly [0,0,0] in a fully unlit spot (every light's amount is 0 there), so a floor
|
|
931
|
+
// multiplied by illum would vanish exactly where it exists to help \u2014 it needs a color of its own
|
|
932
|
+
// (a neutral, self-luminous glow) rather than borrowing whichever light happens to be nearby.
|
|
933
|
+
float trackDensity = max(float(track.r), max(float(track.g), float(track.b)));
|
|
934
|
+
float3 trackGlow = (1.0 - exp(-trackDensity * 0.4)) * (illum + float3(u_trackLightFloor)) * 0.08;
|
|
935
|
+
|
|
936
|
+
float3 rgbLinear = rgbLinearBase * 0.025 + illum * mist + trackGlow;
|
|
937
|
+
rgbLinear = float3(1.0) - exp(-rgbLinear * 1.0);
|
|
729
938
|
float3 srgb = vgLinearToSrgb(rgbLinear);
|
|
730
939
|
return half4(half3(srgb), half(1.0));
|
|
731
940
|
}
|
|
@@ -743,7 +952,6 @@ uniform float u_settle;
|
|
|
743
952
|
uniform float u_tailDim;
|
|
744
953
|
uniform float u_intensity;
|
|
745
954
|
uniform float u_seed;
|
|
746
|
-
uniform float3 u_channelMask;
|
|
747
955
|
|
|
748
956
|
${VG_VALUE_NOISE}
|
|
749
957
|
|
|
@@ -766,20 +974,46 @@ half4 main(float2 xy) {
|
|
|
766
974
|
best = max(best, v);
|
|
767
975
|
}
|
|
768
976
|
|
|
769
|
-
float3 dye =
|
|
977
|
+
float3 dye = float3(best * u_intensity);
|
|
770
978
|
return half4(half3(dye), half(1.0));
|
|
771
979
|
}
|
|
772
980
|
`;
|
|
773
981
|
|
|
982
|
+
// src/medium/shadow-shader.ts
|
|
983
|
+
var MEDIUM_SHADOW_SHADER = `
|
|
984
|
+
uniform shader u_vapor;
|
|
985
|
+
uniform shader u_condensate;
|
|
986
|
+
uniform float u_condensateGain;
|
|
987
|
+
uniform float2 u_light1Grid;
|
|
988
|
+
uniform float2 u_light2Grid;
|
|
989
|
+
uniform float u_extinction;
|
|
990
|
+
|
|
991
|
+
float vgOpticalDepth(float2 p, float2 lightP) {
|
|
992
|
+
float2 toLight = lightP - p;
|
|
993
|
+
float optical = 0.0;
|
|
994
|
+
for (int i = 1; i <= 24; i++) {
|
|
995
|
+
float2 q = p + toLight * (float(i) / 24.0);
|
|
996
|
+
float inside = step(0.0, q.x) * step(q.x, u_resolution.x) * step(0.0, q.y) * step(q.y, u_resolution.y);
|
|
997
|
+
half4 v = u_vapor.eval(q);
|
|
998
|
+
half4 c = u_condensate.eval(q);
|
|
999
|
+
optical += inside * (float(v.r + v.g + v.b) + float(c.r) * u_condensateGain);
|
|
1000
|
+
}
|
|
1001
|
+
return optical * length(toLight) / 24.0 / max(u_resolution.y, 1.0);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
half4 main(float2 xy) {
|
|
1005
|
+
float2 p = float2(xy.x, u_resolution.y - xy.y);
|
|
1006
|
+
float t1 = exp(-vgOpticalDepth(p, u_light1Grid) * u_extinction);
|
|
1007
|
+
float t2 = exp(-vgOpticalDepth(p, u_light2Grid) * u_extinction);
|
|
1008
|
+
return half4(half(t1), half(t2), half(0.0), half(1.0));
|
|
1009
|
+
}
|
|
1010
|
+
`;
|
|
1011
|
+
|
|
774
1012
|
// src/medium/dynamics.ts
|
|
775
1013
|
function hash01(n) {
|
|
776
1014
|
const s = Math.sin(n * 12.9898) * 43758.5453;
|
|
777
1015
|
return s - Math.floor(s);
|
|
778
1016
|
}
|
|
779
|
-
function channelFromSeed(seed) {
|
|
780
|
-
const idx = Math.floor(hash01(seed * 1.37) * 3) % 3;
|
|
781
|
-
return idx === 0 ? [1, 0, 0] : idx === 1 ? [0, 1, 0] : [0, 0, 1];
|
|
782
|
-
}
|
|
783
1017
|
function lerp(range, t) {
|
|
784
1018
|
return range[0] + (range[1] - range[0]) * t;
|
|
785
1019
|
}
|
|
@@ -793,18 +1027,19 @@ function applyTrackDepth(preset, depth) {
|
|
|
793
1027
|
intensity: preset.intensity * intensityScale
|
|
794
1028
|
};
|
|
795
1029
|
}
|
|
796
|
-
function buildEmission(presetName, source, seed, intensityScale) {
|
|
1030
|
+
function buildEmission(presetName, source, seed, intensityScale, angleOverride, lengthScale = 1) {
|
|
797
1031
|
const preset = MEDIUM_TRACK_PRESETS[presetName];
|
|
798
1032
|
const depth = hash01(seed * 4.63);
|
|
799
1033
|
const depthApplied = applyTrackDepth(preset, depth);
|
|
800
1034
|
return {
|
|
801
1035
|
...depthApplied,
|
|
1036
|
+
lengthFrac: depthApplied.lengthFrac * lengthScale,
|
|
802
1037
|
intensity: depthApplied.intensity * intensityScale,
|
|
803
1038
|
source,
|
|
804
|
-
angle: hash01(seed * 7.31) * Math.PI * 2,
|
|
1039
|
+
angle: angleOverride ?? hash01(seed * 7.31) * Math.PI * 2,
|
|
805
1040
|
seed,
|
|
806
|
-
|
|
807
|
-
|
|
1041
|
+
depth,
|
|
1042
|
+
kind: presetName
|
|
808
1043
|
};
|
|
809
1044
|
}
|
|
810
1045
|
function createMediumDynamics() {
|
|
@@ -830,8 +1065,15 @@ function createMediumDynamics() {
|
|
|
830
1065
|
const eligible = hash01(bar * 0.618) < MEDIUM_EMISSION_PROBABILITY && simTime - lastEmissionAt >= MEDIUM_MIN_EMISSION_INTERVAL;
|
|
831
1066
|
if (eligible) {
|
|
832
1067
|
lastEmissionAt = simTime;
|
|
833
|
-
const
|
|
834
|
-
|
|
1068
|
+
const rayCount = Math.round(lerp(MEDIUM_ALPHA_BURST_RAYS, hash01(bar * 9.17)));
|
|
1069
|
+
for (let i = 0; i < rayCount; i += 1) {
|
|
1070
|
+
const raySeed = bar + (i + 1) * 0.0137;
|
|
1071
|
+
const angle = (i + hash01(raySeed * 5.23)) / rayCount * Math.PI * 2;
|
|
1072
|
+
const lengthScale = lerp(MEDIUM_ALPHA_RAY_LENGTH_SCALE, hash01(raySeed * 2.91));
|
|
1073
|
+
emissions.push(
|
|
1074
|
+
buildEmission("alpha", sourcePoint, raySeed, 0.6 + 0.4 * amp, angle, lengthScale)
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
835
1077
|
}
|
|
836
1078
|
}
|
|
837
1079
|
} else if (state === "idle") {
|
|
@@ -843,7 +1085,8 @@ function createMediumDynamics() {
|
|
|
843
1085
|
hash01(idx * 2.1 + 0.5),
|
|
844
1086
|
MEDIUM_SENSITIVE_TOP + hash01(idx * 5.9 + 1.7) * (MEDIUM_SENSITIVE_BOTTOM - MEDIUM_SENSITIVE_TOP)
|
|
845
1087
|
];
|
|
846
|
-
|
|
1088
|
+
const presetName = hash01(idx * 3.14) < 0.5 ? "electron" : "muon";
|
|
1089
|
+
emissions.push(buildEmission(presetName, randomSource, idx, 1));
|
|
847
1090
|
nextNaturalAt = idleClock + MEDIUM_NATURAL_INTERVAL + hash01(idx * 1.7) * MEDIUM_NATURAL_JITTER;
|
|
848
1091
|
}
|
|
849
1092
|
}
|
|
@@ -852,6 +1095,54 @@ function createMediumDynamics() {
|
|
|
852
1095
|
return { step };
|
|
853
1096
|
}
|
|
854
1097
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1098
|
+
// src/medium/light-rig.ts
|
|
1099
|
+
var clamp012 = (v) => Math.min(1, Math.max(0, v));
|
|
1100
|
+
var EDGE_STRIP_CONE_ANGLE = Math.PI - 1e-6;
|
|
1101
|
+
var EDGE_STRIP_TINT_MIX = 0.22;
|
|
1102
|
+
var GEL_LIGHTNESS = 0.7;
|
|
1103
|
+
var LIGHT_HUE_SPREAD = MEDIUM_SPECIES_HUE_SPREAD / 2;
|
|
1104
|
+
function retint(base, hueDeg, chroma, mix) {
|
|
1105
|
+
const [baseL, a, b] = srgbToOklab(base);
|
|
1106
|
+
const l = baseL + (Math.min(baseL, GEL_LIGHTNESS) - baseL) * mix;
|
|
1107
|
+
const rad = hueDeg * Math.PI / 180;
|
|
1108
|
+
const targetA = chroma * Math.cos(rad);
|
|
1109
|
+
const targetB = chroma * Math.sin(rad);
|
|
1110
|
+
const mixedA = a + (targetA - a) * mix;
|
|
1111
|
+
const mixedB = b + (targetB - b) * mix;
|
|
1112
|
+
let lo = 0;
|
|
1113
|
+
let hi = 1;
|
|
1114
|
+
if (inGamut(oklabToSrgb([l, mixedA, mixedB]))) lo = 1;
|
|
1115
|
+
else {
|
|
1116
|
+
for (let i = 0; i < 12; i += 1) {
|
|
1117
|
+
const mid = (lo + hi) / 2;
|
|
1118
|
+
if (inGamut(oklabToSrgb([l, mixedA * mid, mixedB * mid]))) lo = mid;
|
|
1119
|
+
else hi = mid;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const rgb = oklabToSrgb([l, mixedA * lo, mixedB * lo]).map(clamp012);
|
|
1123
|
+
const gain = Math.max(...base) / Math.max(...rgb, 1e-6);
|
|
1124
|
+
return rgb.map((v) => clamp012(v * gain));
|
|
1125
|
+
}
|
|
1126
|
+
var inGamut = (rgb) => rgb.every((v) => v >= -1e-4 && v <= 1 + 1e-4);
|
|
1127
|
+
function lightRigForCharacter(character, baseLights = MEDIUM_DEFAULT_LIGHTS) {
|
|
1128
|
+
const spotlightIndices = [];
|
|
1129
|
+
baseLights.forEach((light, i) => {
|
|
1130
|
+
if (light.coneAngle < EDGE_STRIP_CONE_ANGLE) spotlightIndices.push(i);
|
|
1131
|
+
});
|
|
1132
|
+
const spotlightCount = spotlightIndices.length;
|
|
1133
|
+
return baseLights.map((light, i) => {
|
|
1134
|
+
if (light.coneAngle >= EDGE_STRIP_CONE_ANGLE) {
|
|
1135
|
+
return { ...light, color: retint(light.color, character.hue, character.chroma, EDGE_STRIP_TINT_MIX) };
|
|
1136
|
+
}
|
|
1137
|
+
const slot = spotlightIndices.indexOf(i);
|
|
1138
|
+
const hueOffset = spotlightCount > 1 ? LIGHT_HUE_SPREAD * (2 * slot / (spotlightCount - 1) - 1) : 0;
|
|
1139
|
+
return { ...light, color: retint(light.color, character.hue + hueOffset, character.chroma, 1) };
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
function lightRigForCover(cover, baseLights = MEDIUM_DEFAULT_LIGHTS) {
|
|
1143
|
+
return lightRigForCharacter(characterOf(cover), baseLights);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
export { LINEAR_TO_LMS, LMS_TO_LINEAR, LMS_TO_OKLAB, MEDIUM_ALPHA_BURST_RAYS, MEDIUM_ALPHA_RAY_LENGTH_SCALE, MEDIUM_COMPOSITE_SHADER, MEDIUM_CONDENSATE_CORRECT_SHADER, MEDIUM_CONDENSATE_FORWARD_SHADER, MEDIUM_CONDENSATE_REACT_SHADER, MEDIUM_DEFAULTS, MEDIUM_DEFAULT_CELL_PX, MEDIUM_DEFAULT_LIGHTS, MEDIUM_DRIFT_TURBULENCE, MEDIUM_EMISSION_BEATS, MEDIUM_EMISSION_PROBABILITY, MEDIUM_EMIT_SHADER, MEDIUM_MAX_LIGHTS, MEDIUM_MIN_EMISSION_INTERVAL, MEDIUM_NATURAL_INTERVAL, MEDIUM_NATURAL_JITTER, MEDIUM_PLAYING_TURBULENCE_RANGE, MEDIUM_PROBE_FLOOR_PX, MEDIUM_RESAMPLE_SHADER, MEDIUM_SEED_SHADER, MEDIUM_SENSITIVE_BOTTOM, MEDIUM_SENSITIVE_TOP, MEDIUM_SHADOW_SHADER, MEDIUM_SPECIES_HUE_SPREAD, MEDIUM_SPECIES_LIGHTNESS, MEDIUM_TIME_PERIOD, MEDIUM_TRACK_ADVECT_SHADER, MEDIUM_TRACK_DEPTH_INTENSITY_RANGE, MEDIUM_TRACK_DEPTH_WIDTH_RANGE, MEDIUM_TRACK_LAYER_BURST_ACCENT, MEDIUM_TRACK_LAYER_BURST_SPREAD, MEDIUM_TRACK_LAYER_CURL_PUSH, MEDIUM_TRACK_LAYER_FADE_GAMMA, MEDIUM_TRACK_LAYER_GRAVITY_SAG, MEDIUM_TRACK_LAYER_LIFE_SECONDS, MEDIUM_TRACK_LAYER_LOOK, MEDIUM_TRACK_LAYER_MAX_TRACKS, MEDIUM_TRACK_PRESETS, MEDIUM_TURBULENCE_TAU, MEDIUM_VAPOR_CORRECT_SHADER, MEDIUM_VAPOR_FORWARD_SHADER, MEDIUM_VAPOR_REACT_SHADER, OKLAB_TO_LMS, VG_CURL_NOISE, VG_OKLAB_TO_SRGB, VG_VALUE_NOISE, applyTrackDepth, characterOf, computeMediumSpatialPeriods, createMediumDynamics, depthFarOffsetPx, lightRigForCharacter, lightRigForCover, neutralSpecies, oklabToSrgb, speciesFamily, srgbToOklab, vgPotentialJS, wrappedDistanceFromZero };
|
|
1147
|
+
//# sourceMappingURL=chunk-VY22O2II.js.map
|
|
1148
|
+
//# sourceMappingURL=chunk-VY22O2II.js.map
|