Skip to content

Instantly share code, notes, and snippets.

@schollz
Created April 27, 2026 03:24
Show Gist options
  • Select an option

  • Save schollz/7eee1c9ed8061ff31bc4205fe09bff99 to your computer and use it in GitHub Desktop.

Select an option

Save schollz/7eee1c9ed8061ff31bc4205fe09bff99 to your computer and use it in GitHub Desktop.
reaction diffusion
// 16x16 display, higher-res internal sim (p5.js)
const VIEW_W = 16;
const VIEW_H = 16;
const SCALE = 32;
// internal resolution (must be multiple of 16)
const SIM_W = 64;
const SIM_H = 64;
const BLOCK = SIM_W / VIEW_W; // 4
let grid, nextGrid;
let dA = 1.0;
let dB = 0.5;
let baseFeed = 0.036;
let baseKill = 0.064;
let t = 0;
function setup() {
createCanvas(VIEW_W * SCALE, VIEW_H * SCALE);
noStroke();
grid = makeGrid(SIM_W, SIM_H);
nextGrid = makeGrid(SIM_W, SIM_H);
for (let y = 0; y < SIM_H; y++) {
for (let x = 0; x < SIM_W; x++) {
grid[y][x].a = 1;
grid[y][x].b = random(0, 0.2);
}
}
}
function draw() {
background(255);
t += 0.01;
let feed = baseFeed + 0.004 * sin(t * 0.6);
let kill = baseKill + 0.004 * cos(t * 0.4);
for (let i = 0; i < 8; i++) {
step(feed, kill);
}
injectNoise();
renderDownsampled();
}
function makeGrid(w, h) {
let g = [];
for (let y = 0; y < h; y++) {
g[y] = [];
for (let x = 0; x < w; x++) {
g[y][x] = { a: 1, b: 0 };
}
}
return g;
}
function step(feed, kill) {
for (let y = 0; y < SIM_H; y++) {
for (let x = 0; x < SIM_W; x++) {
let a = grid[y][x].a;
let b = grid[y][x].b;
let lapA = laplace(x, y, "a");
let lapB = laplace(x, y, "b");
let reaction = a * b * b;
let newA = a + (dA * lapA - reaction + feed * (1 - a)) * 0.9;
let newB = b + (dB * lapB + reaction - (kill + feed) * b) * 0.9;
nextGrid[y][x].a = constrain(newA, 0, 1);
nextGrid[y][x].b = constrain(newB, 0, 1);
}
}
let tmp = grid;
grid = nextGrid;
nextGrid = tmp;
}
function laplace(x, y, chem) {
let sum = 0;
sum += sample(x, y, chem) * -1;
sum += sample(x - 1, y, chem) * 0.2;
sum += sample(x + 1, y, chem) * 0.2;
sum += sample(x, y - 1, chem) * 0.2;
sum += sample(x, y + 1, chem) * 0.2;
sum += sample(x - 1, y - 1, chem) * 0.05;
sum += sample(x + 1, y - 1, chem) * 0.05;
sum += sample(x - 1, y + 1, chem) * 0.05;
sum += sample(x + 1, y + 1, chem) * 0.05;
return sum;
}
function sample(x, y, chem) {
x = (x + SIM_W) % SIM_W;
y = (y + SIM_H) % SIM_H;
return grid[y][x][chem];
}
// downsample 64x64 → 16x16
function renderDownsampled() {
for (let gy = 0; gy < VIEW_H; gy++) {
for (let gx = 0; gx < VIEW_W; gx++) {
let sum = 0;
for (let dy = 0; dy < BLOCK; dy++) {
for (let dx = 0; dx < BLOCK; dx++) {
let x = gx * BLOCK + dx;
let y = gy * BLOCK + dy;
sum += grid[y][x].a - grid[y][x].b;
}
}
let avg = sum / (BLOCK * BLOCK);
avg = constrain(avg, 0, 1);
// 16 grayscale levels
let level = floor(avg * 15);
let shade = level * (255 / 15);
fill(shade);
rect(gx * SCALE, gy * SCALE, SCALE, SCALE);
}
}
}
function injectNoise() {
for (let i = 0; i < 8; i++) {
let x = floor(random(SIM_W));
let y = floor(random(SIM_H));
grid[y][x].b += random(0.01, 0.03);
grid[y][x].b = constrain(grid[y][x].b, 0, 1);
}
}
// click = precise injection
function mousePressed() {
let gx = floor(mouseX / SCALE);
let gy = floor(mouseY / SCALE);
for (let dy = 0; dy < BLOCK; dy++) {
for (let dx = 0; dx < BLOCK; dx++) {
let x = gx * BLOCK + dx;
let y = gy * BLOCK + dy;
grid[y][x].b = 1;
}
}
}
// drag = smear
function mouseDragged() {
let gx = floor(mouseX / SCALE);
let gy = floor(mouseY / SCALE);
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
let cx = (gx + dx + VIEW_W) % VIEW_W;
let cy = (gy + dy + VIEW_H) % VIEW_H;
for (let sy = 0; sy < BLOCK; sy++) {
for (let sx = 0; sx < BLOCK; sx++) {
let x = cx * BLOCK + sx;
let y = cy * BLOCK + sy;
grid[y][x].b += 0.05;
grid[y][x].b = constrain(grid[y][x].b, 0, 1);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment