Портирование проектов с Cannon.js на Oimo.js

При портировании проекта с Cannon.js на Oimo.js необходимо учитывать различия в архитектуре движков, подходах к построению мира и внутренней математике.

Cannon.js ориентирован на гибкость и читаемость API, активно использует классы Vec3, Body, Shape, World. Oimo.js построен вокруг более компактной и оптимизированной структуры данных, уделяя особое внимание производительности и минимизации накладных расходов.

Ключевые различия:

  • Разная система масштабов и единиц измерения
  • Отличия в управлении шагом симуляции
  • Разные подходы к созданию тел и форм
  • Иная структура столкновений и контактных данных
  • Отличия в параметрах сна (sleep) и стабилизации

Корректное портирование требует системной переработки физического слоя, а не простого переименования классов.


Создание физического мира

В Cannon.js

const world = new CANNON.World();
world.gravity.set(0, -9.82, 0);
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 10;

В Oimo.js

const world = new OIMO.World({
    gravity: [0, -9.82, 0]
});

Основные отличия

  1. В Oimo.js параметры передаются в конструктор.
  2. Broadphase не настраивается вручную — используется встроенная оптимизированная система.
  3. Итерации решателя задаются через параметры конфигурации:
const world = new OIMO.World({
    gravity: [0, -9.82, 0],
    iterations: 8
});

При портировании следует проверить баланс между производительностью и стабильностью, так как значения по умолчанию отличаются.


Шаг симуляции

Cannon.js

world.step(1/60);

Oimo.js

world.step(1/60);

На первый взгляд API совпадает, но есть различия:

  • В Cannon.js можно передавать дополнительный параметр deltaTime и maxSubSteps.
  • В Oimo.js чаще используется фиксированный шаг.
  • Oimo.js чувствительнее к нестабильным deltaTime.

Для корректного портирования рекомендуется использовать фиксированный timestep и избегать плавающего шага без субшагов.


Создание тел (Body)

Cannon.js

const body = new CANNON.Body({
    mass: 1,
    position: new CANNON.Vec3(0, 5, 0),
    shape: new CANNON.Box(new CANNON.Vec3(1,1,1))
});

world.addBody(body);

Oimo.js

const body = world.add({
    type: 'box',
    size: [2, 2, 2],
    pos: [0, 5, 0],
    move: true,
    density: 1
});

Отличия

  1. В Oimo.js тела создаются через world.add().
  2. Размер коробки задаётся полной длиной сторон, а не половинными extents.
  3. Масса вычисляется через плотность (density), а не напрямую.
  4. Статическое тело создаётся через move: false.

При портировании необходимо пересчитать размеры: если в Cannon использовались halfExtents [1,1,1], то в Oimo нужно передать [2,2,2].


Работа с формами (Shapes)

Коробка

  • Cannon.js: new CANNON.Box(halfExtents)
  • Oimo.js: type: 'box', size: [width, height, depth]

Сфера

Cannon.js:

new CANNON.Sphere(1);

Oimo.js:

type: 'sphere',
size: [1]

Цилиндр

В Cannon.js цилиндр требует дополнительной ориентации. В Oimo.js цилиндр задаётся напрямую:

type: 'cylinder',
size: [radius, height]

При портировании сложных форм необходимо проверить совпадение ориентации осей (в некоторых версиях Oimo цилиндр ориентирован вдоль оси Y по умолчанию).


Материалы и трение

Cannon.js

const material = new CANNON.Material();
const contactMaterial = new CANNON.ContactMaterial(material, material, {
    friction: 0.3,
    restitution: 0.5
});
world.addContactMaterial(contactMaterial);

Oimo.js

В Oimo.js параметры задаются прямо при создании тела:

world.add({
    type: 'box',
    size: [2,2,2],
    friction: 0.3,
    restitution: 0.5
});

Отличия

  • В Cannon.js контактные материалы можно настраивать между конкретными парами.
  • В Oimo.js параметры чаще задаются на уровне тела.
  • Глобальной системы ContactMaterial нет в классическом виде.

При сложной логике взаимодействия материалов потребуется реализовать дополнительные слои логики.


Сон тел (Sleep)

Cannon.js

body.allowSleep = true;
body.sleepSpeedLimit = 0.1;
body.sleepTimeLimit = 1;

Oimo.js

world.add({
    type: 'box',
    sleep: true
});

Параметры сна в Oimo.js менее детализированы. Если проект активно использовал ручное управление сном, потребуется дополнительная оптимизация логики.


Работа с ограничениями (Constraints)

Cannon.js

const constraint = new CANNON.DistanceConstraint(bodyA, bodyB, 2);
world.addConstraint(constraint);

Oimo.js

world.add({
    type: 'jointDistance',
    body1: bodyA,
    body2: bodyB,
    distance: 2
});

Различия

  • В Oimo.js ограничения создаются через world.add().
  • Названия типов отличаются (jointHinge, jointBall, jointDistance).
  • Параметры могут иметь другие имена.

Портирование сложных суставов (hinge, cone twist) требует проверки осей вращения и якорных точек.


Коллизии и события столкновения

Cannon.js

body.addEventListener('collide', function(e) {
    console.log(e.body);
});

Oimo.js

В Oimo.js события столкновений реализуются через проверку контактного списка мира:

let contact = world.contacts;
while(contact){
    // обработка
    contact = contact.next;
}

Отличия

  • В Cannon.js события привязаны к телам.
  • В Oimo.js обработка более низкоуровневая.
  • Нет встроенной системы событий в привычном виде.

При портировании логики игровых триггеров потребуется собственный слой обработки столкновений.


Работа с поворотами и кватернионами

Cannon.js

body.quaternion.setFromEuler(0, Math.PI/2, 0);

Oimo.js

world.add({
    rot: [0, 90, 0]
});

Особенности:

  • В Oimo.js углы часто задаются в градусах.
  • В Cannon.js — в радианах.
  • Внутренние представления отличаются.

Это одна из самых частых причин некорректного поведения после портирования.


Интеграция с Three.js

При связке с Three.js различия проявляются в синхронизации:

Cannon.js:

mesh.position.copy(body.position);
mesh.quaternion.copy(body.quaternion);

Oimo.js:

mesh.position.set(body.getPosition().x, ...);

Или через метод:

body.getMatrix();

Некоторые сборки Oimo.js предоставляют матрицу преобразования напрямую, что может ускорить синхронизацию.


Масштабирование мира

Oimo.js более чувствителен к масштабу сцены. Рекомендуемые размеры объектов:

  • 0.1 – 10 единиц
  • Избегать очень маленьких тел (< 0.01)
  • Избегать огромных масс (> 1000 без необходимости)

Если проект на Cannon.js использовал произвольный масштаб, может потребоваться нормализация размеров.


Производительность

Oimo.js обычно:

  • Быстрее на мобильных устройствах
  • Более стабилен при большом количестве простых тел
  • Менее гибок в кастомизации

Cannon.js лучше подходит для проектов с нестандартной физикой, Oimo.js — для производительных браузерных симуляций.


Типичные проблемы при портировании

1. Неверные размеры коробок Причина — различие halfExtents и полного размера.

2. Неправильные углы вращения Причина — радианы против градусов.

3. Нестабильные соединения Причина — различие параметров joint-ов.

4. Изменение поведения трения Причина — другая модель контактов.

5. Сбои при больших массах Причина — более строгая стабилизация в Oimo.


Стратегия портирования

  1. Перенести создание мира.
  2. Пересчитать размеры всех форм.
  3. Проверить массы и плотности.
  4. Переписать ограничения.
  5. Переписать обработку коллизий.
  6. Проверить вращения.
  7. Провести нагрузочное тестирование.

Переход с Cannon.js на Oimo.js требует внимательной ревизии всех физических параметров. Простая механическая замена API приводит к нестабильной симуляции и рассинхронизации логики сцены.