The Nature Of Code   Chp02 - Forces

Example 2.1
Forces

p5.js

let mover;

function setup() {
  createCanvas(560,390);
  mover = new Mover();
}

function draw() {
  background(220);
  let wind = createVector(0.01, 0);
  let gravity = createVector(0, 0.1);
  mover.applyForce(wind);
  mover.applyForce(gravity);
  mover.update();
  mover.display();
  mover.checkEdges();
}

class Mover {
  constructor(){
    this.mass = 1; // object now has mass, set to 1 for simplicity
    this.location = createVector(30, 30);
    this.velocity = createVector(0, 0);
    this.acceleration = createVector(0, 0);
  }
  
  // Newton's second law: force=accelaration*mass
  applyForce(force) {
    // force/mass=acceleration
    let f = p5.Vector.div(force, this.mass); // receive force, divide by mass
    this.acceleration.add(f); // add force to acceleration
  }

  update() {
    this.velocity.add(this.acceleration);
    this.location.add(this.velocity);
    // clearing acceleration for each frame, so it wouldn't add onto itself from the previous frame
    this.acceleration.mult(0); 
  }

  display() {
    stroke(0);
    strokeWeight(2);
    fill(63,63,147);
    // scaling the size according to mass
    ellipse(this.location.x, this.location.y, this.mass*48, this.mass*48);
  }
  
  // object bounces when it hits the edge of window
  checkEdges() {
    if (this.location.x > width) {
      this.location.x = width;
      this.velocity.x *= -1;
    } else if (this.location.x < 0) {
      this.velocity.x *= -1;
      this.location.x = 0;
    }
    if (this.location.y > height) {
      this.velocity.y *= -1; // reverse the direction of object when it reaches the edge 
      this.location.y = height;
    }
  }
}
							

Example 2.2
Forces Acting On Many Objects

Because acceleration = force/mass, the larger the mass, the smaller the acceleration. So, the smaller circles reach the right of the window faster than the larger ones   p5.js

let movers = [];

function setup() {
  createCanvas(560,390);
  for (let i = 0; i < 20; i++) {
    // initialize objects with random mass and start at 0,0
    movers[i] = new Mover(random(0.1,5),0,0);
  }
}

function draw() {
  background(220);
    for (let i = 0; i < movers.length; i++) {
    let wind = createVector(0.01,0);
    let gravity = createVector(0,0.1); // make up two forces
    // apply both forces to each objects
    movers[i].applyForce(wind);
    movers[i].applyForce(gravity);
    movers[i].update();
    movers[i].display();
    movers[i].checkEdges();
  }
}

class Mover {
  constructor(m, x, y) {
    // mass and location initialized via arguments now
    this.mass = m;
    this.location = createVector(x, y);
    this.velocity = createVector(0, 0);
    this.acceleration = createVector(0, 0);
  }
  
  applyForce(force) {
    // acceleration = force/mass
    let f = p5.Vector.div(force, this.mass);
    this.acceleration.add(f);
  }

  update() {
    this.velocity.add(this.acceleration);
    this.location.add(this.velocity);
    this.acceleration.mult(0); 
  }

  display() {
    stroke(0);
    strokeWeight(2);
    fill(63,63,147,150);
    // scaling the size according to mass
    // the smaller circles reach the right of the window faster than the larger ones
    // because acceleration = force/mass, the larger the mass, the smaller the acceleration
    ellipse(this.location.x, this.location.y, this.mass*16, this.mass*16);
  }
  
  // object bounces when it hits the edge of window
  checkEdges() {
    if (this.location.x > width) {
      this.location.x = width;
      this.velocity.x *= -1;
    } else if (this.location.x < 0) {
      this.velocity.x *= -1;
      this.location.x = 0;
    }
    if (this.location.y > height) {
      this.velocity.y *= -1; // reverse the direction of object when it reaches the edge 
      this.location.y = height;
    }
  }
}
                            

Example 2.3
Gravity Scaled By Mass

If the force is scaled according to mass, it is canceled out when acceleration is divided by mass, but the strength of the wind force is independent of mass, the smaller objects still accelerate to the right more quickly   p5.js

let movers = [];

function setup() {
  createCanvas(560,390);
  for (let i = 0; i < 20; i++) {
    // initialize objects with random mass and start at 0,0
    movers[i] = new Mover(random(1,4),0,0);
  }
}

function draw() {
  background(220);
    for (let i = 0; i < movers.length; i++) {
    let wind = createVector(0.01,0);
    let gravity = createVector(0,0.1*movers[i].mass); // scaling gravity by mass to be more accurate
    // apply both forces to each objects
    movers[i].applyForce(wind);
    movers[i].applyForce(gravity);
    movers[i].update();
    movers[i].display();
    movers[i].checkEdges();
  }
}

class Mover {
  constructor(m, x, y) {
    // mass and location initialized via arguments now
    this.mass = m;
    this.location = createVector(x, y);
    this.velocity = createVector(0, 0);
    this.acceleration = createVector(0, 0);
  }
  
  applyForce(force) {
    // acceleration = force/mass
    let f = p5.Vector.div(force, this.mass);
    this.acceleration.add(f);
  }

  update() {
    this.velocity.add(this.acceleration);
    this.location.add(this.velocity);
    this.acceleration.mult(0); 
  }

  display() {
    stroke(0);
    strokeWeight(2);
    fill(63,63,147,150);
    ellipse(this.location.x, this.location.y, this.mass*16, this.mass*16);
  }
  
  // object bounces when it hits the edge of window
  checkEdges() {
    if (this.location.x > width) {
      this.location.x = width;
      this.velocity.x *= -1;
    } else if (this.location.x < 0) {
      this.velocity.x *= -1;
      this.location.x = 0;
    }
    if (this.location.y > height) {
      this.velocity.y *= -1; // reverse the direction of object when it reaches the edge 
      this.location.y = height;
    }
  }
}
                            

Example 2.4
Including Friction

Friction continuously pushes against the object in the opposite direction of its movement, the object continuously slows down   p5.js

let movers = [];

function setup() {
  createCanvas(560,390);
  for (let i = 0; i < 20; i++) {
    // initialize objects with random mass and start at 0,0
    movers[i] = new Mover(random(0.1,5),0,0);
  }
}

function draw() {
  background(220);
    for (let i = 0; i < movers.length; i++) {
    let wind = createVector(0.01,0);
    let gravity = createVector(0,0.1*movers[i].mass);
    // friction = -1 * coefficient of friction * normal force * velocity unit vector
    let c = 0.01; // set coefficient of friction to 0.01
    let normal = 1; // set normal force to 1
    let frictionMag = c * normal; // magnitude of friction
    let friction = movers[i].velocity.copy(); // make a copy before using it (because object is pass by reference)
    friction.mult(-1);
    friction.normalize();
    friction.mult(frictionMag); // take unit vector and multiply it by magnitude = force vector
    // friction continuously pushes against the object in the opposite direction of its movement
    // the object continuously slows down
    movers[i].applyForce(friction); // apply friction force vector to objects
    movers[i].applyForce(wind);
    movers[i].applyForce(gravity);
    movers[i].update();
    movers[i].display();
    movers[i].checkEdges();
  }
}

class Mover {
  constructor(m, x, y) {
    // mass and location initialized via arguments
    this.mass = m;
    this.location = createVector(x, y);
    this.velocity = createVector(0, 0);
    this.acceleration = createVector(0, 0);
  }
  
  applyForce(force) {
    let f = p5.Vector.div(force, this.mass);
    this.acceleration.add(f);
  }

  update() {
    this.velocity.add(this.acceleration);
    this.location.add(this.velocity);
    this.acceleration.mult(0); 
  }

  display() {
    stroke(0);
    strokeWeight(2);
    fill(63,63,147,150);
    ellipse(this.location.x, this.location.y, this.mass*16, this.mass*16);
  }
  
  // object bounces when it hits the edge of window
  checkEdges() {
    if (this.location.x > width) {
      this.location.x = width;
      this.velocity.x *= -1;
    } else if (this.location.x < 0) {
      this.velocity.x *= -1;
      this.location.x = 0;
    }
    if (this.location.y > height) {
      this.velocity.y *= -1; 
      this.location.y = height;
    }
  }
}
                            

Example 2.5
Fluid Resistance

(Click mouse to reset) Because Newton’s second law, Acceleration = Force / Mass, a massive object will accelerate less, a smaller object will accelerate more which means "slow donw" there. So, the smaller objects will slow down at a greater rate than the larger ones   p5.js

let movers = [];
let liquid;

function setup() {
  // text must be before create graphics
  let text = createP("Click mouse to reset");
  createCanvas(560,390);
  reset();
  // create liquid object
  // coefficient of drag(0.1) is low otherwise object would come to faily quick
  liquid = new Liquid(0, height/2, width, height/2, 0.1);

  // call methods of each element to set location and id
  text.position(10,5);
}

function draw() {
  background(220);
  liquid.display(); // draw liquid
    for (let i = 0; i < movers.length; i++) {
    if (liquid.contains(movers[i])) { // check if Mover is inside liquid
      let dragForce = liquid.calculateDrag(movers[i]); // calculate drag force
      movers[i].applyForce(dragForce); // Mover apply drag force
    }

    let gravity = createVector(0, 0.1 * movers[i].mass); // gravity is scaled by mass
    movers[i].applyForce(gravity); // Mover apply gravity

    // update and display
    movers[i].update();
    movers[i].display();
    movers[i].checkEdges();
  }
}

// reset all the Mover objects randomly
function reset() {
  for (let i = 0; i < 9; i++) {
    movers[i] = new Mover(random(0.5, 3), 40 + i * 70, 0);
  }
}

function mousePressed() {
  reset();
}

class Liquid {
  // liquid object will be a rectangle and has location, width, height, and coefficient of drag
  constructor(x, y, w, h, c) { 
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.c = c;
  }

  // if Mover is inside the rectangle defined by the Liquid class
  contains(m) {
    let l = m.location;
    return l.x > this.x && l.x < this.x + this.w &&
      l.y > this.y && l.y < this.y + this.h;
  }

  // calculate drag force
  calculateDrag(m) {
    // magnitude = coefficient * speed * speed
    let speed = m.velocity.mag();
    let dragMagnitude = this.c * speed * speed;

    // force direction is inverse of velocity = -1 * velocity
    let dragForce = m.velocity.copy();
    dragForce.mult(-1);

    // scale according to magnitude
    dragForce.normalize();
    dragForce.mult(dragMagnitude); // finalize the force, magnitude and direction together
    return dragForce;
  }

  display() {
    noStroke();
    fill(50);
    rect(this.x, this.y, this.w, this.h);
  }
}

class Mover {
  constructor(m, x, y) {
    this.mass = m;
    this.location = createVector(x, y);
    this.velocity = createVector(0, 0);
    this.acceleration = createVector(0, 0);
  }
  // Newton's 2nd law: F = M * A, so A = F / M
  applyForce(force) {
    let f = p5.Vector.div(force, this.mass);
    this.acceleration.add(f);
  }

  update() {
    this.velocity.add(this.acceleration); // velocity changes according to acceleration
    this.location.add(this.velocity); // location changes by velocity
    this.acceleration.mult(0); // clearing acceleration for each frame
  }


  display() {
    stroke(0);
    strokeWeight(2);
    fill(63,63,147);
    ellipse(this.location.x, this.location.y, this.mass*16, this.mass*16);
  }
  
  // bounce off bottom of window
  checkEdges() {
    if (this.location.y > height) {
      this.velocity.y *= -0.9; // a little dampening when hitting the bottom
      this.location.y = height;
    }
  }
}
                            

Example 2.6
Attraction

Drag and move big circle with mouse to interact with   p5.js

let mover;
let attractor;

function setup() {
  createCanvas(560,390);
  mover = new Mover();
  attractor = new Attractor(); // initialize Attractor object
}

function draw() {
  background(220);
  // apply the attraction force from the Attractor on the Mover
  let force = attractor.calculateAttraction(mover);
  mover.applyForce(force);
  mover.update();
  attractor.display(); // display Attrctor object
  mover.display();
}

function mouseMoved() {
  attractor.handleHover(mouseX,mouseY);
}

function mousePressed() {
  attractor.handlePress(mouseX,mouseY);
}

function mouseDragged() {
  attractor.handleHover(mouseX,mouseY);
  attractor.handleDrag(mouseX,mouseY);
}

function mouseReleased() {
  attractor.stopDragging();
}

class Attractor { // an object for a draggable Attractor
  constructor() {
    this.location = createVector(width/2, height/2);
    this.mass = 20; // Attractor doesn't move so we just need a mass and location
    this.G = 1;
    this.dragOffset = createVector(0,0);
    this.dragging = false;
    this.rollover = false;
  }
  
  calculateAttraction(m) {
    // Calculate direction of force
    // vector that points from one object to another
    let force = p5.Vector.sub(this.location,m.location);
    // distance between two objects
    let distance = force.mag();
    // Limiting the distance to eliminate "extreme" results for very close or very far objects
    // constrain the range if it's less than 5 pixels or more than 25 pixels away from the attractor
    distance = constrain(distance,5,25);
    // Normalize vector (distance doesn't matter here, we just want this vector for direction)
    // normalize and scale the force vector to the appropriate magnitude
    force.normalize();
    // use the formula for gravity to compute the strength of the force
    let strength = (this.G * this.mass * m.mass) / (distance * distance);
    // Get force vector --> magnitude * direction
    force.mult(strength);
    return force; // return force so it can be applied
  }
  // method to display
  display() {
    // https://p5js.org/reference/#/p5/ellipseMode
    ellipseMode(CENTER);
    strokeWeight(4);
    stroke(0);
    if (this.dragging) {
      fill(50);
    } else if (this.rollover) {
      fill(100);
    } else {
      fill(175, 200);
    }
    ellipse(this.location.x, this.location.y, this.mass * 2, this.mass * 2);
  }
  // mouse interaction
  handlePress(mx, my) {
    let d = dist(mx, my, this.location.x, this.location.y);
    if (d < this.mass) {
      this.dragging = true;
      this.dragOffset.x = this.location.x - mx;
      this.dragOffset.y = this.location.y - my;
    }
  }
    // mouse interaction
  handleHover(mx, my) {
    let d = dist(mx, my, this.location.x, this.location.y);
    if (d < this.mass) {
      this.rollover = true;
    } else {
      this.rollover = false;
    }
  }
    // mouse interaction
  stopDragging() {
    this.dragging = false;
  }
    // mouse interaction
  handleDrag(mx, my) {
    if (this.dragging) {
      this.location.x = mx + this.dragOffset.x;
      this.location.y = my + this.dragOffset.y;
    }
  }
}

class Mover {
  constructor() {
    this.location = createVector(400, 50);
    this.velocity = createVector(1, 0);
    this.acceleration = createVector(0, 0);
    this.mass = 1;
  }
  // Newton's 2nd law: F = M * A, so A = F / M
  applyForce(force) {
    let f = p5.Vector.div(force, this.mass);
    this.acceleration.add(f);
  }

  update() {
    this.velocity.add(this.acceleration); // velocity changes according to acceleration
    this.location.add(this.velocity); // location changes by velocity
    this.acceleration.mult(0); // clearing acceleration for each frame
  }


  display() {
    stroke(0);
    strokeWeight(2);
    fill(63,63,147);
    ellipse(this.location.x, this.location.y, this.mass*16, this.mass*16);
  }
  
   checkEdges() {
    if (this.location.x > width) {
      this.location.x = width;
      this.velocity.x *= -1;
    } else if (this.location.x < 0) {
      this.velocity.x *= -1;
      this.location.x = 0;
    }
    if (this.location.y > height) {
      this.velocity.y *= -1;
      this.location.y = height;
    }
  }
}
                            

Example 2.7
Attraction With Many Movers

Drag and move big circle with mouse to interact with   p5.js

let movers = [];
let attractor;

function setup() {
  createCanvas(560,390);
  for (let i = 0; i < 10; i++) {
    movers[i] = new Mover(random(0.1,2), random(width), random(height));
  }
  attractor = new Attractor(); // initialize Attractor object
}

function draw() {
  background(220);
  attractor.display();
  for (let i = 0; i < movers.length; i++) {
    // apply the attraction force from the Attractor on the Mover
    let force = attractor.calculateAttraction(movers[i]);
    movers[i].applyForce(force);

    movers[i].update();
    movers[i].display();
  }
}

function mouseMoved() {
  attractor.handleHover(mouseX, mouseY);
}

function mousePressed() {
  attractor.handlePress(mouseX, mouseY);
}

function mouseDragged() {
  attractor.handleHover(mouseX, mouseY);
  attractor.handleDrag(mouseX, mouseY);
}

function mouseReleased() {
  attractor.stopDragging();
}

class Attractor { // an object for a draggable Attractor
  constructor() {
    this.location = createVector(width/2, height/2);
    this.mass = 20; // Attractor doesn't move so we just need a mass and location
    this.G = 1;
    this.dragOffset = createVector(0,0);
    this.dragging = false;
    this.rollover = false;
  }
  
  calculateAttraction(m) {
    // Calculate direction of force
    // vector that points from one object to another
    let force = p5.Vector.sub(this.location,m.location);
    // distance between two objects
    let distance = force.mag();
    // Limiting the distance to eliminate "extreme" results for very close or very far objects
    // constrain the range if it's less than 5 pixels or more than 25 pixels away from the attractor
    distance = constrain(distance,5,25);
    // Normalize vector (distance doesn't matter here, we just want this vector for direction)
    // normalize and scale the force vector to the appropriate magnitude
    force.normalize();
    // use the formula for gravity to compute the strength of the force
    let strength = (this.G * this.mass * m.mass) / (distance * distance);
    // Get force vector --> magnitude * direction
    force.mult(strength);
    return force; // return force so it can be applied
  }
  // method to display
  display() {
    // https://p5js.org/reference/#/p5/ellipseMode
    ellipseMode(CENTER);
    strokeWeight(4);
    stroke(0);
    if (this.dragging) {
      fill(50);
    } else if (this.rollover) {
      fill(100);
    } else {
      fill(175, 200);
    }
    ellipse(this.location.x, this.location.y, this.mass * 2, this.mass * 2);
  }
  // mouse interaction
  handlePress(mx, my) {
    let d = dist(mx, my, this.location.x, this.location.y);
    if (d < this.mass) {
      this.dragging = true;
      this.dragOffset.x = this.location.x - mx;
      this.dragOffset.y = this.location.y - my;
    }
  }
    // mouse interaction
  handleHover(mx, my) {
    let d = dist(mx, my, this.location.x, this.location.y);
    if (d < this.mass) {
      this.rollover = true;
    } else {
      this.rollover = false;
    }
  }
    // mouse interaction
  stopDragging() {
    this.dragging = false;
  }
    // mouse interaction
  handleDrag(mx, my) {
    if (this.dragging) {
      this.location.x = mx + this.dragOffset.x;
      this.location.y = my + this.dragOffset.y;
    }
  }
}

class Mover {
  constructor(mass, x, y) {
    this.location = createVector(x, y);
    this.velocity = createVector(1, 0);
    this.acceleration = createVector(0, 0);
    this.mass = mass;
  }
  // Newton's 2nd law: F = M * A, so A = F / M
  applyForce(force) {
    let f = p5.Vector.div(force, this.mass);
    this.acceleration.add(f);
  }

  update() {
    this.velocity.add(this.acceleration); // velocity changes according to acceleration
    this.location.add(this.velocity); // location changes by velocity
    this.acceleration.mult(0); // clearing acceleration for each frame
  }


  display() {
    stroke(0);
    strokeWeight(2);
    fill(63,63,147,180);
    ellipse(this.location.x, this.location.y, this.mass*16, this.mass*16);
  }
  
   checkEdges() {
    if (this.location.x > width) {
      this.location.x = width;
      this.velocity.x *= -1;
    } else if (this.location.x < 0) {
      this.velocity.x *= -1;
      this.location.x = 0;
    }
    if (this.location.y > height) {
      this.velocity.y *= -1;
      this.location.y = height;
    }
  }
}
                            

Example 2.8
Mutual Attraction

p5.js

let movers = [];
let G = 1;

function setup() {
  createCanvas(560,390);
  for (let i = 0; i < 10; i++) {
    movers[i] = new Mover(random(0.1, 2), random(width), random(height));
  }
}

function draw() {
  background(220);
  for (let i = 0; i < movers.length; i++) {
    for (let j = 0; j < movers.length; j++) { // for every Mover, check every Mover
      if (i !== j) { // avoid attracting yourself
        let force = movers[j].calculateAttraction(movers[i]);
        movers[i].applyForce(force);
      }
    }

    movers[i].update();
    movers[i].display();
  }
}

class Mover {
  constructor(m, x, y) {
    this.mass = m;
    this.location = createVector(x, y);
    this.velocity = createVector(0, 0);
    this.acceleration = createVector(0, 0);
  }

  applyForce(force) {
    let f = p5.Vector.div(force, this.mass);
    this.acceleration.add(f);
  }

  update() {
    this.velocity.add(this.acceleration);
    this.location.add(this.velocity);
    this.acceleration.mult(0);
  }

  display() {
    stroke(0);
    strokeWeight(2);
    fill(63,63,147,180);
    ellipse(this.location.x, this.location.y, this.mass * 25, this.mass * 25);
  }

  calculateAttraction(m) {
    // Calculate direction of force
    let force = p5.Vector.sub(this.location, m.location);
    // Distance between objects
    let distance = force.mag();
    // Limiting the distance to eliminate "extreme" results for very close or very far objects
    distance = constrain(distance, 5.0, 25.0);
    // Normalize vector (distance doesn't matter here, we just want this vector for direction
    force.normalize();
    // Calculate gravitional force magnitude
    let strength = (G * this.mass * m.mass) / (distance * distance);
    // Get force vector --> magnitude * direction
    force.mult(strength);
    return force;
  }
}
                            

The Nature Of Code

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