Совместное использование с Three.js: базовая связка

Для работы с физикой в 3D на вебе библиотека Ammo.js используется как движок физического моделирования, а Three.js обеспечивает визуализацию. Интеграция начинается с подключения обоих библиотек. Ammo.js можно подключить через модуль или CDN:

import * as THREE from 'three';
import Ammo from 'ammo.js';

let physicsWorld;
Ammo().then(AmmoLib => {
    // Инициализация физического мира
    const collisionConfiguration = new AmmoLib.btDefaultCollisionConfiguration();
    const dispatcher = new AmmoLib.btCollisionDispatcher(collisionConfiguration);
    const overlappingPairCache = new AmmoLib.btDbvtBroadphase();
    const solver = new AmmoLib.btSequentialImpulseConstraintSolver();
    physicsWorld = new AmmoLib.btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
    physicsWorld.setGravity(new AmmoLib.btVector3(0, -9.81, 0));
});

Ключевой момент: физический мир создаётся отдельно от сцены Three.js. Все объекты должны существовать в обоих мирах: визуальном и физическом.


Создание физического тела и визуального объекта

Взаимодействие объектов в физическом мире требует соответствия между Three.js Mesh и Ammo.js RigidBody. Основные шаги:

  1. Создать визуальную геометрию и материал.
  2. Создать физическую форму (btCollisionShape) и параметры массы.
  3. Создать объект btRigidBody и добавить его в physicsWorld.

Пример создания динамического куба:

const boxSize = 1;
const mass = 1;

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

// Ammo.js Collision Shape
const boxShape = new Ammo.btBoxShape(new Ammo.btVector3(boxSize/2, boxSize/2, boxSize/2));
const transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(0, 5, 0));

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

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

physicsWorld.addRigidBody(boxBody);

Ключевой момент: три параметра — форма, масса и момент инерции — определяют поведение объекта в симуляции.


Обновление позиции объектов

Ammo.js хранит состояние физики отдельно от Three.js, поэтому позиции и вращения нужно синхронизировать на каждом кадре:

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

    // Синхронизация всех объектов
    const ms = boxBody.getMotionState();
    if (ms) {
        const transform = new Ammo.btTransform();
        ms.getWorldTransform(transform);
        const origin = transform.getOrigin();
        const rotation = transform.getRotation();
        boxMesh.position.set(origin.x(), origin.y(), origin.z());
        boxMesh.quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());
    }
}

Ключевой момент: stepSimulation принимает параметры deltaTime и количество итераций для повышения точности расчётов. Чем больше итераций, тем точнее симуляция, но выше нагрузка на CPU.


Коллизии и материалы

Ammo.js поддерживает разные типы форм и материалов. Наиболее распространённые формы:

  • btBoxShape — куб, параллелепипед
  • btSphereShape — сфера
  • btCapsuleShape — капсула, полезна для персонажей
  • btCylinderShape — цилиндр
  • btConvexHullShape — произвольная выпуклая форма
  • btBvhTriangleMeshShape — статическая сложная сетка

Материалы определяются через коэффициенты трения и упругости:

boxBody.setFriction(0.5);
boxBody.setRestitution(0.7); // отскок

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


Статические и динамические объекты

  • Динамические объекты имеют массу > 0, подвержены гравитации и столкновениям.
  • Статические объекты (например, пол, стены) имеют массу = 0, служат опорой:
const groundShape = new Ammo.btBoxShape(new Ammo.btVector3(50, 1, 50));
const groundTransform = new Ammo.btTransform();
groundTransform.setIdentity();
groundTransform.setOrigin(new Ammo.btVector3(0, -1, 0));

const groundMotionState = new Ammo.btDefaultMotionState(groundTransform);
const groundBodyInfo = new Ammo.btRigidBodyConstructionInfo(0, groundMotionState, groundShape, new Ammo.btVector3(0,0,0));
const groundBody = new Ammo.btRigidBody(groundBodyInfo);
physicsWorld.addRigidBody(groundBody);

Ключевой момент: статические объекты экономят ресурсы, так как не требуют интеграции движения.


Совместная анимация с Three.js

Для плавной интеграции с анимацией в Three.js можно использовать основной рендер-цикл:

const clock = new THREE.Clock();

function animate() {
    requestAnimationFrame(animate);
    const deltaTime = clock.getDelta();
    updatePhysics(deltaTime);
    renderer.render(scene, camera);
}
animate();

Ключевой момент: deltaTime должен быть синхронизирован с шагом симуляции Ammo.js, чтобы физика оставалась стабильной при изменении FPS.


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

Для составных объектов используется btCompoundShape. Он позволяет объединить несколько форм в одно тело:

const compoundShape = new Ammo.btCompoundShape();

const childShape1 = new Ammo.btBoxShape(new Ammo.btVector3(1,1,1));
const transform1 = new Ammo.btTransform();
transform1.setIdentity();
transform1.setOrigin(new Ammo.btVector3(0,1,0));
compoundShape.addChildShape(transform1, childShape1);

const childShape2 = new Ammo.btSphereShape(0.5);
const transform2 = new Ammo.btTransform();
transform2.setIdentity();
transform2.setOrigin(new Ammo.btVector3(0,-1,0));
compoundShape.addChildShape(transform2, childShape2);

const compoundBodyInfo = new Ammo.btRigidBodyConstructionInfo(5, new Ammo.btDefaultMotionState(), compoundShape, new Ammo.btVector3(0,0,0));
const compoundBody = new Ammo.btRigidBody(compoundBodyInfo);
physicsWorld.addRigidBody(compoundBody);

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


Оптимизация

  • Использовать статические объекты для сцен, где динамика не нужна.
  • Минимизировать количество физических тел с высокой полигональностью.
  • Для повторяющихся объектов применять одинаковые collision shapes.
  • Настраивать количество итераций и частоту обновления stepSimulation в зависимости от производительности.

Связь с анимацией персонажей

Для персонажей лучше использовать CapsuleShape и контролировать движение через силы и импульсы, а не прямое изменение позиции:

const capsuleShape = new Ammo.btCapsuleShape(0.5, 1.0);
const characterBodyInfo = new Ammo.btRigidBodyConstructionInfo(1, new Ammo.btDefaultMotionState(), capsuleShape, new Ammo.btVector3(0,0,0));
const characterBody = new Ammo.btRigidBody(characterBodyInfo);
characterBody.setAngularFactor(new Ammo.btVector3(0,0,0)); // запрет вращения
physicsWorld.addRigidBody(characterBody);

Ключевой момент: фиксирование угловой свободы предотвращает переворачивание персонажа при столкновениях и падениях.