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с):
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. Відштовхування агент-агент
Без відштовхуючого члена агенти проходили б наскрізь одне через одного. Модель Гельбінга використовує експоненційно спадаючу силу на основі проміжку між поверхнями тіл (відстань мінус обидва радіуси), тому вона сильна при контакті й нехтовна на відстані кількох метрів:
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 };
}
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. Розширення моделі
- Осциляція вузького місця: у вузьких виходах реальні натовпи демонструють переривчастий потік — сплески агентів, що проходять, з короткими заторами між ними — автоматично відтворюється експоненційним відштовхуванням, коли щільність біля дверей достатньо висока.
- Параметр паніки: додайте поле
panicкожному агенту, що підвищує бажану швидкість і зменшує час релаксації τ при високій локальній щільності, моделюючи емпірично спостережувану втрату впорядкованої поведінки під стресом. - Кілька виходів: дайте кожному агенту функцію корисності над видимими виходами — комбінуючи відстань і сприйняту локальну заторність — і переоцінюйте періодично, щоб агенти могли змінювати вихід, коли один переповнюється.
- Просторова сітка: для 1000+ агентів замініть O(N²) сканування в
agentRepulsionна запит сусідів через рівномірну сітку, описаний у нашій статті про архітектуру ABM.
Часті запитання
Чого я навчуся в цьому уроці?
Мінімальна, але робоча агент-орієнтована модель евакуації натовпу: Social Force Model — сила бажаного напрямку, відштовхування між агентами, відштовхування від стін і один вихід — близько 100 рядків JavaScript.
Які теми розглядаються в цьому уроці?
Цей урок охоплює такі теми: Стан агента, Бажана сила до виходу, Відштовхування агент-агент, Відштовхування від стін, Повна симуляція на ~100 рядків, Розширення моделі.
Скільки часу займає цей урок?
Цей урок займає приблизно 20 хв.
Які попередні знання потрібні?
Це урок рівня «Середній рівень» — окрема попередня підготовка, крім базового JavaScript, не потрібна.