boids
Version:
ERROR: No README.md file found!
171 lines (151 loc) • 5.5 kB
JavaScript
var EventEmitter = require('events').EventEmitter
, inherits = require('inherits')
, POSITIONX = 0
, POSITIONY = 1
, SPEEDX = 2
, SPEEDY = 3
, ACCELERATIONX = 4
, ACCELERATIONY = 5
module.exports = Boids
function Boids(opts, callback) {
if (!(this instanceof Boids)) return new Boids(opts, callback)
EventEmitter.call(this)
opts = opts || {}
callback = callback || function(){}
this.speedLimitRoot = opts.speedLimit || 0
this.accelerationLimitRoot = opts.accelerationLimit || 1
this.speedLimit = Math.pow(this.speedLimitRoot, 2)
this.accelerationLimit = Math.pow(this.accelerationLimitRoot, 2)
this.separationDistance = Math.pow(opts.separationDistance || 60, 2)
this.alignmentDistance = Math.pow(opts.alignmentDistance || 180, 2)
this.cohesionDistance = Math.pow(opts.cohesionDistance || 180, 2)
this.separationForce = opts.separationForce || 0.15
this.cohesionForce = opts.cohesionForce || 0.1
this.alignmentForce = opts.alignmentForce || opts.alignment || 0.25
this.attractors = opts.attractors || []
var boids = this.boids = []
for (var i = 0, l = opts.boids === undefined ? 50 : opts.boids; i < l; i += 1) {
boids[i] = [
Math.random()*25, Math.random()*25 // position
, 0, 0 // speed
, 0, 0 // acceleration
]
}
this.on('tick', function() {
callback(boids)
})
}
inherits(Boids, EventEmitter)
Boids.prototype.tick = function() {
var boids = this.boids
, sepDist = this.separationDistance
, sepForce = this.separationForce
, cohDist = this.cohesionDistance
, cohForce = this.cohesionForce
, aliDist = this.alignmentDistance
, aliForce = this.alignmentForce
, speedLimit = this.speedLimit
, accelerationLimit = this.accelerationLimit
, accelerationLimitRoot = this.accelerationLimitRoot
, speedLimitRoot = this.speedLimitRoot
, size = boids.length
, current = size
, sforceX, sforceY
, cforceX, cforceY
, aforceX, aforceY
, spareX, spareY
, attractors = this.attractors
, attractorCount = attractors.length
, attractor
, distSquared
, currPos
, length
, target
, ratio
while (current--) {
sforceX = 0; sforceY = 0
cforceX = 0; cforceY = 0
aforceX = 0; aforceY = 0
currPos = boids[current]
// Attractors
target = attractorCount
while (target--) {
attractor = attractors[target]
spareX = currPos[0] - attractor[0]
spareY = currPos[1] - attractor[1]
distSquared = spareX*spareX + spareY*spareY
if (distSquared < attractor[2]*attractor[2]) {
length = hypot(spareX, spareY)
boids[current][SPEEDX] -= (attractor[3] * spareX / length) || 0
boids[current][SPEEDY] -= (attractor[3] * spareY / length) || 0
}
}
target = size
while (target--) {
if (target === current) continue
spareX = currPos[0] - boids[target][0]
spareY = currPos[1] - boids[target][1]
distSquared = spareX*spareX + spareY*spareY
if (distSquared < sepDist) {
sforceX += spareX
sforceY += spareY
} else {
if (distSquared < cohDist) {
cforceX += spareX
cforceY += spareY
}
if (distSquared < aliDist) {
aforceX += boids[target][SPEEDX]
aforceY += boids[target][SPEEDY]
}
}
}
// Separation
length = hypot(sforceX, sforceY)
boids[current][ACCELERATIONX] += (sepForce * sforceX / length) || 0
boids[current][ACCELERATIONY] += (sepForce * sforceY / length) || 0
// Cohesion
length = hypot(cforceX, cforceY)
boids[current][ACCELERATIONX] -= (cohForce * cforceX / length) || 0
boids[current][ACCELERATIONY] -= (cohForce * cforceY / length) || 0
// Alignment
length = hypot(aforceX, aforceY)
boids[current][ACCELERATIONX] -= (aliForce * aforceX / length) || 0
boids[current][ACCELERATIONY] -= (aliForce * aforceY / length) || 0
}
current = size
// Apply speed/acceleration for
// this tick
while (current--) {
if (accelerationLimit) {
distSquared = boids[current][ACCELERATIONX]*boids[current][ACCELERATIONX] + boids[current][ACCELERATIONY]*boids[current][ACCELERATIONY]
if (distSquared > accelerationLimit) {
ratio = accelerationLimitRoot / hypot(boids[current][ACCELERATIONX], boids[current][ACCELERATIONY])
boids[current][ACCELERATIONX] *= ratio
boids[current][ACCELERATIONY] *= ratio
}
}
boids[current][SPEEDX] += boids[current][ACCELERATIONX]
boids[current][SPEEDY] += boids[current][ACCELERATIONY]
if (speedLimit) {
distSquared = boids[current][SPEEDX]*boids[current][SPEEDX] + boids[current][SPEEDY]*boids[current][SPEEDY]
if (distSquared > speedLimit) {
ratio = speedLimitRoot / hypot(boids[current][SPEEDX], boids[current][SPEEDY])
boids[current][SPEEDX] *= ratio
boids[current][SPEEDY] *= ratio
}
}
boids[current][POSITIONX] += boids[current][SPEEDX]
boids[current][POSITIONY] += boids[current][SPEEDY]
}
this.emit('tick', boids)
}
// double-dog-leg hypothenuse approximation
// http://forums.parallax.com/discussion/147522/dog-leg-hypotenuse-approximation
function hypot(a, b) {
a = Math.abs(a)
b = Math.abs(b)
var lo = Math.min(a, b)
var hi = Math.max(a, b)
return hi + 3 * lo / 32 + Math.max(0, 2 * lo - hi) / 8 + Math.max(0, 4 * lo - hi) / 16
}