Синхронизация положения Mesh и RigidBody

В Oimo.js объекты сцены, участвующие в физическом моделировании, разделяются на графические объекты (Mesh) и физические тела (RigidBody). Mesh отвечает за визуальное представление, а RigidBody — за физику: положение, ориентацию, столкновения, силы и импульсы. Для корректной симуляции необходимо синхронизировать эти два компонента, чтобы визуальный объект точно повторял физическое движение.

Создание 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 определяет, будет ли тело динамическим или статическим. Динамические тела участвуют в физике, реагируя на силы и столкновения. Статические тела остаются неподвижными и служат для коллизий, например, пол или стены.

Связь RigidBody с Mesh

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 до применения физики, что приводит к рассинхронизации.

Рекомендации по производительности

  1. Минимизировать создание новых объектов: использовать методы copy() и set() вместо генерации новых THREE.Vector3 и THREE.Quaternion каждый кадр.
  2. Синхронизировать только видимые объекты: для невидимых объектов синхронизация не обязательна.
  3. Использовать фиксированный шаг симуляции: Oimo.js позволяет задавать фиксированный интервал времени, что улучшает стабильность движения:
world.step(1/60);  // шаг 1/60 секунды
  1. Группировать обновления Mesh: при множестве объектов эффективнее обновлять позиции и кватернионы в одном цикле.

Примеры сложной сцены

Для нескольких объектов синхронизация выполняется аналогично, просто циклом:

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 является фундаментальной частью любой физической симуляции. Правильное обновление позиции и ориентации, использование кватернионов и интерполяции обеспечивают точное и плавное визуальное отображение движущихся объектов.