3 мар. 2019 г., 6:57
AFrame Raycaster. Программно определяем пересекаемые предметы на заданной прямой.
Всем привет!
Сегодняшняя статья тоже больше в формате заметки, интересных визуальных примеров не будет. Но на освоение этого ушло несколько часов, поэтому тоже надо записать, чтобы не забыть.
В прошлой статье я писал уже про компонент Raycaster. Он позволяет определить какие объекты в трехмерном пространстве пересекаются при прохождении заданного луча. Это нужно, к примеру, чтобы при наведении мышкой определять на сцене те объекты, на которые мышка наведена.
Но вчера я потратил еще кучу времени на его освоение, ибо не все задачи решаются приведенными в прошлой статье примерами, а так же многое не очевидно и есть некоторая путаница, которые я и попытаюсь здесь объяснить.
В предыдущем примере (как и во многих других), начальная точка луча - это текущая активная камера. То есть, начальная точка луча будет "выходить" из центра экрана. Можно, конечно, объект Raycaster поместить и в любой другой объект в пространстве, и тогда он будет выходить из центра указанного объекта. Пример
Тогда этот объект будет начальной точной для луча, а куда мышка будет направлена, там и будет целевая точка, куда будет проходить луч.
Но как программно задать Raycaster, без необходимости помещать его в какой-то объект и задать ему произвольные точки начала и конца? И вот тут возникла путаница... Дело в том, что в AFrame используется THREE-js. Именно THREE-js отвечает за отрисовку, все эти рейкасты и т.д. и т.п. В документации AFrame не оказалось нужного примера, но он есть в документации THREE-js. Вот он:
Здесь для нас самое главное (определения источника путаницы) вот это:
То есть после его создания и задания ему начальных координат и координат мыши, мы ему должны скормить массив объектов, которые надо проверить на факт пересечения с лучом. В данном случае это scene.children. И вот путаница возникла в том, что в HTML у NODE-элементов тоже есть свойство children и при передачи ноды a-scene никаких ошибок не возникает, но и результат всегда пустой массив приходит, хотя явно пересечения имеются.
Суть проблемы кроется в том, что THREE-js оперирует массивами mesh-объектов и он предполагает, что на вход придет именно массив мешей. А у нас получается даже не массив, а HTMLCollection из DOM-нод. На самом деле THREE-js ругается, но не сильно, то есть выводит сообщение, что это не массив в warn (просто проверяя, что это не массив, хотя тем способом, что он делает перебор, можно и HTMLCollection было перебрать). Но если мы HTMLCollection преобразуем в массив и скормим его, получим фатальную ошибку, что у object нет метода reacast. Дело в том, что a-entity AFrame не имеет этого свойства. ОК, копаем чуть глубже и находим, что есть этот метод у a-entity.object3D. ОК, передаем массив этих объектов.
ОК, теперь не ругается. Но и результат все еще пустой массив. Не растет каменный цветок...
Смотрел-смотрел, что не так, и выяснил, что метод raycast у этих объектов - пустая функция, ничего не выполняющая. Тупо заглушка... А реальная функция находится в объекте a-entity.object3DMap.mesh. ОК, переписываем так:
Вот теперь наконец-то работает!
UPD: забыл сказать, для базового понимания основ 3D, векторов, определения расстояния между точками и векторами, вот отличная статья: https://habr.com/ru/post/334580/