block by erikhazzard ce1b42b9393abafbc20183b79defebfa

voronoi+boids-combined-delaunay-3

Full Screen

Built with blockbuilder.org

forked from erikhazzard‘s block: voronoi+boids-1-grey-transparent

forked from erikhazzard‘s block: voronoi+boids-2-grey-transparent

forked from erikhazzard‘s block: voronoi+boids-4-few-points

forked from erikhazzard‘s block: voronoi+boids-delaunay-1

forked from erikhazzard‘s block: voronoi+boids-delaunay-1

forked from erikhazzard‘s block: voronoi+boids-delaunay-2-mesh

index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <link href="https://fonts.googleapis.com/css?family=Inconsolata|Prociono" rel="stylesheet">
    <title>Voronoi Boids</title>
    <link rel="stylesheet" href="style.css" />
</head>
<body>
<div id='viz-wrapper'>
    <canvas id='canvas' width="1400" height="700"></canvas>
    <svg id='viz'> </svg>
</div>

<script src='d3-topo-combined.js'></script>

<script src='entity-vector.js'></script>

<script>

canvas = document.getElementById('canvas');
context = canvas.getContext('2d');

//width / height
var width = canvas.width;
var height = canvas.height;


var rows = 60;
var columns = 60;
var size = 10;

var GAME = {};
window.GAME = GAME;
GAME.entities = new Entities();
GAME.grid = {};
GAME.resolution = size;

var image = new Image();


//============================================================================
//
//cell
//
//============================================================================
var Cell = function(params){
    var i = 0;
    var param = null;
    this.previous = 0;
    for(i in params){ if (params.hasOwnProperty(i)) {
        param = params[i];
        this[i] = param;
    }}
};
Cell.prototype.draw = function(){
    context.save();

    context.fillStyle = this.color;
    if(this.state < 1){
        this.color = 'rgba(0,0,0,0)';
    } else {
        this.color = 'rgba(100,150,200,0.4)';
    }
    if(this.previous === 1 && this.state === 0){
        this.color = 'rgba(200,50,50,0.4)';
    }
    else if(this.previous === 0 && this.state === 1){
        this.color = 'rgba(50,200,50,0.4)';
    }
    context.fillRect(
        this.i * size,
        this.j * size,
        size,
        size
    );
    context.restore();
};
Cell.prototype.getNeighbors = function(){
    //me
    var neighbors = [];
    var i=0;
    var j=0;
    var targetI = 0;
    var targetJ = 0;

    for(i=-1;i<=1;i++){
        for(j=-1;j<=1;j++){
            targetI = this.i + i;
            targetJ = this.j + j;
            
            if(targetI >= 0 && targetI <= rows
                && targetJ >= 0 && targetJ <= columns
                && GAME.grid[targetI] && GAME.grid[targetI][targetJ]
            ){
                neighbors.push( GAME.grid[targetI][targetJ] );
            }
        }
    }
    
    return neighbors;
}
Cell.prototype.getState = function(){
    //get neighbors
    var neighbors = this.getNeighbors();
    var cell = new Cell({
        i: this.i,
        j: this.j,
        state: this.state,
        color: this.color
    });
    var i=0;
    var state = this.state;
    var num = 0;
    for(i=neighbors.length-1;i>=0;i--){
        if( neighbors[i] && this !== neighbors[i] && neighbors[i].state > 0){
            num += 1; 
        }
    }
    
    if(this.state === 1 && num < 2){
        cell.state = 0;
    }else if(this.state === 1 && num > 3){
        cell.state = 0;
    }else if(this.state === 0 && num === 3){
        cell.state = 1;
    }
    
    return cell;
};

/**
 * Setup cells
 */
var i=j=0;
for(i=rows-1;i>=0;i--){
    GAME.grid[i] = [];
    for(j=rows-1;j>=0;j--){
        GAME.grid[i][j] = new Cell({ 
            i: i, 
            j: j,
            color: 'rgba(100,150,200,1)',
            state: 0
        });
    }
};

var updateGrid = function(){
    var nextGrid = {};
    var color = null;
    var cell = null;
    var neighbors = null;
    var i=0;
    var j=0;
    //Draw grid
    for(i=rows-1;i>=0;i--){
        nextGrid[i] = []
        for(j=rows-1;j>=0;j--){
            cell = GAME.grid[i][j];
            cell.draw();
            newCell = cell.getState();
            nextGrid[i][j] = newCell;
            nextGrid[i][j].previous = cell.state;
        }
    }
    GAME.grid = nextGrid;
}


/**
 * Entities
 */

//create some entities
var numEntities = 20;
for(var i=0;i<numEntities; i++){
    GAME.entities.add( new Entity({
        position: new Vector(
            Math.random() * width | 0,
            Math.random() * height | 0
        ),
        velcoity: new Vector(
            Math.random() * 20 | 0,
            Math.random() * 20 | 0
        ),
        maxSpeed: 7,
        mass: 10
    }));
}

GAME.entitiesStatic = new Entities();
var numStaticEntities = 10;
for(var i=0;i<numStaticEntities; i++){
    GAME.entitiesStatic.add(
        new Entity({ 
            position: new Vector(width/2, height/2), 
            mass: 40,
            maxSpeed: 9
        })
    );
}

GAME.util = {};
GAME.mouse = new Vector(0,0);
canvas.addEventListener('mousemove', function(e){
    GAME.mouse = new Vector( e.clientX, e.clientY );
});


var TOTAL_ENTITIES = numEntities + numStaticEntities;












var canvas = document.querySelector("canvas"),
    width = canvas.width,
    height = canvas.height,
    context = canvas.getContext("2d"),
    voro = d3.voronoi().extent([[1, 1], [width - 1, height - 1]]);
    
var voronoi = d3.voronoi()
    .extent([[-1, -1], [width + 1, height + 1]]);


var n = TOTAL_ENTITIES,
    particles = new Array(n),
    fill = d3.interpolate(
        d3.cubehelix(80, 1.50, 0.8), 
        d3.cubehelix(-100, 0.75, 0.35)
    ),
    radius = 10;

fill = function () { return 'rgba(100, 150, 200, 1)'; }

for (var i = 0; i < n; ++i) particles[i] = {0: Math.random() * width, 1: Math.random() * height, vx: 0, vy: 0};
for (var i = 0; i <= width; i += 80) particles.push([i, 0], [i, height]);
for (var i = 0; i <= height; i += 80) particles.push([0, i], [width, i]);

d3.timer(function(elapsed) {
    var i=0,j=0,
        entity = null, attraction=null,
        steer = steerLine = desired = null;
    var statics = GAME.entitiesStatic.entities;
    var entities = GAME.entities.entities;

    //GAME.grid = [];

    //Draw entities
    for(i in entities){
        entity = entities[i];

        ////seek the mouse
        //if(Math.random() < 0.7){
            //entity.applyForce(
                //entity.seekForce(GAME.mouse).multiply(.5)
            //);
        //}

        //Separate this entity from nearby entities
        entity.applyForce(
            entity.separate(entities).multiply(0.3)
        );
        
        ////FLOCK
        entity.flock(entities);
        
        //Random walking
        entity.applyForce(
            entity.walkForce().multiply(1)
        );

        for(j in statics){
            entity.applyForce(
                entity.seekForce(statics[j].position, false, 85).multiply(-4)
            );
        }
        //entity.separate(statics)
        
        //setup grid
        column = parseInt(parseInt(entity.position.x,10) / GAME.resolution);
        row = parseInt(parseInt(entity.position.y,10) / GAME.resolution);
        
        if( !GAME.grid[column] ){
            GAME.grid[column] = [];
        } 
        if( !GAME.grid[column][row] ){
            GAME.grid[column][row] = [];
        }
        //add to grid
        GAME.grid[column][row].state = 1;
        entity.update();
    }

    ////STATIC entities
    for(i in GAME.entitiesStatic.entities){
        entity = GAME.entitiesStatic.entities[i];

        column = parseInt(parseInt(entity.position.x,10) / GAME.resolution);
        row = parseInt(parseInt(entity.position.y,10) / GAME.resolution);
        if(GAME.grid[column] && GAME.grid[column][row]){
            GAME.grid[column][row].state = 0;
        }
        entity.applyForce(
            entity.walkForce().multiply(1)
        );

        entity.applyForce(
            entity.separate(GAME.entitiesStatic.entities).multiply(1.2)
        );

        // follow mouse
        entity.applyForce(
            entity.seekForce(GAME.mouse).multiply(1.1)
        );

        entity.update();
    }


    let entitiesToIterate = entities.concat(GAME.entitiesStatic.entities);
    window.z = entitiesToIterate;


  for (i = 0; i < n; ++i) {
    var p = particles[i];
    p[0] += p.vx; if (p[0] < 0) p[0] += width; else if (p[0] > width) p[0] -= width;
    p[1] += p.vy; if (p[1] < 0) p[1] += height; else if (p[1] > height) p[1] -= height;
    p.vx += 0.1 * (Math.random() - .5) - 0.01 * p.vx;
    p.vy += 0.1 * (Math.random() - .5) - 0.01 * p.vy;
    
        // use entity positions
        p[0] = entitiesToIterate[i].position.x;
        p[1] = entitiesToIterate[i].position.y;
  }


    /**
    * Draw
    */
    context.clearRect(0, 0, width, height);

context.globalAlpha = 0.1;
    /* Draw triangles */
    var triangles = voro.triangles(particles),
    links = voro.links(particles);

    /** draw it */
    triangles.forEach(function(t) {
        context.beginPath();
        var r = drawRoundedTriangle(t[0][0], t[0][1], t[1][0], t[1][1], t[2][0], t[2][1], radius);
        context.fillStyle = fill(Math.sqrt(r / 50));
        context.fill();
    });
    
    context.beginPath();
    triangles.forEach(function(t) {
        drawRoundedTriangle(t[0][0], t[0][1], t[1][0], t[1][1], t[2][0], t[2][1], radius);
    });
    context.lineWidth = 5;
    context.strokeStyle = "#ddd";
    context.stroke();

    /** Draw links */
    //context.beginPath();
    //links.forEach(function(l) {
        //drawLink(l.source[0], l.source[1], l.target[0], l.target[1]);
    //});
    //context.lineWidth = 1;
    //context.strokeStyle = "#aaa";
    //context.stroke();

    /** Draw dots */
    //context.beginPath();
    //particles.forEach(function(p) { drawParticle(p[0], p[1]); });
    //context.fillStyle = "#000";
    //context.fill();




    

context.globalAlpha = 0.8;


    // voronoi draw
    var topology = computeTopology(voronoi(particles));
    /** Draw topology */
    context.beginPath();
    renderMultiPolygon(context, topojson.merge(topology, topology.objects.voronoi.geometries.filter(function(d, i) { return i & 1; })));
    context.fillStyle = "rgba(100,150,200,0.1)";
    context.fill();
    context.lineWidth = 1.5;
    context.lineJoin = "round";
    context.strokeStyle = "rgba(50,100,150,1)";
    context.stroke();
});





function drawParticle(x, y) {
  context.moveTo(x + 2, y);
  context.arc(x, y, 2, 0, 2 * Math.PI);
}

function drawTriangle(x0, y0, x1, y1, x2, y2) {
  context.moveTo(x0, y0);
  context.lineTo(x1, y1);
  context.lineTo(x2, y2);
  context.closePath();
}

function drawLink(x0, y0, x1, y1) {
  context.moveTo(x0, y0);
  context.lineTo(x1, y1);
}

function drawRoundedTriangle(x0, y0, x1, y1, x2, y2, r) {
  var circle = inscribeTriangle(x0, y0, x1, y1, x2, y2);
  if (circle.radius <= r) {
    context.moveTo(circle[0] + circle.radius, circle[1]);
    context.arc(circle[0], circle[1], circle.radius, 0, 2 * Math.PI);
  } else {
    var p0 = closestPoint(circle[0], circle[1], x0, y0, x1, y1),
        p1 = closestPoint(circle[0], circle[1], x1, y1, x2, y2),
        p2 = closestPoint(circle[0], circle[1], x2, y2, x0, y0);
    context.moveTo(p0[0], p0[1]);
    context.arcTo(x1, y1, p1[0], p1[1], r);
    context.arcTo(x2, y2, p2[0], p2[1], r);
    context.arcTo(x0, y0, p0[0], p0[1], r);
    context.closePath();
  }
  return circle.radius;
}

function closestPoint(x2, y2, x0, y0, x1, y1) {
  var x10 = x1 - x0, y10 = y1 - y0,
      x20 = x2 - x0, y20 = y2 - y0,
      t = (x20 * x10 + y20 * y10) / (x10 * x10 + y10 * y10);
  return [x0 + t * x10, y0 + t * y10];
}

function inscribeTriangle(x0, y0, x1, y1, x2, y2) {
  var x01 = x0 - x1, y01 = y0 - y1,
      x02 = x0 - x2, y02 = y0 - y2,
      x12 = x1 - x2, y12 = y1 - y2,
      l01 = Math.sqrt(x01 * x01 + y01 * y01),
      l02 = Math.sqrt(x02 * x02 + y02 * y02),
      l12 = Math.sqrt(x12 * x12 + y12 * y12),
      k0 = l01 / (l01 + l02),
      k1 = l12 / (l12 + l01),
      center = segmentIntersection(x0, y0, x1 - k0 * x12, y1 - k0 * y12, x1, y1, x2 + k1 * x02, y2 + k1 * y02);
  center.radius = Math.sqrt((l02 + l12 - l01) * (l12 + l01 - l02) * (l01 + l02 - l12) / (l01 + l02 + l12)) / 2;
  return center;
}

function segmentIntersection(x0, y0, x1, y1, x2, y2, x3, y3) {
  var x02 = x0 - x2, y02 = y0 - y2,
      x10 = x1 - x0, y10 = y1 - y0,
      x32 = x3 - x2, y32 = y3 - y2,
      t = (x32 * y02 - y32 * x02) / (y32 * x10 - x32 * y10);
  return [x0 + t * x10, y0 + t * y10];
}


function computeTopology(diagram) {
    var cells = diagram.cells,
            arcs = [],
            arcIndex = -1,
            arcIndexByEdge = {};

    return {
        objects: {
            voronoi: {
                type: "GeometryCollection",
                geometries: cells.map(function(cell) {
                    var cell,
                            site = cell.site,
                            halfedges = cell.halfedges,
                            cellArcs = [],
                            clipArc;

                    halfedges.forEach(function(halfedge) {
                        var edge = diagram.edges[halfedge];
                        if (edge.right) {
                            var l = edge.left.index,
                                    r = edge.right.index,
                                    k = l + "," + r,
                                    i = arcIndexByEdge[k];
                            if (i == null) arcs[i = arcIndexByEdge[k] = ++arcIndex] = edge;
                            cellArcs.push(site === edge.left ? i : ~i);
                            clipArc = null;
                        } else if (clipArc) { // Coalesce border edges.
                            if (edge.left) edge = edge.slice(); // Copy-on-write.
                            clipArc.push(edge[1]);
                        } else {
                            arcs[++arcIndex] = clipArc = edge;
                            cellArcs.push(arcIndex);
                        }
                    });

                    // Ensure the last point in the polygon is identical to the first point.
                    var firstArcIndex = cellArcs[0],
                            lastArcIndex = cellArcs[cellArcs.length - 1],
                            firstArc = arcs[firstArcIndex < 0 ? ~firstArcIndex : firstArcIndex],
                            lastArc = arcs[lastArcIndex < 0 ? ~lastArcIndex : lastArcIndex];
                    lastArc[lastArcIndex < 0 ? 0 : lastArc.length - 1] = firstArc[firstArcIndex < 0 ? firstArc.length - 1 : 0].slice();

                    return {
                        type: "Polygon",
                        data: site.data,
                        arcs: [cellArcs]
                    };
                })
            }
        },
        arcs: arcs
    };
}

function renderMultiLineString(context, line) {
    line.coordinates.forEach(function(line) {
        line.forEach(function(point, i) {
            if (i) context.lineTo(point[0], point[1]);
            else context.moveTo(point[0], point[1]);
        });
    });
}

function renderMultiPolygon(context, polygon) {
    polygon.coordinates.forEach(function(polygon) {
        polygon.forEach(function(ring) {
            ring.forEach(function(point, i) {
                if (i) context.lineTo(point[0], point[1]);
                else context.moveTo(point[0], point[1]);
            });
        });
    });
}

</script>
</body>
</html>

entity-vector.js

//============================================================================
//
//Entity Class - Definition for Entity
//
//============================================================================
var Entity = function(params){
    //Setup the entity
    params = params || {};
    this.position = params.position || new Vector(
        Math.random() * 200 | 0,
        Math.random() * 200 | 0
    );
    this.velocity = params.velocity || new Vector(0,0);
    this.canBirth = true;
    this.acceleration = params.acceleration || new Vector(0,0);
    this.color = params.color || 'rgba(' 
        + (Math.random() * 255 | 0)
        + ',' 
        + (Math.random() * 255 | 0)
        + ','
        + (Math.random() * 255 | 0)
        + ','
        + '1)';
    this.health = params.health || 80;
    this.mass = params.mass || (Math.random() * 20 | 0) + 5;
    this.maxSpeed = params.maxSpeed || 8;
    this.maxForce = params.maxForce || .5;
    

    this.ruleAlign = Math.random() * 2;
    this.ruleCohesion = Math.random() * 2;
    this.ruleSeparate = Math.random() * 2;

    return this;
};
Entity.prototype.update = function(){
    //Adds the velocity to the location
    this.velocity.add(this.acceleration);
    this.velocity.limit(this.maxSpeed);

    this.position.add(this.velocity);
    this.checkEdges();

    //reset acceleration
    this.acceleration.multiply(0);
};

//Utility functions
Entity.prototype.draw = function(){
    context.save();
    context.fillStyle = this.color;
    context.fillRect(
        this.position.x - (this.mass / 2),
        this.position.y - (this.mass / 2),
        this.mass,
        this.mass
    );
    //context.beginPath();
    //context.arc(
        //(this.position.x - (this.mass / 4)) | 0,
        //(this.position.y - (this.mass / 4)) | 0,
        //this.mass / 2, 
        //0,
        //Math.PI * 2
    //)
    //context.closePath();
    //context.fill();
    context.restore();
}


Entity.prototype.applyForce = function(force){
    //Add the passed in force to the acceleration
    this.acceleration.add(
        force.copy()//.divide(this.mass)
    );
};
Entity.prototype.checkEdges = function(){
    //Wrap around 
    if(this.position.x >= width){
        this.position.x = this.position.x % (width);
    } else if (this.position.x < 0){
        this.position.x = width - 1;
    }
    if(this.position.y >= height){
        this.position.y = this.position.y % (height);
    } else if (this.position.y < 0){
        this.position.y = height - 1;
    }

    //Bounce off walls
    /*
    if(this.position.x > width){
        this.position.x = width;
        this.velocity.x *= -1;
    } else if(this.position.x < 0){
        this.velocity.x *= -1;
        this.position.x = 0;
    }

    if (this.position.y > height){
        this.velocity.y *= -1;
        this.position.y = height;
    }
    if (this.position.y < 0){
        this.velocity.y *= -1;
        this.position.y = 0;
    }
    */
};

//---------------------------------------
//Behaviors - Desired forces
//---------------------------------------
//Calculate steering force towards a target
Entity.prototype.seekForce = function(target, flee, maxForceDistance){
    //How far to check for neighbors in
    var maxDistance = 100;
    
    //check if the passed in object has a position property
    try{
        if(target.position){
            target = target.position;
        }
    }catch(err){}

    //seek a target
    var desiredVelocity = Vector.prototype.subtract(
        target,
        this.position);

    ////Simple - no arriving behavior
    //desiredVelocity.normalize();
    
    
    //-----------------------------------
    //get distance threshold
    //-----------------------------------
    if(maxForceDistance){
        curDistance = this.position.distance(target)
        //Make sure entity is within range of other entities
        if(curDistance <= 0 || curDistance > maxForceDistance){ return new Vector(0,0); }
    }

    //-----------------------------------
    //Arriving behavior - slow down on approach if within radius
    //-----------------------------------
    var distance = desiredVelocity.magnitude();
    var magnitude = 0;

    var scale = d3.scaleLinear()
        .domain([0, maxDistance])
        .range([0, this.maxSpeed])

    //val to map, current range min and max, then output range
    if(distance < maxDistance){
        var magnitude = scale(distance);
        desiredVelocity.multiply(magnitude);
    }else{
        //outside of radius, so go max speed towards it
        desiredVelocity.multiply(this.maxSpeed);
    }

    //steer force
    var steer = Vector.prototype.subtract(
        desiredVelocity,
        this.velocity);

    //draw the line (optional)
    //-----------------------------------
    var steerLine = Vector.prototype.add(this.position, steer);
    //GAME.util.drawLine(this.position, steerLine);
    //GAME.util.drawLine(
        //this.position, 
        //Vector.prototype.add(this.position, this.velocity)
    //);

    //limit steer amount
    //-----------------------------------
    steer.limit(this.maxForce);

    if(flee){ steer.multiply(-1); }
    
    return steer;
};

Entity.prototype.cosLookup = {};
Entity.prototype.sinLookup = {};
Entity.prototype.walkForce = function(futureDistance, radius){
    //Pick a spot based on current velocity, then randomly
    //  pick a spot at radius r at a random angle. This is
    //  the new target
    futureDistance = futureDistance || 40;
    radius = radius || 30;

    var futurePosition = this.velocity.copy();
    futurePosition.normalize();

    //If entity is NOT already moving, make it move
    if(futurePosition.magnitude() < 0.1){
        //Random position
        futurePosition.add(
            new Vector(
                (Math.random() * 3 | 0) - 1 || 1,
                (Math.random() * 3 | 0) - 1 || 1
            )
        );
    }
    
    //set the length of the vector
    futurePosition.multiply(futureDistance);

    //get a random position
    var scale = d3.scaleLinear()
        .domain([0,1])
        .range([0,360])

    var randomAngle = Math.random() * 361 | 0;
    
    //Lookup table (nasty - todo make this better);
    var cosLookup = Entity.prototype.cosLookup;
    if( !cosLookup[randomAngle] ){
        cosLookup[randomAngle] = Math.cos(randomAngle);
    }
    var cos = cosLookup[randomAngle];
        
    var sinLookup = Entity.prototype.sinLookup;
    if( !sinLookup[randomAngle] ){
        sinLookup[randomAngle] = Math.sin(randomAngle);
    }
    var sin = sinLookup[randomAngle];
    
    var x = radius * cos;
    var y = radius * sin;

    //now we got the target
    var target = new Vector(x,y);

    //Add the target location to the entity's position
    target.add(this.position);

    //we have target now, so seek it
    var force = this.seekForce(target);
    
    return force;
};

//Follow flow field
Entity.prototype.flowForce = function(){
    var desired = field.lookup(this.position.x, this.position.y);
    desired.multiply(this.maxSpeed);

    var steer = Vector.prototype.subtract( desired, this.velocity );
    steer.limit(this.maxForce);
    this.applyForce(steer);
};

//---------------------------------------
//Behaviors - Desired forces
//---------------------------------------
Entity.prototype.separate = function(entities){
    //Apply a separation force between this entity and a list of
    //  passed in entities.  If the distance is greater than some
    //  value, don't apply the force
    var separationDistance = this.mass;
    var targetEntity = null;
    var curDistance = 0;
    var diffVector = null;
    var sumVector = new Vector(0,0);
    var count = 0;
    var steer = new Vector(0,0);

    for(entity in entities){
        targetEntity = entities[entity];
        curDistance = this.position.distance(targetEntity.position)

        //Make sure entity is within range of other entities
        if(curDistance > 0 && curDistance < separationDistance && targetEntity !== this){
            //get vector which points away (get a new vector)
            diffVector = Vector.prototype.subtract(this.position, targetEntity.position);
            diffVector.normalize();
            diffVector.divide(curDistance);
            //closer it is, further it should flee
            sumVector.add(diffVector);
            count += 1;
        }
    }

    //divide to get average
    if(count > 0){ 
        sumVector.divide(count); 
        sumVector.normalize();
        sumVector.multiply(this.maxSpeed);

        steer = Vector.prototype.subtract(sumVector, this.velocity);
        steer.limit(this.maxSpeed);
        //lower the force even more
        //steer.divide(4);
    }
    return steer;
};

Entity.prototype.align = function(entities){
    var distance = 40;
    var sum = new Vector(0,0);
    var i=0, entity=null;
    var curDistance = 0;
    var count = 0;

    for(i in entities){
        entity = entities[i];
        
        curDistance = this.position.distance(entity.position);
        if(curDistance <= distance){
            sum.add(entity.velocity);
            count += 1;
        }
    }
    var steer = new Vector(0,0);
    
    if(count > 0){
        sum.divide(count);
        sum.normalize();
        sum.multiply(this.maxSpeed);
        steer = Vector.prototype.subtract(sum, this.velocity);
        steer.limit(this.maxForce);
    }
    
    return steer;
};

Entity.prototype.cohesion = function(entities){
    var sum = new Vector(0,0);
    var i=0, entity=null;
    var distance = 40;
    var curDistance = 0;
    var count = 0;

    for(i in entities){
        entity = entities[i];
        
        curDistance = this.position.distance(entity.position);
        if(curDistance <= distance){
            sum.add(entity.position);
            count += 1;
        }
    }
    var steer = new Vector(0,0);
    
    if(count > 0){
        sum.divide(count);
        steer = this.seekForce(sum);
    }
    
    return steer;
};

Entity.prototype.flock = function(entities){
    var sep = this.separate(entities);
    
    var align = this.align(entities);
    var cohesion = this.cohesion(entities);

    sep.multiply(this.ruleSeparate);
    align.multiply(this.ruleAlign);
    cohesion.multiply(this.ruleCohesion);
    
    this.applyForce(sep);
    this.applyForce(align);
    this.applyForce(cohesion);
};
//============================================================================
//
//Entities - Collection of Entities
//
//============================================================================
var Entities = function(){
    this.entities = [];
};

Entities.prototype.add = function(params){
    params = params || {};
    this.entities.push(new Entity(params));
}
Entities.prototype.remove = function(i){
    this.entities.splice(i,1);
}

Entities.prototype.applyForce = function(force){
    for(entity in this.entities){
        this.entities[entity].applyForce(force);
    }
}



//============================================================================
//vector class
//============================================================================
var Vector = function(x,y){
    x = x || 0;
    y = y || 0;
    this.x = x;
    this.y = y;
};
Vector.prototype.add = function(vector, vector2){
    //If one vector passed in, add to this instance vector
    if(!vector2){
        //If a scalar was passed in, add it
        if(typeof(vector) === 'number'){
            this.x += vector;
            this.y += vector;
        }else {
            //It's a vector
            this.x += vector.x;
            this.y += vector.y;
        }
        return this;
    }else {
        return new Vector(
            vector.x + vector2.x,
            vector.y + vector2.y
        );
    }
};
Vector.prototype.subtract = function(vector, vector2){
    if(!vector2){
        this.x -= vector.x;
        this.y -= vector.y;
        return this;
    }else{
        return new Vector(
            vector.x - vector2.x,
            vector.y - vector2.y
        );
    }
};
Vector.prototype.multiply = function(scalar){
    this.x *= scalar;
    this.y *= scalar;
    return this;
};
Vector.prototype.divide = function(scalar){
    if(this.x != 0){
        this.x /= scalar;
    }
    if(this.y != 0){
        this.y /= scalar;
    }
    return this;
};

Vector.prototype.copy = function(){
    //returns a copy of the passed in vector
    return new Vector( this.x, this.y );
};
Vector.prototype.magnitude = function(){
    return Math.sqrt( (this.x * this.x) + (this.y * this.y) );
};
Vector.prototype.magnitudeSquared = function(){
    return (this.x * this.x) + (this.y * this.y);
};

Vector.prototype.limit = function(max){
    var magnitude = this.magnitude();
    if(Math.abs(magnitude) > max){
        //normalize
        this.divide(magnitude); 
        //multiply
        this.multiply(max);
    }
    return this;
};

Vector.prototype.normalize = function(){
    var magnitude = this.magnitude();
    if(magnitude !== 0){
        this.divide(magnitude); 
    }
    return this;
};

Vector.prototype.dotProduct = function(vector1, vector2){
    //Can pass in either one or two vectors. If one vector is passed,
    //  assume we multiply it by ourself
    var dot;
    
    if(!vector2){
        dot = (this.x * vector1.x) + (this.y * vector1.y);
    } else {
        //two vectors passed in
        dot = (vector1.x * vector2.x) + (vector1.y * vector2.y);
    }

    return dot;
}

Vector.prototype.angle = function(vector1, vector2){
    if(!vector2){
        //use vector 1 and 2, set vector2 to vector1
        vector2 = vector1;
        vector1 = this;
    }

    //Find angle between two vectors
    var dot = vector1.dotProduct(vector1, vector2);
    var angle = Math.acos(
        dot / ( vector1.magnitude() * vector2.magnitude())
    );
    //convert to degrees
    angle = angle * (180 / Math.PI);

    return angle;
}

Vector.prototype.distance = function(vector1, vector2){
    if(!vector2){
        //use vector 1 and 2, set vector2 to vector1
        vector2 = vector1;
        vector1 = this;
    }
    var dist = Math.sqrt( 
        Math.pow((vector1.x - vector2.x), 2)
        + Math.pow((vector1.y - vector2.y) , 2)
    );

    return dist;
}