<canvas id="canvas"></canvas>
<h1>Happy Halloween!</h1>
@import url(https://fonts.googleapis.com/css?family=Euphoria+Script);
html, body{
width: 100%;
height: 100%;
background: #ff8833;
overflow: hidden;
text-rendering: optimizeLegibility;
}
canvas{
display: block;
background: #ff8833;
}
h1 {
position: absolute;
z-index: 1;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: 80px;
text-align: center;
margin: auto;
font-family: 'Euphoria Script';
font-size: 80px;
color: rgba(104, 54, 0, 0.6);
}
// A simple experiment regarding birds flocking.
var Vector = (function () {
function Vector(x, y) {
if (typeof x === "undefined") { x = 0; }
if (typeof y === "undefined") { y = 0; }
this.x = x;
this.y = y;
}
Vector.prototype.clone = function () {
return new Vector(this.x, this.y);
};
Vector.prototype.zero = function () {
this.x = this.y = 0;
return this;
};
Object.defineProperty(Vector.prototype, "zeroed", {
get: function () {
return (this.x === 0 && this.y === 0);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Vector.prototype, "length", {
get: function () {
return Math.sqrt(this.lengthSq);
},
set: function (value) {
var a = this.angle;
this.x = Math.cos(a) * value;
this.y = Math.sin(a) * value;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Vector.prototype, "lengthSq", {
get: function () {
return this.x * this.x + this.y * this.y;
},
enumerable: true,
configurable: true
});
Object.defineProperty(Vector.prototype, "angle", {
get: function () {
return Math.atan2(this.y, this.x);
},
set: function (value) {
var length = this.length;
this.x = Math.cos(value) * length;
this.y = Math.sin(value) * length;
},
enumerable: true,
configurable: true
});
Vector.prototype.normalize = function () {
if(this.length === 0) {
this.x = 1;
return this;
}
var length = this.length;
this.x /= length;
this.y /= length;
return this;
};
Vector.prototype.truncate = function (max) {
this.length = Math.min(max, this.length);
return this;
};
Vector.prototype.reverse = function () {
this.x = -this.x;
this.y = -this.y;
return this;
};
Object.defineProperty(Vector.prototype, "normalized", {
get: function () {
return this.length == 1;
},
enumerable: true,
configurable: true
});
Vector.prototype.dotProduct = function (vector) {
return this.x * vector.x + this.y * vector.y;
};
Vector.angleBetween = function angleBetween(vector1, vector2) {
if(!vector1.normalized) {
vector1 = vector1.clone().normalize();
}
if(!vector2.normalized) {
vector2 = vector2.clone().normalize();
}
return Math.acos(vector1.dotProduct(vector2));
};
Object.defineProperty(Vector.prototype, "perpendicular", {
get: function () {
return new Vector(-this.y, this.x);
},
enumerable: true,
configurable: true
});
Vector.prototype.sign = function (vector) {
return this.perpendicular.dotProduct(vector) < 0 ? -1 : 1;
};
Vector.prototype.dist = function (vector) {
return Math.sqrt(this.distSq(vector));
};
Vector.prototype.distSq = function (vector) {
var dx = vector.x - this.x;
var dy = vector.y - this.y;
return dx * dx + dy * dy;
};
Vector.prototype.add = function (vector) {
return new Vector(this.x + vector.x, this.y + vector.y);
};
Vector.prototype.subtract = function (vector) {
return new Vector(this.x - vector.x, this.y - vector.y);
};
Vector.prototype.multiply = function (value) {
return new Vector(this.x * value, this.y * value);
};
Vector.prototype.divide = function (value) {
return new Vector(this.x / value, this.y / value);
};
Vector.prototype.equals = function (vector) {
return this.x === vector.x && this.y === vector.y;
};
return Vector;
})();
var Vehicle = (function () {
function Vehicle() {
this.mass = 1.0;
this.maxSpeed = 10;
this.position = new Vector();
this.velocity = new Vector();
}
Vehicle.WRAP = "wrap";
Vehicle.BOUNCE = "bounce";
Vehicle.prototype.draw = function (context) {
context.save();
context.translate(this.position.x, this.position.y);
context.rotate(this.velocity.angle);
//This is where I draw them as candy corn :)
context.fillStyle = "#ff4814";
context.beginPath();
context.moveTo(this.size, 0);
context.lineTo(-this.size, this.size / 2);
context.lineTo(-this.size, -this.size / 2);
context.lineTo(this.size, 0);
context.fill();
context.fillStyle = "#ffcd2d";
context.beginPath();
context.moveTo(this.size, 0);
context.lineTo(-this.size * 0.5, this.size / 2 * 0.8);
context.lineTo(-this.size * 0.5, -this.size / 2 * 0.8);
context.lineTo(this.size, 0);
context.fill();
context.fillStyle = "#eeeeee";
context.beginPath();
context.moveTo(this.size, 0);
context.lineTo(Math.floor(-this.size * 0.1) + 7, this.size / 2 * 0.3);
context.lineTo(Math.floor(-this.size * 0.1) + 7, -this.size / 2 * 0.3);
context.lineTo(this.size, 0);
context.fill();
context.restore();
};
Vehicle.prototype.update = function (width, height) {
this.velocity = this.velocity.truncate(this.maxSpeed);
this.position = this.position.add(this.velocity);
if(this.edgeBehavior == Vehicle.WRAP) {
this.wrap(width, height);
} else if(this.edgeBehavior == Vehicle.BOUNCE) {
this.bounce(width, height);
}
};
Vehicle.prototype.bounce = function (width, height) {
if(this.position.x > width) {
this.position.x = width;
this.velocity.x *= -1;
} else if(this.position.x < 0) {
this.position.x = 0;
this.velocity.x *= -1;
} else if(this.position.y > height) {
this.position.y = height;
this.velocity.y *= -1;
} else if(this.position.y < 0) {
this.position.y = 0;
this.velocity.y *= -1;
}
};
Vehicle.prototype.wrap = function (width, height) {
if(this.position.x > width) {
this.position.x = 0;
}
if(this.position.x < 0) {
this.position.x = width;
}
if(this.position.y > height) {
this.position.y = 0;
}
if(this.position.y < 0) {
this.position.y = height;
}
};
return Vehicle;
})();
var __extends = this.__extends || function (d, b) {
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
var SteeringVehicle = (function (_super) {
__extends(SteeringVehicle, _super);
function SteeringVehicle() {
_super.call(this);
this.maxForce = 1;
this.arrivalThreshold = 100;
this.wanderRadius = 5;
this.wanderAngle = 0;
this.wanderRange = 1;
this.wanderDistance = 10;
this.avoidDistance = 300;
this.avoidBuffer = 20;
this.pathIndex = 0;
this.pathThreshold = 20;
this.tooCloseDist = 60;
this.inSightDist = 200;
this.steeringForce = new Vector();
}
SteeringVehicle.prototype.seek = function (target) {
var desiredVelocity = target.subtract(this.position);
desiredVelocity.normalize();
desiredVelocity = desiredVelocity.multiply(this.maxSpeed);
var force = desiredVelocity.subtract(this.velocity);
this.steeringForce = this.steeringForce.add(force);
};
SteeringVehicle.prototype.flee = function (target) {
var desiredVelocity = target.subtract(this.position);
desiredVelocity.normalize();
desiredVelocity = desiredVelocity.multiply(this.maxSpeed);
var force = desiredVelocity.subtract(this.velocity);
this.steeringForce = this.steeringForce.subtract(force);
};
SteeringVehicle.prototype.arrive = function (target) {
var desiredVelocity = target.subtract(this.position);
desiredVelocity.normalize();
var dist = this.position.dist(target);
if(dist > this.arrivalThreshold) {
desiredVelocity = desiredVelocity.multiply(this.maxSpeed);
} else {
desiredVelocity = desiredVelocity.multiply(this.maxSpeed * dist / this.arrivalThreshold);
}
var force = desiredVelocity.subtract(this.velocity);
this.steeringForce = this.steeringForce.add(force);
};
SteeringVehicle.prototype.pursue = function (target) {
var lookAheadTime = this.position.dist(target.position) / this.maxSpeed;
var predictedTarget = target.position.add(target.velocity.multiply(lookAheadTime));
this.seek(predictedTarget);
};
SteeringVehicle.prototype.evade = function (target) {
var lookAheadTime = this.position.dist(target.position) / this.maxSpeed;
var predictedTarget = target.position.add(target.velocity.multiply(lookAheadTime));
this.flee(predictedTarget);
};
SteeringVehicle.prototype.wander = function () {
var center = this.velocity.clone().normalize().multiply(this.wanderDistance);
var offset = new Vector();
offset.length = this.wanderRadius;
offset.angle = this.wanderAngle;
this.wanderAngle += Math.random() * this.wanderRange - this.wanderRange * 0.5;
var force = center.add(offset);
this.steeringForce = this.steeringForce.add(force);
};
SteeringVehicle.prototype.avoid = function (targets) {
for(var i = 0, l = targets.length; i < l; i++) {
var target = targets[i];
var heading = this.velocity.clone().normalize();
var difference = target.position.subtract(this.position);
var dotProd = difference.dotProduct(heading);
if(dotProd > 0) {
var feeler = heading.multiply(this.avoidDistance);
var projection = heading.multiply(dotProd);
var dist = projection.subtract(difference).length;
if(dist < target.size + this.avoidBuffer && projection.length < feeler.length) {
var force = heading.multiply(this.maxSpeed);
force.angle += difference.sign(this.velocity) * Math.PI / 2;
force = force.multiply(1 - projection.length / feeler.length);
this.steeringForce = this.steeringForce.add(force);
this.velocity = this.velocity.multiply(projection.length / feeler.length);
}
}
}
};
SteeringVehicle.prototype.path = function (path, loop) {
if (typeof loop === "undefined") { loop = false; }
var point = path[this.pathIndex];
if(point == null) {
return;
}
if(this.position.dist(point) < this.pathThreshold) {
if(this.pathIndex >= path.length - 1) {
if(loop) {
this.pathIndex = 0;
}
} else {
this.pathIndex++;
}
}
if(this.pathIndex >= path.length - 1 && !loop) {
this.arrive(point);
} else {
this.seek(point);
}
};
SteeringVehicle.prototype.flock = function (flock) {
var averageVelocity = this.velocity.clone();
var averagePosition = new Vector();
var inSightCount = 0;
for(var i = 0, l = flock.length; i < l; i++) {
var vehicle = flock[i];
if(vehicle != this && this.inSight(vehicle)) {
averageVelocity = averageVelocity.add(vehicle.velocity);
averagePosition = averagePosition.add(vehicle.position);
if(this.tooClose(vehicle)) {
this.flee(vehicle.position);
}
inSightCount++;
}
}
if(inSightCount > 0) {
averageVelocity = averageVelocity.divide(inSightCount);
averagePosition = averagePosition.divide(inSightCount);
this.seek(averagePosition);
this.steeringForce.add(averageVelocity.subtract(this.velocity));
}
};
SteeringVehicle.prototype.inSight = function (vehicle) {
if(this.position.dist(vehicle.position) > this.inSightDist) {
return false;
}
var heading = this.velocity.clone().normalize();
var difference = vehicle.position.subtract(this.position);
var dotProd = difference.dotProduct(heading);
if(dotProd < 0) {
return false;
}
return true;
};
SteeringVehicle.prototype.tooClose = function (vehicle) {
return this.position.dist(vehicle.position) < this.tooCloseDist;
};
SteeringVehicle.prototype.update = function (width, height) {
this.steeringForce = this.steeringForce.truncate(this.maxForce);
this.steeringForce = this.steeringForce.divide(this.mass);
this.velocity = this.velocity.add(this.steeringForce);
this.steeringForce = new Vector();
_super.prototype.update.call(this, width, height);
};
return SteeringVehicle;
})(Vehicle);
var Playground = (function () {
function Playground() {
this.mouseX = 0;
this.mouseY = 0;
this.pinned = true;
this.color = "#ffffff";
var canvas = document.querySelector("#canvas");
this.context = canvas.getContext("2d");
this.context.lineWidth = 1;
this.resize();
this.init();
window.addEventListener('resize', this.resize.bind(this));
this.animate();
}
Playground.prototype.init = function () {
this.birds = [];
this.flockQuantity = 25; //Number of boids
for(var i = 0; i < this.flockQuantity; i++) {
this.birds[i] = new SteeringVehicle();
this.birds[i].size = 20; //Size of boids
this.birds[i].position = new Vector(Math.random() * this.width, Math.random() * this.height);
this.birds[i].mass = 2; //Boid mass
this.birds[i].velocity.length = 3;
this.birds[i].velocity.angle = Math.PI / 5;
this.birds[i].edgeBehavior = Vehicle.WRAP;
}
};
Playground.prototype.onMouseMove = function (e) {
this.mouseX = e.clientX;
this.mouseY = e.clientY;
};
Playground.prototype.resize = function () {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.context.canvas.width = this.width;
this.context.canvas.height = this.height;
};
Playground.prototype.animate = function () {
this.context.clearRect(0, 0, this.width, this.height);
this.context.fillStyle = this.context.strokeStyle = this.color;
this.context.globalCompositeOperation = "source-over";
for(var i = 0; i < this.flockQuantity; i++) {
this.birds[i].flock(this.birds);
this.birds[i].update(this.width, this.height);
this.birds[i].draw(this.context);
}
requestAnimationFrame(this.animate.bind(this));
};
return Playground;
})();
var Run = (function () {
function Run() {
var playground = new Playground();
}
return Run;
})();
var run = new Run();
This Pen doesn't use any external CSS resources.