diff -r efd9c589177a -r c0b4a8b5a012 toolkit/javascript/d3/examples/voroboids/boid.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/toolkit/javascript/d3/examples/voroboids/boid.js Thu Apr 10 14:20:23 2014 +0200 @@ -0,0 +1,235 @@ +// Boid flocking based on http://harry.me/2011/02/17/neat-algorithms---flocking +var boid = (function() { + function boid() { + var position = [0, 0], + velocity = [0, 0], + gravityCenter = null, + neighborRadius = 50, + maxForce = .1, + maxSpeed = 1, + separationWeight = 2, + alignmentWeight = 1, + cohesionWeight = 1, + desiredSeparation = 10; + + function boid(neighbors) { + var accel = flock(neighbors); + d3_ai_boidWrap(position); + velocity[0] += accel[0]; + velocity[1] += accel[1]; + if (gravityCenter) { + var g = d3_ai_boidGravity(gravityCenter, position, neighborRadius); + velocity[0] += g[0]; + velocity[1] += g[1]; + } + d3_ai_boidLimit(velocity, maxSpeed); + position[0] += velocity[0]; + position[1] += velocity[1]; + return position; + } + + function flock(neighbors) { + var separation = [0, 0], + alignment = [0, 0], + cohesion = [0, 0], + separationCount = 0, + alignmentCount = 0, + cohesionCount = 0, + i = -1, + l = neighbors.length; + while (++i < l) { + var n = neighbors[i]; + if (n === this) continue; + var npos = n.position(), + d = d3_ai_boidDistance(position, npos); + if (d > 0) { + if (d < desiredSeparation) { + var tmp = d3_ai_boidNormalize(d3_ai_boidSubtract(position.slice(), npos)); + separation[0] += tmp[0] / d; + separation[1] += tmp[1] / d; + separationCount++; + } + if (d < neighborRadius) { + var nvel = n.velocity(); + alignment[0] += nvel[0]; + alignment[1] += nvel[1]; + alignmentCount++; + cohesion[0] += npos[0]; + cohesion[1] += npos[1]; + cohesionCount++; + } + } + } + + if (separationCount > 0) { + separation[0] /= separationCount; + separation[1] /= separationCount; + } + + if (alignmentCount > 0) { + alignment[0] /= alignmentCount; + alignment[1] /= alignmentCount; + } + d3_ai_boidLimit(alignment, maxForce); + + if (cohesionCount > 0) { + cohesion[0] /= cohesionCount; + cohesion[1] /= cohesionCount; + } else { + cohesion = position.slice(); + } + cohesion = steerTo(cohesion); + + return [ + separation[0] * separationWeight + + alignment[0] * alignmentWeight + + cohesion[0] * cohesionWeight, + separation[1] * separationWeight + + alignment[1] * alignmentWeight + + cohesion[1] * cohesionWeight + ]; + } + + function steerTo(target) { + var desired = d3_ai_boidSubtract(target, position), + d = d3_ai_boidMagnitude(desired); + + if (d > 0) { + d3_ai_boidNormalize(desired); + + // Two options for desired vector magnitude (1 -- based on distance, 2 -- maxspeed) + var mul = maxSpeed * (d < 100 ? d / 100 : 1); + desired[0] *= mul; + desired[1] *= mul; + + // Steering = Desired minus Velocity + var steer = d3_ai_boidSubtract(desired, velocity); + d3_ai_boidLimit(steer, maxForce) // Limit to maximum steering force + } else { + steer = [0, 0]; + } + return steer; + } + + boid.position = function(x) { + if (!arguments.length) return position; + position = x; + return boid; + } + + boid.velocity = function(x) { + if (!arguments.length) return velocity; + velocity = x; + return boid; + } + + boid.gravityCenter = function(x) { + if (!arguments.length) return gravityCenter; + gravityCenter = x; + return boid; + } + + boid.neighborRadius = function(x) { + if (!arguments.length) return neighborRadius; + neighborRadius = x; + return boid; + } + + boid.maxForce = function(x) { + if (!arguments.length) return maxForce; + maxForce = x; + return boid; + } + + boid.maxSpeed = function(x) { + if (!arguments.length) return maxSpeed; + maxSpeed = x; + return boid; + } + + boid.separationWeight = function(x) { + if (!arguments.length) return separationWeight; + separationWeight = x; + return boid; + } + + boid.alignmentWeight = function(x) { + if (!arguments.length) return alignmentWeight; + alignmentWeight = x; + return boid; + } + + boid.cohesionWeight = function(x) { + if (!arguments.length) return cohesionWeight; + cohesionWeight = x; + return boid; + } + + boid.desiredSeparation = function(x) { + if (!arguments.length) return desiredSeparation; + desiredSeparation = x; + return boid; + } + + return boid; + } + + function d3_ai_boidNormalize(a) { + var m = d3_ai_boidMagnitude(a); + if (m > 0) { + a[0] /= m; + a[1] /= m; + } + return a; + } + + function d3_ai_boidWrap(position) { + if (position[0] > w) position[0] = 0; + else if (position[0] < 0) position[0] = w; + if (position[1] > h) position[1] = 0; + else if (position[1] < 0) position[1] = h; + } + + function d3_ai_boidGravity(center, position, neighborRadius) { + if (center[0] != null) { + var m = d3_ai_boidSubtract(center.slice(), position), + d = d3_ai_boidMagnitude(m) - 10; + if (d > 0 && d < neighborRadius * 5) { + d3_ai_boidNormalize(m); + m[0] /= d; + m[1] /= d; + return m; + } + } + return [0, 0]; + } + + function d3_ai_boidDistance(a, b) { + var dx = a[0] - b[0], + dy = a[1] - b[1]; + if (dx > w / 2) dx = w - dx; + if (dy > h / 2) dy = h - dy; + return Math.sqrt(dx * dx + dy * dy); + } + + function d3_ai_boidSubtract(a, b) { + a[0] -= b[0]; + a[1] -= b[1]; + return a; + } + + function d3_ai_boidMagnitude(v) { + return Math.sqrt(v[0] * v[0] + v[1] * v[1]); + } + + function d3_ai_boidLimit(a, max) { + if (d3_ai_boidMagnitude(a) > max) { + d3_ai_boidNormalize(a); + a[0] *= max; + a[1] *= max; + } + return a; + } + + return boid; +})();