Spec-Zone.ru › Web APIs

Входы и источники ввода

Безопасный контекст: Эта функция доступна только в безопасных контекстах (HTTPS), в некоторых или во всех поддерживающих браузерах.

Полный опыт WebXR не ограничивается показом пользователю полностью виртуальной сцены или дополнением реальности путем добавления или изменения окружающего мира. Для создания увлекательного и вовлекающего опыта пользователю необходимо взаимодействовать с ним. С этой целью WebXR поддерживает различные типы устройств ввода.

В этом руководстве мы рассмотрим, как использовать функции управления устройствами ввода WebXR, чтобы определить доступные источники ввода и затем отслеживать эти источники для обработки взаимодействия пользователя с вашей виртуальной или дополненной средой.

Входы в WebXR

Входы в WebXR в основном делятся на две категории: прицеливание и действия. Прицеливание — это определение точки в пространстве с помощью ввода пользователя. Это может включать нажатие пользователем на точку на экране, отслеживание его взгляда или использование джойстика или датчика движения для перемещения курсора.

Действия включают как действия выбора, такие как нажатие кнопки, так и действия сжатия, такие как нажатие спускового крючка или сжатие захвата при ношении сенсорных перчаток.

Комбинируя эти два типа ввода с изменением положения и/или ориентации просмотра через гарнитуру или другие механизмы, вы можете создать интерактивную симулированную среду.

Типы устройств ввода

WebXR поддерживает различные типы устройств для обработки ввода прицеливания и действий. К ним относятся, но не ограничиваются:

  • Нажатия на экран (особенно, но не только, на телефонах или планшетах) могут использоваться для одновременного выполнения прицеливания и выбора.
  • Устройства отслеживания движения, использующие акселерометры, магнитометры и другие датчики для отслеживания движения и прицеливания, а также, возможно, любые кнопки, джойстики, сенсорные панели, тачпады, датчики силы и т. д. для предоставления дополнительных источников ввода для прицеливания и выбора.
  • Сжимаемые спусковые крючки или сенсорные подушки перчаток для обеспечения действий сжатия.
  • Командный голос с помощью распознавания речи.
  • Пространственно отслеживаемые сочлененные руки, такие как проволочные перчатки, могут обеспечить как прицеливание, так и действия сжатия, а также выбор, если они оснащены кнопками или другими источниками действий выбора.
  • Устройства с одиночной кнопкой.
  • Отслеживание взгляда (слежение за движениями глаза для выбора целей).

Источники ввода

Каждый источник данных ввода WebXR представлен объектом XRInputSource, который описывает источник ввода и его текущее состояние. Информация для каждого источника ввода включает в себя руку, в которой он удерживается (если применимо), метод прицеливания, XRSpace, которые могут использоваться для рисования луча прицеливания и для поиска целевого объекта или местоположения, а также для рисования объектов в руках пользователя, и строки профилей, определяющие предпочтительный способ представления контроллера в области просмотра пользователя, а также способ работы ввода.

Основные возможности источника ввода:

Прицеливание

Отслеживание направляющих элементов управления (например, датчика движения, джойстика или трекпада) для прицеливания в направлении, возможно, на цель, хотя прицеливание вам нужно реализовать самостоятельно. Дополнительную информацию см. в разделе Ориентация и прицеливание.

Выбор

Использование основной кнопки «выбор» или другого ввода на контроллере для выбора целевого направления (или объекта, на который он указывает) или для выполнения другого действия. Подробнее об основном действии см. в разделе Основное действие.

Сжатие

Сжатие контроллера или механизма на контроллере для выполнения дополнительного действия. Подробное описание см. в разделе Основное действие сжатия.

Доступ к дополнительным возможностям контроллера WebXR осуществляется через объект gamepad источника ввода. Этот объект предоставляет доступ ко всем кнопкам, осям, тачпадам и т. д., которые могут быть частью контроллера. Узнайте, как использовать эти контроллеры, в разделе Расширенные контроллеры с использованием объекта gamepad.

Свойства экземпляра источников ввода

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

Рука

Рука, указанная свойством XRInputSource handedness, представляет собой строку, указывающую, в какой из рук зрителя находится контроллер: left или right. Она также может быть установлена в значение none , если контроллер не удерживается в руке или неизвестно, в какой руке он находится.

Свойство «рука» может использоваться для различных целей, включая выбор подходящей сетки для представления контроллера в представлении и правильного отображения его в соответствующей руке при отрисовке рук на дисплее. Оно также полезно, если ваше приложение использует понятия «главная рука» и «вторая рука» для определения функциональности контроллера; например, в игре контроллер основной руки может быть оружием игрока, а контроллер второй руки может использоваться для управления положением щита.

Режим луча наведения

Режим луча наведения — это строка, найденная в свойстве targetRayMode. Она описывает метод определения луча наведения и как он должен быть отображен пользователю, если он отображается визуально.

Когда режим луча наведения равен gaze, точка начала луча находится в позиции зрителя и направлена в направлении взгляда пользователя. Этот метод ввода по взгляду довольно прост и не требует специальных элементов управления, так как он основан на направлении взгляда, сообщенном гарнитурой или любым устройством, определяющим направление взгляда зрителя. Луч наведения должен выходить из точки между глазами в направлении, перпендикулярном лицу зрителя.

Более гибким является режим tracked-pointer, в котором точка начала луча находится в позиции контроллера или системы отслеживания рук и направлена в направлении, в котором направлен контроллер. Луч выдвигается в направлении, определяемом используемой платформой и контроллером, если это определено; в противном случае луч выдвигается в том же направлении, в котором пользователь направляет указательный палец, если он сейчас вытянут.

Третий и последний режим луча наведения чаще всего встречается на мобильных устройствах, таких как смартфоны и планшеты. Режим screen указывает, что луч наведения определяется на основе взаимодействия пользователя с контекстом WebXR путем взаимодействия с экраном каким-либо образом — скорее всего, путем нажатия зрителя на экран или перемещения луча наведения пальцами.

Пространство луча наведения

Пространство XRSpace, используемое для описания позиции и ориентации луча наведения, находится в свойстве targetRaySpace. Нативное начало этого пространства расположено в точке, из которой испускается луч наведения (например, в передней части контроллера или конце ствола, если контроллер отображается как пистолет), а вектор ориентации пространства выдвигается вдоль траектории луча наведения.

Вы можете легко получить луч наведения, соответствующий targetRaySpace , внутри обработчика отрисовки для данного кадра, используя метод XRFrame's getPose(). Возвращаемый XRPose's transform — это преобразование, соответствующее лучу наведения. Таким образом, для контроллера ввода primaryInput:

let targetRayPose = frame.getPose(primaryInput.targetRaySpace, viewerRefSpace);
let targetRayOrigin = targetRayPose.transform.position;
let targetRayVector = targetRayPose.transform.orientation;

Теперь у вас есть точка, из которой исходит луч наведения (targetRayOrigin) и направление, в котором он направлен (targetRayVector), заданные в пространстве отсчета зрителя (viewerRefSpace). Этого достаточно, чтобы нарисовать луч наведения, определить, на что он направлен, выполнить тестирование попадания и т. д.

Пространство захвата

Свойство gripSpace источника ввода — это XRSpace, которое вы можете использовать для отображения объектов так, чтобы они казались удерживаемыми в руке зрителя.

Рисунок: Система координат для пространства захвата левой руки. A diagram showing how the grip space indicates the local coordinate system for the player's hand relative to the world. Рисунок: Система координат для пространства захвата правой руки. A diagram showing how the grip space indicates the local coordinate system for the player's hand relative to the world.

Нативное начало пространства захвата, расположенное примерно в центре кулака игрока, равно (0, 0, 0) в локальной системе координат источника ввода, в то время как XRSpace, указанное в gripSpace, может использоваться в любое время для преобразования координат или векторов из пространства источника в мировые координаты (или наоборот).

Это означает, что если вы используете 3D-модель для представления контроллера, рук аватара игрока или чего-либо, что представляет положение контроллера в пространстве, то gripSpace может использоваться в качестве матрицы преобразования, которая правильно позиционирует и ориентирует модель объекта для отрисовки. Для этого необходимо использовать преобразование для преобразования пространства захвата в систему мировых координат, используемую WebGL для отрисовки.

Рисунок: Сопоставление пространства захвата с мировой системой координат. Расстояния x, y и z вместе составляют мировые координаты (x, y, z), соответствующие началу пространства захвата G. A diagram showing the relationship between the grip space and the world space

На диаграмме выше мы видим пространство захвата, начало которого расположено в точке G, в середине захвата контроллера пользователем, направленного прямо от пользователя, параллельно оси z. По отношению к началу мирового пространства W, начало пространства захвата находится в x единицах вправо, y единицах вверх и z единицах дальше. Учитывая направленность осей, координаты пространства захвата можно выразить в мировых координатах как (x, y, -z); z отрицательный, так как пространство захвата находится дальше по оси z, то есть в отрицательном направлении.

Если контроллер вместо этого расположен слева и ближе к пользователю, чем начало мирового пространства (или, возможно, позади пользователя, если пользователь находится в начале координат, хотя это неудобный способ держать контроллер), координаты будут иметь отрицательное значение для x, но положительное значение для z. Значение y по-прежнему будет положительным, если контроллер не был перемещен ниже начала мирового пространства.

Это показано на диаграмме ниже, на которой контроллер расположен ниже и слева от начала мирового пространства и ближе к нам, чем начало мирового пространства. В результате значения x и y оба отрицательные, а z положительное.

Рисунок: Сопоставление пространства захвата с мировым началом, когда контроллер расположен ниже и слева от мирового начала и ближе к нам, чем мировое начало. The relationship between another grip space and the world space

Запись Gamepad

Каждый источник ввода имеет свойство gamepad, которое, если не NULL, является объектом Gamepad, описывающим различные элементы управления и виджеты, доступные на контроллере. Если устройство ввода имеет только основные сенсоры движения, элемент управления сжатием и кнопку, у него может отсутствовать запись Gamepad. Однако, если запись gamepad присутствует, вы можете использовать ее для идентификации и опроса кнопок и осей, доступных на контроллере.

Хотя запись Gamepad определяется спецификацией Gamepad API, она фактически не управляется Gamepad API и не работает точно так же. Дополнительную информацию см. в разделе Расширенные контроллеры с использованием объекта gamepad.

Строки профиля

Каждый источник ввода может иметь ноль или более строк имени профиля ввода, находящихся в массиве profiles, каждая из которых описывает предпочтительное визуальное представление источника ввода в трехмерном мире, а также то, как источник ввода функционирует. Использование этих профилей кратко описано в разделе Профили ввода ниже.

Временные источники ввода

Некоторые устройства могут создавать временные источники ввода, которые используются в паре с действием, которое фактически не исходит от этого устройства, но представляется так, как будто оно исходит. Например, если устройство XR предоставляет режим, в котором мышь используется для моделирования событий на устройстве, новый объект XRInputSource может быть создан для представления моделируемого источника ввода в течение обработки действия.

Это необходимо из-за разделения между стандартными устройствами ввода и источниками ввода XR. Искусственный источник используется для представления внешнего источника в течение каждого временного действия.

Управление источниками ввода

Когда доступно несколько источников ввода, вам необходимо получить информацию о каждом из них, включая его положение и ориентацию, луч наведения (если это необходимо для ваших задач) и детали, которые помогут вам решить, как визуализировать источник ввода, если это необходимо. Вам также нужно будет определить, какой источник ввода использовать для каких задач; например, если у пользователя есть два контроллера, какой из них будет отслеживаться для управления элементами интерфейса пользователя, или оба?

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

Перечисление источников ввода

Сессия WebXR, представленная объектом XRSession, имеет свойство inputSources, которое является динамическим списком WebXR устройств ввода, подключенных к XR системе.

let inputSourceList = xrSession.inputSources;

Из-за того, что содержимое объектов XRInputSource, представляющих каждый источник ввода в списке, является только для чтения, изменения ввода производятся системой WebXR путём удаления записи источника и добавления новой записи для её замены. Событие inputsourceschange отправляется в ваш XRSession всякий раз, когда один или несколько источников ввода изменяются или когда источник ввода добавляется в список или удаляется из него.

Например, если вам нужно отслеживать, какой контроллер удерживается в каждой руке игрока, вы можете сделать следующее:

let inputSourceList = NULL;
let leftHandSource = NULL;
let rightHandSource = NULL;

xrSession.addEventListener("inputsourceschange", (event) => {
  inputSourceList = event.session.inputSources;

  inputSourceList.forEach((source) => {
    switch (source) {
      case "left":
        leftHandSource = source;
        break;
      case "right":
        rightHandSource = source;
        break;
    }
  });
});

Событие inputsourceschange также срабатывает один раз, когда обратный вызов создания сессии завершает выполнение, поэтому вы можете использовать его для получения списка источников ввода, как только он станет доступен при запуске. Событие передаётся как XRInputSourcesChangeEvent, который включает три интересных свойства:

session

XRSession для которого изменились источники ввода.

added

Массив из нуля или более объектов XRInputSource, указывающих на источники ввода, которые были недавно добавлены в XR систему.

removed

Массив из нуля или более объектов XRInputSource, указывающих на источники ввода, которые были удалены из XR системы.

Определение профиля ввода

Каждый источник ввода имеет свойство profiles, которое содержит динамический список профилей ввода WebXR, применяемых к источнику ввода, в порядке убывания специфичности от самого специфичного к наименее специфичному.

Для выполнения каких-либо действий, связанных со сканированием профилей, выходящих за рамки базовой идентификации функций, вам может потребоваться импортировать базу данных профилей JSON из реестра профилей ввода WebXR.

См. Профили ввода для более подробной информации о работе с профилями ввода.

Выбор первичного контроллера

Для предотвращения проблем, возникающих из-за нескольких контроллеров, которые могут непроизвольно взаимодействовать с пользовательским интерфейсом одновременно, вашему приложению может потребоваться «первичный» контроллер. Этот контроллер будет нести ответственность не только за навигацию по пользовательскому интерфейсу вашего приложения, но и считаться «главной рукой», в то время как другие контроллеры будут «второстепенными» или дополнительными.

Примечание: Это не означает, что вашему приложению необходимо определять первичный контроллер. Но если это необходимо, эти стратегии могут помочь.

Существует несколько способов определения первичного контроллера. Рассмотрим три из них.

Преимущественная рука

Самый прямой способ определить первичный контроллер — это иметь настраиваемое пользователем предпочтение «Преимущественная рука», с помощью которого пользователь указывает, какая рука является доминирующей. Затем вы будете просматривать каждый источник ввода и искать совпадение с этим предпочтением, если оно доступно, переходя к другому контроллеру, если контроллер с указанной рукой отсутствует.

const primaryInputSource =
  xrSession.inputSources.find((src) => src.handedness === user.handedness) ??
  xrSession.inputSources[0];

Этот фрагмент кода начинает с предположения, что первый источник ввода является первичным, но затем ищет источник ввода, чья handedness совпадает со значением, указанным в объекте user. Если совпадение найдено, этот источник ввода выбирается как первичный.

Первый использованный

Другой вариант — использовать первый источник ввода, на котором пользователь активирует действие выбора. Приведенный ниже код начинает с предположения, что первый источник ввода является первичным, затем устанавливает обработчик для события select, который записывает источник события как первичный источник ввода. Затем обработчик события select заменяется функцией realSelectHandler(), которая будет использоваться для обработки всех последующих событий select. Затем мы передаём событие в realSelectHandler(), чтобы позволить событию обрабатываться в обычном режиме.

let primaryInputSource = xrSession.inputSources[0];

xrSession.onselect = (event) => {
  primaryInputSource = event.inputSource;
  xrSession.onselect = realSelectHandler;
  return realSelectHandler(event);
};

Результат состоит в том, что мы устанавливаем первичный источник ввода в первый раз, когда принимается событие select , независимо от того, откуда оно исходит, обрабатываем событие как обычно, а затем обрабатываем события обычным образом, не беспокоясь о том, какой источник ввода является первичным.

Выбор пользователя

Наиболее сложный способ определения первичного источника ввода является очень гибким, но может потребовать значительных усилий для реализации. В этом случае вы перебираете список источников ввода и их профилей, чтобы собрать информацию о каждом источнике ввода, затем предоставляете пользовательский интерфейс, описывающий каждый источник, позволяя пользователю назначать использование каждому из них. Правильная реализация такого подхода может быть сложной задачей, но может быть полезна для сложных приложений, которые могут включать несколько пользовательских вводов.

Большая часть информации, необходимой для реализации этого подхода, может быть найдена в разделе Профили ввода, ниже. Однако подробности выходят за рамки этой статьи.

Профили ввода

Как упоминалось выше, каждый источник ввода имеет список имён профилей ввода, соответствующих набору информации об этом источнике ввода и о том, как его можно использовать. Эти имена находятся в свойстве profiles источника ввода, а официальный реестр этих строк профилей поддерживается в реестре профилей ввода WebXR на GitHub.

Например, профиль generic-trigger-squeeze-touchpad может использоваться для поиска следующих данных профиля JSON, поиска поля profileId, имеющего значение generic-trigger-squeeze-touchpad.

{
  "profileId": "generic-trigger-squeeze-touchpad",
  "fallbackProfileIds": [],
  "layouts": {
    "left-right-none": {
      "selectComponentId": "xr-standard-trigger",
      "components": {
        "xr-standard-trigger": { "type": "trigger" },
        "xr-standard-squeeze": { "type": "squeeze" },
        "xr-standard-touchpad": { "type": "touchpad" }
      },
      "gamepad": {
        "mapping": "xr-standard",
        "buttons": [
          "xr-standard-trigger",
          "xr-standard-squeeze",
          "xr-standard-touchpad"
        ],
        "axes": [
          { "componentId": "xr-standard-touchpad", "axis": "x-axis" },
          { "componentId": "xr-standard-touchpad", "axis": "y-axis" }
        ]
      }
    }
  }
}

Это контроллер, у которого независимо от того, в какой руке он находится (и даже если он не связан с конкретной рукой) есть три компонента: стандартная кнопка, стандартный контроль сжатия и сенсорная панель. Согласно свойству selectComponentId, компонент xr-standard-trigger используется для выполнения основного действия.

Кроме того, объект gamepad сопоставляет эти вводы с контроллером геймпада, назначая нажатие кнопки, сжатие и нажатие сенсорной панели на список кнопок источника ввода, а «оси» сенсорной панели — на список осей.

Список в profiles упорядочен в порядке обратной специфичности; то есть, самое точное описание идёт первым, а наименее точное — последним. Первая запись в списке обычно указывает на точную модель контроллера или модель, с которой контроллер совместим.

Например, запись 0 в profiles для контроллера Oculus Touch — oculus-touch. Следующая запись — generic-trigger-squeeze-thumbstick, указывающая на универсальное устройство с кнопкой, контролем сжатия и аналоговым джойстиком. Хотя контроллер Oculus Touch фактически имеет сенсорную панель, а не аналоговый джойстик, общее описание «достаточно близкое», что детали в профиле, соответствующие имени, позволят корректно интерпретировать контроллер.

Действия

В WebXR, действие — это специальный тип события, которое срабатывает при активации пользователем специальной кнопки на контроллере. Любые дополнительные кнопки (а также такие элементы, как оси управления — джойстики, например — и т. п.) управляются исключительно через свойство XRInputSource gamepad. Смотрите Расширенные контроллеры, использующие объект геймпада ниже для получения дополнительной информации об использовании этих дополнительных элементов управления и кнопок.

Основное действие — это действие, которое срабатывает, когда пользователь активирует основной элемент управления, который выполняет особую функцию. В настоящее время существует два типа основных действий:

  • Основное действие — это действие, которое активируется, когда пользователь активирует основной или «выбор» ввод на своём контроллере. Этот ввод может быть кнопкой, триггером, нажатием или щелчком тачпада, голосовым управлением, специальным жестом руки или, возможно, другим типом ввода. Например, на контроллере с тачпадом, который можно нажимать, триггером управления, а также кнопками «Назад» и «Меню», нажатие тачпада, скорее всего, будет основным действием. Некоторые контроллеры могут иметь кнопку «Выбор». На контроллере типа геймпада кнопка «А» скорее всего будет основным действием.
  • Основное действие сжатия — это действие, которое активируется, когда пользователь сжимает контроллер. Это «сжатие» может обнаруживаться с помощью датчика давления в контроллере или моделироваться с помощью триггера, жеста руки или другого механизма. Например, если контроллер ввода — это перчатка с обратной связью, он может сообщить, что основное действие сжатия произошло, когда пользователь сжал кулак.

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

Эти типы действий ввода описаны более подробно ниже.

Основное действие

Каждый источник ввода должен определять основное действие. Основное действие (иногда сокращенно «действие выбора») — это платформа-зависимое действие, которое реагирует на манипуляции пользователя, посылая, в порядке следования, события selectstart, select и selectend. Каждое из этих событий имеет тип XRInputSourceEvent.

Примечание: Если у источника ввода нет основного действия, этот источник считается вспомогательным источником ввода.

Когда пользователь направляет устройство вдоль луча цели в вашем 3D-пространстве и затем выполняет действие выбора, следующие события отправляются активной XRSession:

  1. Событие selectstart, указывающее, что пользователь выполнил действие, начинающее основное действие. Это может быть жест, нажатие кнопки или подобное.
  2. Если основное действие завершается успешно (например, из-за отпускания кнопки или триггера), а не из-за ошибки, отправляется событие select.
  3. После отправки события select или если контроллер, на котором выполняется действие, отключен или иным образом становится недоступным, отправляется событие selectend.

В общем случае события selectstart и selectend сообщают вам, когда можно отобразить пользователю информацию о том, что выполняется основное действие. Это может быть отрисовка контроллера с активированной кнопкой в новом цвете или отображение целевого объекта, который захватывается и перемещается, начиная с момента появления selectstart и заканчивая получением selectend.

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

Если ваше основное действие — это просто действие триггера, и вам не нужно анимировать что-либо во время задействования триггера, вы можете проигнорировать события selectstart и selectend и действовать по событию select.

xrSession.addEventListener("select", (event) => {
  let inputSource = event.inputSource;
  let frame = event.frame;

  /* handle the event */
});

Некоторые действия могут отправлять эти события очень быстро, одно за другим. Время, прошедшее между этими событиями, зависит как от аппаратного обеспечения, вызывающего действие, так и от программных драйверов, которые интерпретируют действие аппаратного обеспечения и преобразуют его в серию событий. Не предполагайте, что эти события будут происходить с каким-то определённым временным промежутком.

Например, если аппаратное обеспечение, вызывающее основное действие, — это кнопка, вы получите selectstart при нажатии пользователем кнопки, а затем select и selectend при её отпускании.

В документации, например, в разделе о Нацеливании и луче нацеливания, есть множество примеров обработки событий select.

Основное действие сжатия

Основное действие сжатия — это платформа-зависимое действие, которое отправляет события XRSession squeezestart, squeezeend и squeeze. Это обычно генерируется пользователем при сжатии контроллера, имитировании жеста сжатия, или нажатии триггера.

Последовательность событий идентична событиям основного действия, за исключением имени каждого события:

  1. В сессию отправляется событие squeezestart, указывающее, что пользователь начал действие сжатия.
  2. Если основное действие сжатия завершается успешно, в сессию отправляется событие squeeze.
  3. Затем отправляется событие squeezeend, чтобы указать, что действие сжатия больше не выполняется. Это отправляется независимо от того, было ли действие сжатия успешным или нет.

Два распространенных варианта применения основного действия сжатия — захват и/или подъем объектов в 3D-мире и нажатие триггера для стрельбы оружием в игре или симуляции.

Пример

Этот фрагмент кода демонстрирует набор обработчиков событий сжатия, которые реализуют эти события для управления сбором и удерживанием объектов из сцены. Код предполагает существование объекта avatar , представляющего персонажа, как используется в других примерах на этой странице, а также функций pickUpObject() и dropObject(), которые отвечают за перемещение объекта из мира в конкретную руку и за возврат объекта из руки в мир.

Взятие объекта: обработка событий squeezestart
xrSession.addEventListener("squeezestart", (event) => {
  const targetRaySpace = event.inputSource.targetRaySpace;
  const hand = event.inputSource.handedness;

  let targetRayPose = event.frame.getPose(targetRaySpace, viewerRefSpace);
  if (!targetRayPose) {
    return;
  }

  let targetRayTransform = targetRayPose.transform;
  let targetObject = findTargetObject(targetRayTransform);

  if (targetObject) {
    if (avatar.heldObject[hand]) {
      dropObject(hand);
    }
    pickUpObject(targetObject, hand);
  }
});

Событие squeezestart обрабатывается путем получения этих данных позы и преобразования, как обычно, и получения handedness источника ввода в локальную константу hand. Мы будем использовать это для сопоставления руки с объектом, удерживаемым в этой руке.

Затем код определяет целевой объект, и если объект найден вдоль луча нацеливания, берет его. Взятие объекта включает в себя сначала проверку, не удерживает ли рука, представленная avatar.heldObject[hand], какой-либо объект. Если объект уже удерживается в этой руке, он отпускается с помощью функции dropObject().

Затем вызывается функция pickUpObject(), указывая целевой объект как объект для удаления из сцены и размещения в указанной hand. pickUpObject() также записывает исходное положение объекта, чтобы он мог быть возвращен в это место, если сжатие отменено или прервано.

Отпускание объекта: обработчик событий сжатия

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

Этот код предполагает существование дополнительных функций findTargetPosition(), которая отслеживает луч цели до столкновения с чем-либо, затем возвращает координаты столкновения, и putObject(), которая размещает удерживаемый объект в указанной hand в заданной позиции, удаляя его из руки.

xrSession.addEventListener("squeeze", (event) => {
  const targetRaySpace = event.inputSource.targetRaySpace;
  const hand = event.inputSource.handedness;

  let targetRayPose = event.frame.getPose(targetRaySpace, viewerRefSpace);
  if (!targetRayPose) {
    return;
  }

  let targetRayTransform = targetRayPose.transform;
  let targetPosition = findTargetPosition(targetRayTransform);

  if (targetPosition) {
    if (avatar.heldObject[hand]) {
      putObject(hand, targetPosition);
      avatar.heldObject[hand] = null;
    }
  }
});

Как и в обработчике squeezestart, это начинается с сбора информации, необходимой о событии, включая руку, отпускающую объект, и преобразование луча нацеливания. Преобразование луча нацеливания передается в предполагаемую функцию findTargetPosition() для получения координат, в которых следует разместить отпускаемый объект.

Получив позицию, мы можем отпустить объект, вызвав функцию putObject(), которая принимает в качестве входных данных hand и целевую позицию. Задача этой функции — удалить объект из указанной руки и добавить его обратно в сцену, установив его позицию в соответствии с координатами, возвращаемыми findTargetPosition().

Отмена сжатия в обработчике squeezeend

Событие squeezeend принимается после завершения сжатия, даже если оно не удается. Мы обрабатываем его, возвращая текущий удерживаемый объект в место, где он был, когда его взяли.

xrSession.addEventListener("squeezeend", (event) => {
  const targetRaySpace = event.inputSource.targetRaySpace;
  const hand = event.inputSource.handedness;

  if (avatar.heldObject[hand]) {
    returnObject(hand);
    avatar.heldObject[hand] = null;
  }
});

Здесь, функция returnObject() предполагается, что она знает, как вернуть объект, удерживаемый в указанной hand, в его начальное положение, как он был записан pickUpObject() в обработчике события squeezestart.

Здесь функция returnObject() предполагается, что она знает, как вернуть объект, удерживаемый в указанной hand, в его начальное положение, как он был записан функцией pickUpObject() в обработчике события squeezestart.

Временные действия

Если устройство XR использует мышь для имитации контроллера в режиме inline , происходит приблизительно следующая последовательность действий:

  1. Пользователь нажимает кнопку мыши внутри <canvas>, отображающего веб-XR-сцену.
  2. Событие мыши捕获由 XR 设备的驱动程序。
  3. Устройство создаёт новый XRInputSource для представления имитируемого источника XR ввода. targetRayMode устанавливается в screen, а другая информация заполняется соответствующим образом. Этот новый источник ввода временно добавляется в список, возвращаемый свойством XRSession inputSources.
  4. Браузер отправляет события pointerdown, соответствующие действию.
  5. Генерируется и отправляется основное действие в виде события selectstart с источником, установленным на новый XRInputSource. Или, если мышь используется как дополнительный или вторичный контроллер, вместо этого отправляется вспомогательное действие.
  6. Когда пользователь отпускает кнопку мыши, в XRSession отправляется событие select, а затем DOM получает событие click. Затем сессия получает событие selectend, указывающее на завершение действия.
  7. Когда действие завершено, браузер удаляет временный источник ввода, и отправляются соответствующие события pointerup.

Таким образом, временный источник ввода действительно временный — он существует только во время обработки ввода и не будет отображаться в списке источников ввода.

Направление и нацеливание

Направление взгляда — это направление, в котором смотрит пользователь. Оно не задаётся с помощью источника ввода. Вместо этого оно определяется с помощью XRPose, полученного из метода XRFrame.getViewerPose() текущей кадровой информации анимации. Компонента вращения матрицы преобразования позы зрителя определяет направление взгляда пользователя.

Дополнительную информацию о том, как использовать позу зрителя для определения направления взгляда, можно найти в статье Точки обзора и зрители.

Наведение — это действие, когда пользователь направляет указатель на определённое направление с помощью источника ввода. targetRaySpace источника ввода — это XRSpace (и, скорее всего, XRReferenceSpace), которое можно использовать для определения ориентации луча наведения относительно направления взгляда пользователя.

Это действие может или не может включать фактическое наведение на конкретный объект в 3D-мире; вам нужно определить это самостоятельно, используя проверку пересечения — то есть, проверяя, пересекается ли луч наведения с какими-либо объектами в вашей сцене.

Наведение и луч наведения

Луч наведения — это луч, начало которого расположено в начале координат пространства луча наведения, и который направлен в направлении, в котором пользователь наводит устройство управления. Луч наведения определяется с помощью XRSpace, начало которого находится в источнике луча наведения (обычно в выступающей части контроллера или его представлении в 3D-мире), а ориентация имеет ось -Z, направленную наружу от контроллера в том же направлении, что и ось XRInputSource в gripSpace.

Это пространство находится в свойстве targetRaySpace источника ввода. Его можно использовать для определения направления, в котором наведён контроллер, и для определения начала и ориентации луча наведения. Это можно сделать, например, реализовав обработчик события select, который нуждается в этой информации. Как обычно, этот код предполагает использование glMatrix для выполнения математических операций над матрицами и векторами:

xrSession.addEventListener("select", (event) => {
  const targetRaySpace = event.inputSource.targetRaySpace;

  let targetRayPose = event.frame.getPose(targetRaySpace, viewerRefSpace);
  if (!targetRayPose) {
    return;
  }

  let targetRayTransform = targetRayPose.transform;
  let targetObject = findTargetObject(targetRayTransform);

  if (targetObject) {
    /* do stuff with the targeted object */
  }
});

Это позволяет получить начало луча наведения в векторе targetSourcePoint и ориентацию луча в кватернионе targetDirection. С помощью

Это начинается с извлечения пространства луча наведения в локальную константу targetRaySpace. Это используется при вызове метода XRFrame getPose() для создания объекта XRPose, представляющего положение и ориентацию луча наведения в пространстве отсчёта зрителя, viewerRefSpace. Если это null, обработчик события возвращается, ничего не делая дальше.

Преобразование луча наведения извлекается из свойства transform позы и сохраняется в локальной targetRayTransform. Это, в свою очередь, используется (в данном случае через функцию, называемую findTargetObject()) для поиска первого объекта, с которым луч пересекается. Если луч наведения пересекает объект в сцене, мы можем выполнить с ним необходимые действия.

Если вам нужно извлечь фактическое положение начала луча наведения и направление луча, вы можете сделать это следующим образом:

const targetRayOrigin = vec3.create();
const targetRayDirection = quat.create();
mat4.getTranslation(targetRayOrigin, viewerRefSpace);
mat4.getRotation(targetRayDirection, viewerRefSpace);

Для определения объекта, на который направлен указатель, следует отследить луч наведения до пересечения с объектом. Этот процесс называется проверкой пересечений или обнаружением коллизий. Подход к проверке пересечений сильно зависит от конкретных потребностей вашего приложения. Первый вопрос: вы обнаруживаете коллизии с виртуальными объектами или поверхностями, реальными объектами или поверхностями, или с обоими?

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

Подробнее о процессе наведения и обнаружения пересечений см. в разделе Наведение и обнаружение пересечений.

Отображение удерживаемых в руке объектов

Свойство gripSpace источника ввода идентифицирует XRSpace, описывающий начало и ориентацию для использования при рендеринге объекта, чтобы он выглядел так, как будто он удерживается в той же руке, что и его источник ввода. Это пространство предназначено для рисования модели удерживаемого веб-XR-контроллера ввода, представленного объектом XRInputSource, но также может использоваться для рисования любых объектов, таких как шар, инструмент или оружие. Мы рассмотрели пространство захвата grip space выше, но давайте посмотрим, как его можно использовать для рисования объектов, представляющих руку или находящихся в руке.

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

Расширенные контроллеры с использованием объекта gamepad

Источник ввода XRInputSource имеет свойство gamepad, значение которого, если оно не null, является объектом Gamepad, предоставляющим доступ к кнопкам в стиле джойстика, осям управления (например, джойстикам или аналоговым стикам) и т.д. Это может включать те же кнопки, которые вызывают стандартные действия XRInputSource, но может также включать любое количество дополнительных кнопок и элементов управления.

Примечание: Хотя Gamepad определяется API джойстиков, он не управляется им, поэтому вы не должны пытаться использовать методы API джойстиков с ним. Тип объекта повторно используется для удобства.

Если значение gamepad равно null, источник ввода не определяет элементы управления с помощью записи Gamepad, либо потому, что он их не поддерживает, либо потому, что дополнительных элементов управления в нём нет.

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

Поскольку это использование интерфейса Gamepad является удобством, а не реальным применением API джойстиков, существуют некоторые отличия в способе его использования с WebXR и в приложениях API джойстиков. Наиболее заметное (но не единственное) различие заключается в том, что WebXR добавляет отображение джойстика xr-standard, см. свойство XRInputSource.gamepad для получения дополнительных различий. Это отображение джойстика определяет, как элементы управления на типичном VR-контроллере, удерживаемом одной рукой, отображаются на элементах управления джойстика.

Включение ввода из источников, не являющихся WebXR

Иногда вам нужно предоставить пользователю способ ввода с помощью контроллеров, которые внешние по отношению к WebXR. Чаще всего это ввод с клавиатуры и мыши, но вы также можете использовать устройства джойстиков, не являющиеся XR, сетевые вводы или другие источники данных для имитации элементов управления пользователем. WebXR не поддерживает непосредственное подключение этих устройств ввода к XR-сцене, но вы можете самостоятельно собирать входные данные и применять их.

Предполагая, что ввод используется для управления аватаром в симуляции, что является наиболее распространённым случаем, ввод WebXR используется для влияния на аватара следующим образом, используя данные, собранные с устройства ввода, не являющегося XR:

Положение

Положение аватара изменяется путём применения дельты к ранее известному положению, затем пространство отсчёта аватара заменяется новым, преобразование которого отражает новое положение.

Ориентация

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

Действие

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

Некоторые вводы вместо этого используются для управления приложением, а не аватаром. Например, кнопка может открыть меню параметров для настройки приложения. Пока открыто это меню, ввод, который в противном случае управлял аватаром, может вместо этого использоваться для управления интерфейсом меню.

Использование событий клавиатуры и мыши

Получение ввода с клавиатуры и мыши выполняется так же, как и в любом веб-приложении. Настройте обработчики для событий, которые вам необходимо обработать, чтобы получить нужный ввод. Интересно, что вы делаете с этим вводом.

Представьте объект avatar, который мы будем использовать для отслеживания информации об аватаре и его мировоззрении. Мы хотим, чтобы игрок мог использовать клавиши W, A, S и D для передвижения вперед, влево, назад и вправо. Поскольку мы управляем позицией аватара, определяемой клавиатурой и мышью, помимо того, что может делать оборудование XR, нам нужно хранить эту информацию отдельно и применять ее как преобразование перед отрисовкой аватара (или мира с точки зрения аватара).

Для этого мы включаем в объект avatar свойство posDelta, типа DOMPoint, которое содержит смещения для применения ко всем трём осям, чтобы скорректировать положение аватара (начало пространства отсчета положения зрителя) с учётом перемещения и вращения с клавиатуры и мыши.

Соответствующий код для ввода с клавиатуры может выглядеть примерно так:

document.addEventListener("keydown", (event) => {
  switch (event.key) {
    case "a":
    case "A":
      avatar.posDelta.x -= ACCEL_X;
      break;
    case "d":
    case "D":
      avatar.posDelta.x += ACCEL_X;
      break;
    case "w":
    case "W":
      avatar.posDelta.y += ACCEL_Y;
      break;
    case "s":
    case "S":
      avatar.posDelta.y -= ACCEL_Y;
      break;
    default:
      break;
  }
});

Это простой пример, где ускорение постоянное и не очень реалистичное. Вы можете значительно улучшить его, применив знания физики, чтобы ускорение менялось со временем в зависимости от того, как долго нажата клавиша, и других факторов.

Применение ввода к сцене

Теперь, когда у нас есть приращения, которые нужно применить к положению и ориентации — в нашем примере, в свойствах posDelta и orientDelta объекта avatar, — мы можем написать код для применения этих изменений. Поскольку мы уже отрисовываем сцену по расписанию, мы можем просто добавить код для применения этих изменений туда же, вместе с подготовкой и отрисовкой сцены.

function drawFrame(time, frame) {
  applyExternalInputs(avatar);
  let pose = frame.getViewerPose(avatar.referenceSpace);

  animationFrameRequest = session.requestAnimationFrame(drawFrame);

  /* draw the frame here */
}

Функция drawFrame(), показанная здесь, является обратным вызовом, вызываемым, когда пришло время отрисовать кадр, как определено вызовом метода XRSession requestAnimationFrame(). Она вызывает функцию applyExternalInputs(), которую мы определим чуть позже; она принимает объект avatar и использует его информацию для обновления кадра отсчёта аватара.

После этого всё происходит как обычно: получение положения зрителя из обновлённого кадра отсчёта, запрос следующего обратного вызова кадра через requestAnimationFrame(), а затем продолжается настройка WebGL и отрисовка сцены. Код отрисовки и другие связанные с ним коды можно найти в примере Перемещение, ориентация и движение.

Метод applyExternalInputs() принимает объект avatar и заменяет его свойство referenceSpace новым пространством отсчёта, которое включает обновлённые приращения.

function applyExternalInputs(avatar) {
  if (!avatar.posDelta.x && !avatar.posDelta.y && !avatar.posDelta.z) {
    return; // Player hasn't moved with keyboard
  }

  let newTransform = new XRRigidTransform({
    x: avatar.posDelta.x,
    y: avatar.posDelta.y,
    z: avatar.posDelta.z,
  });
  avatar.referenceSpace =
    avatar.referenceSpace.getOffsetReferenceSpace(newTransform);
}

См. также

  • Цель и обнаружение попадания
  • Геометрия и пространства отсчёта в WebXR
  • Пространственное отслеживание в WebXR
  • Отрисовка и обратный вызов анимации кадра WebXR
  • Точки зрения и зрители: Моделирование камер в WebXR
  • Перемещение, ориентация и движение: пример WebXR

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API/Inputs

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API