The Nature Of Code   Chp04 - Particle Systems

Example 4.1
A single Particle

p5.js

let p;

function setup() {
  createCanvas(560,390);
  p = new Particle(createVector(width / 2, 20));
}

function draw() {
  background(220);

  p.run(); //operating single particle
  if (p.isDead()) {
    p = new Particle(createVector(width / 2, 20));
  }
}

class Particle {
  constructor(location) {
    //a particle object has location, velocity and acceleration
    //for demonstration purpose, assign particle an initial velocity and constant acceleration
    this.acceleration = createVector(0, 0.05);
    this.velocity = createVector(random(-1, 1), random(-1, 0));
    this.location = location.copy();
    //lifespan is for keep track of how long the particle has been alive
    //lifespan start at 255 and count down for convenience
    this.lifespan = 255.0;
  }
  
    //us run function to call all the other functions that we need
  run() {
    this.update();
    this.display();
  }

  // Method to update location
  update() {
    this.velocity.add(this.acceleration);
    this.location.add(this.velocity);
    //lifespan decrease
    this.lifespan -= 2;
  }

  display() {
    //lifespan range from 255 to 0, we use it as alpha for stroke and fill
    stroke(50, this.lifespan);
    strokeWeight(2);
    fill(63,63,147, this.lifespan);
    ellipse(this.location.x, this.location.y, 12, 12);
  }

  // Is the particle still useful?
  isDead() {
    if (this.lifespan < 0.0) {
      return true;
    } else {
      return false;
    }
  }
}
							

Example 4.2
ArrayList Of Particles With Iterator

p5.js

let particles = [];

function setup() {
  createCanvas(560,390);
}

function draw() {
  background(220);
  particles.push(new Particle(createVector(width / 2, 50)));

  // Looping through backwards to delete
  for (var i = particles.length - 1; i >= 0; i--) {
    var p = particles[i];
    p.run();
    //if particle is dead, go ahead and delete it from the list
    if (p.isDead()) {
      //remove the particle
      particles.splice(i, 1);
    }
  }
}

class Particle {

  constructor(position) {
    this.acceleration = createVector(0, 0.05);
    this.velocity = createVector(random(-1, 1), random(-1, 0));
    this.position = position.copy();
    this.lifespan = 255.0;
  }

  run() {
    this.update();
    this.display();
  }

  // Method to update position
  update() {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.lifespan -= 2;
  }

  display() {
    //lifespan range from 255 to 0, we use it as alpha for stroke and fill
    stroke(50, this.lifespan);
    strokeWeight(2);
    fill(63,63,147,this.lifespan);
    ellipse(this.position.x, this.position.y, 12, 12);
  }

  // Is the particle still useful?
  isDead() {
    if (this.lifespan < 0.0) {
      return true;
    } else {
      return false;
    }
  }
}
                            

Example 4.3
Simple Single Particle System

p5.js

let ps;

function setup() {
  createCanvas(560,390);
  ps = new ParticleSystem(createVector(width / 2, 50));
}

function draw() {
  background(220);
  ps.addParticle();
  ps.run();
}

class ParticleSystem {
  constructor(position) {
    //origin point where each Particle begins
    this.origin = position.copy();
    this.particles = [];
  }
    
  //origin is passed to each Particle when it is added
  addParticle() {
    this.particles.push(new Particle(this.origin));
  }

  run() {
    // Run every particle
    for (let particle of this.particles) {
      particle.run();
    }

    // Filter removes any elements of the array that do not pass the test
    this.particles = this.particles.filter(particle => !particle.isDead());
  }
}

class Particle {
  constructor(position) {
    this.acceleration = createVector(0, 0.05);
    this.velocity = createVector(random(-1, 1), random(-1, 0));
    this.position = position.copy();
    this.lifespan = 255.0;
  }

  run() {
    this.update();
    this.display();
  }

  // Method to update position
  update() {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.lifespan -= 2;
  }

  // Method to display
  display() {
    //lifespan range from 255 to 0, we use it as alpha for stroke and fill
    stroke(50, this.lifespan);
    strokeWeight(2);
    fill(63,63,147,this.lifespan);
    ellipse(this.position.x, this.position.y, 12, 12);
  }

  // Is the particle still useful?
  isDead() {
    if (this.lifespan < 0.0) {
      return true;
    } else {
      return false;
    }
  }
}
                            

Example 4.4
System Of Systems

Click to add particle systems   p5.js

let systems = [];

function setup() {
  let text = createP("Click to add particle systems");
  text.position(10, 5);

  createCanvas(560, 390);
}

function draw() {
  background(220);
  for (let i = 0; i < systems.length; i++) {
    systems[i].addParticle();
    systems[i].run();
  }
}

function mousePressed() {
  systems.push(new ParticleSystem(1, createVector(mouseX, mouseY)));
}

class ParticleSystem {
  constructor(num, position) {
    this.origin = position.copy();
    this.particles = [];
    for (let i = 0; i < num; i++) {
      this.particles.push(new Particle(this.origin));
    }
  }

  addParticle() {
    this.particles.push(new Particle(this.origin));
  }

  run() {
    // Run every particle
    for (let particle of this.particles) {
      particle.run();
    }

    // Filter removes any elements of the array that do not pass the test
    this.particles = this.particles.filter(particle => !particle.isDead());
  }
}

class Particle {
  constructor(position) {
    this.acceleration = createVector(0, 0.05);
    this.velocity = createVector(random(-1, 1), random(-1, 0));
    this.position = position.copy();
    this.lifespan = 255.0;
  }

  run() {
    this.update();
    this.display();
  }

  // Method to update position
  update() {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.lifespan -= 2;
  }

  display() {
    //lifespan range from 255 to 0, we use it as alpha for stroke and fill
    stroke(50, this.lifespan);
    strokeWeight(2);
    fill(63,63,147,this.lifespan);
    ellipse(this.position.x, this.position.y, 12, 12);
  }

  // Is the particle still useful?
  isDead() {
    if (this.lifespan < 0.0) {
      return true;
    } else {
      return false;
    }
  }
}
                            

Example 4.5
Particle System Inheritance And Polymorphism

p5.js

let particleSystem;

function setup() {
  createCanvas(560, 390);
  particleSystem = new ParticleSystem(createVector(width / 2, 50));
}

function draw() {
  background(220);
  particleSystem.addParticle();
  particleSystem.run();
}

class ParticleSystem {

  constructor(position) {
    this.origin = position.copy();
    this.particles = [];
  }

  addParticle() {
    let r = random(1);
    //50 percent chance of adding each kind of Particle 
    if (r < 0.5) {
      this.particles.push(new Particle(this.origin));
    } else {
      this.particles.push(new Confetti(this.origin));
    }
  }

  run() {
    // Run every particle
    for (let particle of this.particles) {
      particle.run();
    }

    // Filter removes any elements of the array that do not pass the test
    this.particles = this.particles.filter(particle => !particle.isDead());
  }
}

class Particle {

  constructor(position) {
    this.acceleration = createVector(0, 0.05);
    this.velocity = createVector(random(-1, 1), random(-1, 0));
    this.position = position.copy();
    this.lifespan = 255;
  }

  run() {
    this.update();
    this.display();
  }

  // Method to update position
  update() {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.lifespan -= 2;
  }

  display() {
    stroke(50,this.lifespan);
    strokeWeight(2);
    fill(63,63,147,this.lifespan);
    ellipse(this.position.x, this.position.y, 12, 12);
  }

  // Is the particle still useful?
  isDead() {
    if (this.lifespan < 0.0) {
      return true;
    } else {
      return false;
    }
  }
}

// Child class constructor
class Confetti extends Particle {

  // Override the display method
  display() {
    rectMode(CENTER);
    fill(63,63,147,this.lifespan);
    stroke(50, this.lifespan);
    strokeWeight(2);
    push();
    translate(this.position.x, this.position.y);
    var theta = map(this.position.x, 0, width, 0, TWO_PI * 2);
    rotate(theta);
    rect(0, 0, 12, 12);
    pop();
  }
}
                            

Example 4.6
Particle System With Forces

p5.js

let ps;

function setup() {
  createCanvas(560, 390);
  setFrameRate(60);
  ps = new ParticleSystem(createVector(width / 2, 50));
}

function draw() {
  background(220);

  // Apply gravity force to all Particles
  let gravity = createVector(0, 0.1);
  ps.applyForce(gravity);

  ps.addParticle();
  ps.run();
}

class ParticleSystem {
  constructor(position) {
    this.origin = position.copy();
    this.particles = [];
  }

  addParticle() {
    this.particles.push(new Particle(this.origin));
  }

  run() {
    // Run every particle
    for (let particle of this.particles) {
      particle.run();
    }

    // Filter removes any elements of the array that do not pass the test
    this.particles = this.particles.filter(particle => !particle.isDead());
  }

  // A function to apply a force to all Particles using an enhanced loop
  applyForce(f) {
    for (let i = 0; i < this.particles.length; i++) {
      this.particles[i].applyForce(f);
    }
  }
}

class Particle {
  constructor(position) {
    //start with acceleration of 0
    this.acceleration = createVector(0, 0.0);
    this.velocity = createVector(random(-1, 1), random(-2, 0));
    this.position = position.copy();
    this.lifespan = 255.0;
    this.mass = 1; // vary mass for more interesting result
  }

  run() {
    this.update();
    this.display();
  }
    
  //Newton's second law and force accumulation
  applyForce(force) {
    let f = force.copy();
    f.div(this.mass);
    this.acceleration.add(f);
  }

  // Method to update position
  update() {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.acceleration.mult(0);
    this.lifespan -= 2.0;
  }

  display() {
    stroke(50,this.lifespan);
    strokeWeight(2);
    fill(63,63,147,this.lifespan);
    ellipse(this.position.x, this.position.y, 12, 12);
  }

  // Is the particle still useful?
  isDead() {
    if (this.lifespan < 0.0) {
      return true;
    } else {
      return false;
    }
  }
}
                            

Example 4.7
ParticleSystem With Repeller

Move mouse to interact with   p5.js

let ps;
let repeller;

function setup() {
  createCanvas(560, 390);
  ps = new ParticleSystem(createVector(width / 2, 50));
  repeller = new Repeller(width / 2, height / 2);
}

function draw() {
  background(220);
  ps.addParticle(mouseX, mouseY);

  // Apply gravity force to all Particles
  let gravity = createVector(0, 0.02);
  ps.applyForce(gravity);

  ps.applyRepeller(repeller);

  repeller.display();
  ps.run();
}

class Repeller {
  constructor(x, y) {
    this.power = 150;
    this.position = createVector(x, y);
  }

  display() {
    stroke(50);
    strokeWeight(2);
    fill(63,63,147);
    ellipse(this.position.x, this.position.y, 32, 32);
  }

  repel(p) {
    let dir = p5.Vector.sub(this.position, p.position); // Calculate direction of force
    let d = dir.mag(); // Distance between objects
    dir.normalize(); // Normalize vector (distance doesn't matter here, we just want this vector for direction)
    d = constrain(d, 1, 100); // Keep distance within a reasonable range
    let force = -1 * this.power / (d * d); // Repelling force is inversely proportional to distance
    dir.mult(force); // Get force vector --> magnitude * direction
    return dir;
  }
};

class ParticleSystem {
  constructor(position) {
    this.origin = position.copy();
    this.particles = [];
  }

  addParticle(x, y) {
    if (x !== undefined && y !== undefined) {
      this.particles.push(new Particle(x, y));
    } else {
      this.particles.push(new Particle(this.origin.x, this.origin.y));
    }
  }

  run() {
    // Run every particle
    for (let particle of this.particles) {
      particle.run();
    }

    // Filter removes any elements of the array that do not pass the test
    this.particles = this.particles.filter(particle => !particle.isDead());
  }

  // A function to apply a force to all Particles
  applyForce(f) {
    for (let particle of this.particles) {
      particle.applyForce(f);
    }
  }

  applyRepeller(r) {
    for (let particle of this.particles) {
      let force = r.repel(particle);
      particle.applyForce(force);
    }
  }
}

class Particle {
  constructor(x, y) {
    this.position = createVector(x, y);
    this.velocity = createVector(random(-1, 1), random(-1, 0));
    this.acceleration = createVector(0, 0);
    this.lifespan = 255.0;
  }

  run() {
    this.update();
    this.display();
  }

  applyForce(f) {
    this.acceleration.add(f);
  }

  // Method to update position
  update() {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.lifespan -= 2;

    this.velocity.limit(5);
  }

  display() {
    stroke(50,this.lifespan);
    strokeWeight(2);
    fill(63,63,147,this.lifespan);
    ellipse(this.position.x, this.position.y, 12, 12);
  }


  // Is the particle still useful?
  isDead() {
    if (this.lifespan < 0.0) {
      return true;
    } else {
      return false;
    }
  }
}
                            

The Nature Of Code

  • Introduction
  • Vectors
  • Forces
  • Oscillation
  • Particle Systems
  • Physics Libraries
  • Autonomous Agents
  • speng2@wpi.edu  (Liz Peng)