Архитектура двухпоточного приложения

Oimo.js — это высокопроизводительная физическая библиотека для JavaScript, реализующая твердые тела с динамикой столкновений и симуляцией физических законов. Основной принцип работы основан на динамическом интегрировании и решении ограничений через систему тел и суставов. Библиотека позволяет создавать сцены с множеством объектов, поддерживая как статические, так и динамические тела, а также взаимодействия между ними.

В основе лежат следующие концепции:

  • World — пространство физической симуляции, содержащее все тела и управляющее шагами симуляции.
  • RigidBody — отдельное физическое тело, которое может быть динамическим, статическим или кинематическим.
  • Shape — геометрическая форма тела, определяющая его коллизию (Box, Sphere, Cylinder и т. д.).
  • Joint — соединение двух тел с ограничениями движения (Hinge, Slider, BallJoint).
  • Solver — модуль решения системы ограничений и столкновений.

Создание мира и тел

import * as OIMO from 'oimo';

let world = new OIMO.World({
    timestep: 1/60,
    gravity: [0, -9.8, 0],
    iterations: 8
});

let ground = world.add({
    type: 'box',
    size: [100, 1, 100],
    pos: [0, -0.5, 0],
    move: false
});

let ball = world.add({
    type: 'sphere',
    size: [1],
    pos: [0, 5, 0],
    move: true,
    density: 1
});
  • timestep — шаг симуляции, влияет на точность и стабильность.
  • gravity — вектор силы тяжести.
  • iterations — количество итераций решателя для ограничения ошибок коллизий.

RigidBody создается через метод world.add(), указывая тип формы, размер, позицию и подвижность. Статические объекты (move: false) не участвуют в динамике, но создают коллизии.

Управление шагами симуляции

Основная функция — world.step(), вызываемая в цикле отрисовки. Она обновляет позиции и ориентации тел, рассчитывает силы, столкновения и ограничения суставов.

function update() {
    world.step();
    let pos = ball.getPosition();
    console.log(`Ball position: ${pos}`);
    requestAnimationFrame(update);
}

update();

Важно: частота вызова step() должна соответствовать timestep. Несоответствие приводит к нестабильности и “прыжкам” объектов.

Свойства тел

У объектов Oimo.js есть ключевые свойства:

  • position и rotation — положение и ориентация в мировых координатах.
  • linearVelocity и angularVelocity — скорость перемещения и вращения.
  • mass и density — масса и плотность, влияющие на динамику.
  • friction и restitution — коэффициенты трения и упругости при столкновениях.
ball.setLinearVelocity([0, 5, 0]);
ball.setAngularVelocity([0, 0, 1]);
ball.friction = 0.5;
ball.restitution = 0.8;

Формы и коллизии

Oimo.js поддерживает несколько базовых форм:

  • Box — параллелепипед, используется для платформ и стен.
  • Sphere — идеально подходит для шаров и круглых объектов.
  • Cylinder — применяется для цилиндрических тел и колонн.
  • Capsule — удобна для персонажей и скользящих объектов.

Каждая форма имеет свои параметры размера и центр масс. Коллизии рассчитываются по геометрии формы с применением алгоритмов bounding volume hierarchy (BVH) для ускорения.

Суставы и ограничения

Суставы соединяют два тела и ограничивают их движение:

  • HingeJoint — вращение вокруг одной оси.
  • SliderJoint — движение по одной линии.
  • BallJoint — свободное вращение, как шаровой шарнир.
let hinge = new OIMO.HingeJoint(ball, ground, {
    pos1: [0, 5, 0],
    pos2: [0, 0, 0],
    axis: [0, 0, 1]
});
world.addJoint(hinge);

Параметры суставов позволяют задавать пределы углов, силы пружины и демпфирование.

Двухпоточная архитектура

Для сложных сцен с большим количеством тел эффективным решением является двухпоточная архитектура. Основная идея — разделение:

  1. Главный поток — рендеринг и обработка событий пользователя.
  2. Поток симуляции (Worker) — вычисление физических шагов через world.step().

Преимущества:

  • Уменьшение нагрузки на главный поток, предотвращение зависаний интерфейса.
  • Возможность выполнения сложных расчётов коллизий и суставов параллельно.
  • Более точное соблюдение timestep без пропуска кадров рендеринга.

Пример передачи данных в worker:

// main.js
const worker = new Worker('physicsWorker.js');
worker.postMessage({ type: 'init', config: { gravity: [0, -9.8, 0] } });

function update() {
    worker.postMessage({ type: 'step' });
    requestAnimationFrame(update);
}

// physicsWorker.js
importScripts('oimo.min.js');
let world;

onmess age = function(e) {
    const data = e.data;
    if(data.type === 'init') {
        world = new OIMO.World({ gravity: data.config.gravity });
    }
    if(data.type === 'step') {
        world.step();
        postMessage({ positions: world.getPositions() });
    }
}

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

Оптимизация производительности

  • Использование статических тел для платформ, пола и стен снижает нагрузку на решатель.
  • Минимизация количества суставов и сложных коллизий.
  • Разделение сцен на слои и активация физических расчётов только для активных объектов.
  • Настройка параметров iterations и timestep для балансировки точности и производительности.

Итоговая структура приложения

  1. Инициализация мира в worker.
  2. Создание тел и суставов с корректными свойствами.
  3. Цикл симуляции в отдельном потоке с передачей данных о позициях.
  4. Рендеринг на главном потоке с использованием полученных координат.

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