xxxxxxxxxx
415
//
// Particles 2
// by Philip, November 15, 2020
//
// Particles moving around on a grid which exchanges energy with them.
// Energy is conserved between grid and particles.
//
// 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 = 300;
let maxEnergy = 100;
let energyRechargeRate = 0.1;
let massPerPixel = 0.5;
let gravityStrength = 50.0;
let keyStrength = 0.05;
let gridSize = 50.0;
let startVelocity = 200.0;
let startGridVelocity = 200.0;
let dampingRate = 0.000001;
let gridDecay = 1.0;
let gridEnergy = 0.0; // Compute total energy stored in grid
let gridToParticle = 0.1; // fraction of grid velocity added to particle
let particleToGrid = 0.1; // fraction of particle velocity added to grid
let avatars = [];
let vGrid = [];
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 repeat = false;
let gravity = false;
let collisions = false;
let showGrid = false;
function setup() {
createCanvas(1024, 2048);
//fullscreen(true);
noStroke();
grabOrigin = createVector(0,0);
//. Create vector valued grid
print("Setup");
for (let x = 0; x * gridSize < width; x++) {
vGrid[x] = [];
for (let y = 0; y * gridSize < height; y++) {
vGrid[x][y] = new p5.Vector(random(-1.0, 1.0),random(-1.0, 1.0));
vGrid[x][y].mult(startGridVelocity);
}
}
for (let i = 0; i < num; i++) {
let size = random(5, 20);
let mass = size*size * massPerPixel / 50000;
let thisColor = color(random(255), random(255), random(255));
if (random(0,100) < 0) {
size = 60;
mass = 1;
thisColor = color(0,0,0);
mass = mass * 10;
}
avatars[i] = new Avatar(random(width),
random(height),
random(-1.0, 1.0) * startVelocity,
random(-1.0, 1.0) * startVelocity,
size,
1.0 - dampingRate,
mass,
thisColor,
avatars,
i);
}
}
function updateGrid() {
gridEnergy = 0.0;
for (let x = 0; x * gridSize < width; x++) {
for (let y = 0; y * gridSize < height; y++) {
gridEnergy += vGrid[x][y].mag();
vGrid[x][y].mult(gridDecay);
}
}
}
function drawGrid() {
strokeWeight(1);
stroke(160);
let tail = createVector(0,0);
let head = createVector(0,0);
for (let x = 0; x * gridSize < width; x++) {
line(x * gridSize, 0, x * gridSize, height);
}
for (let y = 0; y * gridSize < height; y++) {
line(0, y * gridSize, width, y * gridSize);
}
for (let x = 0; x * gridSize < width; x++) {
for (let y = 0; y * gridSize < height; y++) {
tail.set(x * gridSize + gridSize / 2, y * gridSize + gridSize / 2);
let vDisplay = p5.Vector.mult(vGrid[x][y], 0.01);
head.set(p5.Vector.add(tail, vDisplay));
line(tail.x, tail.y, head.x, head.y);
}
}
}
function draw() {
background(128);
let kE = 0;
for (let i = 0; i < num; i++) {
avatars[i].update();
avatars[i].display();
kE += avatars[i].kE();
}
updateGrid();
if (showGrid) drawGrid();
noStroke();
strokeWeight(1);
fill(0);
text("kE: " + round(kE), 5, height - 10);
text("gE: " + round(gridEnergy), 5, height - 20);
//text("g - toggle gravity", width - 200, height - 10);
}
// Avatar class
function Avatar (_x, _y, _vx, _vy, _s, _d, _m, _color, _others,
_id) {
// Screen values
this.position = createVector(_x, _y);
this.velocity = createVector(_vx, _vy);
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.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);
}
}
// 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();
if (collisions) {
// 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);
}
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.mult(dFactor);
// Refuel energy
if (this.energy < 0) this.energy = 0;
this.energy += energyRechargeRate;
if (this.energy > maxEnergy) this.energy = maxEnergy;
// Communicate with grid
let x = round(this.position.x / gridSize - 0.5);
let y = round(this.position.y / gridSize - 0.5);
//. Add velocity from grid to particle
this.velocity.add(p5.Vector.mult(vGrid[x][y], gridToParticle));
//this.velocity.set(p5.Vector.add(p5.Vector.mult(vGrid[x][y], gridToParticle), p5.Vector.mult(this.velocity, (1.0 - gridToParticle))));
// Mix grid toward particle motion
//vGrid[x][y].add(p5.Vector.mult(this.velocity, particleToGrid));
vGrid[x][y].set(p5.Vector.add(p5.Vector.mult(vGrid[x][y], (1.0 - particleToGrid)), p5.Vector.mult(this.velocity, particleToGrid)));
// update position from velocity
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 (repeat) {
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;
} else {
if (this.position.x >= width) {
this.velocity.x *= -1.0;
this.position.x = width - 1;
}
if (this.position.x < 0) {
this.velocity.x *= -1.0;
this.position.x = 0.0;
}
if (this.position.y >= height) {
this.velocity.y *= -1.0;
this.position.y = height - 1;
}
if (this.position.y < 0) {
this.velocity.y *= -1.0;
this.position.y = 0.0;
}
}
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);
}
// Display energy level
if (this.isSelected) {
stroke(this.color);
strokeWeight(5);
line(0, 3, this.energy / maxEnergy * width, 3);
}
}
};
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;
}