Физический пикинг мышью

Физический пикинг в Cannon.js позволяет взаимодействовать с объектами физического мира с помощью курсора или другого указательного устройства. Основная идея заключается в определении объекта под курсором, создании связи (constraint) между курсором и объектом, и последующем управлении объектом через эту связь.

Cannon.js предоставляет гибкие инструменты для реализации таких взаимодействий, включая классы Ray, Vec3, Body, World и различные типы constraints, например PointToPointConstraint.


Настройка физического мира

Перед реализацией пикинга необходимо создать физический мир и добавить объекты. Ключевые шаги:

const world = new CANNON.World();
world.gravity.set(0, -9.82, 0);  // гравитация

// Создание тела для взаимодействия
const sphereBody = new CANNON.Body({
    mass: 1,
    shape: new CANNON.Sphere(1),
    position: new CANNON.Vec3(0, 5, 0)
});
world.addBody(sphereBody);

// Добавление плоской поверхности
const groundBody = new CANNON.Body({
    mass: 0,
    shape: new CANNON.Plane(),
    position: new CANNON.Vec3(0, 0, 0)
});
world.addBody(groundBody);

Ключевой момент — все объекты, которые должны взаимодействовать через пикинг, должны иметь ненулевую массу. Статические объекты (mass = 0) участвуют в коллизиях, но не могут перемещаться.


Применение лучевого взаимодействия (Raycasting)

Для определения объекта под курсором используется луч (Ray). Он запускается из позиции камеры в направлении курсора, преобразованного в мировые координаты.

const ray = new CANNON.Ray(world);
ray.from.copy(camera.position);
ray.to.copy(new CANNON.Vec3(x, y, z)); // точка в пространстве, соответствующая курсору

ray.intersectBodies(world.bodies, function(result) {
    if(result.length) {
        const hitBody = result[0].body;
        const hitPoint = result[0].hitPointWorld;
        // дальнейшие действия с найденным телом
    }
});

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

  • from и to — объекты типа CANNON.Vec3.
  • intersectBodies возвращает массив результатов, каждый из которых содержит тело, точку попадания и нормаль поверхности.
  • Можно фильтровать объекты по типу или маске столкновений для исключения ненужных тел.

Создание связи с объектом

После нахождения тела под курсором создается constraint, фиксирующая точку на теле относительно курсора. Для этого используется PointToPointConstraint.

let pickConstraint = null;
const localPivot = new CANNON.Vec3(); // точка на теле относительно его центра

function pickBody(body, hitPoint) {
    body.position.vsub(hitPoint, localPivot); // вычисление локальной точки
    pickConstraint = new CANNON.PointToPointConstraint(
        body,
        localPivot,
        null,           // в пространстве курсора нет тела
        hitPoint
    );
    world.addConstraint(pickConstraint);
}

Ключевые моменты:

  • localPivot задает смещение относительно центра масс тела.
  • Второй аргумент null указывает, что точка привязки в мировых координатах, а не к другому телу.
  • Constraint нужно удалять после завершения взаимодействия, чтобы тело снова стало свободным.

Перемещение объекта с курсором

При перемещении курсора обновляется позиция точки в мире, к которой привязано тело. Для этого обновляется pivotB constraint:

function movePickedBody(hitPointWorld) {
    if(pickConstraint) {
        pickConstraint.updatePivotB(hitPointWorld);
    }
}

Особенности управления:

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

Завершение взаимодействия

Когда пользователь отпускает объект, constraint удаляется из мира:

function releasePickedBody() {
    if(pickConstraint) {
        world.removeConstraint(pickConstraint);
        pickConstraint = null;
    }
}

Это гарантирует, что тело снова участвует в динамике мира без ограничений.


Советы по оптимизации

  1. Фильтрация тел: не запускать луч по всем объектам, использовать collisionFilterMask и collisionFilterGroup.
  2. Интерполяция: для предотвращения рывков при быстром перемещении курсора лучше интерполировать позицию точки привязки.
  3. Масса объектов: слишком легкие тела могут “улетать” при быстром движении курсора. Для стабильности используйте умеренные значения массы.
  4. Частота обновления: обновлять позицию constraint в основном цикле мира (world.step) для корректной симуляции.

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

Часто пикинг применяется совместно с визуализацией в Three.js. В этом случае луч пересчитывается с учётом камеры и координат мыши на экране:

const mouse = new THREE.Vector2(); // координаты курсора в [-1, 1]

function onMouseMove(event) {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    const vector = new THREE.Vector3(mouse.x, mouse.y, 0.5).unproject(camera);
    const dir = vector.sub(camera.position).normalize();
    const distance = 100;
    const target = camera.position.clone().add(dir.multiplyScalar(distance));

    ray.from.copy(camera.position);
    ray.to.copy(target);
    movePickedBody(ray.to);
}

Интеграция с Three.js позволяет визуально контролировать процесс пикинга, синхронизируя физическое тело и 3D-модель.


Физический пикинг в Cannon.js сочетает лучевое определение объектов, создание constraints и управление через курсор. Правильная настройка массы, фильтрации и плавности движения обеспечивает реалистичное взаимодействие с физическим миром.