I have ported my simulation from Java to vanilla Javascript. The most important aim was to keep the sum of all energies constant. I have this: **simulation.js** var Configuration = { PIXELS_PER_UNIT_LENGTH: 10.0, FORCE_CONSTANT: 500.0, SLEEP_TIME: 10, TIME_STEP: 0.01, MINIMUM_PARTICLE_MASS: 10.0, MAXIMUM_PARTICLE_MASS: 30.0, MAXIMUM_INITIAL_VELOCITY: 40.0, DEFAULT_NUMBER_OF_PARTICLES: 10 }; function Particle(mass, radius) { this.mass = mass; this.radius = radius; this.x = 0.0; this.y = 0.0; this.velocityX = 0.0; this.velocityY = 0.0; } Particle.prototype.getMass = function() { return this.mass; }; Particle.prototype.getX = function() { return this.x; }; Particle.prototype.getY = function() { return this.y; }; Particle.prototype.getVelocityX = function() { return this.velocityX; }; Particle.prototype.getVelocityY = function() { return this.velocityY; }; Particle.prototype.setX = function(x) { this.x = x; }; Particle.prototype.setY = function(y) { this.y = y; }; Particle.prototype.setVelocityX = function(velocityX) { this.velocityX = velocityX; }; Particle.prototype.setVelocityY = function(velocityY) { this.velocityY = velocityY; }; Particle.prototype.getSpeed = function() { var vxSquared = this.velocityX * this.velocityX; var vySquared = this.velocityY * this.velocityY; return Math.sqrt(vxSquared + vySquared); }; Particle.prototype.getDistance = function(other) { var dx = this.x - other.x; var dy = this.y - other.y; return Math.sqrt(dx * dx + dy * dy); }; Particle.prototype.getKineticEnergy = function() { var speed = this.getSpeed(); return 0.5 * this.mass * speed * speed; }; Particle.prototype.getRadius = function() { return this.radius; }; function getForce(particle1, particle2) { var distance = particle1.getDistance(particle2); var mass1 = particle1.getMass(); var mass2 = particle2.getMass(); return Configuration.FORCE_CONSTANT * mass1 * mass2 / (distance * distance); } function getForceVector(particle1, particle2) { var vectorLength = getForce(particle1, particle2); var dx = particle1.getX() - particle2.getX(); var dy = particle1.getY() - particle2.getY(); var angle = Math.atan2(dy, dx); var xComponent = vectorLength * Math.cos(angle); var yComponent = vectorLength * Math.sin(angle); return new Vector(xComponent, yComponent); } function getPotentialEnergy(particle1, particle2) { var mass1 = particle1.getMass(); var mass2 = particle2.getMass(); var distance = particle1.getDistance(particle2); return Configuration.FORCE_CONSTANT * mass1 * mass2 / distance; } function ParticleRenderer(particle, color, canvasContext) { this.particle = particle; this.color = color; this.canvasContext = canvasContext; } ParticleRenderer.prototype.draw = function() { var effectiveX = this.particle.getX() * Configuration.PIXELS_PER_UNIT_LENGTH; var effectiveY = this.particle.getY() * Configuration.PIXELS_PER_UNIT_LENGTH; this.canvasContext.fillStyle = this.color; this.canvasContext.beginPath(); this.canvasContext.arc(effectiveX, effectiveY, this.particle.getRadius() * Configuration.PIXELS_PER_UNIT_LENGTH, 0, 2 * Math.PI, false); this.canvasContext.fill(); }; function Vector(x, y) { this.x = x; this.y = y; } Vector.prototype.getX = function() { return this.x; }; Vector.prototype.getY = function() { return this.y; }; Vector.prototype.plus = function(other) { return new Vector(this.x + other.x, this.y + other.y); }; Vector.prototype.multiply = function(factor) { return new Vector(this.x * factor, this.y * factor); }; function SimulationEngine(canvasContext, canvasElement, particles, renderers, timeStep, worldWidth, worldHeight, sleepTime) { this.canvasContext = canvasContext; this.canvasElement = canvasElement; this.particles = particles; this.renderers = renderers; this.timeStep = timeStep; this.totalEnergy = null; this.worldWidth = worldWidth; this.worldHeight = worldHeight; this.sleepTime = sleepTime; this.exit = false; this.pause = true; this.particleToForceVectorMap = {}; } SimulationEngine.prototype.togglePause = function() { this.pause = !this.pause; }; SimulationEngine.prototype.run = function() { this.redraw(); this.totalEnergy = this.computeTotalEnergy(); var self = this; setInterval(function() { if (!self.pause) { self.performStep(); self.redraw(); } }, this.sleepTime); }; SimulationEngine.prototype.performStep = function() { this.computeForceVectors(); this.updateParticleVelocities(); this.moveParticles(); this.resolveWorldBorderCollisions(); this.normalizeVelocityVectors(); this.particleToForceVectorMap = {}; }; SimulationEngine.prototype.redraw = function() { this.canvasContext.fillStyle = "#000"; this.canvasContext.fillRect(0, 0, this.canvasElement.width, this.canvasElement.height); for (var i = 0; i < this.renderers.length; ++i) { var renderer = this.renderers[i]; renderer.draw(); } this.canvasContext.fillStyle = "#fff"; this.font = "20px Arial"; this.canvasContext.fillText("Total energy: " + this.computeTotalEnergy(), 0, 30); }; SimulationEngine.prototype.computeForceVectors = function() { this.particleToForceVectorMap = []; for (var i = 0; i < this.particles.length; ++i) { var particle = this.particles[i]; var vector = new Vector(0.0, 0.0); for (var j = 0; j < this.particles.length; ++j) { var otherParticle = this.particles[j]; if (i === j) { continue; } var aux = getForceVector(particle, otherParticle); vector = vector.plus(aux); } this.particleToForceVectorMap.push([particle, vector]); } }; SimulationEngine.prototype.updateParticleVelocities = function() { for (var i = 0; i < this.particleToForceVectorMap.length; ++i) { var particle = this.particleToForceVectorMap[i][0]; var vector = this.particleToForceVectorMap[i][1]; vector = vector.multiply(1.0 / particle.getMass()); particle.setVelocityX(particle.getVelocityX() + vector.getX() * this.timeStep); particle.setVelocityY(particle.getVelocityY() + vector.getY() * this.timeStep); } }; SimulationEngine.prototype.moveParticles = function() { for (var i = 0; i < this.particles.length; ++i) { var particle = this.particles[i]; particle.setX(particle.getX() + particle.getVelocityX() * this.timeStep); particle.setY(particle.getY() + particle.getVelocityY() * this.timeStep); } }; SimulationEngine.prototype.resolveWorldBorderCollisions = function() { for (var i = 0; i < this.particles.length; ++i) { var particle = this.particles[i]; var radius = particle.getRadius(); if (particle.getY() - radius <= 0.0) { particle.setVelocityY(-particle.getVelocityY()); } else if (particle.getY() + radius >= this.worldHeight) { particle.setVelocityY(-particle.getVelocityY()); } if (particle.getX() - radius <= 0.0) { particle.setVelocityX(-particle.getVelocityX()); } else if (particle.getX() + radius >= this.worldWidth) { particle.setVelocityX(-particle.getVelocityX()); } } }; SimulationEngine.prototype.normalizeVelocityVectors = function() { var totalEnergyDelta = this.computeTotalEnergyDelta(); var factor = this.getNormalizationFactor(totalEnergyDelta); for (var i = 0; i < this.particles.length; ++i) { var particle = this.particles[i]; particle.setVelocityX(factor * particle.getVelocityX()); particle.setVelocityY(factor * particle.getVelocityY()); } }; SimulationEngine.prototype.getNormalizationFactor = function(totalEnergyDelta) { var aux = totalEnergyDelta / this.computeTotalKineticEnergy() + 1.0; if (aux < 0.0) { console.log("aux: " + aux); } return Math.sqrt(aux); }; SimulationEngine.prototype.computeTotalKineticEnergy = function() { var energy = 0.0; for (var i = 0; i < this.particles.length; ++i) { var particle = this.particles[i]; energy += particle.getKineticEnergy(); } return energy; }; SimulationEngine.prototype.computeTotalEnergyDelta = function() { return this.totalEnergy - this.computeTotalEnergy(); }; SimulationEngine.prototype.computeTotalEnergy = function() { var totalEnergy = 0.0; for (var i = 0; i < this.particles.length; ++i) { totalEnergy += this.particles[i].getKineticEnergy(); } for (var i = 0; i < this.particles.length; ++i) { var particle1 = this.particles[i]; for (var j = i + 1; j < this.particles.length; ++j) { var particle2 = this.particles[j]; totalEnergy += getPotentialEnergy(particle1, particle2); } } return totalEnergy; }; function setCanvasDimensions(canvasElement) { canvasElement.width = window.innerWidth; canvasElement.height = window.innerHeight; } function createRandomParticles(number_of_particles, worldWidth, worldHeight, canvasContext) { var particleData = []; for (var i = 0; i < number_of_particles; ++i) { particleData.push(createRandomParticleData(worldWidth, worldHeight, canvasContext)); } return particleData; } function getRandomColor() { var letters = "0123456789abcdef"; var color = "#"; for (var i = 0; i < 6; ++i) { color += letters[Math.floor(Math.random() * 16)]; } return color; } function createRandomParticleData(worldWidth, worldHeight, canvasContext) { var mass = Configuration.MINIMUM_PARTICLE_MASS + (Configuration.MAXIMUM_PARTICLE_MASS - Configuration.MINIMUM_PARTICLE_MASS) * Math.random(); var radius = mass / Configuration.PIXELS_PER_UNIT_LENGTH; var particle = new Particle(mass, radius); particle.setX(worldWidth * Math.random()); particle.setY(worldHeight * Math.random()); particle.setVelocityX(Configuration.MAXIMUM_INITIAL_VELOCITY * Math.random()); particle.setVelocityY(Configuration.MAXIMUM_INITIAL_VELOCITY * Math.random()); var color = getRandomColor(); var renderer = new ParticleRenderer(particle, color, canvasContext); return [particle, renderer]; } function extractParticles(particleData) { particles = []; for (var i = 0; i < particleData.length; ++i) { particles.push(particleData[i][0]); } return particles; } function extractRenderers(particleData) { particles = []; for (var i = 0; i < particleData.length; ++i) { particles.push(particleData[i][1]); } return particles; } function main() { var canvasElement = document.getElementById("cnvs"); setCanvasDimensions(canvasElement); var canvasContext = canvasElement.getContext("2d"); var worldWidth = canvasElement.width / Configuration.PIXELS_PER_UNIT_LENGTH; var worldHeight = canvasElement.height / Configuration.PIXELS_PER_UNIT_LENGTH; var particleData = createRandomParticles(Configuration.DEFAULT_NUMBER_OF_PARTICLES, worldWidth, worldHeight, canvasContext); var particles = extractParticles(particleData); var renderers = extractRenderers(particleData); var simulationEngine = new SimulationEngine(canvasContext, canvasElement, particles, renderers, Configuration.TIME_STEP, worldWidth, worldHeight, Configuration.SLEEP_TIME); window.onkeydown = function(e) { if (e.keyCode === 32) { simulationEngine.togglePause(); } }; simulationEngine.run(); } main(); **index.html** <!DOCTYPE html> <html> <head> <title>simulating...</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <canvas id="cnvs" style="background-color: black; margin:0; padding:0;">Your browser does not support HTML5 canvases!</canvas> <script src="simulation.js"></script> </body> </html> You can view the simulation [here](http://coderodde.github.io/particles/index.html). (Press Space bar for turning pause on/off.) # Critique request Since I am not a professional Javascript developer, it would be nice to hear the experts' opinions. Also, how should I go about making sure that the canvas occupies the entire view port?