Ammo.js — это порт популярного физического движка Bullet Physics на JavaScript с использованием Emscripten. Работа с ним напрямую в TypeScript требует корректной типизации, так как библиотека поставляется без встроенных деклараций типов. Типизация обеспечивает безопасность кода, автодополнение и предотвращает большинство ошибок на этапе компиляции.
Ammo.js обычно подключается как модуль через import или
глобально через <script>. Для TypeScript
рекомендуется использовать модульный подход:
import Ammo from "ammo.js";
Для работы с TypeScript создаются декларации типов
(.d.ts), которые описывают основные классы, методы
и свойства:
declare module "ammo.js" {
export default function Ammo(): Promise<typeof AmmoNamespace>;
export namespace AmmoNamespace {
class btVector3 {
constructor(x?: number, y?: number, z?: number);
x(): number;
y(): number;
z(): number;
setValue(x: number, y: number, z: number): void;
}
class btRigidBody {
constructor(constructionInfo: btRigidBodyConstructionInfo);
setMassProps(mass: number, inertia: btVector3): void;
setLinearVelocity(velocity: btVector3): void;
}
class btRigidBodyConstructionInfo {
constructor(mass: number, motionState: btMotionState, collisionShape: btCollisionShape, localInertia: btVector3);
}
class btCollisionShape {}
class btBoxShape extends btCollisionShape {
constructor(halfExtents: btVector3);
}
class btMotionState {}
}
}
Такая декларация покрывает базовые сущности, необходимые для работы с физикой тел. В реальных проектах рекомендуется расширять её по мере использования дополнительных возможностей Ammo.js.
Класс btVector3 — основной для представления
позиции, скорости, силы. В TypeScript важно определить
типы аргументов и возвращаемых значений:
const position = new Ammo.btVector3(0, 10, 0);
const velocity = new Ammo.btVector3(1, 0, 0);
position.setValue(5, 15, 0);
const xCoord: number = position.x();
Типизация предотвращает передачу некорректных типов, например строк вместо чисел. Это особенно важно при расчетах физических взаимодействий.
Создание rigid body требует корректной типизации всех параметров:
const boxShape = new Ammo.btBoxShape(new Ammo.btVector3(1, 1, 1));
const mass = 1;
const localInertia = new Ammo.btVector3(0, 0, 0);
boxShape.calculateLocalInertia?.(mass, localInertia);
const motionState = new Ammo.btMotionState();
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, boxShape, localInertia);
const body = new Ammo.btRigidBody(rbInfo);
body.setLinearVelocity(new Ammo.btVector3(0, 10, 0));
Здесь каждый объект имеет чётко определённый тип. Использование
optional chaining (?.) учитывает возможность отсутствия
метода в некоторых версиях Ammo.js.
Если Ammo.js подключается через <script> и создаёт
глобальный объект, TypeScript требует объявить его:
declare var Ammo: {
btVector3: typeof AmmoNamespace.btVector3;
btRigidBody: typeof AmmoNamespace.btRigidBody;
btBoxShape: typeof AmmoNamespace.btBoxShape;
btRigidBodyConstructionInfo: typeof AmmoNamespace.btRigidBodyConstructionInfo;
btMotionState: typeof AmmoNamespace.btMotionState;
};
Такое объявление позволяет использовать библиотеку без ошибок компиляции, сохраняя автодополнение.
Ammo.js предоставляет множество классов для работы с коллизиями:
btCollisionShape, btBoxShape,
btSphereShape, btCollisionObject. Для
TypeScript желательно создать обобщённый тип:
type CollisionShape = AmmoNamespace.btBoxShape | AmmoNamespace.btSphereShape | AmmoNamespace.btCollisionShape;
Это упрощает создание функций, которые могут принимать разные формы тел:
function createRigidBody(shape: CollisionShape, mass: number): AmmoNamespace.btRigidBody {
const localInertia = new Ammo.btVector3(0, 0, 0);
shape.calculateLocalInertia?.(mass, localInertia);
const motionState = new Ammo.btMotionState();
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
return new Ammo.btRigidBody(rbInfo);
}
Типизация позволяет избежать ошибок, когда случайно передаётся неподдерживаемый объект.
Ammo.js часто поставляется как асинхронный модуль:
let AmmoInstance: typeof AmmoNamespace;
Ammo().then((AmmoLib) => {
AmmoInstance = AmmoLib;
const vec = new AmmoInstance.btVector3(0, 1, 0);
});
В TypeScript важно определить переменную AmmoInstance с
точным типом typeof AmmoNamespace. Это гарантирует, что все
методы и классы будут корректно распознаны компилятором.
Для крупных проектов рекомендуется создавать собственные декларации, расширяя стандартный набор Ammo.js:
declare module "ammo.js" {
export namespace AmmoNamespace {
class btSphereShape extends btCollisionShape {
constructor(radius: number);
}
class btQuaternion {
constructor(x: number, y: number, z: number, w: number);
setValue(x: number, y: number, z: number, w: number): void;
}
}
}
Это позволяет покрыть все используемые классы и методы, сохраняя строгую типизацию TypeScript и минимизируя runtime-ошибки.
Ammo.js использует WebAssembly, поэтому объекты необходимо очищать вручную, чтобы не было утечек памяти:
const vec = new Ammo.btVector3(1, 2, 3);
// использование
vec.delete();
Типизация помогает отслеживать, какие объекты нужно удалить, особенно при работе с массивами тел и коллизий:
const bodies: AmmoNamespace.btRigidBody[] = [];
bodies.forEach(body => body.delete());
.d.ts с описанием всех используемых классов и
методов.<script>.Эта интеграция позволяет использовать все преимущества TypeScript: безопасность типов, автодополнение, проверку ошибок на этапе компиляции, одновременно работая с высокопроизводительным физическим движком Ammo.js.