Для работы с физикой в браузере используется библиотека Ammo.js, которая представляет собой порт Bullet Physics на JavaScript с использованием WebAssembly. Инициализация начинается с загрузки модуля и создания основных объектов физического мира:
import Ammo from 'ammo.js';
let physicsWorld;
Ammo().then(function(AmmoLib) {
const collisionConfiguration = new AmmoLib.btDefaultCollisionConfiguration();
const dispatcher = new AmmoLib.btCollisionDispatcher(collisionConfiguration);
const overlappingPairCache = new AmmoLib.btDbvtBroadphase();
const solver = new AmmoLib.btSequentialImpulseConstraintSolver();
physicsWorld = new AmmoLib.btDiscreteDynamicsWorld(
dispatcher,
overlappingPairCache,
solver,
collisionConfiguration
);
physicsWorld.setGravity(new AmmoLib.btVector3(0, -9.81, 0));
});
Ключевые элементы:
Все объекты в мире Ammo.js делятся на три типа: статические, динамические и кинетические. Создание объекта включает определение формы, массы и начальной позиции:
const boxShape = new Ammo.btBoxShape(new Ammo.btVector3(1, 1, 1));
const mass = 1;
const startTransform = new Ammo.btTransform();
startTransform.setIdentity();
startTransform.setOrigin(new Ammo.btVector3(0, 10, 0));
const localInertia = new Ammo.btVector3(0, 0, 0);
boxShape.calculateLocalInertia(mass, localInertia);
const motionState = new Ammo.btDefaultMotionState(startTransform);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, boxShape, localInertia);
const body = new Ammo.btRigidBody(rbInfo);
physicsWorld.addRigidBody(body);
Важно учитывать:
Симуляция происходит шагами времени, обычно в цикле
requestAnimationFrame:
const deltaTime = 1 / 60;
function simulatePhysics() {
physicsWorld.stepSimulation(deltaTime, 10);
// Получение позиции объектов
const transform = new Ammo.btTransform();
body.getMotionState().getWorldTransform(transform);
const origin = transform.getOrigin();
console.log(`X: ${origin.x()}, Y: ${origin.y()}, Z: ${origin.z()}`);
requestAnimationFrame(simulatePhysics);
}
simulatePhysics();
Параметры:
Ammo.js использует WebAssembly и активно оперирует памятью, поэтому критически важно профилировать производительность при разработке игр и сложных симуляций. Основные шаги:
В Chrome DevTools:
Профайлер покажет распределение времени между JS вызовами, WebAssembly функциями и GC.
Важно измерять отдельно:
Пример ручного тайминга:
const t0 = performance.now();
physicsWorld.stepSimulation(deltaTime, 10);
const t1 = performance.now();
console.log(`Step simulation time: ${t1 - t0} ms`);
Это помогает выявить узкие места: например, большое количество динамических объектов или сложные коллизионные формы.
Ammo.js использует ручное управление памятью через
Ammo.destroy().
Пример очистки:
physicsWorld.removeRigidBody(body);
Ammo.destroy(body);
Ammo.destroy(motionState);
Ammo.destroy(boxShape);
Это снижает нагрузку на сборщик мусора и предотвращает утечки.
Сложные треугольные меши значительно замедляют шаги симуляции. Рекомендуется:
Для контроля производительности удобно выводить среднее время шага и FPS:
let lastTime = performance.now();
let frames = 0;
function fpsMonitor() {
frames++;
const now = performance.now();
if (now - lastTime >= 1000) {
console.log(`FPS: ${frames}`);
frames = 0;
lastTime = now;
}
requestAnimationFrame(fpsMonitor);
}
fpsMonitor();
Высокое время шага указывает на необходимость оптимизации коллизий, ограничения количества тел или использования LOD для физики.
Практика показывает, что большинство узких мест находятся в:
btTransform без последующего уничтожения
объектов.Комплексный подход с ручным профилированием и корректным управлением памятью позволяет держать симуляцию стабильной и отзывчивой даже при сотнях объектов.