Ammo.js — это JavaScript-порт известной физической библиотеки Bullet Physics, позволяющий создавать реалистичную физику в браузере. Основное назначение библиотеки — симуляция динамики твёрдых тел, столкновений, трения, гравитации и ограничений между объектами. В контексте игрового движка или визуализации ключевой задачей является связывание физических тел с игровыми объектами для корректного отображения и управления.
В Ammo.js физические объекты представлены через
btRigidBody, а визуальные объекты — через классы движка
рендеринга (например, Three.js Mesh). Связь между ними
обеспечивает паттерн синхронизации, при котором
изменения состояния физического тела отражаются на игровом объекте.
Физическое тело создаётся по следующей схеме:
Определение формы
(btCollisionShape) Примеры форм:
btBoxShape — кубическая форма, принимает вектор
половинных размеров.btSphereShape — сферическая форма, принимает
радиус.btCapsuleShape — капсула, принимает радиус и
высоту.Масса и инерция Масса определяет, является объект динамическим или статическим. Для динамических тел необходимо рассчитать локальную инерцию:
const mass = 1;
const localInertia = new Ammo.btVector3(0, 0, 0);
shape.calculateLocalInertia(mass, localInertia);Создание движения
(btDefaultMotionState) Этот объект хранит позицию
и ориентацию тела и служит связующим звеном между физикой и
визуализацией:
const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(x, y, z));
const motionState = new Ammo.btDefaultMotionState(transform);Создание btRigidBody С телом
связываются форма, масса и инерция:
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
const body = new Ammo.btRigidBody(rbInfo);
dynamicsWorld.addRigidBody(body);После добавления тела в мир необходимо синхронизировать визуальный объект с физическим:
function syncVisualWithPhysics(mesh, body) {
const transform = new Ammo.btTransform();
body.getMotionState().getWorldTransform(transform);
const origin = transform.getOrigin();
const rotation = transform.getRotation();
mesh.position.set(origin.x(), origin.y(), origin.z());
mesh.quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());
}
Ключевые моменты:
getWorldTransform() возвращает текущую позицию и
ориентацию физического тела.mesh.position вне
синхронизации с физикой для динамических объектов.Для упрощения синхронизации часто используют Motion State
Proxy, оборачивающий btDefaultMotionState и
визуальный объект:
class MotionStateProxy {
constructor(mesh) {
this.mesh = mesh;
this.transform = new Ammo.btTransform();
this.motionState = new Ammo.btDefaultMotionState();
}
updateFromPhysics(body) {
body.getMotionState().getWorldTransform(this.transform);
const origin = this.transform.getOrigin();
const rotation = this.transform.getRotation();
this.mesh.position.set(origin.x(), origin.y(), origin.z());
this.mesh.quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());
}
}
Использование такого паттерна повышает читаемость кода и централизует управление синхронизацией.
Кинематические тела не подчиняются физическим силам, но могут влиять
на динамические объекты. Их создают с нулевой массой и флагом
CF_KINEMATIC_OBJECT:
body.setCollisionFlags(body.getCollisionFlags() | 2); // 2 — CF_KINEMATIC_OBJECT
body.setActivationState(4); // 4 — DISABLE_DEACTIVATION
Позицию таких тел обновляют напрямую через btTransform,
синхронизируя с визуальным объектом.
Для игровых объектов с несколькими частями (например, робот с соединёнными сегментами) используется паттерн Compound Shape:
const compoundShape = new Ammo.btCompoundShape();
const childTransform = new Ammo.btTransform();
childTransform.setIdentity();
childTransform.setOrigin(new Ammo.btVector3(offsetX, offsetY, offsetZ));
compoundShape.addChildShape(childTransform, childShape);
После добавления всех частей создаётся одно btRigidBody,
что позволяет управлять сложной структурой как единым целым, при этом
каждое визуальное представление синхронизируется через соответствующий
Motion State.
btTransform и btVector3 замедляет симуляцию.
Лучше переиспользовать объекты.Симуляция Ammo.js обычно интегрируется в игровой цикл следующим образом:
function update(deltaTime) {
dynamicsWorld.stepSimulation(deltaTime, 10);
for (const obj of dynamicObjects) {
obj.motionStateProxy.updateFromPhysics(obj.body);
}
renderer.render(scene, camera);
}
stepSimulation() выполняет шаг физического мира.10 в примере).Ammo.js поддерживает различные виды соединений (constraints), связывающих два тела:
btPoint2PointConstraint — шарнирное соединение.btHingeConstraint — вращение вокруг одной оси.btSliderConstraint — линейное движение по оси.После добавления ограничения синхронизация визуальных объектов осуществляется по той же схеме через Motion State Proxy, при этом физика автоматически рассчитывает взаимное влияние тел.
MotionStateProxy и Compound Shapes.stepSimulation() для корректного отображения столкновений и
взаимодействий.Такой подход обеспечивает надёжную интеграцию Ammo.js в игровую архитектуру, позволяя поддерживать физически достоверные сцены с минимальными ошибками и высокой производительностью.