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