Build a Crowd Evacuation ABM in ~100 Lines of JavaScript
You don't need a physics engine or a game framework to simulate a crowd finding an exit under pressure. This tutorial builds a working, visually convincing evacuation model from Dirk Helbing's Social Force Model in a single compact simulation loop — desired-direction force, agent repulsion, wall repulsion, and Euler integration.
1. Agent State
Each pedestrian is a point mass: position, velocity, a fixed desired speed, and a target point (the exit). We store agents as an array of plain objects — simple, and fast enough for a few hundred agents at 60fps without any spatial-index tricks:
function makeAgent(x, y, exitX, exitY) {
return {
x, y,
vx: 0, vy: 0,
desiredSpeed: 1.3 + Math.random() * 0.4, // m/s, individual variation
radius: 0.25, // m, body radius
exitX, exitY,
};
}
2. Desired Force Toward the Exit
The driving term of the Social Force Model pulls each agent's actual velocity toward a desired velocity — full desired speed, pointed at the exit — with a relaxation time τ (how quickly pedestrians adjust, typically ~0.5s):
function desiredForce(a) {
const dx = a.exitX - a.x, dy = a.exitY - a.y;
const dist = Math.hypot(dx, dy) || 1e-6;
const vdx = (dx / dist) * a.desiredSpeed;
const vdy = (dy / dist) * a.desiredSpeed;
const TAU = 0.5;
return {
fx: (vdx - a.vx) / TAU,
fy: (vdy - a.vy) / TAU,
};
}
3. Agent-Agent Repulsion
Without a repulsive term, agents would walk straight through each other. Helbing's model uses an exponentially decaying force based on the gap between body surfaces (distance minus both radii), so it is strong at contact and negligible a few metres away:
function agentRepulsion(a, agents) {
const A = 2000, B = 0.08; // N, m — tuned constants
let fx = 0, fy = 0;
for (const b of agents) {
if (b === a) continue;
const dx = a.x - b.x, dy = a.y - b.y;
const dist = Math.hypot(dx, dy) || 1e-6;
if (dist > 3) continue; // cutoff radius: skip far agents
const gap = a.radius + b.radius - dist;
const mag = A * Math.exp(gap / B);
fx += (dx / dist) * mag;
fy += (dy / dist) * mag;
}
return { fx, fy };
}
4. Wall Repulsion
Walls use the same exponential-decay shape as agent repulsion, but measured against the nearest point on each wall segment instead of another agent's centre:
function wallRepulsion(a, walls) {
const A = 2000, B = 0.08;
let fx = 0, fy = 0;
for (const w of walls) {
const { px, py, dist } = closestPointOnSegment(a.x, a.y, w);
if (dist > 2) continue;
const gap = a.radius - dist;
const mag = A * Math.exp(gap / B);
fx += ((a.x - px) / (dist || 1e-6)) * mag;
fy += ((a.y - py) / (dist || 1e-6)) * mag;
}
return { fx, fy };
}
function closestPointOnSegment(x, y, w) {
const { x1, y1, x2, y2 } = w;
const dx = x2 - x1, dy = y2 - y1;
const len2 = dx*dx + dy*dy || 1e-6;
let t = ((x - x1) * dx + (y - y1) * dy) / len2;
t = Math.max(0, Math.min(1, t));
const px = x1 + t * dx, py = y1 + t * dy;
return { px, py, dist: Math.hypot(x - px, y - py) };
}
5. The Full ~100-Line Simulation
Summing the three forces and integrating with simple Euler steps gives a complete, runnable evacuation simulation — this is the same structure used by the Crowd Evacuation simulation on this site:
const N = 150;
const WORLD = { w: 20, h: 12 }; // metres
const EXIT = { x: 20, y: 6 };
const walls = [
{ x1: 0, y1: 0, x2: 20, y2: 0 }, // bottom wall
{ x1: 0, y1: 12, x2: 20, y2: 12 }, // top wall
{ x1: 0, y1: 0, x2: 0, y2: 12 }, // left wall
];
const agents = Array.from({ length: N }, () =>
makeAgent(
Math.random() * 10,
Math.random() * WORLD.h,
EXIT.x, EXIT.y
)
);
function step(dt) {
const forces = Array(agents.length);
// 1) DECIDE: compute total force per agent without moving anyone yet
for (let i = 0; i < agents.length; i++) {
const a = agents[i];
const fd = desiredForce(a);
const fr = agentRepulsion(a, agents);
const fw = wallRepulsion(a, walls);
forces[i] = {
fx: fd.fx + fr.fx + fw.fx,
fy: fd.fy + fr.fy + fw.fy,
};
}
// 2) ACT: integrate velocity and position (semi-implicit Euler)
for (let i = 0; i < agents.length; i++) {
const a = agents[i], f = forces[i];
a.vx += f.fx * dt;
a.vy += f.fy * dt;
const speed = Math.hypot(a.vx, a.vy);
const maxSpeed = a.desiredSpeed * 1.3; // physiological cap
if (speed > maxSpeed) { a.vx *= maxSpeed / speed; a.vy *= maxSpeed / speed; }
a.x += a.vx * dt;
a.y += a.vy * dt;
}
// 3) Remove agents who reached the exit
for (let i = agents.length - 1; i >= 0; i--) {
if (Math.hypot(agents[i].x - EXIT.x, agents[i].y - EXIT.y) < 0.5) {
agents.splice(i, 1);
}
}
}
// Main loop
function animate() {
step(1 / 60);
render(agents, walls, EXIT); // draw to canvas — not shown
if (agents.length > 0) requestAnimationFrame(animate);
}
animate();
That is the entire model — roughly 100 lines counting the helper functions above, zero dependencies, and it already reproduces the two signature phenomena of real crowd evacuation: arching at the doorway (agents cluster into an arc a few body-widths from the exit as repulsion balances the desired force) and faster-is-slower (raising desired speed too far increases clogging and total evacuation time instead of reducing it).
6. Extending the Model
- Bottleneck oscillation: at narrow exits, real crowds show intermittent flow — bursts of agents passing followed by brief jams — reproduced automatically by the exponential repulsion once density near the door is high enough.
- Panic parameter: add a
panicfield per agent that increases desired speed and reduces the relaxation time τ under high local density, modelling the empirically observed loss of orderly behaviour under stress. - Multiple exits: give each agent a utility function over visible exits — combining distance and perceived local congestion — and re-evaluate periodically so agents can switch exits when one becomes crowded.
- Spatial grid: for 1,000+ agents, replace the O(N²) scans in
agentRepulsionwith the uniform-grid neighbour query described in our ABM architecture article.
Frequently Asked Questions
What will I learn in this tutorial?
A minimal but working crowd evacuation agent-based model: Social Force Model desired-direction, agent-agent repulsion, wall avoidance and a single exit — in about 100 lines of JavaScript.
What topics are covered in this tutorial?
This tutorial covers: Agent State, Desired Force Toward the Exit, Agent-Agent Repulsion, Wall Repulsion, The Full ~100-Line Simulation, Extending the Model.
How long does this tutorial take?
This tutorial takes approximately 20 minutes to complete.
What prerequisites do I need before starting?
This is a Intermediate-level tutorial — no special preparation beyond basic JavaScript is assumed.