diff --git a/src/dev.js b/src/dev.js index 65a0bc8..0316640 100644 --- a/src/dev.js +++ b/src/dev.js @@ -13,6 +13,7 @@ // // // +// // // // diff --git a/src/kernel.js b/src/kernel.js index 2ad7149..ffaa682 100644 --- a/src/kernel.js +++ b/src/kernel.js @@ -9,7 +9,7 @@ var chrome_local_file = window.location.protocol == "file:" && navigator.userAgent.toLowerCase().indexOf('chrome') > -1; var USE_WORKER = (window.Worker !== undefined && !chrome_local_file) - + var _physics = null var _tween = null var _fpsWindow = [] // for keeping track of the actual frame rate @@ -36,7 +36,7 @@ var params = pSystem.parameters() if(USE_WORKER){ - trace('using web workers') + trace('arbor.js/web-workers',params) _screenInterval = setInterval(that.screenUpdate, params.timeout) _physics = new Worker(arbor_path()+'physics/worker.js') @@ -46,8 +46,8 @@ physics:objmerge(params, {timeout:Math.ceil(params.timeout)}) }) }else{ - trace("couldn't use web workers, be careful...") - _physics = Physics(params.dt, params.stiffness, params.repulsion, params.friction, that.system._updateGeometry) + trace('arbor.js/single-threaded',params) + _physics = Physics(params.dt, params.stiffness, params.repulsion, params.friction, that.system._updateGeometry, params.integrator) that.start() } diff --git a/src/physics/physics.js b/src/physics/physics.js index 38ee34d..6536f65 100644 --- a/src/physics/physics.js +++ b/src/physics/physics.js @@ -4,7 +4,7 @@ // the particle system itself. either run inline or in a worker (see worker.js) // - var Physics = function(dt, stiffness, repulsion, friction, updateFn){ + var Physics = function(dt, stiffness, repulsion, friction, updateFn, integrator){ var bhTree = BarnesHutTree() // for computing particle repulsion var active = {particles:{}, springs:{}} var free = {particles:{}} @@ -17,6 +17,7 @@ var SPEED_LIMIT = 1000 // the max particle velocity per tick var that = { + integrator:['verlet','euler'].indexOf(integrator)>=0 ? integrator : 'verlet', stiffness:(stiffness!==undefined) ? stiffness : 1000, repulsion:(repulsion!==undefined)? repulsion : 600, friction:(friction!==undefined)? friction : .3, @@ -29,14 +30,14 @@ }, modifyPhysics:function(param){ - $.each(['stiffness','repulsion','friction','gravity','dt','precision'], function(i, p){ + $.each(['stiffness','repulsion','friction','gravity','dt','precision', 'integrator'], function(i, p){ if (param[p]!==undefined){ if (p=='precision'){ that.theta = 1-param[p] return } that[p] = param[p] - + if (p=='stiffness'){ var stiff=param[p] $.each(active.springs, function(id, spring){ @@ -129,10 +130,20 @@ return _epoch }, - tick:function(){ that.tendParticles() - that.eulerIntegrator(that.dt) + if (that.integrator=='euler'){ + that.updateForces() + that.updateVelocity(that.dt) + that.updatePosition(that.dt) + }else{ + // default to verlet + that.updateForces(); + that.cacheForces(); // snapshot f(t) + that.updatePosition(that.dt); // update position to x(t + 1) + that.updateForces(); // calculate f(t+1) + that.updateVelocity(that.dt); // update using f(t) and f(t+1) + } that.tock() }, @@ -168,8 +179,8 @@ }, - // Physics stuff - eulerIntegrator:function(dt){ + // Physics stuff + updateForces:function() { if (that.repulsion>0){ if (that.theta>0) that.applyBarnesHutRepulsion() else that.applyBruteForceRepulsion() @@ -177,10 +188,15 @@ if (that.stiffness>0) that.applySprings() that.applyCenterDrift() if (that.gravity) that.applyCenterGravity() - that.updateVelocity(dt) - that.updatePosition(dt) }, - + + cacheForces:function() { + // keep a snapshot of the current forces for the verlet integrator + $.each(active.particles, function(id, point) { + point._F = point.f; + }); + }, + applyBruteForceRepulsion:function(){ $.each(active.particles, function(id1, point1){ $.each(active.particles, function(id2, point2){ @@ -266,6 +282,7 @@ updateVelocity:function(timestep){ // translate forces to a new velocity for this particle + var sum=0, max=0, n = 0; $.each(active.particles, function(id, point) { if (point.fixed){ point.v = new Point(0,0) @@ -273,32 +290,43 @@ return } - var was = point.v.magnitude() - point.v = point.v.add(point.f.multiply(timestep)).multiply(1-that.friction); + if (that.integrator=='euler'){ + point.v = point.v.add(point.f.multiply(timestep)).multiply(1-that.friction); + }else{ + point.v = point.v.add(point.f.add(point._F).multiply(timestep*0.5)).multiply(1-that.friction); + } point.f.x = point.f.y = 0 var speed = point.v.magnitude() if (speed>SPEED_LIMIT) point.v = point.v.divide(speed*speed) + + var speed = point.v.magnitude(); + var e = speed*speed + sum += e + max = Math.max(e,max) + n++ }); + _energy = {sum:sum, max:max, mean:sum/n, n:n} + }, updatePosition:function(timestep){ // translate velocity to a position delta - var sum=0, max=0, n = 0; var bottomright = null - var topleft = null - + var topleft = null + $.each(active.particles, function(i, point) { + // move the node to its new position - point.p = point.p.add(point.v.multiply(timestep)); + if (that.integrator=='euler'){ + point.p = point.p.add(point.v.multiply(timestep)); + }else{ + //this should follow the equation + //x(t+1) = x(t) + v(t) * timestep + 1/2 * timestep^2 * a(t) + var accelPart = point.f.multiply(0.5 * timestep * timestep); + point.p = point.p.add(point.v.multiply(timestep)).add(accelPart); + } - // keep stats to report in systemEnergy - var speed = point.v.magnitude(); - var e = speed*speed - sum += e - max = Math.max(e,max) - n++ - if (!bottomright){ bottomright = new Point(point.p.x, point.p.y) topleft = new Point(point.p.x, point.p.y) @@ -309,11 +337,10 @@ if (pt.x===null || pt.y===null) return if (pt.x > bottomright.x) bottomright.x = pt.x; if (pt.y > bottomright.y) bottomright.y = pt.y; - if (pt.x < topleft.x) topleft.x = pt.x; - if (pt.y < topleft.y) topleft.y = pt.y; + if (pt.x < topleft.x) topleft.x = pt.x; + if (pt.y < topleft.y) topleft.y = pt.y; }); - _energy = {sum:sum, max:max, mean:sum/n, n:n} _bounds = {topleft:topleft||new Point(-1,-1), bottomright:bottomright||new Point(1,1)} }, @@ -333,4 +360,4 @@ var y = center_pt.y var d = r*2 return new Point(x-r+Math.random()*d, y-r+Math.random()*d) - } + } \ No newline at end of file diff --git a/src/physics/system.js b/src/physics/system.js index b1b722f..3ce52ee 100644 --- a/src/physics/system.js +++ b/src/physics/system.js @@ -4,8 +4,8 @@ // the main controller object for creating/modifying graphs // - var ParticleSystem = function(repulsion, stiffness, friction, centerGravity, targetFps, dt, precision){ - // also callable with ({stiffness:, repulsion:, friction:, timestep:, fps:, dt:, gravity:}) + var ParticleSystem = function(repulsion, stiffness, friction, centerGravity, targetFps, dt, precision, integrator){ + // also callable with ({integrator:, stiffness:, repulsion:, friction:, timestep:, fps:, dt:, gravity:}) var _changes=[] var _notification=null @@ -17,8 +17,8 @@ var _bounds = null var _boundsTarget = null - if (typeof stiffness=='object'){ - var _p = stiffness + if (typeof repulsion=='object'){ + var _p = repulsion friction = _p.friction repulsion = _p.repulsion targetFps = _p.fps @@ -26,8 +26,11 @@ stiffness = _p.stiffness centerGravity = _p.gravity precision = _p.precision + integrator = _p.integrator } + // param validation and defaults + if (integrator!='verlet' && integrator!='euler') integrator='verlet' friction = isNaN(friction) ? .5 : friction repulsion = isNaN(repulsion) ? 1000 : repulsion targetFps = isNaN(targetFps) ? 55 : targetFps @@ -35,8 +38,9 @@ dt = isNaN(dt) ? 0.02 : dt precision = isNaN(precision) ? .6 : precision centerGravity = (centerGravity===true) + var _systemTimeout = (targetFps!==undefined) ? 1000/targetFps : 1000/50 - var _parameters = {repulsion:repulsion, stiffness:stiffness, friction:friction, dt:dt, gravity:centerGravity, precision:precision, timeout:_systemTimeout} + var _parameters = {integrator:integrator, repulsion:repulsion, stiffness:stiffness, friction:friction, dt:dt, gravity:centerGravity, precision:precision, timeout:_systemTimeout} var _energy var state = { @@ -68,10 +72,10 @@ else that.parameters({timeout:1000/(newFPS||50)}) }, - start:function(){ state.kernel.start() }, + stop:function(){ state.kernel.stop() },