Compartilhar via


Controladores de mãos e emovimento no DirectX

Observação

Este artigo está relacionado às APIs nativas herdadas do WinRT. Para novos projetos de aplicativo nativo, é recomendável usar a API OpenXR.

Em Windows Mixed Reality, a entrada do controlador de movimento e de mão é manipulada por meio das APIs de entrada espaciais, encontradas no namespace Windows.UI.Input.Spatial. Isso permite que você lide facilmente com ações comuns, como Selecionar pressiona da mesma maneira entre as mãos e os controladores de movimento.

Introdução

Para acessar a entrada espacial no Windows Mixed Reality, comece com a interface SpatialInteractionManager. Você pode acessar essa interface chamando SpatialInteractionManager::GetForCurrentView, normalmente em algum momento durante a inicialização do aplicativo.

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

SpatialInteractionManager interactionManager = SpatialInteractionManager::GetForCurrentView();

O trabalho do SpatialInteractionManager é fornecer acesso a SpatialInteractionSources, que representam uma fonte de entrada. Há três tipos de SpatialInteractionSources disponíveis no sistema.

  • Hand representa a mão detectada de um usuário. As fontes de mão oferecem recursos diferentes com base no dispositivo, desde gestos básicos no HoloLens até acompanhamento de mão totalmente articulado em HoloLens 2.
  • O controlador representa um controlador de movimento emparelhado. Os controladores de movimento podem oferecer funcionalidades diferentes, por exemplo, Selecionar gatilhos, botões de menu, botões de compreensão, touchpads e thumbsticks.
  • A voz representa as palavras-chave detectadas pelo sistema de voz do usuário. Por exemplo, essa origem injetará uma tecla Select e uma versão sempre que o usuário disser "Selecionar".

Os dados por quadro de uma fonte são representados pela interface SpatialInteractionSourceState . Há duas maneiras diferentes de acessar esses dados, dependendo se você deseja usar um modelo baseado em eventos ou sondagem em seu aplicativo.

Entrada controlada por eventos

O SpatialInteractionManager fornece vários eventos que seu aplicativo pode escutar. Alguns exemplos incluem SourcePressed, [SourceReleased e SourceUpdated.

Por exemplo, o código a seguir conecta um manipulador de eventos chamado MyApp::OnSourcePressed ao evento SourcePressed. Isso permite que seu aplicativo detecte pressionamentos em qualquer tipo de fonte de interação.

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

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

Esse evento pressionado é enviado ao seu aplicativo de forma assíncrona, juntamente com o SpatialInteractionSourceState correspondente no momento em que a imprensa ocorreu. Seu aplicativo ou mecanismo de jogo pode querer iniciar o processamento imediatamente ou enfileirar os dados do evento em sua rotina de processamento de entrada. Aqui está uma função de manipulador de eventos para o evento SourcePressed, que verifica se o botão de seleção foi pressionado.

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
	}
}

O código acima verifica apenas a tecla 'Select', que corresponde à ação primária no dispositivo. Exemplos incluem fazer um AirTap no HoloLens ou efetuar pull do gatilho em um controlador de movimento. As pressionamentos 'Select' representam a intenção do usuário de ativar o holograma que ele está direcionando. O evento SourcePressed será acionado para vários botões e gestos diferentes e você poderá inspecionar outras propriedades no SpatialInteractionSource para testar esses casos.

Entrada baseada em sondagem

Você também pode usar SpatialInteractionManager para sondar o estado atual de entrada de cada quadro. Para fazer isso, chame GetDetectedSourcesAtTimestamp a cada quadro. Essa função retorna uma matriz que contém um SpatialInteractionSourceState para cada SpatialInteractionSource ativo. Isso significa um para cada controlador de movimento ativo, um para cada mão rastreada e outro para fala se um comando 'select' foi pronunciado recentemente. Em seguida, você pode inspecionar as propriedades em cada SpatialInteractionSourceState para direcionar a entrada para seu aplicativo.

Aqui está um exemplo de como marcar para a ação "selecionar" usando o método de sondagem. A variável de previsão representa um objeto HolographicFramePrediction , que pode ser obtido do 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
	}
}

Cada SpatialInteractionSource tem uma ID, que você pode usar para identificar novas fontes e correlacionar fontes existentes de quadro em quadro. As mãos obtêm uma nova ID sempre que saem e entram no FOV, mas as IDs do controlador permanecem estáticas durante a sessão. Você pode usar os eventos em SpatialInteractionManager, como SourceDetected e SourceLost, para reagir quando as mãos entram ou saem do modo de exibição do dispositivo ou quando os controladores de movimento são ativados/desativados ou são emparelhados/não emparelhados.

Poses previstas versus históricas

GetDetectedSourcesAtTimestamp tem um parâmetro timestamp. Isso permite que você solicite dados de estado e pose previstos ou históricos, permitindo que você correlacione interações espaciais com outras fontes de entrada. Por exemplo, ao renderizar a posição da mão no quadro atual, você pode passar o carimbo de data/hora previsto fornecido pelo HolographicFrame. Isso permite que o sistema preveja a posição da mão para se alinhar de perto com a saída do quadro renderizado, minimizando a latência percebida.

No entanto, essa pose prevista não produz um raio de apontamento ideal para direcionamento com uma fonte de interação. Por exemplo, quando um botão do controlador de movimento é pressionado, pode levar até 20 ms para que esse evento se esvaia por Bluetooth até o sistema operacional. Da mesma forma, depois que um usuário faz um gesto de mão, algum tempo pode passar antes que o sistema detecte o gesto e seu aplicativo, em seguida, sonda-o. Quando seu aplicativo sonda uma alteração de estado, a cabeça e a mão posam usadas para direcionar essa interação realmente aconteceu no passado. Se você direcionar passando o carimbo de data/hora do HolographicFrame atual para GetDetectedSourcesAtTimestamp, a pose será prevista para o raio de destino no momento em que o quadro será exibido, o que pode ser mais de 20 ms no futuro. Essa pose futura é boa para renderizar a fonte de interação, mas agrava nosso problema de tempo para direcionar a interação, já que o direcionamento do usuário ocorreu no passado.

Felizmente, os eventos SourcePressed, [SourceReleased e SourceUpdated fornecem o estado histórico associado a cada evento de entrada. Isso inclui diretamente as poses de cabeça e mão históricas disponíveis por meio de TryGetPointerPose, juntamente com um carimbo de data/ hora histórico que você pode passar para outras APIs para correlacionar com esse evento.

Isso leva às seguintes práticas recomendadas ao renderizar e direcionar com mãos e controladores a cada quadro:

  • Para renderização manual/controlador de cada quadro, seu aplicativo deve sondar a pose prevista para frente de cada fonte de interação no momento do fóton do quadro atual. Você pode sondar todas as fontes de interação chamando GetDetectedSourcesAtTimestamp em cada quadro, passando o carimbo de data/hora previsto fornecido por HolographicFrame::CurrentPrediction.
  • Para direcionamento de mão/controlador em uma imprensa ou versão, seu aplicativo deve manipular eventos pressionados/liberados, raycasting com base na pose de cabeça ou mão histórica para esse evento. Você obtém esse raio de direcionamento manipulando o evento SourcePressed ou SourceReleased, obtendo a propriedade State dos argumentos do evento e, em seguida, chamando seu método TryGetPointerPose .

Propriedades de entrada entre dispositivos

A API SpatialInteractionSource dá suporte a controladores e sistemas de acompanhamento manual com uma ampla variedade de recursos. Vários desses recursos são comuns entre tipos de dispositivo. Por exemplo, o acompanhamento manual e os controladores de movimento fornecem uma ação 'select' e uma posição 3D. Sempre que possível, a API mapeia esses recursos comuns para as mesmas propriedades no SpatialInteractionSource. Isso permite que os aplicativos ofereçam suporte mais facilmente a uma ampla gama de tipos de entrada. A tabela a seguir descreve as propriedades com suporte e como elas se comparam entre tipos de entrada.

Propriedade Descrição Gestos do HoloLens (1ª geração) Controladores de movimento Mãos Articuladas
SpatialInteractionSource::Handedness Mão direita ou esquerda/controlador. Sem suporte Com suporte Com suporte
SpatialInteractionSourceState::IsSelectPressed Estado atual do botão primário. Air Tap Gatilho Toque de Ar Relaxado (pinçagem vertical)
SpatialInteractionSourceState::IsGrasped Estado atual do botão de captura. Sem suporte Botão Pegar Pinçar ou Mão Fechada
SpatialInteractionSourceState::IsMenuPressed Estado atual do botão de menu. Sem suporte Botão menu Sem suporte
SpatialInteractionSourceLocation::Position Local XYZ da mão ou posição da alça no controlador. Localização da palma da mão Posição da pose de aderência Localização da palma da mão
SpatialInteractionSourceLocation::Orientation Quatérnion que representa a orientação da mão ou da pose de alça no controlador. Sem suporte Orientação da pose de aderência Orientação da palma da mão
SpatialPointerInteractionSourcePose::Position Origem do raio apontador. Sem suporte Com suporte Com suporte
SpatialPointerInteractionSourcePose::ForwardDirection Direção do raio apontador. Sem suporte Com suporte Com suporte

Algumas das propriedades acima não estão disponíveis em todos os dispositivos e a API fornece um meio de testar isso. Por exemplo, você pode inspecionar a propriedade SpatialInteractionSource::IsGraspSupported para determinar se a origem fornece uma ação de compreensão.

Pose de aderência vs. pose apontando

Windows Mixed Reality dá suporte a controladores de movimento em diferentes fatores forma. Ele também dá suporte a sistemas de acompanhamento de mão articulados. Todos esses sistemas têm relações diferentes entre a posição da mão e a direção natural de "avançar" que os aplicativos devem usar para apontar ou renderizar objetos na mão do usuário. Para dar suporte a tudo isso, há dois tipos de poses 3D fornecidas para controle de mão e controladores de movimento. A primeira é a pose de aderência, que representa a posição da mão do usuário. A segunda é a pose apontando, que representa um raio apontador proveniente da mão ou do controlador do usuário. Portanto, se você quiser renderizar a mão do usuário ou um objeto mantido na mão do usuário, como uma espada ou uma arma, use a pose de aperto. Se você quiser raycast do controlador ou da mão, por exemplo, quando o usuário estiver **apontando para a interface do usuário, use a pose apontando.

Você pode acessar a pose de aderência por meio de SpatialInteractionSourceState::P roperties::TryGetLocation(...). Ele é definido da seguinte maneira:

  • A posição da aderência: o centroide da palma da mão ao segurar o controlador naturalmente, ajustado para a esquerda ou direita para centralizar a posição dentro da alça.
  • Eixo direito da orientação de aderência: quando você abre completamente a mão para formar uma pose plana de 5 dedos, o raio que é normal para a palma da mão (para a frente da palma da esquerda, para trás da palma da direita)
  • Eixo de avanço da orientação de aderência: quando você fecha a mão parcialmente (como se estivesse segurando o controlador), o raio que aponta "para a frente" através do tubo formado por seus dedos não polegares.
  • O eixo Para Cima da orientação de aderência: o eixo Para cima implícito pelas definições Right e Forward.

Você pode acessar a pose do ponteiro por meio de SpatialInteractionSourceState::P roperties::TryGetLocation(...)::SourcePointerPose ou SpatialInteractionSourceState::TryGetPointerPose(...)::TryGetInteractionSourcePose.

Propriedades de entrada específicas do controlador

Para controladores, SpatialInteractionSource tem uma propriedade Controller com recursos adicionais.

  • HasThumbstick: Se for true, o controlador terá um botão de polegar. Inspecione a propriedade ControllerProperties do SpatialInteractionSourceState para adquirir os valores de thumbstick x e y (ThumbstickX e ThumbstickY), bem como seu estado pressionado (IsThumbstickPressed).
  • HasTouchpad: Se for true, o controlador terá um touchpad. Inspecione a propriedade ControllerProperties do SpatialInteractionSourceState para adquirir os valores x e y do touchpad (TouchpadX e TouchpadY) e para saber se o usuário está tocando no teclado (IsTouchpadTouched) e se está pressionando o touchpad para baixo (IsTouchpadPressed).
  • SimpleHapticsController: A API SimpleHapticsController para o controlador permite que você inspecione os recursos hápticos do controlador e também permite controlar comentários hápticos.

O intervalo para touchpad e thumbstick é -1 a 1 para ambos os eixos (de baixo para cima e da esquerda para a direita). O intervalo do gatilho analógico, que é acessado usando a propriedade SpatialInteractionSourceState::SelectPressedValue, tem um intervalo de 0 a 1. Um valor de 1 correlaciona-se com IsSelectPressed sendo igual a true; qualquer outro valor se correlaciona com IsSelectPressed sendo igual a false.

Acompanhamento articulado da mão

A API Windows Mixed Reality fornece suporte completo para acompanhamento articulado da mão, por exemplo, em HoloLens 2. O acompanhamento articulado da mão pode ser usado para implementar modelos de entrada de ponto e confirmação e manipulação direta em seus aplicativos. Ele também pode ser usado para criar interações totalmente personalizadas.

Esqueleto da mão

O acompanhamento articulado da mão fornece um esqueleto de 25 articulações que permite muitos tipos diferentes de interações. O esqueleto fornece cinco articulações para o índice/meio/anel/pequenos dedos, quatro articulações para o polegar e uma articulação de pulso. A articulação do pulso serve como a base da hierarquia. A imagem a seguir ilustra o layout do esqueleto.

Esqueleto da Mão

Na maioria dos casos, cada articulação é nomeada com base no osso que ela representa. Como há dois ossos em cada articulação, usamos uma convenção de nomear cada articulação com base no osso filho naquele local. O osso da criança é definido como o osso mais distante do pulso. Por exemplo, a articulação "Index Proximal" contém a posição inicial do osso proximal do índice e a orientação desse osso. Não contém a posição final do osso. Se você precisar disso, obterá a partir da próxima articulação na hierarquia, a articulação "Index Intermediate".

Além das 25 articulações hierárquicas, o sistema fornece uma articulação de palma. A palma da mão normalmente não é considerada parte da estrutura esquelética. Ele é fornecido apenas como uma maneira conveniente de obter a posição e a orientação gerais da mão.

As seguintes informações são fornecidas para cada conjunto:

Nome Descrição
Posição Posição 3D da articulação, disponível em qualquer sistema de coordenadas solicitado.
Orientation Orientação 3D do osso, disponível em qualquer sistema de coordenadas solicitado.
Raio Distância até a superfície da pele na posição da articulação. Útil para ajustar interações diretas ou visualizações que dependem da largura do dedo.
Precisão Fornece uma dica de como o sistema se sente confiante sobre as informações dessa articulação.

Você pode acessar os dados do esqueleto da mão por meio de uma função no SpatialInteractionSourceState. A função é chamada TryGetHandPose e retorna um objeto chamado HandPose. Se a origem não der suporte a mãos articuladas, essa função retornará nulo. Depois de ter um HandPose, você poderá obter dados de conjunto atuais chamando TryGetJoint, com o nome da articulação em que você está interessado. Os dados são retornados como uma estrutura JointPose . O código a seguir obtém a posição da ponta do dedo indicador. A variável currentState representa uma instância de 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
	}
}

Malha manual

A API de acompanhamento de mão articulada permite uma malha de mão de triângulo totalmente deformável. Essa malha pode se deformar em tempo real junto com o esqueleto da mão e é útil para técnicas avançadas de visualização e física. Para acessar a malha manual, primeiro você precisa criar um objeto HandMeshObserver chamando TryCreateHandMeshObserverAsync no SpatialInteractionSource. Isso só precisa ser feito uma vez por fonte, normalmente na primeira vez que você vê-lo. Isso significa que você chamará essa função para criar um objeto HandMeshObserver sempre que uma mão entrar no FOV. Essa é uma função assíncrona, portanto, você terá que lidar com um pouco de simultaneidade aqui. Uma vez disponível, você pode solicitar ao objeto HandMeshObserver o buffer de índice de triângulo chamando GetTriangleIndices. Os índices não alteram o quadro sobre o quadro, portanto, você pode obtê-los uma vez e armazená-los em cache durante o tempo de vida da origem. Os índices são fornecidos em ordem de enrolamento no sentido horário.

O código a seguir cria um std::thread desanexado para criar o observador de malha e extrai o buffer de índice quando o observador de malha está disponível. Ele começa com uma variável chamada currentState, que é uma instância de SpatialInteractionSourceState que representa uma mão rastreada.

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();

Iniciar um thread desanexado é apenas uma opção para lidar com chamadas assíncronas. Como alternativa, você pode usar a nova funcionalidade de co_await com suporte do C++/WinRT.

Depois de ter um objeto HandMeshObserver, você deverá mantê-lo pressionado durante o período em que o SpatialInteractionSource correspondente estiver ativo. Em seguida, cada quadro, você pode solicitar o buffer de vértice mais recente que representa a mão chamando GetVertexStateForPose e passando uma instância handPose que representa a pose para a qual você deseja vértices. Cada vértice no buffer tem uma posição e um normal. Aqui está um exemplo de como obter o conjunto atual de vértices para uma malha de mão. Como antes, a variável currentState representa uma instância de 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
    }
}

Ao contrário das articulações de esqueleto, a API de malha manual não permite que você especifique um sistema de coordenadas para os vértices. Em vez disso, o HandMeshVertexState especifica o sistema de coordenadas no qual os vértices são fornecidos. Em seguida, você pode obter uma transformação de malha chamando TryGetTransformTo e especificando o sistema de coordenadas desejado. Você precisará usar essa transformação de malha sempre que trabalhar com os vértices. Essa abordagem reduz a sobrecarga da CPU, especialmente se você estiver usando apenas a malha para fins de renderização.

Gestos de composição de Foco e Confirmação

Para aplicativos que usam o modelo de entrada de foco e confirmação, especialmente no HoloLens (primeira geração), a API de Entrada Espacial fornece um SpatialGestureRecognizer opcional que pode ser usado para habilitar gestos compostos criados sobre o evento 'select'. Ao rotear interações do SpatialInteractionManager para o SpatialGestureRecognizer de um holograma, os aplicativos podem detectar eventos de Toque, Retenção, Manipulação e Navegação uniformemente entre mãos, voz e dispositivos de entrada espacial, sem precisar lidar manualmente com pressionamentos e versões.

SpatialGestureRecognizer faz apenas a desambiguação mínima entre o conjunto de gestos que você solicita. Por exemplo, se você solicitar apenas Tocar, o usuário poderá segurar o dedo enquanto quiser e um Toque ainda ocorrerá. Se você solicitar Toque e Segurar, depois de cerca de um segundo segurando o dedo, o gesto promoverá um Hold e um Toque não ocorrerá mais.

Para usar SpatialGestureRecognizer, manipule o evento InteractionDetected do SpatialInteractionManager e pegue o SpatialPointerPose exposto lá. Use o raio de foco da cabeça do usuário dessa pose para se cruzar com os hologramas e as malhas de superfície no ambiente do usuário para determinar com o que o usuário pretende interagir. Em seguida, encaminhe SpatialInteraction nos argumentos de evento para SpatialGestureRecognizer do holograma de destino, usando seu método CaptureInteraction . Isso começa a interpretar essa interação de acordo com o conjunto SpatialGestureSettings nesse reconhecedor no momento da criação ou por TrySetGestureSettings.

No HoloLens (primeira geração), interações e gestos devem derivar seu direcionamento do foco da cabeça do usuário, em vez de renderizar ou interagir no local da mão. Depois que uma interação é iniciada, os movimentos relativos da mão podem ser usados para controlar o gesto, como com o gesto de Manipulação ou Navegação.

Confira também