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