Трекинг плоскостей¶
Трекинг плоскостей (plane tracking) - это технология дополненной реальности( AR ), при которой система распознает, выделяет и отслеживает плоские поверхности в окружающем пространстве. В контексте приложений с дополненной реальностью, трекинг плоскостей позволяет разместить виртуальные объекты на горизонтальной или вертикальной поверхности: например на полу, на столе, на стене. Алгоритм обнаружит плоскую поверхность и при изменении положения камеры «удержит» виртуальный объект, размещенный на ней.
Для трекинга плоскостей на мобильных устройствах используются технологии ARCore и ARKit. ARCore - это платформа Google для создания приложений дополненной реальности на поддерживаемых устройствах Android. ARCore использует технологию SLAM: изображение с камеры и информация с датчиков(гироскоп, акселерометр и т.д.) используются, чтобы построить виртуальную карту точек окружающего пространства и определить относительное местоположение устройства. Для трекинга плоскостей ARCore извлекает из карты особенные точки, анализирует их и находит плоскости. Плоскости могут быть как горизонтальные(пол, поверхность стола, потолок), так и вертикальные(стены). Чтобы отслеживать виртуальные объекты на плоскости, ARCore использует так называемые «якоря» - отслеживаему объекту соответствует определенный «якорь» на карте, который привязан к окружающему пространству и его положение корректируется при изменении положения камеры, т.е. тем самым виртуальный объект сохраняет свое положение в реальном пространстве. ARCore в реальном времени обновляет плоскости: при перемещении камеры плоскость может расширяться(добавляются новые точки), изменять границы(если обнаружены новые части), объединяться с другими плоскостями и теряться(если поверхность пропала из поля зрения камеры). ARKit - это платформа Apple для разработки приложений дополненной реальности (AR) на iPhone и iPad. Она интегрирована прямо в iOS. ARKit имеет схожую с ARCore технологию, однако некоторые устройства Apple(например iPhone 12+ Pro/Pro Max) оснащены специальным оптическим сенсором(LiDAR), который заметно быстрее и точнее выполняет трекинг плоскостей(при чем делает это даже в затемненных помещениях, в отличие от ARCore, который чувствителен к освещению).
Трекинг плоскостей в EV Toolbox доступен в версии Advanced и подключается с помощью lua-скриптов, пример подключения вы можете найти на нашем гитхабе
Пример достаточно прост: отсканируйте камерой мобильного устройства окружающее пространство(поворачивая камеру телефона по сторонам), и когда будет найдена подходящая плоскость - можно будет разместить на ней модель. Подробно разберем этот пример:
function getReactorOrDie(name)
local reactor = reactorController:getReactorByName(name)
if not reactor then
error("Cannot find reactor '" .. tostring(name) .. "'")
end
return reactor
end
В начале утилитарная функция getReactorOrDie, вовращающая объект реактора по имени или ошибку, если реактор не был найден. Более подробно про работу с реакторами в коде можно прочитать в разделе Работа с реакторами
local info_txt = getReactorOrDie("info_txt")
local button = getReactorOrDie("btn")
local ar_plane_transform = getReactorOrDie("ar_plane_transform")
local plane_image = getReactorOrDie("plane/image")
local plane_model = getReactorOrDie("plane/model")
-- initial state
button:hide(0x0) -- not visible, not clickable
ar_plane_transform:hide() -- just hide (does not matter if clickable or not)
plane_image:show() -- just show (does not matter if clickable or not)
plane_model:hide()
Получение объектов всех необходимых реакторов: кнопки, изображения маркера плоскости, модели, которую мы будем размещать, текстовой информации. Затем начальная инициализация - объекты показываются или скрываются.
local busARCore = trackingSystem.busARCore
local busARKit = trackingSystem.busARKit
-- Symlinks for clean code
local busAR = busARCore and busARCore or busARKit
local BusAR_Class = busARCore and evar.BusARCore or evar.BusARKit
busARCore и busARKit - это внутренняя реализация доставки сообщений и событий между разными компонентами системы трекинга, в данном случае являются атрибутами глобального внутреннего объекта trackingSystem,
который инкапсулирует логику, связанную с трекингом(не путать с Система трекинга (TrackingSystemReactor)).
if not busAR then
local msg = "Cannot detect ARKit/ARcore"
if sys == "android" then
msg = msg .. "\nPlease install Google Play Services for AR"
end
logger:error(msg)
info_txt.text.value = msg
info_txt.text.color = osg.Vec4(1.0, 0.0, 0.0, 1.0)
return
end
Проверка: на мобильных устройствах поддерживающих технологию ARCore или ARKit переменная busAR будет инициализирована, на остальных нет. В зависимости от этого, в запущенном приложении будет показано
информационное сообщение сверху экрана.
Важно
На телефонах Android ARCore устанавливается как отдельное приложение/обновление и может называться Google Play Services for AR или ARCore
local planeFindingMode = bit_or(BusAR_Class.PLANE_HORIZONTAL, BusAR_Class.PLANE_VERTICAL)
busAR:setPlaneFindingModeMask(planeFindingMode)
Установка маски для поиска плоскостей. В данном случае используется функция bit_or (побитовое или) для установки маски в поиск горизонтальных и вертикальных плоскостей.
local function showPlane()
ar_plane_transform:show()
button:show(bit_or(NodeReactor.Mask.VISIBLE, NodeReactor.Mask.CLICKABLE))
end
local function onPlaneEnter()
logger:info("Plane entered")
showPlane()
end
Функция onPlaneEnter будет вызвана при нахождении плоскости. В данном случае, будет вызвана функция showPlane, которая покажет изображение(аватар) найденной плоскости и кнопку, по нажатии на которую можно будет поместить
модель на плоскость.
local function onPlaneMove(mat)
local m = evar.evar2osg(mat, false)
ar_plane_transform.node:setMatrix(m)
if not ar_plane_transform.visible then
showPlane()
end
end
Функция onPlaneMove будет вызвана при перемещении уже найденной плоскости(т.е. при перемещении камеры телефона, ранее найденная плоскость будет дополнена и ее расположение будет обновлено).
Аргументом этой функции приходит матрица трансформации плоскости, мы конвертируем ее внутренней функцией evar.evar2osg и устанавливаем у объекта Система координат (TransformNodeReactor) эту матрицу,
переведенную из одной системы координат в другую.
local function onPlaneLeave()
logger:info("Plane leave")
ar_plane_transform:hide(0x0)
button:hide(0x0)
end
Функция onPlaneLeave будет вызвана, если не удалось найти плоскость(т.е. ранее найденная плоскость пропала из поля зрения камеры) и в данном случае будет скрыта кнопка интерфейса и аватар.
local classification, min_area, min_distance, max_distance = BusAR_Class.MarkerPlane.PLANE_CLASS_NONE, 1.0, 1.0, 5.0
local planeTraits = BusAR_Class.MarkerPlane.Traits(classification, min_area, min_distance, max_distance)
local markerPlaneImpl = BusAR_Class.MarkerPlane(planeTraits) -- markerImpl ref. by the handler
Конфигурация поиска плоскостей. Переменная classification устанавливает категорию плоскостей: PLANE_CLASS_NONE, PLANE_CLASS_WALL, PLANE_CLASS_FLOOR, PLANE_CLASS_CEILING. Если установлен первый параметр, то будут найдены все типы плоскостей.
Последующие параметры включают трекинг только стен, пола, потолка соответственно. Переменные min_area, min_distance, max_distance устанавливают минимальную площадь плоскости, минимальную дистанцию до плоскости, максимальную дистанцию до плоскости.
markerImplHandler = nil -- handler not ref. by busARCore/busARKit, so create it global, this will be fixed in the future releases
if busARCore then
markerImplHandler = evar.BusARCore.Marker.HandlerLua(markerPlaneImpl, onPlaneEnter, onPlaneMove, onPlaneLeave)
else
markerImplHandler = evar.BusARKit.Marker.HandlerLua(markerPlaneImpl, trackingSystem.bus, onPlaneEnter, onPlaneMove, onPlaneLeave)
end
busAR:addListener(markerImplHandler) -- handler not ref. by busARCore/busARKit
busAR:addMarker(markerPlaneImpl)
В зависимости от платформы, устанавливаем наши обработчики событий и передаем их busAR.
button:subscribeEvent("onDown", function()
if markerPlaneImpl:isTagged() then
markerPlaneImpl:untag()
button.text.value = "ПОМЕСТИТЬ"
plane_image:show()
plane_model:hide()
else
markerPlaneImpl:tag()
button.text.value = "ПЕРЕМЕСТИТЬ"
plane_image:hide()
plane_model:show()
end
end)
Далее мы обрабатываем нажатие на кнопку «Поместить». Логика простая - если плоскость найдена, мы ее «закрепляем» функцией markerPlaneImpl:tag() и показываем модель на ней, заменив также текст на кнопке на «Переместить». Если
же плоскость уже закреплена, то мы ее открепляем функцией markerPlaneImpl:untag(), скрываем модель и меняем текст кнопки на «Поместить».