Передача трансформов без лишних аллокаций

В контексте физического движка Ammo.js критически важно правильно работать с трансформами объектов, чтобы избежать лишних аллокаций памяти и повышенной нагрузки на сборщик мусора. Каждый физический объект (rigid body) содержит объект btRigidBody, который имеет свой трансформ btTransform, представляющий позицию и ориентацию объекта в мировых координатах.

Основные методы работы с трансформами:

  • getWorldTransform() — возвращает текущий мировой трансформ тела.
  • setWorldTransform(btTransform) — задаёт новый мировой трансформ.

Ключевой момент заключается в том, что каждый вызов new btTransform() создаёт новый объект в памяти. При частом обновлении трансформаций это приводит к интенсивной работе сборщика мусора и падению производительности.


Использование одного объекта btTransform повторно

Чтобы избежать лишних аллокаций, рекомендуется создавать объект btTransform один раз и повторно использовать его для чтения и записи трансформов. Например:

const tmpTransform = new Ammo.btTransform();

// чтение трансформа тела
rigidBody.getMotionState().getWorldTransform(tmpTransform);

// модификация позиции
const origin = tmpTransform.getOrigin();
origin.setValue(x, y, z);

// установка обратно в тело
rigidBody.getMotionState().setWorldTransform(tmpTransform);
rigidBody.setWorldTransform(tmpTransform);

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


Разделение чтения и записи трансформов

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

const tmpReadTransform = new Ammo.btTransform();
rigidBody.getMotionState().getWorldTransform(tmpReadTransform);

const pos = tmpReadTransform.getOrigin();
const quat = tmpReadTransform.getRotation();

При этом объект для записи (tmpTransform) остаётся неизменным, что упрощает управление памятью и предотвращает непреднамеренные побочные эффекты.


Прямое обновление btRigidBody

Иногда требуется обновить положение тела без использования MotionState. В этом случае можно напрямую работать с btRigidBody:

tmpTransform.setOrigin(new Ammo.btVector3(x, y, z));
tmpTransform.setRotation(new Ammo.btQuaternion(qx, qy, qz, qw));

rigidBody.setWorldTransform(tmpTransform);

Важно помнить, что btVector3 и btQuaternion также являются объектами, и их постоянное создание приведёт к аллокациям. Для оптимизации следует заранее создавать их и переиспользовать.

const tmpOrigin = new Ammo.btVector3();
const tmpQuat = new Ammo.btQuaternion();

// в цикле обновления
tmpOrigin.setValue(x, y, z);
tmpQuat.setValue(qx, qy, qz, qw);

tmpTransform.setOrigin(tmpOrigin);
tmpTransform.setRotation(tmpQuat);
rigidBody.setWorldTransform(tmpTransform);

Интеграция с визуализацией

Для синхронизации физики с рендером рекомендуется использовать одну структуру данных для хранения трансформов. Вместо того чтобы создавать новые объекты Vector3 или Quaternion на каждый кадр, можно обновлять существующие:

const meshPosition = { x: 0, y: 0, z: 0 };
const meshQuaternion = { x: 0, y: 0, z: 0, w: 1 };

rigidBody.getMotionState().getWorldTransform(tmpTransform);

const origin = tmpTransform.getOrigin();
meshPosition.x = origin.x();
meshPosition.y = origin.y();
meshPosition.z = origin.z();

const rotation = tmpTransform.getRotation();
meshQuaternion.x = rotation.x();
meshQuaternion.y = rotation.y();
meshQuaternion.z = rotation.z();
meshQuaternion.w = rotation.w();

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


Рекомендации по оптимизации

  1. Создавать объекты трансформаций один раз и переиспользовать их во всём цикле обновления.
  2. Использовать отдельные временные объекты для чтения и записи, чтобы не изменять текущий трансформ по ошибке.
  3. Минимизировать создание btVector3 и btQuaternion внутри циклов. Локальные объекты должны быть постоянными и переиспользуемыми.
  4. Обновлять визуальные объекты напрямую из одного временного трансформа без промежуточных аллокаций.
  5. Не вызывать new btTransform() каждый кадр, это ключевой источник утечек и тормозов при большом количестве объектов.

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