mirror of
https://github.com/pcottle/learnGitBranching.git
synced 2025-07-03 03:04:27 +02:00
in the beginning
This commit is contained in:
commit
a817cec5f5
23 changed files with 4243 additions and 0 deletions
364
arbor_src/physics/physics.js
Normal file
364
arbor_src/physics/physics.js
Normal file
|
@ -0,0 +1,364 @@
|
|||
//
|
||||
// physics.js
|
||||
//
|
||||
// the particle system itself. either run inline or in a worker (see worker.js)
|
||||
//
|
||||
|
||||
var Physics = function(dt, stiffness, repulsion, friction, updateFn, integrator){
|
||||
var bhTree = BarnesHutTree() // for computing particle repulsion
|
||||
var active = {particles:{}, springs:{}}
|
||||
var free = {particles:{}}
|
||||
var particles = []
|
||||
var springs = []
|
||||
var _epoch=0
|
||||
var _energy = {sum:0, max:0, mean:0}
|
||||
var _bounds = {topleft:new Point(-1,-1), bottomright:new Point(1,1)}
|
||||
|
||||
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,
|
||||
gravity:false,
|
||||
dt:(dt!==undefined)? dt : 0.02,
|
||||
theta:.4, // the criterion value for the barnes-hut s/d calculation
|
||||
|
||||
init:function(){
|
||||
return that
|
||||
},
|
||||
|
||||
modifyPhysics:function(param){
|
||||
$.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){
|
||||
spring.k = stiff
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
addNode:function(c){
|
||||
var id = c.id
|
||||
var mass = c.m
|
||||
|
||||
var w = _bounds.bottomright.x - _bounds.topleft.x
|
||||
var h = _bounds.bottomright.y - _bounds.topleft.y
|
||||
var randomish_pt = new Point((c.x != null) ? c.x: _bounds.topleft.x + w*Math.random(),
|
||||
(c.y != null) ? c.y: _bounds.topleft.y + h*Math.random())
|
||||
|
||||
|
||||
active.particles[id] = new Particle(randomish_pt, mass);
|
||||
active.particles[id].connections = 0
|
||||
active.particles[id].fixed = (c.f===1)
|
||||
free.particles[id] = active.particles[id]
|
||||
particles.push(active.particles[id])
|
||||
},
|
||||
|
||||
dropNode:function(c){
|
||||
var id = c.id
|
||||
var dropping = active.particles[id]
|
||||
var idx = $.inArray(dropping, particles)
|
||||
if (idx>-1) particles.splice(idx,1)
|
||||
delete active.particles[id]
|
||||
delete free.particles[id]
|
||||
},
|
||||
|
||||
modifyNode:function(id, mods){
|
||||
if (id in active.particles){
|
||||
var pt = active.particles[id]
|
||||
if ('x' in mods) pt.p.x = mods.x
|
||||
if ('y' in mods) pt.p.y = mods.y
|
||||
if ('m' in mods) pt.m = mods.m
|
||||
if ('f' in mods) pt.fixed = (mods.f===1)
|
||||
if ('_m' in mods){
|
||||
if (pt._m===undefined) pt._m = pt.m
|
||||
pt.m = mods._m
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addSpring:function(c){
|
||||
var id = c.id
|
||||
var length = c.l
|
||||
var from = active.particles[c.fm]
|
||||
var to = active.particles[c.to]
|
||||
|
||||
if (from!==undefined && to!==undefined){
|
||||
active.springs[id] = new Spring(from, to, length, that.stiffness)
|
||||
springs.push(active.springs[id])
|
||||
|
||||
from.connections++
|
||||
to.connections++
|
||||
|
||||
delete free.particles[c.fm]
|
||||
delete free.particles[c.to]
|
||||
}
|
||||
},
|
||||
|
||||
dropSpring:function(c){
|
||||
var id = c.id
|
||||
var dropping = active.springs[id]
|
||||
|
||||
dropping.point1.connections--
|
||||
dropping.point2.connections--
|
||||
|
||||
var idx = $.inArray(dropping, springs)
|
||||
if (idx>-1){
|
||||
springs.splice(idx,1)
|
||||
}
|
||||
delete active.springs[id]
|
||||
},
|
||||
|
||||
_update:function(changes){
|
||||
// batch changes phoned in (automatically) by a ParticleSystem
|
||||
_epoch++
|
||||
|
||||
$.each(changes, function(i, c){
|
||||
if (c.t in that) that[c.t](c)
|
||||
})
|
||||
return _epoch
|
||||
},
|
||||
|
||||
tick:function(){
|
||||
that.tendParticles()
|
||||
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()
|
||||
},
|
||||
|
||||
tock:function(){
|
||||
var coords = []
|
||||
$.each(active.particles, function(id, pt){
|
||||
coords.push(id)
|
||||
coords.push(pt.p.x)
|
||||
coords.push(pt.p.y)
|
||||
})
|
||||
|
||||
if (updateFn) updateFn({geometry:coords, epoch:_epoch, energy:_energy, bounds:_bounds})
|
||||
},
|
||||
|
||||
tendParticles:function(){
|
||||
$.each(active.particles, function(id, pt){
|
||||
// decay down any of the temporary mass increases that were passed along
|
||||
// by using an {_m:} instead of an {m:} (which is to say via a Node having
|
||||
// its .tempMass attr set)
|
||||
if (pt._m!==undefined){
|
||||
if (Math.abs(pt.m-pt._m)<1){
|
||||
pt.m = pt._m
|
||||
delete pt._m
|
||||
}else{
|
||||
pt.m *= .98
|
||||
}
|
||||
}
|
||||
|
||||
// zero out the velocity from one tick to the next
|
||||
pt.v.x = pt.v.y = 0
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
|
||||
// Physics stuff
|
||||
updateForces:function() {
|
||||
if (that.repulsion>0){
|
||||
if (that.theta>0) that.applyBarnesHutRepulsion()
|
||||
else that.applyBruteForceRepulsion()
|
||||
}
|
||||
if (that.stiffness>0) that.applySprings()
|
||||
that.applyCenterDrift()
|
||||
if (that.gravity) that.applyCenterGravity()
|
||||
},
|
||||
|
||||
cacheForces:function() {
|
||||
// keep a snapshot of the current forces for the verlet integrator
|
||||
$.each(active.particles, function(id, point) {
|
||||
point._F = point.f;
|
||||
point._m = point.m;
|
||||
});
|
||||
},
|
||||
|
||||
applyBruteForceRepulsion:function(){
|
||||
$.each(active.particles, function(id1, point1){
|
||||
$.each(active.particles, function(id2, point2){
|
||||
if (point1 !== point2){
|
||||
var d = point1.p.subtract(point2.p);
|
||||
var distance = Math.max(1.0, d.magnitude());
|
||||
var direction = ((d.magnitude()>0) ? d : Point.random(1)).normalize()
|
||||
|
||||
// apply force to each end point
|
||||
// (consult the cached `real' mass value if the mass is being poked to allow
|
||||
// for repositioning. the poked mass will still be used in .applyforce() so
|
||||
// all should be well)
|
||||
point1.applyForce(direction.multiply(that.repulsion*(point2._m||point2.m)*.5)
|
||||
.divide(distance * distance * 0.5) );
|
||||
point2.applyForce(direction.multiply(that.repulsion*(point1._m||point1.m)*.5)
|
||||
.divide(distance * distance * -0.5) );
|
||||
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
applyBarnesHutRepulsion:function(){
|
||||
if (!_bounds.topleft || !_bounds.bottomright) return
|
||||
var bottomright = new Point(_bounds.bottomright)
|
||||
var topleft = new Point(_bounds.topleft)
|
||||
|
||||
// build a barnes-hut tree...
|
||||
bhTree.init(topleft, bottomright, that.theta)
|
||||
$.each(active.particles, function(id, particle){
|
||||
bhTree.insert(particle)
|
||||
})
|
||||
|
||||
// ...and use it to approximate the repulsion forces
|
||||
$.each(active.particles, function(id, particle){
|
||||
bhTree.applyForces(particle, that.repulsion)
|
||||
})
|
||||
},
|
||||
|
||||
applySprings:function(){
|
||||
$.each(active.springs, function(id, spring){
|
||||
var d = spring.point2.p.subtract(spring.point1.p); // the direction of the spring
|
||||
var displacement = spring.length - d.magnitude()//Math.max(.1, d.magnitude());
|
||||
var direction = ( (d.magnitude()>0) ? d : Point.random(1) ).normalize()
|
||||
|
||||
// BUG:
|
||||
// since things oscillate wildly for hub nodes, should probably normalize spring
|
||||
// forces by the number of incoming edges for each node. naive normalization
|
||||
// doesn't work very well though. what's the `right' way to do it?
|
||||
|
||||
// apply force to each end point
|
||||
spring.point1.applyForce(direction.multiply(spring.k * displacement * -0.5))
|
||||
spring.point2.applyForce(direction.multiply(spring.k * displacement * 0.5))
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
applyCenterDrift:function(){
|
||||
// find the centroid of all the particles in the system and shift everything
|
||||
// so the cloud is centered over the origin
|
||||
var numParticles = 0
|
||||
var centroid = new Point(0,0)
|
||||
$.each(active.particles, function(id, point) {
|
||||
centroid.add(point.p)
|
||||
numParticles++
|
||||
});
|
||||
|
||||
if (numParticles==0) return
|
||||
|
||||
var correction = centroid.divide(-numParticles)
|
||||
$.each(active.particles, function(id, point) {
|
||||
point.applyForce(correction)
|
||||
})
|
||||
|
||||
},
|
||||
applyCenterGravity:function(){
|
||||
// attract each node to the origin
|
||||
$.each(active.particles, function(id, point) {
|
||||
var direction = point.p.multiply(-1.0);
|
||||
point.applyForce(direction.multiply(that.repulsion / 100.0));
|
||||
});
|
||||
},
|
||||
|
||||
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)
|
||||
point.f = new Point(0,0)
|
||||
return
|
||||
}
|
||||
|
||||
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.divide(point._m)).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 bottomright = null
|
||||
var topleft = null
|
||||
|
||||
$.each(active.particles, function(i, point) {
|
||||
|
||||
// move the node to its new position
|
||||
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).divide(point.m);
|
||||
point.p = point.p.add(point.v.multiply(timestep)).add(accelPart);
|
||||
}
|
||||
|
||||
if (!bottomright){
|
||||
bottomright = new Point(point.p.x, point.p.y)
|
||||
topleft = new Point(point.p.x, point.p.y)
|
||||
return
|
||||
}
|
||||
|
||||
var pt = point.p
|
||||
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;
|
||||
});
|
||||
|
||||
_bounds = {topleft:topleft||new Point(-1,-1), bottomright:bottomright||new Point(1,1)}
|
||||
},
|
||||
|
||||
systemEnergy:function(timestep){
|
||||
// system stats
|
||||
return _energy
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return that.init()
|
||||
}
|
||||
|
||||
var _nearParticle = function(center_pt, r){
|
||||
var r = r || .0
|
||||
var x = center_pt.x
|
||||
var y = center_pt.y
|
||||
var d = r*2
|
||||
return new Point(x-r+Math.random()*d, y-r+Math.random()*d)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue