Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions js/boid.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
export default function Boid(position, speed) {
this.position = position;
this.speed = speed;
}
export default class Boid {
constructor(position, speed) {
this.position = position;
this.speed = speed;
}

Boid.prototype.compare = function(that, isEven) {
return this.position.compare(that.position, isEven);
};
compare(that, isEven) {
return this.position.compare(that.position, isEven);
}

Boid.prototype.toString = function() {
return this.position.toString();
};
toString() {
return this.position.toString();
}
}
2 changes: 1 addition & 1 deletion js/browser-benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function tickBenchmark() {
let numReports = [];
console.log("Running benchmark, please wait..");
for (let n of boidNums) {
const boids = Boids({ boids: n });
const boids = new Boids({ boids: n });

for (let i = 0; i < 1000; i++) {
const startTime = performance.now();
Expand Down
2 changes: 1 addition & 1 deletion js/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Boid from "./boid.js";
const anchor = document.createElement("a"),
canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d"),
boids = Boids();
boids = new Boids();

canvas.addEventListener("click", function(e) {
let x = e.pageX,
Expand Down
290 changes: 145 additions & 145 deletions js/index.js
Original file line number Diff line number Diff line change
@@ -1,173 +1,173 @@
import Vector from "./vector.js";
import Boid from "./boid.js";

export default function Boids(opts) {
if (!(this instanceof Boids)) return new Boids(opts);

opts = opts || {};

this.speedLimit = opts.speedLimit || 1;
this.accelerationLimit = opts.accelerationLimit || 0.03;
this.separationDistance = opts.separationDistance || 30;
this.separationDistanceSq = Math.pow(this.separationDistance, 2);
this.alignmentDistance = opts.alignmentDistance || 60;
this.alignmentDistanceSq = Math.pow(this.alignmentDistance, 2);
this.cohesionDistance = opts.cohesionDistance || 60;
this.cohesionDistanceSq = Math.pow(this.cohesionDistance, 2);
/* jshint laxbreak: true */
this.separationForce = !isNaN(opts.separationForce)
? opts.separationForce
: 2;
this.cohesionForce = !isNaN(opts.cohesionForce) ? opts.cohesionForce : 1;
this.alignmentForce = !isNaN(opts.alignmentForce) ? opts.alignmentForce : 1;
this.maxDistSq = Math.max(
this.separationDistanceSq,
this.cohesionDistanceSq,
this.alignmentDistanceSq
);

const boids = (this.boids = []);

for (
let i = 0, l = opts.boids === undefined ? 150 : opts.boids;
i < l;
i += 1
) {
boids[i] = new Boid(
new Vector(Math.random() * 100 - 50, Math.random() * 100 - 50),
new Vector(0, 0)
export default class Boids {
constructor(opts) {
opts = opts || {};

this.speedLimit = opts.speedLimit || 1;
this.accelerationLimit = opts.accelerationLimit || 0.03;
this.separationDistance = opts.separationDistance || 30;
this.separationDistanceSq = Math.pow(this.separationDistance, 2);
this.alignmentDistance = opts.alignmentDistance || 60;
this.alignmentDistanceSq = Math.pow(this.alignmentDistance, 2);
this.cohesionDistance = opts.cohesionDistance || 60;
this.cohesionDistanceSq = Math.pow(this.cohesionDistance, 2);
/* jshint laxbreak: true */
this.separationForce = !isNaN(opts.separationForce)
? opts.separationForce
: 2;
this.cohesionForce = !isNaN(opts.cohesionForce) ? opts.cohesionForce : 1;
this.alignmentForce = !isNaN(opts.alignmentForce) ? opts.alignmentForce : 1;
this.maxDistSq = Math.max(
this.separationDistanceSq,
this.cohesionDistanceSq,
this.alignmentDistanceSq
);
}
}

Boids.prototype.findNeighbors = function(point) {
const neighbors = [];
for (let i = 0; i < this.boids.length; i++) {
const boid = this.boids[i];

if (point === boid.position) {
continue;
}
const boids = (this.boids = []);

const distSq = boid.position.distSquared(point);
if (distSq < this.maxDistSq) {
neighbors.push({
neighbor: this.boids[i],
distSq: distSq
});
for (
let i = 0, l = opts.boids === undefined ? 150 : opts.boids;
i < l;
i += 1
) {
boids[i] = new Boid(
new Vector(Math.random() * 100 - 50, Math.random() * 100 - 50),
new Vector(0, 0)
);
}
}

return neighbors;
};

Boids.prototype.calcCohesion = function(boid, neighbors) {
let total = new Vector(0, 0),
count = 0;

for (let i = 0; i < neighbors.length; i++) {
const target = neighbors[i].neighbor;
if (boid === target) continue;
#isInFrontOf(boid, point) {
return (
boid.position.angle(boid.position.add(boid.speed), point) <= Math.PI / 3
);
}

const distSq = neighbors[i].distSq;
if (
distSq < this.cohesionDistanceSq &&
isInFrontOf(boid, target.position)
) {
total = total.add(target.position);
count++;
findNeighbors(point) {
const neighbors = [];
for (let i = 0; i < this.boids.length; i++) {
const boid = this.boids[i];

if (point === boid.position) {
continue;
}

const distSq = boid.position.distSquared(point);
if (distSq < this.maxDistSq) {
neighbors.push({
neighbor: this.boids[i],
distSq: distSq
});
}
}

return neighbors;
}

if (count === 0) return new Vector(0, 0);

return total
.divideBy(count)
.subtract(boid.position)
.normalize()
.subtract(boid.speed)
.limit(this.accelerationLimit);
};

Boids.prototype.calcSeparation = function(boid, neighbors) {
let total = new Vector(0, 0),
count = 0;

for (let i = 0; i < neighbors.length; i++) {
const target = neighbors[i].neighbor;
if (boid === target) continue;

const distSq = neighbors[i].distSq;
if (distSq < this.separationDistanceSq) {
total = total.add(
target.position
.subtract(boid.position)
.normalize()
.divideBy(target.position.distance(boid.position))
);
count++;
calcCohesion(boid, neighbors) {
let total = new Vector(0, 0),
count = 0;

for (let i = 0; i < neighbors.length; i++) {
const target = neighbors[i].neighbor;
if (boid === target) continue;

const distSq = neighbors[i].distSq;
if (
distSq < this.cohesionDistanceSq &&
this.#isInFrontOf(boid, target.position)
) {
total = total.add(target.position);
count++;
}
}
}

if (count === 0) return new Vector(0, 0);
if (count === 0) return new Vector(0, 0);

return total
.divideBy(count)
.normalize()
.add(boid.speed) // Adding speed instead of subtracting because separation is repulsive
.limit(this.accelerationLimit);
};
return total
.divideBy(count)
.subtract(boid.position)
.normalize()
.subtract(boid.speed)
.limit(this.accelerationLimit);
}

Boids.prototype.calcAlignment = function(boid, neighbors) {
let total = new Vector(0, 0),
count = 0;
calcSeparation(boid, neighbors) {
let total = new Vector(0, 0),
count = 0;

for (let i = 0; i < neighbors.length; i++) {
const target = neighbors[i].neighbor;
if (boid === target) continue;

const distSq = neighbors[i].distSq;
if (distSq < this.separationDistanceSq) {
total = total.add(
target.position
.subtract(boid.position)
.normalize()
.divideBy(target.position.distance(boid.position))
);
count++;
}
}

for (let i = 0; i < neighbors.length; i++) {
const target = neighbors[i].neighbor;
if (boid === target) continue;
if (count === 0) return new Vector(0, 0);

const distSq = neighbors[i].distSq;
if (
distSq < this.alignmentDistanceSq &&
isInFrontOf(boid, target.position)
) {
total = total.add(target.speed);
count++;
}
return total
.divideBy(count)
.normalize()
.add(boid.speed) // Adding speed instead of subtracting because separation is repulsive
.limit(this.accelerationLimit);
}

if (count === 0) return new Vector(0, 0);
calcAlignment(boid, neighbors) {
let total = new Vector(0, 0),
count = 0;

for (let i = 0; i < neighbors.length; i++) {
const target = neighbors[i].neighbor;
if (boid === target) continue;

const distSq = neighbors[i].distSq;
if (
distSq < this.alignmentDistanceSq &&
this.#isInFrontOf(boid, target.position)
) {
total = total.add(target.speed);
count++;
}
}

if (count === 0) return new Vector(0, 0);

return total
.divideBy(count)
.normalize()
.subtract(boid.speed)
.limit(this.accelerationLimit);
};
return total
.divideBy(count)
.normalize()
.subtract(boid.speed)
.limit(this.accelerationLimit);
}

Boids.prototype.tick = function() {
const accelerations = [];
for (let i = 0; i < this.boids.length; i++) {
let boid = this.boids[i];
let neighbors = this.findNeighbors(boid.position);
tick() {
const accelerations = [];
for (let i = 0; i < this.boids.length; i++) {
let boid = this.boids[i];
let neighbors = this.findNeighbors(boid.position);

const acceleration = this.calcCohesion(boid, neighbors)
.multiplyBy(this.cohesionForce)
.add(this.calcAlignment(boid, neighbors).multiplyBy(this.alignmentForce))
.subtract(this.calcSeparation(boid, neighbors).multiplyBy(this.separationForce));
const acceleration = this.calcCohesion(boid, neighbors)
.multiplyBy(this.cohesionForce)
.add(this.calcAlignment(boid, neighbors).multiplyBy(this.alignmentForce))
.subtract(this.calcSeparation(boid, neighbors).multiplyBy(this.separationForce));

accelerations.push(acceleration);
}
accelerations.push(acceleration);
}

for (let j = 0; j < this.boids.length; j++) {
const boid = this.boids[j];
boid.speed = boid.speed.add(accelerations[j]).limit(this.speedLimit);
boid.position = boid.position.add(boid.speed);
for (let j = 0; j < this.boids.length; j++) {
const boid = this.boids[j];
boid.speed = boid.speed.add(accelerations[j]).limit(this.speedLimit);
boid.position = boid.position.add(boid.speed);
}
}
};

function isInFrontOf(boid, point) {
return (
boid.position.angle(boid.position.add(boid.speed), point) <= Math.PI / 3
);
}
Loading