Raycasting в контексте физического движка Ammo.js представляет собой процесс проверки пересечения луча с телами физического мира. Этот инструмент используется для обнаружения столкновений, реализации прицеливания, сенсоров и AI-наблюдения. Производительность raycast напрямую зависит от частоты вызовов и числа объектов в сцене, поэтому грамотная оптимизация критически важна для поддержания высокой частоты кадров.
Ammo.js является портом C++-библиотеки Bullet Physics на JavaScript через Emscripten. В ней существуют несколько методов для выполнения raycast:
rayTest(from, to, resultCallback) –
базовый метод, проверяющий пересечение луча с телами сцены.rayTestAll(from, to, resultCallback) –
возвращает все столкновения вдоль луча, а не только первое.ClosestRayResultCallback(from, to) –
структура, в которую записывается информация о ближайшем
пересечении.AllHitsRayResultCallback(from, to) –
структура для хранения всех точек пересечения луча с телами.Ключевые моменты для производительности:
btVector3. Их часто
пересоздают на каждый кадр, что увеличивает нагрузку на сборщик мусора.
Оптимально использовать повторно объекты
btVector3 и очищать их вручную через
btVector3.prototype.delete().ClosestRayResultCallback и
AllHitsRayResultCallback выделяют память в WebAssembly, что
также может замедлять работу при высокой частоте вызовов. Рекомендуется
реиспользовать объекты callback, если лучи вызываются
каждый кадр.Частота вызовов raycast напрямую влияет на нагрузку на CPU:
60 fps) – если выполнять
по 50–100 лучей за кадр, это может создать заметное падение FPS на
сложных сценах.Пример подхода с частотой:
let frameCount = 0;
const rayInterval = 3; // Проверять каждые 3 кадра
function updatePhysics() {
frameCount++;
if (frameCount % rayInterval === 0) {
performRaycast();
}
}
Такой метод позволяет снизить количество операций на 60–70%, сохранив функциональность.
Производительность raycast зависит не только от частоты вызовов, но и от числа тел в физическом мире:
collisionFilterMask) позволяет ограничить
проверку только к релевантным объектам. Например, луч AI против стен и
препятствий, но не против всех динамических объектов.ClosestRayResultCallback быстрее, чем AllHitsRayResultCallback, поскольку возвращает только ближайшее пересечение и не собирает массив точек. Для большинства игровых сценариев достаточно ближайшего столкновения.
AllHitsRayResultCallback следует использовать только при необходимости анализа всех пересечений вдоль луча, например, для лазерных сенсоров или визуализации сканирования.
btVector3 и callback –
повторное использование объектов предотвращает частую аллокацию
памяти.requestIdleCallback или Web Worker для тяжелых raycast
позволяет избежать просадок FPS.// Инициализация пулов
const fromVec = new Ammo.btVector3();
const toVec = new Ammo.btVector3();
const rayCallback = new Ammo.ClosestRayResultCallback(fromVec, toVec);
function performRaycast() {
fromVec.setValue(playerX, playerY, playerZ);
toVec.setValue(targetX, targetY, targetZ);
rayCallback.set_m_collisionFilterMask(WALL_MASK | ENEMY_MASK);
physicsWorld.rayTest(fromVec, toVec, rayCallback);
if (rayCallback.hasHit()) {
const hitPoint = rayCallback.get_m_hitPointWorld();
// обработка столкновения
}
}
Этот пример демонстрирует минимизацию аллокаций, фильтрацию по маске столкновений и использование пулов для объектов.
При правильной оптимизации raycast можно выполнять десятки и сотни проверок за кадр без значительного падения производительности. Ключевые факторы:
ClosestRayResultCallback вместо
AllHitsRayResultCallback там, где возможно.Комплексное применение этих техник позволяет сохранять высокую производительность даже в сценах с большим количеством динамических объектов.