- New Sloped wall piece: a half-block triangle in any wall material, at half a wall's cost. Dragging walls or slopes at about 45° lays a diagonal line; the drag picks the slope's orientation and T flips or turns it. - Each slope fills one cell past its corner, so a diagonal run is sealed against grains and water. - New test/t_slopes.js: a slope funnel passes 400/400 sand (square steps 379/400). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
338 lines
17 KiB
JavaScript
338 lines
17 KiB
JavaScript
// Smooth shaders: vertex and fragment programs for the rounded world look.
|
|
const SM_VS = `#version 300 es
|
|
void main() {
|
|
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
|
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
|
|
}`;
|
|
|
|
const SM_FS = `#version 300 es
|
|
precision highp float;
|
|
precision highp int;
|
|
uniform sampler2D tCol, tColL, tBack, tTree, tField, tLight, tGlow;
|
|
uniform vec2 uSize, uOrigin, uLightSize, uGlowSize;
|
|
uniform vec4 uView, uLightMap;
|
|
uniform float uGlowK, uTime, uSurf, uRain;
|
|
out vec4 o;
|
|
|
|
float h21(vec2 p) { p = fract(p * vec2(123.34, 456.21)); p += dot(p, p + 45.32); return fract(p.x * p.y); }
|
|
float vn(vec2 p) {
|
|
vec2 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f);
|
|
return mix(mix(h21(i), h21(i + vec2(1, 0)), f.x), mix(h21(i + vec2(0, 1)), h21(i + vec2(1, 1)), f.x), f.y);
|
|
}
|
|
float fbm(vec2 p) { float a = 0.5, s = 0.0; for (int k = 0; k < 4; k++) { s += a * vn(p); p = p * 2.03 + 17.1; a *= 0.5; } return s / 0.9375; }
|
|
|
|
vec4 lin(sampler2D t, vec2 q) { return textureLod(t, q / uSize, 0.0); }
|
|
// cubic B-spline from four bilinear taps: rounds a field of 0s and 1s into smooth shapes
|
|
vec4 bsp(sampler2D t, vec2 q) {
|
|
vec2 tc = q - 0.5, i = floor(tc), f = tc - i, f2 = f * f, f3 = f2 * f;
|
|
vec2 w0 = (1.0 - 3.0 * f + 3.0 * f2 - f3) / 6.0, w1 = (4.0 - 6.0 * f2 + 3.0 * f3) / 6.0;
|
|
vec2 w2 = (1.0 + 3.0 * f + 3.0 * f2 - 3.0 * f3) / 6.0, w3 = f3 / 6.0;
|
|
vec2 g0 = w0 + w1, g1 = w2 + w3;
|
|
vec2 h0 = (i - 0.5 + w1 / g0) / uSize, h1 = (i + 1.5 + w3 / g1) / uSize;
|
|
return g0.y * (g0.x * textureLod(t, h0, 0.0) + g1.x * textureLod(t, vec2(h1.x, h0.y), 0.0))
|
|
+ g1.y * (g0.x * textureLod(t, vec2(h0.x, h1.y), 0.0) + g1.x * textureLod(t, h1, 0.0));
|
|
}
|
|
vec4 cellAt(sampler2D t, ivec2 c) { return texelFetch(t, clamp(c, ivec2(0), ivec2(uSize) - 1), 0); }
|
|
int cls(vec4 c) { return int(c.a * 255.0 + 0.5); }
|
|
float isT(int n) { return (n == 1 || n == 4 || n == 6 || (n >= 7 && n <= 12)) ? 1.0 : 0.0; }
|
|
float isM(int n) { return n == 2 ? 1.0 : isT(n); } // matter: ground, built pieces or loose grains
|
|
float isF(int n) { return (n == 13 || n == 14) ? 1.0 : 0.0; }
|
|
float isBelt(int n) { return (n == 7 || n == 8) ? 1.0 : 0.0; }
|
|
float isN(int n, int k) { return n == k ? 1.0 : 0.0; }
|
|
// signed distance (in grains) to the outline of one block of belt: a band 3.7 grains tall; where
|
|
// the line of belts ends, the end is square with rounded corners. Negative inside.
|
|
float beltSD(vec2 lb, bool endL, bool endR) {
|
|
float x0 = endL ? 0.15 : -2.0, x1 = endR ? 3.85 : 6.0, r = 0.75;
|
|
vec2 c = vec2((x0 + x1) * 0.5, 2.0), hs = vec2((x1 - x0) * 0.5, 1.85);
|
|
vec2 q = abs(lb - c) - hs + r;
|
|
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
|
|
}
|
|
// The outline of the belt or filter in cell c. Belts and filters side by side are one belt, so
|
|
// the line only rounds off where the whole run ends.
|
|
float beltLineSD(ivec2 c, vec2 lb) {
|
|
int nr = cls(cellAt(tCol, c + ivec2(4, 0))), nl = cls(cellAt(tCol, c - ivec2(4, 0)));
|
|
return beltSD(lb, isBelt(nl) + isF(nl) < 0.5, isBelt(nr) + isF(nr) < 0.5);
|
|
}
|
|
// The rubber band of a belt or filter: 1 on a ridge. Ridges travel along the top run one way and
|
|
// back along the bottom.
|
|
float bandRidge(vec2 w, vec2 lb, float dir) {
|
|
float run = lb.y < 2.0 ? 1.0 : -1.0;
|
|
return step(0.72, fract((w.x - dir * run * uTime * 30.0) * 0.5));
|
|
}
|
|
vec3 bandColor(vec2 lb, float rg) { return mix(vec3(0.2, 0.21, 0.24), vec3(0.34, 0.35, 0.39), rg) * (lb.y < 2.0 ? 1.0 : 0.72); }
|
|
|
|
void main() {
|
|
float zp = uView.z, px = 1.0 / zp;
|
|
vec2 sp = vec2(gl_FragCoord.x, uView.w - gl_FragCoord.y);
|
|
vec2 q = sp * px + uView.xy; // position in grains, in the window of grains uploaded
|
|
vec2 w = q + uOrigin; // position in the world
|
|
vec2 cam = uOrigin + uView.xy; // world position of the screen's top-left corner
|
|
vec2 scr = q - uView.xy; // grains from the screen's top-left corner
|
|
float shc = uView.w * px; // screen height in grains
|
|
ivec2 c = ivec2(floor(q));
|
|
vec2 qq = q - 0.5; ivec2 c0 = ivec2(floor(qq)); vec2 t = qq - floor(qq);
|
|
vec4 k0 = cellAt(tCol, c0), k1 = cellAt(tCol, c0 + ivec2(1, 0)), k2 = cellAt(tCol, c0 + ivec2(0, 1)), k3 = cellAt(tCol, c0 + ivec2(1, 1));
|
|
int n0 = cls(k0), n1 = cls(k1), n2 = cls(k2), n3 = cls(k3);
|
|
vec4 own = cellAt(tCol, c); int nc = cls(own);
|
|
vec2 s = smoothstep(0.1, 0.9, t);
|
|
vec4 wq = vec4((1.0 - s.x) * (1.0 - s.y), s.x * (1.0 - s.y), (1.0 - s.x) * s.y, s.x * s.y);
|
|
|
|
// fields, and everything that needs screen derivatives (worked out before any branching)
|
|
vec4 Fb = bsp(tField, q);
|
|
vec4 F = mix(lin(tField, q), Fb, 0.62);
|
|
// loose-grain occupancy from the four grains around (bilinear): ground and grains together make "matter"
|
|
vec4 wl = vec4((1.0 - t.x) * (1.0 - t.y), t.x * (1.0 - t.y), (1.0 - t.x) * t.y, t.x * t.y);
|
|
float pw = dot(wl, vec4(isN(n0, 2), isN(n1, 2), isN(n2, 2), isN(n3, 2)));
|
|
float M = clamp(F.r + pw, 0.0, 1.0);
|
|
float lf = clamp(mix(F.g, Fb.g, 0.6) + F.r + pw, 0.0, 1.0);
|
|
float fwT = max(fwidth(F.r), 1e-4), fwM = max(fwidth(M), 1e-4), fwA = max(fwidth(F.a), 1e-4), fwL = max(fwidth(lf), 1e-4);
|
|
vec2 gT = vec2(dFdx(mix(Fb.r, M, 0.5)), dFdy(mix(Fb.r, M, 0.5))), gL = vec2(dFdx(F.g), dFdy(F.g));
|
|
|
|
// ---- behind everything: sky with clouds and far hills, or the cave's back wall
|
|
vec4 back = lin(tBack, q);
|
|
vec3 C = back.rgb;
|
|
float sky = back.a;
|
|
if (sky > 0.01) {
|
|
vec3 S = C;
|
|
vec2 P0 = scr + cam * 0.08;
|
|
// clouds; while it rains they thicken and turn grey
|
|
float cl = smoothstep(0.56 - 0.3 * uRain, 0.78 - 0.2 * uRain, fbm(vec2(P0.x * 0.016 + uTime * 0.006, P0.y * 0.05)));
|
|
float hi = clamp((uSurf - 40.0 - w.y) / 50.0, 0.0, 1.0);
|
|
S = mix(S, mix(vec3(0.96, 0.95, 0.93), vec3(0.5, 0.52, 0.56), uRain), cl * (0.6 + 0.3 * uRain) * hi);
|
|
float k1 = 0.16; vec2 P1 = scr + cam * k1;
|
|
float base1 = shc * 0.5 + 8.0 + (uSurf - shc * 0.5) * k1;
|
|
float top1 = base1 - 36.0 * (0.25 + 0.9 * fbm(vec2(P1.x * 0.009, 1.7)));
|
|
float m1 = smoothstep(top1 - px * 1.5, top1 + px * 1.5, P1.y);
|
|
vec3 far = mix(vec3(0.50, 0.60, 0.72), S, 0.35) * (0.94 + 0.08 * fbm(P1 * 0.05));
|
|
S = mix(S, far, m1);
|
|
float k2 = 0.38; vec2 P2 = scr + cam * k2;
|
|
float base2 = shc * 0.5 + 14.0 + (uSurf - shc * 0.5) * k2;
|
|
float top2 = base2 - 20.0 * (0.2 + 0.9 * fbm(vec2(P2.x * 0.016, 7.3)));
|
|
float m2 = smoothstep(top2 - px * 1.5, top2 + px * 1.5, P2.y);
|
|
vec3 near = vec3(0.36, 0.5, 0.47) * (0.92 + 0.14 * fbm(P2 * 0.08));
|
|
near = mix(near, S, 0.42 - 0.2 * smoothstep(top2, top2 + 30.0, P2.y));
|
|
S = mix(S, near, m2);
|
|
C = mix(C, S, sky);
|
|
}
|
|
// the back wall: rough rock, and darker close to the tunnel walls (ambient occlusion)
|
|
float notSky = 1.0 - sky;
|
|
C *= mix(1.0, 1.08 + 0.4 * fbm(w * 0.21), notSky);
|
|
float near = textureLod(tField, q / uSize, 2.2).r;
|
|
C *= 1.0 - 0.34 * smoothstep(0.12, 0.85, near) * (1.0 - 0.75 * sky);
|
|
|
|
// ---- trees (behind the ground and everything on it)
|
|
if (F.a > 0.2) {
|
|
vec4 a0 = cellAt(tTree, c0), a1 = cellAt(tTree, c0 + ivec2(1, 0)), a2 = cellAt(tTree, c0 + ivec2(0, 1)), a3 = cellAt(tTree, c0 + ivec2(1, 1));
|
|
vec4 m = vec4(step(0.4, a0.a), step(0.4, a1.a), step(0.4, a2.a), step(0.4, a3.a)) * wq;
|
|
float sm = m.x + m.y + m.z + m.w;
|
|
if (sm > 1e-4) {
|
|
vec3 tc = (a0.rgb * m.x + a1.rgb * m.y + a2.rgb * m.z + a3.rgb * m.w) / sm;
|
|
float leaf = (a0.a * m.x + a1.a * m.y + a2.a * m.z + a3.a * m.w) / sm;
|
|
float lv = smoothstep(0.6, 0.9, leaf);
|
|
tc *= mix(0.86 + 0.22 * vn(vec2(w.x * 1.4, w.y * 0.25)), 0.72 + 0.5 * fbm(w * 0.42), lv);
|
|
float d = (F.a - 0.36) / fwA;
|
|
tc *= mix(0.55, 1.0, smoothstep(0.4, 1.6, d));
|
|
C = mix(C, tc, smoothstep(-0.7, 0.7, d));
|
|
}
|
|
}
|
|
|
|
// ---- smoke, steam and flame: soft, see-through puffs
|
|
if (Fb.b > 0.02) {
|
|
vec4 m = vec4(isN(n0, 5), isN(n1, 5), isN(n2, 5), isN(n3, 5)) * wq;
|
|
float sm = m.x + m.y + m.z + m.w;
|
|
vec3 gc = sm > 1e-4 ? (k0.rgb * m.x + k1.rgb * m.y + k2.rgb * m.z + k3.rgb * m.w) / sm : own.rgb;
|
|
if (sm > 1e-4 || nc == 5) C = mix(C, gc, smoothstep(0.03, 0.55, Fb.b) * 0.92);
|
|
}
|
|
|
|
// ---- liquids: smooth surfaces that fill right up against the ground, with a glossy top line
|
|
float anyL = isN(n0, 3) + isN(n1, 3) + isN(n2, 3) + isN(n3, 3);
|
|
if (anyL > 0.0 && lf > 0.3) {
|
|
vec4 m = vec4(isN(n0, 3), isN(n1, 3), isN(n2, 3), isN(n3, 3)) * (wq + 0.02);
|
|
float sm = m.x + m.y + m.z + m.w;
|
|
vec3 lc = (k0.rgb * m.x + k1.rgb * m.y + k2.rgb * m.z + k3.rgb * m.w) / sm;
|
|
lc *= 0.94 + 0.1 * vn(w * 0.55 + vec2(uTime * 0.7, uTime * 0.25));
|
|
float gl = length(gL);
|
|
float topSide = gl > 1e-5 ? clamp(gL.y / gl, 0.0, 1.0) : 0.0;
|
|
float band = 1.0 - smoothstep(0.5, 0.75, F.g);
|
|
lc += vec3(0.16, 0.18, 0.2) * topSide * band;
|
|
C = mix(C, lc, smoothstep(0.5 - fwL, 0.5 + fwL, lf));
|
|
}
|
|
|
|
// ---- the ground: rounded, outlined, with a lit top edge and a shaded underside
|
|
if (F.r > 0.3) {
|
|
vec4 m = vec4(isT(n0), isT(n1), isT(n2), isT(n3)) * wq;
|
|
float sm = m.x + m.y + m.z + m.w;
|
|
vec3 blur = bsp(tColL, q).rgb, tc = own.rgb;
|
|
float bd = 1e9;
|
|
if (m.x > 0.0) { vec3 e = k0.rgb - blur; float dd = dot(e, e); if (dd < bd) { bd = dd; tc = k0.rgb; } }
|
|
if (m.y > 0.0) { vec3 e = k1.rgb - blur; float dd = dot(e, e); if (dd < bd) { bd = dd; tc = k1.rgb; } }
|
|
if (m.z > 0.0) { vec3 e = k2.rgb - blur; float dd = dot(e, e); if (dd < bd) { bd = dd; tc = k2.rgb; } }
|
|
if (m.w > 0.0) { vec3 e = k3.rgb - blur; float dd = dot(e, e); if (dd < bd) { bd = dd; tc = k3.rgb; } }
|
|
if (sm < 1e-4) tc = own.rgb;
|
|
tc *= 0.84 + 0.3 * fbm(w * 0.28);
|
|
tc *= 0.95 + 0.1 * vn(w * 1.6);
|
|
float gl = length(gT);
|
|
vec2 nrm = gl > 1e-6 ? -gT / gl : vec2(0.0);
|
|
float band = 1.0 - smoothstep(0.5, 0.9, M);
|
|
float lit = dot(nrm, normalize(vec2(-0.5, -1.0)));
|
|
tc *= 1.0 + 0.42 * lit * band;
|
|
// Keep the air outline; a softer, screen-width seam also separates rock from loose grains.
|
|
float dm = (M - 0.5) / fwM;
|
|
tc *= mix(0.4, 1.0, smoothstep(0.5, 1.4 + zp * 0.08, dm));
|
|
float dt = (F.r - 0.5) / fwT;
|
|
if (nc == 1 && pw > 0.0) tc *= mix(0.65, 1.0, smoothstep(0.0, 1.5, dt));
|
|
C = mix(C, tc, smoothstep(-0.6, 0.6, (F.r - 0.5) / fwT));
|
|
}
|
|
|
|
// ---- loose grains: little round pebbles; a pocket closed in by four grains is filled
|
|
int np = int(isN(n0, 2) + isN(n1, 2) + isN(n2, 2) + isN(n3, 2));
|
|
bool closed = isM(n0) + isM(n1) + isM(n2) + isM(n3) > 3.5;
|
|
if (np > 0) {
|
|
float best = 1e9; vec3 gc = vec3(0.0); vec2 gd = vec2(0.0); float gr = 0.6;
|
|
for (int k = 0; k < 4; k++) {
|
|
ivec2 o4 = ivec2(k & 1, k >> 1);
|
|
int nk = k == 0 ? n0 : k == 1 ? n1 : k == 2 ? n2 : n3;
|
|
if (nk != 2) continue;
|
|
vec4 ck = k == 0 ? k0 : k == 1 ? k1 : k == 2 ? k2 : k3;
|
|
vec2 ce = vec2(c0 + o4) + 0.5;
|
|
float r = 0.6 + 0.1 * (h21(ce + uOrigin) - 0.5);
|
|
vec2 dv = (q - ce) / r;
|
|
float dd = length(dv);
|
|
if (dd < best) { best = dd; gc = ck.rgb; gd = dv; gr = r; }
|
|
}
|
|
float cover = np == 4 ? 1.0 : 1.0 - smoothstep(1.0 - px / gr, 1.0 + px / gr, best);
|
|
if (closed && F.r < 0.5) cover = 1.0;
|
|
// Pebbles stop at the rock face so they cannot paint over its thin seam.
|
|
cover *= 1.0 - smoothstep(-0.6, 0.6, (F.r - 0.5) / fwT);
|
|
if (cover > 0.0) {
|
|
float r2 = min(1.0, dot(gd, gd));
|
|
vec3 n3 = normalize(vec3(gd * 0.85, sqrt(1.0 - r2) + 0.25));
|
|
float sh = 0.98 + 0.18 * max(0.0, dot(n3, normalize(vec3(-0.45, -0.8, 0.6))));
|
|
sh *= mix(1.0, 0.94, smoothstep(0.7 - px * 0.2, 1.15 + px * 0.2, best));
|
|
vec3 pc = (np == 4 ? (k0.rgb * wq.x + k1.rgb * wq.y + k2.rgb * wq.z + k3.rgb * wq.w) : gc) * sh;
|
|
if (!closed) pc *= mix(1.0, 0.72, smoothstep(0.7, 1.0, best));
|
|
C = mix(C, pc, cover);
|
|
}
|
|
}
|
|
|
|
// ---- built pieces: crisp, and wall blocks drawn as bevelled tiles with mortar lines
|
|
if (nc == 4) C = own.rgb;
|
|
else if (nc == 7 || nc == 8) {
|
|
// belt: a rubber band round two copper rollers per block; the top run moves the load
|
|
float dir = nc == 8 ? 1.0 : -1.0;
|
|
vec2 lb = fract(w * 0.25) * 4.0;
|
|
// outline: square ends with rounded corners where the line of belts (and filters) ends
|
|
float sd = beltLineSD(c, lb);
|
|
float inside = 1.0 - smoothstep(-px, px, sd);
|
|
if (inside > 0.0) {
|
|
float edge = -sd;
|
|
vec3 bc;
|
|
if (edge < 0.8) {
|
|
bc = bandColor(lb, bandRidge(w, lb, dir));
|
|
bc *= mix(0.55, 1.0, smoothstep(0.0, max(px * 1.4, 0.12), edge));
|
|
} else {
|
|
vec2 rc = vec2(lb.x < 2.0 ? 1.0 : 3.0, 2.0), dv = lb - rc;
|
|
float rd = length(dv);
|
|
bc = vec3(0.15, 0.16, 0.18);
|
|
if (rd < 0.95) {
|
|
float ang = atan(dv.y, dv.x) - dir * uTime * 30.0 / 0.95;
|
|
float spoke = smoothstep(0.55, 0.85, cos(ang * 3.0));
|
|
bc = mix(vec3(0.5, 0.36, 0.25), vec3(0.72, 0.53, 0.36), 0.5 + 0.5 * dot(normalize(dv + 1e-4), vec2(-0.6, -0.8)) * smoothstep(0.2, 0.95, rd));
|
|
bc *= mix(1.0, 0.72, spoke * smoothstep(0.25, 0.4, rd));
|
|
bc *= mix(1.0, 0.55, smoothstep(0.82, 0.95, rd));
|
|
}
|
|
}
|
|
C = mix(C, bc, inside);
|
|
}
|
|
}
|
|
else if (nc >= 9 && nc <= 12) {
|
|
// screw tube: a copper pipe; what is inside shows through, and the screw's flights turn
|
|
bool vert = nc == 9 || nc == 11;
|
|
float sp2 = nc == 9 ? -1.0 : nc == 10 ? 1.0 : nc == 11 ? 1.0 : -1.0;
|
|
float a = (vert ? fract(w.x * 0.25) : fract(w.y * 0.25)) * 4.0, along = vert ? w.y : w.x;
|
|
float wall = 0.62;
|
|
vec3 tc;
|
|
if (a < wall || a > 4.0 - wall) {
|
|
float u2 = a < wall ? a / wall : (4.0 - a) / wall; // 0 at the outside, 1 at the bore
|
|
float cyl = sin(u2 * 1.5708);
|
|
tc = vec3(0.62, 0.41, 0.27) * (0.6 + 0.55 * cyl);
|
|
if (a < wall) tc *= 1.08;
|
|
float ring = 1.0 - smoothstep(0.0, 0.3, min(fract(along * 0.25), 1.0 - fract(along * 0.25)) * 4.0);
|
|
tc *= 1.0 - 0.3 * ring;
|
|
tc *= mix(0.5, 1.0, smoothstep(0.0, max(px * 1.3, 0.1), min(a, 4.0 - a)));
|
|
} else {
|
|
bool full = own.r + own.g + own.b > 0.0;
|
|
float fl = fract((along + a * 0.9 - sp2 * uTime * 14.0) * 0.5);
|
|
float flight = smoothstep(0.82, 0.92, fl) * (1.0 - smoothstep(0.92, 1.0, fl));
|
|
vec3 bore = vec3(0.11, 0.1, 0.11) * (0.8 + 0.4 * (1.0 - abs(a - 2.0) / 1.4));
|
|
tc = full ? own.rgb * (0.9 + 0.1 * vn(w * 1.3)) : bore;
|
|
tc = mix(tc, vec3(0.6, 0.48, 0.38), flight * (full ? 0.35 : 0.8));
|
|
tc *= 1.0 - 0.25 * smoothstep(0.9, 1.3, abs(a - 2.0));
|
|
}
|
|
C = tc;
|
|
}
|
|
else if (nc == 6 || (nc >= 15 && nc <= 18)) {
|
|
vec2 lb = fract(w * 0.25) * 4.0, rb = 4.0 - lb;
|
|
float e = min(min(lb.x, lb.y), min(rb.x, rb.y));
|
|
vec3 tc = own.rgb * (0.92 + 0.14 * fbm(w * 0.55));
|
|
float hl = max(1.0 - smoothstep(0.0, 0.8, lb.x), 1.0 - smoothstep(0.0, 0.8, lb.y));
|
|
float shd = max(1.0 - smoothstep(0.0, 0.8, rb.x), 1.0 - smoothstep(0.0, 0.8, rb.y));
|
|
tc *= 1.0 + 0.2 * hl - 0.26 * shd;
|
|
tc *= mix(0.5, 1.0, smoothstep(px * 0.5, px * 0.5 + max(px * 1.2, 0.14), e));
|
|
if (nc >= 15) {
|
|
vec2 sl = lb;
|
|
if (nc == 16 || nc == 18) sl.x = 4.0 - sl.x;
|
|
if (nc >= 17) sl.y = 4.0 - sl.y;
|
|
float d = (sl.y - sl.x) * 0.70710678;
|
|
tc *= 1.0 + 0.2 * (1.0 - smoothstep(0.0, 0.6, d));
|
|
C = mix(C, tc, smoothstep(-px * 0.5, px * 0.5, d));
|
|
} else C = tc;
|
|
}
|
|
|
|
else if (nc == 13 || nc == 14) {
|
|
// filter: a belt whose middle is a see-through steel mesh. It has the same band as a belt, so a
|
|
// row of belts and filters reads as one belt, rounded only where the whole line ends. The
|
|
// band is perforated; its colour tag sits on the top run for what rides along, on the
|
|
// bottom run for what drops through.
|
|
float dir = nc == 14 ? 1.0 : -1.0;
|
|
vec2 lb = fract(w * 0.25) * 4.0;
|
|
vec4 tag = cellAt(tTree, c);
|
|
bool carry = tag.a > 0.16, tinted = tag.r + tag.g + tag.b > 0.0;
|
|
float sd = beltLineSD(c, lb);
|
|
float inside = 1.0 - smoothstep(-px, px, sd);
|
|
if (inside > 0.0) {
|
|
float edge = -sd;
|
|
vec3 steel = vec3(0.42, 0.44, 0.5);
|
|
// through the holes: whatever is behind (water, sky, rock), or what is passing through
|
|
vec3 behind = own.r + own.g + own.b > 0.0 ? own.rgb : C * 0.8;
|
|
vec2 cg = abs(fract(w) - 0.5);
|
|
float bw2 = max(px * 0.9, 0.09);
|
|
float hole = 1.0 - smoothstep(0.5 - bw2 - px, 0.5 - bw2, max(cg.x, cg.y));
|
|
vec3 fc;
|
|
if (edge < 0.8) {
|
|
float rg = bandRidge(w, lb, dir);
|
|
vec3 band = bandColor(lb, rg);
|
|
if (tinted && (carry == (lb.y < 2.0))) band = mix(band, tag.rgb * (lb.y < 2.0 ? 1.0 : 0.8), 0.75 * rg);
|
|
fc = mix(band, behind * 0.85, hole * (1.0 - rg) * 0.75);
|
|
fc *= mix(0.55, 1.0, smoothstep(0.0, max(px * 1.4, 0.12), edge));
|
|
} else {
|
|
fc = mix(steel * 0.78, behind, hole);
|
|
fc *= mix(0.7, 1.0, smoothstep(0.0, max(px * 1.4, 0.12), edge - 0.8));
|
|
}
|
|
C = mix(C, fc, inside);
|
|
}
|
|
}
|
|
|
|
// ---- light, then the glow of hot things on top
|
|
if (uLightMap.w > 0.5) C *= textureLod(tLight, (q - uLightMap.xy) / uLightMap.z / uLightSize, 0.0).rgb;
|
|
if (uGlowK > 0.0) {
|
|
vec2 gu = q / (2.0 * uGlowSize), d = 1.0 / uGlowSize;
|
|
vec3 g = textureLod(tGlow, gu, 0.0).rgb * 0.25;
|
|
g += (textureLod(tGlow, gu + vec2(d.x, 0), 0.0).rgb + textureLod(tGlow, gu - vec2(d.x, 0), 0.0).rgb + textureLod(tGlow, gu + vec2(0, d.y), 0.0).rgb + textureLod(tGlow, gu - vec2(0, d.y), 0.0).rgb) * 0.125;
|
|
g += (textureLod(tGlow, gu + d, 0.0).rgb + textureLod(tGlow, gu - d, 0.0).rgb + textureLod(tGlow, gu + vec2(d.x, -d.y), 0.0).rgb + textureLod(tGlow, gu + vec2(-d.x, d.y), 0.0).rgb) * 0.0625;
|
|
C += g * uGlowK;
|
|
}
|
|
o = vec4(C, 1.0);
|
|
}`;
|
|
|