Orbital Simulation
In p5.js
Made by a human
This project presents a simple N-body simulation built using p5.js, where multiple objects move and interact through a gravitational-like force in a two-dimensional space.
A main body simulating the sun is placed in the center of the scene and acts as a central moving object.
setup(){
MainBody = new ObjectWithG(width/2, height/2, 8, 0, 0, color(0, 0, 0));
}
Around it (still in the setup function), many smaller bodies are randomly distributed, each with its own position and initial velocity.
for (var i = 0; i < NumberOFBodies; i++){
bodies.push(new ObjectWithG(random(50, width-50), random(50, height-50), 4, random(-2, 2), random(-2, 2)));
}
During the simulation, all bodies are updated continuously.
for(var i = 0; i < NumberOFBodies; i++){
bodies[i].getForce(MainBody, G);
bodies[i].update();
bodies[i].show();
}
The smaller bodies are attracted toward the main body through a gravitational force calculated in a method of the class ObjectWithG.
getForce(other, G){ //other is another object
let dir = p5.Vector.sub(other.Vect, this.Vect); //direction of the Vector
let distance = dir.mag();
//magnitude = size of the Vector ----->.mag() > -->.mag()
distance = constrain(distance, 1, 15);
//so that if an object comes too close to the main Object its speed doesn't get too big
let forceMag = G * ((this.mass * other.mass) / (distance * distance)); //Newton's formula
dir.normalize();
dir.mult(forceMag);
let acc = p5.Vector.div(dir, this.mass);
this.acc.add(acc);
}
That force depends on their mass and distance according to Newton's universal law of gravitation:
To improve numerical stability and smoothness, the simulation is calculated in multiple substeps per frame:
let substeps = 30
for (let iter = 0; iter < substeps; iter++){
//repeats this code 30 times per frame
}
Class Explanation
Constructor method
The radius of the body is defined during initialization.
this.Vect is the vector that represents the position of the body, and its x and y coordinates are also set during initialization.
The mass is calculated by dividing the body's radius by ten.
this.acc is a vector that is added to this.vel, which is then added to this.Vect, therefore changing the body's position.
constructor(x, y, r, vx=0, vy=0, c = undefined){
if (c !== undefined){
this.c = color(0, 0, 0)
}
else{
this.c = color(100, 255, 100)
}
this.radius = r;
this.Vect = createVector(x, y);
this.mass = this.radius/10;
this.acc = createVector(0, 0);
this.vel = createVector(vx, vy);
}
Get Force method
To calculate the force that a body is experiencing we use Vectors:
let dir = p5.Vector.sub(other.Vect, this.Vect);
let acc = p5.Vector.div(dir, this.mass);
When we normalize the direction vector we transform the vector's length (magnitude) to 1 so we turn it into the force Vector:
dir.normalize(); //mag = 1
dir.mult(forceMag); //mag = 1*forceMag
The last step to calculate the force is to turn the force into acceleration:
let acc = p5.Vector.div(dir, this.mass); // a = F/Mass Newton's second law
this.acc.add(acc);
Full method:
getForce(other, G){
let dir = p5.Vector.sub(other.Vect, this.Vect);
let distance = dir.mag();
distance = constrain(distance, 15, 30);
let forceMag = G * ((this.mass * other.mass) / (distance * distance)); //Newton's formula
dir.normalize();
dir.mult(forceMag);
let acc = p5.Vector.div(dir, this.mass); //a = F/Mass
this.acc.add(acc);
}
Update and Show methods
The update method applies Forces in this order: Force → acceleration → velocity → Position
update(){
this.vel.add(this.acc)
this.Vect.add(this.vel)
this.acc.mult(0)
}
The show method simply draws a circle with a blue outline
show(){
stroke(0, 0, 200)
strokeWeight(1)
fill(this.c)
circle(this.Vect.x, this.Vect.y, this.radius*2)
}
Detect Collisions
The final method of this class, detectCollision, checks if the distance between two bodies is smaller than the sum of their radius by creating a vector between the two, checking the vector's magnitude and returning true if the distance is smaller than the sum of their radius.
detectCollision(other){
if(p5.Vector.sub(other.Vect, this.Vect).mag() < this.radius + other.radius){
return true
}
return false
}
Full Class
class ObjectWithG{
constructor(x, y, r, vx=0, vy=0, c = undefined){
if (c !== undefined){
this.c = color(0, 0, 0)
}
else{
this.c = color(100, 255, 100)
}
this.radius = r;
this.Vect = createVector(x, y);
this.mass = this.radius/10;
this.acc = createVector(0, 0);
this.vel = createVector(vx, vy);
}
getForce(other, G){
let dir = p5.Vector.sub(other.Vect, this.Vect);
let distance = dir.mag();
distance = constrain(distance, 15, 30);
let forceMag = G * ((this.mass * other.mass) / (distance * distance));
dir.normalize();
dir.mult(forceMag);
let acc = p5.Vector.div(dir, this.mass);
this.acc.add(acc);
}
update(){
this.vel.add(this.acc)
this.Vect.add(this.vel)
this.acc.mult(0)
}
show(){
stroke(0, 0, 200)
strokeWeight(1)
fill(this.c)
circle(this.Vect.x, this.Vect.y, this.radius*2)
}
detectCollision(other){
if(p5.Vector.sub(other.Vect, this.Vect).mag() < this.radius + other.radius){
return true
}
return false
}
}
Full Code
function setup() {
createCanvas(min(windowWidth, windowHeight)-3, min(windowWidth, windowHeight)-3);
MainBody = new ObjectWithG(width/2, height/2, 8, 0, 0, color(0, 0, 0));
substeps = 3
G = 2/substeps
NumberOFBodies = 5
print(NumberOFBodies)
bodies = []
for (var i = 0; i < NumberOFBodies; i++){
bodies.push(new ObjectWithG(random(50, width), random(50, height-50), 6, random(-1, 1), random(-1, 1)))
}
}
function draw() {
for (let iter = 0; iter < substeps; iter++){
background(255, 10)
MainBody.show(color(255, 0, 0));
for(var i = 0; i < NumberOFBodies; i++){
if (bodies[i] !== undefined){
bodies[i].getForce(MainBody, G)
bodies[i].update()
bodies[i].show()
if(bodies[i].detectCollision(MainBody)){
background(0, 255, 255)
bodies[i] = undefined
}
}
}
}
}