В Oimo.js объекты сцены, участвующие в физическом моделировании, разделяются на графические объекты (Mesh) и физические тела (RigidBody). Mesh отвечает за визуальное представление, а RigidBody — за физику: положение, ориентацию, столкновения, силы и импульсы. Для корректной симуляции необходимо синхронизировать эти два компонента, чтобы визуальный объект точно повторял физическое движение.
RigidBody создаётся через класс OIMO.Body, который
содержит параметры типа формы, массы и начального положения. Основные
шаги:
let body = world.add({
type: 'box', // Форма тела: 'box', 'sphere', 'cylinder', 'capsule'
size: [2, 2, 2], // Размеры тела
pos: [0, 10, 0], // Начальная позиция
rot: [0, 0, 0], // Начальная ориентация (углы Эйлера)
move: true, // Движущееся тело
density: 1 // Плотность
});
Параметр move определяет, будет ли тело динамическим или
статическим. Динамические тела участвуют в физике,
реагируя на силы и столкновения. Статические тела
остаются неподвижными и служат для коллизий, например, пол или
стены.
Mesh создаётся с помощью Three.js или любого другого графического движка. После создания его необходимо привязать к RigidBody:
let mesh = new THREE.Mesh(
new THREE.BoxGeometry(2, 2, 2),
new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
scene.add(mesh);
Синхронизация выполняется в основном цикле анимации:
function upd ate() {
world.step(); // Обновление физического мира
mesh.position.copy(body.getPosition());
mesh.quaternion.copy(body.getQuaternion());
renderer.render(scene, camera);
requestAnimationFrame(upd ate);
}
update();
Ключевой момент: body.getPosition()
возвращает вектор позиции, а body.getQuaternion() — текущую
ориентацию тела в виде кватерниона. Использование кватернионов
обеспечивает точное вращение без глюков Эйлеровых углов.
Oimo.js хранит внутренние данные RigidBody в формате:
body.position — объект типа OIMO.Vec3,
содержащий координаты X, Y, Z.body.rotation — кватернион или матрица вращения.Чтобы Mesh корректно повторял движение, необходимо каждый
кадр обновлять его position и
quaternion. Прямое присвоение:
mesh.position.se t(body.position.x, body.position.y, body.position.z);
mesh.quaternion.se t(body.rotation.x, body.rotation.y, body.rotation.z, body.rotation.w);
работает, но использование методов copy() удобнее и
предотвращает создание новых объектов в памяти, что важно для
производительности.
Для более плавной анимации можно использовать интерполяцию между кадрами. Oimo.js делает дискретное обновление физики, что иногда приводит к рывкам Mesh. Интерполяция позволяет визуальному объекту перемещаться плавно:
mesh.position.lerp(body.getPosition(), 0.8);
mesh.quaternion.slerp(body.getQuaternion(), 0.8);
Параметр 0.8 регулирует скорость сглаживания. Такой
подход особенно полезен при визуализации на низком FPS или при сетевой
синхронизации.
Если на RigidBody воздействуют силы через applyForce или
applyImpulse, синхронизация с Mesh не меняется, но
необходимо учитывать момент обновления физики:
body.applyImpulse(new OIMO.Vec3(0, 10, 0), body.getPosition());
Mesh автоматически повторит изменение положения после вызова
world.step() в следующем кадре. Ошибка многих разработчиков
— попытка напрямую обновлять Mesh до применения физики, что приводит к
рассинхронизации.
copy() и set() вместо
генерации новых THREE.Vector3 и
THREE.Quaternion каждый кадр.world.step(1/60); // шаг 1/60 секунды
Для нескольких объектов синхронизация выполняется аналогично, просто циклом:
for (let i = 0; i < bodies.length; i++) {
meshes[i].position.copy(bodies[i].getPosition());
meshes[i].quaternion.copy(bodies[i].getQuaternion());
}
Такой подход масштабируем и подходит для динамических сцен с десятками или сотнями объектов.
Для удобства часто создаются обёртки:
function syncMeshWithBody(mesh, body) {
mesh.position.copy(body.getPosition());
mesh.quaternion.copy(body.getQuaternion());
}
Это позволяет избежать дублирования кода и поддерживает чистоту структуры проекта.
Для составных объектов с несколькими RigidBody (например, транспортные средства или кости персонажей) синхронизация выполняется для каждого тела:
composite.bodies.forEach((body, i) => {
composite.meshes[i].position.copy(body.getPosition());
composite.meshes[i].quaternion.copy(body.getQuaternion());
});
Важно соблюдать точное соответствие индексов, чтобы Mesh соответствовал именно своему RigidBody.
Синхронизация Mesh и RigidBody в Oimo.js является фундаментальной частью любой физической симуляции. Правильное обновление позиции и ориентации, использование кватернионов и интерполяции обеспечивают точное и плавное визуальное отображение движущихся объектов.