Crowd Simulation: агенты и толпа

Симуляция толпы в Babylon.js строится на работе с множеством агентов, каждый из которых обладает собственными свойствами и поведением. Агент в контексте 3D-сцены — это объект с позицией, ориентацией, скоростью и набором правил движения. Основная цель — создание правдоподобного поведения толпы без ручного управления каждым объектом.

Babylon.js предоставляет гибкую систему для управления большим числом объектов через Mesh, InstancedMesh и ThinInstance, что критически важно для производительности при тысячах агентов.


Создание агентов

Агент реализуется как класс с набором ключевых свойств:

class Agent {
    constructor(mesh, position, speed) {
        this.mesh = mesh;
        this.position = position.clone();
        this.velocity = new BABYLON.Vector3(0, 0, 0);
        this.speed = speed;
        this.target = null;
    }

    setTarget(target) {
        this.target = target.clone();
    }

    update(deltaTime) {
        if (!this.target) return;

        const direction = this.target.subtract(this.position).normalize();
        this.velocity = direction.scale(this.speed);
        this.position.addInPlace(this.velocity.scale(deltaTime));
        this.mesh.position.copyFrom(this.position);
    }
}

Ключевые моменты:

  • position хранит текущее положение агента в мире.
  • velocity определяет направление и скорость движения.
  • Метод update отвечает за перемещение агента по направлению к цели с учетом времени между кадрами (deltaTime).

Оптимизация большого количества агентов

Использование отдельных Mesh для каждого агента становится неэффективным при сотнях и тысячах объектов. Для оптимизации применяются:

  • InstancedMesh: позволяет создавать копии одного базового меша с индивидуальными позициями и поворотами. Пример:
const baseMesh = BABYLON.MeshBuilder.CreateBox("agent", { size: 1 }, scene);
const agents = [];

for (let i = 0; i < 500; i++) {
    const instance = baseMesh.createInstance("agent" + i);
    const agent = new Agent(instance, new BABYLON.Vector3(Math.random()*50, 0, Math.random()*50), 1 + Math.random());
    agents.push(agent);
}
  • ThinInstance: эффективен для тысяч объектов с минимальной нагрузкой на GPU, особенно при одинаковой геометрии.

Алгоритмы движения толпы

Поведение толпы строится на нескольких принципах:

  1. Следование цели (Goal Seeking) Агент движется к заранее заданной точке или движущемуся объекту. Используется нормализация вектора направления и умножение на скорость.

  2. Разделение (Separation) Агент избегает столкновений с соседями, создавая вектор отталкивания от близких объектов:

function separation(agent, neighbors, desiredSeparation) {
    let steer = new BABYLON.Vector3(0,0,0);
    let count = 0;
    for (let other of neighbors) {
        const distance = BABYLON.Vector3.Distance(agent.position, other.position);
        if (distance > 0 && distance < desiredSeparation) {
            const diff = agent.position.subtract(other.position).normalize().scale(1/distance);
            steer.addInPlace(diff);
            count++;
        }
    }
    if (count > 0) steer.scaleInPlace(1 / count);
    return steer;
}
  1. Выравнивание (Alignment) Агент старается двигаться в направлении среднего вектора скорости соседей, создавая согласованное движение группы.

  2. Сближение (Cohesion) Агент стремится к среднему положению соседей, удерживая целостность группы.

Эти три принципа (Separation, Alignment, Cohesion) известны как Boids-модель, широко применяемая для симуляции поведения стаи или толпы.


Обновление сцены и анимация

Для реалистичного движения необходимо обновлять всех агентов каждый кадр:

scene.onBeforeRenderObservable.add(() => {
    const deltaTime = engine.getDeltaTime() / 1000; // в секундах
    for (let agent of agents) {
        const sep = separation(agent, agents, 2);
        agent.velocity.addInPlace(sep);
        agent.update(deltaTime);
    }
});

Дополнительно можно добавлять ограничения на скорость, плавное вращение и коллизии с препятствиями, чтобы движение выглядело естественно.


Взаимодействие с окружением

Агенты не должны проходить сквозь стены или объекты сцены. Для этого используются:

  • Collisions: встроенная система Babylon.js для столкновений мешей.
  • NavMesh: сетка навигации, которая задает доступные области для движения агента. Применяется при сложных сценах с лабиринтами или препятствиями.

Пример интеграции NavMesh:

BABYLON.SceneLoader.ImportMesh("", "models/", "navmesh.glb", scene, (meshes) => {
    const navMesh = meshes[0];
    const navigationPlugin = new BABYLON.AStarNavigationPlugin();
    const navSystem = new BABYLON.NavigationHelper(navMesh, navigationPlugin);
    agents.forEach(agent => agent.setTarget(navSystem.getRandomPoint()));
});

Анимация и визуальная реалистичность

Для создания эффекта настоящей толпы важно визуально различать агентов:

  • Разные модели или текстуры для разных агентов.
  • Анимация ходьбы или бега с помощью AnimationGroup.
  • Использование LOD (Level of Detail) для оптимизации рендеринга на дальних дистанциях.

Пример анимации с AnimationGroup:

const walkAnim = scene.getAnimationGroupByName("Walk");
agents.forEach(agent => {
    const anim = walkAnim.clone("WalkClone" + agent.mesh.name);
    anim.start(true, 1.0, anim.from, anim.to, true);
});

Масштабирование и производительность

Для больших толп (тысячи агентов) критично:

  • Использовать ThinInstance вместо отдельных мешей.
  • Минимизировать вычисления на CPU: предварительно рассчитывать направления, использовать простые модели движения.
  • Ограничивать проверку соседей через spatial hashing или grid, чтобы не перебирать всех агентов для Separation/Alignment/Cohesion.

Эти методы позволяют симулировать правдоподобную толпу в Babylon.js без просадок FPS даже на сложных сценах.