topical media & game development

talk show tell print

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.