Created
March 26, 2014 16:28
-
-
Save anonymous/9787363 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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