136 lines
3.8 KiB
Plaintext
136 lines
3.8 KiB
Plaintext
#version 430 core
|
|
layout(local_size_x = 16, local_size_y = 16) in;
|
|
|
|
// state_curr: read-only; state_next: write-only
|
|
layout(r8ui, binding = 0) readonly uniform uimage2D state_curr;
|
|
layout(r8ui, binding = 1) writeonly uniform uimage2D state_next;
|
|
|
|
// claim buffer to resolve conflicts
|
|
layout(r32ui, binding = 2) coherent uniform uimage2D claim;
|
|
|
|
uniform ivec2 u_gridSize;
|
|
uniform uint u_frame;
|
|
|
|
const uint AIR = 0u;
|
|
const uint SAND = 1u;
|
|
|
|
// How far down we scan to approximate column thickness
|
|
const int MAX_SCAN = 6;
|
|
|
|
// How much “taller” this column must be vs neighbor before we slide
|
|
// Smaller -> shallower, more flowy piles
|
|
const int SLOPE_THRESHOLD = 2;
|
|
|
|
uint hash(uvec2 p, uint seed) {
|
|
p = p * 1664525u + 1013904223u + seed;
|
|
p.x ^= p.y >> 16;
|
|
p.y ^= p.x >> 16;
|
|
return p.x ^ p.y;
|
|
}
|
|
|
|
// Approximate thickness of the sand column under (x, y)
|
|
int columnThickness(int x, int y) {
|
|
int t = 0;
|
|
for (int dy = 0; dy < MAX_SCAN; ++dy) {
|
|
int yy = y + dy;
|
|
if (yy >= u_gridSize.y) break;
|
|
uint v = imageLoad(state_curr, ivec2(x, yy)).r;
|
|
if (v == SAND) {
|
|
t++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
|
|
void main() {
|
|
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
|
if (pos.x >= u_gridSize.x || pos.y >= u_gridSize.y) return;
|
|
|
|
uint self = imageLoad(state_curr, pos).r;
|
|
if (self != SAND) return;
|
|
|
|
// Lock bottom row in place
|
|
if (pos.y == u_gridSize.y - 1) {
|
|
imageStore(state_next, pos, uvec4(SAND, 0u, 0u, 0u));
|
|
return;
|
|
}
|
|
|
|
ivec2 dest = pos;
|
|
|
|
// Only relax grains that are resting on something
|
|
ivec2 down = pos + ivec2(0, 1);
|
|
uint below = imageLoad(state_curr, down).r;
|
|
if (below == SAND) {
|
|
int hSelf = columnThickness(pos.x, pos.y);
|
|
|
|
bool canLeft = false;
|
|
bool canRight = false;
|
|
|
|
ivec2 left = pos + ivec2(-1, 0);
|
|
ivec2 right = pos + ivec2( 1, 0);
|
|
|
|
int hLeft = 0;
|
|
int hRight = 0;
|
|
|
|
// Check left side
|
|
if (left.x >= 0) {
|
|
uint leftCell = imageLoad(state_curr, left).r;
|
|
if (leftCell == AIR) {
|
|
hLeft = columnThickness(left.x, pos.y);
|
|
int slopeL = hSelf - hLeft;
|
|
if (slopeL > SLOPE_THRESHOLD) {
|
|
canLeft = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check right side
|
|
if (right.x < u_gridSize.x) {
|
|
uint rightCell = imageLoad(state_curr, right).r;
|
|
if (rightCell == AIR) {
|
|
hRight = columnThickness(right.x, pos.y);
|
|
int slopeR = hSelf - hRight;
|
|
if (slopeR > SLOPE_THRESHOLD) {
|
|
canRight = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (canLeft || canRight) {
|
|
if (canLeft && canRight) {
|
|
// Prefer the *lower* column; randomize if equal
|
|
if (hLeft < hRight) {
|
|
dest = left;
|
|
} else if (hRight < hLeft) {
|
|
dest = right;
|
|
} else {
|
|
uint h = hash(uvec2(pos), u_frame);
|
|
bool chooseLeft = (h & 1u) != 0u;
|
|
dest = chooseLeft ? left : right;
|
|
}
|
|
} else if (canLeft) {
|
|
dest = left;
|
|
} else { // canRight
|
|
dest = right;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool staying = (dest.x == pos.x && dest.y == pos.y);
|
|
|
|
if (staying) {
|
|
imageStore(state_next, pos, uvec4(SAND, 0u, 0u, 0u));
|
|
} else {
|
|
// Moving to new cell; resolve conflicts via claim buffer
|
|
uint old = imageAtomicCompSwap(claim, dest, 0u, 1u);
|
|
if (old == 0u) {
|
|
imageStore(state_next, dest, uvec4(SAND, 0u, 0u, 0u));
|
|
} else {
|
|
// lost: stay put
|
|
imageStore(state_next, pos, uvec4(SAND, 0u, 0u, 0u));
|
|
}
|
|
}
|
|
}
|