Механизм коллбэков в Ammo.js

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

Коллбэк — это функция, которая вызывается движком автоматически при наступлении определённого события. В Ammo.js такие события чаще всего связаны с столкновениями тел, событиями триггеров или изменениями состояния динамических объектов.


Структура коллбэков столкновений

В Ammo.js обработка столкновений происходит на уровне контактов тел. Основной объект для отслеживания контактов — btCollisionWorld. Через него можно получить информацию о каждом контакте между телами.

Классический подход:

  1. Создание света мира:
let collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
let dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
let overlappingPairCache = new Ammo.btDbvtBroadphase();
let solver = new Ammo.btSequentialImpulseConstraintSolver();

let dynamicsWorld = new Ammo.btDiscreteDynamicsWorld(
    dispatcher,
    overlappingPairCache,
    solver,
    collisionConfiguration
);
  1. Добавление тел в мир:
dynamicsWorld.addRigidBody(rigidBody1);
dynamicsWorld.addRigidBody(rigidBody2);
  1. Получение информации о столкновениях:
let numManifolds = dispatcher.getNumManifolds();
for (let i = 0; i < numManifolds; i++) {
    let contactManifold = dispatcher.getManifoldByIndexInternal(i);
    let body0 = contactManifold.getBody0();
    let body1 = contactManifold.getBody1();

    let numContacts = contactManifold.getNumContacts();
    for (let j = 0; j < numContacts; j++) {
        let pt = contactManifold.getContactPoint(j);
        if (pt.getDistance() < 0) {
            let position = pt.getPositionWorldOnA();
            let normal = pt.getNormalWorldOnB();
            // Здесь можно обрабатывать столкновение
        }
    }
}

Ключевой момент: Ammo.js не имеет встроенного глобального события onCollision. Все коллбэки реализуются через перебор контактных манифолдов в цикле симуляции.


Пользовательские коллбэки через btCollisionWorld::ContactResultCallback

Для более удобной работы можно использовать класс Ammo.ConcreteContactResultCallback, который позволяет отслеживать столкновения конкретного тела с любыми другими объектами:

let callback = new Ammo.ConcreteContactResultCallback();
callback.addSingleResult = function(cp, colObj0Wrap, partId0, index0, colObj1Wrap, partId1, index1) {
    let collisionPoint = cp.getPositionWorldOnA();
    let collisionNormal = cp.getNormalWorldOnB();
    console.log("Столкновение в точке:", collisionPoint.x(), collisionPoint.y(), collisionPoint.z());
};

dynamicsWorld.contactTest(rigidBody, callback);

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

  • contactTest проверяет столкновения конкретного тела.
  • Метод addSingleResult вызывается для каждого контакта.
  • Можно создавать свои классы коллбэков, наследуя Ammo.ConcreteContactResultCallback.

Коллбэки при триггерах

Ammo.js позволяет использовать триггерные объекты — тела, которые не реагируют физически на столкновения, но отслеживают проникновение других тел.

Для реализации триггеров:

  1. Создать тело с флагом CF_NO_CONTACT_RESPONSE:
let shape = new Ammo.btBoxShape(new Ammo.btVector3(1,1,1));
let motionState = new Ammo.btDefaultMotionState(transform);
let rbInfo = new Ammo.btRigidBodyConstructionInfo(0, motionState, shape, new Ammo.btVector3(0,0,0));
let triggerBody = new Ammo.btRigidBody(rbInfo);
triggerBody.setCollisionFlags(triggerBody.getCollisionFlags() | Ammo.cf_noContactResponse);
dynamicsWorld.addRigidBody(triggerBody);
  1. Использовать контактный коллбэк для проверки пересечений:
let triggerCallback = new Ammo.ConcreteContactResultCallback();
triggerCallback.addSingleResult = function(cp, colObj0Wrap, partId0, index0, colObj1Wrap, partId1, index1) {
    console.log("Объект вошёл в триггер");
};

dynamicsWorld.contactTest(triggerBody, triggerCallback);

Ключевой момент: даже без физического отклика объекты можно отслеживать через коллбэки.


Оптимизация работы коллбэков

  1. Фильтрация по типу тела В коллбэках часто требуется реагировать только на определённые объекты. Можно проверять getUserIndex() или хранить ссылку на объект в userPointer.
let userPointer = {name: "player"};
rigidBody.setUserPointer(userPointer);

В коллбэке:

let obj = Ammo.castObject(colObj1Wrap.getCollisionObject().getUserPointer(), Ammo.btVector3);
if(obj.name === "player") { /* обработка */ }
  1. Сокращение количества проверок Не проверять все тела мира каждый кадр. Использовать contactTest или contactPairTest для конкретных объектов.

  2. Минимизация аллокаций Ammo.js работает через WebAssembly. Частое создание btVector3 или btTransform может замедлять симуляцию. Рекомендуется использовать заранее созданные объекты и переиспользовать их.


Коллбэки динамических изменений

Некоторые коллбэки могут реагировать не только на столкновения, но и на изменения состояния тел:

  • Изменение позиции или вращения.
  • Достижение определённой скорости.
  • Изменение активности (isActive()).

Пример отслеживания падения объекта:

if(rigidBody.getLinearVelocity().length() > threshold) {
    console.log("Объект движется слишком быстро");
}

Эти проверки обычно реализуются в основном игровом цикле, но по сути выполняют роль пользовательских коллбэков.


Сводка по типам коллбэков

Тип коллбэка Использование Методы
Столкновения Реакция на контакт между телами dispatcher.getManifoldByIndexInternal, contactTest
Триггеры Отслеживание пересечений без физической реакции CF_NO_CONTACT_RESPONSE, contactTest
Динамика Проверка состояния тел getLinearVelocity, getWorldTransform, isActive
Пользовательские Полная кастомизация реакции Наследование ConcreteContactResultCallback, переопределение addSingleResult

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