diff --git a/boids.mjs b/boids.mjs index 9a5ea5d..aa9e88b 100644 --- a/boids.mjs +++ b/boids.mjs @@ -1,79 +1,75 @@ import Vector from './vector.mjs'; -const findNeighbors = (boids, boid, radius) => -{ - const neighbors = []; - - for (let otherBoid of boids) - { - if (boid != otherBoid) - { - const dist = boid.pos.sub(otherBoid.pos).normSquared(); - - if (dist < radius * radius) - { - neighbors.push(otherBoid); - } - } - } - - return neighbors; -}; +const meanPos = new Vector(0, 0); +const meanVel = new Vector(0, 0); +const repelForce = new Vector(0, 0); export const update = (boids, params, time) => { - for (let boid of boids) + const boidsSize = boids.length; + + for (let i = 0; i < boidsSize; ++i) { - const visible = findNeighbors(boids, boid, params.visibleDist); - const close = findNeighbors(boids, boid, params.closeDist); + const me = boids[i]; - // Attract towards center of visible flock - const center = new Vector(0, 0); + meanPos.reset(); + meanVel.reset(); + repelForce.reset(); - for (let otherBoid of visible) + let visibleSize = 0; + + for (let j = 0; j < boidsSize; ++j) { - center.addMut(otherBoid.pos); + if (i != j) + { + const you = boids[j]; + const dist = Vector.distSquared(me.pos, you.pos); + + if (dist < params.visibleDist * params.visibleDist) + { + meanPos.add(you.pos); + meanVel.add(you.vel); + visibleSize += 1; + + if (dist < params.closeDist * params.closeDist) + { + repelForce.add(me.pos).sub(you.pos); + } + } + } } - if (visible.length >= 1) + // Attract towards center of visible flock + if (visibleSize >= 1) { - center.divMut(visible.length); - boid.vel.addMut(center.sub(boid.pos).mul(params.centerAccel)); + me.vel.add(meanPos.div(visibleSize) + .sub(me.pos) + .mul(params.centerAccel)); } // Attract toward center of screen - boid.vel.addMut(boid.pos.mul(-0.01)); + me.vel.addMul(me.pos, -0.01); // Repel away from close boids - for (let otherBoid of close) - { - boid.vel.addMut(boid.pos - .sub(otherBoid.pos) - .mul(params.repelAccel)); - } + me.vel.add(repelForce.mul(params.repelAccel)); // Match other boids’ velocity - const velocity = new Vector(0, 0); - - for (let otherBoid of visible) + if (visibleSize >= 1) { - velocity.addMut(otherBoid.vel); - } - - if (visible.length >= 1) - { - velocity.divMut(visible.length); - boid.vel.addMut(velocity.sub(boid.vel).mul(params.matchAccel)); + me.vel.add(meanVel.div(visibleSize) + .sub(me.vel) + .mul(params.matchAccel)); } // Do not surpass maximum speed - const speed = boid.vel.normSquared(); + const speed = me.vel.normSquared(); if (speed > params.maxSpeed * params.maxSpeed) { - boid.vel.divMut(Math.sqrt(speed)).mulMut(params.maxSpeed); + me.vel.div(Math.sqrt(speed)).mul(params.maxSpeed); } - boid.pos.addMut(boid.vel.mul(time)); + // Integrate speed + me.pos.addMul(me.vel, time); } }; diff --git a/draw.mjs b/draw.mjs index d0d256f..33aae10 100644 --- a/draw.mjs +++ b/draw.mjs @@ -7,8 +7,13 @@ const boidShape = [ new Vector(2, 0), ]; +const boidShapeSize = boidShape.length; +const transformed = new Vector(0, 0); + export const boids = (activeBoids, params, ctx, width, height, center) => { + const boidsSize = activeBoids.length; + ctx.clearRect(0, 0, width, height); // // Draw each boid’s visibility and proximity zones @@ -75,24 +80,31 @@ export const boids = (activeBoids, params, ctx, width, height, center) => ctx.beginPath(); ctx.fillStyle = 'black'; - for (let boid of activeBoids) + for (let i = 0; i < boidsSize; ++i) { + const boid = activeBoids[i]; const angle = boid.vel.angle(); let isFirst = true; - for (let point of boidShape) + for (let j = 0; j < boidShapeSize; ++j) { - const trans = boid.pos.add(center) - .add(point.rotate(angle).mul(params.radius)) + transformed.x = boidShape[j].x; + transformed.y = boidShape[j].y; + + transformed + .rotate(angle) + .mul(params.radius) + .add(center) + .add(boid.pos); if (isFirst) { - ctx.moveTo(trans.x, trans.y); + ctx.moveTo(transformed.x, transformed.y); isFirst = false; } else { - ctx.lineTo(trans.x, trans.y); + ctx.lineTo(transformed.x, transformed.y); } } } diff --git a/vector.mjs b/vector.mjs index 5198aba..d26fb8c 100644 --- a/vector.mjs +++ b/vector.mjs @@ -7,23 +7,20 @@ class Vector } add(vector) - { - return new Vector(this.x + vector.x, this.y + vector.y); - } - - addMut(vector) { this.x += vector.x; this.y += vector.y; return this; } - sub(vector) + addMul(vector, scalar) { - return new Vector(this.x - vector.x, this.y - vector.y); + this.x += vector.x * scalar; + this.y += vector.y * scalar; + return this; } - subMut(vector) + sub(vector) { this.x -= vector.x; this.y -= vector.y; @@ -31,11 +28,6 @@ class Vector } mul(scalar) - { - return new Vector(this.x * scalar, this.y * scalar); - } - - mulMut(scalar) { this.x *= scalar; this.y *= scalar; @@ -47,20 +39,16 @@ class Vector return this.mul(1 / scalar); } - divMut(scalar) - { - return this.mulMut(1 / scalar); - } - rotate(angle) { const cos = Math.cos(angle); const sin = Math.sin(angle); + const nextX = cos * this.x - sin * this.y; + const nextY = cos * this.y + sin * this.x; - return new Vector( - cos * this.x - sin * this.y, - cos * this.y + sin * this.x, - ); + this.x = nextX; + this.y = nextY; + return this; } angle() @@ -72,6 +60,26 @@ class Vector { return this.x * this.x + this.y * this.y; } + + static distSquared(lhs, rhs) + { + return ( + (lhs.x - rhs.x) * (lhs.x - rhs.x) + + (lhs.y - rhs.y) * (lhs.y - rhs.y) + ); + } + + reset() + { + this.x = 0; + this.y = 0; + return this; + } + + clone() + { + return new Vector(this.x, this.y); + } } export default Vector;