Controladores de mãos e emovimento no DirectX
Observação
Este artigo refere-se às APIs nativas do WinRT herdadas. Para novos projetos de aplicativo nativo, é recomendável usar a API OpenXR.
Em Windows Mixed Reality, a entrada do controlador de movimento e mão é manipulada por meio das APIs de entrada espaciais, encontradas no Windows. UI. Namespace Input.Spatial. Isso permite que você lide facilmente com ações comuns, como Selecionar, pressiona da mesma maneira em ambas as mãos e controladores de movimento.
Introdução
Para acessar a entrada espacial em 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.
- A mão representa a mão detectada pelo usuário. Fontes manuais oferecem recursos diferentes com base no dispositivo, desde gestos básicos em HoloLens até acompanhamento manual totalmente articulado no HoloLens 2.
- O controlador representa um controlador de movimento emparelhado. Os controladores de movimento podem oferecer diferentes funcionalidades, por exemplo, selecionar gatilhos, botões menu, botões de garra, touchpads e polegares.
- A voz representa as palavras-chave detectadas pelo sistema de voz do usuário. Por exemplo, essa fonte 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 uma série de 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 aconteceu. 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 selecionar 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 imprensa "Selecionar", que corresponde à ação primária no dispositivo. Exemplos incluem fazer um AirTap em HoloLens ou puxar o gatilho em um controlador de movimento. As prensas '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 controlada e outro para fala se um comando "select" foi pronunciado recentemente. Em seguida, você pode inspecionar as propriedades em cada SpatialInteractionSourceState para conduzir a entrada em seu aplicativo.
Veja um exemplo de como verificar 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 a quadro. As mãos obtêm uma nova ID toda vez 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 pagos.
Poses previstas versus históricas
GetDetectedSourcesAtTimestamp tem um parâmetro de carimbo de data/hora. Isso permite que você solicite dados de estado e pose previstos ou históricos, permitindo correlacionar 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 manual 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 até Bluetooth para 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 para ele. Quando seu aplicativo pesquisa uma alteração de estado, as poses de cabeça e mão usadas para direcionar essa interação realmente aconteceram 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 ter 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 do TryGetPointerPose, juntamente com um carimbo de data/hora histórico que você pode passar para outras APIs para se correlacionar com esse evento.
Isso leva às seguintes práticas recomendadas ao renderizar e direcionar com mãos e controladores em 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 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 lidar com 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 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 gama de recursos. Vários desses recursos são comuns entre os tipos de dispositivo. Por exemplo, controle manual e 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 de HoloLens(1ª geração) | Controladores de movimento | Mãos Articuladas |
---|---|---|---|---|
SpatialInteractionSource::Handedness | Braço direito ou esquerdo/controlador. | Sem suporte | Com suporte | Com suporte |
SpatialInteractionSourceState::IsSelectPressed | Estado atual do botão primário. | Toque no Ar | Gatilho | Toque de Ar Relaxado (pinça vertical) |
SpatialInteractionSourceState::IsGrasped | Estado atual do botão de captura. | Sem suporte | Botão Pegar | Beliscar 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 de aperto no controlador. | Local da palma | Posição da pose de aperto | Local da palma |
SpatialInteractionSourceLocation::Orientation | Quatérnion que representa a orientação da mão ou da pose de aperto no controlador. | Sem suporte | Orientação da pose de aperto | Orientação palmeirense |
SpatialPointerInteractionSourcePose::Position | Origem do raio apontando. | Sem suporte | Com suporte | Com suporte |
SpatialPointerInteractionSourcePose::ForwardDirection | Direção do raio apontando. | 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 aperto vs. pose apontando
Windows Mixed Reality dá suporte a controladores de movimento em diferentes fatores de forma. Ele também dá suporte a sistemas de acompanhamento manual articulados. Todos esses sistemas têm relações diferentes entre a posição da mão e a direção "forward" natural 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 manual e controladores de movimento. A primeira é a pose de aperto, que representa a posição da mão do usuário. A segunda é a pose de apontamento, que representa um raio apontando originário da mão ou controlador do usuário. Portanto, se você quiser renderizar a mão do usuário ou um objeto na mão do usuário, como uma espada ou uma arma, use a pose de aperto. Se você quiser fazer 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 de aperto: o centroide da palma ao manter o controlador naturalmente, ajustado para a esquerda ou direita para centralizar a posição dentro da alça.
- O 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 esquerda, para trás da palma direita)
- O eixo Forward 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 Up da orientação de aderência: o eixo Para cima implícito pelas definições Right e Forward.
Você pode acessar a pose de ponteiro por meio 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 recursos adicionais.
- HasThumbstick: Se for verdadeiro, o controlador tem um 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 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 usuário está tocando no bloco (IsTouchpadTouched) e se está pressionando o touchpad para baixo (IsTouchpadPressed).
- SimpleHapticsController: A API SimpleHapticsController para o controlador permite inspecionar os recursos hápticos do controlador e também 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 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 correlaciona-se com IsSelectPressed sendo igual a false.
Acompanhamento de mão articulado
A API Windows Mixed Reality fornece suporte total para o acompanhamento de mão articulado, por exemplo, no HoloLens 2. O acompanhamento manual articulado pode ser usado para implementar modelos de entrada de manipulação direta e ponto e confirmação 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 os dedos index/médio/anel/pequeno, quatro articulações para o polegar e uma articulação de pulso. A articulação do pulso serve como 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 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 filho é 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á na próxima articulação na hierarquia, a junção "Index Intermediate".
Além das 25 articulações hierárquicas, o sistema fornece uma junção de palma. A palma normalmente não é considerada parte da estrutura esquelética. Ele é fornecido apenas como uma maneira conveniente de obter a posição geral e a orientação da mão.
As seguintes informações são fornecidas para cada articulação:
Nome | Descrição |
---|---|
Posição | Posição 3D da articulação, disponível em qualquer sistema de coordenadas solicitado. |
Orientação | 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 sobre o quão confiante o sistema se sente 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 conjuntos atuais chamando TryGetJoint, com o nome da junta na qual 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 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 de visualização e física avançada. Para acessar a malha manual, primeiro você precisa criar um objeto HandMeshObserver chamando TryCreateHandMeshObserverAsync no SpatialInteractionSource. Isso só precisa ser feito uma vez por origem, 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. Depois de disponível, você pode solicitar o objeto HandMeshObserver para o buffer de índice 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 assim que o observador de malha estiver disponível. Ele 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 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á segurá-lo durante a duração 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 do HandPose que representa a pose para a qual você deseja vertices. 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 manual. 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
}
}
Em contraste com as articulações do 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 em 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, Espera, Manipulação e Navegação uniformemente entre mãos, voz e dispositivos de entrada espaciais, sem precisar lidar com pressionamentos e versões manualmente.
SpatialGestureRecognizer faz apenas a desambiguação mínima entre o conjunto de gestos que você solicita. Por exemplo, se você solicitar apenas Toque, o usuário poderá segurar o dedo enquanto quiser e um toque ainda ocorrerá. Se você solicitar toque e espera, 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 a 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 SpatialGestureSettings definido nesse reconhecedor no momento da criação ou por TrySetGestureSettings.
Em HoloLens (primeira geração), interações e gestos devem derivar seu direcionamento do olhar de cabeça do usuário, em vez de renderizar ou interagir no local da mão. Depois que uma interação for iniciada, os movimentos relativos da mão poderão ser usados para controlar o gesto, como com o gesto de Manipulação ou Navegação.