Портирование проекта с Ammo.js на Oimo.js требует понимания фундаментальных различий между движками. Ammo.js представляет собой Emscripten-порт C++-движка Bullet Physics и повторяет его архитектуру: указатели, ручное управление памятью, сложная иерархия классов, низкоуровневые структуры данных.
Oimo.js — нативная JavaScript-реализация физического движка, ориентированная на простоту API, меньший объём кода и более удобную интеграцию с WebGL-сценами.
| Характеристика | Ammo.js | Oimo.js |
|---|---|---|
| Происхождение | Порт Bullet | Нативный JS |
| Управление памятью | Ручное, через Ammo.destroy |
Автоматическое (GC) |
| API | Низкоуровневое | Более компактное |
| Производительность | Высокая при сложной физике | Оптимизировано для веб-сцен |
| Настройка | Гибкая, но сложная | Быстрая конфигурация |
Переход требует изменения подхода к созданию мира, тел, форм и шагу симуляции.
В Ammo.js инициализация мира включает несколько обязательных компонентов:
const collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
const broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();
const physicsWorld = new Ammo.btDiscreteDynamicsWorld(
dispatcher,
broadphase,
solver,
collisionConfiguration
);
physicsWorld.setGravity(new Ammo.btVector3(0, -9.8, 0));
В Oimo.js инициализация значительно проще:
const world = new OIMO.World({
gravity: [0, -9.8, 0]
});
Отличие: отсутствуют отдельные конфигурации
диспетчера, broadphase и solver. В Oimo эти механизмы скрыты внутри
World.
const shape = new Ammo.btBoxShape(new Ammo.btVector3(1, 1, 1));
const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(0, 5, 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);
physicsWorld.addRigidBody(body);
const body = world.add({
type: 'box',
size: [2, 2, 2],
pos: [0, 5, 0],
move: true,
density: 1
});
btRigidBodyConstructionInfo.| Ammo.js | Oimo.js |
|---|---|
btBoxShape |
type: 'box' |
btSphereShape |
type: 'sphere' |
btCylinderShape |
type: 'cylinder' |
btConvexHullShape |
type: 'convex' |
Ammo.js:
const shape = new Ammo.btSphereShape(1);
Oimo.js:
world.add({
type: 'sphere',
size: [2],
pos: [0, 10, 0],
move: true
});
Размеры в Oimo задаются диаметром, тогда как в Ammo радиус передаётся напрямую.
physicsWorld.stepSimulation(deltaTime, 10);
world.step();
Oimo обычно использует фиксированный шаг времени (по умолчанию 1/60). Для изменения:
const world = new OIMO.World({
timestep: 1/120,
gravity: [0, -9.8, 0]
});
const transform = new Ammo.btTransform();
body.getMotionState().getWorldTransform(transform);
const origin = transform.getOrigin();
const rotation = transform.getRotation();
const position = body.getPosition();
const quaternion = body.getQuaternion();
В Oimo отсутствует необходимость работать с временными объектами
btTransform.
Критически важно уничтожать объекты:
Ammo.destroy(rbInfo);
Ammo.destroy(motionState);
Ammo.destroy(transform);
Отсутствие освобождения памяти приводит к утечкам, поскольку объекты находятся в WebAssembly-памяти.
Используется сборщик мусора JavaScript. Явное уничтожение требуется только при удалении тела:
world.remove(body);
В Ammo.js доступны практически все типы шарниров Bullet:
btHingeConstraintbtSliderConstraintbtPoint2PointConstraintOimo поддерживает базовые типы соединений через конфигурацию:
world.add({
type: 'jointHinge',
body1: bodyA,
body2: bodyB,
pos1: [0, 0, 0],
pos2: [0, 0, 0],
axe1: [0, 1, 0],
axe2: [0, 1, 0]
});
API отличается концептуально: в Ammo создаётся отдельный объект
ограничения, в Oimo — добавляется конфигурационный объект через
world.add.
Часто портирование выполняется в проектах на базе Three.js.
mesh.position.set(origin.x(), origin.y(), origin.z());
mesh.quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());
mesh.position.copy(body.getPosition());
mesh.quaternion.copy(body.getQuaternion());
Oimo возвращает объекты, совместимые с THREE.Vector3 и
THREE.Quaternion, что упрощает интеграцию.
Ammo.js лучше справляется с:
Oimo.js демонстрирует преимущества:
При портировании крупных сцен рекомендуется:
body.setFriction(0.5);
body.setRestitution(0.2);
world.add({
type: 'box',
size: [2,2,2],
friction: 0.5,
restitution: 0.2
});
Параметры задаются при создании тела.
В Ammo используется явное значение mass. В Oimo масса
рассчитывается из density и объёма.
Решение: вычислить плотность:
density = mass / volume
Bullet и Oimo используют правую систему координат, но параметры цилиндров и шарниров могут отличаться по оси по умолчанию.
Ammo.js чувствителен к слишком маленьким или большим масштабам (оптимальный диапазон 0.1–10). Oimo более устойчив, но также требует разумного масштаба сцены.
OIMO.World.btRigidBody через
world.add.physicsWorld.stepSimulation(deltaTime);
body.getMotionState().getWorldTransform(tmpTrans);
mesh.position.set(
tmpTrans.getOrigin().x(),
tmpTrans.getOrigin().y(),
tmpTrans.getOrigin().z()
);
world.step();
mesh.position.copy(body.getPosition());
mesh.quaternion.copy(body.getQuaternion());
Портирование целесообразно при:
При проектах, завязанных на расширенные возможности Bullet, сохранение Ammo.js остаётся более рациональным решением.
Oimo.js обеспечивает более компактный и читаемый код, снижает порог входа и упрощает сопровождение веб-проектов с физикой.