Created
August 28, 2025 08:18
-
-
Save jdmichaud/90d67ae962bdf1e70a08b347b8919d7b to your computer and use it in GitHub Desktop.
Sand
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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