Arrow keys move the camera, + and - zoom in/out, 'r' for new system, 't' toggles trails, '1', '2', and '3' keys toggle color mode, 'l' toggles lines for force(blue) and velocity(red), 'b' toggles shape borders, 'p' to pause. See code for details.
A fork of Gravity Simulation by Ouch...
xxxxxxxxxx
p5.disableFriendlyErrors = true; //tries to help speed up framerates
let system;
function setup() {
paused = false;
frameRate(120);
createCanvas(windowWidth, windowHeight);
clusterValue = 1.00; //affects how clustered bodies are initially
cam = createVector(0, 0); //allows for a moveable camera
zoom = 0.5; //Scaling factor for camera
spawnWindow = 1200; //This dictates the size of the spawning box for the bodies
background(0);
noSmooth();
ellipseMode(CENTER); //bodies are centered on their positions
angleMode(DEGREES);
initialBodies = 200;
maxMass = 1;
minMass = 1;
densityFactor = 3;
densityThreshold = (maxMass + minMass) * initialBodies / 4;
G = 0.1; //Gravitational Constant for simulation
D = 1; //Drag (Because every good physics simulation needs this option -- 0-1, lower numbers mean more drag)
E1 = 1.0;
E2 = 1.0;
/*Energy lost as heat during collisions (0-1, lower numbers mean more energy loss)
You can change the relative energy loss that the two colliding masses experience.
E1 is the amount of energy lost from the object that will be destroyed (i.e. the less massive one)
E2 is the amount of energy lost from the object that will survive the collision (i.e. the more massive one)
Set both to 1 to ignore energy loss*/
colorSet = 'normal'; //'velocity' for color based on linear velocity, 'mass' for color based on mass
physLines = false; //toggles lines for velocity(red) and force(blue)
colors = ['red', 'orange', 'yellow', 'lime', 'cyan', 'dodgerblue', 'mediumorchid'];
borders = true; //toggles shape borders
iV = 0.250; //max initial velocity magnitude
trailsOn = false; //toggles trails
trailLength = 10; //this affects the length of trails
system = new System();
let i = 0;
while (i < initialBodies) {
let p = createVector(0, spawnWindow / 2);
p.rotate(random(0, 360));
p.mult(random(0, 1), random(0,1));
if (noise(5 * (p.x + spawnWindow / 2) / spawnWindow, 5 * (p.y + spawnWindow / 2) / spawnWindow) * clusterValue < 0.5 & i < initialBodies) {
let b = new Body(p.x, p.y);
system.addBody(b);
i++;
}
}
}
function keyPressed() {
if (key === 'r') {
noiseSeed(random(-100000, 100000));
system.bodies.splice(0, system.bodies.length);
//reset camera
cam.x = 0;
cam.y = 0;
background(0);
let i = 0;
while (i < initialBodies) {
let p = createVector(0, spawnWindow / 2);
p.rotate(random(0, 360));
p.mult(random(0, 1), random(0,1));
if (noise(5 * (p.x + spawnWindow / 2) / spawnWindow, 5 * (p.y + spawnWindow / 2) / spawnWindow) * clusterValue < 0.5 & i < initialBodies) {
let b = new Body(p.x, p.y);
system.addBody(b);
i++;
}
}
redraw();
}
if (key === 't') {
trailsOn = !trailsOn;
}
if (key === 'b') {
borders = !borders;
}
if (key === 'p') {
paused = !paused;
}
if (key === 'l') {
physLines = !physLines;
}
if (key === '1') {
colorSet = 'normal';
}
if (key === '2') {
colorSet = 'velocity';
}
if (key === '3') {
colorSet = 'mass'
}
}
function draw() {
frameRate(120);
background(0);
//camera movement
if (keyIsDown(LEFT_ARROW)) {
cam.x += 5 / zoom;
}
if (keyIsDown(RIGHT_ARROW)) {
cam.x += -5 / zoom;
}
if (keyIsDown(UP_ARROW)) {
cam.y += 5 / zoom;
}
if (keyIsDown(DOWN_ARROW)) {
cam.y += -5 / zoom;
}
//camera zooming
if (keyIsDown(189) & zoom > 0.25) {
zoom += -0.06;
}
if (keyIsDown(187) & zoom < 8) {
zoom += 0.06;
}
//centers camera
push();
translate(width / 2, height / 2);
scale(zoom);
translate(-width / 2, -height / 2);
translate(cam.x, cam.y);
system.run();
pop();
//displays frame rate and # of simulated bodies
fill(255);
textSize(12);
text(system.bodies.length, 10, windowHeight - 10);
text(frameRate().toFixed(1), 10, windowHeight - 22);
}
function System() { //this defines the whole set of moving objects
this.bodies = [];
}
System.prototype.addBody = function(b) {
this.bodies.push(b);
}
System.prototype.run = function() {
frameRate(120);
let collided = []; //stores bodies which have been destroyed in a collision
for (let i = this.bodies.length - 1; i >= 0; i--) {
this.bodies[i].run(this.bodies);
if (this.bodies[i].collided === true) {
collided.push(i);
}
}
for (let i = this.bodies.length - 1; i >= 0; i--) {
if (paused !== true) {
this.bodies[i].update();
}
}
//removes destroyed bodies
for (let i = 0; i < collided.length; i++) {
this.bodies.splice(collided[i], 1);
}
}
function Body(x, y) { //these are the objects that move
this.position = createVector(width / 2 + x, height / 2 + y);
this.velocity = createVector((noise(x) - 0.5) * 2 * iV, (noise(y) - 0.5) * 2 * iV);
this.acceleration = createVector(0, 0);
this.color = color(random(colors));
this.mass = random(minMass, maxMass);
this.density = pow(densityFactor, ((this.mass / densityThreshold) - (1 / densityThreshold)));
this.size = this.mass / (this.density * 2);
this.G = createVector(0, 0);
this.collided = false;
this.pPos = [this.position];
}
Body.prototype.accel = function(force) {
this.acceleration.add(force);
}
Body.prototype.grav = function(bodies) {
frameRate(120);
let grav = createVector(0, 0);
let gPoint = createVector(0, 0);
for (let i = 0; i < bodies.length; i++) {
let b = bodies[i];
let rPos = p5.Vector.sub(b.position, this.position);
let dist = rPos.mag();
let minD = this.size / 2 + b.size / 2;
if (dist < minD & dist > 0 & this.collided !== true) {
this.collide(b);
}
if (dist >= minD & this.collided !== true) {
let dir = p5.Vector.normalize(rPos);
gPoint.add(dir);
gPoint.mult(G);
gPoint.mult(b.mass);
gPoint.mult(this.mass);
//limit distance to prevent absurd values for force
if (dist >= 0.55) {
gPoint.div(pow(dist, 2));
} else {
gPoint.div(pow(0.55, 2));
}
grav.add(gPoint);
gPoint.mult(0);
}
}
this.G.set(grav.x, grav.y);
grav.div(this.mass);
return grav;
}
Body.prototype.collide = function(b) {
let tMass = this.mass + b.mass;
let i1 = createVector(this.velocity.x, this.velocity.y);
i1.mult(this.mass);
i1.mult(E1);
let i2 = createVector(b.velocity.x, b.velocity.y);
i2.mult(b.mass);
i2.mult(E2);
let vf = p5.Vector.add(i1, i2);
vf.div(tMass);
if (this.mass < b.mass | this.mass === b.mass) {
b.velocity.set(vf);
b.mass = this.mass + b.mass;
b.density = (pow(densityFactor, ((b.mass / densityThreshold) - (1 / densityThreshold))));
b.size = b.mass / (b.density * 2);
this.collided = true;
}
}
Body.prototype.attract = function(bodies) {
let g = this.grav(bodies);
this.accel(g);
}
Body.prototype.update = function() {
frameRate(120);
this.velocity.add(this.acceleration);
this.velocity.mult(D);
this.position.add(this.velocity);
if (trailsOn === true) {
if (frameCount % 3 === 0) {
this.pPos.push(new p5.Vector(this.position.x, this.position.y));
}
if (this.pPos.length > (trailLength + 1)) {
this.pPos.splice(0, 1);
}
} else {
this.pPos.splice(0, this.pPos.length);
}
this.acceleration.mult(0);
}
Body.prototype.display = function() {
let c;
if (colorSet === 'velocity') {
push();
translate(this.position.x, this.position.y);
c = color((this.velocity.mag() / (7 * G)) * 255, 255 - ((this.velocity.mag() / (7 * G)) * 255), 0);
if (trailsOn === true) {
for (let i = 1; i < this.pPos.length; i++) {
let pPos = this.pPos[i];
let prePos = this.pPos[i - 1];
strokeWeight(this.size / 4);
stroke(c);
line(pPos.x - this.position.x, pPos.y - this.position.y, prePos.x - this.position.x, prePos.y - this.position.y);
}
}
if (borders === true) {
strokeWeight(this.size / 10);
stroke(255);
} else {
noStroke();
}
fill(c);
ellipse(0, 0, this.size);
noStroke();
c.setAlpha(lerp(0, 175, 1 - (densityThreshold / (this.mass + densityThreshold / 2))));
fill(c);
ellipse(0, 0, this.size * 1.5);
c.setAlpha(lerp(0, 100, 1 - (densityThreshold / (this.mass + densityThreshold / 2))));
fill(c);
ellipse(0, 0, this.size * 2);
c.setAlpha(lerp(0, 50, 1 - (densityThreshold / (this.mass + densityThreshold / 2))));
fill(c);
ellipse(0, 0, this.size * 2.5);
c.setAlpha(255);
if (physLines === true) {
strokeWeight(1);
stroke(255, 0, 0);
line(0, 0, this.velocity.x * 10, this.velocity.y * 10);
stroke(0, 0, 255);
line(0, 0, this.G.x * 5 / G, this.G.y * 5 / G);
this.G.mult(0);
}
pop();
} else if (colorSet === 'mass') {
push();
translate(this.position.x, this.position.y);
c = color(0, ((2 * this.mass / densityThreshold) * 255), 255 - ((2 * this.mass / densityThreshold) * 255));
if (trailsOn === true) {
for (let i = 1; i < this.pPos.length; i++) {
let pPos = this.pPos[i];
let prePos = this.pPos[i - 1];
strokeWeight(this.size / 4);
stroke(c);
line(pPos.x - this.position.x, pPos.y - this.position.y, prePos.x - this.position.x, prePos.y - this.position.y);
}
}
if (borders === true) {
strokeWeight(this.size / 10);
stroke(255);
} else {
noStroke();
}
fill(c);
ellipse(0, 0, this.size);
noStroke();
c.setAlpha(lerp(0, 175, 1 - (densityThreshold / (this.mass + densityThreshold / 2))));
fill(c);
ellipse(0, 0, this.size * 1.5);
c.setAlpha(lerp(0, 100, 1 - (densityThreshold / (this.mass + densityThreshold / 2))));
fill(c);
ellipse(0, 0, this.size * 2);
c.setAlpha(lerp(0, 50, 1 - (densityThreshold / (this.mass + densityThreshold / 2))));
fill(c);
ellipse(0, 0, this.size * 2.5);
c.setAlpha(255);
if (physLines === true) {
strokeWeight(1);
stroke(255, 0, 0);
line(0, 0, this.velocity.x * 10, this.velocity.y * 10);
stroke(0, 0, 255);
line(0, 0, this.G.x * 5 / G, this.G.y * 5 / G);
this.G.mult(0);
}
pop();
} else if (colorSet !== 'mass' & colorSet !== 'velocity') {
push();
translate(this.position.x, this.position.y);
if (trailsOn === true) {
for (let i = 1; i < this.pPos.length; i++) {
let pPos = this.pPos[i];
let prePos = this.pPos[i - 1];
strokeWeight(this.size / 4);
stroke(this.color);
line(pPos.x - this.position.x, pPos.y - this.position.y, prePos.x - this.position.x, prePos.y - this.position.y);
}
}
if (borders === true) {
strokeWeight(this.size / 10);
stroke(255);
} else {
noStroke();
}
fill(this.color);
ellipse(0, 0, this.size);
noStroke();
this.color.setAlpha(lerp(0, 175, 1 - (densityThreshold / (this.mass + densityThreshold / 2))));
fill(this.color);
ellipse(0, 0, this.size * 1.5);
this.color.setAlpha(lerp(0, 100, 1 - (densityThreshold / (this.mass + densityThreshold / 2))));
fill(this.color);
ellipse(0, 0, this.size * 2);
this.color.setAlpha(lerp(0, 50, 1 - (densityThreshold / (this.mass + densityThreshold / 2))));
fill(this.color);
ellipse(0, 0, this.size * 2.5);
this.color.setAlpha(255);
if (physLines === true) {
strokeWeight(1);
stroke(255, 0, 0);
line(0, 0, this.velocity.x * 10, this.velocity.y * 10);
stroke(0, 0, 255);
line(0, 0, this.G.x * 5 / G, this.G.y * 5 / G);
this.G.mult(0);
}
pop();
}
}
Body.prototype.run = function(bodies) {
frameRate(120);
if (paused !== true) {
this.attract(bodies);
}
this.display();
}