Abelian sandpile
An exploration of animating Abelian sandpiles. Turned more fun by adding "negative sandpiles" and ensuring the total sum on-screen is always 0.
Click to drop a square grain of sand. Right click to drop a circle grain of sand. Enjoy the chain reactions.
let N = 35
let counts = Array(N * N).fill(0)
let nextCounts = counts.slice()
let t = 1
function setup() {
let s = min(windowWidth, windowHeight)
createCanvas(s, s);
rectMode(CENTER)
document.querySelector('canvas').addEventListener('contextmenu', e => e.preventDefault()) // https://github.com/processing/p5.js/issues/7098
}
function windowResized() {
let s = min(windowWidth, windowHeight)
resizeCanvas(s, s);
}
function draw() {
background(220);
t += keyIsDown(32) ? 1 : deltaTime / 100
if (t > 1) {
;[counts, nextCounts] = [nextCounts, counts]
for (let i = 0; i < nextCounts.length; i++) {
nextCounts[i] = counts[i] % 4 +
Math.trunc(counts[(i + 1) % counts.length] / 4) +
Math.trunc(counts[(i + counts.length - 1) % counts.length] / 4) +
Math.trunc(counts[(i + N) % counts.length] / 4) +
Math.trunc(counts[(i + counts.length - N) % counts.length] / 4)
}
if (mouseIsPressed) {
let unitSize = width / N
let amount = (mouseButton.right ? -1 : 1)
let mousePos = (floor(mouseX / unitSize) + floor(mouseY / unitSize) * N) % nextCounts.length
nextCounts[mousePos] += amount
nextCounts[floor(random(nextCounts.length))] -= amount
}
t = 0
}
let interp = t
for (let i = 0; i < counts.length; i++) {
drawGrain(i % N, floor(i / N), lerp(counts[i], nextCounts[i], interp))
}
}
function drawGrain(i, j, size) {
let unitSize = width / N
let sizeOnScreen = unitSize * (abs(size) / 4) ** 0.8
push()
fill(paletteLerp([
[color(100, 255, 255), -8],
[color(0, 255, 255), -4],
[color(0, 0, 255), -0],
[color(255, 255, 0), 0],
[color(255, 100, 0), 4],
[color(255, 0, 100), 8]
], size))
translate((i + 0.5) * unitSize, (j + 0.5) * unitSize)
if (size < -3) {
rotate(PI / 4 * min(-3 - size, 1) ** 3)
let fracture = sizeOnScreen * sqrt(-3 - size) * 0.1
arc(+fracture, +fracture, sizeOnScreen, sizeOnScreen, 0, PI / 2, PIE)
arc(-fracture, +fracture, sizeOnScreen, sizeOnScreen, PI / 2, PI, PIE)
arc(-fracture, -fracture, sizeOnScreen, sizeOnScreen, PI, PI * 3 / 2, PIE)
arc(+fracture, -fracture, sizeOnScreen, sizeOnScreen, PI * 3 / 2, PI * 2, PIE)
} else if (size < 0) {
circle(0, 0, sizeOnScreen)
} else if (size <= 3) {
rect(0, 0, sizeOnScreen)
} else {
rotate(PI / 4 * min(size - 3, 1) ** 3)
let fracture = sizeOnScreen * (0.25 + sqrt(size - 3) * 0.1)
rect(-fracture, -fracture, sizeOnScreen/2)
rect(+fracture, -fracture, sizeOnScreen/2)
rect(+fracture, +fracture, sizeOnScreen/2)
rect(-fracture, +fracture, sizeOnScreen/2)
}
pop()
}(Originally seen at https://editor.p5js.org/bojidar-bg/sketches/CqOEm4Zpr)
Browse more articles?
← Sync-unsync Experiments tagged p5 (45/85) Interactive truchet →
← Scroll-to-rotate, 3D Experiments tagged interactive (15/26) Interactive truchet →
← Sync-unsync Experiments on this site (45/85) Interactive truchet →