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
<!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 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;
}