Creating a JavaScript gravity simulator using p5.js

Recently I came across p5.js, a JavaScript library that specialises in graphics. It’s designed to be easy to use, so I thought it would be a good way to develop my JavaScript skills.

You can see the online in-browser version here: https://brychanthomas.github.io/gravity/. You can click on the canvas to add an object in a specific location, and press ‘p’ to pause.

For my first project I thought some sort of simulator would be fun to make. I ended up settling with a gravity simulator because the equations I’d need aren’t too complicated, so it should be a nice introduction to the library.

The first equation I needed is Newton’s law of Universal Gravitation:

{\displaystyle F=G{\frac {m_{1}m_{2}}{r^{2}}},}
F is force, m is mass, r is distance and G is gravitational constant

As you can see the force exerted on two objects by due to gravity is their two masses multiplied, divided by the distance between the their centres, all multiplied by the gravitational constant.

In order to find the distance between them I used trigonometry – the difference in the x coordinates can be though of as one side of a right-angled triangle and the difference in y as another, with the hypotenuse then being the distance between the two objects.

Once I calculate the force I then resolve it into a component along the x axis and a component along the y axis, again by using trigonometry. This makes it easier to move the object as we can have separate velocities in the x axis and y axis.

This law has since been superseded by Einstein’s Theory of General Relativity. However, it is still a good approximation. General Relativity is usually only needed when there is a need for extreme accuracy or when dealing with very strong gravitational fields, neither of which the program should have to do.

Now that we have the force acting on the two objects we need to be able to convert this to an acceleration. This is done using Newton’s second law of motion:

{\vec {F}}=m{\vec {a}}
F is force, m is mass and a is acceleration

We can rearrange the above equation to find the acceleration of the two objects; the acceleration is the force divided by its mass. The force acting on both objects is of the same magnitude but in opposite directions. This means that each object’s acceleration relative to the other depends on its mass.

The third equation we need calculates momentum:

\mathbf{p} = m \mathbf{v}.
p is momentum, m is mass and v is velocity

In the program, when two objects collide they join together to become one. Their masses are added together to get the mass of the new object. When such a collision happens the momentum before is equal to the momentum after. Therefore, in order to find the velocity of the new object we must use momentum. Firstly we calculate the momentum of the two objects at the moment of the collision. We then add them together. Finally, we divide this momentum sum by the combined mass of the new object to find its velocity.

At this point I should note that the program does not use standard units – for velocity it uses pixels/frame. The gravitational constant, which in real life is 6.67408 × 10-11 m3 kg-1 s-2 (very small) is 50 in the program in order to compensate for the small masses of the ‘planets’. The mass of the Earth is 5.9722×1024 kg whereas the mass of the planets in the program is by default 40 kg.

Here is the code for the program:

var gravitational_constant = 50;
var objects = [];
var numberOfObjects = 0; //used to give each object an ID so it can be recognised
var paused = false;

function setup() { //runs once when program starts
	objects = [new object(40, createVector(800,800), createVector(-2.5,0)),
	new object(40, createVector(800,1000), createVector(2.5,0))];
	
	createCanvas(6000, 3000);
}

function draw() { //runs every frame
	if (paused === true) {return 0}
	background(25);
	for (var obj=0; obj<objects.length; obj++) {
		if (objects[obj] != undefined) {
			objects[obj].update();
			if (objects[obj] != undefined) {
				objects[obj].show();
			}
		}
	}
}

function mouseClicked() { //create a new object when canvas is clicked
	var x = mouseX;
	var y = mouseY;
	paused = true;
	var mass = parseFloat(prompt("Enter mass: ", "40"));
	var xv = parseFloat(prompt("Enter x velocity: ", "0"));
	var yv = parseFloat(prompt("Enter y velocity: ", "0"));
	objects.push(new object(mass, createVector(x,y), createVector(xv,yv)));
	paused = false;
}

function keyPressed() { //pause when p pressed
	if (key==='p') {
		paused = !paused;
	}
}

class object { //a class used to create an object (planet, particle or whatever you want to call it)
	constructor(mass,position,velocity) {
		this.mass = mass;
		this.x = position.x;
		this.y = position.y;
		this.velocity = velocity;
		this.number = count;
		count++;
		this.trailPositions=[];
		
		let red = random(255); //find a colour that stands out on black background
		let green = random(255);
		let blue = random(255);
		//formula for determining brightness of an RGB colour
		let brightness  =  Math.sqrt(0.241 * Math.pow(red,2) + 0.691 * Math.pow(green,2) + 0.068 * Math.pow(blue,2));
		while (brightness < 100) {
			red = random(255);
			green = random(255);
			blue = random(255);
			brightness  =  Math.sqrt(0.241 * Math.pow(red,2) + 0.691 * Math.pow(green,2) + 0.068 * Math.pow(blue,2));
		}
		this.colour = color(red, green, blue);
	}
	calculate_force_with(other_object) { //use gravity equation to find x and y force components with other object
		var distance = Math.sqrt(Math.pow(this.x-other_object.x,2) + Math.pow(this.y-other_object.y,2));
		if (distance < 30) {
			this.collide(other_object);
			return createVector(0,0);
		}
		//Law of universal gravitation
		var force = gravitational_constant * ((this.mass * other_object.mass) / Math.pow(distance,2));
		var direction = Math.atan(Math.abs(this.y-other_object.y) / Math.abs(this.x-other_object.x));
		var fx = force * Math.abs(Math.cos(direction));
		var fy = force * Math.abs(Math.sin(direction));
		if (other_object.x < this.x) {fx = fx*-1}
		if (other_object.y < this.y) {fy = fy*-1}
		this.fx = fx;
		this.fy = fy;
		return createVector(fx,fy);
	}
	collide(other_object) { //fuse two objects to create a single object
		//momentum equation
		var myMomentum = createVector(this.mass * this.velocity.x, this.mass * this.velocity.y);
		var otherMomentum = createVector(other_object.mass * other_object.velocity.x, other_object.mass * other_object.velocity.y);
		var momentumSum = createVector (myMomentum.x + otherMomentum.x, myMomentum.y + otherMomentum.y);
		this.mass = this.mass + other_object.mass;
		this.velocity = createVector (momentumSum.x / this.mass, momentumSum.y / this.mass);
		var fx=0;
		var fy=0;
		for (let i=0; i<objects.length; i++) {
			if (objects[i].number == other_object.number) {
				objects.splice(i, 1);
				break
			}
		}
	}
	calculate_acceleration(force) { //calculate acceleration on x and y axis based on force
		//Newton's second law
		var ax=force.x / this.mass;
		var ay=force.y / this.mass;
		return createVector(ax, ay);
	}
	accelerate(acceleration) { //change velocity based on acceleration
		this.velocity.x = this.velocity.x + acceleration.x;
		this.velocity.y = this.velocity.y + acceleration.y;
	}
	move () { //move object based on velocities
		this.x = this.x + this.velocity.x;
		this.y = this.y + this.velocity.y;
	}
	update () { //update object's position and its trail
		for (let obj=0; obj<objects.length; obj++) {
			if (this.number != objects[obj].number) {
				var force = this.calculate_force_with(objects[obj]);
				var acceleration = this.calculate_acceleration(force);
				this.accelerate(acceleration);
			}
		}
		this.move();
		this.trailPositions.push(createVector(this.x,this.y));
		if (this.trailPositions.length > 500) {
			this.trailPositions.splice(0,1);
		}
	}
	show() { //draw the object and its trail on the canvas
		noFill();
		beginShape();
		stroke(this.colour);
		for (let i=0; i < this.trailPositions.length; i++) {
			var pos = this.trailPositions[i];
			vertex(pos.x, pos.y);
		}
		endShape();
		fill(this.colour);
		circle(this.x, this.y, this.mass/2);
	}
}

At first I was having trouble finding the direction of the gravitational force in order to resolve it. It turned out that JavaScript’s Math library’s trigonometry functions accepts values in radians, while I giving them values in degrees. Once I fixed this it worked nicely.

You can see a version of the program without any pre-created objects here: https://brychanthomas.github.io/gravity/empty/

Leave a comment

Design a site like this with WordPress.com
Get started