function Ball(radius,color,mass,charge,gradient,line, field, sizefield){
if(typeof(radius)==='undefined') radius = 20;
if(typeof(color)==='undefined') color = '#0000ff';
if(typeof(mass)==='undefined') mass = 1;
if(typeof(charge)==='undefined') charge = 0;
if(typeof(gradient)==='undefined') gradient = false;
if(typeof(line)==='undefined') line = false;
if(typeof(field)==='undefined') field = false;
if(typeof(sizefield)==='undefined') sizefield = 0;
this.gradient = gradient;
this.sizefield = sizefield;
return new Vector2D(this.x,this.y);
return new Vector2D(this.vx,this.vy);
draw: function (context) {
context.fillStyle = "rgba(32,214,199,.3)";
context.arc(this.x, this.y, this.radius + this.sizefield , 0, 2*Math.PI, true);
grad = context.createRadialGradient(this.x,this.y,0,this.x,this.y,this.radius);
grad.addColorStop(0,'#ffffff');
grad.addColorStop(1,this.color);
context.fillStyle = grad;
context.fillStyle = this.color;
context.arc(this.x, this.y, this.radius, 0, 2*Math.PI, true);
context.strokeStyle = this.color;
context.moveTo(this.x-this.radius,this.y);
context.lineTo(this.x+this.radius,this.y);
context.moveTo(this.x,this.y+this.radius);
context.lineTo(this.x,this.y-this.radius);
Forces.zeroForce = function() {
return (new Vector2D(0,0));
Forces.constantGravity = function(m,g){
return new Vector2D(0,m*g);
Forces.gravity = function(G,m1,m2,r){
return r.multiply(-G*m1*m2/(r.lengthSquared()*r.length()));
Forces.electric = function(k,q1,q2,r){
return r.multiply(k*q1*q2/(r.lengthSquared()*r.length()));
Forces.forceField = function(q,E) {
Forces.lorentz = function(q,E,B,vel) {
return E.multiply(q).add(vel.perp(q*B*vel.length()));
Forces.central = function(k,n,r) {
return r.multiply(k*Math.pow(r.length(),n-1));
Forces.linearDrag = function(k,vel){
var velMag = vel.length();
force = vel.multiply(-k);
force = new Vector2D(0,0);
Forces.drag = function(k,vel) {
var velMag = vel.length();
force = vel.multiply(-k*velMag);
force = new Vector2D(0,0);
Forces.lift = function(k,vel) {
var velMag = vel.length();
force = vel.perp(k*velMag);
force = new Vector2D(0,0);
Forces.upthrust = function(rho,V,g) {
return new Vector2D(0,-rho*V*g);
Forces.vortex = function(k,r0,r){
force = r.multiply(-k*Math.pow(r0/rMag,4));
force = new Vector2D(0,0);
Forces.spring = function(k,r){
Forces.damping = function(c,vel){
var velMag = vel.length();
force = vel.multiply(-c);
force = new Vector2D(0,0);
Forces.add = function(arr){
var forceSum = new Vector2D(0,0);
for (var i=0; i<arr.length; i++){
forceSum.incrementBy(force);
lengthSquared: function(){
return this.x*this.x + this.y*this.y;
return Math.sqrt(this.lengthSquared());
return Math.atan2(this.y,this.x);
return new Vector2D(this.x,this.y);
var length = this.length();
return new Vector2D(this.x/length,this.y/length);
return new Vector2D(0,0);
var length = this.length();
return new Vector2D(this.x + vec.x,this.y + vec.y);
incrementBy: function(vec) {
subtract: function(vec) {
return new Vector2D(this.x - vec.x,this.y - vec.y);
decrementBy: function(vec) {
return new Vector2D(k*this.x,k*this.y);
addScaled: function(vec,k) {
return new Vector2D(this.x + k*vec.x, this.y + k*vec.y);
dotProduct: function(vec) {
return this.x*vec.x + this.y*vec.y;
projection: function(vec) {
var length = this.length();
var lengthVec = vec.length();
if( (length == 0) || ( lengthVec == 0) ){
proj = (this.x*vec.x + this.y*vec.y)/lengthVec;
return vec.para(this.projection(vec));
para: function(u,positive){
if (typeof(positive)==='undefined') positive = true;
var length = this.length();
var vec = new Vector2D(this.x, this.y);
perp: function(u,anticlockwise){
if (typeof(anticlockwise)==='undefined') anticlockwise = true;
var length = this.length();
var vec = new Vector2D(this.y, -this.x);
Vector2D.distance = function(vec1,vec2){
return (vec1.subtract(vec2)).length();
Vector2D.angleBetween = function(vec1,vec2){
return Math.acos(vec1.dotProduct(vec2)/(vec1.length()*vec2.length()));
Vector2D.scale = function(vec,sca){
Vector2D.vector2D = function(mag,angle,clockwise){
if (typeof(clockwise)==='undefined') clockwise = true;
var vec = new Vector2D(0,0);
vec.x = mag*Math.cos(angle);
vec.y = mag*Math.sin(angle);
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var W = window.innerWidth,
var refPoint = new Vector2D( W / cols / 2, W / rows / 2);
var numAttractors = rows * cols;
attractors = new Array();
for (var i=0; i<cols*rows; i++){
var color = "rgba(17,63,89,1)";
var attractor = new Ball(r,color,m,0,true, false, true, 30);
var ncol = Math.floor(i/rows);
attractor.pos2D = new Vector2D(refPoint.x+ncol*spacing,refPoint.y+(i-ncol*rows-1)*spacing);
attractors.push(attractor);
for (var i=0; i<numOrbiters; i++){
var color = 'rgba(243,237,91,1)';
var orbiter = new Ball(3,color,1,0,false,false,true, 5);
orbiter.pos2D = new Vector2D(Math.random()*canvas.width,Math.random()*canvas.height);
orbiter.velo2D = new Vector2D((Math.random()-0.5)*100,(Math.random()-0.5)*100);
t0 = new Date().getTime();
window.addEventListener( 'mousedown', onDocumentMouseDown, false );
window.addEventListener( 'resize', onResize, false );
function onDocumentMouseDown( e ){
if (e.pageX || e.pageY) {
else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
var color = 'rgba(243,237,91,1)';
var orbiter = new Ball(3,color,1,0,false,false,true, 5);
orbiter.pos2D = new Vector2D(posx,posy);
orbiter.velo2D = new Vector2D((Math.random()-0.5)*100,(Math.random()-0.5)*100);
console.log(numOrbiters);
requestAnimationFrame(animFrame,canvas);
var t1 = new Date().getTime();
context.clearRect(0, 0, canvas.width, canvas.height);
for (var i=0; i<numOrbiters; i++){
var orbiter = orbiters[i];
updateAccel(orbiter.mass);
for (var i=0; i<numAttractors; i++){
var attractor = attractors[i];
function moveObject(obj){
obj.pos2D = obj.pos2D.addScaled(obj.velo2D,dt);
if (obj.x < 0 || obj.x > canvas.width || obj.y < 0 || obj.y > canvas.height){
function updateAccel(mass){
acc = force.multiply(1/mass);
function updateVelo(obj){
obj.velo2D = obj.velo2D.addScaled(acc,dt);
force = Forces.zeroForce();
for (var i=0; i<numAttractors; i++){
var attractor = attractors[i];
var dist = obj.pos2D.subtract(attractor.pos2D);
if (dist.length() < attractor.radius+obj.radius+attractor.sizefield){
context.strokeStyle = obj.color;
context.moveTo(obj.pos2D.x,obj.pos2D.y);
context.lineTo(attractor.pos2D.x,attractor.pos2D.y);
if (dist.length() > attractor.radius+obj.radius){
gravity = Forces.gravity(G,attractor.mass,obj.mass,dist);
force = Forces.add([force, gravity]);
var normalVelo1 = obj.velo2D.project(dist);
var normalVelo2 = attractor.velo2D.project(dist);
var tangentVelo1 = obj.velo2D.subtract(normalVelo1);
var tangentVelo2 = attractor.velo2D.subtract(normalVelo2);
var L = obj.radius + attractor.radius-dist.length();
var vrel = normalVelo1.subtract(normalVelo2).length();
obj.pos2D = obj.pos2D.addScaled(normalVelo1,-L/vrel);
attractor.pos2D = attractor.pos2D.addScaled(normalVelo2,-L/vrel);
var u1 = normalVelo1.projection(dist);
var u2 = normalVelo2.projection(dist);
var v1 = ((m1-m2)*u1+2*m2*u2)/(m1+m2);
var v2 = ((m2-m1)*u2+2*m1*u1)/(m1+m2);
normalVelo1 = dist.para(v1);
normalVelo2 = dist.para(v2);
obj.velo2D = normalVelo1.add(tangentVelo1);
function recycleOrbiter(obj){
obj.pos2D = new Vector2D(Math.random()*canvas.width,Math.random()*canvas.height);
obj.velo2D = new Vector2D((Math.random()-0.5)*100,(Math.random()-0.5)*100);