Синхронизация AbstractMesh с RigidBody

Oimo.js — это легковесная физическая библиотека для симуляции твердых тел в JavaScript. Основной задачей при работе с визуальными объектами (например, AbstractMesh в Babylon.js) и физикой является точное соответствие позиции и ориентации визуальной модели положению и вращению физического тела (RigidBody). Без такой синхронизации объекты будут визуально «отставать» от физических расчетов, создавая визуальные артефакты.


Создание RigidBody и связанной визуальной модели

Каждый физический объект создается через OIMO.World с определенной формой, массой и параметрами физики. Для куба, например, процесс выглядит следующим образом:

// Создание мира с базовыми настройками
const world = new OIMO.World({
    timestep: 1/60,
    iterations: 8,
    broadphase: 2, // 2 — sweep and prune
    worldscale: 1,
    random: true
});

// Параметры физического тела
const boxConfig = {
    type: "box",
    size: [1, 1, 1],
    pos: [0, 5, 0],
    rot: [0, 0, 0],
    move: true,
    density: 1
};

// Создание RigidBody
const rigidBody = world.add(boxConfig);

// Создание визуальной модели (Babylon.js AbstractMesh)
const boxMesh = BABYLON.MeshBuilder.CreateBox("box", {size: 1}, scene);
boxMesh.position.set(0, 5, 0);

Ключевой момент: RigidBody хранит положение и ориентацию в собственной системе координат, которую нужно преобразовывать в систему координат визуального движка.


Прямое присвоение позиции и вращения

Самый простой способ синхронизации — напрямую обновлять свойства position и rotationQuaternion:

scene.registerBeforeRender(() => {
    const pos = rigidBody.getPosition();
    const rot = rigidBody.getQuaternion();

    boxMesh.position.set(pos.x, pos.y, pos.z);
    boxMesh.rotationQuaternion = new BABYLON.Quaternion(rot.x, rot.y, rot.z, rot.w);
});

Особенности:

  • getPosition() возвращает объект {x, y, z}.
  • getQuaternion() возвращает кватернион {x, y, z, w}.
  • Обновление должно происходить каждый кадр до рендера, иначе визуальная модель будет отставать от физики.

Интерполяция движения

Для плавного отображения движения можно использовать интерполяцию между предыдущей и текущей позицией:

let prevPos = rigidBody.getPosition();
let prevRot = rigidBody.getQuaternion();

scene.registerBeforeRender(() => {
    const currentPos = rigidBody.getPosition();
    const currentRot = rigidBody.getQuaternion();

    // Линейная интерполяция позиции
    boxMesh.position.lerpTo(
        new BABYLON.Vector3(currentPos.x, currentPos.y, currentPos.z),
        0.1
    );

    // Сферическая интерполяция кватернионов
    boxMesh.rotationQuaternion = BABYLON.Quaternion.Slerp(
        new BABYLON.Quaternion(prevRot.x, prevRot.y, prevRot.z, prevRot.w),
        new BABYLON.Quaternion(currentRot.x, currentRot.y, currentRot.z, currentRot.w),
        0.1
    );

    prevPos = currentPos;
    prevRot = currentRot;
});

Преимущества: уменьшение резких «рывков» при низкой частоте физического расчета, особенно при быстрых движениях или крупных объектах.


Синхронизация сложных объектов

Для объектов с несколькими визуальными элементами (Compound Mesh) каждый компонент должен иметь собственный RigidBody или быть частью составного тела (compound shape). В Oimo.js можно использовать addCompound:

const compoundBody = world.add({
    type: "compound",
    move: true,
    shapes: [
        { type: "box", size: [1, 1, 1], pos: [0, 0, 0] },
        { type: "sphere", size: [0.5], pos: [0, 1, 0] }
    ]
});

// Соответствующие mesh
const boxPart = BABYLON.MeshBuilder.CreateBox("boxPart", {size: 1}, scene);
const spherePart = BABYLON.MeshBuilder.CreateSphere("spherePart", {diameter: 1}, scene);

scene.registerBeforeRender(() => {
    const pos = compoundBody.getPosition();
    const rot = compoundBody.getQuaternion();

    boxPart.position.set(pos.x, pos.y, pos.z);
    boxPart.rotationQuaternion = new BABYLON.Quaternion(rot.x, rot.y, rot.z, rot.w);

    spherePart.position.set(pos.x, pos.y + 1, pos.z);
    spherePart.rotationQuaternion = new BABYLON.Quaternion(rot.x, rot.y, rot.z, rot.w);
});

Важно: компоненты составного тела наследуют поведение RigidBody, поэтому их позиции вычисляются относительно центра масс.


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

  1. Минимизация числа RigidBody: для сцен с большим количеством объектов объединение мелких элементов в составные тела сокращает нагрузку.
  2. Снижение частоты обновления: если визуальная модель не требует высокой точности, можно обновлять позицию каждые несколько кадров.
  3. Использование кватернионов: вращения через rotationQuaternion работают быстрее и точнее, чем через Euler angles.

Дополнительные техники

  • Следование камеры: для динамических объектов можно привязывать камеру к RigidBody через промежуточный слой, используя позицию и кватернион.
  • События столкновений: при детектировании столкновений RigidBody можно использовать обратные вызовы для визуальных эффектов (частицы, деформации).
  • Фиксация ориентации: для объектов, которые должны оставаться вертикальными, применяется ограничение вращения через setRotationLock(true, true, true).

Совместимость с Babylon.js

AbstractMesh и RigidBody работают в разных системах координат:

  • Oimo.js использует метры мира в метрах.
  • Babylon.js использует ту же систему, но необходимо учитывать масштаб сцены.
  • При масштабировании объектов синхронизация позиции должна учитывать множитель масштаба:
const scale = 2; // если масштаб сцены x2
const pos = rigidBody.getPosition();
boxMesh.position.set(pos.x * scale, pos.y * scale, pos.z * scale);

Правильное управление масштабом предотвращает несоответствие физики и визуала.


Система синхронизации AbstractMesh с RigidBody в Oimo.js позволяет реализовать реалистичную физику в 3D сценах, управлять сложными составными телами, оптимизировать производительность и контролировать визуальные эффекты, сохраняя точное соответствие физическим расчетам.