Public
Edited
May 7, 2023
2 forks
Importers
29 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
myBoids = {
const sim = new Boids();
// Add 500 boids
for (let i = 0; i < 500; i++) {
sim.add();
}
return sim;
}
Insert cell
myBoids
.alignment(alignment)
.cohesion(cohesion)
.separation(separation)
.perception(perception)
.quadtree(!!useTree);
Insert cell
function addBoidOnEvent(e){
myBoids.add({
pos: [e.offsetX, e.offsetY]
});
}
Insert cell
class Boids {
constructor(){
this._width = width;
this._height = height;
this._perception = 20;
this._alignment = 1;
this._cohesion = 1;
this._separation = 1;
this._maxSpeed = 4;
this._quadtree = true;
this.maxForce = 0.2;
this.flock = [];
this.tree = new BoidBush();
}
alignment(n){
if (isFinite(n)){
this._alignment = n;
for (let i = 0, l = this.flock.length; i < l; i++){
this.flock[i]._alignment = n;
}
return this;
}
else {
return this._alignment;
}
}
cohesion(n){
if (isFinite(n)){
this._cohesion = n;
for (let i = 0, l = this.flock.length; i < l; i++){
this.flock[i]._cohesion = n;
}
return this;
}
else {
return this._cohesion;
}
}

perception(n){
if (isFinite(n)){
this._perception = n;
for (let i = 0, l = this.flock.length; i < l; i++){
this.flock[i]._perception = n;
}
return this;
}
else {
return this._perception;
}
}
separation(n){
if (isFinite(n)){
this._separation = n;
for (let i = 0, l = this.flock.length; i < l; i++){
this.flock[i]._separation = n;
}
return this;
}
else {
return this._separation;
}
}
width(n){
if (isFinite(n)){
this._width = n;
for (let i = 0, l = this.flock.length; i < l; i++){
this.flock[i]._width = n;
}
return this;
}
else {
return this._width;
}
}
height(n){
if (isFinite(n)){
this._height = n;
for (let i = 0, l = this.flock.length; i < l; i++){
this.flock[i]._height = n;
}
return this;
}
else {
return this._height;
}
}
quadtree(bool){
if (arguments.length) {
this._quadtree = bool;
if (this._quadtree && !this.tree) this.tree = new BoidBush();
for (let i = 0, l = this.flock.length; i < l; i++){
this.flock[i]._quadtree = bool;
this.flock[i].tree = this.tree;
}
return this;
}
else {
return this._quadtree;
}
}
maxSpeed(n){
if (isFinite(n)){
this._maxSpeed = n;
for (let i = 0, l = this.flock.length; i < l; i++){
this.flock[i]._maxSpeed = n;
}
return this;
}
else {
return this._maxSpeed;
}
}
add(opts){
this.flock.push(new Boid(this, opts));
return this;
}
each(fn){
for (let i = 0, l = this.flock.length; i < l; i++){
fn(this.flock[i], i, this.flock);
}
return this;
}
tick(){
if (this._quadtree) {
this.tree.clear();
this.tree.load(this.flock);
}
this.each(boid => boid.update());
return this;
}
}
Insert cell
class Boid {
constructor(Boids, opts){
Object.assign(this, Boids);
Object.assign(this, opts);

// Angle, position, and speed can be assigned by the user.
this.ang = this.ang || 2 * Math.random() * Math.PI;
this.pos = this.pos || [
Math.random() * this._width,
Math.random() * this._height
];
this.speed = this.speed || 1;
const obj = {
pos: this.pos,
ang: this.ang,
speed: this.speed,
vel: vecmath.sub(
vecmath.trans(this.pos, this.ang, this.speed),
this.pos
),
acc: [0, 0],
id: this.flock.length
};
Object.assign(this, obj);
}
update(){
const prev = { ...this };
let alignment = [0, 0],
cohesion = [0, 0],
separation = [0, 0],
n = 0,
candidates = this.flock;
if (this._quadtree){
candidates = this.tree.search({
minX: this.pos[0] - this._perception,
minY: this.pos[1] - this._perception,
maxX: this.pos[0] + this._perception,
maxY: this.pos[1] + this._perception,
});
}
for (let i = 0, l = candidates.length; i < l; i ++){
const that = candidates[i],
dist = vecmath.dist(this.pos, that.pos);

if (this.id !== that.id && dist < this._perception){
alignment = vecmath.add(alignment, that.vel);
cohesion = vecmath.add(cohesion, that.pos);
const diff = vecmath.div(
vecmath.sub(this.pos, that.pos),
Math.max(dist, epsilon)
);
separation = vecmath.add(separation, diff);
n++;
}
}
if (n > 0){
alignment = vecmath.div(alignment, n);
alignment = vecmath.setMag(alignment, this._maxSpeed);
alignment = vecmath.sub(alignment, this.vel);
alignment = vecmath.limit(alignment, this.maxForce);
cohesion = vecmath.div(cohesion, n);
cohesion = vecmath.sub(cohesion, this.pos);
cohesion = vecmath.setMag(cohesion, this._maxSpeed);
cohesion = vecmath.sub(cohesion, this.vel);
cohesion = vecmath.limit(cohesion, this.maxForce);
separation = vecmath.div(separation, n);
separation = vecmath.setMag(separation, this._maxSpeed);
separation = vecmath.sub(separation, this.vel);
separation = vecmath.limit(separation, this.maxForce);
}
alignment = vecmath.mult(alignment, this._alignment);
cohesion = vecmath.mult(cohesion, this._cohesion);
separation = vecmath.mult(separation, this._separation);
this.acc = vecmath.add(this.acc, alignment);
this.acc = vecmath.add(this.acc, cohesion);
this.acc = vecmath.add(this.acc, separation);
this.pos = vecmath.add(this.pos, this.vel);
this.vel = vecmath.add(this.vel, this.acc);
this.vel = vecmath.limit(this.vel, this._maxSpeed);
if (this.pos[0] > this._width) this.pos[0] = 0;
if (this.pos[0] < 0) this.pos[0] = this._width;
if (this.pos[1] > this._height) this.pos[1] = 0;
if (this.pos[1] < 0) this.pos[1] = this._height;
this.ang = vecmath.ang(prev.pos, this.pos);
this.speed = vecmath.dist(prev.pos, this.pos);
this.acc = vecmath.mult(this.acc, 0);
}
}
Insert cell
class BoidBush extends RBush {
toBBox(boid) { return {minX: boid.pos[0], minY: boid.pos[1], maxX: boid.pos[0], maxY: boid.pos[1]}; }
compareMinX(a, b) { return a.pos[0] - b.pos[0]; }
compareMinY(a, b) { return a.pos[1] - b.pos[1]; }
}
Insert cell
Insert cell
Insert cell
Insert cell
html`<style>
table td, table th {
padding: 4px;
}
.ticker {
color: #555;
font-size: 14px;
margin-top: -10px;
text-align: right;
}
</style>`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more