Коллизионные маски при рейкасте

Cannon.js — это физический движок для JavaScript, обеспечивающий реалистичное моделирование твердых тел, столкновений и взаимодействий в 3D-пространстве. Одним из ключевых инструментов для оптимизации работы с физикой является рейкаст (raycast) — проверка пересечения луча с объектами сцены. В сочетании с коллизионными масками рейкаст становится гибким инструментом фильтрации объектов.


Принцип работы рейкаста

Рейкаст в Cannon.js создаётся с помощью объекта CANNON.Ray. Основные параметры:

  • from — начальная точка луча (CANNON.Vec3).
  • to — конечная точка луча (CANNON.Vec3).
  • collisionFilterMask — маска для фильтрации объектов, с которыми может пересекаться луч.
  • collisionFilterGroup — группа самого луча для взаимодействия с объектами сцены.

Пример базового создания луча:

const ray = new CANNON.Ray(from, to);
ray.intersectWorld(world, {
  collisionFilterMask: 1,
  collisionFilterGroup: 1,
  skipBackfaces: true
});

Параметры collisionFilterMask и collisionFilterGroup определяют, с какими объектами луч будет проверять столкновения.


Коллизионные группы и маски

Каждое тело в Cannon.js имеет два свойства:

  • collisionFilterGroup — указывает группу, к которой принадлежит объект.
  • collisionFilterMask — указывает группы, с которыми объект может сталкиваться.

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

Пример назначения группы и маски:

const sphere = new CANNON.Body({
  mass: 5,
  shape: new CANNON.Sphere(1),
  collisionFilterGroup: 2, // принадлежит группе 2
  collisionFilterMask: 1 | 4 // сталкивается с группами 1 и 4
});

В этом примере сфера может взаимодействовать только с объектами из групп 1 и 4, игнорируя все остальные.


Применение коллизионных масок в рейкасте

При использовании ray.intersectWorld можно задать маску через объект options:

ray.intersectWorld(world, {
  collisionFilterMask: 1 | 2, // проверка только объектов из групп 1 и 2
  collisionFilterGroup: 8,    // луч принадлежит группе 8
  skipBackfaces: false
});

Если луч принадлежит группе, которая не указана в маске объекта, столкновение не будет засчитано. Это позволяет:

  1. Исключить ненужные объекты из проверки (оптимизация).
  2. Создать специализированные лучи, например, для детекции врагов, не влияя на объекты окружения.
  3. Разделять лучи по функционалу — одни могут “видеть” только стены, другие — только персонажей.

Практический пример: детекция врагов

Предположим, что есть игрок и враги, каждый с собственными коллизионными группами:

const PLAYER_GROUP = 1;
const ENEMY_GROUP = 2;
const ENV_GROUP = 4;

// тело врага
const enemy = new CANNON.Body({
  mass: 0,
  shape: new CANNON.Box(new CANNON.Vec3(1,1,1)),
  collisionFilterGroup: ENEMY_GROUP,
  collisionFilterMask: PLAYER_GROUP
});

// тело стены
const wall = new CANNON.Body({
  mass: 0,
  shape: new CANNON.Box(new CANNON.Vec3(5,5,1)),
  collisionFilterGroup: ENV_GROUP,
  collisionFilterMask: PLAYER_GROUP | ENEMY_GROUP
});

// создание луча для проверки попадания игрока во врагов
const ray = new CANNON.Ray(player.position, enemy.position);
ray.intersectWorld(world, {
  collisionFilterMask: ENEMY_GROUP, // игнорируем стены
  collisionFilterGroup: PLAYER_GROUP
});

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


Особенности и рекомендации

  • Использование битовых операций: маски задаются через битовые маски, например 1 << n. Это позволяет гибко комбинировать группы.
  • skipBackfaces: если выставлено в true, луч не учитывает обратные стороны треугольников, что полезно для визуальных эффектов и оптимизации.
  • Интеграция с Body и World: рейкасты работают только с телами, которые находятся в world.bodies и имеют физические формы (shape).
  • Множественные пересечения: метод intersectWorld позволяет получать массив всех пересечений с объектами, удовлетворяющими маске.

Пример получения всех попаданий:

const hits = [];
ray.intersectWorld(world, {
  collisionFilterMask: ENEMY_GROUP,
  collisionFilterGroup: PLAYER_GROUP,
  mode: CANNON.Ray.ALL,
  result: hits
});
console.log(hits.length); // количество объектов, с которыми пересёкся луч
  • Рейкаст с групповой фильтрацией снижает нагрузку на движок, так как проверка производится только с необходимыми объектами.
  • Комбинации масок позволяют создавать сложные сценарии: например, луч может проверять только объекты с обеими масками, исключая все остальное.

Заключение по функционалу

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


Хотите, я могу подготовить подробную схему визуализации взаимодействия групп и масок для рейкаста, чтобы было проще понять битовую логику?