Ammo.js — это порт популярного физического движка Bullet
на JavaScript через Emscripten. Важной особенностью работы с библиотекой
является асинхронная инициализация, которая позволяет
загружать и компилировать WebAssembly-модуль до начала физического
симулятора. Основной метод для этого — вызов глобальной функции
Ammo().
Когда выполняется вызов Ammo(), создается объект модуля
WebAssembly, включающий все классы и функции Bullet. Важно учитывать,
что этот процесс не мгновенный: загрузка и компиляция
WASM требуют времени, особенно на слабых устройствах или при работе с
большим числом скриптов.
Функция Ammo() может использоваться двумя способами:
then:Ammo().then((AmmoLib) => {
const collisionConfiguration = new AmmoLib.btDefaultCollisionConfiguration();
const dispatcher = new AmmoLib.btCollisionDispatcher(collisionConfiguration);
const broadphase = new AmmoLib.btDbvtBroadphase();
const solver = new AmmoLib.btSequentialImpulseConstraintSolver();
const physicsWorld = new AmmoLib.btDiscreteDynamicsWorld(
dispatcher,
broadphase,
solver,
collisionConfiguration
);
physicsWorld.setGravity(new AmmoLib.btVector3(0, -9.81, 0));
});
async/await:async function initPhysics() {
const AmmoLib = await Ammo();
const collisionConfiguration = new AmmoLib.btDefaultCollisionConfiguration();
const dispatcher = new AmmoLib.btCollisionDispatcher(collisionConfiguration);
const broadphase = new AmmoLib.btDbvtBroadphase();
const solver = new AmmoLib.btSequentialImpulseConstraintSolver();
const physicsWorld = new AmmoLib.btDiscreteDynamicsWorld(
dispatcher,
broadphase,
solver,
collisionConfiguration
);
physicsWorld.setGravity(new AmmoLib.btVector3(0, -9.81, 0));
return physicsWorld;
}
Использование async/await делает код более читаемым и
упрощает организацию сложной логики инициализации.
После успешного выполнения Ammo() доступны все базовые
компоненты Bullet:
btDefaultCollisionConfiguration —
хранит настройки столкновений и контактные параметры.btCollisionDispatcher — отвечает за
расчёт столкновений между объектами.btDbvtBroadphase — структура для
ускоренной проверки пересечений объектов (broadphase).btSequentialImpulseConstraintSolver —
решает системы ограничений (constraints) и столкновений.btDiscreteDynamicsWorld — основной
контейнер физической симуляции, включающий все объекты и
взаимодействия.Каждый объект требует отдельного создания через
new AmmoLib.ClassName(), что гарантирует выделение памяти в
рамках WebAssembly.
Ammo.js управляет памятью через
WASM-heap. Все объекты, созданные через конструкторы
(btVector3, btRigidBody,
btTransform), занимают память в пуле WebAssembly. После
завершения работы с объектом его необходимо вручную
освобождать, используя метод delete:
const vec = new AmmoLib.btVector3(1, 2, 3);
// использование в физике
vec.delete();
Невыполнение этого правила приводит к утечкам памяти, особенно заметным при длительных симуляциях или множестве создаваемых объектов.
При интеграции Ammo.js в проект часто используют
одиночное обещание (singleton promise), чтобы
гарантировать, что модуль загружен только один раз:
let ammoPromise = null;
function getAmmo() {
if (!ammoPromise) {
ammoPromise = Ammo();
}
return ammoPromise;
}
// использование
getAmmo().then((AmmoLib) => {
// инициализация физики
});
Такой подход позволяет избежать повторной загрузки WASM и обеспечивает согласованность объектов между разными модулями приложения.
Ammo() должна происходить до
создания сцен или объектов, чтобы избежать блокировки основного
потока.async/await или промисы, избегая
синхронных вызовов Ammo() в циклах или коллбеках
рендеринга.После получения экземпляра physicsWorld рекомендуется
настроить шаг симуляции:
function stepPhysics(physicsWorld, deltaTime) {
physicsWorld.stepSimulation(deltaTime, 10); // 10 итераций для точности
}
deltaTime — время между кадрами в секундах.Эта функция вызывается в основном цикле игры или анимации, обеспечивая плавное взаимодействие объектов.
Асинхронная инициализация через Ammo() — ключевой
элемент правильной работы физического движка на JavaScript. Она
обеспечивает загрузку и компиляцию WebAssembly, управление памятью и
создание всех базовых компонентов симуляции. Корректная организация
промисов и async/await гарантирует стабильность,
предотвращает утечки памяти и упрощает интеграцию физики в сложные
сцены.