Skip to content

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.

An explosive bunch of particles.

Rules pictured here:

(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)

Experiments tagged p5 (49/85)

Experiments tagged particles (2/3)

Experiments on this site (49/85)