В основе фильтрации столкновений в Ammo.js лежит механизм групп и масок, унаследованный от физического движка Bullet. Каждое физическое тело (rigid body) может принадлежать к определённой группе столкновений и одновременно иметь маску — набор групп, с которыми ему разрешено взаимодействовать.
Фильтрация происходит до расчёта контактов, что позволяет:
Группа — это битовая маска, обозначающая принадлежность тела к определённой категории.
Каждая группа задаётся степенью двойки:
| Назначение | Значение | Бит |
|---|---|---|
| Группа 1 | 1 | 0001 |
| Группа 2 | 2 | 0010 |
| Группа 3 | 4 | 0100 |
| Группа 4 | 8 | 1000 |
Использование степеней двойки позволяет комбинировать группы через
побитовое ИЛИ (|).
Маска определяет, с какими группами объект может сталкиваться.
Если:
(groupA & maskB) !== 0
и
(groupB & maskA) !== 0
то объекты взаимодействуют.
Иначе столкновение игнорируется полностью — даже широкая фаза (broadphase) не будет учитывать такую пару.
В Bullet (и, соответственно, в Ammo.js) определены базовые группы:
btBroadphaseProxy.DefaultFilterbtBroadphaseProxy.StaticFilterbtBroadphaseProxy.KinematicFilterbtBroadphaseProxy.DebrisFilterbtBroadphaseProxy.SensorTriggerbtBroadphaseProxy.CharacterFilterbtBroadphaseProxy.AllFilterВ Ammo.js эти значения обычно задаются вручную как числа.
Пример соответствия:
DefaultFilter = 1
StaticFilter = 2
KinematicFilter = 4
DebrisFilter = 8
SensorTrigger = 16
CharacterFilter = 32
AllFilter = -1
При добавлении тела в мир используется метод:
physicsWorld.addRigidBody(body, group, mask);
Если параметры group и mask не переданы,
используется DefaultFilter и AllFilter.
const GROUP_STATIC = 1;
const GROUP_DYNAMIC = 2;
const MASK_STATIC = GROUP_DYNAMIC;
const MASK_DYNAMIC = GROUP_STATIC;
// Статическая платформа
physicsWorld.addRigidBody(staticBody, GROUP_STATIC, MASK_STATIC);
// Динамический куб
physicsWorld.addRigidBody(dynamicBody, GROUP_DYNAMIC, MASK_DYNAMIC);
В этом случае:
Группы позволяют выстраивать сложную архитектуру физического взаимодействия.
| Группа | Назначение |
|---|---|
| 1 | Игрок |
| 2 | Враги |
| 4 | Пули |
| 8 | Статика |
| 16 | Триггеры |
const GROUP_PLAYER = 1;
const GROUP_ENEMY = 2;
const GROUP_BULLET = 4;
const GROUP_STATIC = 8;
const GROUP_TRIGGER = 16;
const MASK_PLAYER = GROUP_ENEMY | GROUP_STATIC | GROUP_TRIGGER;
const MASK_ENEMY = GROUP_PLAYER | GROUP_STATIC;
const MASK_BULLET = GROUP_ENEMY | GROUP_STATIC;
const MASK_STATIC = GROUP_PLAYER | GROUP_ENEMY | GROUP_BULLET;
const MASK_TRIGGER = GROUP_PLAYER;
Такая конфигурация обеспечивает:
Битовые операции — ключ к гибкости системы.
const mask = GROUP_PLAYER | GROUP_ENEMY;
if (mask & GROUP_PLAYER) {
// группа входит в маску
}
Фильтрация через группы снижает нагрузку на:
При большом количестве объектов (1000+) правильная конфигурация групп может уменьшить число проверяемых пар на порядок.
Использование AllFilter для всех тел:
physicsWorld.addRigidBody(body);
Это приводит к проверке каждого тела со всеми остальными.
После добавления в мир изменить фильтрацию напрямую нельзя. Требуется:
physicsWorld.removeRigidBody(body);
physicsWorld.addRigidBody(body, newGroup, newMask);
Это важно учитывать при реализации:
Триггеры создаются как объекты без физического отклика, но с проверкой пересечений.
Необходимо:
body.setCollisionFlags(
body.getCollisionFlags() | 4 // CF_NO_CONTACT_RESPONSE
);
И добавить в мир с нужной группой:
physicsWorld.addRigidBody(body, GROUP_TRIGGER, MASK_TRIGGER);
Такие тела:
Kinematic-тела обычно:
Пример:
const GROUP_KINEMATIC = 32;
physicsWorld.addRigidBody(
kinematicBody,
GROUP_KINEMATIC,
GROUP_DYNAMIC
);
Это исключает их столкновение со статикой и друг с другом.
const GROUP_PLAYER = 1;
const GROUP_ENEMY = 2;
const GROUP_BULLET = 4;
const GROUP_STATIC = 8;
const GROUP_TRIGGER = 16;
const GROUP_KINEMATIC = 32;
physicsWorld.addRigidBody(playerBody, GROUP_PLAYER, GROUP_ENEMY | GROUP_STATIC | GROUP_TRIGGER);
physicsWorld.addRigidBody(enemyBody, GROUP_ENEMY, GROUP_PLAYER | GROUP_STATIC | GROUP_BULLET);
physicsWorld.addRigidBody(bulletBody, GROUP_BULLET, GROUP_ENEMY | GROUP_STATIC);
physicsWorld.addRigidBody(staticBody, GROUP_STATIC, GROUP_PLAYER | GROUP_ENEMY | GROUP_BULLET);
physicsWorld.addRigidBody(triggerBody, GROUP_TRIGGER, GROUP_PLAYER);
physicsWorld.addRigidBody(kinematicBody, GROUP_KINEMATIC, GROUP_PLAYER | GROUP_ENEMY);
Такая структура обеспечивает:
Внутри Bullet проверка происходит в broadphase через
btBroadphaseProxy.
Каждый proxy содержит:
m_collisionFilterGroupm_collisionFilterMaskПеред созданием пары проверяется:
if ((proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0 &&
(proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask) != 0)
{
// создаётся потенциальная пара
}
Ammo.js полностью повторяет эту логику, так как является Emscripten-портом Bullet.
При проектировании рекомендуется:
Признаки некорректной конфигурации:
Методы проверки:
AllFilter,Фильтрация через группы является фундаментальным инструментом оптимизации и логической организации физической сцены в Ammo.js. Правильная архитектура collision-групп позволяет контролировать взаимодействия на уровне движка, не перегружая код дополнительными проверками.