Skip to content

Instantly share code, notes, and snippets.

Created March 26, 2014 16:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save anonymous/9787363 to your computer and use it in GitHub Desktop.
Save anonymous/9787363 to your computer and use it in GitHub Desktop.
<script language="JavaScript">
// BOID ANTS by Jonathon Crane
// Based on Pseudocode and great explanation by Conrad Parker.
// Find it at: /web/20020604171037/http://www.cse.unsw.edu.au/~conradp/boids/pseudocode.html
// DHTML library routines based on
// dhtmllib.js, Copyright 1999 by Mike Hall.
// Web address: /web/20020604171037/http://www.brainjar.com
// Updated here to work with NS6
// Version 01 got Ants to flock.
// Version 02 got Ants to flock and follow mouse.
// Version 03 I changed the parameters a bit, but I like 02 better.
// Version 04 got ants to go to center a bit more. Also they scatter
// from the mouse when you click it.
// Vars for this application
// Shorcuts to access the images of ants in different directions
var dir = "images/";
var directions = new Array(
"n", "ne", "e", "se", "s", "sw", "w", "nw"
);
function getAntImageFileName(antDirection) {
return (dir + "ant-" + antDirection + ".gif");
}
// Determine browser.
var origWidth; // Vars for NS resizing
var origHeight;
//-----------------------------------------------------------------------------
// Layer Visibility
//-----------------------------------------------------------------------------
function hideLayer(layer) {
layer.style.visibility = "hidden";
}
function showLayer(layer) {
layer.style.visibility = "visible";
}
//-----------------------------------------------------------------------------
// Layer positioning
//-----------------------------------------------------------------------------
function moveLayerTo(layer, x, y) {
layer.style.left = x + "px";
layer.style.top = y + "px";
}
function moveLayerBy(layer, dx, dy) {
var nx = parseInt(layer.style.left) + dx;
var ny = parseInt(layer.style.top) + dy;
layer.style.left = nx + "px";
layer.style.top = ny + "px";
}
function getLeft(layer) {
return(parseInt(layer.style.left));
}
function getTop(layer) {
return(parseInt(layer.style.top));
}
function getWidth(layer) {
return layer.offsetWidth;
}
function getHeight(layer) {
return layer.offsetHeight;
}
//-----------------------------------------------------------------------------
// Layer utilities.
//-----------------------------------------------------------------------------
function getLayer(name) {
return document.getElementById(name);
}
function findLayer(name, doc) {
var i, layer;
for (i = 0; i < doc.layers.length; i++) {
layer = doc.layers[i];
if (layer.name == name)
return layer;
if (layer.document.layers.length > 0)
if ((layer = findLayer(name, layer.document)) != null)
return layer;
}
return null;
}
//-----------------------------------------------------------------------------
// Window and page properties.
//-----------------------------------------------------------------------------
function getWindowWidth() {
return window.innerWidth;
}
function getWindowHeight() {
return window.innerHeight;
}
function getPageScrollX() {
return window.pageXOffset;
}
function getPageScrollY() {
return window.pageYOffset;
}
//-----------------------------------------------------------------------------
// Event Handling
//-----------------------------------------------------------------------------
// These variables will hold the current mouse pointer position.
var mouseX = 0;
var mouseY = 0;
// Set up event capturing.
document.onmousemove = getMousePosition;
document.onclick = onMouseClick;
function getMousePosition(e) {
mouseX = e.clientX;
mouseY = e.clientY;
return true;
}
function onMouseClick(e) {
// When you click the mouse, make the ants scatter away
gRule1Scale = -10;
return true;
}
function handleResize() {
// First check if the window size has changed. If it hasn't we
// want to leave it alone.
if (innerWidth==origWidth && innerHeight==origHeight) return false;
// If the size has changed, want to reload so things get laid out in
// a nice manner.
location.reload();
return false;
}
//-----------------------------------------------------------------------------
// Real process code
//-----------------------------------------------------------------------------
var ants = new Array();
function initAnts() {
var i, layer;
// Get handles to all the ant layers.
i = 0;
while ((layer = getLayer("ant" + (i + 1))) != null) {
ants[i] = layer;
ants[i].image = document.images["antImg" + (i + 1)];
// Set initial position
setAnt(i);
// Set initial velocity
ants[i].velocityX = 0;
ants[i].velocityY = 0;
showLayer(ants[i]);
i++;
}
updateAnts();
}
function setAnt(n) {
var s, x, y;
// Randomly place an ant on the window.
x = Math.floor(Math.random() * getWindowWidth());
y = Math.floor(Math.random() * getWindowHeight());
s = Math.floor(Math.random() * 4);
if (s == 0)
x = -getWidth(ants[n]);
if (s == 1)
x = getWindowWidth();
if (s == 2)
y = -getHeight(ants[n]);
if (s == 3)
y = getWindowHeight();
x += getPageScrollX();
y += getPageScrollY();
moveLayerTo(ants[n], x, y);
}
function showAntDir(antNum, dirName) {
// Just need to change the image of this ant
ants[antNum].image.src = getAntImageFileName(dirName);
}
function rule1b() {
// Calculate the center of mass of the flock
// First I'm just going to set the status bar to the
// center of mass.
var i;
var sumX = 0;
var sumY = 0;
var cX, cY;
for (i = 0; i < ants.length; i++) {
sumX += getLeft(ants[i]);
sumY += getTop(ants[i]);
}
cX = sumX / ants.length;
cY = sumY / ants.length;
// This status bar write will probably only work on IE5
console.log("Center: (" + cX + ", " + cY + ") Mouse: (" + mouseX + ", " + mouseY + ")")
}
var gRule1Scale = 1.0;
function rule1(thisAnt) {
// Calculate the percieved center of the flock from
// the perspective of thisAnt.
var i, cX, cY, dx, dy;
var sumX = 0;
var sumY = 0;
// Go through each and and add the mass except thisAnt
for (i = 0; i < ants.length; i++) {
if (i != thisAnt) {
sumX += getLeft(ants[i]);
sumY += getTop(ants[i]);
}
}
// Take the mean
cX = sumX / (ants.length - 1);
cY = sumY / (ants.length - 1);
// Now calculate the offset to move the ant closer
dx = (cX - getLeft(ants[thisAnt]))/3;
dy = (cY - getTop(ants[thisAnt]))/3;
// Add this velocity on
ants[thisAnt].velocityX += dx;
ants[thisAnt].velocityY += dy;
// This status bar write will probably only work on IE5
//window.status = "Dist from center: (" + dx + ", " + dy + ") Mouse: (" + mouseX + ", " + mouseY + ")"
}
var sx, sy;
function rule2(thisAnt) {
// Keep this ant away from other ants
var i;
var sX = 0;
var sY = 0;
for (i = 0; i < ants.length; i++) {
if (i != thisAnt) {
if (tooClose(thisAnt, i)) {
sX = sX + sx;
sY = sY + sy;
//window.status = sX + " too close to " + sY;
}
}
}
// Add this velocity on
ants[thisAnt].velocityX += sX;
ants[thisAnt].velocityY += sY;
//if (thisAnt == 3) {
// sLength = Math.sqrt(sX*sX + sY*sX);
// window.status = sLength + " (" + sX + ", " + sY + ")";
//}
}
function tooClose(ant1, ant2) {
// Will return true if ant1 is too close to ant2
var dx, dy, d, ux, uy, scale;
dx = getLeft(ants[ant1]) - getLeft(ants[ant2]);
dy = getTop(ants[ant1]) - getTop(ants[ant2]);
// Get hypotenuse of triangle
d = Math.sqrt(dx*dx + dy*dx);
if ((d < 100) && (d != 0)) {
// Scale the vector
ux = dx/d;
uy = dy/d;
scale = -(10/100)*d + 10;
//scale = 1;
sx = scale*ux;
sy = scale*uy;
return true;
} else {
return false;
}
}
function rule3(thisAnt) {
// Match velocity with other nearby ants
//window.status = ants[thisAnt].velocityY;
// Calculate the percieved velocity of the flock
var i, cX, cY, dx, dy;
var sumX = 0;
var sumY = 0;
// Go through each and and add the velocity except thisAnt
for (i = 0; i < ants.length; i++) {
if (i != thisAnt) {
sumX += ants[i].velocityX;
sumY += ants[i].velocityY;
}
}
// Take the mean
cX = sumX / (ants.length - 1);
cY = sumY / (ants.length - 1);
// Now calculate the offset to move the ant closer
dx = (cX - ants[thisAnt].velocityX)/8;
dy = (cY - ants[thisAnt].velocityY)/8;
// Add this velocity on
ants[thisAnt].velocityX += dx;
ants[thisAnt].velocityY += dy;
}
function limitVelocity(thisAnt) {
// put the velocity within bounds so that ants
// never go to fast
var vx, vy, magV;
var cVelocityLimit = 20; // Ants don't move more than this many pixels in a time step
vx = ants[thisAnt].velocityX;
vy = ants[thisAnt].velocityY;
magV = Math.sqrt(vx*vx + vy*vy);
if (magV > cVelocityLimit) {
ants[thisAnt].velocityX = (vx / magV) * cVelocityLimit;
ants[thisAnt].velocityY = (vy / magV) * cVelocityLimit;
}
}
function tendToMouse(thisAnt) {
// Guide the ant toward the current mouse pointer
var dx, dy;
// Calculate the offset to move the ant closer
dx = (mouseX - getLeft(ants[thisAnt]))/10;
dy = (mouseY - getTop(ants[thisAnt]))/10;
// Scale this vector by a value determined by the mouse click
// before adding it.
dx = gRule1Scale * dx;
dy = gRule1Scale * dy;
// Add this velocity on
ants[thisAnt].velocityX += dx;
ants[thisAnt].velocityY += dy;
}
function boundPosition(thisAnt) {
var xMax = getWindowWidth;
var xMin = 0;
var yMax = getWindowHeight;
var yMin = 0;
var boundFactor = 10;
ax = getLeft(ants[thisAnt]);
ay = getTop(ants[thisAnt]);
vx = ants[thisAnt].velocityX;
vy = ants[thisAnt].velocityY;
// Bound X
if (ax < xMin) {
vx = vx + boundFactor;
} else if (ax > xMax) {
vx = vx - boundFactor;
}
// Bound Y
if (ay < yMin) {
vy = vy + boundFactor;
} else if (ay > yMax) {
vy = vy - boundFactor;
}
// Assign velocites
ants[thisAnt].velocityX = vx;
ants[thisAnt].velocityY = vy;
}
function updateAnts() {
var i, dx, dy, theta, d;
// Move each ant toward the mouse pointer, if she hits it, drop her back onto
// the page randomly.
d = 3;
// If the rule1 scale is anything other than 1.0, gradually scale
// it back so it is.
if (gRule1Scale > 1)
gRule1Scale = gRule1Scale - 0.1;
else if (gRule1Scale < 1)
gRule1Scale = gRule1Scale + (1 - gRule1Scale)*0.2;
for (i = 0; i < ants.length; i++) {
// Apply the flocking rules to determine velocity
rule1(i); // Go towards center of flock
rule2(i); // Don't hit other birds
rule3(i); // Match velocity.
tendToMouse(i); // Steer the ants toward the mouse pointer
boundPosition(i); // Keep em locked in
limitVelocity(i);
// The ants move one step of the velocity per time step.
dx = ants[i].velocityX ;
dy = ants[i].velocityY;
// Move the ant by this amount
moveLayerBy(ants[i], dx, dy);
// Find the angle between the ant and the pointer.
//dx = mouseX - getLeft(ants[i]);
//dy = mouseY - getTop(ants[i]);
theta = Math.round(Math.atan2(-dy, dx) * 180 / Math.PI);
if (theta < 0)
theta += 360;
// Hit the pointer?
//if (Math.abs(dx) < d && Math.abs(dy) < d)
// setAnt(i);
// Calculate the displacement from velocity
if (false)
window.status = "hey";
// If not, move the ant and set the image based on angle.
else if (theta > 23 && theta <= 68) {
showAntDir(i, "ne");
}
else if (theta > 68 && theta <= 113) {
showAntDir(i, "n");
}
else if (theta > 113 && theta <= 158) {
showAntDir(i, "nw");
}
else if (theta > 158 && theta <= 203) {
showAntDir(i, "w");
}
else if (theta > 203 && theta <= 248) {
showAntDir(i, "sw");
}
else if (theta > 248 && theta <= 293) {
showAntDir(i, "s");
}
else if (theta > 293 && theta <= 338) {
showAntDir(i, "se");
}
else {
showAntDir(i, "e");
}
}
// Debug rule1
//rule3(3);
s1 = "Velocity: (" + ants[3].velocityX + ", " + ants[3].velocityY + ") ";
s2 = "Position: (" + getLeft(ants[3]) + ", " + getTop(ants[3]) + ") ";
vx = ants[3].velocityX;
vy = ants[3].velocityY;
s3 = "MagVel: " + Math.sqrt((vx*vx) + (vy*vy));
//window.status = s1 + s2 + s3;
// Set up next call.
setTimeout('updateAnts()', 50);
return;
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment