Интерфейс btMotionState

btMotionState является ключевым интерфейсом в физическом движке Bullet, реализованном в JavaScript через Ammo.js. Он предназначен для синхронизации состояния физического тела с внешним представлением объекта в сцене, например, в WebGL или Three.js. В отличие от прямого обновления трансформаций через btRigidBody, btMotionState позволяет отделить физические расчёты от визуального представления, обеспечивая чистую архитектуру и предотвращая прямое вмешательство в симуляцию.


Структура и методы интерфейса

Интерфейс btMotionState имеет два ключевых метода:

  1. getWorldTransform(transform) Этот метод вызывается движком при создании тела или при необходимости узнать текущую позицию и ориентацию объекта. Он должен записать трансформацию в переданный объект transform. Пример реализации:
class MyMotionState extends Ammo.btMotionState {
    constructor(mesh) {
        super();
        this.mesh = mesh;
        this.transform = new Ammo.btTransform();
    }

    getWorldTransform(worldTrans) {
        const pos = this.mesh.position;
        const quat = this.mesh.quaternion;

        this.transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
        this.transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));

        worldTrans.setOrigin(this.transform.getOrigin());
        worldTrans.setRotation(this.transform.getRotation());
    }

    setWorldTransform(worldTrans) {
        const origin = worldTrans.getOrigin();
        const rotation = worldTrans.getRotation();

        this.mesh.position.set(origin.x(), origin.y(), origin.z());
        this.mesh.quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());
    }
}
  1. setWorldTransform(transform) Метод вызывается движком после каждого шага симуляции, чтобы обновить визуальное представление объекта. Он передаёт вычисленные физические координаты, которые затем применяются к связанной модели.

Связь с btRigidBody

При создании тела в Ammo.js важно передать экземпляр btMotionState в конструктор btRigidBody:

const motionState = new MyMotionState(mesh);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
const body = new Ammo.btRigidBody(rbInfo);
physicsWorld.addRigidBody(body);

Использование btMotionState обеспечивает корректное обновление визуального объекта независимо от внутренней симуляции. Это особенно важно при высокой частоте обновления физики и сложных сценах.


Преимущества использования btMotionState

  • Синхронизация без задержек: Позволяет движку самостоятельно обновлять положение и ориентацию объекта после каждого шага симуляции.
  • Разделение логики: Физика и визуализация остаются независимыми, что упрощает поддержку и расширение кода.
  • Оптимизация: Снижает количество прямых вызовов к объектам сцены и предотвращает избыточное копирование данных.

Практические рекомендации

  • Для динамических тел, которые активно участвуют в симуляции, обязательно реализовывать оба метода getWorldTransform и setWorldTransform.
  • Для статических объектов можно использовать btDefaultMotionState, но при необходимости кастомной синхронизации лучше создавать собственный класс.
  • При работе с большим количеством объектов рекомендуется переиспользовать экземпляры btTransform и btVector3 внутри MotionState, чтобы уменьшить нагрузку на сборщик мусора.

Особенности взаимодействия с Three.js

В связке Ammo.js и Three.js btMotionState чаще всего используется для передачи данных между btRigidBody и THREE.Mesh. Важно учитывать:

  • Единицы измерения: Ammo.js использует систему координат в метрах; Three.js — обычно в единицах сцены, поэтому масштабирование может понадобиться.
  • Кватернионы: Для вращения объектов необходимо использовать кватернионы, а не углы Эйлера, чтобы избежать проблемы с гимбал-локом.
  • Память: Все объекты Ammo.js создаются вручную и требуют вызова Ammo.destroy() после завершения работы, иначе возникает утечка памяти.

Расширенные возможности

btMotionState позволяет реализовать:

  • Сетевую синхронизацию: Можно отправлять координаты тел по сети, используя метод setWorldTransform для локальных объектов.
  • Анимацию на основе физики: Можно объединять физическое движение с анимацией модели, корректируя позицию и вращение в зависимости от игрового процесса.
  • Кастомные эффекты: Внутри методов getWorldTransform и setWorldTransform можно применять фильтры, ограничения или модификации движений объекта.

Использование btMotionState является стандартным подходом в Ammo.js для обеспечения стабильной и гибкой связи между физической симуляцией и визуальными объектами. Этот интерфейс обеспечивает чистую архитектуру и позволяет строить сложные симуляции с минимальными проблемами синхронизации.