precision highp float; precision highp int; in vec2 vUv; uniform sampler2D uWorld; uniform sampler2D uMaterialProps; uniform vec2 uBlockOffset; uniform int uFrame; out vec4 fragColor; uint hash(uint x) { x ^= x >> 16u; x *= 0x45d9f3bu; x ^= x >> 16u; return x; } void main() { ivec2 pixel = ivec2(gl_FragCoord.xy); ivec2 blockBase = ((pixel - ivec2(uBlockOffset)) / 2) * 2 + ivec2(uBlockOffset); ivec2 localPos = pixel - blockBase; int localIndex = localPos.x + localPos.y * 2; // bounds check int worldSize = int(WORLD_SIZE); if (blockBase.x < 0 || blockBase.y < 0 || blockBase.x + 1 >= worldSize || blockBase.y + 1 >= worldSize) { fragColor = texelFetch(uWorld, pixel, 0); return; } // read 2x2 block // index mapping (GL coords — Y increases upward): // [2][3] <- top row (y offset 1, higher Y) // [0][1] <- bottom row (y offset 0, lower Y) vec4 cells[4]; float behaviors[4]; float densities[4]; for (int i = 0; i < 4; i++) { ivec2 p = blockBase + ivec2(i % 2, i / 2); cells[i] = texelFetch(uWorld, p, 0); vec4 props = texelFetch(uMaterialProps, ivec2(int(cells[i].r), 0), 0); behaviors[i] = props.r; densities[i] = props.g; } // capture blocked state before vertical swaps modify the arrays // "blocked" = column is settled (top lighter/equal to bottom) or contains solid bool col0Blocked = (behaviors[0] == BEHAVIOR_SOLID || behaviors[2] == BEHAVIOR_SOLID || densities[2] <= densities[0]); bool col1Blocked = (behaviors[1] == BEHAVIOR_SOLID || behaviors[3] == BEHAVIOR_SOLID || densities[3] <= densities[1]); // random seed for this block this frame uint seed = hash(uint(blockBase.x) * 7919u + uint(blockBase.y) * 6271u + uint(uFrame) * 3571u); // vertical gravity: top cells (2,3) fall to bottom (0,1) if heavier for (int col = 0; col < 2; col++) { int top = col + 2; // 2 or 3 — top row (higher Y) int bot = col; // 0 or 1 — bottom row (lower Y) if (behaviors[top] != BEHAVIOR_SOLID && behaviors[bot] != BEHAVIOR_SOLID) { if (densities[top] > densities[bot]) { // swap entire vec4 vec4 tmp = cells[top]; cells[top] = cells[bot]; cells[bot] = tmp; // update cached props float tmpB = behaviors[top]; behaviors[top] = behaviors[bot]; behaviors[bot] = tmpB; float tmpD = densities[top]; densities[top] = densities[bot]; densities[bot] = tmpD; } } } // diagonal: if a top cell couldn't fall straight down, try diagonal // top-left (2) -> bottom-right (1), top-right (3) -> bottom-left (0) bool tryLeftFirst = (seed & 1u) == 0u; if (tryLeftFirst) { // try 2->1 diagonal (top-left to bottom-right) if (col0Blocked && behaviors[2] != BEHAVIOR_SOLID && behaviors[1] != BEHAVIOR_SOLID && densities[2] > densities[1]) { vec4 tmp = cells[2]; cells[2] = cells[1]; cells[1] = tmp; float tmpB = behaviors[2]; behaviors[2] = behaviors[1]; behaviors[1] = tmpB; float tmpD = densities[2]; densities[2] = densities[1]; densities[1] = tmpD; } // try 3->0 diagonal (top-right to bottom-left) if (col1Blocked && behaviors[3] != BEHAVIOR_SOLID && behaviors[0] != BEHAVIOR_SOLID && densities[3] > densities[0]) { vec4 tmp = cells[3]; cells[3] = cells[0]; cells[0] = tmp; float tmpB = behaviors[3]; behaviors[3] = behaviors[0]; behaviors[0] = tmpB; float tmpD = densities[3]; densities[3] = densities[0]; densities[0] = tmpD; } } else { // try 3->0 first if (col1Blocked && behaviors[3] != BEHAVIOR_SOLID && behaviors[0] != BEHAVIOR_SOLID && densities[3] > densities[0]) { vec4 tmp = cells[3]; cells[3] = cells[0]; cells[0] = tmp; float tmpB = behaviors[3]; behaviors[3] = behaviors[0]; behaviors[0] = tmpB; float tmpD = densities[3]; densities[3] = densities[0]; densities[0] = tmpD; } // try 2->1 if (col0Blocked && behaviors[2] != BEHAVIOR_SOLID && behaviors[1] != BEHAVIOR_SOLID && densities[2] > densities[1]) { vec4 tmp = cells[2]; cells[2] = cells[1]; cells[1] = tmp; float tmpB = behaviors[2]; behaviors[2] = behaviors[1]; behaviors[1] = tmpB; float tmpD = densities[2]; densities[2] = densities[1]; densities[1] = tmpD; } } fragColor = cells[localIndex]; }