Подключение Ammo.js через CDN

Особенности распространения

Ammo.js — это JavaScript-порт физического движка Bullet, скомпилированный в WebAssembly или asm.js с помощью Emscripten. Библиотека распространяется в виде одного или нескольких файлов:

  • ammo.js — asm.js-версия;
  • ammo.wasm.js + ammo.wasm.wasm — WebAssembly-версия;
  • минифицированные сборки (ammo.min.js).

При подключении через CDN чаще всего используется готовая сборка WebAssembly, так как она обеспечивает значительно более высокую производительность по сравнению с asm.js.


Выбор CDN

Наиболее распространённые CDN для подключения библиотек:

  • cdnjs
  • jsDelivr
  • unpkg

CDN позволяют подключить библиотеку без установки через npm и без локального хранения файлов проекта. Это особенно удобно для:

  • прототипирования,
  • учебных примеров,
  • демонстрационных проектов,
  • песочниц и онлайн-редакторов.

Базовое подключение через <script>

Минимальный вариант подключения через jsDelivr:

<script src="https://cdn.jsdelivr.net/npm/ammo.js"></script>

Однако важно учитывать, что Ammo.js инициализируется асинхронно. В отличие от большинства библиотек, глобальный объект Ammo становится доступен не сразу.

Современная версия возвращает Promise, поэтому корректное использование выглядит следующим образом:

<script src="https://cdn.jsdelivr.net/npm/ammo.js"></script>
<script>
  Ammo().then(function (AmmoLib) {
    // AmmoLib — инициализированный API
    const collisionConfig = new AmmoLib.btDefaultCollisionConfiguration();
    console.log("Ammo.js успешно загружен");
  });
</script>

Почему используется Promise

Сборка WebAssembly требует:

  1. Загрузки .wasm-файла
  2. Компиляции модуля
  3. Инициализации памяти
  4. Связывания экспортов

Все эти операции происходят асинхронно. Поэтому попытка использовать Ammo напрямую без then() приведёт к ошибке.


Подключение конкретной версии

Фиксация версии критична для стабильности учебных и продакшен-проектов.

Пример подключения определённой версии через jsDelivr:

<script src="https://cdn.jsdelivr.net/npm/ammo.js@0.0.10/builds/ammo.wasm.js"></script>

Фиксирование версии защищает от:

  • внезапных изменений API,
  • несовместимых обновлений,
  • изменения структуры сборки.

Подключение WebAssembly-сборки явно

Иногда CDN не автоматически находит .wasm-файл. В этом случае требуется явно указать путь:

<script>
  Ammo({
    locateFile: function (path) {
      return "https://cdn.jsdelivr.net/npm/ammo.js@0.0.10/builds/" + path;
    }
  }).then(function (AmmoLib) {
    console.log("WASM загружен");
  });
</script>

Назначение locateFile

Функция locateFile переопределяет путь к вспомогательным файлам (в частности .wasm). Без неё браузер будет искать файл относительно текущего HTML-документа.


Использование с модульным JavaScript (ES Modules)

При работе в среде с поддержкой ES6-модулей подключение через CDN возможно динамическим импортом:

<script type="module">
  import AmmoModule from "https://cdn.jsdelivr.net/npm/ammo.js@0.0.10/builds/ammo.wasm.js";

  AmmoModule().then((AmmoLib) => {
    const world = new AmmoLib.btDiscreteDynamicsWorld();
    console.log(world);
  });
</script>

Важно учитывать, что не все CDN корректно отдают сборки как ES-модули. В некоторых случаях потребуется использовать версию, совместимую с UMD.


Проверка успешной загрузки

Корректная инициализация определяется наличием ключевых классов:

Ammo().then(function (AmmoLib) {
  if (AmmoLib.btVector3) {
    console.log("Physics API доступен");
  }
});

Если btVector3 отсутствует, значит:

  • библиотека не загрузилась,
  • произошла ошибка инициализации,
  • заблокирован .wasm-файл (CORS).

Особенности CORS и MIME-типа

WebAssembly-файл должен:

  • возвращаться с корректным MIME-типом application/wasm,
  • быть доступным по CORS.

CDN обычно корректно настраивают эти параметры, но при проксировании или нестандартных настройках сервера могут возникнуть ошибки:

WebAssembly.instantiate(): incorrect MIME type

В таком случае рекомендуется:

  • использовать официальный CDN,
  • проверять заголовки ответа в DevTools.

Подключение вместе с Three.js

В связке с трёхмерной графикой часто используется Three.js. Порядок подключения имеет значение:

<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ammo.js@0.0.10/builds/ammo.wasm.js"></script>

После загрузки обе библиотеки могут использоваться совместно внутри Ammo().then().

Важно понимать, что:

  • Three.js и Ammo.js не зависят друг от друга напрямую;
  • синхронизация позиций объектов выполняется вручную;
  • физический мир должен обновляться до рендеринга кадра.

Глобальная область видимости

При подключении через обычный <script> библиотека добавляет фабричную функцию Ammo в глобальный scope:

window.Ammo

После инициализации API передаётся в callback. Рекомендуется сохранять ссылку:

let AmmoLib;

Ammo().then(function (lib) {
  AmmoLib = lib;
});

Это предотвращает повторные вызовы фабрики и лишние аллокации памяти.


Производительность WebAssembly против asm.js

CDN может отдавать asm.js-версию при неправильной конфигурации. Отличия:

Параметр asm.js WebAssembly
Скорость ниже значительно выше
Размер больше меньше
Поддержка устаревшая современная

Для реальных физических симуляций рекомендуется использовать только WASM-сборку.


Типовые ошибки при подключении

1. Ammo is not defined

Причины:

  • <script> подключён после кода,
  • опечатка в URL,
  • блокировка CDN.

2. Использование API вне then()

const world = new Ammo.btDiscreteDynamicsWorld(); // Ошибка

Ammo ещё не инициализирован.

3. Повторная инициализация

Ammo().then(...)
Ammo().then(...)

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


Минимальный рабочий шаблон HTML

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Ammo.js CDN</title>
</head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/ammo.js@0.0.10/builds/ammo.wasm.js"></script>
  <script>
    Ammo().then(function (AmmoLib) {

      const collisionConfig = new AmmoLib.btDefaultCollisionConfiguration();
      const dispatcher = new AmmoLib.btCollisionDispatcher(collisionConfig);
      const broadphase = new AmmoLib.btDbvtBroadphase();
      const solver = new AmmoLib.btSequentialImpulseConstraintSolver();

      const world = new AmmoLib.btDiscreteDynamicsWorld(
        dispatcher,
        broadphase,
        solver,
        collisionConfig
      );

      world.setGravity(new AmmoLib.btVector3(0, -9.8, 0));

      console.log("Физический мир создан");
    });
  </script>
</body>
</html>

Когда CDN-подключение не подходит

Несмотря на удобство, существуют ограничения:

  • отсутствие контроля над версией в долгосрочной перспективе,
  • невозможность кастомной сборки,
  • зависимость от внешнего сервиса,
  • невозможность tree-shaking.

Для крупных проектов предпочтительнее установка через npm и сборка через bundler (Webpack, Vite, Rollup).


Подключение через CDN остаётся оптимальным способом быстрого старта, прототипирования и учебной разработки, позволяя сосредоточиться на работе с физическим API без настройки инфраструктуры сборки.