vireglass 0.1.0 → 2.0.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 +83 -9
- package/dist/adapters-BfeAopGO.d.ts +154 -0
- package/dist/adapters-DugC6Qri.d.cts +154 -0
- package/dist/chunk-3PGYF2NK.js +360 -0
- package/dist/chunk-3PGYF2NK.js.map +1 -0
- package/dist/{chunk-HF2AWZEZ.js → chunk-IQOBZGCF.js} +210 -606
- package/dist/chunk-IQOBZGCF.js.map +1 -0
- package/dist/chunk-JF3FK254.js +377 -0
- package/dist/chunk-JF3FK254.js.map +1 -0
- package/dist/chunk-P2ZOODXD.js +58 -0
- package/dist/chunk-P2ZOODXD.js.map +1 -0
- package/dist/chunk-TTOKBARR.js +456 -0
- package/dist/chunk-TTOKBARR.js.map +1 -0
- package/dist/dom.cjs +2055 -0
- package/dist/dom.cjs.map +1 -0
- package/dist/dom.d.cts +347 -0
- package/dist/dom.d.ts +347 -0
- package/dist/dom.js +1082 -0
- package/dist/dom.js.map +1 -0
- package/dist/geometry-GvXtt-_S.d.ts +105 -0
- package/dist/geometry-RhKd7CM5.d.cts +105 -0
- package/dist/group-model-8u4GKrUu.d.cts +194 -0
- package/dist/group-model-vYSmEVia.d.ts +194 -0
- package/dist/index.cjs +737 -244
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -113
- package/dist/index.d.ts +122 -113
- package/dist/index.js +6 -277
- package/dist/index.js.map +1 -1
- package/dist/law.cjs +478 -0
- package/dist/law.cjs.map +1 -0
- package/dist/law.d.cts +543 -0
- package/dist/law.d.ts +543 -0
- package/dist/law.js +3 -0
- package/dist/law.js.map +1 -0
- package/dist/{material-CkUHUBoy.d.cts → material-CnUmdKg1.d.cts} +1 -1
- package/dist/{material-CkUHUBoy.d.ts → material-CnUmdKg1.d.ts} +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/reference.cjs +221 -0
- package/dist/reference.cjs.map +1 -0
- package/dist/reference.d.cts +183 -0
- package/dist/reference.d.ts +183 -0
- package/dist/reference.js +197 -0
- package/dist/reference.js.map +1 -0
- package/dist/web/index.cjs +247 -165
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +4 -29
- package/dist/web/index.d.ts +4 -29
- package/dist/web/index.js +5 -90
- package/dist/web/index.js.map +1 -1
- package/docs/adr-001-rendering.md +288 -0
- package/docs/architecture.md +161 -0
- package/docs/benchmarks.md +191 -0
- package/docs/dom.md +160 -0
- package/docs/law.md +65 -0
- package/docs/material-lab.md +799 -0
- package/docs/platform-parity.md +266 -0
- package/docs/reference.md +397 -0
- package/package.json +24 -3
- package/src/accent.ts +35 -0
- package/src/accessibility.ts +30 -16
- package/src/adapters.ts +71 -3
- package/src/appear.ts +45 -0
- package/src/concentric.ts +66 -0
- package/src/dom/body.ts +98 -0
- package/src/dom/displacement.ts +163 -0
- package/src/dom/group.ts +124 -0
- package/src/dom/index.ts +739 -0
- package/src/dom/map-cache.ts +37 -0
- package/src/dom/nesting.ts +32 -0
- package/src/dom/preferences.ts +53 -0
- package/src/dom/probe.ts +163 -0
- package/src/dom/rim.ts +103 -0
- package/src/dom/scroll-edge.ts +106 -0
- package/src/dom/shadow.ts +56 -0
- package/src/dom/spectral-map.ts +102 -0
- package/src/dom/support.ts +47 -0
- package/src/geometry.ts +41 -14
- package/src/glass-scale.ts +3 -5
- package/src/index.ts +3 -1
- package/src/law.ts +562 -0
- package/src/lens-shader.ts +53 -30
- package/src/native/glass-surface.tsx +8 -14
- package/src/native/provider.tsx +22 -2
- package/src/optics.ts +43 -62
- package/src/reference.ts +9 -0
- package/src/scroll-edge.ts +22 -7
- package/src/sdf.ts +195 -8
- package/src/shadow-tint.ts +23 -0
- package/src/spectral.ts +110 -0
- package/src/surface-shader.ts +8 -6
- package/src/touch-response.ts +42 -27
- package/src/web/index.ts +0 -1
- package/dist/adapters-BPB0D5nr.d.cts +0 -357
- package/dist/adapters-BgSc-smX.d.ts +0 -357
- package/dist/chunk-HF2AWZEZ.js.map +0 -1
|
@@ -1,51 +1,42 @@
|
|
|
1
|
+
import { bevelDp, thicknessDp, rimDp, surfacePadDp, shadowReachDp, bevelFraction, halfMinDp } from './chunk-P2ZOODXD.js';
|
|
2
|
+
import { DERIVE, TOUCH, EPSILON, BEVEL, LENS, BODY, MEDIUM, SPECTRAL, DISPERSION, RIM, ACCENT, MORPH } from './chunk-TTOKBARR.js';
|
|
3
|
+
|
|
1
4
|
// src/optics.ts
|
|
2
5
|
var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
|
|
3
6
|
var clamp01 = (v) => clamp(v, 0, 1);
|
|
4
7
|
var fresnelF0 = (ior) => ((ior - 1) / (ior + 1)) ** 2;
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
var dispersion = (ior) => clamp01((ior - 1) * DISPERSION_PER_IOR);
|
|
13
|
-
var IOR_SPREAD_PER_DISPERSION = 0.045;
|
|
14
|
-
var iorSpread = (ior) => dispersion(ior) * IOR_SPREAD_PER_DISPERSION;
|
|
15
|
-
var absorption = (pathDp) => 1 - Math.exp(-0.017 * Math.max(pathDp, 0));
|
|
8
|
+
var fresnelStrength = (ior) => clamp01(fresnelF0(ior) * DERIVE.fresnelGain);
|
|
9
|
+
var FRESNEL_EXPONENT = DERIVE.fresnelExponent;
|
|
10
|
+
var refractionStrength = (ior) => clamp01((ior - 1) / DERIVE.iorFullBend);
|
|
11
|
+
var refractionScale = (ior, thicknessDp2) => 1 + DERIVE.magnifyPerDp * thicknessDp2 * (1 - 1 / Math.max(ior, 1));
|
|
12
|
+
var dispersion = (ior) => clamp01((ior - 1) * DERIVE.dispersionPerIor);
|
|
13
|
+
var iorSpread = (ior) => dispersion(ior) * DERIVE.iorSpreadPerDispersion;
|
|
14
|
+
var absorption = (pathDp) => 1 - Math.exp(-DERIVE.absorbPerDp * Math.max(pathDp, 0));
|
|
16
15
|
var edgeDensity = (thicknessDp2, bevelDp2) => {
|
|
17
16
|
const body = absorption(thicknessDp2);
|
|
18
17
|
if (body <= 0) return 1;
|
|
19
|
-
return clamp(absorption(thicknessDp2 + bevelDp2) / body, 1,
|
|
18
|
+
return clamp(absorption(thicknessDp2 + bevelDp2) / body, 1, DERIVE.edgeDensityMax);
|
|
20
19
|
};
|
|
21
|
-
var
|
|
22
|
-
var
|
|
23
|
-
var
|
|
24
|
-
var
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
r: a.r + (b.r - a.r) * t,
|
|
30
|
-
g: a.g + (b.g - a.g) * t,
|
|
31
|
-
b: a.b + (b.b - a.b) * t
|
|
32
|
-
});
|
|
20
|
+
var blur = (roughness) => clamp01(roughness) * DERIVE.blurMaxDp;
|
|
21
|
+
var specularPower = (roughness) => DERIVE.specularPowerSmooth - clamp01(roughness) * (DERIVE.specularPowerSmooth - DERIVE.specularPowerRough);
|
|
22
|
+
var specularStrength = (ior, roughness) => clamp01(fresnelStrength(ior) * (1 - clamp01(roughness) * DERIVE.specularRoughDamping));
|
|
23
|
+
var mixHue = (a, b, t) => [
|
|
24
|
+
a[0] + (b[0] - a[0]) * t,
|
|
25
|
+
a[1] + (b[1] - a[1]) * t,
|
|
26
|
+
a[2] + (b[2] - a[2]) * t
|
|
27
|
+
];
|
|
33
28
|
function mediumTint(ior) {
|
|
34
|
-
const t = clamp01((ior -
|
|
35
|
-
const hue = t < 0.5 ? mixHue(
|
|
36
|
-
const lift =
|
|
37
|
-
return { r: clamp01(hue
|
|
29
|
+
const t = clamp01((ior - DERIVE.hueFromIor) / DERIVE.hueSpan);
|
|
30
|
+
const hue = t < 0.5 ? mixHue(DERIVE.hueCool, DERIVE.hueNeutral, t * 2) : mixHue(DERIVE.hueNeutral, DERIVE.hueWarm, (t - 0.5) * 2);
|
|
31
|
+
const lift = DERIVE.bodyLiftBase + fresnelF0(ior) * DERIVE.bodyLiftPerF0;
|
|
32
|
+
return { r: clamp01(hue[0] * lift), g: clamp01(hue[1] * lift), b: clamp01(hue[2] * lift) };
|
|
38
33
|
}
|
|
39
|
-
var
|
|
40
|
-
var
|
|
41
|
-
var
|
|
42
|
-
var
|
|
43
|
-
var
|
|
44
|
-
var
|
|
45
|
-
var edgeLight = (ior) => clamp01(fresnelF0(ior) * EDGE_LIGHT_GAIN);
|
|
46
|
-
var iridescence = (ior, filmNm) => filmNm <= 0 ? 0 : clamp01(fresnelStrength(ior) * 1.6);
|
|
47
|
-
var diffraction = (ior) => clamp01(dispersion(ior) * 0.5);
|
|
48
|
-
var colorPickup = (ior) => clamp(fresnelStrength(ior) * 0.9, 0, 0.42);
|
|
34
|
+
var gatherRadius = (bevelDp2) => clamp(Math.max(bevelDp2, 1) * DERIVE.gatherPerBevel, DERIVE.gatherMinDp, DERIVE.gatherMaxDp);
|
|
35
|
+
var bodyDensity = (thicknessDp2) => DERIVE.bodyDensity * absorption(thicknessDp2);
|
|
36
|
+
var edgeLight = (ior) => clamp01(fresnelF0(ior) * DERIVE.edgeLightGain);
|
|
37
|
+
var iridescence = (ior, filmNm) => filmNm <= 0 ? 0 : clamp01(fresnelStrength(ior) * DERIVE.iridescencePerFresnel);
|
|
38
|
+
var diffraction = (ior) => clamp01(dispersion(ior) * DERIVE.diffractionPerDispersion);
|
|
39
|
+
var colorPickup = (ior) => clamp(fresnelStrength(ior) * DERIVE.colorPickupPerFresnel, 0, DERIVE.colorPickupMax);
|
|
49
40
|
|
|
50
41
|
// src/material.ts
|
|
51
42
|
var MATERIAL_RANGES = {
|
|
@@ -335,59 +326,8 @@ function debugIndex(mode) {
|
|
|
335
326
|
return DEBUG_MODES.indexOf(mode);
|
|
336
327
|
}
|
|
337
328
|
|
|
338
|
-
// src/geometry.ts
|
|
339
|
-
var circleGeometry = (size) => ({
|
|
340
|
-
width: size,
|
|
341
|
-
height: size,
|
|
342
|
-
cornerRadius: size / 2
|
|
343
|
-
});
|
|
344
|
-
var capsuleGeometry = (width, height) => ({
|
|
345
|
-
width,
|
|
346
|
-
height,
|
|
347
|
-
cornerRadius: Math.min(width, height) / 2
|
|
348
|
-
});
|
|
349
|
-
var roundedRectGeometry = (width, height, cornerRadius) => ({ width, height, cornerRadius });
|
|
350
|
-
var halfMinDp = (g) => Math.max(Math.min(g.width, g.height) / 2, 1);
|
|
351
|
-
var MAX_STRETCH = 0.34;
|
|
352
|
-
var SIZE_REF_DP = 24;
|
|
353
|
-
var sizeGain = (g) => Math.min(Math.max(Math.sqrt(halfMinDp(g) / SIZE_REF_DP), 0.8), 2.4);
|
|
354
|
-
var MAX_BEVEL_FRACTION = 0.65;
|
|
355
|
-
var bevelFraction = (g, o) => Math.min(MAX_BEVEL_FRACTION, o.bevelDp * sizeGain(g) / halfMinDp(g));
|
|
356
|
-
var bevelDp = (g, o) => Math.max(bevelFraction(g, o) * halfMinDp(g), 1);
|
|
357
|
-
var thicknessDp = (g, o) => o.thicknessDp * sizeGain(g);
|
|
358
|
-
var RIM_FRACTION = 0.85;
|
|
359
|
-
var rimDp = (g, o) => thicknessDp(g, o) * RIM_FRACTION;
|
|
360
|
-
var shadowReachDp = (g) => Math.min(Math.max(halfMinDp(g) * 0.65, 6), 36);
|
|
361
|
-
function morphReachDp(g, morph) {
|
|
362
|
-
if (!morph || morph.smoothing <= 0) return 0;
|
|
363
|
-
return Math.max(
|
|
364
|
-
0,
|
|
365
|
-
Math.abs(morph.offsetX) + morph.width / 2 - g.width / 2,
|
|
366
|
-
Math.abs(morph.offsetY) + morph.height / 2 - g.height / 2
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
var PAD_STEP = 8;
|
|
370
|
-
var quantise = (v) => Math.ceil(v / PAD_STEP) * PAD_STEP;
|
|
371
|
-
function lensPadDp(g, o, morph, dragLimit = 0) {
|
|
372
|
-
const sampling = Math.max(o.blur, o.gatherRadiusDp);
|
|
373
|
-
const stretch = halfMinDp(g) * MAX_STRETCH;
|
|
374
|
-
return quantise(sampling + dragLimit + stretch + morphReachDp(g, morph) + 2);
|
|
375
|
-
}
|
|
376
|
-
function surfacePadDp(g, dragLimit = 0, morph) {
|
|
377
|
-
return quantise(shadowReachDp(g) * 1.2 + dragLimit + morphReachDp(g, morph) + 2);
|
|
378
|
-
}
|
|
379
|
-
var SHADOW_FLAT = 0.8;
|
|
380
|
-
var SHADOW_BUSY_GAIN = 6;
|
|
381
|
-
var SHADOW_BUSY_RISE = 1.2;
|
|
382
|
-
var SHADOW_STEP = 0.05;
|
|
383
|
-
function shadowOpacity(busy) {
|
|
384
|
-
return SHADOW_FLAT + Math.min(Math.max(busy, 0) * SHADOW_BUSY_GAIN, SHADOW_BUSY_RISE);
|
|
385
|
-
}
|
|
386
|
-
function shadowOpacityFrom(sample) {
|
|
387
|
-
return Math.round(shadowOpacity(sample.busy) / SHADOW_STEP) * SHADOW_STEP;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
329
|
// src/sdf.ts
|
|
330
|
+
var VG_TAU = 6.28318530718;
|
|
391
331
|
var VG_SDF = `
|
|
392
332
|
float vgRoundRect(float2 p, float2 halfSize, float corner) {
|
|
393
333
|
float2 q = abs(p) - halfSize + corner;
|
|
@@ -424,7 +364,7 @@ float2 vgTouchWarp(float2 p, float2 touch, float2 pull, float press, float radiu
|
|
|
424
364
|
float waveAmp, float wavePhase) {
|
|
425
365
|
if (radius <= 0.0) { return p; }
|
|
426
366
|
// Under the finger the whole element grows (M 3:51; HIG: interactive "expands").
|
|
427
|
-
p /= 1.0 +
|
|
367
|
+
p /= 1.0 + ${TOUCH.pressGrow} * press;
|
|
428
368
|
float2 d = p - touch;
|
|
429
369
|
float r = length(d);
|
|
430
370
|
|
|
@@ -442,16 +382,16 @@ float2 vgTouchWarp(float2 p, float2 touch, float2 pull, float press, float radiu
|
|
|
442
382
|
// Without this ridge the shape just balloons, and a dense medium doesn't behave that way.
|
|
443
383
|
float rim = k * k * (1.0 - k) * 4.0;
|
|
444
384
|
|
|
445
|
-
float2 q = p - pull * (core -
|
|
446
|
-
q -= d * (press *
|
|
385
|
+
float2 q = p - pull * (core - ${TOUCH.ridge} * rim);
|
|
386
|
+
q -= d * (press * ${TOUCH.pressPull} * core);
|
|
447
387
|
|
|
448
388
|
// The wave has its own scale, twice the drag radius: the ripple has to reach the far edge,
|
|
449
389
|
// otherwise it reads as jitter under the finger rather than a wave across the surface. The
|
|
450
390
|
// wavelength is short: in a dense medium ripples are frequent and small, long shallow swells
|
|
451
391
|
// are water.
|
|
452
392
|
if (waveAmp > 0.0 && r > 0.0001) {
|
|
453
|
-
float span = radius *
|
|
454
|
-
float ring = sin(r / (span *
|
|
393
|
+
float span = radius * ${TOUCH.waveSpan}.0;
|
|
394
|
+
float ring = sin(r / (span * ${TOUCH.waveLength}) - wavePhase * ${VG_TAU}) * exp(-r / (span * ${TOUCH.waveDecay}));
|
|
455
395
|
q -= (d / r) * ring * waveAmp;
|
|
456
396
|
}
|
|
457
397
|
return q;
|
|
@@ -485,14 +425,14 @@ float2 vgSceneNormal(float2 p, float2 halfSize, float corner,
|
|
|
485
425
|
float a = vgRoundRect(p, halfSize, corner);
|
|
486
426
|
float b = vgRoundRect(q, halfB, cornerB);
|
|
487
427
|
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
|
|
488
|
-
float2 nab = normalize(mix(vgRoundRectNormal(q, halfB, cornerB), na, h) + float2(
|
|
428
|
+
float2 nab = normalize(mix(vgRoundRectNormal(q, halfB, cornerB), na, h) + float2(${EPSILON}, ${EPSILON}));
|
|
489
429
|
if (halfC.x <= 0.0) { return nab; }
|
|
490
430
|
// Same derivation as for the pair: normals blend with the same weight as the distances.
|
|
491
431
|
float2 qc = p - offsetC;
|
|
492
432
|
float ab = vgSmin(a, b, k);
|
|
493
433
|
float c = vgRoundRect(qc, halfC, cornerC);
|
|
494
434
|
float hc = clamp(0.5 + 0.5 * (c - ab) / k, 0.0, 1.0);
|
|
495
|
-
return normalize(mix(vgRoundRectNormal(qc, halfC, cornerC), nab, hc) + float2(
|
|
435
|
+
return normalize(mix(vgRoundRectNormal(qc, halfC, cornerC), nab, hc) + float2(${EPSILON}, ${EPSILON}));
|
|
496
436
|
}
|
|
497
437
|
|
|
498
438
|
// Position within the bevel: 0 is the flat middle, 1 is the very edge. This one value feeds the
|
|
@@ -509,7 +449,7 @@ float vgBevelT(float sd, float bevel) {
|
|
|
509
449
|
// This still doesn't control the field of view: only the bevel bends, the middle stays flat,
|
|
510
450
|
// otherwise the surface reads as a soap bubble.
|
|
511
451
|
float vgBevelSlope(float t) {
|
|
512
|
-
return min(t * inversesqrt(max(1.0 - t * t *
|
|
452
|
+
return min(t * inversesqrt(max(1.0 - t * t * ${BEVEL.sphere}, ${BEVEL.floor})), ${BEVEL.slopeMax});
|
|
513
453
|
}
|
|
514
454
|
|
|
515
455
|
// PROGRESS FRACTION. Progress is an active state turned into a FIELD: to the left of the boundary
|
|
@@ -528,6 +468,97 @@ float vgProgress(float2 p, float2 halfSize, float progress) {
|
|
|
528
468
|
}
|
|
529
469
|
`;
|
|
530
470
|
var VG_FALLOFF = 2.6;
|
|
471
|
+
function sdfRoundedRect(x, y, w, h, r) {
|
|
472
|
+
const qx = Math.abs(x) - w / 2 + r;
|
|
473
|
+
const qy = Math.abs(y) - h / 2 + r;
|
|
474
|
+
return Math.min(Math.max(qx, qy), 0) + Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) - r;
|
|
475
|
+
}
|
|
476
|
+
function sdfRoundedRectGradient(x, y, w, h, r) {
|
|
477
|
+
const qx = Math.abs(x) - w / 2 + r;
|
|
478
|
+
const qy = Math.abs(y) - h / 2 + r;
|
|
479
|
+
let gx;
|
|
480
|
+
let gy;
|
|
481
|
+
if (qx > 0 && qy > 0) {
|
|
482
|
+
const cx = Math.max(qx, 1e-4);
|
|
483
|
+
const cy = Math.max(qy, 1e-4);
|
|
484
|
+
const len = Math.hypot(cx, cy);
|
|
485
|
+
gx = cx / len;
|
|
486
|
+
gy = cy / len;
|
|
487
|
+
} else if (qx > qy) {
|
|
488
|
+
gx = 1;
|
|
489
|
+
gy = 0;
|
|
490
|
+
} else {
|
|
491
|
+
gx = 0;
|
|
492
|
+
gy = 1;
|
|
493
|
+
}
|
|
494
|
+
return [gx * Math.sign(x), gy * Math.sign(y)];
|
|
495
|
+
}
|
|
496
|
+
function smin(a, b, k) {
|
|
497
|
+
const h = Math.min(Math.max(0.5 + 0.5 * (b - a) / k, 0), 1);
|
|
498
|
+
return b + (a - b) * h - k * h * (1 - h);
|
|
499
|
+
}
|
|
500
|
+
function neckToBridge(gap) {
|
|
501
|
+
return gap > 0 ? gap * 2 : 0;
|
|
502
|
+
}
|
|
503
|
+
function sceneDistance(x, y, w, h, r, smoothing = 0, b, c) {
|
|
504
|
+
const a = sdfRoundedRect(x, y, w, h, r);
|
|
505
|
+
if (smoothing <= 0 || !b || b.width <= 0) return a;
|
|
506
|
+
const db = sdfRoundedRect(x - b.offsetX, y - b.offsetY, b.width, b.height, b.cornerRadius);
|
|
507
|
+
const ab = smin(a, db, smoothing);
|
|
508
|
+
if (!c || c.width <= 0) return ab;
|
|
509
|
+
const dc = sdfRoundedRect(x - c.offsetX, y - c.offsetY, c.width, c.height, c.cornerRadius);
|
|
510
|
+
return smin(ab, dc, smoothing);
|
|
511
|
+
}
|
|
512
|
+
function sceneGradient(x, y, w, h, r, smoothing = 0, b, c) {
|
|
513
|
+
const na = sdfRoundedRectGradient(x, y, w, h, r);
|
|
514
|
+
if (smoothing <= 0 || !b || b.width <= 0) return na;
|
|
515
|
+
const bx = x - b.offsetX;
|
|
516
|
+
const by = y - b.offsetY;
|
|
517
|
+
const da = sdfRoundedRect(x, y, w, h, r);
|
|
518
|
+
const db = sdfRoundedRect(bx, by, b.width, b.height, b.cornerRadius);
|
|
519
|
+
const hb = Math.min(Math.max(0.5 + 0.5 * (db - da) / smoothing, 0), 1);
|
|
520
|
+
const nb = sdfRoundedRectGradient(bx, by, b.width, b.height, b.cornerRadius);
|
|
521
|
+
let gx = nb[0] + (na[0] - nb[0]) * hb;
|
|
522
|
+
let gy = nb[1] + (na[1] - nb[1]) * hb;
|
|
523
|
+
if (c && c.width > 0) {
|
|
524
|
+
const cx = x - c.offsetX;
|
|
525
|
+
const cy = y - c.offsetY;
|
|
526
|
+
const dab = smin(da, db, smoothing);
|
|
527
|
+
const dc = sdfRoundedRect(cx, cy, c.width, c.height, c.cornerRadius);
|
|
528
|
+
const hc = Math.min(Math.max(0.5 + 0.5 * (dc - dab) / smoothing, 0), 1);
|
|
529
|
+
const nc = sdfRoundedRectGradient(cx, cy, c.width, c.height, c.cornerRadius);
|
|
530
|
+
gx = nc[0] + (gx - nc[0]) * hc;
|
|
531
|
+
gy = nc[1] + (gy - nc[1]) * hc;
|
|
532
|
+
}
|
|
533
|
+
gx += EPSILON;
|
|
534
|
+
gy += EPSILON;
|
|
535
|
+
const len = Math.hypot(gx, gy) || 1;
|
|
536
|
+
return [gx / len, gy / len];
|
|
537
|
+
}
|
|
538
|
+
function touchWarp(x, y, t) {
|
|
539
|
+
if (t.radius <= 0) return [x, y];
|
|
540
|
+
const grow = 1 + TOUCH.pressGrow * t.press;
|
|
541
|
+
let px = x / grow;
|
|
542
|
+
let py = y / grow;
|
|
543
|
+
const dx = px - t.x;
|
|
544
|
+
const dy = py - t.y;
|
|
545
|
+
const r = Math.hypot(dx, dy);
|
|
546
|
+
const k = Math.min(Math.max((t.radius - r) / t.radius, 0), 1);
|
|
547
|
+
const s = k * k * (3 - 2 * k);
|
|
548
|
+
const core = s * s * (3 - 2 * s);
|
|
549
|
+
const rim = k * k * (1 - k) * 4;
|
|
550
|
+
px -= t.pullX * (core - TOUCH.ridge * rim);
|
|
551
|
+
py -= t.pullY * (core - TOUCH.ridge * rim);
|
|
552
|
+
px -= dx * (t.press * TOUCH.pressPull * core);
|
|
553
|
+
py -= dy * (t.press * TOUCH.pressPull * core);
|
|
554
|
+
if (t.waveAmp > 0 && r > 1e-4) {
|
|
555
|
+
const span = t.radius * TOUCH.waveSpan;
|
|
556
|
+
const ring = Math.sin(r / (span * TOUCH.waveLength) - t.wavePhase * VG_TAU) * Math.exp(-r / (span * TOUCH.waveDecay));
|
|
557
|
+
px -= dx / r * ring * t.waveAmp;
|
|
558
|
+
py -= dy / r * ring * t.waveAmp;
|
|
559
|
+
}
|
|
560
|
+
return [px, py];
|
|
561
|
+
}
|
|
531
562
|
|
|
532
563
|
// src/lens-shader.ts
|
|
533
564
|
var LENS_SHADER = `
|
|
@@ -603,51 +634,51 @@ const int VG_FROST_TAPS = 20;
|
|
|
603
634
|
const int VG_FROST_TAPS_MAX = 64;
|
|
604
635
|
// The radius correction for backdrop compression at the rim is capped: at the silhouette it goes
|
|
605
636
|
// to infinity.
|
|
606
|
-
const float VG_FOOTPRINT_MAX =
|
|
637
|
+
const float VG_FOOTPRINT_MAX = ${LENS.footprintMax};
|
|
607
638
|
const float VG_TAU = 6.28318530718;
|
|
608
639
|
// Ceiling on body lightness under light ink (and the mirrored floor under dark ink). This is a
|
|
609
640
|
// ceiling, not a difference: "0.14 darker than white" is a lightness of 0.86, at which white text
|
|
610
641
|
// is invisible.
|
|
611
|
-
const float VG_BODY_CAP_LOOSE =
|
|
612
|
-
const float VG_BODY_CAP_TIGHT =
|
|
642
|
+
const float VG_BODY_CAP_LOOSE = ${BODY.capLoose};
|
|
643
|
+
const float VG_BODY_CAP_TIGHT = ${BODY.capTight};
|
|
613
644
|
// Tint lightness in both directions: real glass is never coal-black or paper-white.
|
|
614
|
-
const float VG_TINT_DARK =
|
|
615
|
-
const float VG_TINT_LIGHT =
|
|
645
|
+
const float VG_TINT_DARK = ${BODY.tintDark};
|
|
646
|
+
const float VG_TINT_LIGHT = ${BODY.tintLight};
|
|
616
647
|
// Reference channel wavelengths, nm \u2014 for diffraction and interference.
|
|
617
|
-
const float3 VG_LAMBDA = float3(
|
|
648
|
+
const float3 VG_LAMBDA = float3(${LENS.lambdaR}.0, ${LENS.lambdaG}.0, ${LENS.lambdaB}.0);
|
|
618
649
|
// Film index of refraction: every thin film on glass sits around this value.
|
|
619
|
-
const float VG_FILM_IOR =
|
|
650
|
+
const float VG_FILM_IOR = ${LENS.filmIor};
|
|
620
651
|
// Ceiling on the profile's slope right at the silhouette: it goes to infinity there.
|
|
621
|
-
const float VG_SLOPE_MAX =
|
|
652
|
+
const float VG_SLOPE_MAX = ${LENS.slopeMax}.0;
|
|
622
653
|
// Fraction of the gather radius that scattering reaches over a busy backdrop under ink (M 11:47).
|
|
623
654
|
// Set against the reference: there, structure under the capsule fades by a factor of 9-10, not
|
|
624
655
|
// thirty.
|
|
625
|
-
const float VG_SCATTER_MAX =
|
|
626
|
-
const float VG_SCATTER_BASE =
|
|
656
|
+
const float VG_SCATTER_MAX = ${MEDIUM.scatterMax};
|
|
657
|
+
const float VG_SCATTER_BASE = ${MEDIUM.scatterBase};
|
|
627
658
|
// The response to structure under the glass saturates early: what competes with the ink isn't
|
|
628
659
|
// the area of foreign text but the mere fact that it's there (a line under a tile gives a busy
|
|
629
660
|
// of about 0.12).
|
|
630
|
-
const float VG_STRUCTURE_GAIN =
|
|
661
|
+
const float VG_STRUCTURE_GAIN = ${MEDIUM.structureGain}.0;
|
|
631
662
|
// How much denser the body with ink gets over a BUSY backdrop than over a calm one. There's no
|
|
632
663
|
// flat floor here: legibility picks up its own targeted requirement further down, and base
|
|
633
664
|
// frosting comes from VG_MATTE_LIFT.
|
|
634
|
-
const float VG_GROUND_SPAN =
|
|
665
|
+
const float VG_GROUND_SPAN = ${BODY.groundSpan};
|
|
635
666
|
// Scattered light on a frosted element is an ADDITION on top of the backdrop, not a fraction of
|
|
636
667
|
// the way to the tint: the fraction goes to zero once the canvas reaches the tint's lightness,
|
|
637
668
|
// and darkens past it (docs/benchmarks.md).
|
|
638
|
-
const float VG_MATTE_LIFT =
|
|
669
|
+
const float VG_MATTE_LIFT = ${MEDIUM.matteLift};
|
|
639
670
|
// The fraction of backdrop spread that survives through to the body past scattering: the
|
|
640
671
|
// legibility requirement is computed from this edge, not from the spot's average lightness.
|
|
641
|
-
const float VG_BUSY_EDGE =
|
|
672
|
+
const float VG_BUSY_EDGE = ${BODY.busyEdge};
|
|
642
673
|
// Light concentration: the body is a touch lighter than what's beneath it (M 2:29).
|
|
643
|
-
const float VG_CONCENTRATE =
|
|
674
|
+
const float VG_CONCENTRATE = ${MEDIUM.concentrate};
|
|
644
675
|
// The lightness the medium pulls content under the glass toward, and the strength of that pull.
|
|
645
676
|
// The medium both removes light and mixes in scattered light: over a light backdrop the body
|
|
646
677
|
// darkens, over a dark one it lightens.
|
|
647
|
-
const float VG_MEDIUM_LUMA =
|
|
648
|
-
const float VG_MEDIUM_PULL =
|
|
678
|
+
const float VG_MEDIUM_LUMA = ${MEDIUM.luma};
|
|
679
|
+
const float VG_MEDIUM_PULL = ${MEDIUM.pull};
|
|
649
680
|
// How much ambient light reaches the body on top of the pull.
|
|
650
|
-
const float VG_AMBIENT_SPILL =
|
|
681
|
+
const float VG_AMBIENT_SPILL = ${MEDIUM.ambientSpill};
|
|
651
682
|
|
|
652
683
|
float vgLuma(float3 c) { return dot(c, float3(0.2126, 0.7152, 0.0722)); }
|
|
653
684
|
|
|
@@ -712,8 +743,8 @@ float3 vgInterference(float cosI) {
|
|
|
712
743
|
|
|
713
744
|
// Edge diffraction: fringes get denser the sharper the bevel. Also a hue, not a brightness.
|
|
714
745
|
float3 vgDiffraction(float distFromEdge, float bevel) {
|
|
715
|
-
float phase = VG_TAU *
|
|
716
|
-
float3 d = 0.5 + 0.5 * cos(phase * (
|
|
746
|
+
float phase = VG_TAU * ${SPECTRAL.diffractionFringes}.0 * distFromEdge / max(bevel, 1.0);
|
|
747
|
+
float3 d = 0.5 + 0.5 * cos(phase * (${SPECTRAL.referenceLambda}.0 / VG_LAMBDA));
|
|
717
748
|
return d / max((d.r + d.g + d.b) / 3.0, 0.001);
|
|
718
749
|
}
|
|
719
750
|
|
|
@@ -800,8 +831,8 @@ half4 main(float2 xy) {
|
|
|
800
831
|
float spread = max(footprint - 1.0, 0.0) * 0.5 * on;
|
|
801
832
|
|
|
802
833
|
// Dispersion: the same ray with each channel's own index. Blue bends more than red.
|
|
803
|
-
float2 dR = vgShift(N, z, u_ior
|
|
804
|
-
float2 dB = vgShift(N, z, u_ior +
|
|
834
|
+
float2 dR = vgShift(N, z, u_ior + ${DISPERSION.redShift} * u_iorSpread) * lens - shift;
|
|
835
|
+
float2 dB = vgShift(N, z, u_ior + ${DISPERSION.blueShift} * u_iorSpread) * lens - shift;
|
|
805
836
|
float chroma = length(dB - dR);
|
|
806
837
|
|
|
807
838
|
float3 rgb;
|
|
@@ -892,9 +923,9 @@ half4 main(float2 xy) {
|
|
|
892
923
|
// up, and there's nothing there for direction to depend on. Using the SDF normal instead
|
|
893
924
|
// painted the body as a cone.
|
|
894
925
|
float facing = dot(N.xy, L);
|
|
895
|
-
float key = u_specular * (pow(max(facing, 0.0),
|
|
926
|
+
float key = u_specular * (pow(max(facing, 0.0), ${RIM.lobeExponent}.0) + ${RIM.opposingArc} * pow(max(-facing, 0.0), ${RIM.lobeExponent}.0));
|
|
896
927
|
float rimFacing = dot(n, L);
|
|
897
|
-
float rimKey = u_specular * (pow(max(rimFacing, 0.0),
|
|
928
|
+
float rimKey = u_specular * (pow(max(rimFacing, 0.0), ${RIM.lobeExponent}.0) + ${RIM.opposingArc} * pow(max(-rimFacing, 0.0), ${RIM.lobeExponent}.0));
|
|
898
929
|
// Wherever the key light doesn't fall, the face reflects its shaded surroundings \u2014 hence the
|
|
899
930
|
// dark outline.
|
|
900
931
|
float3 env = refl * (0.55 + 0.45 * min(key, 1.0)) + float3(1.2 * key);
|
|
@@ -906,7 +937,7 @@ half4 main(float2 xy) {
|
|
|
906
937
|
spectral *= mix(float3(1.0), vgInterference(cosT), u_iridescence);
|
|
907
938
|
}
|
|
908
939
|
if (u_diffraction > 0.001) {
|
|
909
|
-
float w = u_diffraction * smoothstep(
|
|
940
|
+
float w = u_diffraction * smoothstep(${SPECTRAL.diffractionOnset}, 1.0, t);
|
|
910
941
|
spectral *= mix(float3(1.0), vgDiffraction(e, bevel), w);
|
|
911
942
|
}
|
|
912
943
|
|
|
@@ -976,7 +1007,7 @@ half4 main(float2 xy) {
|
|
|
976
1007
|
// Where there's no ink, the glass tints away from the backdrop. The direction is chosen by the
|
|
977
1008
|
// average lightness under the element, not the local spot's lightness: otherwise on a gradient
|
|
978
1009
|
// the threshold cuts across the body as a diagonal step.
|
|
979
|
-
float darkSide = smoothstep(
|
|
1010
|
+
float darkSide = smoothstep(${BODY.darkSideFrom}, ${BODY.darkSideTo}, mean);
|
|
980
1011
|
float away = mix(VG_TINT_LIGHT, VG_TINT_DARK, darkSide);
|
|
981
1012
|
float tintLuma = mix(away, mix(VG_TINT_LIGHT, VG_TINT_DARK, pol), demand);
|
|
982
1013
|
|
|
@@ -988,16 +1019,38 @@ half4 main(float2 xy) {
|
|
|
988
1019
|
float inkHi = min(local + edge, 1.0);
|
|
989
1020
|
float inkLo = max(local - edge, 0.0);
|
|
990
1021
|
float needForLight = inkHi > capLight
|
|
991
|
-
? clamp((inkHi - capLight) / max(inkHi - VG_TINT_DARK, 1e-4), 0.0,
|
|
1022
|
+
? clamp((inkHi - capLight) / max(inkHi - VG_TINT_DARK, 1e-4), 0.0, ${BODY.maxDemand})
|
|
992
1023
|
: 0.0;
|
|
993
1024
|
float needForDark = inkLo < floorDark
|
|
994
|
-
? clamp((floorDark - inkLo) / max(VG_TINT_LIGHT - inkLo, 1e-4), 0.0,
|
|
1025
|
+
? clamp((floorDark - inkLo) / max(VG_TINT_LIGHT - inkLo, 1e-4), 0.0, ${BODY.maxDemand})
|
|
995
1026
|
: 0.0;
|
|
996
1027
|
float needForInk = mix(needForDark, needForLight, pol) * demand;
|
|
997
1028
|
|
|
998
1029
|
float ground = structure * u_legibility * VG_GROUND_SPAN;
|
|
999
1030
|
float density = max(max(u_bodyDensity, ground), needForInk);
|
|
1000
1031
|
|
|
1032
|
+
// PRESENCE (\xA73). Over a uniform backdrop there is nothing to refract, and an element that is
|
|
1033
|
+
// only a rim honestly disappears \u2014 right for a piece of background, wrong for a control.
|
|
1034
|
+
//
|
|
1035
|
+
// This used to live on the rim line alone, as lit = max(rimKey, u_presence x 2), while the
|
|
1036
|
+
// DOM path has always implemented it as a floor on the BODY. Two renderers, one field, two
|
|
1037
|
+
// meanings. Measured on Apple's own control over a flat page (frames/verify/m-707), the body
|
|
1038
|
+
// carries 16 levels of separation there and the rim 11.6 \u2014 so the body is where it belongs,
|
|
1039
|
+
// and the rim's reading was the wrong one.
|
|
1040
|
+
// The separation asked for is capped by the range that is left: over a backdrop at 0.96 there
|
|
1041
|
+
// is no room to go 0.05 lighter. And what it is DIVIDED by is the distance to the tint's own
|
|
1042
|
+
// end, not to the tint the body currently sits at \u2014 dividing by the latter explodes wherever
|
|
1043
|
+
// the two are close, which is how the first version of this drove density to its ceiling at
|
|
1044
|
+
// both extremes and took the window down to 7%.
|
|
1045
|
+
bool lighter = local < 0.5;
|
|
1046
|
+
float target = lighter ? min(local + u_presence, 1.0) : max(local - u_presence, 0.0);
|
|
1047
|
+
float separation = abs(target - local);
|
|
1048
|
+
float reached = abs(tintLuma - local) * density;
|
|
1049
|
+
if (u_presence > 0.0 && reached < separation) {
|
|
1050
|
+
float toEnd = abs((lighter ? VG_TINT_LIGHT : VG_TINT_DARK) - local);
|
|
1051
|
+
density = max(density, min(separation / max(toEnd, 1e-4), ${BODY.maxDemand}));
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1001
1054
|
// Glass has no color of its own \u2014 only the color of what's beneath it (HIG "Color").
|
|
1002
1055
|
float3 tintHue = mix(float3(1.0), vgHue(wide * 0.5 + ambient * 0.5), u_colorPickup);
|
|
1003
1056
|
// The element appears by the lens and body building up, not by fading in (M 2:55): at
|
|
@@ -1009,7 +1062,7 @@ half4 main(float2 xy) {
|
|
|
1009
1062
|
// tone \u2014 deeper over dark, lighter over light \u2014 and the content's texture still shows through
|
|
1010
1063
|
// the color.
|
|
1011
1064
|
if (u_accent.a > 0.0) {
|
|
1012
|
-
float3 tone = u_accent.rgb * mix(
|
|
1065
|
+
float3 tone = u_accent.rgb * mix(${ACCENT.deep}, ${ACCENT.light}, local);
|
|
1013
1066
|
float3 through = tone * (0.85 + 0.3 * vgLuma(rgb));
|
|
1014
1067
|
rgb = mix(rgb, clamp(through, float3(0.0), float3(1.0)), u_accent.a * u_appear);
|
|
1015
1068
|
}
|
|
@@ -1045,7 +1098,7 @@ half4 main(float2 xy) {
|
|
|
1045
1098
|
// The dark rim is its own layer around the whole silhouette, and the highlight sits ON TOP of
|
|
1046
1099
|
// it (iOS 27). While the outline used to fade via a (1 \u2212 lit) multiplier, the silhouette read
|
|
1047
1100
|
// as a single arc: highlight OR shadow.
|
|
1048
|
-
rgb *= 1.0 -
|
|
1101
|
+
rgb *= 1.0 - ${RIM.darkEdge} * outline;
|
|
1049
1102
|
rgb = mix(rgb, mix(refl, float3(1.0), 0.75), line * lit);
|
|
1050
1103
|
|
|
1051
1104
|
if (u_debug > 9.5 && u_debug < 10.5) { rgb = spectral * 0.5; }
|
|
@@ -1066,395 +1119,6 @@ half4 main(float2 xy) {
|
|
|
1066
1119
|
}
|
|
1067
1120
|
`;
|
|
1068
1121
|
|
|
1069
|
-
// src/surface-shader.ts
|
|
1070
|
-
var SURFACE_SHADER = `
|
|
1071
|
-
uniform shader u_icon;
|
|
1072
|
-
/** App-colored content ON the glass \u2014 cover art, a thumbnail. A separate layer from the ink
|
|
1073
|
-
* mask: that one is single-channel and colored by polarity, this one carries its own color
|
|
1074
|
-
* as-is. */
|
|
1075
|
-
uniform shader u_overlay;
|
|
1076
|
-
|
|
1077
|
-
uniform float2 u_center;
|
|
1078
|
-
uniform float2 u_halfSize;
|
|
1079
|
-
uniform float u_corner;
|
|
1080
|
-
uniform float u_bevel;
|
|
1081
|
-
uniform float u_thickness;
|
|
1082
|
-
uniform float2 u_morphOffset;
|
|
1083
|
-
uniform float2 u_morphHalf;
|
|
1084
|
-
uniform float u_morphCorner;
|
|
1085
|
-
uniform float u_morphK;
|
|
1086
|
-
uniform float2 u_morph2Offset;
|
|
1087
|
-
uniform float2 u_morph2Half;
|
|
1088
|
-
uniform float u_morph2Corner;
|
|
1089
|
-
|
|
1090
|
-
uniform float u_press;
|
|
1091
|
-
uniform float u_active;
|
|
1092
|
-
/* 0 \u2014 the element sinks in under the finger, 1 \u2014 it rises into glass (reference \xA75). */
|
|
1093
|
-
uniform float u_lift;
|
|
1094
|
-
|
|
1095
|
-
uniform float u_edgeDensity;
|
|
1096
|
-
uniform float u_dispersion;
|
|
1097
|
-
uniform float u_refraction;
|
|
1098
|
-
uniform float4 u_tint;
|
|
1099
|
-
uniform float u_shadow;
|
|
1100
|
-
uniform float u_shadowReach;
|
|
1101
|
-
uniform float3 u_ambient;
|
|
1102
|
-
uniform float u_presence;
|
|
1103
|
-
uniform float u_progress;
|
|
1104
|
-
uniform float u_appear;
|
|
1105
|
-
uniform float u_debug;
|
|
1106
|
-
|
|
1107
|
-
uniform float u_iconOn;
|
|
1108
|
-
uniform float u_overlayOn;
|
|
1109
|
-
uniform float u_iconScale;
|
|
1110
|
-
uniform float4 u_inkIdle;
|
|
1111
|
-
uniform float4 u_inkActive;
|
|
1112
|
-
uniform float2 u_touch;
|
|
1113
|
-
uniform float2 u_pull;
|
|
1114
|
-
uniform float u_touchPress;
|
|
1115
|
-
uniform float u_touchRadius;
|
|
1116
|
-
uniform float2 u_wave;
|
|
1117
|
-
|
|
1118
|
-
${VG_SDF}
|
|
1119
|
-
|
|
1120
|
-
const float VG_FALLOFF = ${VG_FALLOFF};
|
|
1121
|
-
|
|
1122
|
-
// Tint density in the flat middle; at the bevel it's multiplied by u_edgeDensity.
|
|
1123
|
-
const float VG_BODY_DENSITY = 0.19;
|
|
1124
|
-
/* Fraction of the ambient hue in the shadow. The shadow has to stay a shadow, not a colored
|
|
1125
|
-
* blob. */
|
|
1126
|
-
const float VG_SHADOW_TINT = 1.0;
|
|
1127
|
-
// How much weaker the shadow is right at the outline than under the middle of the gap, and what
|
|
1128
|
-
// fraction of the offset it takes to reach full depth. In the reference the shadow's minimum
|
|
1129
|
-
// sits below the rim, not right at it.
|
|
1130
|
-
const float VG_GAP_LIGHT = 0.76;
|
|
1131
|
-
const float VG_GAP_REACH = 0.20;
|
|
1132
|
-
/* Depth of the ink beneath the surface, dp. The normal displaces it by exactly this much right
|
|
1133
|
-
* at the rim. */
|
|
1134
|
-
const float VG_INK_DEPTH = 4.0;
|
|
1135
|
-
/* Ink defocus under the finger (reference \xA76): a glyph loses its edge and drowns in milk. Given
|
|
1136
|
-
* as a FRACTION of the touch blob, not a number: the blob already arrives in device units, so
|
|
1137
|
-
* the radius automatically follows both screen density and the element's size. At rest it's
|
|
1138
|
-
* zero, and the sample stays a single one. */
|
|
1139
|
-
const float VG_INK_DEFOCUS = 0.08;
|
|
1140
|
-
|
|
1141
|
-
half4 vgPack(half3 c, float a) { return half4(c * half(a), half(a)); }
|
|
1142
|
-
|
|
1143
|
-
// What bleeds into the shadow is the COLOR of the surroundings, not its brightness: the hue's own
|
|
1144
|
-
// lightness is subtracted out of it, otherwise the shadow pales and loses depth instead of
|
|
1145
|
-
// warming up. Strength is set by how much light there is around: over a nearly black backdrop
|
|
1146
|
-
// there's nothing to bleed into the shadow.
|
|
1147
|
-
float3 vgShadowTint(float3 ambient) {
|
|
1148
|
-
float peak = max(max(ambient.r, ambient.g), max(ambient.b, 0.001));
|
|
1149
|
-
float3 hue = ambient / peak;
|
|
1150
|
-
float lumaWeights = dot(ambient, float3(0.2126, 0.7152, 0.0722));
|
|
1151
|
-
return (hue - dot(hue, float3(0.2126, 0.7152, 0.0722))) * lumaWeights;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
half3 vgHeat(float v) {
|
|
1155
|
-
float x = clamp(v, 0.0, 1.0);
|
|
1156
|
-
return half3(half(clamp(x * 2.2 - 0.2, 0.0, 1.0)),
|
|
1157
|
-
half(clamp(1.0 - abs(x - 0.5) * 2.2, 0.0, 1.0)),
|
|
1158
|
-
half(clamp(1.2 - x * 2.4, 0.0, 1.0)));
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
half4 main(float2 xy) {
|
|
1162
|
-
float2 p = vgTouchWarp(xy - u_center, u_touch, u_pull, u_touchPress, u_touchRadius, u_wave.x, u_wave.y);
|
|
1163
|
-
|
|
1164
|
-
// Inverse deformation: stretch A along the pull vector, compression 1/sqrt(A) across it. This
|
|
1165
|
-
// exact law mirrors the transform of the live backdrop under the canvas, otherwise the two
|
|
1166
|
-
// would drift apart. There's NO motion geometry here \u2014 no pull, no press-driven bulge. All of
|
|
1167
|
-
// that is done by a single wrapper transform, the same one that carries the native lens: the
|
|
1168
|
-
// shader doesn't touch it, and two pipelines driving one motion would drift apart by a frame,
|
|
1169
|
-
// making the layers visible as separate.
|
|
1170
|
-
|
|
1171
|
-
float halfMin = max(min(u_halfSize.x, u_halfSize.y), 1.0);
|
|
1172
|
-
float bevel = max(u_bevel, 1.0);
|
|
1173
|
-
|
|
1174
|
-
float sd = vgScene(p, u_halfSize, u_corner, u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,
|
|
1175
|
-
u_morph2Offset, u_morph2Half, u_morph2Corner);
|
|
1176
|
-
float t = vgBevelT(sd, bevel);
|
|
1177
|
-
float2 n = vgSceneNormal(p, u_halfSize, u_corner, u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,
|
|
1178
|
-
u_morph2Offset, u_morph2Half, u_morph2Corner);
|
|
1179
|
-
float3 N = normalize(float3(n * vgBevelSlope(t), 1.0));
|
|
1180
|
-
|
|
1181
|
-
// Progress is an active state turned into a field: the played fraction shines and glows
|
|
1182
|
-
// exactly as much as an active element shines whole. This does NOT touch ink: recoloring text
|
|
1183
|
-
// as the track advances would mean changing it mid-word.
|
|
1184
|
-
float lit = max(u_active, vgProgress(p, u_halfSize, u_progress));
|
|
1185
|
-
|
|
1186
|
-
// The highlight and the rim are computed by the lens: reflection is a function of the
|
|
1187
|
-
// surroundings, and only the lens can see them.
|
|
1188
|
-
|
|
1189
|
-
if (u_debug > 0.5) {
|
|
1190
|
-
float inMask = 1.0 - smoothstep(-1.0, 1.0, sd);
|
|
1191
|
-
if (u_debug < 1.5) {
|
|
1192
|
-
float band = abs(fract(sd / (halfMin * 0.22)) - 0.5) * 2.0;
|
|
1193
|
-
half3 c = sd < 0.0 ? half3(0.20, 0.62, 1.0) : half3(1.0, 0.42, 0.22);
|
|
1194
|
-
return vgPack(c * half(0.25 + band * 0.75), 1.0);
|
|
1195
|
-
}
|
|
1196
|
-
if (u_debug < 2.5) { return vgPack(half3(1.0), inMask); }
|
|
1197
|
-
if (u_debug < 3.5) { return vgPack(vgHeat(t), inMask); }
|
|
1198
|
-
// Fresnel now lives in the lens; here we show its SHAPE \u2014 how grazing the view is.
|
|
1199
|
-
if (u_debug < 4.5) { return vgPack(vgHeat(1.0 - clamp(N.z, 0.0, 1.0)), inMask); }
|
|
1200
|
-
if (u_debug < 5.5) {
|
|
1201
|
-
float push = pow(t, VG_FALLOFF) * u_refraction;
|
|
1202
|
-
return vgPack(vgHeat(push), inMask);
|
|
1203
|
-
}
|
|
1204
|
-
if (u_debug < 6.5) { return half4(0.0); }
|
|
1205
|
-
if (u_debug < 7.5) { return half4(0.0); }
|
|
1206
|
-
// Dispersion is computed by the lens; here is the field it grows along.
|
|
1207
|
-
if (u_debug < 8.5) { return vgPack(vgHeat(u_dispersion * t * t), inMask); }
|
|
1208
|
-
if (u_debug < 9.5) { return vgPack(half3(N * 0.5 + 0.5), inMask); }
|
|
1209
|
-
// spectral and adapt are shown by the LENS \u2014 the surface has to get out of the way.
|
|
1210
|
-
return half4(0.0);
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
// The shadow and the halo live OUTSIDE the shape: inside, their place is taken by the glass
|
|
1214
|
-
// itself. Separation from the content rests entirely on the shadow \u2014 without it the surface
|
|
1215
|
-
// would sit ON the picture, not above it. The shadow only lives at the rim and outside it: at
|
|
1216
|
-
// sd < \u22121 the outside multiplier is already zero. Computing it deep inside the shape would be
|
|
1217
|
-
// a wasted FULL SDF evaluation for every such pixel, and the body covers almost the whole
|
|
1218
|
-
// area. The branch here is on the coordinate, but only the threads right at the rim actually
|
|
1219
|
-
// diverge.
|
|
1220
|
-
float shade = 0.0;
|
|
1221
|
-
float halo = 0.0;
|
|
1222
|
-
if (sd > -1.0) {
|
|
1223
|
-
float outside = smoothstep(-1.0, 1.0, sd);
|
|
1224
|
-
// Under the finger a button moves TOWARD the backdrop, and the shadow tightens: it's exactly
|
|
1225
|
-
// the gap between the two. A control rising into glass (a switch knob, a slider handle \u2014
|
|
1226
|
-
// reference \xA75) moves AWAY from it, and then the shadow spreads out instead. Only the app
|
|
1227
|
-
// knows which one applies, so the direction arrives as a fraction: 0 is sinking in, the way
|
|
1228
|
-
// every element behaved before this rule existed.
|
|
1229
|
-
float reach = u_shadowReach * (1.0 + mix(-0.25, 0.55, u_lift) * u_press);
|
|
1230
|
-
// The shadow is soft and wide, shifted downward (M 11:58): it's what holds the element's
|
|
1231
|
-
// separation from the content.
|
|
1232
|
-
float sdDrop = vgScene(p - float2(0.0, reach * 0.35), u_halfSize, u_corner,
|
|
1233
|
-
u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,
|
|
1234
|
-
u_morph2Offset, u_morph2Half, u_morph2Corner);
|
|
1235
|
-
float amb = 1.0 - smoothstep(-reach * 0.3, reach, sdDrop);
|
|
1236
|
-
// THE GAP UNDER A RAISED ELEMENT IS LIT: right at the outline the shadow is WEAKER than
|
|
1237
|
-
// further down. Otherwise the minimum sits right against the silhouette \u2014 both parts of the
|
|
1238
|
-
// shadow are monotonic in distance.
|
|
1239
|
-
float gap = smoothstep(0.0, max(reach * VG_GAP_REACH, 1.0), max(sd, 0.0));
|
|
1240
|
-
shade = amb * amb * 0.13 * mix(VG_GAP_LIGHT, 1.0, gap) * outside * u_shadow * u_appear;
|
|
1241
|
-
halo = 1.0 - smoothstep(0.0, reach * 0.30, max(sd, 0.0));
|
|
1242
|
-
halo = halo * halo * lit * 0.10 * outside * u_appear;
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
if (sd > 1.0) {
|
|
1246
|
-
// Ambient light bleeds into the shadow (219 @8:22): it isn't a black hole, it's tinted by
|
|
1247
|
-
// whatever lies nearby. Otherwise an element over colored content would hang over a gray
|
|
1248
|
-
// blob.
|
|
1249
|
-
half3 spill = max(half3(half(halo)) + half3(vgShadowTint(u_ambient)) * half(VG_SHADOW_TINT * shade * (1.0 - halo)), half3(0.0));
|
|
1250
|
-
return half4(spill, half(halo + shade * (1.0 - halo)));
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
float density = u_tint.w;
|
|
1254
|
-
float inner = clamp(-sd / halfMin, 0.0, 1.0);
|
|
1255
|
-
half3 col = half3(u_tint.rgb);
|
|
1256
|
-
|
|
1257
|
-
// The tint is LIGHT and weak, not dark: dark glass over dark content disappears, and has to be
|
|
1258
|
-
// held together with a heavy rim \u2014 which makes the surface read as a chrome bead. Tint density
|
|
1259
|
-
// at the bevel is a separate value: physically the bevel bends light harder, but it doesn't
|
|
1260
|
-
// get hazier. Coupled together, the two produced a milky ring around the whole outline.
|
|
1261
|
-
float body = mix(VG_BODY_DENSITY, VG_BODY_DENSITY * u_edgeDensity, smoothstep(0.10, 0.62, t)) * density;
|
|
1262
|
-
float vignette = smoothstep(0.15, 1.0, inner) * 0.19 * density;
|
|
1263
|
-
// There's NO density boost on press here. It was meant as "the element stands out more under
|
|
1264
|
-
// the finger," but it pulls the body toward the tint, and the tint depends on polarity: over a
|
|
1265
|
-
// light backdrop (dark polarity) press ended up DARKENING the element. Presence is shown
|
|
1266
|
-
// instead by the vgTouchWarp field's deformation and the highlight blooming below \u2014 neither
|
|
1267
|
-
// cares about the sign of polarity.
|
|
1268
|
-
float a = max(body, vignette);
|
|
1269
|
-
|
|
1270
|
-
// Active state does NOT touch the body's color. Mixing in a fixed gray here flipped the
|
|
1271
|
-
// effect's sign depending on the backdrop: over dark the element lightened, over light it
|
|
1272
|
-
// darkened, for the exact same state. Active state is shown instead by the highlight and rim
|
|
1273
|
-
// lighting above: neither cares about the backdrop.
|
|
1274
|
-
|
|
1275
|
-
// THE ICON SITS BENEATH THE SURFACE, it isn't stuck on top of it: it used to be mixed in last,
|
|
1276
|
-
// over the highlight, and read as a flat sticker on volumetric glass. At the rim the normal
|
|
1277
|
-
// displaces it, like everything seen through the glass, while the highlight sits ON TOP \u2014 it
|
|
1278
|
-
// lives on the surface itself.
|
|
1279
|
-
//
|
|
1280
|
-
// There is NO shadow under the icon here. It used to be made from the difference of two offset
|
|
1281
|
-
// mask samples and produced a second outline along the edge \u2014 the icon's borders looked
|
|
1282
|
-
// ragged.
|
|
1283
|
-
// THE INK'S SHIFT IS SET BY ITS OWN DEPTH, NOT BY BEVEL WIDTH. The ink sits right at the
|
|
1284
|
-
// surface, and it's displaced by exactly the thin layer of glass above it \u2014 not by how wide a
|
|
1285
|
-
// bevel this particular piece of glass happens to have. It used to be a fraction of the bevel:
|
|
1286
|
-
// on thin glass the shift came out to 4 dp and everything lined up, but on thick glass it was
|
|
1287
|
-
// 8, and cover art inset from the edge by 6 stretched toward the rim and spilled past its
|
|
1288
|
-
// bounds. The ceiling is absolute: ink has one depth regardless of the glass.
|
|
1289
|
-
float2 inkShift = n * (min(u_bevel * 0.5, VG_INK_DEPTH) * t);
|
|
1290
|
-
float2 inkUv = u_center + p - inkShift;
|
|
1291
|
-
|
|
1292
|
-
// INK LOSES SHARPNESS UNDER THE FINGER (reference \xA76): in the reference, a glyph under
|
|
1293
|
-
// pressure drowns in milk rather than simply getting lighter. This branch is uniform across
|
|
1294
|
-
// the whole element: press arrives as a uniform, so defocus costs nothing while the element
|
|
1295
|
-
// isn't being touched.
|
|
1296
|
-
float defocus = VG_INK_DEFOCUS * u_touchRadius * u_touchPress;
|
|
1297
|
-
float2 inkDx = float2(defocus, 0.0);
|
|
1298
|
-
float2 inkDy = float2(0.0, defocus);
|
|
1299
|
-
|
|
1300
|
-
half4 ink;
|
|
1301
|
-
half4 over;
|
|
1302
|
-
if (defocus > 0.01) {
|
|
1303
|
-
// A cross around the center with double weight in the middle: the ink mask is
|
|
1304
|
-
// high-contrast, and four samples are enough for a stroke to stop holding its edge.
|
|
1305
|
-
ink = (u_icon.eval((inkUv - inkDx) * u_iconScale)
|
|
1306
|
-
+ u_icon.eval((inkUv + inkDx) * u_iconScale)
|
|
1307
|
-
+ u_icon.eval((inkUv - inkDy) * u_iconScale)
|
|
1308
|
-
+ u_icon.eval((inkUv + inkDy) * u_iconScale)
|
|
1309
|
-
+ u_icon.eval(inkUv * u_iconScale) * 2.0) * half(u_iconOn / 6.0);
|
|
1310
|
-
// Colored content lives in the same plane as the ink and blurs together with it: different
|
|
1311
|
-
// sharpness for two layers in one plane would read as a defect, not a press.
|
|
1312
|
-
over = (u_overlay.eval((inkUv - inkDx) * u_iconScale)
|
|
1313
|
-
+ u_overlay.eval((inkUv + inkDx) * u_iconScale)
|
|
1314
|
-
+ u_overlay.eval((inkUv - inkDy) * u_iconScale)
|
|
1315
|
-
+ u_overlay.eval((inkUv + inkDy) * u_iconScale)
|
|
1316
|
-
+ u_overlay.eval(inkUv * u_iconScale) * 2.0) * half(u_overlayOn * u_appear / 6.0);
|
|
1317
|
-
} else {
|
|
1318
|
-
ink = u_icon.eval(inkUv * u_iconScale) * half(u_iconOn);
|
|
1319
|
-
over = u_overlay.eval(inkUv * u_iconScale) * half(u_overlayOn * u_appear);
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
// The backing under the ink is held by the LENS (body density from the probe), not the
|
|
1323
|
-
// surface: legibility is a property of the material, and computing it here a second time
|
|
1324
|
-
// would give two different answers to one question.
|
|
1325
|
-
|
|
1326
|
-
// Layers are composited premultiplied-over: in straight alpha, a nearly transparent dark body
|
|
1327
|
-
// used to pull the color of semi-transparent ink and the finger's light toward itself \u2014 letter
|
|
1328
|
-
// edges grayed out, pressing darkened things.
|
|
1329
|
-
half3 pm = col * half(a);
|
|
1330
|
-
half inkA = ink.g * half(mix(0.82, 1.0, u_active) * u_appear);
|
|
1331
|
-
half3 inkCol = mix(half3(u_inkIdle.rgb), half3(u_inkActive.rgb), half(u_active));
|
|
1332
|
-
pm = pm * (1.0 - inkA) + inkCol * inkA;
|
|
1333
|
-
a = a + float(inkA) * (1.0 - a);
|
|
1334
|
-
|
|
1335
|
-
// COLORED CONTENT LIVES IN THE SAME PLACE AS THE INK \u2014 inside the material, at the same
|
|
1336
|
-
// coordinate. Otherwise deformation would carry them apart: on press, the title and artist
|
|
1337
|
-
// name would shake together with the surface while the cover art stayed put, because it used
|
|
1338
|
-
// to be a separate layer on top of the glass. Polarity doesn't touch it \u2014 it has its own
|
|
1339
|
-
// color, and there's nothing to swap it for.
|
|
1340
|
-
pm = pm * (1.0 - over.a) + over.rgb * over.a;
|
|
1341
|
-
a = a + float(over.a) * (1.0 - a);
|
|
1342
|
-
|
|
1343
|
-
// Light from the finger is gathered by the LENS: it's a concentration of the surroundings, not
|
|
1344
|
-
// the glass's own whiteness. The surface is left with only what the glass spills outward \u2014 the
|
|
1345
|
-
// halo above.
|
|
1346
|
-
|
|
1347
|
-
float mask = 1.0 - smoothstep(-1.0, 1.0, sd);
|
|
1348
|
-
pm = clamp(pm, half3(0.0), half3(1.0)) * half(mask);
|
|
1349
|
-
a *= mask;
|
|
1350
|
-
pm = max(pm + half3(vgShadowTint(u_ambient)) * half(VG_SHADOW_TINT * shade * (1.0 - a)), half3(0.0));
|
|
1351
|
-
return half4(pm, half(a + shade * (1.0 - a)));
|
|
1352
|
-
}
|
|
1353
|
-
`;
|
|
1354
|
-
|
|
1355
|
-
// src/reference-scene.ts
|
|
1356
|
-
var REFERENCE_SHAPES = {
|
|
1357
|
-
circle: circleGeometry(120),
|
|
1358
|
-
capsule: capsuleGeometry(300, 110),
|
|
1359
|
-
tile: roundedRectGeometry(220, 120, 32)
|
|
1360
|
-
};
|
|
1361
|
-
var REFERENCE_SCENE_WIDTH = 336;
|
|
1362
|
-
var REFERENCE_SCENE_HEIGHT = 252;
|
|
1363
|
-
var REFERENCE_SURROUND = "#3c1f4a";
|
|
1364
|
-
var REFERENCE_PIECE_AT = {
|
|
1365
|
-
xDp: REFERENCE_SCENE_WIDTH / 2,
|
|
1366
|
-
yDp: REFERENCE_SCENE_HEIGHT / 2
|
|
1367
|
-
};
|
|
1368
|
-
var whole = (layer) => [
|
|
1369
|
-
{ heightDp: REFERENCE_SCENE_HEIGHT, layer }
|
|
1370
|
-
];
|
|
1371
|
-
var stripeLevel = (level) => level < 0.5 ? level + 0.22 : level - 0.22;
|
|
1372
|
-
var REFERENCE_SCENES = [
|
|
1373
|
-
{
|
|
1374
|
-
name: "flat",
|
|
1375
|
-
what: "the body standing off the backdrop: the element must be visible over a flat canvas",
|
|
1376
|
-
level: 0.5,
|
|
1377
|
-
bands: (level) => whole({ kind: "flat", level })
|
|
1378
|
-
},
|
|
1379
|
-
{
|
|
1380
|
-
name: "stripes",
|
|
1381
|
-
what: "the window: the brightness range inside the element is a noticeable fraction of the range outside it",
|
|
1382
|
-
level: 0.5,
|
|
1383
|
-
bands: (level) => whole({ kind: "stripes", level, other: stripeLevel(level), periodDp: 24, widthDp: 10 })
|
|
1384
|
-
},
|
|
1385
|
-
{
|
|
1386
|
-
name: "busy",
|
|
1387
|
-
what: "ink against content: both the ink and whatever is under the glass have to live over a busy cover",
|
|
1388
|
-
level: 0.5,
|
|
1389
|
-
bands: (level) => whole({ kind: "checker", level, amp: 0.28, cellDp: 16 })
|
|
1390
|
-
},
|
|
1391
|
-
{
|
|
1392
|
-
name: "bar",
|
|
1393
|
-
what: "the rim gathers content: at the silhouette the lens bulges out whatever sits behind it",
|
|
1394
|
-
level: 0.93,
|
|
1395
|
-
bands: (level) => whole({ kind: "bar", level, barLevel: 0.12, thicknessDp: 20 })
|
|
1396
|
-
},
|
|
1397
|
-
{
|
|
1398
|
-
name: "steps",
|
|
1399
|
-
what: "the body profile at five levels at once \u2014 platforms are compared against it",
|
|
1400
|
-
level: 0.5,
|
|
1401
|
-
bands: () => [
|
|
1402
|
-
{ heightDp: 36, layer: { kind: "flat", level: 0.1 } },
|
|
1403
|
-
{ heightDp: 36, layer: { kind: "flat", level: 0.3 } },
|
|
1404
|
-
{ heightDp: 36, layer: { kind: "flat", level: 0.5 } },
|
|
1405
|
-
{ heightDp: 36, layer: { kind: "flat", level: 0.69 } },
|
|
1406
|
-
{ heightDp: 36, layer: { kind: "flat", level: 0.9 } },
|
|
1407
|
-
{ heightDp: 36, layer: { kind: "step", left: 0.05, right: 0.28 } },
|
|
1408
|
-
{ heightDp: 36, layer: { kind: "bar", level: 0.92, barLevel: 0.12, thicknessDp: 6 } }
|
|
1409
|
-
]
|
|
1410
|
-
},
|
|
1411
|
-
{
|
|
1412
|
-
name: "edge",
|
|
1413
|
-
what: "tint at a hard break: one half under the glass is black, the other white",
|
|
1414
|
-
level: 0.5,
|
|
1415
|
-
bands: () => whole({ kind: "step", left: 0.03, right: 0.95 })
|
|
1416
|
-
},
|
|
1417
|
-
{
|
|
1418
|
-
name: "gradient",
|
|
1419
|
-
what: "ink polarity: where the automatic switch flips the ink from light to dark",
|
|
1420
|
-
level: 0.5,
|
|
1421
|
-
bands: () => whole({ kind: "gradient", from: 0.04, to: 0.96, steps: 28 })
|
|
1422
|
-
},
|
|
1423
|
-
{
|
|
1424
|
-
name: "grid",
|
|
1425
|
-
what: "lens distortion: drift, tearing and jitter of a straight line show up immediately (by eye, not by number)",
|
|
1426
|
-
level: 0.91,
|
|
1427
|
-
measurable: false,
|
|
1428
|
-
bands: (level) => whole({ kind: "grid", level, lineLevel: level > 0.5 ? level - 0.14 : level + 0.14, stepDp: 12 })
|
|
1429
|
-
}
|
|
1430
|
-
];
|
|
1431
|
-
var referenceScene = (name) => {
|
|
1432
|
-
const scene = REFERENCE_SCENES.find((s) => s.name === name);
|
|
1433
|
-
if (!scene) throw new Error(`vireglass: no reference canvas "${name}"`);
|
|
1434
|
-
return scene;
|
|
1435
|
-
};
|
|
1436
|
-
var refGray = (level) => {
|
|
1437
|
-
const v = Math.round(Math.min(Math.max(level, 0), 1) * 255).toString(16).padStart(2, "0");
|
|
1438
|
-
return `#${v}${v}${v}`;
|
|
1439
|
-
};
|
|
1440
|
-
var refCheckerCells = (layer, widthDp, heightDp) => {
|
|
1441
|
-
const cells = [];
|
|
1442
|
-
const lo = layer.level - layer.amp;
|
|
1443
|
-
const hi = layer.level + layer.amp;
|
|
1444
|
-
let seed = 1;
|
|
1445
|
-
for (let y = 0; y < heightDp; y += layer.cellDp) {
|
|
1446
|
-
for (let x = 0; x < widthDp; x += layer.cellDp) {
|
|
1447
|
-
seed = seed * 1103515245 + 12345 & 2147483647;
|
|
1448
|
-
cells.push({ xDp: x, yDp: y, level: (seed >> 16) % 2 ? hi : lo });
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
return cells;
|
|
1452
|
-
};
|
|
1453
|
-
var refGradientSteps = (layer) => Array.from(
|
|
1454
|
-
{ length: layer.steps },
|
|
1455
|
-
(_, i) => layer.from + (layer.to - layer.from) * i / Math.max(layer.steps - 1, 1)
|
|
1456
|
-
);
|
|
1457
|
-
|
|
1458
1122
|
// src/adapters.ts
|
|
1459
1123
|
var NO_TOUCH = {
|
|
1460
1124
|
x: 0,
|
|
@@ -1468,7 +1132,7 @@ var NO_TOUCH = {
|
|
|
1468
1132
|
};
|
|
1469
1133
|
var NO_MORPH = { offsetX: 0, offsetY: 0, width: 0, height: 0, cornerRadius: 0, smoothing: 0 };
|
|
1470
1134
|
var NO_PROGRESS = -1;
|
|
1471
|
-
var ACCENT_AMOUNT =
|
|
1135
|
+
var ACCENT_AMOUNT = ACCENT.amount;
|
|
1472
1136
|
var NO_ACCENT = [0, 0, 0, 0];
|
|
1473
1137
|
var lensMagnify = (o) => 1 + (o.refractionScale - 1) * o.refraction;
|
|
1474
1138
|
var shapes = /* @__PURE__ */ new Map();
|
|
@@ -1638,7 +1302,13 @@ var REST_LIGHT = [-0.577, -0.817];
|
|
|
1638
1302
|
var DYNAMIC_UNIFORMS = ["u_press", "u_active"];
|
|
1639
1303
|
var ICON_UNIFORMS = ["u_iconOn", "u_iconScale", "u_inkIdle", "u_inkActive"];
|
|
1640
1304
|
var OVERLAY_UNIFORMS = ["u_overlayOn"];
|
|
1641
|
-
|
|
1305
|
+
function neckFor(half, aw, ah, bw, bh, offsetX, offsetY) {
|
|
1306
|
+
const gx = Math.abs(offsetX) - (aw + bw) / 2;
|
|
1307
|
+
const gy = Math.abs(offsetY) - (ah + bh) / 2;
|
|
1308
|
+
const gap = Math.hypot(Math.max(gx, 0), Math.max(gy, 0));
|
|
1309
|
+
const widest = Math.min(aw, ah, bw, bh);
|
|
1310
|
+
return Math.min(Math.max(half * MORPH.neck, neckToBridge(gap) * MORPH.fuse), widest);
|
|
1311
|
+
}
|
|
1642
1312
|
function morphBetween(a, b, offsetX, offsetY, t) {
|
|
1643
1313
|
if (t <= 0) return void 0;
|
|
1644
1314
|
const half = Math.min(halfMinDp(a), halfMinDp(b));
|
|
@@ -1648,94 +1318,28 @@ function morphBetween(a, b, offsetX, offsetY, t) {
|
|
|
1648
1318
|
width: b.width,
|
|
1649
1319
|
height: b.height,
|
|
1650
1320
|
cornerRadius: b.cornerRadius,
|
|
1651
|
-
smoothing: half
|
|
1321
|
+
smoothing: neckFor(half, a.width, a.height, b.width, b.height, offsetX, offsetY) * Math.min(t, 1)
|
|
1652
1322
|
};
|
|
1653
1323
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
}
|
|
1671
|
-
function convertEvalCalls(src) {
|
|
1672
|
-
const samplers = /* @__PURE__ */ new Set();
|
|
1673
|
-
const declRe = /uniform\s+sampler2D\s+(\w+)\s*;/g;
|
|
1674
|
-
for (let m = declRe.exec(src); m; m = declRe.exec(src)) samplers.add(m[1]);
|
|
1675
|
-
if (samplers.size === 0) return src;
|
|
1676
|
-
const callRe = /(\w+)\.eval\(/g;
|
|
1677
|
-
let out = "";
|
|
1678
|
-
let cursor = 0;
|
|
1679
|
-
for (let m = callRe.exec(src); m; m = callRe.exec(src)) {
|
|
1680
|
-
const name = m[1];
|
|
1681
|
-
if (!samplers.has(name)) continue;
|
|
1682
|
-
const argStart = m.index + m[0].length;
|
|
1683
|
-
let depth = 1;
|
|
1684
|
-
let i = argStart;
|
|
1685
|
-
while (i < src.length && depth > 0) {
|
|
1686
|
-
if (src[i] === "(") depth += 1;
|
|
1687
|
-
else if (src[i] === ")") depth -= 1;
|
|
1688
|
-
i += 1;
|
|
1689
|
-
}
|
|
1690
|
-
const args = src.slice(argStart, i - 1);
|
|
1691
|
-
out += src.slice(cursor, m.index);
|
|
1692
|
-
out += `texture(${name}, (${args}) / ${sizeUniformName(name)})`;
|
|
1693
|
-
cursor = i;
|
|
1694
|
-
callRe.lastIndex = i;
|
|
1695
|
-
}
|
|
1696
|
-
out += src.slice(cursor);
|
|
1697
|
-
return out;
|
|
1698
|
-
}
|
|
1699
|
-
function convertReturns(mainBody) {
|
|
1700
|
-
return mainBody.replace(/\breturn\s+([^;]+);/g, "fragColor = $1; return;");
|
|
1701
|
-
}
|
|
1702
|
-
var ENTRY_RE = /half4\s+main\s*\(\s*float2\s+(\w+)\s*\)\s*\{/;
|
|
1703
|
-
function convertEntryPoint(src) {
|
|
1704
|
-
const match = ENTRY_RE.exec(src);
|
|
1705
|
-
if (!match) {
|
|
1706
|
-
throw new Error('vireglass/targets/glsl: entry point "half4 main(float2 xy)" not found');
|
|
1707
|
-
}
|
|
1708
|
-
const bodyStart = match.index + match[0].length;
|
|
1709
|
-
let depth = 1;
|
|
1710
|
-
let i = bodyStart;
|
|
1711
|
-
while (i < src.length && depth > 0) {
|
|
1712
|
-
if (src[i] === "{") depth += 1;
|
|
1713
|
-
else if (src[i] === "}") depth -= 1;
|
|
1714
|
-
i += 1;
|
|
1715
|
-
}
|
|
1716
|
-
const bodyEnd = i - 1;
|
|
1717
|
-
const param = match[1];
|
|
1718
|
-
const body = convertReturns(src.slice(bodyStart, bodyEnd));
|
|
1719
|
-
const head = src.slice(0, match.index);
|
|
1720
|
-
const tail = src.slice(i);
|
|
1721
|
-
return `${head}void main() {
|
|
1722
|
-
vec2 ${param} = vec2(gl_FragCoord.x, u_resolution.y - gl_FragCoord.y);
|
|
1723
|
-
${body}}${tail}`;
|
|
1724
|
-
}
|
|
1725
|
-
function addPrologue(src) {
|
|
1726
|
-
return "#version 300 es\nprecision highp float;\n\nout vec4 fragColor;\nuniform vec2 u_resolution;\n\n" + src;
|
|
1727
|
-
}
|
|
1728
|
-
function toGLSL(shaderSource) {
|
|
1729
|
-
let out = shaderSource;
|
|
1730
|
-
out = convertEntryPoint(out);
|
|
1731
|
-
out = convertShaderUniforms(out);
|
|
1732
|
-
out = convertEvalCalls(out);
|
|
1733
|
-
out = convertVecTypes(out);
|
|
1734
|
-
out = convertHalfTypes(out);
|
|
1735
|
-
out = addPrologue(out);
|
|
1736
|
-
return out;
|
|
1324
|
+
function morphOutOf(source, target, offsetX, offsetY, t) {
|
|
1325
|
+
if (t <= 0) return void 0;
|
|
1326
|
+
const k = Math.min(t, 1);
|
|
1327
|
+
const width = source.width + (target.width - source.width) * k;
|
|
1328
|
+
const height = source.height + (target.height - source.height) * k;
|
|
1329
|
+
const cornerRadius = source.cornerRadius + (target.cornerRadius - source.cornerRadius) * k;
|
|
1330
|
+
const half = Math.min(halfMinDp(source), Math.min(width, height) / 2);
|
|
1331
|
+
return {
|
|
1332
|
+
offsetX: offsetX * k,
|
|
1333
|
+
offsetY: offsetY * k,
|
|
1334
|
+
width,
|
|
1335
|
+
height,
|
|
1336
|
+
cornerRadius,
|
|
1337
|
+
// The lobe travels as it grows, so the span changes every frame and the neck is measured
|
|
1338
|
+
// against where the lobe actually is, not against where it will end up.
|
|
1339
|
+
smoothing: neckFor(half, source.width, source.height, width, height, offsetX * k, offsetY * k)
|
|
1340
|
+
};
|
|
1737
1341
|
}
|
|
1738
1342
|
|
|
1739
|
-
export { ACCENT_AMOUNT, ADAPT_RADIUS, ALL_EFFECTS_ON, DEBUG_MODES, DYNAMIC_UNIFORMS, EFFECTS, FRESNEL_EXPONENT, ICON_UNIFORMS, LEGACY_NAMES, LEGACY_OPTICS, LENS_SHADER, MATERIAL_PRESETS, MATERIAL_RANGES,
|
|
1740
|
-
//# sourceMappingURL=chunk-
|
|
1741
|
-
//# sourceMappingURL=chunk-
|
|
1343
|
+
export { ACCENT_AMOUNT, ADAPT_RADIUS, ALL_EFFECTS_ON, DEBUG_MODES, DYNAMIC_UNIFORMS, EFFECTS, FRESNEL_EXPONENT, ICON_UNIFORMS, LEGACY_NAMES, LEGACY_OPTICS, LENS_SHADER, MATERIAL_PRESETS, MATERIAL_RANGES, OVERLAY_UNIFORMS, PRESET_NAMES, PRODUCT_DIM, REST_LIGHT, SURFACE_LENGTH_UNIFORMS, VG_FALLOFF, VG_SDF, VIREGLASS_CLEAR_MATERIAL, VIREGLASS_CONTROL_MATERIAL, VIREGLASS_LYRICS_MATERIAL, VIREGLASS_MATERIAL, VIREGLASS_MATERIAL_V4, VIREGLASS_MATERIAL_V5, VIREGLASS_SHEET_MATERIAL, absorption, activeMaterial, applyToggles, blur, bodyDensity, colorPickup, debugIndex, diffraction, dispersion, edgeDensity, edgeLight, fresnelF0, fresnelStrength, gatherRadius, iorSpread, iridescence, lensMagnify, materialForInk, mediumTint, morphBetween, morphOutOf, neckToBridge, refractionScale, refractionStrength, resolveMaterial, resolveOptics, sceneDistance, sceneGradient, sdfRoundedRect, sdfRoundedRectGradient, smin, specularPower, specularStrength, toDeviceSurfaceUniforms, toLensProps, toSurfaceUniforms, touchWarp };
|
|
1344
|
+
//# sourceMappingURL=chunk-IQOBZGCF.js.map
|
|
1345
|
+
//# sourceMappingURL=chunk-IQOBZGCF.js.map
|