Для работы с физикой в 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. Основные
шаги:
btCollisionShape) и параметры
массы.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 поддерживает разные типы форм и материалов. Наиболее распространённые формы:
Материалы определяются через коэффициенты трения и упругости:
boxBody.setFriction(0.5);
boxBody.setRestitution(0.7); // отскок
Ключевой момент: трение влияет на скольжение, а restitution — на реакцию при столкновении. Для игровых сцен значения обычно подбираются экспериментально.
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 можно использовать основной рендер-цикл:
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);
Ключевой момент: компаунд-объекты упрощают создание сложных физических конструкций без ручного управления каждым элементом.
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);
Ключевой момент: фиксирование угловой свободы предотвращает переворачивание персонажа при столкновениях и падениях.