Oimo.js — это трёхмерный физический движок, реализующий жёсткотельную динамику в среде JavaScript. Его архитектура построена по классической схеме пошаговой симуляции с дискретным временем. В основе лежит цикл обновления, в котором происходит интеграция движения тел, обнаружение столкновений и разрешение контактов.
Общая схема работы движка включает следующие ключевые подсистемы:
Каждая из этих подсистем участвует в пошаговой симуляции и взаимодействует с остальными через строго определённый порядок вызовов.
Основной управляющий объект — World. Он:
step().Пример создания мира:
const world = new OIMO.World({
gravity: [0, -9.8, 0]
});
Гравитация задаётся вектором, влияющим на все динамические тела.
Движок использует дискретную интеграцию. Каждый
вызов world.step(dt) выполняет полный цикл симуляции на
интервал времени dt.
Типичная схема в игровом цикле:
function update() {
world.step(1/60);
requestAnimationFrame(update);
}
Фиксированный шаг обеспечивает стабильность расчётов и предсказуемость поведения системы.
Жёсткое тело — базовый физический объект. Оно содержит:
Тела бывают двух типов:
Пример создания динамического тела:
const body = world.add({
type: 'box',
size: [1, 1, 1],
pos: [0, 10, 0],
move: true
});
Форма определяет геометрию столкновения. Oimo.js поддерживает:
Форма прикрепляется к телу и участвует только в расчётах столкновений. Визуализация осуществляется сторонними библиотеками, например, Three.js.
Важно понимать разделение:
Это разделение позволяет оптимизировать вычисления.
Каждый вызов step() выполняет строгую последовательность
действий.
На этом этапе:
Формула линейного ускорения:
a = F / m
Где:
Задача — быстро определить потенциально пересекающиеся пары тел.
Используется алгоритм пространственного разбиения (чаще всего Sweep and Prune). Он:
На этом этапе точная геометрия не проверяется.
Для каждой потенциальной пары:
В зависимости от формы используются различные алгоритмы:
Результатом является объект контакта.
Контакт описывается как ограничение, предотвращающее проникновение тел друг в друга.
Каждый контакт содержит:
Контакт формирует импульсное ограничение:
Jv + b = 0
Где:
Эти ограничения передаются в решатель.
Oimo.js использует итерационный импульсный решатель.
Этапы работы:
Решатель работает несколько итераций за шаг (обычно 8–20), что повышает стабильность.
Импульсы корректируют:
Это устраняет проникновение и моделирует трение.
После разрешения всех ограничений выполняется интеграция движения.
Используется полуявный метод Эйлера:
v += a * dt
x += v * dt
Для вращения применяется интеграция кватерниона.
Этап интеграции:
После расчётов:
Типичный цикл взаимодействия с визуализацией:
mesh.position.copy(body.getPosition());
mesh.quaternion.copy(body.getQuaternion());
Таким образом физика и графика остаются независимыми.
Для оптимизации Oimo.js использует механизм “сна”.
Тело засыпает, если:
Спящее тело исключается из вычислений до тех пор, пока не будет разбужено столкновением или силой.
Это значительно снижает нагрузку при большом количестве объектов.
Помимо контактов существуют механические ограничения:
Они формируют дополнительные уравнения, обрабатываемые тем же решателем.
Каждое ограничение:
Суммарно один шаг симуляции включает:
Эта последовательность повторяется на каждом кадре.
Движок не содержит встроенного рендеринга. Он интегрируется в цикл приложения:
function loop() {
world.step(1/60);
render();
requestAnimationFrame(loop);
}
Часто используется совместно с Three.js, где:
Такое разделение соответствует классической архитектуре игрового движка: физика — логика — визуализация.
Внутренний порядок вызовов можно представить упрощённой схемой:
World.step()
├── applyForces()
├── broadPhase()
├── narrowPhase()
├── solveConstraints()
│ ├── preSolve()
│ ├── iterate()
│ └── postSolve()
├── integrate()
└── updateSleepState()
Чёткая последовательность критична. Нарушение порядка приводит к:
Стабильность обеспечивается:
При слишком большом dt:
Рекомендуемое значение: 1/60 или 1/120.
Каждый объект в мире хранит:
Контакты образуют граф взаимодействий. Решатель проходит по этому графу при вычислении импульсов.
Таким образом физический мир представляет собой динамическую систему взаимосвязанных ограничений.
Общая схема работы Oimo.js может быть представлена как:
Силы → Столкновения → Ограничения → Импульсы → Новые скорости → Новые позиции
Этот цикл повторяется дискретно, создавая иллюзию непрерывного движения.
Вся архитектура движка построена вокруг строгой пошаговой обработки, что обеспечивает предсказуемость, расширяемость и высокую производительность при моделировании трёхмерной физики в среде JavaScript.