Передача трансформаций через TypedArray

Библиотека Oimo.js предназначена для высокопроизводительной физической симуляции тел в 3D-пространстве. Одним из ключевых аспектов работы с физикой является эффективная передача данных о трансформациях объектов между движком и визуализацией. Для этого используется TypedArray, который обеспечивает быстрый доступ к массивам чисел в формате, подходящем для WebGL и других графических API.

Основные концепции TypedArray

TypedArray — это типизированный массив в JavaScript, который хранит элементы строго определённого типа (например, Float32Array, Uint16Array). В контексте Oimo.js TypedArray применяется для:

  • хранения позиций и ориентаций тел,
  • передачи данных о масштабах и вращениях,
  • минимизации накладных расходов на конвертацию между JS-объектами и буферами GPU.

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

Структура трансформаций

Каждое тело в Oimo.js содержит позицию и ориентацию, которые можно представить в виде массива чисел:

// Пример структуры трансформации для одного тела
// [posX, posY, posZ, rotX, rotY, rotZ, rotW]
const transform = new Float32Array(7);
  • posX, posY, posZ — координаты центра масс тела.
  • rotX, rotY, rotZ, rotW — компоненты кватерниона, определяющие вращение.

Для передачи трансформаций сразу для всех тел создаётся большой массив:

const numBodies = world.numRigidBodies;
const transforms = new Float32Array(numBodies * 7);

for (let i = 0; i < numBodies; i++) {
    const body = world.rigidBodies[i];
    const offset = i * 7;
    transforms[offset] = body.position.x;
    transforms[offset + 1] = body.position.y;
    transforms[offset + 2] = body.position.z;
    transforms[offset + 3] = body.rotation.x;
    transforms[offset + 4] = body.rotation.y;
    transforms[offset + 5] = body.rotation.z;
    transforms[offset + 6] = body.rotation.w;
}

Связь с графическим движком

При работе с WebGL или Three.js массив трансформаций может быть передан напрямую в Shader Storage Buffer Object или использоваться для обновления Matrix4 объектов:

for (let i = 0; i < numBodies; i++) {
    const offset = i * 7;
    const position = transforms.subarray(offset, offset + 3);
    const rotation = transforms.subarray(offset + 3, offset + 7);

    mesh[i].position.set(position[0], position[1], position[2]);
    mesh[i].quaternion.set(rotation[0], rotation[1], rotation[2], rotation[3]);
}

Использование subarray позволяет избежать копирования данных и напрямую работать с нужной областью памяти.

Оптимизация производительности

  1. Предварительное выделение массива Создание одного большого Float32Array на весь кадр эффективнее, чем отдельные массивы для каждого тела.

  2. Использование кватернионов вместо матриц Кватернионы экономят память (4 числа против 16 в матрице) и упрощают интерполяцию вращений.

  3. Обновление только изменённых тел В динамических сценах можно хранить флаги изменения трансформации и обновлять только те тела, которые движутся.

  4. Совмещение массивов Для сложных объектов (позиция, вращение, масштаб) удобно объединять все данные в один TypedArray, чтобы передавать их одним блоком в шейдер.

Работа с масштабом

Oimo.js позволяет задать масштаб тела при создании через setShape. Для передачи масштаба вместе с позицией и вращением массив трансформаций расширяется:

// [posX, posY, posZ, rotX, rotY, rotZ, rotW, scaleX, scaleY, scaleZ]
const transformWithScale = new Float32Array(numBodies * 10);

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

Применение в симуляциях

TypedArray для трансформаций особенно полезен при:

  • симуляции множества твёрдых тел (например, разрушение конструкции, падение объектов),
  • реализации интерактивных приложений, где сцена обновляется на каждом кадре,
  • интеграции с GPU-вычислениями через WebGL 2 или WebGPU, где массивы напрямую загружаются в шейдер.

Эта методика обеспечивает высокую производительность и минимизацию задержек между расчётом физики и визуализацией.

Особенности работы с Oimo.js

  • Синхронизация с рендером: Обычный цикл выглядит как world.step() → обновление TypedArray → рендер.
  • Избежание лишних объектов: TypedArray позволяет не создавать временные объекты Vector3 и Quaternion каждый кадр.
  • Поддержка SIMD и WebAssembly: В будущих версиях Oimo.js возможна оптимизация через SIMD, где массивы уже совместимы с такими подходами.

Использование TypedArray делает систему трансформаций плоской, компактной и совместимой с современными графическими пайплайнами, что критично для сложных 3D-сцен с сотнями и тысячами динамических тел.