Передача пользовательских данных через setUserPointer и getUserPointer

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


Основы работы с setUserPointer и getUserPointer

Каждый объект, унаследованный от btCollisionObject, обладает внутренним полем user pointer, которое по умолчанию пустое. Оно используется для хранения ссылки на произвольный JavaScript-объект, связанный с конкретным физическим телом:

const body = new Ammo.btRigidBody(rbInfo);
body.setUserPointer({ name: "Player", health: 100 });
  • setUserPointer(pointer) — сохраняет произвольный объект или ссылку на объект внутри физического тела.
  • getUserPointer() — возвращает ранее сохранённый объект, либо null, если указатель не установлен.

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


Применение в коллизиях

Чаще всего setUserPointer используется для идентификации объектов при обработке столкновений. События коллизий в Ammo.js возвращают объекты типа btCollisionObject, и с помощью пользовательского указателя можно определить, какой визуальный объект или логическая сущность находится в столкновении.

Пример обработки столкновений:

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

for (let i = 0; i < numManifolds; i++) {
    const contactManifold = dispatcher.getManifoldByIndexInternal(i);
    const bodyA = contactManifold.getBody0();
    const bodyB = contactManifold.getBody1();

    const userA = Ammo.castObject(bodyA.getUserPointer(), Ammo.wrapPointer) || null;
    const userB = Ammo.castObject(bodyB.getUserPointer(), Ammo.wrapPointer) || null;

    if (userA && userB) {
        console.log("Столкновение между:", userA.name, "и", userB.name);
    }
}

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


Особенности работы с JavaScript-объектами

Ammo.js использует внутренний C++ движок Bullet, поэтому при передаче JS-объекта через setUserPointer фактически создается обертка на указатель. Это важно учитывать:

  1. Прямое сохранение сложных объектов или DOM-элементов возможно, но при удалении физического тела необходимо заботиться о сборке мусора, чтобы не оставлять «висячие» ссылки.
  2. Для получения данных в коллизиях часто используют вспомогательные структуры, содержащие только нужные свойства:
const metadata = { id: 42, type: "Enemy" };
body.setUserPointer(metadata);
  1. Для безопасного чтения данных иногда применяют Ammo.wrapPointer, чтобы корректно обработать указатели из C++.

Использование с разными типами объектов

Механизм user pointer работает не только с btRigidBody, но и с:

  • btCollisionObject
  • btGhostObject
  • btSoftBody

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

const ghost = new Ammo.btGhostObject();
ghost.setUserPointer({ zoneName: "SpawnArea" });

dynamicsWorld.addCollisionObject(
    ghost, 
    Ammo.CollisionFilterGroups.Trigger, 
    Ammo.CollisionFilterGroups.All
);

В этом случае при проверке столкновений можно узнать, какой игрок или объект вошёл в область триггера.


Рекомендации по организации данных

  • Минимизировать размер объектов. Чем проще объект, тем меньше накладных расходов и риск утечки памяти.
  • Использовать ссылки на существующие структуры вместо создания новых объектов каждый кадр.
  • Очистка при удалении тел: перед вызовом Ammo.destroy(body) рекомендуется обнулить user pointer:
body.setUserPointer(null);
Ammo.destroy(body);
  • Типизация данных: хранить объекты с предсказуемой структурой, чтобы избежать ошибок при чтении через getUserPointer.

Примеры типовых сценариев

  1. Связывание визуальной модели с телом:
const mesh = { meshId: "box1" };
const body = new Ammo.btRigidBody(rbInfo);
body.setUserPointer(mesh);
  1. Определение участника коллизии по ID:
const data = { id: 7, faction: "Allies" };
body.setUserPointer(data);

// В обработчике столкновений:
const user = body.getUserPointer();
console.log(user.id, user.faction);
  1. Использование в игровых триггерах:
triggerZone.setUserPointer({ event: "HealZone", value: 50 });

if (playerBody.getUserPointer() === triggerZone.getUserPointer()) {
    playerHealth += triggerZone.getUserPointer().value;
}

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