html, body {
  margin: 0;
}
/**
* Work-in-progress flocks simulation
*/

let flocks = [];
const pane = new Tweakpane.Pane();
let params = {
  align: 1.2,
  cohesion: 1.5,
  separation: 1.8
}

class Particle {
  constructor() {
    this.position = createVector(random(width), random(height))
    this.velocity = p5.Vector.random2D()
    this.velocity.setMag(random(2, 4));
    this.acceleration = createVector()
    this.maxForce = 0.3
    this.maxSpeed = 5
  }
  
  edges() {
    if (this.position.x < 0) {
      let reflect = createVector(this.maxSpeed, this.velocity.y)
      reflect.sub(this.velocity)
      reflect.limit(this.maxForce)
      this.acceleration.add(reflect)
    } else if (this.position.x > width) {
      let reflect = createVector(-this.maxSpeed, this.velocity.y)
      reflect.sub(this.velocity)
      reflect.limit(this.maxForce)
      this.acceleration.add(reflect)
    }
    if (this.position.y < 0) {
      let reflect = createVector(this.velocity.x, this.maxSpeed)
      reflect.sub(this.velocity)
      reflect.limit(this.maxForce)
      this.acceleration.add(reflect)
    } else if (this.position.y > height) {
      let reflect = createVector(this.velocity.x, -this.maxSpeed)
      reflect.sub(this.velocity)
      reflect.limit(this.maxForce)
      this.acceleration.add(reflect)
    }
  }
  
  align(flocks) {
    let senseRadius = 10
    let steering = createVector()
    let total = 0;
    
    for (let other of flocks) {
      if (other != this) {
        let d = this.position.dist(other.position)
        if (d <= senseRadius) {
          steering.add(other.velocity)
          total++
        }
      }
    }
    
    if (total > 0) {
      steering.div(total)
      steering.setMag(this.maxSpeed)
      steering.sub(this.velocity)
      steering.limit(this.maxForce)
    }
    
    return steering
  }
  
  cohesion(flocks) {
    let senseRadius = 50
    let steering = createVector()
    let total = 0
    
    for(let other of flocks) {
      if (other != this) {
        let d = this.position.dist(other.position)
        if (d <= senseRadius) { 
          steering.add(other.position)
          total++
        }
      }
    }
        
    if (total > 0) {
      steering.div(total)
      steering.sub(this.position)
      steering.setMag(this.maxSpeed)
      steering.sub(this.velocity)
      steering.limit(this.maxForce)
    }
    
    return steering
  }
  
  separation(flocks) {
    let senseRadius = 15
    let steering = createVector()
    let total = 0
    
    for(let other of flocks) {
      if (other != this) {
        let d = this.position.dist(other.position)
        if (d <= senseRadius) {
          let push = p5.Vector.sub(this.position, other.position)
          push.div(d)
          
          steering.add(push)
          total++
        }
      }
    }
        
    if (total > 0) {
      steering.div(total)
      steering.setMag(this.maxSpeed)
      steering.sub(this.velocity)
      steering.limit(this.maxForce)
    }
    
    return steering
  }
  
  behavior(flocks) {
    let alignSteering = this.align(flocks)
    let cohesionSteering = this.cohesion(flocks)
    let separationSteering = this.separation(flocks)
    
    alignSteering.mult(params.align)
    cohesionSteering.mult(params.cohesion)
    separationSteering.mult(params.separation)
    
    this.acceleration.add(alignSteering)
    this.acceleration.add(cohesionSteering)
    this.acceleration.add(separationSteering)
  }
  
  update() {
    this.position.add(this.velocity)
    this.velocity.add(this.acceleration)
    this.velocity.limit(this.maxSpeed)
    this.acceleration.mult(0)
  }
  
  draw() {
    push()
    stroke('#9f5f80')
    fill('#ff8474')
    strokeWeight(2)
    let angle = atan2(this.velocity.y, this.velocity.x)
    translate(this.position.x, this.position.y)
    rotate(angle)
    quad(-15, 0, 0, -5, 5, 0, 0, 5)
    pop()
  }
}

function setup() {
  createCanvas(windowWidth, windowHeight)
  
  pane.addInput(params, 'align', {
    min: 0,
    max: 2
  })
  pane.addInput(params, 'cohesion', {
    min: 0,
    max: 2
  })
  pane.addInput(params, 'separation', {
    min: 0,
    max: 2
  })
  
  for(let i=0; i<100; i++) {
    flocks.push(new Particle())
  }
}

function draw() {
  background('#393e46')
  
  for(let p of flocks) {
    p.edges()
    p.behavior(flocks)
    p.update()
    p.draw()
  }
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js
  2. https://cdn.jsdelivr.net/npm/tweakpane@3.0.4/dist/tweakpane.min.js