[Implementing Gravity with JavaScript] 2. Coding
Gravity Simulation Applying the Law of Universal Gravitation
![[Implementing Gravity with JavaScript] 2. Coding [Implementing Gravity with JavaScript] 2. Coding](/static/6c72a52042fa8bad71e5e927ca2d86d6/2d839/thumbnail.jpg)
In this post, following the previous post, I’m going to directly implement gravity with JS. The development environment uses JavaScript ES7, babel, Webpack, and Three.js.
Declaring Necessary Constants
First, I’ll declare the constant values to use in the program.
export const initOptions = {
framerate: 60,
G: 250, // Gravitational constant as I please
START_SPEED: 30, // Objects' speed during initialization
OBJECT_COUNT: 40, // Number of objects to render after initialization
TRACE_LENGTH: 100, // Length of objects' movement trajectory
MIN_MASS: 400, // Minimum mass of objects
MAX_MASS: 3000, // Maximum mass of objects
DENSITY: 0.15 // Density at which objects are rendered
};
export const SPHERE_SIDES = 20;
export const MASS_FACTOR = 0.01;Here, I set the gravitational constant G, which originally has a small value of 6.6742e-11, to 250, because the masses of objects appearing in the simulation I’m making are too small.
To apply the actual gravitational constant while still having gravitational influence visible to the eye, the mass would also need to be planet-sized, but that would make calculations too difficult, so I adjusted by reducing object mass and increasing the gravitational constant.
MASS_FACTOR is a constant I declared to compensate because when rendering later, I’m trying to set sphere size proportional to object mass, but since mass values are 400-3000, the sphere volume would become too large.
It’s a feeling similar to a compression ratio. When defining sphere size later, I’ll multiply the object’s mass by MASS_FACTOR to reduce size at a constant ratio.
Since this post is not for explaining ThreeJS, I’ll skip over Scene and Renderer declaration and initialization.
Declaring the Mover Class
Now let’s implement the things that will actually move. I wanted to name it Object, but as you know, the name Object is already taken by a Built-in Object in JS. So after agonizing over another name, I just called it Mover.
import { SPHERE_SIDES, MASS_FACTOR } from 'src/constants';
import {
Vector3, SphereGeometry, Line,
MeshPhongMaterial, PointLight, Mesh, Geometry
} from 'three';
export class Mover {
constructor(mass, velocity, location, id, scene) {
this.uid = `mover-${id}`;
this.location = location;
this.velocity = velocity;
this.acceleration = new Vector3(0.0, 0.0, 0.0);
this.mass = mass;
this.alive = true;
this.geometry = new SphereGeometry(100, SPHERE_SIDES, SPHERE_SIDES);
this.vertices = [];
this.line = new Line();
this.color = this.line.material.color;
this.basicMaterial = new MeshPhongMaterial({
color: this.color,
specular: this.color,
shininess: 10
});
this.mesh = new Mesh(this.geometry, this.basicMaterial);
this.mesh.castShadow = false;
this.mesh.receiveShadow = true;
this.position = this.location;
this.parentScene = scene;
}
}Objects created with the Mover class have random mass, speed, and position values received when constructor executes. And it initializes the acceleration value representing acceleration. Since acceleration is a speed moving in some direction, I declared it as a vector with 3 elements.
And when collision detection occurs between Movers, I plan to merge two Movers into one to make a Mover with larger mass, so I declared an alive member variable to determine whether that Mover is dead or alive.
Rendering Mover Objects
Then I wrote logic in Scene to draw Movers during initialization.
export default {
reset() {
const movers = this.movers;
if(movers) { // Initialize movers list
movers.forEach(v => {
this.scene.remove(v.mesh);
this.scene.remove(v.selectionLight);
this.scene.remove(v.line);
});
}
movers = [];
for (let i = 0; i < parseInt(this.options.MOVER_COUNT); i++) {
const mass = this.getRandomize(this.options.MIN_MASS, this.options.MAX_MASS);
const maxDistance = parseFloat(1000 / this.options.DENSITY);
const maxSpeed = parseFloat(this.options.START_SPEED);
const velocity = new Vector3(
this.getRandomize(-maxSpeed, maxSpeed),
this.getRandomize(-maxSpeed, maxSpeed),
this.getRandomize(-maxSpeed, maxSpeed)
);
const location = new Vector3(
this.getRandomize(-maxDistance, maxDistance),
this.getRandomize(-maxDistance, maxDistance),
this.getRandomize(-maxDistance, maxDistance)
);
// Create Mover with random speed, position, mass
movers.push(new Mover(mass, velocity, location, i, this.scene));
}
// Put Mesh, Line, Light objects created when Mover initialized into Scene
movers.forEach(v => v.addMover());
this.movers = movers;
},
getRandomize(min, max) {
return Math.random() * (max - min) + min;
}
}Now Scene has movers, which is a list containing multiple Movers. Now we just calculate during rendering and we’re done! But…
As I said in the previous post, n-body problems for finding gravity for multiple objects have no solution.
So we need to make logic that traverses movers and calculates gravity with other Movers as two-body problems each time we traverse.
Moving Mover Objects
let movers = this.movers;
movers.forEach((o1, i) => {
if(!o1.alive) {
return;
}
movers.forEach((o2, j) => {
if(o1.alive && o2.alive && i !== j) {
// Distance from o1 -> o2
const distance = o1.location.distanceTo(o2.location);
// o1, o2's radii r1, r2
const r1 = (o1.mass / MASS_FACTOR / MASS_FACTOR / 4 * Math.PI) ** (1/3);
const r2 = (o2.mass / MASS_FACTOR / MASS_FACTOR / 4 * Math.PI) ** (1/3);
if(distance <= r1 + r2) {
// If their distance is less than or equal to the sum of their radii, judge as collision and merge the two objects
o2.eat(o1);
}
else {
// If not collision, just make them move
o2.attract(o1, this.options);
}
}
});
});First, I made collision detection while traversing all Movers like this. The Mover class’s eat method has logic to merge two Movers that had collision detection into one, and the attract method measures gravity at the current distance of the two objects and adds it to the Mover’s acceleration.
After all calculations finish, I updated the Mover’s position, size, speed, direction, etc. The Gravity class’s calcGravity method is exactly the same logic I wrote in the previous post.
Methods related to Mover’s motion are as follows:
export class Mover {
constructor(mass, velocity, location, id, scene) {
// ...
}
eat(otherMover) {
const newMass = this.mass + otherMover.mass;
const newLocation = new Vector3(
(this.location.x * this.mass + otherMover.location.x * otherMover.mass) / newMass,
(this.location.y * this.mass + otherMover.location.y * otherMover.mass) / newMass,
(this.location.z * this.mass + otherMover.location.z * otherMover.mass) / newMass
);
const newVelocity = new Vector3(
(this.velocity.x * this.mass + otherMover.velocity.x * otherMover.mass) / newMass,
(this.velocity.y * this.mass + otherMover.velocity.y * otherMover.mass) / newMass,
(this.velocity.z * this.mass + otherMover.velocity.z * otherMover.mass) / newMass
);
this.location = newLocation;
this.velocity = newVelocity;
this.mass = newMass;
otherMover.kill();
}
attract(otherMover, options) {
const force = Gravity.calcGravity(this, otherMover, options.G);
this.applyForce(force);
}
applyForce(force) {
if(!this.mass) this.mass = 1.0;
const f = force.divideScalar(this.mass);
// Apply force to mover's acceleration
this.acceleration.add(f);
}
update() {
this.velocity.add(this.acceleration); // Add acceleration to velocity
this.location.add(this.velocity); // Add velocity to position to move
this.acceleration.multiplyScalar(0); // Reset acceleration
this.mesh.position.copy(this.location); // Apply position to mover's mesh object
}
}To summarize, every frame we traverse the movers list, calculate gravity between each Movers, apply acceleration, and actually move the Mover. The full source can be checked in the gravity test project GitHub repository.
That’s all for this post on implementing gravity with JavaScript.
관련 포스팅 보러가기
[Implementing Gravity with JavaScript] 1. What is Gravity?
Programming/Graphics[Simulating Celestial Bodies with JavaScript] Implementing Planetary Motion
Programming/Graphics[Simulating Celestial Bodies with JavaScript] Understanding the Keplerian Elements
Programming/GraphicsIs the Randomness Your Computer Creates Actually Random?
Programming/Algorithm[Deep Learning Series] Understanding Backpropagation
Programming/Machine Learning