Туторіал · Соціальне моделювання · JavaScript
📅 Липень 2026 ⏱ ≈ 20 хв 🎯 Середній рівень

ABM натовп евакуації за ~100 рядків JavaScript

Не потрібен фізичний рушій чи ігровий фреймворк, щоб симулювати, як натовп шукає вихід під тиском. Цей туторіал будує робочу, візуально переконливу модель евакуації на основі Social Force Model Дірка Гельбінга в одному компактному циклі симуляції — сила бажаного напрямку, відштовхування агентів, відштовхування від стін та інтегрування Ейлера.

1. Стан агента

Кожен пішохід — матеріальна точка: позиція, швидкість, фіксована бажана швидкість і цільова точка (вихід). Зберігаємо агентів як масив звичайних об'єктів — просто, і достатньо швидко для кількох сотень агентів при 60fps без жодних трюків з просторовим індексом:

function makeAgent(x, y, exitX, exitY) {
  return {
    x, y,
    vx: 0, vy: 0,
    desiredSpeed: 1.3 + Math.random() * 0.4, // м/с, індивідуальна варіація
    radius: 0.25,                                   // м, радіус тіла
    exitX, exitY,
  };
}

2. Бажана сила до виходу

Рушійний член Social Force Model тягне поточну швидкість агента до бажаної швидкості — повна бажана швидкість, спрямована на вихід — з часом релаксації τ (наскільки швидко пішоходи адаптуються, зазвичай ~0.5с):

F_бажана = (v_бажана − v_поточна) / τ
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. Відштовхування агент-агент

Без відштовхуючого члена агенти проходили б наскрізь одне через одного. Модель Гельбінга використовує експоненційно спадаючу силу на основі проміжку між поверхнями тіл (відстань мінус обидва радіуси), тому вона сильна при контакті й нехтовна на відстані кількох метрів:

F_ij = A · exp((rᵢ + rⱼ − dᵢⱼ) / B) · n̂ᵢⱼ
function agentRepulsion(a, agents) {
  const A = 2000, B = 0.08;   // Н, м — підібрані константи
  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;             // радіус відсічення: пропустити далеких агентів
    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 };
}
Продуктивність: це O(N²) щотіку. Для кількох сотень агентів це комфортно працює на 60fps; понад ~2000 агентів обгорніть цю функцію просторовою сіткою зі статті про архітектуру ABM, щоб кожен агент сканував лише свій локальний блок 3×3 клітини.

4. Відштовхування від стін

Стіни використовують ту саму експоненційну форму спадання, що й відштовхування агентів, але вимірювану відносно найближчої точки на кожному сегменті стіни, а не центру іншого агента:

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. Повна симуляція на ~100 рядків

Підсумувавши три сили й проінтегрувавши простими кроками Ейлера, отримуємо повну, робочу симуляцію евакуації — це та сама структура, що використовується в симуляції евакуації натовпу на цьому сайті:

const N = 150;
const WORLD = { w: 20, h: 12 };   // метри
const EXIT = { x: 20, y: 6 };
const walls = [
  { x1: 0, y1: 0, x2: 20, y2: 0 },   // нижня стіна
  { x1: 0, y1: 12, x2: 20, y2: 12 }, // верхня стіна
  { x1: 0, y1: 0, x2: 0, y2: 12 },   // ліва стіна
];

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: обчислити сумарну силу на агента, ще нікого не рухаючи
  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: проінтегрувати швидкість і позицію (напівнеявний Ейлер)
  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;  // фізіологічний ліміт
    if (speed > maxSpeed) { a.vx *= maxSpeed / speed; a.vy *= maxSpeed / speed; }
    a.x += a.vx * dt;
    a.y += a.vy * dt;
  }

  // 3) Видалити агентів, що досягли виходу
  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);
    }
  }
}

// Головний цикл
function animate() {
  step(1 / 60);
  render(agents, walls, EXIT);       // малювання на canvas — не показано
  if (agents.length > 0) requestAnimationFrame(animate);
}
animate();

Це вся модель — приблизно 100 рядків, рахуючи допоміжні функції вище, нуль залежностей, і вона вже відтворює два характерні явища реальної евакуації натовпу: арочний ефект (arching) у дверях (агенти скупчуються дугою за кілька ширин тіла від виходу, коли відштовхування врівноважує бажану силу) і "швидше = повільніше" (надмірне підвищення бажаної швидкості збільшує затори й загальний час евакуації замість того, щоб його зменшувати).

6. Розширення моделі

Спробуйте наживо: симуляція евакуації натовпу на цьому сайті реалізує саме цю модель з просторовою сіткою, регульованою шириною виходу та живим показником часу евакуації.

Часті запитання

Чого я навчуся в цьому уроці?

Мінімальна, але робоча агент-орієнтована модель евакуації натовпу: Social Force Model — сила бажаного напрямку, відштовхування між агентами, відштовхування від стін і один вихід — близько 100 рядків JavaScript.

Які теми розглядаються в цьому уроці?

Цей урок охоплює такі теми: Стан агента, Бажана сила до виходу, Відштовхування агент-агент, Відштовхування від стін, Повна симуляція на ~100 рядків, Розширення моделі.

Скільки часу займає цей урок?

Цей урок займає приблизно 20 хв.

Які попередні знання потрібні?

Це урок рівня «Середній рівень» — окрема попередня підготовка, крім базового JavaScript, не потрібна.