Skip to content

Instantly share code, notes, and snippets.

@jdmichaud
Created August 28, 2025 08:18
Show Gist options
  • Save jdmichaud/90d67ae962bdf1e70a08b347b8919d7b to your computer and use it in GitHub Desktop.
Save jdmichaud/90d67ae962bdf1e70a08b347b8919d7b to your computer and use it in GitHub Desktop.
Sand
<!DOCTYPE html>
<html>
<head>
<title>Sand</title>
<meta charset="utf-8">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<script type="text/javascript">
const SAND = 0xEF | 0xDD << 8 | 0x6F << 16 | 0xFF << 24;
const WATER = 0x1C | 0xA3 << 8 | 0xEC << 16 | 0xFF << 24;
let run = true;
function init(canvas, buffer) {
// buffer[(canvas.height / 2) * canvas.width + canvas.width / 2] = SAND;
}
function applyPhysics(width, height, originBuffer, destinationBuffer) {
const lastLine = width * (height - 1);
destinationBuffer.fill(0xFF000000);
destinationBuffer.set(originBuffer.subarray(lastLine), lastLine);
for (let y = height - 2; y >= 0; y--) {
for (let x = 0; x < width; x++) {
const index = y * width + x;
const belowIndex = index + width;
// TODO: fix limit
const belowLeftIndex = index + width - 1;
const belowRightIndex = index + width + 1;
if ((originBuffer[index] & SAND) === SAND) {
if ((destinationBuffer[belowIndex] & SAND) !== SAND) {
destinationBuffer[belowIndex] = SAND;
} else {
if ((destinationBuffer[belowLeftIndex] & SAND) !== SAND && x > 0) {
destinationBuffer[belowLeftIndex] = SAND;
} else if ((destinationBuffer[belowRightIndex] & SAND) !== SAND && x < width - 1) {
destinationBuffer[belowRightIndex] = SAND;
} else destinationBuffer[index] = SAND;
}
} else if ((originBuffer[index] & WATER) === WATER) {
if ((destinationBuffer[belowIndex] & 0x00FFFFFF) === 0) {
destinationBuffer[belowIndex] = WATER;
} else {
let i;
for (i = 0; i < width; ++i) {
if ((destinationBuffer[belowIndex - i] & 0x00FFFFFF) === 0 && x > 0) {
destinationBuffer[belowIndex - i] = WATER;
break;
} else if ((destinationBuffer[belowIndex + i] & 0x00FFFFFF) === 0 && x < width - 1) {
destinationBuffer[belowIndex + i] = WATER;
break;
}
}
if (i === width) {
destinationBuffer[index] = WATER
};
}
}
}
}
}
function mouseSet(buffer, viewport, canvas, event, type) {
const posX = (event.clientX / viewport.width * canvas.width) | 0;
const posY = (event.clientY / viewport.height * canvas.height) | 0;
console.log(posX, posY);
buffer[posY * canvas.width + posX] = type;
}
function main() {
console.log('ready');
const viewport = document.getElementById('viewport');
const viewportCtx = viewport.getContext('2d');
viewportCtx.imageSmoothingEnabled = false;
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const context = canvas.getContext('2d');
context.imageSmoothingEnabled = false
context.fillStyle = 'black';
context.fillRect(0, 0, canvas.width, canvas.height);
let imageData = context.getImageData(0, 0, canvas.width, canvas.height);
let imageData2 = context.getImageData(0, 0, canvas.width, canvas.height);
let buffer = new Uint32Array(imageData.data.buffer);
let buffer2 = new Uint32Array(imageData2.data.buffer);
init(canvas, buffer)
let imageDataTmp;
let bufferTmp;
function render(timestamp) {
applyPhysics(canvas.width, canvas.height, buffer, buffer2);
bufferTmp = buffer;
buffer = buffer2;
buffer2 = bufferTmp;
imageDataTmp = imageData;
imageData = imageData2;
imageData2 = imageDataTmp;
context.putImageData(imageData, 0, 0);
viewportCtx.drawImage(canvas, 0, 0, viewport.width, viewport.height);
if (run) {
window.requestAnimationFrame(timestamp => render(timestamp));
}
}
window.requestAnimationFrame(timestamp => render(timestamp));
viewport.addEventListener('mousedown', downEvent => {
let event = downEvent;
console.log(downEvent);
const interval = setInterval(() => mouseSet(buffer, viewport, canvas, event, downEvent.buttons === 1 ? SAND : WATER), 16);
viewport.addEventListener('mousemove', moveEvent => {
event = moveEvent;
});
viewport.addEventListener('mouseup', () => {
clearInterval(interval);
});
});
document.body.addEventListener('contextmenu', e => {
e.preventDefault();
});
}
window.onload = main;
</script>
</head>
<body>
<canvas id="viewport" width="768" height="768"></canvas>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment