Bullet Physics как основа Ammo.js

В основе Ammo.js лежит движок физики твёрдых тел Bullet Physics — высокопроизводительная библиотека, написанная на C++ и широко применяемая в игровых движках и системах симуляции. Ammo.js представляет собой его порт в JavaScript, выполненный с использованием Emscripten, что позволяет запускать нативный C++-код в браузере через WebAssembly или asm.js.

Bullet Physics изначально проектировался как модульная система. Ammo.js наследует эту архитектуру практически без изменений, что означает сохранение тех же классов, структур данных и принципов работы:

  • система динамики твёрдых тел;
  • алгоритмы обнаружения столкновений;
  • решатели ограничений;
  • система широкофазной и узкофазной проверки пересечений;
  • поддержка soft body и транспортных средств.

Понимание внутренней структуры Bullet значительно упрощает работу с Ammo.js, поскольку API во многом повторяет оригинальную C++-модель.


Ключевые подсистемы Bullet Physics

1. Система обнаружения столкновений

Механизм столкновений делится на два уровня:

Широкофазная проверка (Broadphase) Определяет потенциально пересекающиеся объекты, используя упрощённые bounding volumes. В Ammo.js доступны реализации:

  • btDbvtBroadphase
  • btAxisSweep3

Broadphase уменьшает количество проверок, передавая только кандидатов в следующую фазу.

Узкофазная проверка (Narrowphase) Выполняет точные вычисления пересечения геометрий. Используются алгоритмы:

  • GJK (Gilbert-Johnson-Keerthi)
  • EPA (Expanding Polytope Algorithm)

В Ammo.js это реализовано через btCollisionDispatcher.


2. Конфигурация мира столкновений

Физический мир в Bullet строится из нескольких обязательных компонентов:

const collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
const broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();

const dynamicsWorld = new Ammo.btDiscreteDynamicsWorld(
    dispatcher,
    broadphase,
    solver,
    collisionConfiguration
);

Каждый компонент отвечает за конкретную часть пайплайна:

  • CollisionConfiguration — параметры обработки столкновений;
  • Dispatcher — управление узкофазными алгоритмами;
  • Broadphase — грубый отбор пар;
  • Solver — разрешение импульсов и ограничений;
  • DynamicsWorld — центральный координатор.

Модель твёрдого тела

Bullet Physics реализует динамику через класс btRigidBody. Ammo.js сохраняет ту же структуру:

  1. Collision Shape — геометрия столкновения.
  2. Mass — масса объекта.
  3. Motion State — связующее звено между физикой и визуализацией.
  4. Inertia Tensor — распределение массы.

Пример создания динамического тела:

const shape = new Ammo.btBoxShape(new Ammo.btVector3(1, 1, 1));
const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(0, 10, 0));

const motionState = new Ammo.btDefaultMotionState(transform);

const mass = 1;
const localInertia = new Ammo.btVector3(0, 0, 0);
shape.calculateLocalInertia(mass, localInertia);

const rbInfo = new Ammo.btRigidBodyConstructionInfo(
    mass,
    motionState,
    shape,
    localInertia
);

const body = new Ammo.btRigidBody(rbInfo);
dynamicsWorld.addRigidBody(body);

Решатель ограничений

В Bullet используется импульсный метод решения ограничений. Ammo.js по умолчанию применяет btSequentialImpulseConstraintSolver.

Алгоритм работы:

  1. Вычисление контактных точек.
  2. Построение системы ограничений.
  3. Итеративное применение импульсов.
  4. Коррекция скоростей и позиций.

Параметры решателя влияют на стабильность:

dynamicsWorld.getSolverInfo().set_m_numIterations(10);

Увеличение числа итераций повышает точность, но увеличивает нагрузку.


Типы Collision Shapes

Bullet Physics предлагает широкий набор форм:

  • btBoxShape
  • btSphereShape
  • btCapsuleShape
  • btCylinderShape
  • btConvexHullShape
  • btBvhTriangleMeshShape

Convex vs Concave

  • Выпуклые формы (Convex) быстрее обрабатываются.
  • Вогнутые (Concave) применяются для статических объектов, например ландшафтов.

Для оптимизации часто используется составная форма:

const compound = new Ammo.btCompoundShape();
compound.addChildShape(localTransform, childShape);

Шаг симуляции

Основной цикл:

dynamicsWorld.stepSimulation(deltaTime, maxSubSteps);

Параметры:

  • deltaTime — прошедшее время;
  • maxSubSteps — максимальное количество внутренних шагов.

Bullet использует фиксированный внутренний timestep для стабильности.


Soft Body в Bullet и Ammo.js

Bullet поддерживает мягкие тела через btSoftRigidDynamicsWorld. Ammo.js сохраняет эту функциональность при сборке с соответствующими флагами.

Soft body включает:

  • симуляцию ткани;
  • канаты;
  • деформируемые объекты.

Для этого требуется отдельная конфигурация:

const softBodySolver = new Ammo.btDefaultSoftBodySolver();

Система ограничений (Constraints)

Bullet реализует физические связи:

  • btPoint2PointConstraint
  • btHingeConstraint
  • btSliderConstraint
  • btConeTwistConstraint
  • btGeneric6DofConstraint

Пример шарнирного соединения:

const hinge = new Ammo.btHingeConstraint(
    bodyA,
    bodyB,
    pivotA,
    pivotB,
    axisA,
    axisB
);

dynamicsWorld.addConstraint(hinge, true);

Constraints создают кинематические связи, которые затем обрабатываются решателем.


Оптимизация производительности

Поскольку Ammo.js — порт C++ в WebAssembly, возникают особенности:

1. Управление памятью

Объекты Ammo не управляются сборщиком мусора JavaScript. Необходимо вручную освобождать память:

Ammo.destroy(object);

Иначе возможны утечки памяти.


2. Использование WebAssembly

Современные сборки Ammo.js используют WebAssembly, что обеспечивает почти нативную производительность по сравнению с C++-версией Bullet Physics.


3. Ограничение числа тел

Сложность широкофазной проверки растёт с числом объектов. Оптимальные практики:

  • использование compound-форм;
  • статические объекты вместо динамических;
  • уменьшение количества substeps.

Внутренний цикл Bullet

Bullet работает по следующей схеме:

  1. Применение сил и гравитации.
  2. Интеграция скоростей.
  3. Обнаружение столкновений.
  4. Генерация контактных точек.
  5. Решение ограничений.
  6. Интеграция позиций.
  7. Обновление трансформаций.

Ammo.js полностью повторяет этот цикл.


Интеграция с рендерингом

MotionState используется для синхронизации физики и графики:

const transform = new Ammo.btTransform();
body.getMotionState().getWorldTransform(transform);

const origin = transform.getOrigin();
const rotation = transform.getRotation();

Полученные данные передаются в движок визуализации (например, Three.js), обеспечивая корректное отображение физического состояния.


Отличия от нативного Bullet

  1. Работа в однопоточном окружении браузера.
  2. Ограниченный контроль над памятью.
  3. Отсутствие SIMD-оптимизаций в старых сборках.
  4. Необходимость асинхронной загрузки модуля Ammo.

При этом API максимально близок к оригиналу, что позволяет использовать документацию C++-версии Bullet Physics.


Роль Bullet в экосистеме физики

Bullet Physics используется во множестве профессиональных инструментов и игровых систем благодаря:

  • устойчивости симуляции;
  • поддержке сложных ограничений;
  • высокой производительности;
  • открытому исходному коду.

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