Отслеживание рук — MRTK3

Обзор

Так как данные о суставах руки являются одними из немногих входных данных, которые еще не обрабатываются нативно системой ввода Unity, они обрабатываются нашими подсистемами.

Примечание

Если вы не знакомы с подсистемами MRTK3 и их отличиями от служб MRTK 2.x, изучите документацию по архитектуре подсистем MRTK3, чтобы подробно ознакомиться с нашей философией и дизайном.

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

  • OpenXRHandsSubsystem получает данные о движениях руки непосредственно из подключаемого модуля OpenXR.
  • XRSDKHandsSubsystem получает данные о движениях руки из уровня абстракции пакета SDK для XR системы Unity (который, в свою очередь, может получать данные из OpenXR или другого источника).
  • SyntheticHandsSubsystem синтезирует фиктивные суставы руки на основе входных действий, поступающих из системы (например, devicePosition, deviceRotation и т. д.). Эта подсистема предоставляет суставы, которые отображаются при использовании имитации ввода в редакторе.

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

Важно!

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

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

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

Характеристики сжатия

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

Hands Aggregator subsystem configuration

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

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

Примеры

// Get a reference to the aggregator.
var aggregator = XRSubsystemHelpers.GetFirstRunningSubsystem<HandsAggregatorSubsystem>();
// Wait until an aggregator is available.
IEnumerator EnableWhenSubsystemAvailable()
{
    yield return new WaitUntil(() => XRSubsystemHelpers.GetFirstRunningSubsystem<HandsAggregatorSubsystem>() != null);
    GoAhead();
}
// Get a single joint (Index tip, on left hand, for example)
bool jointIsValid = aggregator.TryGetJoint(TrackedHandJoint.IndexTip, XRNode.LeftHand, out HandJointPose jointPose);
// Get an entire hand's worth of joints from the left hand.
bool allJointsAreValid = aggregator.TryGetEntireHand(XRNode.LeftHand, out IReadOnlyList<HandJointPose> joints)
// Check whether the user's left hand is facing away (commonly used to check "aim" intent)
// This is adjustable with the HandFacingAwayTolerance option in the Aggregator configuration.
// "handIsValid" represents whether there was valid hand data in the first place!
bool handIsValid = aggregator.TryGetPalmFacingAway(XRNode.LeftHand, out bool isLeftPalmFacingAway)
// Query pinch characteristics from the left hand.
// pinchAmount is [0,1], normalized to the open/closed thresholds specified in the Aggregator configuration.
// "isReadyToPinch" is adjusted with the HandRaiseCameraFOV and HandFacingAwayTolerance settings in the configuration.
bool handIsValid = aggregator.TryGetPinchProgress(XRNode.LeftHand, out bool isReadyToPinch, out bool isPinching, out float pinchAmount)