Particle automata
An experiment in making rules for interactions between colored particles, and seeing the kinds of systems that evolve from that.
Couldn't figure a way to fit momentum preservation in without having all particles collapse into one rapidly-interacting particle complex, so, currently every interaction gains kinetic energy but we add a bit of friction so it doesn't explode.
Rules pictured here:
- A green captures a blue to become 1 purple.
- A yellow captures a blue to become 1 red.
- A green and a yellow collide to form 4 blue.
- A red and a blue collide to form 2 green.
- A purple and a blue collide to form 2 yellow.
(The Green + Blue + Blue -> 2 Yellow chain (and v.v.) is necessary to maintain an on-going reaction; otherwise, the blue particles are quickly absorbed by groups of green/yellow to form non-reactive red/purple)
function setup() {
let c = createCanvas(400, 400);
let graph = new p5(() => {})
let graphC = graph.createCanvas(400, 200);
graph.background(0);
graph.noStroke();
let colors = {
'R': color(255, 0, 0),
'P': color(150, 0, 150),
'G': color(0, 150, 0),
'Y': color(200, 150, 0),
'B': color(0, 0, 255)
}
let radii = {
'R': 12,
'P': 12,
'G': 10,
'Y': 10,
'B': 8
}
let rules = {
'GY': 'BBBB',
'RB': 'GG',
'PB': 'YY',
'GB': 'P',
'YB': 'R',
}
let cells = []
for (let i = 0; i < 100; i++) {
cells.push({
t: random(Object.keys(colors)),
x: random(width),
y: random(height),
vx: random(-1, 1),
vy: random(-1, 1),
i: 0
})
}
draw = function () {
background(220);
fill(0)
noStroke()
translate(200, 200)
scale(1)
translate(-200, -200)
let colorKeys = Object.keys(colors)
let graphT = (frameCount) % graph.width
let counts = cells.reduce((a,x) => (a[x.t] = (a[x.t] || 0) + 1, a), {})
graph.fill(0, 15)
graph.rect(graphT, 0, 30, graph.height)
graph.fill(0)
graph.rect(graphT, 0, 5, graph.height)
let total = 0
for(let i = 0; i < colorKeys.length; i++) {
graph.fill(colors[colorKeys[i]])
total += counts[colorKeys[i]] || 0
graph.rect(graphT, graph.height - total, 1, counts[colorKeys[i]] || 0)
}
for(let frame = 0; frame < 3; frame ++) {
let grid = Object.create(null)
for (let i = 0; i < cells.length; i ++) {
let d = mag(cells[i].x - 200, cells[i].y - 200)
cells[i].vx -= (cells[i].x - 200) / 10000000 * d
cells[i].vy -= (cells[i].y - 200) / 10000000 * d
cells[i].vx *= 0.995
cells[i].vy *= 0.995
cells[i].x += cells[i].vx
cells[i].y += cells[i].vy
if (cells[i].i) {
cells[i].i --
stroke(255)
} else {
for (let dx = 0; dx < 2; dx ++) {
for (let dy = 0; dy < 2; dy ++) {
let gk = [floor(cells[i].x / 10) + dx, floor(cells[i].y / 10) + dy]
grid[gk] = grid[gk] || []
grid[gk].push(i)
}
}
noStroke()
}
if (frame == 0) {
fill(colors[cells[i].t])
circle(cells[i].x, cells[i].y, radii[cells[i].t])
}
}
for (let gk in grid) {
for (let i of grid[gk]) {
for (let j of grid[gk]) {
if (i != j && cells[i] && cells[j] &&
rules[cells[i].t + cells[j].t] !== undefined &&
(cells[i].x - cells[j].x) ** 2 + (cells[i].y - cells[j].y) ** 2 < 10**2) {
let newTypes = rules[cells[i].t + cells[j].t].split("")
let newCells = newTypes.map(t => ({t, i: 1}))
for (let k of ['x', 'y', 'vx', 'vy']) {
let low = min(cells[i][k], cells[j][k])
let high = max(cells[i][k], cells[j][k])
let weights = Array(max(newTypes.length - 1, 0)).fill().map(t => random(1))
let totalWeight = weights.reduce((a,b) => a + b, 0)
let cumulativeWeight = 0
newCells = shuffle(newCells)
for (let i = 0; i < newCells.length; i ++) {
newCells[i][k] = lerp(low, high, totalWeight > 0 ? cumulativeWeight / totalWeight : 0.5)
cumulativeWeight += weights[i]
}
}
for (let i = 0; i < newCells.length; i ++) {
newCells[i].vx += random(-1, 1) * 0.8
newCells[i].vy += random(-1, 1) * 0.8
}
cells[i] = undefined
cells[j] = undefined
cells.push(...newCells)
}
}
}
}
cells = cells.filter(x => x)
}
}
}
var draw(Originally seen at https://editor.p5js.org/bojidar-bg/sketches/9v7WujmAo)
Browse more articles?
← Bowling planet Experiments tagged p5 (49/85) Circle wave →
← Particles 1 Experiments tagged particles (2/3) Particles 2 →
← Bowling planet Experiments on this site (49/85) Circle wave →