The Nature Of Code   Chp03 - Oscillation

Example 3.1
Angular Motion Using Rotate()

The baton starts onscreen with no rotation and then spins faster and faster as the angle of rotation accelerates   p5.js

let angle = 0; //location
let aVelocity = 0; //velocity
let aAcceleration = 0.0001; //accelaration

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

function draw() {
  background(220);
    fill(63,63,147);
  stroke(0);
  strokeWeight(2);
  
  translate(width/2, height/2);
  rotate(angle);
    line(-60, 0, 60, 0);
  ellipse(60, 0, 16, 16);
  ellipse(-60, 0, 16, 16);

  angle += aVelocity; //angular = velocity.add(acceration)
  aVelocity += aAcceleration; //angular = location.add(velocity)
}
							

Example 3.2
Forces With (Arbitrary) Angular Motion

p5.js

let movers = [];
let attractor;

function setup() {
  createCanvas(560,390);
  for (let i = 0; i < 20; i++) {
    movers.push(new Mover(random(0.1, 2), random(width), random(height)));
  }
  attractor = new Attractor();
}

function draw() {
  background(220);
    attractor.display();
  for (let i = 0; i < movers.length; i++) {
    let force = attractor.calculateAttraction(movers[i]);
    movers[i].applyForce(force);
    movers[i].update();
    movers[i].display();
  }
}

class Attractor { //an object for a draggable attractive body
  constructor() {
    this.location = createVector(width / 2, height / 2);
    this.mass = 20;
    this.G = 1;
  }

  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, 25);
    // Normalize vector (distance doesn't matter here, we just want this vector for direction)
    force.normalize();
    // Calculate gravitional force magnitude
    let strength = (this.G * this.mass * m.mass) / (distance * distance);
    // Get force vector --> magnitude * direction
    force.mult(strength);
    return force;
  }

  display() {
    ellipseMode(CENTER);
    strokeWeight(2);
    stroke(0);
    fill(127);
    ellipse(this.location.x, this.location.y, this.mass * 2, this.mass * 2);
  }
}

class Mover {
  constructor(m, x, y) {
    this.location = createVector(x, y);
    this.velocity = createVector(random(-1, 1), random(-1, 1));
    this.acceleration = createVector(0, 0);
    this.mass = m;
    
    this.angle = 0;
    this.aVelocity = 0;
    this.aAcceleration = 0;
  }

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

  update() {
    this.location.add(this.velocity);
    this.velocity.add(this.acceleration);
    this.acceleration.mult(0);
    
    //angular motion
    this.angle += this.aVelocity;
    //calculate angular acceleration according to acceleration's horizontal direction and magnitude
    this.aAcceleration = this.acceleration.x / 10.0;
    this.aVelocity += this.aAcceleration;
    //use constrain() to ensure that angular velocity doesn't spin out of control
    //constrain() function: constrains a value between a minimum and maximum value
    this.aVelocity = constrain(this.aVelocity, -0.1, 0.1);
  }

  display() {
    //push() and pop() are necessary so the rotation of this shape doesn't affect the rest of our world
    stroke(0);
    fill(63,63,147,200);
    rectMode(CENTER);
    //push() function saves the current drawing style settings and transformations
    push();
    translate(this.location.x, this.location.y); //set origin at the shape's location
    rotate(this.angle); //rotate by the angle
    rect(0, 0, this.mass*16, this.mass*16);
    //while pop() function restores these settings.
    pop();
  }
}
                            

Example 3.3
Pointing In The Direction Of Motion

Move mouse to interact with   p5.js

let mover;

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

function draw() {
  background(220);
    mover.update();
  mover.checkEdges();
  mover.display();
}
class Mover {
  constructor() {
    this.position = createVector(width / 2, height / 2);
    this.velocity = createVector(0, 0);
    this.acceleration = 0;
    this.topspeed = 4;
    this.xoff = 1000;
    this.yoff = 0;
    this.r = 16;
  }

  update() {
    let mouse = createVector(mouseX, mouseY);
    let dir = p5.Vector.sub(mouse, this.position);
    dir.normalize();
    dir.mult(0.5);
    this.acceleration = dir;

    this.velocity.add(this.acceleration);
    this.velocity.limit(this.topspeed);
    this.position.add(this.velocity);
  }

  display() {
    //heading() function: calculates the angle of rotation for a vector (2D vectors only)
    let angle = this.velocity.heading();

    stroke(0);
    strokeWeight(2);
    fill(63,63,147);
    push();
    //https://p5js.org/reference/#/p5/rectMode
    rectMode(CENTER);

    translate(this.position.x, this.position.y);
    rotate(angle); //rotate according to angle
    rect(0, 0, 30, 10);

    pop();
  }

  checkEdges() {
    if (this.position.x > width) {
      this.position.x = 0;
    } else if (this.position.x < 0) {
      this.position.x = width;
    }

    if (this.position.y > height) {
      this.position.y = 0;
    } else if (this.position.y < 0) {
      this.position.y = height;
    }
  }
}
                            

Example 3.4
Polar To Cartesian

Convert a Polar coordinate (r,theta) to Cartesian coordinate (x,y)   p5.js

// convert a polar coordinate (r,theta) to cartesian (x,y):
// x = r * cos(theta)
// y = r * sin(theta)

let r;
let theta;

function setup() {
  createCanvas(560,390);
  //initialize all values
  r = height * 0.30;
  theta = 0;
}

function draw() {
  background(220);
    // translate the origin point to the center of the screen
  translate(width / 2, height / 2);

  // converting from polar (r,theta) to cartesian (x,y)
  // converting for use in ellipse() function
  let x = r * cos(theta);
  let y = r * sin(theta);
  
  // draw the ellipse at the cartesian coordinate
  // https://p5js.org/reference/#/p5/ellipseMode
  ellipseMode(CENTER);
  fill(63,63,147);
  stroke(50);
  strokeWeight(2);
  line(0, 0, x, y);
  ellipse(x, y, 48, 48);

  // increase the angle over time
  theta += 0.02;
}
                            

Example 3.5
Simple Harmonic Motion

Calculating horizontal location according to the formula for simple harmonic motion   p5.js

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

function draw() {
  background(220);
    
  let period = 120; //period is measued in frames (our unit of time for animation)
  let amplitude = 200; //amplitude is measured in pixel
  // calculate horizontal position according to formula for simple harmonic motion
  let x = amplitude * sin(TWO_PI * frameCount / period); 
  
  stroke(50);
  strokeWeight(2);
  fill(63,63,147);
  translate(width/2, height/2);
  line(0, 0, x, 0);
  ellipse(x, 0, 48, 48);
}
                            

Example 3.6
Simple Harmonic Motion II

Using the concept of angular velocity to increment an angle variable   p5.js

let angle = 0;
let aVelocity = 0.03;

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

function draw() {
  background(220);
    
  let amplitude = 200;
  let x = amplitude * sin(angle);
  angle += aVelocity; //use the concept of angular velocity to increment an  angle variable

  translate(width/2, height/2);
  
  stroke(50);
  strokeWeight(2);
  fill(63,63,147);
  line(0, 0, x, 0);
  ellipse(x, 0, 48, 48);
}
                            

Example 3.7
Oscillator Objects

p5.js

let oscillators = [];

function setup() {
  createCanvas(560,390);
  //initialize all objects
  for (let i = 0; i < 10; i++) {
    oscillators.push(new Oscillator());
  }
}

function draw() {
  background(220);
  
  // run all objects
  for (let i = 0; i < oscillators.length; i++) {
    oscillators[i].oscillate();
    oscillators[i].display();
  }
}

class Oscillator {
  constructor() {
    //track two angles
    this.angle = createVector();
    //random velocities, we need two angular velocities
    this.velocity = createVector(random(-0.05, 0.05), random(-0.05, 0.05));
    //random amplitudes, we need two amplitudes (for x-axis and y-axis)
    this.amplitude = createVector(random(20, width/2), random(20, height/2));
  }

  oscillate() {
    this.angle.add(this.velocity);
  }

  display() {
    //oscillating on x-axis and y-axis
    let x = sin(this.angle.x) * this.amplitude.x;
    let y = sin(this.angle.y) * this.amplitude.y;

    push();
    translate(width/2, height/2);
    stroke(50);
    strokeWeight(2);
    fill(63,63,147,200);
    line(0, 0, x, y); //drawing Oscillator as a line connecting a circle
    ellipse(x, y, 32, 32);
    pop();
  }
}
                            

Example 3.8
Static Wave Drawn As A Continuous Line

p5.js

let angle = 0;
//change angleVel to see different result
//the higher the angular velocity, the shorter the period
let angleVel = 0.2;

function setup() {
  createCanvas(560,390);
  background(220);
  stroke(50);
  strokeWeight(2);
  noFill();
    
  //use beginShape() and endShape() to connect the points with a line
  beginShape();
  for (let x = 0; x <= width; x += 5) {
    //calculate y location according to amplitude and sine of the angle
    //we use map() function instead to calculate the vertical location 
    let y = map(sin(angle), -1, 1, 0, height);
    //within beginShape(), we call vertex() to set all the vertices of our shape
    vertex(x, y);
    //increment the angle according to angular velocity
    angle += angleVel;
  }
  endShape();
}
                            

Example 3.9
The Wave

p5.js

//we need to have a variable to track what value of angle the wave should start with
let startAngle = 0;
let angleVel = 0.23;

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

function draw() {
  background(220);
    
  //in order to move the wave, we start at a different theta value each frame
  startAngle += 0.015;
  let angle = startAngle;

  for (let x = 0; x <= width; x += 15) {
    let y = map(sin(angle), -1, 1, 0, height);
    stroke(50);
    fill(63,63,147,150);
    strokeWeight(2);
    ellipse(x, y, 48, 48);
    angle += angleVel;
  }
}
                            

Example 3.10
Swinging Pendulum

p5.js

/*
A simple pendulum simulation
Given a pendulum with an angle theta (0 being the pendulum at rest) and a radius r
we can use sine to calculate the angular component of the gravitational force.

Gravity Force = Mass * Gravitational Constant;
Pendulum Force = Gravity Force * sine(theta)
Angular Acceleration = Pendulum Force / Mass = gravitational acceleration * sine(theta);

Note this is an ideal world scenario with no tension in the
pendulum arm, a more realistic formula might be:
Angular Acceleration = (g / R) * sine(theta)
*/

let p;

function setup() {
  createCanvas(560,390);
  //make a new Pendulum with an origin position and arm length
  p = new Pendulum(createVector(width / 2, 0), 175);
}

function draw() {
  background(220);
    p.go();
}

class Pendulum { 
  constructor(origin, r) {
    //these variables are keep track of the pendulum's various properties
    this.origin = origin.copy(); //location of arm origin
    this.location = createVector(); //location of bob
    this.r = r; //length of arm
    this.angle = PI / 4; //pendulum arm angle
    this.aVelocity = 0.0; //angular velocity
    this.aAcceleration = 0.0; //angular acceleration
    this.damping = 0.995; //arbitrary damping, reduces the velocity by 0.5%, the pendulum slows over time
    this.bob = 48.0; //arbitrary ball radius
  }
  go() {
    this.update();
    this.display();
  };

  //function to update position
  //update the pendulum’s angle according to our formula
  update() {
    let gravity = 0.4; //arbitrary constant
    //calculate acceleration (see: http://www.myphysicslab.com/pendulum1.html)
    this.aAcceleration = (-1 * gravity / this.r) * sin(this.angle); 
    this.aVelocity += this.aAcceleration; //increment velocity
    this.angle += this.aVelocity; //increment angle
    this.aVelocity *= this.damping; //apply damping
  }

  display() {
    //polar to cartesian conversion will tell us where's the bob relative to origin
    this.location.set(this.r * sin(this.angle), this.r * cos(this.angle), 0); 
    //make sure the position is relative to the pendulum's origin
    this.location.add(this.origin);

    stroke(50);
    strokeWeight(2);
    //draw the arm
    line(this.origin.x, this.origin.y, this.location.x, this.location.y);
    ellipseMode(CENTER);
    fill(63,63,147);
    //draw the bob
    ellipse(this.location.x, this.location.y, this.bob, this.bob);
  }
}
                            

Example 3.11
A Spring Connection

Drag the ball and interact with it   p5.js

//mover object
let bob;
//spring object
let spring;

function setup() {
  createCanvas(560,390);
  setFrameRate(60);
  //create objects at starting position
  //note third argument in Spring constructor is "rest length"
  spring = new Spring(width / 2, 10, 100);
  bob = new Bob(width / 2, 100);
}

function draw() {
  background(220);
    
  //apply a gravity force to the bob
  let gravity = createVector(0, 2);
  bob.applyForce(gravity);
  
  //connect the bob to the spring (this calculates the force)
  spring.connect(bob);
  //constrain spring distance between min and max
  spring.constrainLength(bob, 30, 200);
    
  //update bob
  bob.update();

  //draw everything
  spring.displayLine(bob); //draw a line between spring and bob
  bob.display();
  spring.display();
}

function mousePressed() {
  bob.handleClick(mouseX, mouseY);
}

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

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

class Bob {
  constructor(x, y) {
    this.location = createVector(x, y);
    this.velocity = createVector();
    this.acceleration = createVector();
    this.mass = 24;
    //arbitrary damping to simulate friction / drag
    this.damping = 0.98;
    //for user interaction
    this.dragOffset = createVector();
    this.dragging = false;
  }

  //standard Euler integration
  update() {
    this.velocity.add(this.acceleration);
    this.velocity.mult(this.damping);
    this.location.add(this.velocity);
    this.acceleration.mult(0);
  }

  //newton's law: F = M * A
  applyForce(force) {
    let f = force.copy();
    f.div(this.mass);
    this.acceleration.add(f);
  }

  //draw the bob
  display() {
    stroke(50);
    strokeWeight(2);
    fill(63,63,147);
    if (this.dragging) {
      fill(63,63,147,200);
    }
    ellipse(this.location.x, this.location.y, this.mass * 2, this.mass * 2);
  }

  handleClick(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;
    }
  }

  stopDragging() {
    this.dragging = false;
  }

  handleDrag(mx, my) {
    if (this.dragging) {
      this.location.x = mx + this.dragOffset.x;
      this.location.y = my + this.dragOffset.y;
    }
  }
}

//object to describe an anchor point that can connect to "Bob" objects via a spring
//http://www.myphysicslab.com/spring2d.html
class Spring {
  //constructor initializes the anchor point and rest length
  constructor(x, y, l) {
    //keep track of spring's anchor location
    this.anchor = createVector(x, y);
    //rest length and spring constant variables
    this.restLength = l;
    this.k = 0.2;
  }
  //calculate and apply spring force
  connect(b) {
    //vector pointing from anchor to bob location
    let force = p5.Vector.sub(b.location, this.anchor);
    //what is distance
    let d = force.mag();
    //stretch = difference between current distance and rest length
    let stretch = d - this.restLength;

    //calculate force according to Hooke's Law
    //F = k * stretch
    force.normalize();
    force.mult(-1 * this.k * stretch);
    //the function connect() takes care of calling applyForce() 
    //therefore doesn't have to return a vector to the calling area
    b.applyForce(force);
  }

  //constrain the distance between bob and anchor between min and max
  constrainLength(b, minLength, maxLength) {
    //a vector pointing from anchor to bob gives us the current length of the spring
    let dir = p5.Vector.sub(b.location, this.anchor);
    let d = dir.mag();
    //too short?
    if (d < minLength) {
      dir.normalize();
      dir.mult(minLength);
      //reset location and stop from moving (not realistic physics)
      b.location = p5.Vector.add(this.anchor, dir);
      b.velocity.mult(0);
      //too long?
    } else if (d > maxLength) {
      dir.normalize();
      dir.mult(maxLength);
      //reset location and stop from moving (not realistic physics)
      b.location = p5.Vector.add(this.anchor, dir);
      b.velocity.mult(0);
    }
  }

  //constrain the distance between bob and anchor between min and max
  constrainLength(bob, minlen, maxlen) {
    //a vector pointing from anchor to bob gives us the current length of the spring
    let dir = p5.Vector.sub(bob.location, this.anchor);
    let d = dir.mag();
    //too short?
    if (d < minlen) {
      dir.normalize();
      dir.mult(minlen);
      //reset position and stop from moving (not realistic physics)
      bob.location = p5.Vector.add(anchor, dir);
      bob.velocity.mult(0);
      //too long?
    } else if (d > maxlen) {
      dir.normalize();
      dir.mult(maxlen);
      //reset position and stop from moving (not realistic physics)
      bob.location = p5.Vector.add(this.anchor, dir);
      bob.velocity.mult(0);
    }
  }
    //draw anchor
  display() { 
    stroke(50);
    fill(63,63,147);
    strokeWeight(2);
    rectMode(CENTER);
    rect(this.anchor.x, this.anchor.y, 10, 10);
  }
    //draw spring connection between bob location and anchor 
  displayLine(b) {
    strokeWeight(2);
    stroke(50);
    line(b.location.x, b.location.y, this.anchor.x, this.anchor.y);
  }
}
                            

The Nature Of Code

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