Comandos de mãos e movimentos no DirectX

Nota

Este artigo está relacionado com as APIs nativas do WinRT legadas. Para novos projetos de aplicações nativas, recomendamos a utilização da API OpenXR.

No Windows Mixed Reality, a entrada do comando de movimento e mão é processada através das APIs de entrada espacial, encontradas no espaço de nomes Windows.UI.Input.Spatial. Isto permite-lhe lidar facilmente com ações comuns, como Selecionar , que é premida da mesma forma entre as mãos e os comandos de movimento.

Introdução

Para aceder à entrada espacial no Windows Mixed Reality, comece com a interface SpatialInteractionManager. Pode aceder a esta interface ao chamar SpatialInteractionManager::GetForCurrentView, normalmente durante o arranque da aplicação.

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

SpatialInteractionManager interactionManager = SpatialInteractionManager::GetForCurrentView();

A tarefa do SpatialInteractionManager é fornecer acesso ao SpatialInteractionSources, que representa uma origem de entrada. Existem três tipos de SpatialInteractionSources disponíveis no sistema.

  • Hand representa a mão detetada por um utilizador. As fontes manuais oferecem diferentes funcionalidades baseadas no dispositivo, desde gestos básicos no HoloLens até controlo manual totalmente articulado no HoloLens 2.
  • O comando representa um comando de movimento emparelhado. Os comandos de movimento podem oferecer diferentes capacidades, por exemplo, Selecionar acionadores, Botões de menu, botões Agarrar, touchpads e manípulos.
  • A voz representa as palavras-chave de voz detetadas pelo sistema do utilizador. Por exemplo, esta origem injetará uma premição e uma versão Selecionar sempre que o utilizador disser "Selecionar".

Os dados por frame de uma origem são representados pela interface SpatialInteractionSourceState . Existem duas formas diferentes de aceder a estes dados, consoante pretenda utilizar um modelo baseado em eventos ou baseado em consultas na sua aplicação.

Entrada condicionada por eventos

O SpatialInteractionManager fornece uma série de eventos que a sua aplicação pode escutar. Alguns exemplos incluem SourcePressed, [SourceReleased e SourceUpdated.

Por exemplo, o código seguinte liga um processador de eventos denominado MyApp::OnSourcePressed ao evento SourcePressed. Isto permite que a sua aplicação detete pressões em qualquer tipo de origem de interação.

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

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

Este evento premido é enviado para a sua aplicação de forma assíncrona, juntamente com o SpatialInteractionSourceState correspondente no momento em que a imprensa ocorreu. A sua aplicação ou motor de jogo poderá querer começar a processar imediatamente ou colocar os dados do evento em fila na sua rotina de processamento de entrada. Eis uma função de processador de eventos para o evento SourcePressed, que verifica se o botão selecionar foi premido.

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 premição "Selecionar", que corresponde à ação principal no dispositivo. Os exemplos incluem fazer um AirTap no HoloLens ou puxar o acionador num controlador de movimento. As premições "Selecionar" representam a intenção do utilizador de ativar o holograma que está a filtrar. O evento SourcePressed será acionado por vários botões e gestos diferentes e pode inspecionar outras propriedades no SpatialInteractionSource para testar esses casos.

Entrada baseada em consultas

Também pode utilizar SpatialInteractionManager para consultar o estado atual de entrada de cada fotograma. Para tal, chame GetDetectedSourcesAtTimestamp cada frame. Esta função devolve uma matriz que contém um SpatialInteractionSourceState para cada SpatialInteractionSource ativo. Isto significa um para cada comando de movimento ativo, um para cada mão controlada e outro para voz se um comando "selecionar" tiver sido recentemente pronunciado. Em seguida, pode inspecionar as propriedades em cada SpatialInteractionSourceState para impulsionar a entrada na sua aplicação.

Eis um exemplo de como verificar a ação "selecionar" com o método de consulta. A variável de predição representa um objeto HolographicFramePrediction , que pode ser obtido a partir 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 um ID, que pode utilizar para identificar novas origens e correlacionar as origens existentes da moldura para a moldura. As mãos obtêm um novo ID sempre que saem e introduzem o FOV, mas os IDs do controlador permanecem estáticos durante a sessão. Pode utilizar os eventos em SpatialInteractionManager, como SourceDetected e SourceLost, para reagir quando as mãos entram ou saem da vista do dispositivo ou quando os controladores de movimento estão ligados/desativados ou são emparelhados/não pagos.

Poses previstas vs. históricas

GetDetectedSourcesAtTimestamp tem um parâmetro de carimbo de data/hora. Isto permite-lhe pedir o estado e colocar dados previstos ou históricos, o que lhe permite correlacionar interações espaciais com outras origens de entrada. Por exemplo, ao compor a posição da mão na moldura atual, pode transmitir o carimbo de data/hora previsto fornecido pelo HolographicFrame. Isto permite que o sistema reencaminhe e preveja a posição da mão para se alinhar de perto com a saída da moldura composta, minimizando a latência percebida.

No entanto, tal pose prevista não produz um raio apontador ideal para filtrar com uma origem de interação. Por exemplo, quando um botão de comando de movimento é premido, pode demorar até 20 ms para que esse evento seja apresentado através de Bluetooth para o sistema operativo. Da mesma forma, depois de um utilizador fazer um gesto de mão, poderá passar algum tempo antes de o sistema detetar o gesto e a aplicação, em seguida, sondar o mesmo. Quando a sua aplicação procura uma mudança de estado, a cabeça e as poses de mãos utilizadas para direcionar essa interação realmente aconteceram no passado. Se segmentar ao passar o carimbo de data/hora do HolographicFrame atual para GetDetectedSourcesAtTimestamp, a pose será, em vez disso, encaminhada para o raio de destino no momento em que o fotograma será apresentado, o que poderá ser superior a 20 ms no futuro. Esta pose futura é boa para compor a origem de interação, mas compõe o nosso problema de tempo para direcionar a interação, uma vez que o destino do utilizador ocorreu no passado.

Felizmente, os eventos SourcePressed, [SourceReleased e SourceUpdated fornecem o Estado histórico associado a cada evento de entrada. Isto inclui diretamente as poses históricas de cabeça e mão disponíveis através do TryGetPointerPose, juntamente com um carimbo de data/ hora histórico que pode transmitir a outras APIs para correlacionar com este evento.

Isto leva às seguintes melhores práticas ao compor e filtrar com mãos e comandos em cada frame:

  • Para a composição manual/controlador de cada frame, a sua aplicação deve consultar a pose prevista para a frente de cada origem de interação no momento do fotão do fotograma atual. Pode consultar todas as origens de interação ao chamar GetDetectedSourcesAtTimestamp cada frame, transmitindo o carimbo de data/hora previsto fornecido pelo HolographicFrame::CurrentPrediction.
  • Para filtragem manual/controlador numa imprensa ou versão, a sua aplicação deve processar eventos premidos/ lançados, raycasting com base na posição histórica da cabeça ou da mão para esse evento. Obtém este raio de destino ao processar o evento SourcePressed ou SourceReleased , obter a propriedade State a partir dos argumentos do evento e, em seguida, chamar o método TryGetPointerPose .

Propriedades de entrada entre dispositivos

A API SpatialInteractionSource suporta controladores e sistemas de controlo manual com uma vasta gama de capacidades. Várias destas funcionalidades são comuns entre tipos de dispositivos. Por exemplo, o controlo manual e os controladores de movimento fornecem uma ação "selecionar" e uma posição 3D. Sempre que possível, a API mapeia estas capacidades comuns para as mesmas propriedades no SpatialInteractionSource. Isto permite que as aplicações suportem mais facilmente uma vasta gama de tipos de entrada. A tabela seguinte descreve as propriedades que são suportadas e como se comparam entre tipos de entrada.

Propriedade Descrição Gestos do HoloLens(1.ª geração) Comandos de Movimento Mãos Articuladas
SpatialInteractionSource::Handedness Mão direita ou esquerda/comando. Não suportado Suportado Suportado
SpatialInteractionSourceState::IsSelectPressed Estado atual do botão primário. Toque no Ar Acionador Toque de Ar Descontraído (aproximar verticalmente)
SpatialInteractionSourceState::IsGrasped Estado atual do botão de agarrar. Não suportado Botão Agarrar Aproximar ou Fechar Mão
SpatialInteractionSourceState::IsMenuPressed Estado atual do botão de menu. Não suportado Botão menu Não suportado
SpatialInteractionSourceLocation::Position Localização XYZ da posição da mão ou da aderência no controlador. Localização da palma da mão Posição de postura de aderência Localização da palma da mão
SpatialInteractionSourceLocation::Orientation Quaternion representa a orientação da pose da mão ou da pega no controlador. Não suportado Orientação da pose de aperto Orientação da palma da mão
SpatialPointerInteractionSourcePose::Position Origem do raio apontador. Não suportado Suportado Suportado
SpatialPointerInteractionSourcePose::ForwardDirection Direção do raio apontador. Não suportado Suportado Suportado

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

Pose de aperto vs. pose apontando

Windows Mixed Reality suporta controladores de movimento em diferentes fatores de forma. Também suporta sistemas de controlo manual articulados. Todos estes sistemas têm relações diferentes entre a posição manual e a direção natural "reencaminhamento" que as aplicações devem utilizar para apontar ou compor objetos na mão do utilizador. Para suportar tudo isto, existem dois tipos de poses 3D fornecidas para controladores de movimento e controlo de mãos. A primeira é a pose de aperto, que representa a posição da mão do utilizador. A segunda é a pose a apontar, que representa um raio apontador proveniente da mão ou controlador do utilizador. Por isso, se quiser compor a mão do utilizador ou um objeto na mão do utilizador, como uma espada ou uma arma, utilize a pose de aperto. Se quiser fazer raycast a partir do controlador ou da mão, por exemplo, quando o utilizador estiver **a apontar para a IU, utilize a pose de apontamento.

Pode aceder à pose de aperto através de SpatialInteractionSourceState::P roperties::TryGetLocation(...). É definido da seguinte forma:

  • A posição de aperto: o centroid da palma da mão ao manter o controlador naturalmente, ajustado à esquerda ou à direita para centrar a posição dentro da alça.
  • O eixo direito da orientação do aperto: quando abre completamente a mão para formar uma pose plana de 5 dedos, o raio que é normal na palma da mão (para a frente da palma da mão esquerda, para trás a partir da palma da mão direita)
  • O eixo Reencaminhamento da orientação de aperto: quando fecha parcialmente a mão (como se estivesse a segurar o controlador), o raio que aponta "para a frente" através do tubo formado pelos seus dedos não polegares.
  • Eixo Up da orientação do aperto: o eixo Cima implícito pelas definições De Direita e Reencaminhamento.

Pode aceder à pose do ponteiro através de SpatialInteractionSourceState::P roperties::TryGetLocation(...)::SourcePointerPose ou SpatialInteractionSourceState::TryGetPointerPose(...)::TryGetInteractionSourcePose.

Propriedades de entrada específicas do controlador

Para controladores, o SpatialInteractionSource tem uma propriedade Controller com capacidades adicionais.

  • HasThumbstick: Se for verdadeiro, o controlador tem um thumbstick. Inspecione a propriedade ControllerProperties do SpatialInteractionSourceState para adquirir os valores de thumbstick x e y (ThumbstickX e ThumbstickY), bem como o estado premido (IsThumbstickPressed).
  • HasTouchpad: Se for verdadeiro, o controlador tem um touchpad. Inspecione a propriedade ControllerProperties do SpatialInteractionSourceState para adquirir os valores touchpad x e y (TouchpadX e TouchpadY) e para saber se o utilizador está a tocar no teclado (IsTouchpadTouched) e se está a premir o touchpad para baixo (IsTouchpadPressed).
  • SimpleHapticsController: A API SimpleHapticsController para o controlador permite-lhe inspecionar as capacidades hápticas do controlador e também lhe permite controlar os 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 para o acionador analógico, que é acedido com a propriedade SpatialInteractionSourceState::SelectPressedValue, tem um intervalo de 0 a 1. Um valor de 1 está correlacionado com IsSelectPressed sendo igual a verdadeiro; qualquer outro valor está correlacionado com IsSelectPressed sendo igual a falso.

Controlo manual articulado

A API de Windows Mixed Reality fornece suporte total para o controlo manual articulado, por exemplo, em HoloLens 2. O controlo manual articulado pode ser utilizado para implementar modelos de introdução de manipulação direta e de ponto e consolidação nas suas aplicações. Também pode ser utilizado para criar interações totalmente personalizadas.

Esqueleto da mão

O controlo manual articulado 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 do pulso. A articulação do pulso serve como base da hierarquia. A imagem seguinte ilustra o esquema do esqueleto.

Estrutura da Mão

Na maioria dos casos, cada articulação é nomeada com base no osso que representa. Uma vez que há dois ossos em cada articulação, usamos uma convenção de nomear cada articulação com base no osso da criança naquele local. O osso da criança é definido como o osso mais longe 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 precisar disso, irá obtê-lo na próxima junta da hierarquia, a junta "Index Intermediate".

Além das 25 juntas hierárquicas, o sistema fornece uma articulação de palma. Normalmente, a palma da mão não é considerada parte da estrutura esquelética. É fornecido apenas como uma forma conveniente de obter a posição e a orientação gerais da mão.

São fornecidas as seguintes informações para cada articulação:

Nome Descrição
Posição Posição 3D da articulação, disponível em qualquer sistema de coordenadas pedido.
Orientação Orientação 3D do osso, disponível em qualquer sistema de coordenadas pedido.
Radius Distância até à superfície da pele na posição da articulação. Útil para otimizar interações diretas ou visualizações que dependem da largura dos dedos.
Precisão Fornece uma sugestão sobre como o sistema se sente confiante sobre as informações desta junta.

Pode aceder aos dados de estrutura da mão através de uma função no SpatialInteractionSourceState. A função chama-se TryGetHandPose e devolve um objeto chamado HandPose. Se a origem não suportar mãos articuladas, esta função devolverá nulo. Assim que tiver um HandPose, pode obter dados conjuntos atuais ao chamar TryGetJoint, com o nome da articulação em que está interessado. Os dados são devolvidos como uma estrutura JointPose . O código seguinte obtém a posição da ponta do dedo do índice. 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 controlo manual articulada permite uma malha manual de triângulo totalmente deformável. Esta malha pode deformar-se em tempo real juntamente com o esqueleto da mão e é útil para a visualização e técnicas avançadas de física. Para aceder à malha manual, primeiro tem de criar um objeto HandMeshObserver ao chamar TryCreateHandMeshObserverAsync no SpatialInteractionSource. Isto só tem de ser feito uma vez por origem, normalmente a primeira vez que o vir. Isto significa que irá chamar esta função para criar um objeto HandMeshObserver sempre que uma mão entrar no FOV. Esta é uma função assíncrona, pelo que terá de lidar com um pouco de simultaneidade aqui. Uma vez disponível, pode pedir ao objeto HandMeshObserver a memória intermédia do índice triângulo ao chamar GetTriangleIndices. Os índices não alteram a moldura em vez da moldura, pelo que pode obtê-los uma vez e guardá-los durante a duração da origem. Os índices são fornecidos por ordem de enrolamento no sentido dos ponteiros do relógio.

O código seguinte cria um std::thread desanexado para criar o observador de malha e extrai a memória intermédia do índice assim que o observador de malha estiver disponível. Começa a partir de uma variável chamada currentState, que é uma instância de SpatialInteractionSourceState que representa uma mão controlada.

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 processar chamadas assíncronas. Em alternativa, pode utilizar a nova funcionalidade de co_await suportada por C++/WinRT.

Assim que tiver um objeto HandMeshObserver, deve mantê-lo premido durante a duração em que o spatialInteractionSource correspondente esteja ativo. Em seguida, cada frame, pode pedir-lhe a memória intermédia de vértices mais recente que representa a mão ao chamar GetVertexStateForPose e transmitir uma instância handPose que representa a pose para a qual pretende vertices. Cada vértice na memória intermédia tem uma posição e um normal. Eis um exemplo de como obter o conjunto atual de vértices para uma malha manual. Tal como anteriormente, 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 estrutura, a API de malha manual não lhe permite especificar 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, pode obter uma transformação de malha ao chamar TryGetTransformTo e especificar o sistema de coordenadas que pretende. Terá de utilizar esta transformação de malha sempre que trabalhar com os vértices. Esta abordagem reduz a sobrecarga da CPU, especialmente se estiver a utilizar apenas a malha para fins de composição.

Olhar e Consolidar gestos compostos

Para aplicações que utilizam o modelo de entrada de olhar e consolidação, particularmente no HoloLens (primeira geração), a API de Entrada Espacial fornece um SpatialGestureRecognizer opcional que pode ser utilizado para ativar gestos compostos criados sobre o evento "select". Ao encaminhar as interações do SpatialInteractionManager para o SpatialGestureRecognizer de um holograma, as aplicações podem detetar eventos de Toque, Suspensão, Manipulação e Navegação uniformemente entre as mãos, a voz e os dispositivos de entrada espacial, sem ter de processar premições e versões manualmente.

SpatialGestureRecognizer faz apenas a desambiguação mínima entre o conjunto de gestos que pede. Por exemplo, se pedir apenas Toque, o utilizador poderá manter o dedo premido enquanto quiser e ainda ocorrerá um Toque. Se pedir Toque e Suspender, após cerca de um segundo de premir o dedo, o gesto promoverá a suspensão e deixará de ocorrer um Toque.

Para utilizar SpatialGestureRecognizer, processe o evento InteractionDetected do SpatialInteractionManager e agarre o SpatialPointerPose aí exposto. Utilize o raio de olhar da cabeça do utilizador a partir desta pose para se intersectar com os hologramas e as malhas de superfície no ambiente do utilizador para determinar com o que o utilizador pretende interagir. Em seguida, encaminhe a SpatialInteraction nos argumentos do evento para o SpatialGestureRecognizer do holograma de destino, utilizando o método CaptureInteraction . Isto 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), as interações e gestos devem derivar o respetivo alvo do olhar da cabeça do utilizador, em vez de compor ou interagir na localização da mão. Assim que uma interação for iniciada, podem ser utilizados movimentos relativos da mão para controlar o gesto, tal como acontece com o gesto de Manipulação ou Navegação.

Ver também