Настройка проекта с Webpack и Vite

Подключение и сборка Ammo.js

Ammo.js — это порт библиотеки Bullet Physics на JavaScript через Emscripten. Основная задача при интеграции — корректная загрузка WASM-модуля и обеспечение его доступности для физического движка. В современных сборщиках, таких как Webpack и Vite, важно правильно настроить обработку статических файлов и асинхронную инициализацию.

Варианты подключения:

  1. Через npm:
npm install ammo.js

После установки можно импортировать модуль:

import Ammo from 'ammo.js';

При этом Ammo() возвращает Promise, который резолвится после загрузки WASM-движка:

Ammo().then((AmmoLib) => {
    const dynamicsWorld = new AmmoLib.btDiscreteDynamicsWorld(...);
});
  1. Через CDN:
<script src="https://cdn.jsdelivr.net/npm/ammo.js"></script>
<script>
    Ammo().then((AmmoLib) => {
        const collisionConfiguration = new AmmoLib.btDefaultCollisionConfiguration();
        ...
    });
</script>

Для Webpack и Vite предпочтителен первый вариант, так как он позволяет оптимизировать загрузку и интеграцию в модульную систему.


Настройка Webpack

При использовании Webpack, нужно убедиться, что WASM-файл правильно обрабатывается. Для этого:

  1. В webpack.config.js необходимо разрешить загрузку .wasm:
module.exports = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.wasm$/,
        type: 'webassembly/async'
      }
    ]
  },
  experiments: {
    asyncWebAssembly: true
  }
};
  1. Импорт Ammo.js в коде:
import Ammo from 'ammo.js';

async function initPhysics() {
    const AmmoLib = await Ammo();
    const collisionConfiguration = new AmmoLib.btDefaultCollisionConfiguration();
    const dispatcher = new AmmoLib.btCollisionDispatcher(collisionConfiguration);
    const overlappingPairCache = new AmmoLib.btDbvtBroadphase();
    const solver = new AmmoLib.btSequentialImpulseConstraintSolver();
    const dynamicsWorld = new AmmoLib.btDiscreteDynamicsWorld(
        dispatcher,
        overlappingPairCache,
        solver,
        collisionConfiguration
    );
    dynamicsWorld.setGravity(new AmmoLib.btVector3(0, -9.8, 0));
    return dynamicsWorld;
}

Важно использовать async/await или then, так как модуль загружается асинхронно. Без этого попытка создать объекты Ammo.js приведет к ошибкам undefined.

  1. Подключение статических ассетов. Если проект использует кастомный WASM-файл, его нужно положить в папку public или обрабатывать через file-loader.

Настройка Vite

Vite поддерживает ES-модули и WASM из коробки, что упрощает интеграцию Ammo.js:

  1. Установка:
npm install ammo.js
  1. Импорт и инициализация:
import Ammo from 'ammo.js';

let physicsWorld;

async function setupPhysics() {
    const AmmoLib = await Ammo();
    const collisionConfig = new AmmoLib.btDefaultCollisionConfiguration();
    const dispatcher = new AmmoLib.btCollisionDispatcher(collisionConfig);
    const broadphase = new AmmoLib.btDbvtBroadphase();
    const solver = new AmmoLib.btSequentialImpulseConstraintSolver();
    physicsWorld = new AmmoLib.btDiscreteDynamicsWorld(
        dispatcher,
        broadphase,
        solver,
        collisionConfig
    );
    physicsWorld.setGravity(new AmmoLib.btVector3(0, -9.8, 0));
}
  1. Обработка WASM. Vite автоматически подгружает WASM при использовании import. При использовании кастомных файлов достаточно положить их в public/ и указать путь:
const AmmoLib = await Ammo({ locateFile: f => `/wasm/${f}` });

Это позволяет кастомизировать путь к WASM без изменения сборки.


Основные принципы интеграции

  • Асинхронная загрузка: все объекты Ammo.js создаются после резолва Promise.
  • Управление памятью: объекты Bullet создаются через new Ammo.btVector3(...). Для избежания утечек необходимо удалять объекты вручную: Ammo.destroy(obj).
  • Глобальный доступ: при работе с Webpack и Vite Ammo.js может быть локальным модулем. Для тестовых сцен удобно держать ссылку window.AmmoLib = AmmoLib;.
  • Совместимость с TypeScript: можно добавить декларацию:
declare module 'ammo.js' {
    const Ammo: any;
    export default Ammo;
}

Рекомендации по структуре проекта

  • src/physics/ — хранение логики физики.
  • src/physics/init.js — инициализация btDiscreteDynamicsWorld.
  • src/physics/objects.js — создание rigid body и коллайдеров.
  • public/wasm/ — кастомные версии WASM (опционально).

Организация кода по модулям облегчает масштабирование и интеграцию с рендерингом на Three.js или Babylon.js.


Особенности работы с модулями

  • Ammo.js использует Emscripten, поэтому большинство объектов создаются через конструкторы new Ammo.bt....
  • В Webpack необходимо включить experiments.asyncWebAssembly.
  • В Vite достаточно стандартного импорта и, при необходимости, указания locateFile.
  • Для оптимизации производительности рекомендуется создавать пул объектов для векторов и матриц, чтобы уменьшить частые аллокации и сборку мусора.

Пример интеграции с игровым циклом

let deltaTime = 1 / 60;

function updatePhysics() {
    if (!physicsWorld) return;
    physicsWorld.stepSimulation(deltaTime, 10);
    // Обновление объектов сцены на основе rigid bodies
    for (let obj of rigidBodies) {
        const ms = obj.getMotionState();
        if (ms) {
            const transform = new AmmoLib.btTransform();
            ms.getWorldTransform(transform);
            const p = transform.getOrigin();
            const q = transform.getRotation();
            obj.mesh.position.set(p.x(), p.y(), p.z());
            obj.mesh.quaternion.set(q.x(), q.y(), q.z(), q.w());
            Ammo.destroy(transform);
        }
    }
}

Такая структура позволяет плавно интегрировать Ammo.js с любым рендерером, обеспечивая независимость физики от визуальной части и корректное управление памятью.