// dependencies: Vector2D 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.radius = radius; this.color = color; this.mass = mass; this.charge = charge; this.gradient = gradient; this.line = line; this.x = 0; this.y = 0; this.vx = 0; this.vy = 0; this.angVelo = 0; this.field = field; this.sizefield = sizefield; } Ball.prototype = { get pos2D (){ return new Vector2D(this.x,this.y); }, set pos2D (pos){ this.x = pos.x; this.y = pos.y; }, get velo2D (){ return new Vector2D(this.vx,this.vy); }, set velo2D (velo){ this.vx = velo.x; this.vy = velo.y; }, draw: function (context) { if(this.field){ context.fillStyle = "rgba(32,214,199,.3)"; context.beginPath(); context.arc(this.x, this.y, this.radius + this.sizefield , 0, 2*Math.PI, true); context.closePath();2 context.fill(); } if (this.gradient){ 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; }else{ context.fillStyle = this.color; } context.beginPath(); context.arc(this.x, this.y, this.radius, 0, 2*Math.PI, true); context.closePath(); context.fill(); if (this.line){ context.strokeStyle = this.color; context.lineWidth = 1; context.beginPath(); 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); context.closePath(); context.stroke(); } } }; function Forces(){ } // STATIC METHODS 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) { return E.multiply(q); } 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 force; var velMag = vel.length(); if (velMag > 0) { force = vel.multiply(-k); }else { force = new Vector2D(0,0); } return force; } Forces.drag = function(k,vel) { var force; var velMag = vel.length(); if (velMag > 0) { force = vel.multiply(-k*velMag); } else { force = new Vector2D(0,0); } return force; } Forces.lift = function(k,vel) { var force; var velMag = vel.length(); if (velMag > 0) { force = vel.perp(k*velMag); } else { force = new Vector2D(0,0); } return force; } Forces.upthrust = function(rho,V,g) { return new Vector2D(0,-rho*V*g); } Forces.vortex = function(k,r0,r){ var force; var rMag = r.length(); if (rMag > 0){ if (rMag > r0) { force = r.multiply(-k*Math.pow(r0/rMag,4)); }else{ force = r.multiply(k); } }else{ force = new Vector2D(0,0); } return force; } Forces.spring = function(k,r){ return r.multiply(-k); } Forces.damping = function(c,vel){ var force; var velMag = vel.length(); if (velMag>0) { force = vel.multiply(-c); } else { force = new Vector2D(0,0); } return force; } Forces.add = function(arr){ var forceSum = new Vector2D(0,0); for (var i=0; i 0) { return new Vector2D(this.x/length,this.y/length); }else{ return new Vector2D(0,0); } }, normalize: function() { var length = this.length(); if (length > 0) { this.x /= length; this.y /= length; } return this.length(); }, add: function(vec) { return new Vector2D(this.x + vec.x,this.y + vec.y); }, incrementBy: function(vec) { this.x += vec.x; this.y += vec.y; }, subtract: function(vec) { return new Vector2D(this.x - vec.x,this.y - vec.y); }, decrementBy: function(vec) { this.x -= vec.x; this.y -= vec.y; }, multiply: function(k) { 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); }, scaleBy: function(k) { this.x *= k; this.y *= k; }, dotProduct: function(vec) { return this.x*vec.x + this.y*vec.y; }, projection: function(vec) { var length = this.length(); var lengthVec = vec.length(); var proj; if( (length == 0) || ( lengthVec == 0) ){ proj = 0; }else { proj = (this.x*vec.x + this.y*vec.y)/lengthVec; } return proj; }, project: function(vec) { 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); if (positive){ vec.scaleBy(u/length); }else{ vec.scaleBy(-u/length); } return vec; }, perp: function(u,anticlockwise){ if (typeof(anticlockwise)==='undefined') anticlockwise = true; var length = this.length(); var vec = new Vector2D(this.y, -this.x); if (length > 0) { if (anticlockwise){ // anticlockwise with respect to canvas coordinate system vec.scaleBy(u/length); }else{ vec.scaleBy(-u/length); } }else{ vec = new Vector2D(0,0); } return vec; } }; // STATIC METHODS 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){ vec.x *= sca; vec.y *= 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); if (!clockwise){ vec.y *= -1; } return vec; } var canvas = document.getElementById('canvas'); var context = canvas.getContext('2d'); // size var W = window.innerWidth, H = window.innerHeight, halfW = W / 2, halfH = H / 2; canvas.width = W; canvas.height = H; var G = 1; var c = 200; var attractors; var orbiters; var t0; var dt; var force; var acc; var numOrbiters = 50; var rows = 10; var cols = 10; var refPoint = new Vector2D( W / cols / 2, W / rows / 2); var spacing = W / cols ; var numAttractors = rows * cols; window.onload = init; function init() { // create attractors attractors = new Array(); for (var i=0; i0.2) {dt=0;}; move(); } function move(){ context.clearRect(0, 0, canvas.width, canvas.height); for (var i=0; i canvas.width || obj.y < 0 || obj.y > canvas.height){ recycleOrbiter(obj); } obj.draw(context); } function updateAccel(mass){ acc = force.multiply(1/mass); } function updateVelo(obj){ obj.velo2D = obj.velo2D.addScaled(acc,dt); } function calcForce(obj){ var gravity; force = Forces.zeroForce(); for (var i=0; i attractor.radius+obj.radius){ gravity = Forces.gravity(G,attractor.mass,obj.mass,dist); force = Forces.add([force, gravity]); }else{ // normal velocity vectors just before the impact var normalVelo1 = obj.velo2D.project(dist); var normalVelo2 = attractor.velo2D.project(dist); // tangential velocity vectors var tangentVelo1 = obj.velo2D.subtract(normalVelo1); var tangentVelo2 = attractor.velo2D.subtract(normalVelo2); // move particles so that they just touch 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); // normal velocity components after the impact var m1 = obj.mass; var m2 = attractor.mass; 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); // normal velocity vectors after collision normalVelo1 = dist.para(v1); normalVelo2 = dist.para(v2); // final velocity vectors after collision 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); }