Клик и hover на объекты сцены

Взаимодействие пользователя с 3D-сценой в Three.js реализуется через события клика и наведения мыши (hover). Для обработки таких событий необходимо определить, на какие объекты пользователь указывает, и связать эти объекты с соответствующими реакциями. Основной инструмент для этого — Raycaster.


Raycaster

Raycaster позволяет создавать «луч» из точки камеры через позицию курсора на экране и определять, какие объекты пересекает этот луч.

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
  • mouse.x и mouse.y должны быть нормализованы в диапазон от -1 до 1.
  • Нормализация координат выполняется относительно размеров окна рендера:
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  • После этого луч направляется из камеры в сцену:
raycaster.setFromCamera(mouse, camera);
  • Для проверки пересечения с объектами сцены используется метод:
const intersects = raycaster.intersectObjects(scene.children, true);

Параметр true позволяет проверять вложенные объекты (дети).


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

Клик по объекту реализуется через событие мыши click или mousedown:

window.addEventListener('click', (event) => {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(scene.children, true);

    if (intersects.length > 0) {
        const clickedObject = intersects[0].object;
        console.log('Объект кликнут:', clickedObject.name);
        clickedObject.material.color.set(0xff0000);
    }
});

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

  • intersects[0] содержит ближайший к камере объект.
  • Можно использовать поле name или userData для идентификации объекта.
  • События клика могут изменять материал, запускать анимации или выполнять другие действия.

Hover (наведение мыши)

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

window.addEventListener('mousemove', (event) => {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
});

function animate() {
    requestAnimationFrame(animate);

    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(scene.children, true);

    scene.children.forEach(obj => obj.material.emissive?.set(0x000000));

    if (intersects.length > 0) {
        const hoveredObject = intersects[0].object;
        if (hoveredObject.material.emissive) {
            hoveredObject.material.emissive.set(0x555555);
        }
    }

    renderer.render(scene, camera);
}

animate();

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

  • Для hover часто используется свойство material.emissive для визуального подсвечивания.
  • Сначала нужно сбросить подсветку на всех объектах, чтобы предыдущие выделенные объекты возвращались в исходное состояние.
  • Проверка material.emissive предотвращает ошибки на объектах без свойства emissive.

Оптимизация проверки пересечений

Если сцена содержит множество объектов, проверка всех элементов на каждом кадре может быть дорогой. Решения:

  1. Группировка объектов: проверять только нужные группы (raycaster.intersectObjects(group.children)).
  2. Octree или BVH: структуры для быстрого поиска пересечений (существуют библиотеки, интегрированные с Three.js).
  3. Фильтрация по типу: например, проверять только меши (THREE.Mesh).

Хранение пользовательских данных

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

mesh.userData = {
    id: 1,
    type: 'enemy',
    onClick: () => { console.log('Враг атакован'); }
};

Использование userData позволяет:

  • Определять тип объекта
  • Хранить колбэки для клика или hover
  • Избежать проверки имени или материала напрямую
if (intersects.length > 0) {
    intersects[0].object.userData.onClick?.();
}

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

Обычно к объектам привязывают разные реакции для клика и hover:

if (intersects.length > 0) {
    const obj = intersects[0].object;
    obj.userData.hovered = true;
} else {
    scene.children.forEach(o => o.userData.hovered = false);
}
  • Можно реализовать состояния объекта (hovered, selected) для корректного отображения.
  • Для сложных сцен удобно хранить список активных объектов, чтобы не проверять всю сцену каждый раз.

Примеры практического применения

  • Подсветка кнопок 3D UI при наведении.
  • Выбор объектов в игре или визуализаторе (например, вращение, перемещение).
  • Запуск анимаций или звуков при клике по элементу.

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