Обработка пользовательского ввода из основного потока

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

const world = new OIMO.World({
    timestep: 1/60,       // шаг симуляции в секундах
    iterations: 8,        // количество итераций для решения ограничений
    broadphase: 2,        // алгоритм broadphase: 1 - brute force, 2 - sweep & prune
    gravity: [0, -9.8, 0] // вектор гравитации
});

Ключевые параметры:

  • timestep — определяет точность и стабильность симуляции; слишком большой шаг приведет к нестабильности.
  • iterations — количество итераций для решения ограничений; большее число повышает точность столкновений, но снижает производительность.
  • broadphase — алгоритм предварительного определения потенциальных столкновений.

Создание тел

В Oimo.js физические объекты представлены через RigidBody. Тела бывают dynamic, static и kinematic.

const box = world.add({
    type: 'box',
    size: [1, 1, 1],
    pos: [0, 5, 0],
    rot: [0, 0, 0],
    move: true,       // true для dynamic, false для static
    density: 1
});

Основные параметры:

  • size — размеры в метрах по осям X, Y, Z.
  • pos — начальная позиция.
  • rot — угол вращения в радианах.
  • move — определяет динамическое поведение.
  • density — плотность, влияющая на массу тела.

Для sphere или cylinder параметры аналогичны, меняется только геометрическая форма.

Обработка столкновений

Oimo.js позволяет отслеживать столкновения через контакты. Каждое тело хранит массив текущих контактов:

world.step();

box.contacts.forEach(contact => {
    console.log('Контакт с телом', contact.body.name);
});

Для оптимизации можно использовать callback функции при начале и окончании контакта:

box.onCollisionEn ter = function(other) {
    console.log('Начало столкновения с', other.name);
};

box.onCollisionE xit = function(other) {
    console.log('Окончание столкновения с', other.name);
};

Управление движением через пользовательский ввод

Обработка клавиш

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

const velocity = new OIMO.Vec3();

document.addEventListener('keydown', (e) => {
    switch(e.code) {
        case 'ArrowUp': velocity.z = -5; break;
        case 'ArrowDown': velocity.z = 5; break;
        case 'ArrowLeft': velocity.x = -5; break;
        case 'ArrowRight': velocity.x = 5; break;
    }
    box.linearVelocity = velocity;
});

document.addEventListener('keyup', (e) => {
    velocity.set(0, 0, 0);
    box.linearVelocity = velocity;
});

Ключевой момент: Oimo.js использует linearVelocity и angularVelocity, а не прямое изменение позиции. Это обеспечивает корректное взаимодействие с физикой.

Применение силы и импульса

Для более реалистичного движения используют методы applyImpulse и applyForce:

const impulse = new OIMO.Vec3(0, 10, 0);
box.applyImpulse(impulse);
  • applyForce действует постепенно, учитывая массу.
  • applyImpulse мгновенно изменяет скорость тела.

Такой подход полезен для прыжков, толчков или мгновенных эффектов от столкновений.

Синхронизация с визуализацией

Физика работает отдельно от рендеринга. После каждого шага симуляции нужно обновлять объекты на сцене:

function animate() {
    requestAnimationFrame(animate);
    world.step();

    mesh.position.set(box.position.x, box.position.y, box.position.z);
    mesh.quaternion.set(box.rotation.x, box.rotation.y, box.rotation.z, box.rotation.w);

    renderer.render(scene, camera);
}
animate();

Принцип: перемещение визуального объекта следует линейной и угловой скорости тела, а не изменению позиции вручную.

Обработка множественных объектов

Для управления несколькими телами можно хранить их в массиве и применять одинаковую обработку ввода:

const objects = [box1, box2, box3];

document.addEventListener('keydown', e => {
    objects.forEach(obj => {
        if(e.code === 'ArrowUp') obj.linearVelocity.z = -5;
    });
});

Такой подход позволяет реализовать управление несколькими объектами или синхронизацию действий между ними.

Управление ограничениями и соединениями

Oimo.js поддерживает Joints для соединения тел:

  • DistanceJoint — фиксированное расстояние.
  • HingeJoint — вращение вокруг оси.
  • SliderJoint — движение по направлению.
const hinge = world.add({
    type: 'joint',
    jointType: 'hinge',
    body1: box1,
    body2: box2,
    pos1: [0, 0.5, 0],
    pos2: [0, -0.5, 0],
    axis: [0, 0, 1]
});

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

Обработка событий в основном потоке

Все изменения тел, вызванные пользователем, следует делать в основном потоке перед шагом симуляции. Это обеспечивает корректное взаимодействие между физикой и визуализацией.

function mainLoop() {
    handleInput();  // изменение velocity, applyImpulse
    world.step();
    updateMeshes();
    requestAnimationFrame(mainLoop);
}
mainLoop();

Суть: любое вмешательство в физику вне основного цикла может привести к расхождению симуляции и визуализации, поэтому строго соблюдается порядок:

  1. Обработка ввода.
  2. Шаг физического мира.
  3. Обновление визуальных объектов.

Вывод

Oimo.js позволяет реализовать полноценное взаимодействие пользователя с физическим миром через linearVelocity, angularVelocity, силы и импульсы. Управление должно быть синхронизировано с шагами симуляции, а все визуальные объекты — соответствовать состоянию физических тел. Использование событий клавиатуры, мыши или сенсоров возможно при условии обновления параметров тел только в основном потоке перед world.step().