Пикинг объектов: Raycaster
Как понять, по какому 3D-объекту кликнул пользователь? Пустить луч из камеры через курсор. Это и есть Raycaster.
Raycaster пускает воображаемый луч в сцену и возвращает список пересечённых объектов, отсортированный по расстоянию. Так реализуют клики и наведение в 3D.
Идея луча
Экран плоский, а сцена объёмная. Когда вы кликаете в точку канваса, под ней по глубине может быть несколько объектов. Чтобы узнать, по какому именно вы попали, из камеры через позицию курсора пускают луч и смотрят, что он проткнул первым.
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2(); // координаты мыши в NDC
window.addEventListener('click', (e) => {
pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
pointer.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
const hits = raycaster.intersectObjects(scene.children);
if (hits.length > 0) {
hits[0].object.material.color.set(0xff0000); // первый задетый — красный
}
});
intersectObjects возвращает массив попаданий, уже отсортированный: hits[0] — ближайший к камере объект, по которому вы и кликнули.
Как это считается: луч и сфера
Под капотом пересечение луча с объектом — это геометрия. Покажем суть на самом наглядном случае — луч и сфера. Идея: найти, насколько близко луч проходит к центру сферы. Если это расстояние не больше радиуса — есть пересечение.
// Луч: старт origin + направление dir (единичное). Сфера: центр C, радиус R.
function raySphereHit(origin, dir, center, R) {
// вектор от старта луча к центру сферы
const ox = center.x - origin.x;
const oy = center.y - origin.y;
const oz = center.z - origin.z;
// проекция этого вектора на направление луча
const t = ox * dir.x + oy * dir.y + oz * dir.z;
// ближайшая точка луча к центру
const cx = origin.x + dir.x * t;
const cy = origin.y + dir.y * t;
const cz = origin.z + dir.z * t;
// расстояние от центра до луча
const dx = center.x - cx, dy = center.y - cy, dz = center.z - cz;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
return { dist: +dist.toFixed(3), hit: dist <= R };
}
const origin = { x: 0, y: 0, z: 0 };
const dir = { x: 0, y: 0, z: -1 }; // луч смотрит вдоль -Z
console.log('Сфера прямо по курсу:', raySphereHit(origin, dir, { x: 0, y: 0, z: -5 }, 1));
console.log('Сфера в стороне:', raySphereHit(origin, dir, { x: 3, y: 0, z: -5 }, 1));
Вывод:
Сфера прямо по курсу: { dist: 0, hit: true }
Сфера в стороне: { dist: 3, hit: false }
Луч, направленный вдоль -Z, проходит ровно через центр сферы, стоящей по курсу (расстояние 0 ≤ радиуса 1 — попадание). Сфера, сдвинутая на 3 единицы вбок, оказывается дальше радиуса — мимо. Именно такую проверку Three.js делает за вас для каждого объекта.
На практике
Сам Raycaster проверяет пересечение луча с треугольниками меша, а не только со сферой, — но принцип «насколько близко луч проходит» тот же. Используйте его для кликов по объектам, наведения (hover), выделения, простой стрельбы в играх.
Итог
- Raycaster пускает луч из камеры через курсор и находит пересечённые объекты.
intersectObjectsотдаёт попадания по возрастанию расстояния; первый — ближайший.- Суть пересечения: сравнить расстояние от объекта до линии луча с его размером.