A flocking simulation looks like it wants an AI that plans the swarm — some leader steering the murmuration, some choreographer deciding where the cloud folds next. It needs none of that. On screen there is no flock at all, only data: a boid is a point with a velocity, and the flock is a plain array of them. Craig Reynolds worked this out in 1986, and the whole thing fits in about 150 lines of vanilla JavaScript on one canvas. Here's how I built it.
A boid is a point with a velocity
There's no "flock" object. Each boid is a position and a velocity; the array is the world. A few shared weights and a perception radius live in one params object, so a slider can retune every boid at once.
const P = { sep:1.6, ali:1.0, coh:0.9, perc:60, speed:3.2, count:200 };
function makeBoid(){
const a = Math.random()*Math.PI*2, s = P.speed*(0.5+Math.random()*0.5);
return { x:Math.random()*W, y:Math.random()*H,
vx:Math.cos(a)*s, vy:Math.sin(a)*s, ax:0, ay:0 };
}
Find the neighbours a boid can see
A boid reacts only to birds inside its perception radius; everyone else is invisible. Once a frame I scan the array and keep the close ones — and because the sky wraps, I measure to the nearest image of each neighbour, across the seam if that's shorter, so a flock slides off one edge and reappears on the other with no tear. Every rule below is just a different sum over this same list.
function neighbours(b){
const out = [], perc2 = P.perc * P.perc;
for (const o of boids){
if (o === b) continue;
let dx = b.x - o.x, dy = b.y - o.y;
if (dx > W/2) dx -= W; else if (dx < -W/2) dx += W; // wrap to
if (dy > H/2) dy -= H; else if (dy < -H/2) dy += H; // nearest image
if (dx*dx + dy*dy < perc2) out.push({ o, dx, dy });
}
return out;
}
Three rules, each a vector
Separation keeps birds from piling up: for each too-close neighbour add a vector pointing away, weighted by 1/d², so the nearer someone is the harder you shove off. Without it the whole flock collapses to a dot.
Alignment makes it flow: average the neighbours' velocities — their headings, not positions — and steer toward that. A turn begun by a few ripples outward until the group banks as one.
Cohesion stops fraying: average the neighbours' positions and steer toward that local centre. Since dx,dy point from neighbour to you, the average of -dx,-dy is the way to the centre.
Reynolds steering turns a direction into a bank
Each rule hands back a raw direction, but a bird can't teleport onto it — it has to turn. Reynolds' trick: normalise the direction to top speed to get the desired velocity, subtract the current velocity to get the steering, then clamp that to a small max force so the bird banks instead of snapping. Steer all three, scale each by its slider weight, sum into one acceleration.
function steer(dx, dy, b){
const m = Math.hypot(dx,dy) || 1e-6;
const desX = dx/m*P.speed, desY = dy/m*P.speed; // desired velocity
let sx = desX - b.vx, sy = desY - b.vy; // desired − current
const sm = Math.hypot(sx,sy), maxF = P.speed*0.08;
if (sm > maxF){ sx = sx/sm*maxF; sy = sy/sm*maxF; } // cap the turn
return { x:sx, y:sy };
}
b.ax = s.x*P.sep + a.x*P.ali + c.x*P.coh; // weighted blend
b.ay = s.y*P.sep + a.y*P.ali + c.y*P.coh;
Integrate on a frozen snapshot, then wrap
The one subtlety: I compute every boid's acceleration first, in one pass, so each reads the same frozen snapshot — otherwise a boid reacts to a neighbour that already moved this frame. A second pass integrates: velocity gains the acceleration, clamp it between a floor (nobody freezes) and top speed, advance the position, wrap the toroidal edges. Two passes over the array and the motion is done. Draw each boid as a triangle pointing along its velocity, hue by heading, so you can see alignment happen — and it all runs inside one requestAnimationFrame loop.
Nowhere in any of this is the word "flock". The swirling, splitting, re-merging murmuration is emergent — global order that no line of code describes, arising purely from every boid obeying the same three local rules. Drag your mouse through to become an attractor the birds chase or a predator they flee, tune the three weights live, and watch order fall out of arithmetic:
Top comments (0)