В физическом движке на базе Bullet, реализованном в виде WebAssembly-библиотеки Ammo.js, столкновения тел описываются набором структур, содержащих параметры контакта. Ключевыми величинами являются:
Эти данные формируются на этапе узкофазной проверки столкновений (narrowphase) и используются солвером для расчёта импульсов, трения и корректировки позиций тел.
После выполнения шага симуляции (world.stepSimulation)
физический мир формирует набор диспетчеров столкновений. Получение
контактных данных осуществляется через:
const dispatcher = physicsWorld.getDispatcher();
const numManifolds = dispatcher.getNumManifolds();
Каждый btPersistentManifold содержит контактную
информацию для пары тел. Для доступа:
for (let i = 0; i < numManifolds; i++) {
const manifold = dispatcher.getManifoldByIndexInternal(i);
const body0 = manifold.getBody0();
const body1 = manifold.getBody1();
const numContacts = manifold.getNumContacts();
for (let j = 0; j < numContacts; j++) {
const contactPoint = manifold.getContactPoint(j);
}
}
Объект btManifoldPoint содержит нормаль, глубину
проникновения и позиции контакта.
Нормаль контакта — это вектор, направленный перпендикулярно поверхности соприкосновения тел. В Ammo.js она определяется в пространстве мира.
Получение нормали:
const normal = contactPoint.get_m_normalWorldOnB();
Возвращается объект btVector3.
Нормаль всегда направлена:
Это принципиально важно при вычислении импульсов:
[ J = -(1 + e) ]
где
Нормаль используется для:
const nx = normal.x();
const ny = normal.y();
const nz = normal.z();
Нормаль обычно нормализована, однако при нестандартных модификациях стоит проверять её длину.
Глубина проникновения (penetration depth) — это величина перекрытия тел вдоль нормали контакта.
Получение:
const distance = contactPoint.getDistance();
Особенности:
distance < 0, тела пересекаются.distance = 0, тела касаются.distance > 0, точки находятся на
расстоянии.В Bullet/Ammo отрицательное значение означает проникновение.
Глубина рассчитывается как:
[ penetration = - (p_B - p_A) n]
где
Глубина влияет на:
Bullet применяет метод Баумгарте (Baumgarte stabilization):
[ bias = -]
где ( ) — коэффициент стабилизации.
if (distance < 0) {
const penetrationDepth = -distance;
}
Каждый btManifoldPoint хранит две позиции:
const positionWorldOnA = contactPoint.getPositionWorldOnA();
const positionWorldOnB = contactPoint.getPositionWorldOnB();
Обе точки заданы в мировых координатах.
Несмотря на совпадение по нормали, точки могут отличаться из-за:
Солвер использует среднюю точку либо одну из них в зависимости от внутренней логики.
Позиция контакта необходима для:
[ = r F]
где
( r ) — радиус-вектор от центра массы до точки контакта
( F ) — контактная сила
визуализации точек столкновения,
создания триггеров,
построения пользовательской логики столкновений.
Пример получения координат:
const px = positionWorldOnA.x();
const py = positionWorldOnA.y();
const pz = positionWorldOnA.z();
Контактная структура образует целостную систему:
| Параметр | Назначение |
|---|---|
| Нормаль | направление реакции |
| Глубина | величина коррекции |
| Позиция | точка приложения силы |
Алгоритм работы солвера:
btPersistentManifold сохраняет контактные точки между
кадрами для:
Каждый манифолд может содержать до 4 контактных точек.
При каждом шаге:
При интеграции с Three.js контактные точки часто используются для:
Пример преобразования:
const contactVector = new THREE.Vector3(
positionWorldOnA.x(),
positionWorldOnA.y(),
positionWorldOnA.z()
);
Получение контактных данных — операция относительно дорогая, особенно при большом количестве тел.
Рекомендации:
btVector3.После использования временные объекты следует освобождать:
Ammo.destroy(positionWorldOnA);
Положительное значение не означает проникновение.
Неверное направление приводит к неправильному расчёту отражения.
Методы getPositionWorldOnA/B возвращают координаты в
world-space.
Ammo.js не имеет автоматического сборщика мусора для C++-структур.
for (let i = 0; i < dispatcher.getNumManifolds(); i++) {
const manifold = dispatcher.getManifoldByIndexInternal(i);
const numContacts = manifold.getNumContacts();
for (let j = 0; j < numContacts; j++) {
const pt = manifold.getContactPoint(j);
const normal = pt.get_m_normalWorldOnB();
const distance = pt.getDistance();
const posA = pt.getPositionWorldOnA();
if (distance < 0) {
console.log(
"Contact:",
"Normal:", normal.x(), normal.y(), normal.z(),
"Penetration:", -distance,
"Position:", posA.x(), posA.y(), posA.z()
);
}
}
}
Контактные данные представляют собой фундаментальный механизм физического движка. Нормаль задаёт направление реакции, глубина определяет степень коррекции, а позиция фиксирует точку приложения импульса. Именно на основе этих трёх величин формируется реалистичное поведение тел при столкновениях в Ammo.js.