btCollisionWorld::rayTest и ClosestRayResultCallback

Библиотека Ammo.js является JavaScript-портом физического движка Bullet Physics, предоставляя мощные средства для симуляции твердых тел, столкновений и динамики в 3D. Одной из ключевых возможностей является проведение лучевых тестов (ray casting) для определения пересечений объектов в сцене. Основными инструментами для этого являются метод btCollisionWorld::rayTest и класс ClosestRayResultCallback.


Принцип работы rayTest

Метод btCollisionWorld::rayTest выполняет лучевой запрос в физическом мире, проверяя пересечения луча с объектами сцены. Он принимает на вход:

  • Начальную точку луча (from), объект типа btVector3.
  • Конечную точку луча (to), объект типа btVector3.
  • Callback, который обрабатывает результаты пересечения.

Пример базовой структуры вызова:

let from = new Ammo.btVector3(0, 10, 0);
let to = new Ammo.btVector3(0, -10, 0);

let rayCallback = new Ammo.ClosestRayResultCallback(from, to);

physicsWorld.rayTest(from, to, rayCallback);

В этом коде physicsWorld — это экземпляр btCollisionWorld или btDiscreteDynamicsWorld.


Класс ClosestRayResultCallback

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

  • hasHit() — возвращает true, если луч пересек объект.
  • get_m_hitPointWorld() — возвращает точку пересечения в мировых координатах (btVector3).
  • get_m_hitNormalWorld() — нормаль поверхности в точке столкновения.
  • get_m_collisionObject() — объект столкновения (btCollisionObject).
  • set_m_collisionFilterMask(mask) и set_m_collisionFilterGroup(group) — фильтры для ограничения поиска столкновений по группам и маскам.

Пример обработки результатов:

if (rayCallback.hasHit()) {
    let collisionPoint = rayCallback.get_m_hitPointWorld();
    let collisionNormal = rayCallback.get_m_hitNormalWorld();
    let collidedObject = rayCallback.get_m_collisionObject();

    console.log("Точка столкновения:", collisionPoint.x(), collisionPoint.y(), collisionPoint.z());
    console.log("Нормаль столкновения:", collisionNormal.x(), collisionNormal.y(), collisionNormal.z());
}

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

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

rayCallback.set_m_collisionFilterGroup(1); // Группа луча
rayCallback.set_m_collisionFilterMask(2);  // Маска объектов, с которыми разрешено столкновение

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


Практические сценарии

  1. Проверка видимости: можно проверить, виден ли объект из точки камеры или персонажа.
  2. Выбор объектов мышью: проекция луча от экрана в мировое пространство и определение ближайшего объекта.
  3. Проектирование AI и траекторий: использование лучей для навигации и предотвращения столкновений.
  4. Физические взаимодействия: ударные или бросковые действия, где важно узнать точку контакта.

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

  • ClosestRayResultCallback возвращает только ближайшее пересечение. Для получения всех пересечений используется AllHitsRayResultCallback.
  • Все объекты, участвующие в проверке, должны быть добавлены в мир через addRigidBody или аналогичные методы.
  • После использования btVector3 и callback объекты необходимо освобождать через Ammo.destroy() для предотвращения утечек памяти.
  • Координаты возвращаются как btVector3. Для интеграции с Three.js или другими движками их нужно конвертировать в массив или объект в формате {x, y, z}.

Пример полного сценария

let from = new Ammo.btVector3(player.position.x, player.position.y, player.position.z);
let to = new Ammo.btVector3(player.position.x, player.position.y - 100, player.position.z);

let rayCallback = new Ammo.ClosestRayResultCallback(from, to);
physicsWorld.rayTest(from, to, rayCallback);

if (rayCallback.hasHit()) {
    let point = rayCallback.get_m_hitPointWorld();
    let normal = rayCallback.get_m_hitNormalWorld();
    let hitObj = rayCallback.get_m_collisionObject();

    console.log(`Объект столкновения: ${hitObj}`);
    console.log(`Точка: (${point.x()}, ${point.y()}, ${point.z()})`);
    console.log(`Нормаль: (${normal.x()}, ${normal.y()}, ${normal.z()})`);
}

Ammo.destroy(rayCallback);
Ammo.destroy(from);
Ammo.destroy(to);

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


Рекомендации по производительности

  • Использовать фильтры для уменьшения числа проверок.
  • Минимизировать создание новых btVector3 внутри игрового цикла, переиспользуя объекты.
  • При множественных лучах предпочтительнее объединять их в массивы и обрабатывать батчами через AllHitsRayResultCallback или кастомные callback.

Эти инструменты позволяют создавать сложные физические взаимодействия, реализовывать точное управление столкновениями и улучшать реализм физики в браузерных 3D-приложениях на JavaScript.