Raycasting для ИИ: проверка видимости и препятствий

Raycasting — это процесс отправки виртуального луча в сцену для обнаружения столкновений с объектами. В контексте ИИ он используется для проверки видимости, определения препятствий и расчёта траектории движения. В Ammo.js, портированной версии Bullet Physics для JavaScript, raycasting реализуется через класс btCollisionWorld и методы типа rayTest.

Луч создаётся между двумя точками: источник (from) и конечная точка (to). Ammo.js проверяет пересечение луча с коллайдерами физического мира и возвращает информацию о точке столкновения, нормали поверхности и объекте.

let from = new Ammo.btVector3(x1, y1, z1);
let to = new Ammo.btVector3(x2, y2, z2);
let rayCallback = new Ammo.ClosestRayResultCallback(from, to);

physicsWorld.rayTest(from, to, rayCallback);

if(rayCallback.hasHit()) {
    let hitPoint = rayCallback.get_hitPointWorld();
    let hitNormal = rayCallback.get_hitNormalWorld();
    console.log(hitPoint.x(), hitPoint.y(), hitPoint.z());
}

Ключевые элементы:

  • btVector3 — трёхмерный вектор позиции начала и конца луча.
  • ClosestRayResultCallback — callback, возвращающий информацию о ближайшем пересечении.
  • hasHit() — проверка, было ли столкновение.
  • get_hitPointWorld() и get_hitNormalWorld() — координаты точки контакта и нормали поверхности.

Множественные варианты Raycasting

Для ИИ часто важно не только ближайшее столкновение, но и все возможные объекты на пути луча. Для этого используется AllHitsRayResultCallback. Он возвращает массивы точек столкновений, нормалей и ссылок на объекты.

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

if(rayCallback.hasHit()) {
    let hitPoints = rayCallback.get_m_hitPointWorld();
    let hitNormals = rayCallback.get_m_hitNormalWorld();
    let collisionObjects = rayCallback.get_m_collisionObjects();
    
    for(let i = 0; i < hitPoints.size(); i++) {
        let point = hitPoints.at(i);
        console.log(`Hit at: ${point.x()}, ${point.y()}, ${point.z()}`);
    }
}

Использование AllHits позволяет ИИ анализировать окружающее пространство, учитывать все препятствия и выбирать оптимальный путь.

Интеграция с логикой ИИ

Raycasting применяется для:

  • Проверки видимости целей: луч от позиции ИИ к цели проверяет, есть ли преграда между ними.
  • Обнаружения препятствий: передвижение персонажей избегает столкновений с объектами.
  • Навигации и построения траектории: комбинируется с алгоритмами поиска пути (A*, навмеши), чтобы предсказывать безопасные маршруты.

Пример проверки видимости:

function canSeeTarget(aiPos, targetPos) {
    let from = new Ammo.btVector3(aiPos.x, aiPos.y, aiPos.z);
    let to = new Ammo.btVector3(targetPos.x, targetPos.y, targetPos.z);
    let rayCallback = new Ammo.ClosestRayResultCallback(from, to);

    physicsWorld.rayTest(from, to, rayCallback);
    return !rayCallback.hasHit(); // true если путь свободен
}

Оптимизация Raycasting

Для больших сцен и частого использования raycasting необходимо учитывать производительность:

  1. Ограничение диапазона луча: не проверять объекты на слишком большом расстоянии.
  2. Использование слоёв коллизий: проверять столкновения только с нужными типами объектов.
  3. Кеширование результатов: для частых проверок, например, когда объект почти не двигается.
  4. Пакетный raycasting: запуск нескольких лучей одновременно для анализа зоны обзора.
rayCallback.set_m_collisionFilterMask(Ammo.btBroadphaseProxy.DefaultFilter);

С помощью фильтров можно исключить нежелательные коллайдеры и ускорить расчёты.

Применение для зон обзора

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

Пример разбрасывания лучей по конусу:

for(let angle = -FOV/2; angle <= FOV/2; angle += step) {
    let dx = Math.cos(angle) * maxDistance;
    let dz = Math.sin(angle) * maxDistance;
    let to = new Ammo.btVector3(aiPos.x + dx, aiPos.y, aiPos.z + dz);
    let rayCallback = new Ammo.ClosestRayResultCallback(from, to);
    physicsWorld.rayTest(from, to, rayCallback);
    if(rayCallback.hasHit()) {
        // Объект в зоне видимости
    }
}

Такой метод обеспечивает реалистичное поведение ИИ без необходимости проверять все объекты в мире.

Управление результатами столкновений

Ammo.js возвращает данные в формате Bullet:

  • btVector3 — точка столкновения.
  • btCollisionObject — ссылка на объект.
  • Нормаль поверхности — направление, отталкивающее ИИ.

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


Raycasting в Ammo.js сочетает точность физического движка Bullet с гибкостью JavaScript, позволяя создавать сложное, реактивное поведение ИИ, моделировать видимость и препятствия, а также оптимизировать производительность в больших сценах.