Drag-and-drop объектов в 3D

Drag-and-drop (перетаскивание) объектов в 3D-пространстве с использованием Three.js требует понимания нескольких ключевых компонентов: Raycaster, обработка событий мыши и преобразование координат экран → мир.

Raycaster используется для определения пересечения курсора с объектами сцены. С его помощью можно вычислить, какой объект пользователь пытается выбрать и перемещать.

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

События мыши (mousedown, mousemove, mouseup) позволяют отслеживать начало, процесс и завершение перетаскивания:

  • mousedown – определяет объект для захвата.
  • mousemove – изменяет позицию объекта в пространстве.
  • mouseup – завершает операцию drag-and-drop.

Преобразование координат

Для корректной работы требуется преобразовать координаты курсора из пикселей экрана в нормализованные координаты устройства (NDC):

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

После этого Raycaster может вычислить пересечение с объектами сцены:

raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
    selectedObject = intersects[0].object;
}

Ограничение движения по плоскости

Для удобного перетаскивания часто создается виртуальная плоскость (THREE.Plane) через объект и камеру. Это позволяет объекту перемещаться только по определенной поверхности, избегая смещения в глубину сцены:

const plane = new THREE.Plane();
const planeNormal = new THREE.Vector3();

При захвате объекта вычисляется нормаль плоскости относительно камеры:

planeNormal.copy(camera.position).sub(selectedObject.position).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, selectedObject.position);

Для обновления позиции объекта при движении мыши используется пересечение с плоскостью:

const intersection = new THREE.Vector3();
raycaster.ray.intersectPlane(plane, intersection);
selectedObject.position.copy(intersection).add(offset);

Где offset — вектор смещения от точки клика до центра объекта.

Управление несколькими объектами

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

const draggableObjects = [];

И при пересечении проверяется только этот массив:

const intersects = raycaster.intersectObjects(draggableObjects, true);

Сглаживание движения

Чтобы перемещение выглядело плавным, используют интерполяцию:

selectedObject.position.lerp(intersection.clone().add(offset), 0.2);

Здесь 0.2 — коэффициент сглаживания, который можно регулировать для ускорения или замедления движения.

Drag-and-drop с ограничениями

Three.js позволяет накладывать ограничения на движение объекта:

  • По одной оси: меняется только выбранная координата (x, y или z).
  • По плоскости: фиксируются две координаты, третья изменяется.
  • Диапазон перемещения: объект не выходит за заданные границы.

Пример ограничения по оси X:

selectedObject.position.x = intersection.x;

Интеграция с OrbitControls

Когда используется THREE.OrbitControls, необходимо временно отключать вращение камеры во время перетаскивания:

controls.enabled = !isDragging;

Где isDragging — булева переменная, активная при захвате объекта.

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

  • Ограничивать количество проверяемых объектов Raycaster.
  • Перемещать только выбранный объект, не обновляя все объекты сцены.
  • Использовать requestAnimationFrame для плавного обновления сцены.

Примеры расширенного использования

  • Перетаскивание объектов с физикой через Cannon.js или Ammo.js.
  • Snap-to-grid: объекты прилипают к виртуальной сетке при перемещении.
  • Drag-and-drop UI-подсказок: визуальные индикаторы выбранного объекта или линии траектории движения.

Эта схема позволяет создавать интерактивные 3D-сцены с естественным и контролируемым перемещением объектов, полностью используя возможности Three.js и современных браузеров.