Логирование контактов и коллизий

Ammo.js — это JavaScript-порт физического движка Bullet, ориентированный на работу с трёхмерной физикой в браузере. Одним из ключевых аспектов физического симулятора является отслеживание столкновений объектов. Логирование контактов и коллизий позволяет анализировать взаимодействия объектов, отладить физическое поведение сцены и реализовать игровую механику на основе столкновений.


Объекты для отслеживания контактов

В Ammo.js все физические объекты создаются как экземпляры btRigidBody, прикреплённые к btDiscreteDynamicsWorld. Каждое столкновение фиксируется на уровне контактного диспетчера (btDispatcher). Основной подход к логированию контактов состоит в обходе всех манифолд-контактов (contact manifolds) после шага симуляции:

const dispatcher = physicsWorld.getDispatcher();
const numManifolds = dispatcher.getNumManifolds();

for (let i = 0; i < numManifolds; i++) {
    const contactManifold = dispatcher.getManifoldByIndexInternal(i);
    const body0 = contactManifold.getBody0();
    const body1 = contactManifold.getBody1();
    
    const numContacts = contactManifold.getNumContacts();
    for (let j = 0; j < numContacts; j++) {
        const contactPoint = contactManifold.getContactPoint(j);
        if (contactPoint.getDistance() < 0) {
            const pointA = contactPoint.getPositionWorldOnA();
            const pointB = contactPoint.getPositionWorldOnB();
            const normal = contactPoint.getNormalWorldOnB();
            console.log(`Collision between body0 and body1`);
            console.log(`Point A: (${pointA.x()}, ${pointA.y()}, ${pointA.z()})`);
            console.log(`Point B: (${pointB.x()}, ${pointB.y()}, ${pointB.z()})`);
            console.log(`Normal: (${normal.x()}, ${normal.y()}, ${normal.z()})`);
        }
    }
}

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

  • getNumManifolds() возвращает количество контактных манифолд объектов, участвующих в столкновениях.
  • getBody0() и getBody1() — ссылки на объекты, участвующие в конкретном столкновении.
  • getNumContacts() и getContactPoint(j) позволяют пройтись по всем точкам контакта между телами.
  • getDistance() определяет, соприкасаются ли объекты (< 0 означает пересечение).
  • getPositionWorldOnA() и getPositionWorldOnB() дают координаты точек контакта в мировых координатах.
  • getNormalWorldOnB() возвращает вектор нормали столкновения относительно второго тела.

Настройка фильтров и категорий столкновений

Для оптимизации логирования и предотвращения лишних вычислений можно использовать маски столкновений. Каждое тело получает collisionFilterGroup и collisionFilterMask при создании btRigidBody:

const group = 1; // Например, группа динамических объектов
const mask = 2;  // Будет сталкиваться только с объектами маски 2
physicsWorld.addRigidBody(body, group, mask);

Использование масок позволяет фильтровать объекты, чтобы логировать только интересующие взаимодействия, например, игрока с препятствиями или снаряды с окружением.


Расширенное логирование: силы контакта и импульсы

Ammo.js предоставляет методы для получения информации о силе взаимодействия. Для каждой точки контакта можно получить силу реакции и импульс:

const appliedImpulse = contactPoint.getAppliedImpulse();
console.log(`Impulse: ${appliedImpulse}`);
  • getAppliedImpulse() возвращает величину импульса, действующего в точке контакта.
  • Эти данные позволяют реализовать более сложную механику: детектировать сильные удары, вычислять повреждения или физические эффекты (отскок, разрушение).

Использование коллбеков и пользовательских обработчиков

Ammo.js поддерживает создание собственных обработчиков столкновений через наследование btCollisionWorld::ContactResultCallback. Пример пользовательского коллбека:

class MyContactCallback extends Ammo.ConcreteContactResultCallback {
    constructor() { super(); }
    addSingleResult(cp, colObj0Wrap, partId0, index0, colObj1Wrap, partId1, index1) {
        const body0 = Ammo.castObject(colObj0Wrap.getCollisionObject(), Ammo.btRigidBody);
        const body1 = Ammo.castObject(colObj1Wrap.getCollisionObject(), Ammo.btRigidBody);
        console.log(`Custom callback: collision detected between body0 and body1`);
        return 0; // Возвращаем 0 для продолжения обработки
    }
}

const callback = new MyContactCallback();
physicsWorld.contactTest(body, callback);

Особенности:

  • contactTest() проверяет столкновения конкретного тела с остальными объектами в мире.
  • addSingleResult() вызывается для каждой точки контакта, давая возможность выполнять любые действия при столкновении, включая игровую логику или спецэффекты.
  • Для массовых проверок используется contactPairTest() между двумя телами или группами тел.

Практические советы по логированию

  1. Снижение нагрузки: проверка всех манифолд-контактов может быть ресурсоёмкой, особенно при большом количестве объектов. Использовать маски и выборочные contactTest() для оптимизации.
  2. Сохранение истории контактов: можно сохранять данные о точках столкновения в массив или объект для анализа после симуляции, что полезно при отладке сложной физики.
  3. Визуализация: координаты точек контакта и нормали удобно отображать с помощью three.js для наглядного дебага.

Связь с динамическими эффектами

Логирование контактов тесно связано с реализацией динамических эффектов:

  • Отскок и амортизация: корректное вычисление нормали и импульса позволяет точно моделировать отскок объектов.
  • Детектирование столкновений для геймплея: при попадании снаряда в объект можно запускать анимацию разрушения или наносить урон.
  • Физические триггеры: использование коллбеков для активации скриптов при контакте объектов, например, открытие дверей или активация ловушек.

Ammo.js предоставляет мощные инструменты для детального анализа коллизий, позволяя управлять физикой сцены на уровне точек контакта, импульсов и нормалей. Это фундаментальная основа для разработки реалистичных и управляемых физических взаимодействий в браузерных 3D-приложениях.