Физический движок Oimo.js реализует дискретную
пошаговую симуляцию твёрдых тел на основе интеграции уравнений движения
и решения ограничений. Центральным элементом является объект мира —
World, который управляет обновлением тел, столкновениями и
решением ограничений в рамках одного шага времени.
Цикл симуляции состоит из последовательных этапов:
Каждый вызов world.step() запускает полный цикл
обновления сцены.
Физическая симуляция в Oimo.js основана на фиксированном временном
шаге dt, который определяет, насколько «вперёд» во времени
продвигается система за один вызов шага.
world.step();
По умолчанию используется внутреннее значение dt,
заданное при создании мира:
const world = new OIMO.World({
timestep: 1 / 60,
iterations: 8,
broadphase: 2,
worldscale: 1,
random: true,
info: false,
gravity: [0, -9.8, 0]
});
Параметр timestep определяет фиксированный шаг
времени.
В реальных приложениях частота кадров может меняться. Если передавать
реальное deltaTime напрямую, возникают проблемы:
Поэтому рекомендуется использовать фиксированный временной шаг и аккумулировать прошедшее время.
let accumulator = 0;
const fixedTimeStep = 1 / 60;
function update(deltaTime) {
accumulator += deltaTime;
while (accumulator >= fixedTimeStep) {
world.step(fixedTimeStep);
accumulator -= fixedTimeStep;
}
}
Такая схема обеспечивает:
На первом этапе определяется список потенциально пересекающихся тел. Используются грубые объёмы (AABB), чтобы сократить количество проверок.
Broadphase:
Это критично для производительности, особенно при большом числе объектов.
На этом этапе выполняется точная проверка столкновения:
Результатом являются контактные точки, которые передаются в систему ограничений.
Каждое столкновение преобразуется в набор ограничений:
Контакты представляются как уравнения, которые необходимо решить итерационно.
Oimo.js использует итерационный метод (Sequential Impulse Solver), основанный на методах Гаусса–Зейделя.
Основная идея:
Параметр iterations определяет количество проходов:
const world = new OIMO.World({
timestep: 1 / 60,
iterations: 10
});
| Итерации | Результат |
|---|---|
| 4–6 | Быстро, но менее стабильно |
| 8–10 | Баланс качества и скорости |
| 12+ | Более жёсткие соединения, выше нагрузка |
Малое число итераций приводит к:
Большое число итераций увеличивает нагрузку на CPU.
После решения ограничений происходит интеграция:
Используется полунеявный метод Эйлера:
v += a * dt
x += v * dt
Такой подход обеспечивает устойчивость по сравнению с явным методом Эйлера.
Гравитация добавляется как постоянное ускорение:
gravity: [0, -9.8, 0]
Для оптимизации производительности тела могут «засыпать».
Тело засыпает, если:
Преимущества:
Тело автоматически пробуждается при:
Обычно физика обновляется внутри игрового цикла:
let lastTime = performance.now();
function loop() {
const now = performance.now();
const deltaTime = (now - lastTime) / 1000;
lastTime = now;
update(deltaTime);
requestAnimationFrame(loop);
}
loop();
Физика обновляется независимо от рендеринга, но синхронизируется с ним.
Если сцена содержит:
можно выполнять несколько подшагов за кадр.
for (let i = 0; i < 3; i++) {
world.step(1 / 180);
}
Преимущества:
Недостаток — рост нагрузки.
Оптимальным считается диапазон:
1/60 – 1/120 секунды
Солвер работает с импульсами, а не напрямую с силами.
Импульс:
J = F * dt
Каждое ограничение корректирует:
Псевдокод итерационного решения:
for iteration:
for each constraint:
compute corrective impulse
apply impulse to bodies
Сходимость достигается постепенно. Чем больше итераций — тем ближе результат к точному решению.
Физические движки чувствительны к:
Рекомендуемый масштаб:
Нарушение масштабов приводит к:
При фиксированном шаге физики и переменном FPS возможна визуальная интерполяция:
alpha = accumulator / fixedTimeStep
renderPosition = current * alpha + previous * (1 - alpha)
Это устраняет визуальные рывки при низком FPS.
Использование сна.
Если сцена проста — можно уменьшить iterations.
Меньше сложных коллайдеров — быстрее narrowphase.
Не выполнять лишние подшаги без необходимости.
Упрощённая последовательность:
Такой порядок обеспечивает:
Силы применяются перед интеграцией:
body.applyForce([0, 10, 0]);
Во время шага:
Импульсы же воздействуют мгновенно и используются в солвере ограничений.
timestep: 1/60iterations: 8iterations: 10–12При фиксированном:
симуляция будет детерминированной на одной платформе. Это важно для:
Переменный шаг разрушает детерминированность.
Цикл симуляции в Oimo.js представляет собой сбалансированную систему численных методов, оптимизаций и итерационных алгоритмов, обеспечивающих устойчивое и предсказуемое моделирование твёрдых тел в реальном времени.