Система фильтрации столкновений в Ammo.js основана на механизме collision groups (группы) и collision masks (маски). Этот механизм позволяет управлять тем, какие физические тела могут взаимодействовать друг с другом, а какие должны игнорироваться движком. В основе лежит логика побитовых операций, унаследованная от физического движка Bullet.
Каждое физическое тело в мире:
Столкновение между двумя телами происходит только в том случае, если выполняется условие:
(groupA & maskB) !== 0 && (groupB & maskA) !== 0
Иначе говоря, оба тела должны “разрешать” столкновение друг с другом.
Ammo.js, как порт Bullet Physics в WebAssembly/JavaScript, использует предопределённые группы, аналогичные оригинальному движку:
| Группа | Значение | Назначение |
|---|---|---|
| Default | 1 | Обычные динамические объекты |
| Static | 2 | Статическая геометрия |
| Kinematic | 4 | Кинематические тела |
| Debris | 8 | Обломки |
| SensorTrigger | 16 | Сенсоры |
| Character | 32 | Персонажи |
| All | -1 | Все группы |
В реальной практике чаще используются собственные битовые флаги.
Группы определяются степенями двойки:
const GROUP_STATIC = 1 << 0; // 1
const GROUP_DYNAMIC = 1 << 1; // 2
const GROUP_PLAYER = 1 << 2; // 4
const GROUP_ENEMY = 1 << 3; // 8
const GROUP_PROJECTILE = 1 << 4; // 16
Каждое тело может принадлежать только одной группе, но его маска может включать несколько групп через побитовое OR:
const MASK_PLAYER = GROUP_STATIC | GROUP_ENEMY;
Это означает, что игрок будет сталкиваться только со статическими объектами и врагами.
При добавлении тела в btDiscreteDynamicsWorld
используется метод:
world.addRigidBody(body, group, mask);
Пример:
world.addRigidBody(
playerBody,
GROUP_PLAYER,
GROUP_STATIC | GROUP_ENEMY
);
Если параметры group и mask не указаны,
используется группа Default и маска All.
world.addRigidBody(
groundBody,
GROUP_STATIC,
GROUP_DYNAMIC | GROUP_PLAYER | GROUP_ENEMY | GROUP_PROJECTILE
);
Статика взаимодействует со всеми динамическими объектами.
world.addRigidBody(
playerBody,
GROUP_PLAYER,
GROUP_STATIC | GROUP_ENEMY
);
Игрок не сталкивается с собственными снарядами.
world.addRigidBody(
projectileBody,
GROUP_PROJECTILE,
GROUP_STATIC | GROUP_ENEMY
);
Снаряды не сталкиваются друг с другом.
Фильтрация работает в обе стороны. Если одна сторона разрешает столкновение, а другая — нет, контакта не будет.
Пример:
Результат — столкновение отсутствует.
Поэтому проектирование масок должно учитывать симметричность логики.
Ammo.js поддерживает объекты, которые участвуют в проверке пересечений, но не генерируют физический отклик. Для этого используется флаг:
body.setCollisionFlags(
body.getCollisionFlags() | Ammo.btCollisionObject.CF_NO_CONTACT_RESPONSE
);
Такие объекты:
Типичный пример — зона активации:
world.addRigidBody(
triggerBody,
GROUP_SENSOR,
GROUP_PLAYER
);
Для составных объектов (например, ragdoll или транспорт) можно распределять части по разным группам, чтобы исключить нежелательные самостолкновения.
Пример:
const GROUP_RAGDOLL = 1 << 5;
world.addRigidBody(limb1, GROUP_RAGDOLL, GROUP_STATIC | GROUP_DYNAMIC);
world.addRigidBody(limb2, GROUP_RAGDOLL, GROUP_STATIC | GROUP_DYNAMIC);
Маска не включает GROUP_RAGDOLL, поэтому части не
сталкиваются друг с другом.
Группы и маски участвуют в broadphase-фильтрации — самой первой стадии проверки столкновений. Это означает:
Грамотная настройка групп может существенно снизить нагрузку в сценах с большим количеством объектов.
Изменение фильтрации после добавления тела в мир требует:
world.removeRigidBody(body);
world.addRigidBody(body, newGroup, newMask);
Прямого метода изменения маски “на лету” нет, поскольку фильтрация кэшируется в broadphase.
В многопользовательских проектах создаются группы команд:
const GROUP_TEAM_A = 1 << 6;
const GROUP_TEAM_B = 1 << 7;
Маски исключают дружественный огонь.
Проблемы чаще всего возникают из-за:
Полезная практика — централизованное определение всех групп в одном модуле и строгий контроль распределения битов.
Ammo.js является JavaScript/WebAssembly-портом C++ движка Bullet
Physics. Механизм групп и масок полностью повторяет оригинальную
реализацию btBroadphaseProxy::m_collisionFilterGroup и
m_collisionFilterMask.
Понимание принципов Bullet позволяет применять документацию C++ версии напрямую при работе с Ammo.js, включая расширенную фильтрацию через кастомные broadphase callback-объекты.
В сложных проектах возможно внедрение собственного callback-фильтра:
btOverlapFilterCallback;needBroadphaseCollision.В Ammo.js это требует работы с низкоуровневыми биндингами и Emscripten-интерфейсом, но позволяет:
Группы и маски столкновений являются базовым инструментом оптимизации и архитектурного разделения физического взаимодействия в Ammo.js. Их грамотное использование позволяет создавать масштабируемые физические сцены с предсказуемым поведением и контролируемой производительностью.