const TRAIL_FRAME_SKIP = 4;
const BG_COLOR = "#132326";
const COLORS = ["#00a5cf", "#ff9f1c", "#ff5d73", "#a1c349"];
this.position = createVector(x, y);
this.velocity = createVector(random(-1, 1), random(-1, 1));
this.acceleration = createVector(0, 0);
this.size = floor(random(MIN_SIZE, MAX_SIZE));
this.velocity.setMag(random(1, LIMITS.MAX_SPEED));
const clr = color(random(COLORS));
this.trail.push([this.position.x, this.position.y]);
while (this.trail.length > TRAIL_LENGTH) {
const separation = this.separate(boids);
separation.mult(WEIGHTS.SEPARATION);
this.applyForce(separation);
const alignment = this.align(boids);
alignment.mult(WEIGHTS.ALIGNMENT);
this.applyForce(alignment);
const cohesion = this.cohere(boids);
cohesion.mult(WEIGHTS.COHESION);
this.applyForce(cohesion);
const desiredSeparation = this.size * 5;
let sum = createVector();
let force = createVector();
for (const other of boids) {
const dist = this.position.dist(other.position);
if (dist > 0 && dist < desiredSeparation) {
const away = p5.Vector.sub(this.position, other.position);
sum.setMag(LIMITS.MAX_SPEED);
force = p5.Vector.sub(sum, this.velocity);
force.limit(LIMITS.MAX_FORCE);
let sum = createVector();
let force = createVector();
for (const other of boids) {
const d = this.position.dist(other.position);
if (d > 0 && d < visionRange) {
sum.mult(LIMITS.MAX_SPEED);
force = p5.Vector.sub(sum, this.velocity);
force.limit(LIMITS.MAX_FORCE);
let sum = createVector();
let force = createVector();
for (const other of boids) {
const dist = this.position.dist(other.position);
if (dist > 0 && dist < visionRange) {
let desiredLocation = p5.Vector.sub(sum, this.position);
desiredLocation.normalize();
desiredLocation.mult(LIMITS.MAX_SPEED);
force = p5.Vector.sub(desiredLocation, this.velocity);
force.limit(LIMITS.MAX_FORCE);
this.acceleration.add(force);
this.velocity.add(this.acceleration);
this.velocity.limit(LIMITS.MAX_SPEED);
this.position.add(this.velocity);
this.acceleration.mult(0);
if (this.position.x > width) {
} else if (this.position.x < 0) {
if (this.position.y > height) {
} else if (this.position.y < 0) {
this.position.y = height;
if (frameCount % TRAIL_FRAME_SKIP == 0) {
this.color.setAlpha(100);
translate(this.position.x, this.position.y);
rotate(this.velocity.heading() + radians(90));
vertex(-this.size, this.size * 2);
vertex(this.size, this.size * 2);
for (let i = 0; i < this.trail.length; i++) {
const [x, y] = this.trail[i];
const tp = this.trail[i];
const alpha = map(i, 0, this.trail.length, 0, 100);
this.color.setAlpha(alpha);
createCanvas(windowWidth, windowHeight);
colorMode(HSB, 360, 100, 100, 100);
guiOpts = {name: "psettings", hideable: true, closed: false, closeOnTop: true};
gui = new dat.GUI(guiOpts);
const weights = gui.addFolder("Force Weights");
weights.add(WEIGHTS, 'SEPARATION', 0.0, 5.0, 0.1)
weights.add(WEIGHTS, 'ALIGNMENT', 0.0, 5.0, 0.1)
weights.add(WEIGHTS, 'COHESION', 0.0, 5.0, 0.1)
const limits = gui.addFolder("Limits");
limits.add(LIMITS, 'MAX_SPEED', 0.0, 10.0, 0.1)
limits.add(LIMITS, 'MAX_FORCE', 0.0, 1.0, 0.1)
for (let i = 0; i < BOID_COUNT; i++) {
const y = random(height);
const boid = new Boid(x, y, random(2, 5));
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
for (const boid of boids) {