Files
Noita-GPU-Vulkan/shaders/terrain.comp
T
2026-10-19 20:59:15 +02:00

452 lines
24 KiB
Plaintext

#version 450
#extension GL_GOOGLE_include_directive : require
layout(local_size_x = 8, local_size_y = 8) in;
#include "common.glsl"
#include "areas.glsl"
#include "far.glsl"
// Terrain generation on the GPU: GLSL port of the engine's wang-image -> cell path
// (docs/ENGINE_ANALYSIS.md section 18). One invocation per grid cell.
layout(std430, set = 0, binding = 6) readonly buffer Field { float field[]; }; // wang solidity plane
layout(std430, set = 0, binding = 7) readonly buffer MatPlane { uint matplane[]; }; // CellData index + 1, 0 = none
layout(std430, set = 0, binding = 8) readonly buffer Tables { uint perm[]; }; // [0,512) simplex, [512,1024) perlin
layout(std430, set = 0, binding = 9) readonly buffer MatParams { vec2 mparams[]; }; // per material: warp, threshold
layout(std430, set = 0, binding = 10) readonly buffer CellsOld { uint cells_old[]; };
layout(std430, set = 0, binding = 11) readonly buffer ExtOld { vec2 ext_old[]; };
layout(std430, set = 0, binding = 52) readonly buffer BirthOld { uint birth_old[]; }; // the ages before the scroll
layout(std430, set = 0, binding = 12) readonly buffer Scene { uint scene[]; }; // pixel scenes: 0 = none, else material + 1
layout(std430, set = 0, binding = 18) readonly buffer Override { uint ovr[]; }; // cells restored from the persistent store (0xFFFFFFFF = none)
int ffloor(float v) { return int(floor(v)); }
float fade(float t) { return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); }
float sstep(float t) { return (3.0 - t * 2.0) * t * t; }
float sgrad(uint hsh, float x, float y) {
uint h = hsh & 7u;
float u = h < 4u ? x : y;
float v = h < 4u ? y : x;
return (((h & 1u) != 0u) ? -u : u) + (((h & 2u) != 0u) ? -2.0 * v : 2.0 * v);
}
uint ps(uint i) { return perm[i]; }
uint pp(uint i) { return perm[512u + i]; }
float simplex2(float x, float y) {
const float F2 = 0.366025404, G2 = 0.211324865;
float s = (x + y) * F2;
int i = ffloor(x + s), j = ffloor(y + s);
float t = float(i + j) * G2;
float x0 = x - (float(i) - t), y0 = y - (float(j) - t);
int i1 = x0 > y0 ? 1 : 0, j1 = 1 - i1;
float x1 = x0 - float(i1) + G2, y1 = y0 - float(j1) + G2;
float x2 = x0 - 1.0 + 2.0 * G2, y2 = y0 - 1.0 + 2.0 * G2;
uint ii = uint(i) & 255u, jj = uint(j) & 255u;
float n0 = 0.0, n1 = 0.0, n2 = 0.0;
float t0 = 0.5 - x0 * x0 - y0 * y0;
if (t0 >= 0.0) { t0 *= t0; n0 = t0 * t0 * sgrad(ps(ii + ps(jj)), x0, y0); }
float t1 = 0.5 - x1 * x1 - y1 * y1;
if (t1 >= 0.0) { t1 *= t1; n1 = t1 * t1 * sgrad(ps(ii + uint(i1) + ps(jj + uint(j1))), x1, y1); }
float t2 = 0.5 - x2 * x2 - y2 * y2;
if (t2 >= 0.0) { t2 *= t2; n2 = t2 * t2 * sgrad(ps(ii + 1u + ps(jj + 1u)), x2, y2); }
return 40.0 * (n0 + n1 + n2);
}
// 8 gradients of FUN_00872a20
vec2 pgrad(uint g) {
const vec2 G[8] = vec2[8](vec2(-1, 0), vec2(1, 0), vec2(0, -1), vec2(0, 1), vec2(-1, -1), vec2(-1, 1), vec2(1, -1), vec2(1, 1));
return G[g];
}
float perlin2(float x, float y) {
int ix = ffloor(x), iy = ffloor(y);
uint X = uint(ix) & 255u, Y = uint(iy) & 255u, X1 = uint(ix + 1) & 255u, Y1 = uint(iy + 1) & 255u;
float fx = x - float(ix), fy = y - float(iy);
uint pa = pp(X), pb = pp(X1);
float na = dot(pgrad(pp(pa + Y) & 7u), vec2(fx, fy));
float nb = dot(pgrad(pp(pb + Y) & 7u), vec2(fx - 1.0, fy));
float nc = dot(pgrad(pp(pa + Y1) & 7u), vec2(fx, fy - 1.0));
float nd = dot(pgrad(pp(pb + Y1) & 7u), vec2(fx - 1.0, fy - 1.0));
float v = fade(fy), u = fade(fx);
return mix(mix(na, nc, v), mix(nb, nd, v), u);
}
float value2(float x, float y) {
int ix = ffloor(x), iy = ffloor(y);
uint X = uint(ix) & 255u, Y = uint(iy) & 255u, X1 = uint(ix + 1) & 255u, Y1 = uint(iy + 1) & 255u;
float fx = x - float(ix), fy = y - float(iy);
uint pa = pp(X), pb = pp(X1);
float v00 = float(pp(pa + Y)) / 255.0, v01 = float(pp(pa + Y1)) / 255.0;
float v10 = float(pp(pb + Y)) / 255.0, v11 = float(pp(pb + Y1)) / 255.0;
float v = fade(fy), u = fade(fx);
return (mix(mix(v00, v01, v), mix(v10, v11, v), u) - 0.5) * 2.0;
}
int wrapi(int v, int n) { v = v % n; return v < 0 ? v + n : v; }
int gpw, gph, gfo, gmo; // the area of the cell being generated: plane size and offsets
float field_at(int x, int y) { return field[gfo + wrapi(y, gph) * gpw + wrapi(x, gpw)]; }
// FUN_008712d0: nearest wang pixel (smoothstep(f) >= 0.5 rounds up); CellData index or -1
int material_pixel(float px, float py) {
int ix = ffloor(px), iy = ffloor(py);
float fx = px - float(ix), fy = py - float(iy);
if (!(0.5 > sstep(fy))) ++iy;
if (!(0.5 > sstep(fx))) ++ix;
return int(matplane[gmo + wrapi(iy, gph) * gpw + wrapi(ix, gpw)]) - 1;
}
// FUN_00870e60: smoothstep-weighted bilinear sample of the solidity plane
float bilinear(float px, float py) {
int ix = ffloor(px), iy = ffloor(py);
float sx = sstep(px - float(ix)), sy = sstep(py - float(iy));
float top = mix(field_at(ix, iy), field_at(ix + 1, iy), sx);
float bot = mix(field_at(ix, iy + 1), field_at(ix + 1, iy + 1), sx);
return mix(top, bot, sy);
}
// ---- biome <Materials> evaluator (FUN_0086d2a0) and its noise: 70-scaled simplex (FUN_00872300), 4-octave fbm (FUN_009296d0), polka dots (FUN_00872140)
#include "procgrad.glsl"
// FUN_0086fd40 / FUN_0086fe30: the cell hash (one value, and three more from the same product)
float cell_u(vec2 c) {
float cx = floor(c.x * 0.014084507), cy = floor(c.y * 0.014084507);
float a = c.x - cx * 71.0 + 26.0, b = c.y - cy * 71.0 + 161.0;
return b * b * (a * a);
}
float polka(float x, float y, float rlow, float rhigh, bool boxed, float prob) {
vec2 c = vec2(floor(x), floor(y));
float u = cell_u(c);
if (fract(u * 0.0010131986) >= prob) return 0.0;
float fx = x - c.x, fy = y - c.y;
float o0 = fract(u * 0.0010513747), o1 = fract(u * 0.0015553312), o2 = fract(u * 0.0012450161);
float r = (rhigh - rlow) * o2 + rlow;
if (r <= 0.0) return 0.0;
float inv = 2.0 / r;
float dx = fx * inv - (inv - 1.0) + (inv - 2.0) * o0;
float dy = fy * inv - (inv - 1.0) + (inv - 2.0) * o1;
float d;
if (boxed) { d = dy * dy; d *= d; float e = dx * dx; d += e * e; if (d > 1.0) return 0.0; }
else { d = min(dx * dx + dy * dy, 1.0); }
float q = 1.0 - d;
return q * q * q;
}
// area a's component list: 20 words each (see AreaStore): [0] material, [1] flags, [2..3] min max, [4..5] add_perlin scale, [6..7] limit min max, [8..9] rare scale,
// [10..11] rare offset, [12..13] required min max, [14..15] polka radius low high, [16] polka probability
uint eval_materials_at(int base, int cnt, float v, ivec2 w) {
float fx = float(w.x), fy = float(w.y);
for (int k = 0; k < cnt; ++k) {
int o = base + 20 * k;
uint mat = atab[o];
uint fl = atab[o + 1];
if (mat == 0xFFFFFFFFu) continue;
if ((fl & 1u) != 0u && (fy < uintBitsToFloat(atab[o + 6]) || fy > uintBitsToFloat(atab[o + 7]))) continue;
float vv = v;
if ((fl & 2u) != 0u) vv += simplex70(fx * uintBitsToFloat(atab[o + 4]), fy * uintBitsToFloat(atab[o + 5]));
if (!(vv >= uintBitsToFloat(atab[o + 2]) && vv < uintBitsToFloat(atab[o + 3]))) continue;
if ((fl & 4u) == 0u) return mat;
float px = uintBitsToFloat(atab[o + 10]) + fx * uintBitsToFloat(atab[o + 8]);
float py = uintBitsToFloat(atab[o + 11]) + fy * uintBitsToFloat(atab[o + 9]);
float rmin = uintBitsToFloat(atab[o + 12]), rmax = uintBitsToFloat(atab[o + 13]);
if ((fl & 8u) != 0u) {
float n = (fl & 16u) != 0u ? fbm70(px, py) : simplex70(px, py);
if (rmin >= n || n > rmax) continue;
}
if ((fl & 32u) == 0u) return mat;
float n2 = polka(px, py, uintBitsToFloat(atab[o + 14]), uintBitsToFloat(atab[o + 15]), (fl & 64u) != 0u, uintBitsToFloat(atab[o + 16]));
if (rmin < n2 && n2 <= rmax) return mat;
}
return 0u;
}
uint eval_materials(int a, float v, ivec2 w) { return eval_materials_at(arec(a, 14), arec(a, 15), v, w); }
// ---- procedural biomes (FUN_0087e110): gradient between the biome's y limits, inside perlin, noise-type blend; the BitmapCaves image is not generated (value 1 everywhere)
float sin_big(float t) { // sin of a large float: reduce by 2*pi in two steps first (the GPU sin loses its accuracy for large arguments)
float k = floor(t * 0.15915494309189535 + 0.5);
float r = fma(-k, 6.28125, t);
r = fma(-k, 0.0019353071795864769, r);
return sin(r);
}
float iq_hash(float n) { return fract(sin_big(n) * 43758.547); }
float iq_mix(float n, float sx, float sy) {
float a = iq_hash(n), b = iq_hash(n + 1.0), c = iq_hash(n + 57.0), d = iq_hash(n + 58.0);
return mix(mix(a, b, sx), mix(c, d, sx), sy);
}
float iq_noise(float x, float y) { // FUN_00871630
float ix = floor(x), iy = floor(y);
float fx = x - ix, fy = y - iy;
return iq_mix(iy * 57.0 + ix, (3.0 - 2.0 * fx) * fx * fx, (3.0 - 2.0 * fy) * fy * fy);
}
float iq_smooth_par(float f, int i) { return (i % 2 == 0) ? (3.0 - 2.0 * f) * f * f : f; }
float iq_noise2(float x, float y) { // FUN_00871850: the interpolation weight depends on the parity of the cell
float ix = floor(x), iy = floor(y);
float fx = x - ix, fy = y - iy;
int jx = int(ix), jy = int(iy);
float sx0 = iq_smooth_par(fx, jx), sx1 = iq_smooth_par(fx, jx + 1);
float sy0 = iq_smooth_par(fy, jy), sy1 = iq_smooth_par(fy, jy + 1);
return iq_mix(iy * 57.0 + ix, (sx1 - sx0) * fx + sx0, (sy1 - sy0) * fy + sy0);
}
float noise_t(int t, float x, float y) { // FUN_00873c00 (types 0-4 are not used by any biome of the game)
if (t == 5) return simplex70(x, y);
if (t == 6) return perlin2(x, y);
if (t == 7) return value2(x, y);
if (t == 8) return simplex2(x, y);
return 0.0;
}
float fbm_t(int t, float x, float y) { // FUN_00873cc0
const vec4 M = vec4(0.84147, 0.5403, 0.5403, -0.84147);
const float K[3] = float[3](2.02, 2.33, 2.01);
float sum = noise_t(t, x, y) * 0.5, wgt = 0.25;
for (int o = 0; o < 3; ++o) {
float nx = (M.x * x + M.z * y) * K[o], ny = (M.y * x + M.w * y) * K[o];
x = nx; y = ny;
sum += noise_t(t, x, y) * wgt;
wgt *= 0.5;
}
return sum / 0.9375;
}
float inside_perlin(uint rec, float X, float Y) { // FUN_0087e7a0
uint fl = atab[rec];
float x = X * 0.05243442 * tf(rec, 1) + 0.1 + tf(rec, 5);
float y = Y * 0.05243442 * tf(rec, 2) + 0.1 + tf(rec, 6);
if ((fl & 64u) != 0u) { float sd = float(atab[5]); x += sd * 1.312e-5; y += sd * 2.331e-5; }
int it = int((fl >> 12) & 15u);
float n = (fl & 2u) != 0u ? fbm_t(it, x, y) : noise_t(it, x, y);
float v = n;
if ((fl & 4u) != 0u) v = n * n;
float mn = tf(rec, 3), mx = tf(rec, 4);
if ((fl & 8u) != 0u) { if (!(mx > v)) v = mx; if (!(v > mn)) v = mn; }
if ((fl & 16u) != 0u) v = (v * 0.5 + 0.5) * (mx - mn) + mn;
return v;
}
float cave_at(uint base, int cw, int ch, int x, int y) { // the cave image: both axes wrap
x = ((x % cw) + cw) % cw; y = ((y % ch) + ch) % ch;
return field[base + uint(y * cw + x)];
}
float cave_sample(uint base, int cw, int ch, float xf, float yf) { // FUN_00870e60
int ix = int(floor(xf)), iy = int(floor(yf));
float fx = xf - float(ix), fy = yf - float(iy);
float sx = (3.0 - fx * 2.0) * (fx * fx), sy = (3.0 - fy * 2.0) * (fy * fy);
float a = cave_at(base, cw, ch, ix, iy), b = cave_at(base, cw, ch, ix + 1, iy), c = cave_at(base, cw, ch, ix, iy + 1), d = cave_at(base, cw, ch, ix + 1, iy + 1);
float top = (b - a) * sx + a, bot = (d - c) * sx + c;
return (bot - top) * sy + top;
}
float proc_value(ivec2 w, uint rec) {
float X = float(w.x), Y = float(w.y);
float v = 1.0;
if (atab[rec + 18u] != 0xFFFFFFFFu) { // the cave image of the biome (see proc_biome.cpp)
uint cw = atab[rec + 19u] & 0xFFFFu, chh = atab[rec + 19u] >> 16;
float px = (X * 0.49162514 + 2560.0) * 0.1, py = (2560.0 * 6.86035959282328e-07 + Y * 0.49162514) * 0.1;
v = cave_sample(atab[rec + 18u], int(cw), int(chh), px, py);
if (1.0 > v) v = v + simplex70(px, py) * ((1.0 - v) * 0.495);
if (0.0 >= v) return 0.0;
}
v = proc_gradient(w, rec) * v;
float s8 = 0.0, s18 = 0.0, s1c = 0.0;
float mulp = tf(rec, 9);
uint fl = atab[rec];
if (v > 0.51 && mulp != 0.0) { s18 = (v - 0.5) * 2.0; s8 = inside_perlin(rec, X, Y); }
if (v > 0.85) {
s1c = tf(rec, 7);
float nz;
if (((fl >> 8) & 15u) == 3u) { // SIN_CAPPED_SIMPLEX
float amp = iq_noise(X * 0.02, Y * 0.021) * 0.05 + 0.05;
float t1 = sin(Y * 0.0009765625) * 1024.0 * 0.5 * 0.06 * amp, t2 = cos(X * 0.0009765625) * 1024.0 * 0.5 * 0.06 * amp;
nz = float(simplex70(t2, t1)) * v;
} else { // the default noise type
float amp = iq_noise2(X * 0.025, Y * 0.025) * 0.05 + 0.05;
nz = simplex2(X * 0.01 * amp, Y * 0.01 * amp) * v;
}
v = v + (nz - v) * ((v - 0.85) / 0.1);
}
if ((fl & 32u) != 0u) s8 = ((v * s8) - s8) * (Y * 0.001) + s8;
return mulp * s18 * s8 + tf(rec, 8) * v + s1c;
}
// Probe mode (shift_mode 2, docs: the teleport destination test): the world cells listed in probe[] get their cell word: from the live window when inside it
// (flag 4, flag 2 when the cell is falling: ext.x > 0), else generated as the window would generate them (flag 1 = an area is loaded there).
layout(std430, set = 0, binding = 53) buffer Probe { uvec4 phead; ivec4 probe[]; };
bool g_unloaded = false;
uint gen_cell(ivec2 w) {
uint hh = hash3(uint(w.x), uint(w.y), 77u);
uint var = (hh >> 8) & 0xFFu;
// LoadPixelScene images override the terrain (the scenes of every loaded area: a scene may reach into a neighbour)
bool forceSolid = false;
int whiteTile = -1; // the biome-map tile whose biome the white pixels of the scene use (the scene's origin)
for (int a = 0; a < int(atab[0]); ++a) {
int si = scene_index(a, w);
if (si < 0) continue;
uint sv = scene[si];
if (sv >= 0xE000u) { forceSolid = true; whiteTile = sv == 0xFFFFu ? -1 : int(sv - 0xE000u); break; }
if (sv != 0u) { return (sv == 1u ? 0u : ((sv - 1u) | (var << 16))); }
}
const int ai = area_of_cell(w);
if (ai < 0) { g_unloaded = true; return uint(pc.m_rock) | (var << 16); } // no area loaded here
if (forceSolid) { // a white scene pixel: solid, the material from the <Materials> of the biome with the wang value 1
float xw = float(w.x), yw = float(w.y);
float n3 = value2(xw * 0.035, yw * 0.07) * 15.5;
float s = simplex2((xw + n3) * 0.0489275, (yw + n3) * 0.0489275);
const float wv = max(0.5, s * (0.0625 * 5.35 * 0.95) + 1.0);
uint fm;
const uint wrec = whiteTile >= 0 ? tile_rec(whiteTile % int(atab[2]), whiteTile / int(atab[2])) : 0xFFFFFFFFu;
if (wrec != 0xFFFFFFFFu && atab[wrec + 22u] != 0u) fm = eval_materials_at(int(atab[wrec + 21u]), int(atab[wrec + 22u]), wv, w); // the biome of the scene origin
else fm = eval_materials(ai, wv, w);
return fm == 0u ? 0u : (fm | (var << 16));
}
{ // a procedural biome (no wang template): the value function, then the <Materials> list (FUN_0087d0e0, type 0)
const uint trec = topo_rec(w);
if (trec != 0xFFFFFFFFu && (atab[trec] & 1u) != 0u) {
const uint pm = eval_materials(ai, proc_value(w, trec), w);
return pm == 0u ? 0u : (pm | (var << 16));
}
}
gpw = arec(ai, 2); gph = arec(ai, 3); gfo = arec(ai, 4); gmo = arec(ai, 5);
ivec2 o = w - ivec2(arec(ai, 0), arec(ai, 1)); // cell relative to the centre of wang pixel (0,0)
float px = (float(o.x) + 0.5) * 0.1, py = (float(o.y) + 0.5) * 0.1;
if (py < -0.5) { return 0u; } // open sky above the area
// the last wang pixel is 5 cells short of the right / bottom edge of the area's tiles: those cells continue the edge pixel (a 13 cell wall of rock stood there)
px = clamp(px, -0.5, float(gpw) - 0.5001); py = clamp(py, -0.5, float(gph) - 0.5001);
float warp = 1.0, thr = 0.5;
int m0 = material_pixel(px, py);
if (m0 > 0) { vec2 mp = mparams[m0]; warp = mp.x; thr = mp.y; }
float qx = px, qy = py;
if (warp > 0.0) {
float xf = float(o.x), yf = float(o.y);
float n1 = simplex2(xf * 0.13715, yf * 0.13717);
float a = n1 * 0.45 + 0.1;
float xs = xf * 0.11111111, ys = yf * 0.11111111;
float n2 = perlin2(ys, xs);
float t = ((1.0 - a) * 0.33 + 0.111) * warp;
float n3 = value2(xs, ys);
qx = px + n3 * (a * warp);
qy = py + n2 * t;
}
float f = bilinear(qx, qy);
if (f < thr) { return 0u; }
int m1 = material_pixel(qx, qy);
uint mat;
if (m1 > 0) {
mat = uint(m1);
} else {
// the biome's <Materials>: FUN_0087d010 value, then the first matching component (none = air)
float xw = float(w.x), yw = float(w.y);
float n = value2(xw * 0.035, yw * 0.07);
float n3 = n * 15.5;
float h = (f - 0.5) * 0.5;
float s = simplex2((xw + n3) * 0.0489275, (yw + n3) * 0.0489275);
float v = max(0.5, s * (h * h * 5.35 * 0.95) + f);
mat = eval_materials(ai, v, w);
}
return mat == 0u ? 0u : (mat | (var << 16));
}
// The world rules of the cells that enter the window: the header in the tables before the 4 colour-grading words (main.cpp kWorldRules): [0] EVERYTHING_TO_GOLD,
// [1] gold, [2] gold_static; the cuts in binding 55: [0] count, then from word 4, 12 words per GameCutThroughWorldVertical (x, y_min, y_max, radius, edge, the engine's
// loaded region at the call: x0, y0, x1, y1, seed), every cut of the run. A generated cell (one that never was in the window) inside a cut's region takes the cut (007392d0: off = floor(3 sin(0.02 y)
// +- 0.5), air for left < x <= right, the static stainable cells within edge darkened by (1 - sqrt(d / edge)) x (0.4 + 0.3 rand), as the burn level of bits 28-30);
// with EVERYTHING_TO_GOLD every non-air cell is gold (the static ones gold_static), as the engine's redefined materials (00704f70)
const uint kWorldRules = 4u;
layout(std430, set = 0, binding = 55) readonly buffer CutRules { uint cuts[]; };
uint cut_hash(uint a, uint b) { uint h = a * 0x9E3779B1u ^ (b + 0x7F4A7C15u); h ^= h >> 15; h *= 0x2C1B3C6Du; h ^= h >> 12; h *= 0x297A2D39u; h ^= h >> 15; return h; }
uint rules_word(uint c, ivec2 w, bool fresh) {
const uint wb = uint(perm.length()) - 4u - kWorldRules;
if (fresh) {
const uint n = min(cuts[0], (uint(cuts.length()) - 4u) / 12u);
for (uint k = 0u; k < n; ++k) {
const uint b = 4u + 12u * k;
const int x = int(cuts[b]), y0 = int(cuts[b + 1u]), y1 = int(cuts[b + 2u]), r = int(cuts[b + 3u]), edge = int(cuts[b + 4u]);
if (w.y < y0 || w.y >= y1 || w.x < x - r - edge || w.x >= x + r + edge) continue;
if (w.x < int(cuts[b + 5u]) || w.y < int(cuts[b + 6u]) || w.x >= int(cuts[b + 7u]) || w.y >= int(cuts[b + 8u])) continue;
const float v = sin(float(w.y) * 0.02) * 3.0;
const int off = int(floor(v + (v < 0.0 ? -0.5 : 0.5)));
const int left = x - r + off, right = x + r + off;
if (w.x > left && w.x <= right) { c = 0u; continue; }
const uint m = cell_mat(c);
if (edge <= 0 || m == 0u || (mflags(m) & F_STATIC) == 0u || (mflags(m) & F_NOSTAIN) != 0u || (c & BURN_BIT) != 0u) continue;
const float rnd = 0.4 + 0.3 * float(cut_hash(uint(w.y), cuts[b + 9u]) & 0xFFFFu) / 65536.0;
const float d = float(w.x <= left ? left - w.x : w.x - right);
const float s = max(0.0, (1.0 - sqrt(d / float(edge))) * rnd);
const float keep = (1.0 - float((c >> 28) & 7u) / 7.0) * (1.0 - s);
const uint L = uint(clamp(int(round((1.0 - keep) * 7.0)), 0, 7));
c = (c & ~(7u << 28)) | (L << 28);
}
}
if (perm[wb] != 0u) {
const uint m = cell_mat(c);
if (m != 0u && m != perm[wb + 1u] && m != perm[wb + 2u]) c = ((mflags(m) & F_STATIC) != 0u ? perm[wb + 2u] : perm[wb + 1u]) | (c & (0xFFu << 16));
}
return c;
}
// The spell test room (main.cpp --spelltest; cut rules words 1..3): every generated cell is rock, but the hall (x0 <= x < x1, y0 <= y < y1) is air inside 16 cells of
// temple brick
uint spell_room(uint c, ivec2 w) {
if (cuts[1] == 0u) return c;
const ivec2 a = ivec2(int(cuts[2] << 16) >> 16, int(cuts[2]) >> 16), b = ivec2(int(cuts[3] << 16) >> 16, int(cuts[3]) >> 16);
const uint var = (cut_hash(uint(w.x), uint(w.y)) >> 8) & 0xFFu;
if (w.x >= a.x && w.x < b.x && w.y >= a.y && w.y < b.y) return 0u;
if (w.x >= a.x - 16 && w.x < b.x + 16 && w.y >= a.y - 16 && w.y < b.y + 16) return (cuts[1] >> 16) | (var << 16);
return (cuts[1] & 0xFFFFu) | (var << 16);
}
void world_rules(int i, ivec2 w, bool fresh) { const uint c = rules_word(cells[i], w, fresh); if (c != cells[i]) { cells[i] = c; ext[i] = vec2(0.0); } }
void main() {
if (pc.shift_mode == 3) { // far blocks (far.glsl): job z generates its block's cells into its slot - a store-backed block keeps the cells the store had
const int j = int(gl_GlobalInvocationID.z);
if (j >= farmap[kFarJobCount]) return;
const int slot = farmap[kFarJobList + 4 * j], bx = farmap[kFarJobList + 4 * j + 1], by = farmap[kFarJobList + 4 * j + 2], kept = farmap[kFarJobList + 4 * j + 3];
const ivec2 w = ivec2(bx * 64, by * 64) + ivec2(gl_GlobalInvocationID.xy);
const int fi = slot * 4096 + (w.y & 63) * 64 + (w.x & 63);
if (kept != 0 && farcells[fi] != 0xFFFFFFFFu) return;
farcells[fi] = rules_word(spell_room(gen_cell(w), w), w, true);
return;
}
if (pc.shift_mode == 2) {
const uint q = gl_GlobalInvocationID.y * gl_NumWorkGroups.x * 8u + gl_GlobalInvocationID.x;
if (q >= phead.x) return;
const ivec2 w = probe[q].xy;
const ivec2 lp = w - ivec2(pc.view_x, pc.view_y);
if (lp.x >= 0 && lp.y >= 0 && lp.x < pc.gw && lp.y < pc.gh) {
const int li = lp.y * pc.gw + lp.x;
probe[q].z = int(cells[li]);
probe[q].w = 1 | 4 | (ext[li].x > 0.0 ? 2 : 0);
return;
}
g_unloaded = false;
probe[q].z = int(rules_word(spell_room(gen_cell(w), w), w, true));
probe[q].w = g_unloaded ? 0 : 1;
return;
}
ivec2 p = ivec2(gl_GlobalInvocationID.xy);
if (p.x >= pc.gw || p.y >= pc.gh) return;
int i = p.y * pc.gw + p.x;
if (pc.shift_mode == 1) { // scrolling: reuse the simulated overlap, generate only the exposed strips
ivec2 src = p + ivec2(pc.shift_x, pc.shift_y);
if (src.x >= 0 && src.y >= 0 && src.x < pc.gw && src.y < pc.gh) {
cells[i] = cells_old[src.y * pc.gw + src.x];
ext[i] = ext_old[src.y * pc.gw + src.x];
birth[i] = birth_old[src.y * pc.gw + src.x]; // a cell that stays keeps its age (0070d1e0: the age belongs to the cell)
return;
}
}
birth[i] = birth_frame();
if (ovr[i] != 0xFFFFFFFFu) { // a cell that comes back from the store (only its word is kept): a burning cell or a flame gets the fade a new cell of its material has
// (0070f450: the material's temperature_of_fire), so a fire that went out of the window goes on when it comes back instead of dying at its next update
const uint oc = ovr[i];
cells[i] = oc; ext[i] = vec2(0.0);
const uint om = cell_mat(oc);
if (om != 0u && ((oc & BURN_BIT) != 0u || (mflags(om) & F_FIRE) != 0u)) birth[i] = with_fade(birth_frame(), int((mats[3u * om + 2u].w >> 8) & 255u));
world_rules(i, p + ivec2(pc.view_x, pc.view_y), false);
return;
}
ivec2 w = p + ivec2(pc.view_x, pc.view_y); // world cell
ext[i] = vec2(0.0);
cells[i] = spell_room(gen_cell(w), w);
world_rules(i, w, true);
}