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

Cannon.js — это библиотека для симуляции физики в JavaScript, ориентированная на создание реалистичных взаимодействий объектов в 3D-пространстве. Центральным элементом является мир (World), который управляет телами (Bodies), их коллизиями и взаимодействиями с внешними силами.

Создание мира начинается с инициализации объекта CANNON.World и задания глобальных параметров, таких как гравитация:

const world = new CANNON.World();
world.gravity.set(0, -9.82, 0); // гравитация по оси Y
world.broadphase = new CANNON.NaiveBroadphase(); // простой алгоритм broadphase
world.solver.iterations = 10; // точность решения столкновений

Для тел используется CANNON.Body, который принимает массу, форму (Shape) и начальные параметры позиции и ориентации. Формы бывают разнообразные: Box, Sphere, Plane, ConvexPolyhedron.

const groundBody = new CANNON.Body({
    mass: 0, // статическое тело
    shape: new CANNON.Plane(),
    position: new CANNON.Vec3(0, 0, 0)
});
world.addBody(groundBody);

Рейкастинг в Cannon.js

Рейкастинг — это метод определения пересечения луча с объектами в мире. Он часто используется для детектирования поверхности под персонажем, проверки видимости, прицеливания и других взаимодействий.

В Cannon.js рейкастинг осуществляется через CANNON.Ray. Луч имеет начальную точку (from), конечную точку (to) и ряд параметров для точного контроля:

  • collisionFilterMask — какие группы объектов проверять;
  • skipBackfaces — игнорировать обратные стороны объектов;
  • mode — режим работы: ANY, CLOSEST, ALL.
const ray = new CANNON.Ray(fromVec3, toVec3);
ray.intersectWorld(world, {
    mode: CANNON.Ray.ALL,
    skipBackfaces: true
});

Метод intersectWorld возвращает информацию о столкновениях:

  • hasHit — было ли пересечение;
  • hitPointWorld — координаты точки контакта;
  • hitNormalWorld — нормаль поверхности в точке пересечения;
  • body — тело, с которым произошло пересечение.

Реализация рейкаста от первого лица

Для персонажа, управляемого от первого лица, требуется постоянная проверка наличия земли под ногами. Основные шаги:

  1. Определение позиции камеры или персонажа — исходная точка луча.
  2. Выбор направления луча — обычно вниз по оси Y.
  3. Длина луча — должна быть чуть больше предполагаемой высоты шага персонажа, чтобы обеспечить корректное обнаружение земли.

Пример:

function checkGround(playerBody) {
    const origin = new CANNON.Vec3(
        playerBody.position.x,
        playerBody.position.y,
        playerBody.position.z
    );
    const down = new CANNON.Vec3(0, -1, 0);
    const rayLength = 1.1; // чуть больше половины роста персонажа

    const ray = new CANNON.Ray(origin, origin.vadd(down.scale(rayLength)));
    const result = new CANNON.RaycastResult();

    ray.intersectWorld(world, {
        collisionFilterMask: -1,
        skipBackfaces: true,
        result: result
    });

    if (result.hasHit) {
        return {
            onGround: true,
            hitPoint: result.hitPointWorld,
            hitNormal: result.hitNormalWorld
        };
    } else {
        return { onGround: false };
    }
}

Настройка точности и производительности

  • Использование CANNON.Ray.CLOSEST для проверки только ближайшего столкновения повышает производительность, если не нужны все объекты под лучом.
  • Установка skipBackfaces = true предотвращает ложные срабатывания на обратной стороне плоских объектов.
  • Учет масок коллизий позволяет игнорировать ненужные объекты, например декоративные элементы или собственные части персонажа.

Продвинутая работа с результатами рейкаста

Для более точного контроля движения персонажа полезно анализировать нормаль поверхности:

const groundInfo = checkGround(playerBody);
if (groundInfo.onGround) {
    const slopeAngle = Math.acos(groundInfo.hitNormal.dot(new CANNON.Vec3(0, 1, 0)));
    if (slopeAngle < Math.PI / 4) {
        // можно ходить
    } else {
        // крутой склон — ограничение движения
    }
}

Такой подход позволяет корректно обрабатывать наклонные поверхности и предотвращать «проваливание» персонажа.


Интеграция с игровым циклом

Рейкастинг от первого лица должен вызываться в каждой итерации обновления мира:

function update(deltaTime) {
    world.step(deltaTime);

    const groundStatus = checkGround(playerBody);
    playerBody.onGro und = groundStatus.onGround;

    // обработка движения
    if (playerBody.onGround) {
        // нормальная ходьба
    } else {
        // падение или прыжок
    }
}

Сочетание рейкастинга с физическим телом позволяет реализовать естественное движение персонажа и точное детектирование поверхности, что критично для платформеров и игр от первого лица.


Особенности взаимодействия с динамическими объектами

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

if (groundInfo.onGround && groundInfo.body.mass > 0) {
    playerBody.position.vadd(groundInfo.body.velocity.scale(deltaTime), playerBody.position);
}

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


Метод рейкаста в Cannon.js обеспечивает точное детектирование земли и других поверхностей, позволяя создавать реалистичное движение от первого лица с корректной обработкой наклонов, динамических объектов и коллизий.