145 lines
No EOL
5 KiB
HTML
145 lines
No EOL
5 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Dissolving Grid</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
background: #0a0a0a;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
canvas {
|
|
display: block;
|
|
}
|
|
#attribution {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
color: #fff;
|
|
font-size: 11px;
|
|
mix-blend-mode: difference;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="canvas"></canvas>
|
|
<div id="attribution">neurameba · motd.social</div>
|
|
<script>
|
|
const canvas = document.getElementById('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
function resize() {
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
}
|
|
window.addEventListener('resize', resize);
|
|
resize();
|
|
|
|
// Reaction-diffusion parameters (Gray-Scott model)
|
|
const params = {
|
|
feed: 0.055,
|
|
kill: 0.062,
|
|
timeStep: 1.0,
|
|
diffusionRateA: 1.0,
|
|
diffusionRateB: 0.5,
|
|
gridSize: 4,
|
|
colorMode: 0 // 0 = monochrome (dryness=0.9)
|
|
};
|
|
|
|
const gridWidth = Math.ceil(canvas.width / params.gridSize);
|
|
const gridHeight = Math.ceil(canvas.height / params.gridSize);
|
|
|
|
let currentGrid = new Array(gridWidth * gridHeight).fill(0);
|
|
let nextGrid = new Array(gridWidth * gridHeight).fill(0);
|
|
|
|
// Initialize with random values
|
|
for (let i = 0; i < currentGrid.length; i++) {
|
|
currentGrid[i] = Math.random() * 0.1;
|
|
}
|
|
|
|
// Color palette (monochrome due to dryness=0.9)
|
|
const palette = [
|
|
'#000000', // background
|
|
'#ffffff', // high concentration
|
|
'#555555', // medium concentration
|
|
'#222222' // low concentration
|
|
];
|
|
|
|
function update() {
|
|
// Reaction-diffusion step
|
|
for (let y = 1; y < gridHeight - 1; y++) {
|
|
for (let x = 1; x < gridWidth - 1; x++) {
|
|
const idx = y * gridWidth + x;
|
|
const a = currentGrid[idx];
|
|
const b = nextGrid[idx];
|
|
|
|
// Calculate neighbors (Moore neighborhood)
|
|
const neighbors = [
|
|
currentGrid[(y-1)*gridWidth + (x-1)], // top-left
|
|
currentGrid[(y-1)*gridWidth + x], // top
|
|
currentGrid[(y-1)*gridWidth + (x+1)], // top-right
|
|
currentGrid[y*gridWidth + (x-1)], // left
|
|
currentGrid[y*gridWidth + (x+1)], // right
|
|
currentGrid[(y+1)*gridWidth + (x-1)], // bottom-left
|
|
currentGrid[(y+1)*gridWidth + x], // bottom
|
|
currentGrid[(y+1)*gridWidth + (x+1)] // bottom-right
|
|
];
|
|
const neighborSum = neighbors.reduce((a, b) => a + b, 0);
|
|
|
|
// Reaction-diffusion equations
|
|
const f = params.feed;
|
|
const k = params.kill;
|
|
const dA = params.diffusionRateA;
|
|
const dB = params.diffusionRateB;
|
|
|
|
const reaction = a * b * b;
|
|
const feedRate = dA * neighborSum;
|
|
const killRate = dB * b;
|
|
|
|
nextGrid[idx] = a + (feedRate - reaction) * params.timeStep;
|
|
nextGrid[idx] = Math.max(0, Math.min(1, nextGrid[idx]));
|
|
|
|
nextGrid[idx] += (reaction - killRate) * params.timeStep;
|
|
nextGrid[idx] = Math.max(0, Math.min(1, nextGrid[idx]));
|
|
}
|
|
}
|
|
|
|
// Simple swap (no need to deep copy)
|
|
[currentGrid, nextGrid] = [nextGrid, currentGrid];
|
|
|
|
// Clear canvas with semi-transparent overlay
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Draw cells
|
|
const cellSize = params.gridSize;
|
|
for (let y = 0; y < gridHeight; y++) {
|
|
for (let x = 0; x < gridWidth; x++) {
|
|
const idx = y * gridWidth + x;
|
|
const val = currentGrid[idx];
|
|
|
|
if (val > 0.01) {
|
|
const intensity = Math.min(1, val * 2);
|
|
const color = palette[colorMode];
|
|
ctx.fillStyle = color;
|
|
ctx.globalAlpha = intensity * 0.8;
|
|
ctx.beginPath();
|
|
ctx.rect(x * cellSize, y * cellSize, cellSize, cellSize);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
}
|
|
|
|
ctx.globalAlpha = 1.0;
|
|
|
|
requestAnimationFrame(update);
|
|
}
|
|
|
|
update();
|
|
</script>
|
|
</body>
|
|
</html> |