В библиотеке Ammo.js объект
btMotionState отвечает за синхронизацию физического
состояния тела с визуальным представлением. Этот механизм особенно важен
для динамических тел, где физика определяет позицию и ориентацию
объекта, а графический рендеринг должен корректно их отображать.
btMotionState позволяет разделять логику физики и
рендеринга:
Ammo.js предоставляет базовую реализацию
btDefaultMotionState, однако для специфических задач часто
создают пользовательский MotionState, наследуя его
функциональность и добавляя собственную логику.
В JavaScript пользовательский MotionState создается через объект с двумя основными методами:
getWorldTransform(transform) Этот метод
вызывается движком перед интеграцией симуляции, чтобы получить текущее
мировое положение объекта. Внутри метода обычно обновляется экземпляр
btTransform:
const myMotionState = {
getWorldTransform: function (worldTrans) {
const pos = mesh.position;
const quat = mesh.quaternion;
worldTrans.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
worldTrans.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
}
};
Ключевой момент: метод должен корректно
преобразовывать значения из системы рендеринга (Three.js или другой
движок) в объекты btVector3 и
btQuaternion.
setWorldTransform(transform) Вызывается движком после расчета физики для обновления позиции графической модели. Обычно здесь происходит синхронизация с визуальной сценой:
myMotionState.setWorldTransform = function (worldTrans) {
const origin = worldTrans.getOrigin();
const rotation = worldTrans.getRotation();
mesh.position.set(origin.x(), origin.y(), origin.z());
mesh.quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());
};
Особенность: метод вызывается в контексте физического шага симуляции и должен минимизировать накладные расходы на обновление графики.
После создания MotionState объект прикрепляется к телу
(btRigidBody) при его конфигурации:
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);
const shape = new Ammo.btBoxShape(new Ammo.btVector3(1, 1, 1));
shape.calculateLocalInertia(mass, localInertia);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
const body = new Ammo.btRigidBody(rbInfo);
physicsWorld.addRigidBody(body);
Для пользовательского MotionState достаточно заменить
btDefaultMotionState на созданный объект, реализующий
getWorldTransform и setWorldTransform.
Если сцена содержит десятки или сотни динамических тел, создание индивидуальных MotionState может быть ресурсоемким. В таких случаях применяются:
setWorldTransform обновляет массив визуальных моделей.btVector3 и
btQuaternion.Ammo.js — это порт Bullet Physics, использующий WebAssembly. Поэтому:
btVector3,
btQuaternion, btTransform нужно явно
освобождать через .delete(), иначе возникает утечка
памяти.getWorldTransform или
setWorldTransform.Пример с переиспользованием:
const tmpVec = new Ammo.btVector3();
const tmpQuat = new Ammo.btQuaternion();
myMotionState.setWorldTransform = function (worldTrans) {
const origin = worldTrans.getOrigin();
const rotation = worldTrans.getRotation();
tmpVec.setValue(origin.x(), origin.y(), origin.z());
mesh.position.set(tmpVec.x(), tmpVec.y(), tmpVec.z());
tmpQuat.setValue(rotation.x(), rotation.y(), rotation.z(), rotation.w());
mesh.quaternion.set(tmpQuat.x(), tmpQuat.y(), tmpQuat.z(), tmpQuat.w());
};
Такой подход снижает нагрузку на сборщик мусора и улучшает производительность при интенсивной симуляции.
Пользовательский MotionState особенно полезен в следующих сценариях:
Использование пользовательского MotionState позволяет добиться максимальной точности синхронизации между физикой и графикой, гибкости в управлении объектами и высокой производительности при сложных сценах.