Утилитарный класс-обёртка для синхронизации объектов

Библиотека Oimo.js представляет собой физический движок на JavaScript, оптимизированный для симуляции твёрдого тела, столкновений и динамики объектов в 3D-сценах. Одной из ключевых задач при интеграции Oimo.js с графическими движками, такими как Three.js, является корректная синхронизация физических тел с визуальными объектами.

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


Утилитарный класс-обёртка

Для упрощения синхронизации создаётся класс-обёртка, объединяющий физическое тело и визуальный объект. Основная структура класса включает:

class PhysicsObject {
    constructor(mesh, body) {
        this.mesh = mesh;   // Визуальный объект (Three.js Mesh)
        this.body = body;   // Физическое тело (Oimo.Body)
    }

    upd ate() {
        // Синхронизация позиции
        const pos = this.body.getPosition();
        this.mesh.position.se t(pos.x, pos.y, pos.z);

        // Синхронизация вращения
        const rot = this.body.getQuaternion();
        this.mesh.quaternion.set(rot.x, rot.y, rot.z, rot.w);
    }

    applyForce(force) {
        // Применение силы к физическому телу
        this.body.applyForce(force);
    }

    applyImpulse(impulse) {
        // Применение мгновенного импульса
        this.body.applyImpulse(impulse);
    }
}

Ключевые моменты:

  • Метод update() вызывается каждый кадр после шага физического симулятора, обеспечивая соответствие позиции и ориентации визуального объекта данным физики.
  • Методы applyForce() и applyImpulse() дают возможность управлять объектами через физику, не нарушая консистентности.

Создание и инициализация физических тел

Oimo.js предоставляет различные формы тел через Oimo.BoxGeometry, Oimo.SphereGeometry, Oimo.CylinderGeometry и т.д. При создании тела важно правильно настроить массу, тип динамики и столкновения:

const bodyConfig = {
    type: 'box',           // форма: box, sphere, cylinder
    size: [1, 1, 1],       // размеры по осям x, y, z
    pos: [0, 5, 0],        // начальная позиция
    rot: [0, 0, 0],        // начальная ориентация
    move: true,            // динамическое тело
    density: 1             // плотность
};

const boxBody = world.add(bodyConfig);
const boxMesh = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshStandardMaterial({ color: 0x00ff00 })
);

const physicsBox = new PhysicsObject(boxMesh, boxBody);

Организация обновления сцены

В типичном рендер-цикле:

function animate() {
    requestAnimationFrame(animate);

    // Шаг физики
    world.step();

    // Обновление всех объектов
    physicsObjects.forEach(obj => obj.update());

    renderer.render(scene, camera);
}

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

  • Обновлять только видимые объекты или объекты, участвующие в столкновениях.
  • Избегать частого создания и уничтожения физических тел в цикле.
  • Использовать Oimo.Vec3 и Oimo.Quat для локальных вычислений и уменьшения количества временных объектов.

Расширенные возможности класса-обёртки

Можно добавить методы для более сложных взаимодействий:

  • setPosition(x, y, z) – мгновенная установка позиции без учёта текущей скорости.
  • setRotation(x, y, z, w) – установка ориентации через кватернион.
  • followTarget(targetMesh) – копирование позиции и вращения другого объекта, полезно для закрепления визуальных элементов.
  • resetVelocity() – сброс линейной и угловой скорости при необходимости остановки объекта.

Пример расширения:

class PhysicsObjectExtended extends PhysicsObject {
    setPosition(x, y, z) {
        this.body.setPosition(x, y, z);
        this.update();
    }

    resetVelocity() {
        this.body.setLinearVelocity({ x: 0, y: 0, z: 0 });
        this.body.setAngularVelocity({ x: 0, y: 0, z: 0 });
    }
}

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

Для составных объектов, состоящих из нескольких тел (например, робот или транспорт), создаётся контейнерный класс, управляющий коллекцией PhysicsObject. Такой подход позволяет:

  • Централизованно обновлять все визуальные элементы.
  • Применять силы или ограничения к группе тел.
  • Упрощать добавление новых частей без изменения рендер-цикла.

Пример:

class PhysicsGroup {
    constructor(objects = []) {
        this.objects = objects; 
    }

    update() {
        this.objects.forEach(obj => obj.update());
    }

    applyForceToAll(force) {
        this.objects.forEach(obj => obj.applyForce(force));
    }

    add(obj) {
        this.objects.push(obj);
    }
}

Особенности работы с кватернионами

  • Oimo.js использует кватернионы для вращения объектов, что предотвращает эффект Gimbal lock.
  • Для корректной визуализации важно использовать методы setQuaternion(x, y, z, w) у графических объектов.
  • Преобразование из углов Эйлера в кватернионы выполняется только при необходимости начальной установки ориентации.

Интеграция с Three.js

  • mesh.position и mesh.quaternion должны быть синхронизированы после каждого вызова world.step().
  • Для сглаживания анимации можно применять интерполяцию между кадрами, используя предыдущую и текущую позицию тела.
  • Материалы и освещение не влияют на физику, поэтому визуальные эффекты можно изменять динамически без влияния на расчёты Oimo.js.

Оптимизация больших сцен

  • Использовать специальные группы коллайдеров для статических объектов. Статические тела можно обновлять реже.
  • Ограничивать количество динамических тел, так как расчёт столкновений сложен и ресурсоёмок.
  • Применять sleeping для объектов, которые долго не двигаются, чтобы уменьшить нагрузку на физический движок.

Эта структура утилитарного класса-обёртки обеспечивает прозрачное и безопасное управление синхронизацией физики и графики, позволяя строить сложные интерактивные сцены с использованием Oimo.js.