Spec-Zone.ru › Web APIs

Использование контроллеров VR с WebVR

Устарело: Эта функция больше не рекомендуется. Хотя некоторые браузеры могут ее все еще поддерживать, она может быть уже удалена из соответствующих веб-стандартов, может быть в процессе удаления или может поддерживаться только для совместимости. Избегайте ее использования и, при возможности, обновите существующий код; см. таблицу совместимости в нижней части этой страницы для принятия решения. Имейте в виду, что эта функция может перестать работать в любой момент.

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

Примечание: API WebVR заменен на API WebXR. WebVR никогда не был ратифицирован как стандарт, был реализован и включен по умолчанию в очень небольшом количестве браузеров и поддерживал небольшое количество устройств.

API WebVR

API WebVR — это новая, но очень интересная функция веб-платформы, которая позволяет разработчикам создавать веб-приложения виртуальной реальности. Это делается путем предоставления доступа к гарнитурам VR, подключенным к вашему компьютеру, как VRDisplay объекта, которые можно использовать для запуска и остановки презентации на дисплее, запроса данных о движении (например, ориентации и позиции), которые могут быть использованы для обновления отображения в каждом кадре цикла анимации и многого другого.

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

API Gamepad

API Gamepad — это API, достаточно хорошо поддерживаемый, который позволяет разработчикам получать доступ к геймпадам/контроллерам, подключенным к вашему компьютеру, и использовать их для управления веб-приложениями. Основной API Gamepad предоставляет доступ к подключенным контроллерам как к Gamepad объектам, которые затем можно запросить, чтобы узнать, какие кнопки нажаты, и как движутся джойстики (оси) в любой момент и т. д.

Дополнительную информацию об использовании основного API Gamepad можно найти в Использование API Gamepad и Реализация управления с помощью API Gamepad.

Однако в этой статье мы в основном будем сосредоточены на некоторых новых функциях, предоставляемых API расширений Gamepad, который позволяет получить доступ к расширенной информации о контроллере, такой как данные о позиции и ориентации, управление тактильными актуаторами (например, вибрация оборудования) и многое другое. Этот API очень новый и в настоящее время поддерживается и включен по умолчанию только в Firefox 55+ каналах Beta/Nightly.

Типы контроллеров

Существует два типа контроллеров, которые вы встретите при работе с оборудованием VR:

  • Контроллеры 6DoF (шесть степеней свободы) предоставляют доступ к данным о положении и ориентации — они могут управлять VR-сценой и объектами в ней с помощью движения, а также поворота. Хорошим примером являются контроллеры HTC VIVE.
  • Контроллеры 3DoF (три степени свободы) обеспечивают ориентацию, но не данные о положении. Примером является контроллер Google Daydream, который можно поворачивать для указания на разные объекты в 3D-пространстве, как лазерную указку, но нельзя перемещать внутри 3D-сцены.

Базовый доступ к контроллерам

Теперь перейдем к коду. Сначала рассмотрим основы того, как получить доступ к контроллерам VR с помощью API Gamepad. Здесь есть несколько нюансов, которые стоит учитывать.

Мы подготовили пример, чтобы продемонстрировать его — см. наш исходный код vr-controller-basic-info (посмотреть его в работе здесь). Эта демонстрация выводит информацию о VR-дисплеях и геймпадах, подключенных к вашему компьютеру.

Получение информации о дисплее

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

let initialRun = true;

if (navigator.getVRDisplays && navigator.getGamepads) {
  info.textContent = "WebVR API and Gamepad API supported.";
  reportDisplays();
} else {
  info.textContent =
    "WebVR API and/or Gamepad API not supported by this browser.";
}

Здесь мы сначала используем переменную отслеживания, initialRun, чтобы отметить, что это первый раз, когда мы загрузили страницу. Вы узнаете больше об этом позже. Далее мы определяем, поддерживаются ли API WebVR и Gamepad, проверяя наличие методов Navigator.getVRDisplays() и Navigator.getGamepads(). Если да, то мы запускаем нашу reportDisplays() пользовательскую функцию, чтобы начать процесс. Эта функция выглядит так:

function reportDisplays() {
  navigator.getVRDisplays().then((displays) => {
    console.log(`${displays.length} displays`);
    displays.forEach((display, i) => {
      const cap = display.capabilities;
      // cap is a VRDisplayCapabilities object
      const listItem = document.createElement("li");
      listItem.innerText = `
VR Display ID: ${display.displayId}
VR Display Name: ${display.displayName}
Display can present content: ${cap.canPresent}
Display is separate from the computer's main display: ${cap.hasExternalDisplay}
Display can return position info: ${cap.hasPosition}
Display can return orientation info: ${cap.hasOrientation}
Display max layers: ${cap.maxLayers}`;
      listItem.insertBefore(
        document.createElement("strong"),
        listItem.firstChild,
      ).textContent = `Display ${i + 1}`;
      list.appendChild(listItem);
    });

    setTimeout(reportGamepads, 1000);
    // For VR, controllers will only be active after their corresponding headset is active
  });
}

Эта функция сначала использует основанный на обещаниях метод Navigator.getVRDisplays(), который возвращает массив, содержащий VRDisplay объекты, представляющие подключенные дисплеи. Затем она выводит значения VRDisplay.displayId и VRDisplay.displayName каждого дисплея и ряд полезных значений, содержащихся в связанном объекте VRDisplayCapabilities дисплея. Наиболее полезными из них являются hasOrientation и hasPosition, которые позволяют обнаружить, может ли устройство вернуть данные об ориентации и позиции, и настроить ваше приложение соответственно.

Последняя строка в этой функции — это вызов setTimeout(), который запускает функцию reportGamepads() через 1 секунду. Зачем нам это нужно? Во-первых, контроллеры VR будут готовы только после активации связанной с ними VR-гарнитуры, поэтому нам нужно вызвать это после того, как getVRDisplays() была вызвана и вернула информацию о дисплее. Во-вторых, API Gamepad намного старше API WebVR и не основан на обещаниях. Как вы увидите позже, метод getGamepads() является синхронным и сразу же возвращает объекты Gamepad — он не ждет, пока контроллер будет готов сообщить информацию. Если вы не подождете немного, возвращаемая информация может быть неточной (по крайней мере, это то, что мы обнаружили в наших тестах).

Получение информации о геймпаде

Функция reportGamepads() выглядит так:

function reportGamepads() {
  const gamepads = navigator.getGamepads();
  console.log(`${gamepads.length} controllers`);
  for (const gp of gamepads) {
    const listItem = document.createElement("li");
    listItem.classList = "gamepad";
    listItem.innerText = `
Associated with VR Display ID: ${gp.displayId}
Gamepad associated with which hand: ${gp.hand}
Available haptic actuators: ${gp.hapticActuators.length}
Gamepad can return position info: ${gp.pose.hasPosition}
Gamepad can return orientation info: ${gp.pose.hasOrientation}`;
    listItem.insertBefore(
      document.createElement("strong"),
      }),
      listItem.firstChild,
    ).textContent = `Gamepad ${gp.index}`;
    list.appendChild(listItem);
  }
  initialRun = false;
}

Она работает аналогично reportDisplays() — мы получаем массив объектов Gamepad с помощью не основанного на обещаниях метода getGamepads(), затем перебираем каждый из них и выводим информацию о каждом:

  • Свойство Gamepad.displayId совпадает с displayId гарнитуры, с которой связан контроллер, и поэтому полезно для связывания информации о контроллере и гарнитуре.
  • Свойство Gamepad.index — это уникальный числовой индекс, который идентифицирует каждый подключенный контроллер.
  • Gamepad.hand возвращает, в какую руку ожидается держать контроллер.
  • Gamepad.hapticActuators возвращает массив доступных тактильных актуаторов контроллера. Здесь мы возвращаем его длину, чтобы увидеть, сколько каждого доступно.
  • Наконец, мы возвращаем GamepadPose.hasPosition и GamepadPose.hasOrientation, чтобы показать, может ли контроллер вернуть данные о позиции и ориентации. Это работает так же, как и для дисплеев, за исключением того, что в случае с геймпадами эти значения доступны в объекте pose, а не в объекте capabilities.

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

Последнее, что нужно сделать, — установить переменную initialRun в false, поскольку начальный запуск завершен.

События Gamepad

Для завершения этого раздела рассмотрим связанные с геймпадом события. Нам нужно будет рассмотреть два — gamepadconnected и gamepaddisconnected — и их назначение довольно очевидно.

В конце нашего примера мы сначала включаем функцию removeGamepads():

function removeGamepads() {
  const gpLi = document.querySelectorAll(".gamepad");
  for (let i = 0; i < gpLi.length; i++) {
    list.removeChild(gpLi[i]);
  }
  reportGamepads();
}

Эта функция извлекает ссылки на все элементы списка с именем класса gamepad, и удаляет их из DOM. Затем она повторно запускает reportGamepads(), чтобы заполнить список обновленным списком подключенных контроллеров.

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

window.addEventListener("gamepadconnected", (e) => {
  info.textContent = `Gamepad ${e.gamepad.index} connected.`;
  if (!initialRun) {
    setTimeout(removeGamepads, 1000);
  }
});

window.addEventListener("gamepaddisconnected", (e) => {
  info.textContent = `Gamepad ${e.gamepad.index} disconnected.`;
  setTimeout(removeGamepads, 1000);
});

Мы включили вызовы setTimeout() — как мы делали с кодом инициализации в начале скрипта — чтобы убедиться, что геймпады готовы сообщить свою информацию, когда reportGamepads() вызывается в каждом случае.

Но есть еще одна важная деталь — вы заметите, что внутри обработчика gamepadconnected вызов таймаута выполняется только если initialRun равен false. Это связано с тем, что если ваши геймпады подключены при первой загрузке документа, событие gamepadconnected срабатывает один раз для каждого геймпада, поэтому removeGamepads()/reportGamepads() будут выполняться несколько раз. Это может привести к неточным результатам, поэтому мы хотим выполнить removeGamepads() внутри обработчика gamepadconnected только после начального запуска, а не во время него. Именно для этого предназначен initialRun.

Представление реальной демонстрации

Теперь давайте рассмотрим API Gamepad, используемый в реальной демонстрации WebVR. Вы можете найти эту демонстрацию по адресу raw-webgl-controller-example (посмотреть онлайн).

Точно так же, как и в нашей демонстрации raw-webgl-example (подробнее см. Использование API WebVR), она отображает вращающийся 3D куб, который вы можете выбрать для отображения в VR-дисплее. Единственное отличие состоит в том, что в режиме VR-отображения эта демонстрация позволяет перемещать куб, перемещая VR-контроллер (в исходной демонстрации куб перемещается при движении VR-гарнитуры).

Мы рассмотрим различия в коде в этой версии ниже — см. webgl-demo.js.

Получение данных джойстика

Внутри функции drawVRScene(), вы найдете этот фрагмент кода:

const gamepads = navigator.getGamepads();
const gp = gamepads[0];

if (gp) {
  const gpPose = gp.pose;
  const curPos = gpPose.position;
  const curOrient = gpPose.orientation;
  if (poseStatsDisplayed) {
    displayPoseStats(gpPose);
  }
}

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

Далее мы получаем объект GamepadPose для контроллера, сохранённого в gpPose (запросив Gamepad.pose), а также сохраняем текущие положение и ориентацию джойстика для этого кадра в переменных, чтобы их было легко получить позже. Мы также отображаем статистику кадра в DOM с помощью функции displayPoseStats(). Всё это выполняется только если gp имеет значение (если джойстик подключён), что предотвращает ошибку демонстрации, если у нас нет подключённого джойстика.

Немного позже в коде вы найдете этот блок:

if (gp && gpPose.hasPosition) {
  mvTranslate([
    0.0 + curPos[0] * 15 - curOrient[1] * 15,
    0.0 + curPos[1] * 15 + curOrient[0] * 15,
    -15.0 + curPos[2] * 25,
  ]);
} else if (gp) {
  mvTranslate([0.0 + curOrient[1] * 15, 0.0 + curOrient[0] * 15, -15.0]);
} else {
  mvTranslate([0.0, 0.0, -15.0]);
}

Здесь мы изменяем положение куба на экране в соответствии с данными position и orientation, полученными от подключённого контроллера. Эти значения (сохранённые в curPos и curOrient ) — Float32Array, содержащие значения X, Y и Z (здесь мы используем только [0], что соответствует X, и [1], что соответствует Y).

Если переменная gp содержит объект Gamepad и может возвращать значения позиции (gpPose.hasPosition), что указывает на контроллер с 6 степенями свободы, мы изменяем положение куба, используя значения положения и ориентации. Если верно только первое, что указывает на контроллер с 3 степенями свободы, мы изменяем положение куба, используя только значения ориентации. Если джойстик не подключён, мы не изменяем положение куба.

Отображение данных о положении джойстика

В функции displayPoseStats(), мы получаем все необходимые данные из объекта GamepadPose, переданного в него, а затем выводим их на панель пользовательского интерфейса, существующую в демонстрации для отображения таких данных:

function displayPoseStats(pose) {
  const pos = pose.position;

  const formatCoords = ([x, y, z]) =>
    `x ${x.toFixed(3)}, y ${y.toFixed(3)}, z ${z.toFixed(3)}`;

  posStats.textContent = pose.hasPosition
    ? `Position: ${formatCoords(pose.position)}`
    : "Position not reported";

  orientStats.textContent = pose.hasOrientation
    ? `Orientation: ${formatCoords(pose.orientation)}`
    : "Orientation not reported";

  linVelStats.textContent = `Linear velocity: ${formatCoords(
    pose.linearVelocity,
  )}`;
  angVelStats.textContent = `Angular velocity: ${formatCoords(
    pose.angularVelocity,
  )}`;

  linAccStats.textContent = pose.linearAcceleration
    ? `Linear acceleration: ${formatCoords(pose.linearAcceleration)}`
    : "Linear acceleration not reported";

  angAccStats.textContent = pose.angularAcceleration
    ? `Angular acceleration: ${formatCoords(pose.angularAcceleration)}`
    : "Angular acceleration not reported";
}

Резюме

В этой статье вы получили очень базовое представление о том, как использовать расширения Gamepad для использования VR-контроллеров в приложениях WebVR. В реальном приложении, вероятно, будет гораздо более сложная система управления, с назначением контролей на кнопки VR-контроллеров, и отображение будет изменяться как отображением, так и позицией контроллеров одновременно. Однако здесь мы хотели выделить чисто части расширений Gamepad.

См. также

  • API WebVR
  • API Gamepad
  • Использование API WebVR
  • Реализация управления с помощью API Gamepad

© 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/WebVR_API/Using_VR_controllers_with_WebVR

Spec-Zone.ru

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