xxxxxxxxxx
331
//
// Energy
// by Philip, September 25, 2020
//
// Avatar energy simulation, with collisions and locomotion.
//
// Click on an avatar to select it. Drag mouse to hurl it around, or use arrow keys / WASD to
// 'drive' it. The bar at top represents remaining energy reserve for the avatar.
//
// Kinetic energy of all avatars is indicated in lower left corner.
//
// Edit 'num' to change how many avatars there are in simulation. Just a few will let you
// play with driving, trapping avatars, etc. Set to 100 to see crowd/gas.
// More damping means slow down faster, less means keep moving.
//
//
let num = 10;
let maxEnergy = 100;
let energyRechargeRate = 0.1;
let dampingRate = 0.000001;
let massPerPixel = 0.5;
let gravityStrength = 50.0;
let keyStrength = 0.05;
let followStrength = 0.10;
let avatars = [];
let key_up = 0;
let key_down = 0;
let key_left = 0;
let key_right = 0;
let key_b = 0;
let mouseDown = false;
let grabOrigin = 0;
let gravity = false;
function setup() {
createCanvas(800, 600);
noStroke();
grabOrigin = createVector(0,0);
for (let i = 0; i < num; i++) {
let size = random(8, 50);
let mass = size*size * massPerPixel / 50000;
let thisColor = color(random(255), random(255), random(255));
if (random(0,100) < 10) {
size = 60;
mass = 1;
thisColor = color(0,0,0);
mass = mass * 10;
}
let isFollowing = (random(0, 10) < 5);
let followingID = round(random(0, num-1));
if (followingID == i) followingID = 0;
avatars[i] = new Avatar(random(width),
random(height),
size,
1.0 - dampingRate,
mass,
thisColor,
avatars,
i, isFollowing, followingID);
}
}
function draw() {
background(128);
let kE = 0;
for (let i = 0; i < num; i++) {
avatars[i].update();
avatars[i].display();
kE += avatars[i].kE();
}
noStroke();
strokeWeight(1);
fill(0);
text("kE: " + round(kE), 5, height - 10);
//text("g - toggle gravity", width - 200, height - 10);
}
function keyPressed() {
if (keyCode === LEFT_ARROW || keyCode === 65) {
key_left = 1;
}
if (keyCode === RIGHT_ARROW || keyCode === 68) {
key_right = 1;
}
if (keyCode === UP_ARROW || keyCode === 87) {
key_up = 1;
}
if (keyCode === DOWN_ARROW || keyCode === 83) {
key_down = 1;
}
if (keyCode === 66) {
key_b = 1;
}
if (keyCode === 71) {
gravity = !gravity;
}
}
function keyReleased() {
if (keyCode === LEFT_ARROW || keyCode === 65) {
key_left = 0;
}
if (keyCode === RIGHT_ARROW || keyCode === 68) {
key_right = 0;
}
if (keyCode === UP_ARROW || keyCode === 87) {
key_up = 0;
}
if (keyCode === DOWN_ARROW || keyCode === 83) {
key_down = 0;
}
if (keyCode === 66) {
key_b = 0;
}
}
function mousePressed() {
mouseDown = true;
grabOrigin.set(mouseX, mouseY);
for (let i = 0; i < num; i++) {
let disX = avatars[i].position.x - mouseX;
let disY = avatars[i].position.y - mouseY;
let dis = createVector(disX, disY);
if (dis.mag() < avatars[i].size / 2 ) {
avatars[i].isSelected = true;
} else {
avatars[i].isSelected = false;
}
}
}
function mouseReleased() {
mouseDown = false;
}
// Avatar class
function Avatar (_x, _y, _s, _d, _m, _color, _others,
_id, _isFollowing, _followingID) {
// Screen values
this.position = createVector(_x, _y);
this.velocity = createVector(0, 0);
this.acceleration = createVector(0,0);
this.force = createVector(0,0);
this.damping = _d;
this.size = _s;
this.mass = _m;
this.color = _color;
this.grabOrigin = createVector(0,0);
this.isSelected = false;
this.isBreaking = false;
this.isFollowing = _isFollowing;
this.followingID = _followingID;
this.dt = 1.0 / 60.0;
this.energy = random(maxEnergy);
this.kineticEnergy = 0;
this.over = false;
this.move = false;
this.friends = _others;
this.id = _id;
this.update = function() {
let mousePosition = createVector(mouseX, mouseY);
// Add forces from controller (mouse and keyboard)
this.force.set(0,0);
if (mouseDown && this.isSelected) {
this.force.set(mousePosition.sub(grabOrigin));
this.force.mult(0.1);
} else {
if (this.isSelected) {
let keyEnergy = maxEnergy * keyStrength;
this.force.add(createVector(0,-1).mult(key_up));
this.force.add(createVector(0,1).mult(key_down));
this.force.add(createVector(-1,0).mult(key_left));
this.force.add(createVector(1,0).mult(key_right));
this.force.mult(keyEnergy);
}
//if (this.isSelected && key_b == 1) {
// this.force.add(this.velocity
//
//}
}
// Rate of energy use proportional to remaining capacity
this.force.mult(this.energy / maxEnergy);
// Add velocity due to forces applied by controller, and debit energy
this.acceleration.set(this.force.mult(1.0 / this.mass));
let dV = p5.Vector.mult(this.acceleration, this.dt);
this.velocity.add(dV);
this.energy -= 0.5 * this.mass * dV.magSq();
// Check for collisions and add restoring forces
let collisionForces = createVector(0,0,0);
for (let i = 0; i < this.friends.length; i++) {
if (i != this.id) {
let d = p5.Vector.sub(this.position, this.friends[i].position);
let penetration = ((this.size + this.friends[i].size) / 2.0) - d.mag();
if (penetration > 0) {
d.normalize();
// Elastic component - restitution (note: does not conserve energy)
collisionForces.add(d.mult(10 * penetration));
}
}
}
this.acceleration.set(collisionForces.mult(1.0 / this.mass));
// If gravity, add gravity!
if (gravity) {
let gVec = p5.Vector.sub(createVector(width/2, height/2), this.position).normalize();
gVec.mult(gravityStrength);
this.acceleration.add(gVec);
}
// If Following someone, try to match their velocity
if (this.isFollowing) {
this.velocity.add(p5.Vector.sub(this.friends[this.followingID].velocity, this.velocity).mult(followStrength));
}
this.velocity.add(p5.Vector.mult(this.acceleration, this.dt));
//
// Damping removed energy from the world,
// in an amount proportional to the velocity squared.
//
let dFactor = 1.0 - this.velocity.magSq() * dampingRate;
if (dFactor < 0) dFactor = 0.0;
this.velocity = this.velocity.mult(dFactor);
this.position.add(p5.Vector.mult(this.velocity, this.dt));
// Update kinetic energy
this.kineticEnergy = 0.5 * this.mass * this.velocity.magSq();
// Wrap at edges
if (this.position.x > width) this.position.x -= width;
if (this.position.x < 0) this.position.x += width;
if (this.position.y > height) this.position.y -= height;
if (this.position.y < 0) this.position.y += height;
// Refuel energy
if (this.energy < 0) this.energy = 0;
this.energy += energyRechargeRate;
if (this.energy > maxEnergy) this.energy = maxEnergy;
if (this.overEvent()) {
this.over = true;
} else {
this.over = false;
}
}
// Test to see if mouse is over this avatar
this.overEvent = function() {
let disX = this.position.x - mouseX;
let disY = this.position.y - mouseY;
let dis = createVector(disX, disY);
if (dis.mag() < this.size / 2 ) {
return true;
} else {
return false;
}
}
this.kE = function() {
return this.kineticEnergy;
}
this.display = function() {
if (this.over || this.isSelected) {
stroke(255);
strokeWeight(3);
} else {
stroke(0);
strokeWeight(1);
}
fill(this.color);
ellipse(this.position.x, this.position.y, this.size, this.size);
let energyLevel = this.energy / maxEnergy;
if (energyLevel < 0.99) {
strokeWeight(1);
fill(128, 128, 128, 128);
ellipse(this.position.x, this.position.y,
this.size * energyLevel, this.size * energyLevel)
}
// if being grabbed, draw controller
if (mouseDown && this.isSelected) {
stroke(255);
strokeWeight(4);
let controller = p5.Vector.sub(createVector(mouseX, mouseY), grabOrigin);
controller.add(this.position);
line(this.position.x, this.position.y, controller.x, controller.y);
}
// If following, draw line
if (this.isFollowing) {
stroke(255);
strokeWeight(1);
line(this.position.x, this.position.y,
this.friends[this.followingID].position.x,
this.friends[this.followingID].position.y);
fill(255);
ellipse(this.position.x, this.position.y, 2, 2);
}
// Display energy level
if (this.isSelected) {
stroke(this.color);
strokeWeight(5);
line(0, 3, this.energy / maxEnergy * width, 3);
}
}
};