Получение списка контактов: btPersistentManifold

Внутри движка Ammo.js обработка столкновений основана на механизмах библиотеки Bullet Physics. Одним из ключевых элементов этой системы является структура btPersistentManifold — объект, хранящий информацию о контактах между двумя коллидирующими телами.

btPersistentManifold представляет собой набор точек контакта, которые сохраняются между кадрами симуляции. Это позволяет:

  • уменьшить количество повторных вычислений,
  • обеспечить стабильность расчётов,
  • корректно рассчитывать силы отклика,
  • учитывать накопленные импульсы.

Каждая пара объектов, находящихся в контакте, может иметь один или несколько манфолдов, в зависимости от типа коллизии и настроек диспетчера.


Архитектура системы контактов

Система столкновений строится вокруг следующих компонентов:

  • btCollisionWorld
  • btDispatcher
  • btPersistentManifold
  • btManifoldPoint

Последовательность работы:

  1. Обнаружение потенциальной коллизии (broadphase).
  2. Точное вычисление контактов (narrowphase).
  3. Создание или обновление btPersistentManifold.
  4. Использование контактных точек для расчёта импульсов.

btPersistentManifold создаётся диспетчером (btDispatcher) и хранится внутри него до тех пор, пока объекты продолжают находиться в контакте.


Получение списка манфолдов

После выполнения шага симуляции (stepSimulation) можно получить доступ к списку всех текущих контактов через диспетчер:

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

Метод getNumManifolds() возвращает количество активных манфолдов.

Получение конкретного манфолда:

let manifold = dispatcher.getManifoldByIndexInternal(i);

Каждый такой объект — это btPersistentManifold, содержащий информацию о двух телах и их контактных точках.


Доступ к объектам в манфолде

btPersistentManifold хранит ссылки на два объекта:

let body0 = manifold.getBody0();
let body1 = manifold.getBody1();

Эти значения — указатели на btCollisionObject. Для работы с физическими телами требуется приведение к btRigidBody:

let rigidBody0 = Ammo.castObject(body0, Ammo.btRigidBody);
let rigidBody1 = Ammo.castObject(body1, Ammo.btRigidBody);

Это позволяет получить доступ к массе, скорости, пользовательским данным и другим параметрам.


Получение контактных точек

Количество контактных точек в манфолде:

let numContacts = manifold.getNumContacts();

Контактные точки представлены объектами btManifoldPoint.

Получение конкретной точки:

let contactPoint = manifold.getContactPoint(j);

Структура btManifoldPoint

Каждая контактная точка содержит:

  • позицию точки контакта на объекте A
  • позицию точки контакта на объекте B
  • нормаль контакта
  • глубину проникновения
  • импульсы

Основные методы:

let ptA = contactPoint.getPositionWorldOnA();
let ptB = contactPoint.getPositionWorldOnB();
let normal = contactPoint.get_m_normalWorldOnB();
let distance = contactPoint.getDistance();

Интерпретация расстояния

getDistance() возвращает:

  • отрицательное значение — объекты пересекаются
  • ноль — касание
  • положительное значение — зазор

Обычно проверяется условие:

if (distance < 0) {
    // контакт активен
}

Пример полного обхода контактов

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

for (let i = 0; i < numManifolds; i++) {

    let manifold = dispatcher.getManifoldByIndexInternal(i);

    let body0 = Ammo.castObject(
        manifold.getBody0(),
        Ammo.btRigidBody
    );

    let body1 = Ammo.castObject(
        manifold.getBody1(),
        Ammo.btRigidBody
    );

    let numContacts = manifold.getNumContacts();

    for (let j = 0; j < numContacts; j++) {

        let contactPoint = manifold.getContactPoint(j);
        let distance = contactPoint.getDistance();

        if (distance < 0) {

            let positionA = contactPoint.getPositionWorldOnA();
            let positionB = contactPoint.getPositionWorldOnB();
            let normal = contactPoint.get_m_normalWorldOnB();

            console.log("Контакт обнаружен");
        }
    }
}

Жизненный цикл манфолда

btPersistentManifold:

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

Контактные точки могут:

  • переиспользоваться,
  • заменяться новыми,
  • удаляться при потере актуальности.

Это повышает производительность и устойчивость симуляции.


Оптимизация работы с контактами

1. Минимизация перебора

Если требуется отслеживать столкновения конкретного объекта, эффективнее:

  • сохранять ссылки на интересующие тела,
  • проверять только те манфолды, где они участвуют.

2. Использование пользовательских данных

Каждому btRigidBody можно назначить userData в JavaScript-обёртке. Это позволяет:

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

3. Проверка по маске фильтрации

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


Особенности памяти в Ammo.js

Ammo.js использует WebAssembly-обёртку над C++-кодом. Векторы (btVector3) и другие объекты занимают память вне сборщика мусора JavaScript.

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

Ammo.destroy(vector);

Однако объекты, полученные через getContactPoint() или getPositionWorldOnA(), уничтожать не требуется — они управляются внутренним движком.


Использование контактов в игровой логике

Контакты позволяют реализовать:

  • урон при столкновении,
  • определение приземления персонажа,
  • триггеры,
  • звуковые эффекты,
  • реакции частиц.

Пример проверки «на земле»:

if (normal.y() > 0.5) {
    isGrounded = true;
}

Значение нормали позволяет определить направление контакта.


Различие между событиями и ручной проверкой

В Ammo.js отсутствует встроенная система событий столкновения, как в некоторых игровых движках. Поэтому:

  • требуется ручной обход btPersistentManifold,
  • состояние контактов отслеживается самостоятельно,
  • для имитации событий «начало» и «конец» столкновения используется хранение предыдущего состояния.

Для этого создаётся структура:

let previousContacts = new Set();

И сравниваются пары тел между кадрами.


Влияние точности симуляции

Количество и стабильность контактных точек зависит от:

  • значения stepSimulation,
  • частоты обновления,
  • настроек решателя,
  • точности коллизионных форм.

Неправильный timestep может приводить к:

  • пропуску столкновений,
  • дрожанию объектов,
  • нестабильным манфолдам.

Ключевые особенности btPersistentManifold

  • хранит до 4 контактных точек для пары объектов;
  • оптимизирован для повторного использования;
  • используется решателем импульсов;
  • доступен только через диспетчер;
  • не предназначен для ручного создания.

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