birth: Diffusing Patterns of Quiet Breath

This commit is contained in:
motd_admin 2026-04-03 13:47:18 +00:00
parent 497378face
commit 50012d3424

208
index.html Normal file
View file

@ -0,0 +1,208 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generative Art: Reaction-Diffusion System</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #0a0a0a;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: 'Courier New', monospace;
}
canvas {
display: block;
background-color: #0a0a0a;
}
#attribution {
position: absolute;
bottom: 20px;
color: #555;
font-size: 12px;
text-align: center;
width: 100%;
}
</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 resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Parameters derived from the spec
const params = {
motion: 0.509,
density: 0.451,
complexity: 0.496,
connectedness: 0.483,
lifespan: 0.525,
survivingNodes: 37,
branchCount: 23,
loops: 33,
maxDepth: 15,
thicknessRatio: 1.25,
fractalDimension: 0.839,
finalEnergy: 195.8,
pulse: { avg: 1.07, min: 0.30, max: 2.00 },
tone: { anger: 0.00, sadness: 0.00, curiosity: 0.10, dryness: 0.90, playfulness: 0.00, tension: 0.00 }
};
// Reaction-diffusion parameters
const rdParams = {
feed: 0.055,
kill: 0.062,
diffusionA: 1.0,
diffusionB: 0.5,
timeStep: 0.1,
gridSize: 4,
decay: 0.05,
pulseSpeed: 0.02
};
// Initialize reaction-diffusion grid
const cols = Math.floor(canvas.width / rdParams.gridSize);
const rows = Math.floor(canvas.height / rdParams.gridSize);
let grid = new Array(cols * rows);
let nextGrid = new Array(cols * rows);
// Initialize with small random perturbations
for (let i = 0; i < grid.length; i++) {
grid[i] = Math.random() < params.density ? 1 : 0;
nextGrid[i] = 0;
}
// Store activation history for trails
const activationHistory = new Array(cols * rows).fill(0);
// Colors based on tone parameters
const color = {
r: Math.floor(255 * params.tone.dryness),
g: Math.floor(255 * params.tone.dryness),
b: Math.floor(255 * params.tone.dryness),
alpha: 0.8
};
function update() {
// Apply periodic pulse modulation
const pulseMod = 1 + params.pulse.avg * Math.sin(Date.now() * rdParams.pulseSpeed);
// Reaction-diffusion simulation
for (let x = 1; x < cols - 1; x++) {
for (let y = 1; y < rows - 1; y++) {
const idx = x + y * cols;
// Neighborhood indices
const idxN = x + (y - 1) * cols;
const idxS = x + (y + 1) * cols;
const idxE = (x + 1) + y * cols;
const idxW = (x - 1) + y * cols;
const idxNE = (x + 1) + (y - 1) * cols;
const idxNW = (x - 1) + (y - 1) * cols;
const idxSE = (x + 1) + (y + 1) * cols;
const idxSW = (x - 1) + (y + 1) * cols;
const laplacianA = (grid[idxN] + grid[idxS] + grid[idxE] + grid[idxW] +
grid[idxNE] + grid[idxNW] + grid[idxSE] + grid[idxSW] - 8 * grid[idx]);
const laplacianB = (nextGrid[idxN] + nextGrid[idxS] + nextGrid[idxE] + nextGrid[idxW] +
nextGrid[idxNE] + nextGrid[idxNW] + nextGrid[idxSE] + nextGrid[idxSW] - 8 * nextGrid[idx]);
// Reaction terms with pulse modulation
const reactionA = grid[idx] * (rdParams.feed - (rdParams.kill + 1) * nextGrid[idx] / (1 + params.tone.dryness * grid[idx]));
const reactionB = nextGrid[idx] * (-rdParams.feed * nextGrid[idx] / (1 + params.tone.dryness * grid[idx]) + reactionA);
// Update with diffusion and reaction
nextGrid[idx] = grid[idx] + rdParams.timeStep * (
rdParams.diffusionA * laplacianA +
reactionA * pulseMod
);
grid[idx] = nextGrid[idx] + rdParams.timeStep * (
rdParams.diffusionB * laplacianB +
reactionB * pulseMod
);
// Apply decay and bounds
grid[idx] = Math.max(0, Math.min(1, grid[idx] - rdParams.decay * params.motion));
nextGrid[idx] = Math.max(0, Math.min(1, nextGrid[idx] - rdParams.decay * params.motion));
// Update activation history for trails
activationHistory[idx] = activationHistory[idx] * 0.9 + grid[idx] * 0.1;
}
}
// Swap grids
[grid, nextGrid] = [nextGrid, grid];
// Draw with trail effect
ctx.fillStyle = `rgba(${color.r}, ${color.g}, ${color.b}, ${color.alpha * params.tone.dryness})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'lighter';
for (let x = 0; x < cols; x++) {
for (let y = 0; y < rows; y++) {
const idx = x + y * cols;
const val = activationHistory[idx];
if (val > 0.2 && Math.random() < params.complexity) {
const size = rdParams.gridSize * val * params.connectedness;
const alpha = val * (0.3 + params.motion * 0.7);
// Draw branching structures based on complexity
if (Math.random() < params.complexity) {
ctx.beginPath();
ctx.arc(
x * rdParams.gridSize + rdParams.gridSize/2,
y * rdParams.gridSize + rdParams.gridSize/2,
size,
0,
Math.PI * 2
);
ctx.globalAlpha = alpha * 0.7;
ctx.fill();
}
// Draw connecting lines based on connectedness
if (params.connectedness > 0.5 && x > 0 && y > 0) {
ctx.beginPath();
ctx.moveTo(
x * rdParams.gridSize + rdParams.gridSize/2,
y * rdParams.gridSize + rdParams.gridSize/2
);
ctx.lineTo(
(x - 1) * rdParams.gridSize + rdParams.gridSize/2,
(y - 1) * rdParams.gridSize + rdParams.gridSize/2
);
ctx.globalAlpha = alpha * 0.3;
ctx.strokeStyle = `rgba(${color.r}, ${color.g}, ${color.b}, ${alpha * 0.3})`;
ctx.stroke();
}
}
}
}
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1;
requestAnimationFrame(update);
}
update();
</script>
</body>
</html>