Интеграция математики Ammo.js с Three.js (Vector3, Quaternion)

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

Ключевым элементом взаимодействия является синхронизация векторов позиции и вращения между Ammo.js и Three.js через объекты Vector3 и Quaternion. В Ammo.js все расчеты выполняются в собственной системе типов (btVector3, btQuaternion), в то время как Three.js использует свои THREE.Vector3 и THREE.Quaternion.


Работа с векторами

В Ammo.js позиция объекта хранится в виде btVector3, который представляет собой трехкомпонентный вектор (x, y, z). Для создания нового вектора:

const positionAmmo = new Ammo.btVector3(0, 10, 0);

Для получения значения вектора для Three.js необходимо извлечь компоненты:

const positionThree = new THREE.Vector3(
    positionAmmo.x(),
    positionAmmo.y(),
    positionAmmo.z()
);

Обратное преобразование требуется для установки позиции физического тела из графического объекта:

const newPositionAmmo = new Ammo.btVector3(positionThree.x, positionThree.y, positionThree.z);
rigidBody.setWorldTransform(new Ammo.btTransform().setOrigin(newPositionAmmo));

Важно помнить, что btVector3 является объектом WebAssembly, и для больших сцен частое создание новых объектов может привести к утечкам памяти. Оптимальным подходом является переиспользование существующих векторов и очистка их через Ammo.destroy().


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

Для задания ориентации объекта в Ammo.js используется btQuaternion. Кватернион описывает вращение без использования углов Эйлера, что предотвращает проблемы типа гимбального замка.

Создание кватерниона:

const rotationAmmo = new Ammo.btQuaternion(0, 0, 0, 1); // единичный кватернион

Получение данных для Three.js:

const rotationThree = new THREE.Quaternion(
    rotationAmmo.x(),
    rotationAmmo.y(),
    rotationAmmo.z(),
    rotationAmmo.w()
);

Для установки вращения физического объекта:

const newRotationAmmo = new Ammo.btQuaternion(
    rotationThree.x,
    rotationThree.y,
    rotationThree.z,
    rotationThree.w
);

const transform = new Ammo.btTransform();
transform.setRotation(newRotationAmmo);
rigidBody.setWorldTransform(transform);

Синхронизация кватернионов особенно важна при работе с динамическими телами, которые вращаются под воздействием физических сил и импульсов.


Синхронизация графики и физики

Типичная интеграция Ammo.js с Three.js включает цикл обновления, где положение и вращение физического тела обновляются в графическом меше:

function updatePhysics(deltaTime) {
    physicsWorld.stepSimulation(deltaTime, 10);

    for (let i = 0; i < rigidBodies.length; i++) {
        const objThree = rigidBodies[i].mesh;
        const objAmmo = rigidBodies[i].body;
        
        const motionState = objAmmo.getMotionState();
        if (motionState) {
            const tmpTransform = new Ammo.btTransform();
            motionState.getWorldTransform(tmpTransform);

            const origin = tmpTransform.getOrigin();
            const rotation = tmpTransform.getRotation();

            objThree.position.set(origin.x(), origin.y(), origin.z());
            objThree.quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());
        }
    }
}

Здесь stepSimulation выполняет физическое обновление, а затем происходит извлечение позиции и ориентации для визуализации. Такой подход обеспечивает реалистичное движение объектов, включая столкновения, падения и вращение.


Создание физического тела с трансформацией

Ammo.js требует установки начальной позиции и ориентации при создании тела. Используется объект btTransform, который объединяет btVector3 и btQuaternion:

const transform = new Ammo.btTransform();
transform.setIdentity(); // сброс к стандартному положению
transform.setOrigin(new Ammo.btVector3(x, y, z));
transform.setRotation(new Ammo.btQuaternion(qx, qy, qz, qw));

const motionState = new Ammo.btDefaultMotionState(transform);
const mass = 1;
const localInertia = new Ammo.btVector3(0, 0, 0);
shape.calculateLocalInertia(mass, localInertia);

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

physicsWorld.addRigidBody(body);

Такой подход позволяет точно контролировать начальные условия, что особенно важно для симуляций, где важны точность столкновений и правильное вращение.


Важные рекомендации

  • Использовать переиспользуемые объекты btVector3 и btQuaternion, чтобы минимизировать нагрузку на память и сборку мусора.
  • Всегда освобождать память для временных объектов через Ammo.destroy().
  • Сохранять соответствие единиц измерения между Three.js и Ammo.js (обычно метрическая система).
  • Использовать кватернионы для всех вращений, чтобы избежать артефактов при сложных движениях.
  • Синхронизировать объекты после вызова stepSimulation, чтобы визуализация точно отражала физику.

Пример полной интеграции с мешем Three.js

const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const boxMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(boxMesh);

const boxShape = new Ammo.btBoxShape(new Ammo.btVector3(0.5, 0.5, 0.5));
const startTransform = new Ammo.btTransform();
startTransform.setIdentity();
startTransform.setOrigin(new Ammo.btVector3(0, 5, 0));
startTransform.setRotation(new Ammo.btQuaternion(0, 0, 0, 1));

const motionState = new Ammo.btDefaultMotionState(startTransform);
const mass = 1;
const localInertia = new Ammo.btVector3(0, 0, 0);
boxShape.calculateLocalInertia(mass, localInertia);

const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, boxShape, localInertia);
const rigidBody = new Ammo.btRigidBody(rbInfo);

physicsWorld.addRigidBody(rigidBody);
rigidBodies.push({ mesh: boxMesh, body: rigidBody });

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