Tutorial · Social Simulation · JavaScript
📅 July 2026 ⏱ ≈ 20 min 🎯 Intermediate

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):

F_desired = (v_desired − v_current) / τ
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:

F_ij = A · exp((rᵢ + rⱼ − dᵢⱼ) / B) · n̂ᵢⱼ
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 };
}
Performance note: this is O(N²) per tick. For a few hundred agents it runs comfortably at 60fps; past ~2,000 agents, wrap it with the spatial grid from our ABM architecture article so each agent only scans its local 3×3 cell block.

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

Try it live: the Crowd Evacuation simulation on this site implements this exact model with a spatial grid, adjustable exit width, and a live evacuation-time readout.

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.