topical media & game development
graphic-javascript-vector-boids.js / js
// javascript implementation of Boids
// Requires http://raphaeljs.com/raphael.js
// Ben Dowling - Oct 2009
// www.coderholic.com
boid
var Boid = function(x, y, size) {
this.x = x;
this.y = y;
this.xVelocity = 1;
this.yVelocity = -1;
this.circle = paper.circle(x, y, size).attr({fill: '#FF0000'});
}
prototype -- move
Boid.prototype.move = function() {
// RaphaĞl JS doesn't provide any way to GET the position once
// we've moved it so we'll need to manually track the position
this.x += this.xVelocity;
this.y += this.yVelocity;
var border = 5;
if(this.x <= border || this.x >= width - border) {
this.x -= this.xVelocity;
this.x = Math.max(this.x, border);
this.x = Math.min(this.x, width - border);
this.xVelocity = -this.xVelocity;
this.x += this.xVelocity;
}
if(this.y <= border || this.y >= height - border) {
this.y -= this.yVelocity;
this.y = Math.max(this.y, border);
this.y = Math.min(this.y, height - border);
this.yVelocity = -this.yVelocity;
this.y += this.yVelocity;
}
this.circle.translate(this.xVelocity, this.yVelocity);
}
distance
Boid.prototype.distance = function(boid) {
var distX = this.x - boid.x;
var distY = this.y - boid.y;
return Math.sqrt(distX * distX + distY * distY);
}
away
Boid.prototype.moveAway = function(boids, minDistance) {
var distanceX = 0;
var distanceY = 0;
var numClose = 0;
for(var i = 0; i < boids.length; i++) {
var boid = boids[i];
if(boid.x == this.x && boid.y == this.y) continue;
var distance = this.distance(boid)
if(distance < minDistance) {
numClose++;
var xdiff = (this.x - boid.x);
var ydiff = (this.y - boid.y);
if(xdiff >= 0) xdiff = Math.sqrt(minDistance) - xdiff;
else if(xdiff < 0) xdiff = -Math.sqrt(minDistance) - xdiff;
if(ydiff >= 0) ydiff = Math.sqrt(minDistance) - ydiff;
else if(ydiff < 0) ydiff = -Math.sqrt(minDistance) - ydiff;
distanceX += xdiff;
distanceY += ydiff;
boid = null;
}
}
if(numClose == 0) return;
this.xVelocity -= distanceX / 5;
this.yVelocity -= distanceY / 5;
}
closer
Boid.prototype.moveCloser = function(boids, distance) {
if(boids.length < 1) return
var avgX = 0;
var avgY = 0;
for(var i = 0; i < boids.length; i++) {
var boid = boids[i];
if(boid.x == this.x && boid.y == this.y) continue;
if(this.distance(boid) > distance) continue;
avgX += (this.x - boid.x);
avgY += (this.y - boid.y);
boid = null;
}
avgX /= boids.length;
avgY /= boids.length;
distance = Math.sqrt((avgX * avgX) + (avgY * avgY)) * -1.0
if(distance == 0) return;
this.xVelocity= Math.min(this.xVelocity + (avgX / distance) * 0.15, maxVelocity)
this.yVelocity = Math.min(this.yVelocity + (avgY / distance) * 0.15, maxVelocity)
}
with
Boid.prototype.moveWith = function(boids, distance) {
if(boids.length < 1) return
// calculate the average velocity of the other boids
var avgX = 0;
var avgY = 0;
for(var i = 0; i < boids.length; i++) {
var boid = boids[i];
if(boid.x == this.x && boid.y == this.y) continue;
if(this.distance(boid) > distance) continue;
avgX += boid.xVelocity;
avgY += boid.yVelocity;
boid = null;
}
avgX /= boids.length;
avgY /= boids.length;
distance = Math.sqrt((avgX * avgX) + (avgY * avgY)) * 1.0
if(distance == 0) return;
this.xVelocity= Math.min(this.xVelocity + (avgX / distance) * 0.05, maxVelocity)
this.yVelocity = Math.min(this.yVelocity + (avgY / distance) * 0.05, maxVelocity)
}
var random = function(maxNum) {
return Math.ceil(Math.random() * maxNum);
}
onload
// Insert all the boid stuff onto the page and
// set it all up
window.onload = function () {
width = 600;
height = 400;
var stop = false;
maxVelocity = 5;
var elem = document.getElementById("boids");
if(!elem) {
alert("Boids requires an element with id 'boids'");
return;
}
elem.innerHTML = "<div id='boids-canvas' style='margin: auto; background: white; width:" + width + "px; height:" + height + "px;'></div><p><button id='boids-stop'>Stop</button></p>";
paper = Raphael("boids-canvas", width, height);
var boids = [];
var numBoids = 20;
for(var i = 0; i < numBoids; i++) {
boids.push(new Boid(random(width), random(height), 5));
}
function moveBoids() {
for(var i = 0; i < numBoids; i++) {
boids[i].moveWith(boids, 300);
boids[i].moveCloser(boids, 300);
boids[i].moveAway(boids, 15);
}
for(var i = 0; i < numBoids; i++) {
boids[i].move();
}
if(!stop) setTimeout(arguments.callee, 50);
};
moveBoids();
var stopButton = document.getElementById('boids-stop');
stopButton.onclick = function() {
stop = !stop;
if(stop) stopButton.innerHTML = "Start";
else {
moveBoids();
stopButton.innerHTML = "Stop";
}
}
};
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.