Контроллеры движения и жестов в DirectX

Примечание

Эта статья относится к устаревшим собственным API WinRT. Для новых проектов собственных приложений рекомендуется использовать API OpenXR.

В Windows Mixed Reality входные данные вручную и контроллера движения обрабатываются через ИНТЕРФЕЙСы API пространственного ввода, которые находятся в пространстве имен Windows.UI.Input.Spatial. Это позволяет легко обрабатывать распространенные действия, такие как нажатие нажатия select одинаково между руками и контроллерами движения.

Начало работы

Чтобы получить доступ к пространственным входным данным в Windows Mixed Reality, начните с интерфейса SpatialInteractionManager. Вы можете получить доступ к этому интерфейсу, вызвав SpatialInteractionManager::GetForCurrentView, как правило, когда-нибудь во время запуска приложения.

using namespace winrt::Windows::UI::Input::Spatial;

SpatialInteractionManager interactionManager = SpatialInteractionManager::GetForCurrentView();

Задача SpatialInteractionManager заключается в предоставлении доступа к SpatialInteractionSources, которые представляют источник входных данных. В системе доступно три типа SpatialInteractionSources.

  • Hand представляет обнаруженную руку пользователя. Источники рук предлагают различные функции, основанные на устройстве, от базовых жестов на HoloLens до полного отслеживания рук на HoloLens 2.
  • Контроллер представляет собой сопряженный контроллер движения. Контроллеры движения могут предоставлять различные возможности, например выбрать триггеры, кнопки меню, кнопки захвата, сенсорные панели и стикы.
  • Voice представляет ключевые слова, обнаруженные системой, голосовые речи пользователя. Например, этот источник будет внедрять нажатие и отпускать select всякий раз, когда пользователь говорит "Выбрать".

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

Входные данные на основе событий

SpatialInteractionManager предоставляет ряд событий, которые приложение может прослушивать. Примеры: SourcePressed, [SourceReleased и SourceUpdated.

Например, следующий код подключает обработчик событий MyApp::OnSourcePressed к событию SourcePressed. Это позволяет приложению обнаруживать нажатия на любом типе источника взаимодействия.

using namespace winrt::Windows::UI::Input::Spatial;

auto interactionManager = SpatialInteractionManager::GetForCurrentView();
interactionManager.SourcePressed({ this, &MyApp::OnSourcePressed });

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

using namespace winrt::Windows::UI::Input::Spatial;

void MyApp::OnSourcePressed(SpatialInteractionManager const& sender, SpatialInteractionSourceEventArgs const& args)
{
	if (args.PressKind() == SpatialInteractionPressKind::Select)
	{
		// Select button was pressed, update app state
	}
}

Приведенный выше код проверяет только нажатие "Выбрать", которое соответствует основному действию на устройстве. Примеры включают выполнение AirTap на HoloLens или включение триггера на контроллере движения. Нажатия "Выбрать" представляют намерение пользователя активировать голограмму, на которую он нацелен. Событие SourcePressed будет запускаться для нескольких различных кнопок и жестов, и вы можете проверить другие свойства в SpatialInteractionSource для проверки на эти случаи.

Входные данные на основе опроса

Вы также можете использовать SpatialInteractionManager для опроса текущего состояния входных данных каждого кадра. Для этого вызовите Метод GetDetectedSourcesAtTimestamp для каждого кадра. Эта функция возвращает массив, содержащий один SpatialInteractionSourceState для каждого активного SpatialInteractionSource. Это означает, что один для каждого активного контроллера движения, по одному для каждой отслеживаемой руки и один для речи, если команда "выбрать" была недавно произнесна. Затем можно проверить свойства каждого объекта SpatialInteractionSourceState, чтобы включить входные данные в приложение.

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

using namespace winrt::Windows::UI::Input::Spatial;

auto interactionManager = SpatialInteractionManager::GetForCurrentView();
auto sourceStates = m_spatialInteractionManager.GetDetectedSourcesAtTimestamp(prediction.Timestamp());

for (auto& sourceState : sourceStates)
{
	if (sourceState.IsSelectPressed())
	{
		// Select button is down, update app state
	}
}

Каждый объект SpatialInteractionSource имеет идентификатор, который можно использовать для идентификации новых источников и корреляции существующих источников из кадра в кадр. Руки получают новый идентификатор каждый раз, когда они покидают и вводят FOV, но идентификаторы контроллеров остаются статическими в течение всего сеанса. Вы можете использовать события в SpatialInteractionManager, такие как SourceDetected и SourceLost, чтобы реагировать, когда руки входят в представление устройства или выходят из него, а также когда контроллеры движения включены или выключены или являются парными или несвязанными.

Прогнозируемые и исторические позы

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

Однако такая прогнозируемая поза не создает идеального указывающего луча для нацеливания на источник взаимодействия. Например, при нажатии кнопки контроллера движения может потребоваться до 20 мс, чтобы это событие перенапряжилось через Bluetooth в операционную систему. Аналогичным образом, после того, как пользователь выполняет жест руки, может пройти некоторое время, прежде чем система обнаружит жест, и ваше приложение затем опрашивает его. К тому времени, когда ваше приложение опрашивает об изменении состояния, голова и рука позы, используемые для того, чтобы это взаимодействие на самом деле произошло в прошлом. Если вы нацеливаетесь, передав метку времени текущего голографического кадра в GetDetectedSourcesAtTimestamp, то вместо этого положение будет переадресовываться в целевой луч на момент отображения кадра, что может быть более 20 мс в будущем. Эта будущая поза хорошо подходит для отрисовки источника взаимодействия, но усугубляет проблему времени для нацеливания взаимодействия, так как нацеливание пользователя произошло в прошлом.

К счастью, события SourcePressed, [SourceReleased и SourceUpdated предоставляют историческое состояние , связанное с каждым событием ввода. Сюда напрямую входят исторические положения головы и руки, доступные через TryGetPointerPose, а также метка времени , которую можно передать другим API для корреляции с этим событием.

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

  • Для отрисовки каждого кадра вручную или контроллера приложение должно опрашиватьпрогнозируемый вперед позу каждого источника взаимодействия на фотонное время текущего кадра. Вы можете опросить все источники взаимодействия, вызывая Метод GetDetectedSourcesAtTimestamp для каждого кадра , передавая прогнозируемую метку времени, предоставленную HolographicFrame::CurrentPrediction.
  • Для нацеливания руки или контроллера на пресс-релиз ваше приложение должно обрабатывать нажатые или освобожденные события, отправляемые лучами на основе исторической головы или положения руки для этого события. Этот целевой луч можно получить, обрабатывая событие SourcePressed или SourceReleased , получая свойство State из аргументов события, а затем вызывая его метод TryGetPointerPose .

Свойства ввода на нескольких устройствах

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

Свойство Описание Жесты HoloLens (1-го поколения) Контроллеры движения Сформулированные руки
SpatialInteractionSource::Handedness Правая или левая рука или контроллер. Не поддерживается Поддерживается Поддерживается
SpatialInteractionSourceState::IsSelectPressed Текущее состояние основной кнопки. Касание воздуха Триггер Расслабленный воздушный кран (вертикальное нажатие)
SpatialInteractionSourceState::IsGrasped Текущее состояние кнопки захвата. Не поддерживается Кнопка захвата Ущипнуть или закрыть руку
SpatialInteractionSourceState::IsMenuPressed Текущее состояние кнопки меню. Не поддерживается Кнопка меню Не поддерживается
SpatialInteractionSourceLocation::Position XYZ положение руки или захвата на контроллере. Расположение пальмы Положение захвата Расположение пальмы
SpatialInteractionSourceLocation::Orientation Кватернион, представляющий ориентацию руки или положения захвата на контроллере. Не поддерживается Ориентация положения с захватом Ориентация ладони
SpatialPointerInteractionSourcePose::Position Источник указывающего луча. Не поддерживается Поддерживается Поддерживается
SpatialPointerInteractionSourcePose::ForwardDirection Направление указывающего луча. Не поддерживается Поддерживается Поддерживается

Некоторые из указанных выше свойств доступны не на всех устройствах, и API предоставляет средства для тестирования. Например, можно проверить свойство SpatialInteractionSource::IsGraspSupported , чтобы определить, предоставляет ли источник действие захвата.

Положение захвата и положение указателя

Windows Mixed Reality поддерживает контроллеры движения в различных форм-факторах. Кроме того, он поддерживает системы отслеживания рук. Все эти системы имеют разные отношения между положением руки и естественным направлением вперед, которое приложения должны использовать для указания или отрисовки объектов в руке пользователя. Для поддержки всего этого существует два типа трехмерных поз, предоставляемых как для отслеживания рук, так и для контроллеров движения. Первый — это поза захвата, представляющая положение руки пользователя. Второй — это указывающая поза, которая представляет луч указателя, исходящий из руки или контроллера пользователя. Таким образом, если вы хотите отобразить руку пользователя или предмет, удерживаемый в руке пользователя, например меч или пистолет, используйте позу захвата. Если вы хотите передавать данные с контроллера или руки, например, когда пользователь **указывает на пользовательский интерфейс, используйте положение указателя.

Вы можете получить доступ к позе захвата через SpatialInteractionSourceState::P roperties::TryGetLocation(...). Он определяется следующим образом:

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

Доступ к позе указателя можно получить через SpatialInteractionSourceState::P roperties::TryGetLocation(...)::SourcePointerPose или SpatialInteractionSourceState::TryGetPointerPose(...)::TryGetInteractionSourcePose.

Входные свойства контроллера

Для контроллеров SpatialInteractionSource имеет свойство Controller с дополнительными возможностями.

  • HasThumbstick: Если задано значение true, контроллер имеет стик. Проверьте свойство ControllerProperties объекта SpatialInteractionSourceState, чтобы получить значения thumbstick x и y (ThumbstickX и ThumbstickY), а также его нажатое состояние (IsThumbstickPressed).
  • HasTouchpad: Если задано значение true, контроллер имеет сенсорную панель. Проверьте свойство ControllerProperties объекта SpatialInteractionSourceState, чтобы получить значения сенсорной панели x и y (TouchpadX и TouchpadY), а также узнать, касается ли пользователь панели (IsTouchpadTouched) и нажимает ли он сенсорную панель вниз (IsTouchpadPressed).
  • SimpleHapticsController: API SimpleHapticsController для контроллера позволяет проверять тактильные возможности контроллера, а также управлять тактильной обратной связью.

Диапазон для сенсорной панели и стиков составляет от -1 до 1 для обеих осей (снизу вверх и слева направо). Диапазон для аналогового триггера, доступ к которому осуществляется с помощью свойства SpatialInteractionSourceState::SelectPressedValue, имеет диапазон от 0 до 1. Значение 1 коррелирует с isSelectPressed, равным true; Любое другое значение коррелирует со значением IsSelectPressed, равным false.

Отслеживание сформулированной руки

API Windows Mixed Reality обеспечивает полную поддержку четкого отслеживания рук, например на HoloLens 2. Для реализации прямых операций и моделей ввода "точка и фиксация" в приложениях можно использовать функцию отслеживания рук. Его также можно использовать для создания полностью настраиваемых взаимодействий.

Скелет руки

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

Скелет руки

В большинстве случаев каждый сустав называется в зависимости от кости, которую он представляет. Так как в каждом суставе есть две кости, мы используем соглашение об именовании каждого сустава на основе дочерней кости в этом месте. Детская кость определяется как кость дальше от запястья. Например, сустав "Индекс Проксимальный" содержит начальную позицию индекса проксимальной кости и ориентацию этой кости. Он не содержит конечного положения кости. Если вам это нужно, вы получите его из следующего соединения в иерархии, "Index Intermediate".

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

Для каждого соединения предоставляется следующая информация:

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

Получить доступ к данным каркаса руки можно с помощью функции в SpatialInteractionSourceState. Функция называется TryGetHandPose и возвращает объект HandPose. Если источник не поддерживает сформулированные руки, эта функция возвращает значение NULL. Получив HandPose, вы можете получить текущие совместные данные, вызвав TryGetJoint с именем интересующего вас соединения. Данные возвращаются в виде структуры JointPose . Следующий код получает положение кончика указательного пальца. Переменная currentState представляет экземпляр SpatialInteractionSourceState.

using namespace winrt::Windows::Perception::People;
using namespace winrt::Windows::Foundation::Numerics;

auto handPose = currentState.TryGetHandPose();
if (handPose)
{
	JointPose joint;
	if (handPose.TryGetJoint(desiredCoordinateSystem, HandJointKind::IndexTip, joint))
	{
		float3 indexTipPosition = joint.Position;

		// Do something with the index tip position
	}
}

Сетка рук

API отслеживания рук с шарнирным элементом позволяет создать полностью деформируемую сетку для рук треугольника. Эта сетка может деформироваться в режиме реального времени вместе со скелетом руки и полезна для визуализации и передовых методов физики. Чтобы получить доступ к сетке рук, необходимо сначала создать объект HandMeshObserver , вызвав TryCreateHandMeshObserverAsync в SpatialInteractionSource. Это необходимо сделать только один раз для каждого источника, как правило, при первом его отображении. Это означает, что вы будете вызывать эту функцию для создания объекта HandMeshObserver всякий раз, когда рука входит в FOV. Это асинхронная функция, поэтому вам придется иметь дело с немного параллелизма. Когда он доступен, можно запросить у объекта HandMeshObserver буфер треугольника индекса, вызвав Метод GetTriangleIndices. Индексы не изменяют фрейм по кадру, поэтому их можно получить один раз и кэшировать на время существования источника. Индексы предоставляются в порядке обмотки по часовой стрелке.

Следующий код запускает отсоединяемый std::thread для создания наблюдателя сетки и извлекает буфер индекса после того, как наблюдатель сетки становится доступным. Он начинается с переменной с именем currentState, которая представляет собой экземпляр SpatialInteractionSourceState , представляющий отслеживаемую руку.

using namespace Windows::Perception::People;

std::thread createObserverThread([this, currentState]()
{
    HandMeshObserver newHandMeshObserver = currentState.Source().TryCreateHandMeshObserverAsync().get();
    if (newHandMeshObserver)
    {
		unsigned indexCount = newHandMeshObserver.TriangleIndexCount();
		vector<unsigned short> indices(indexCount);
		newHandMeshObserver.GetTriangleIndices(indices);

        // Save the indices and handMeshObserver for later use - and use a mutex to synchronize access if needed!
     }
});
createObserverThread.detach();

Запуск отсоединяемого потока — это только один из вариантов обработки асинхронных вызовов. Кроме того, можно использовать новые функции co_await , поддерживаемые C++/WinRT.

Получив объект HandMeshObserver, следует удерживать его в течение времени активности соответствующего объекта SpatialInteractionSource. Затем каждый кадр можно запросить у него последний буфер вершин, представляющий руку, вызвав Метод GetVertexStateForPose и передав экземпляр HandPose , представляющий положение, для которого требуется вершины. Каждая вершина в буфере имеет положение и норму. Ниже приведен пример того, как получить текущий набор вершин для сетки рук. Как и ранее, переменная currentState представляет экземпляр SpatialInteractionSourceState.

using namespace winrt::Windows::Perception::People;

auto handPose = currentState.TryGetHandPose();
if (handPose)
{
    std::vector<HandMeshVertex> vertices(handMeshObserver.VertexCount());
    auto vertexState = handMeshObserver.GetVertexStateForPose(handPose);
    vertexState.GetVertices(vertices);

    auto meshTransform = vertexState.CoordinateSystem().TryGetTransformTo(desiredCoordinateSystem);
    if (meshTransform != nullptr)
    {
    	// Do something with the vertices and mesh transform, along with the indices that you saved earlier
    }
}

В отличие от скелетных соединений, API сетки рук не позволяет указать систему координат для вершин. Вместо этого HandMeshVertexState указывает систему координат, в которую предоставляются вершины. Затем можно получить преобразование сетки, вызвав TryGetTransformTo и указав нужную систему координат. Это преобразование сетки необходимо использовать при работе с вершинами. Такой подход снижает нагрузку на ЦП, особенно если сетка используется только для отрисовки.

Составные жесты "Взгляд и фиксация"

Для приложений, использующих модель ввода взгляда и фиксации, особенно в HoloLens (первого поколения), API пространственного ввода предоставляет дополнительный SpatialGestureRecognizer , который можно использовать для включения составных жестов, созданных на основе события select. Направляя взаимодействия из SpatialInteractionManager в объект SpatialGestureRecognizer голограммы, приложения могут обнаруживать события касания, удержания, манипуляции и навигации в руках, голосовых и пространственных устройствах ввода без необходимости обрабатывать нажатия и отпускания вручную.

SpatialGestureRecognizer выполняет только минимальное неоднозначности между запрашиваемым набором жестов. Например, если вы запрашиваете только касание, пользователь может удерживать палец до тех пор, пока ей нравится, и касание по-прежнему будет происходить. Если вы запрашиваете как касание, так и удержание, примерно через секунду после удерживания пальца жест будет повышен до удержания, и касание больше не будет выполняться.

Чтобы использовать SpatialGestureRecognizer, обработайте событие InteractionDetected SpatialInteractionManager и захватить предоставленный объект SpatialPointerPose. Используйте луч взгляда пользователя из этой позы, чтобы пересекаться с голограммами и поверхностными сетками в окружении пользователя, чтобы определить, с чем пользователь намерен взаимодействовать. Затем направьте объект SpatialInteraction в аргументах события в объект SpatialGestureRecognizer целевой голограммы с помощью метода CaptureInteraction . Это начинает интерпретировать это взаимодействие в соответствии с SpatialGestureSettings, заданным для этого распознавателя во время создания, или с помощью TrySetGestureSettings.

В HoloLens (первого поколения) взаимодействия и жесты должны быть производными от взгляда головы пользователя, а не отрисовки или взаимодействия в расположении руки. После начала взаимодействия для управления жестом можно использовать относительные движения руки, как в случае с жестом "Манипуляция" или "Навигация".

См. также раздел