Manos y controladores de movimiento en DirectX
Nota:
Este artículo se relaciona con las API nativas heredadas de WinRT. En el caso de los nuevos proyectos de aplicaciones nativas, se recomienda usar la API de OpenXR.
En Windows Mixed Reality, la entrada del controlador de movimiento y la mano se controla a través de las API de entrada espacial, que se encuentran en el espacio de nombres Windows.UI.Input.Spatial. Esto le permite controlar fácilmente acciones comunes como Seleccionar presiona de la misma manera entre las manos y los controladores de movimiento.
Introducción
Para acceder a la entrada espacial en Windows Mixed Reality, comience con la interfaz SpatialInteractionManager. Puedes acceder a esta interfaz llamando a SpatialInteractionManager::GetForCurrentView, normalmente en algún momento durante el inicio de la aplicación.
using namespace winrt::Windows::UI::Input::Spatial;
SpatialInteractionManager interactionManager = SpatialInteractionManager::GetForCurrentView();
El trabajo de SpatialInteractionManager es proporcionar acceso a SpatialInteractionSources, que representa un origen de entrada. Hay tres tipos de SpatialInteractionSources disponibles en el sistema.
- La mano representa la mano detectada de un usuario. Los orígenes de mano ofrecen diferentes características basadas en el dispositivo, desde gestos básicos en HoloLens hasta un seguimiento de manos totalmente articulado en HoloLens 2.
- El controlador representa un controlador de movimiento emparejado. Los controladores de movimiento pueden ofrecer diferentes funcionalidades, por ejemplo, seleccionar desencadenadores, botones de menú, botones de agarre, paneles táctiles y sticks digitales.
- Voz representa las palabras clave detectadas por el sistema de voz del usuario. Por ejemplo, este origen insertará una tecla Select press y release cada vez que el usuario diga "Select".
Los datos por fotograma de un origen se representan mediante la interfaz SpatialInteractionSourceState . Hay dos maneras diferentes de acceder a estos datos, en función de si desea usar un modelo basado en eventos o basado en sondeo en la aplicación.
Entrada controlada por eventos
SpatialInteractionManager proporciona una serie de eventos que la aplicación puede escuchar. Algunos ejemplos son SourcePressed, [SourceReleased y SourceUpdated.
Por ejemplo, el código siguiente enlaza un controlador de eventos denominado MyApp::OnSourcePressed al evento SourcePressed. Esto permite que la aplicación detecte presiones en cualquier tipo de origen de interacción.
using namespace winrt::Windows::UI::Input::Spatial;
auto interactionManager = SpatialInteractionManager::GetForCurrentView();
interactionManager.SourcePressed({ this, &MyApp::OnSourcePressed });
Este evento presionado se envía a la aplicación de forma asincrónica, junto con el spatialInteractionSourceState correspondiente en el momento en que se produjo la prensa. Es posible que la aplicación o el motor de juegos quieran iniciar el procesamiento inmediatamente o poner en cola los datos de eventos en la rutina de procesamiento de entrada. Esta es una función de controlador de eventos para el evento SourcePressed, que comprueba si se ha presionado el botón de selección.
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
}
}
El código anterior solo comprueba la pulsación "Seleccionar", que corresponde a la acción principal del dispositivo. Entre los ejemplos se incluyen realizar airtap en HoloLens o extraer el desencadenador en un controlador de movimiento. "Seleccionar" presiona la intención del usuario de activar el holograma al que se dirige. El evento SourcePressed se activará para varios botones y gestos diferentes, y puede inspeccionar otras propiedades en SpatialInteractionSource para probar esos casos.
Entrada basada en sondeo
También puede usar SpatialInteractionManager para sondear el estado actual de entrada de cada fotograma. Para ello, llame a GetDetectedSourcesAtTimestamp cada fotograma. Esta función devuelve una matriz que contiene un SpatialInteractionSourceState para cada SpatialInteractionSource activo. Esto significa que uno para cada controlador de movimiento activo, uno para cada mano con seguimiento y otro para voz si se ha pronunciado recientemente un comando "select". A continuación, puede inspeccionar las propiedades de cada SpatialInteractionSourceState para controlar la entrada en la aplicación.
Este es un ejemplo de cómo comprobar la acción "seleccionar" mediante el método de sondeo. La variable de predicción representa un objeto HolographicFramePrediction , que se puede obtener de 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 tiene un identificador, que puede usar para identificar nuevos orígenes y correlacionar los orígenes existentes de marco a fotograma. Las manos obtienen un nuevo identificador cada vez que salen y escriben el FOV, pero los identificadores de controlador permanecen estáticos durante la sesión. Puede usar los eventos en SpatialInteractionManager, como SourceDetected y SourceLost, para reaccionar cuando las manos entran o dejan la vista del dispositivo, o cuando los controladores de movimiento están activados o desactivados o están emparejados o no emparejados.
Posturas predichos frente a históricas
GetDetectedSourcesAtTimestamp tiene un parámetro timestamp. Esto le permite solicitar el estado y plantear datos que se predicen o históricos, lo que le permite correlacionar las interacciones espaciales con otros orígenes de entrada. Por ejemplo, al representar la posición de la mano en el marco actual, puede pasar la marca de tiempo prevista proporcionada por holographicFrame. Esto permite que el sistema prediga hacia delante la posición de la mano para alinearse estrechamente con la salida del marco representado, lo que minimiza la latencia percibido.
Sin embargo, esta posición predicho no produce un rayo señalador ideal para el destino con un origen de interacción. Por ejemplo, cuando se presiona un botón de controlador de movimiento, puede tardar hasta 20 ms en propagarse a través de Bluetooth al sistema operativo. De forma similar, después de que un usuario realice un gesto de mano, es posible que pase algún tiempo antes de que el sistema detecte el gesto y la aplicación, a continuación, sondee por él. En el momento en que la aplicación sondea un cambio de estado, la cabeza y las posturas de mano usadas para dirigirse a esa interacción se produjo realmente en el pasado. Si se dirige pasando la marca de tiempo de HolographicFrame actual a GetDetectedSourcesAtTimestamp, la posición se reenviará al rayo de destino en el momento en que se mostrará el fotograma, que podría ser superior a 20 ms en el futuro. Esta posición futura es buena para representar el origen de interacción, pero combina nuestro problema de tiempo para dirigir la interacción, ya que la selección de destino del usuario se produjo en el pasado.
Afortunadamente, los eventos SourcePressed, [SourceReleased y SourceUpdated proporcionan el estado histórico asociado a cada evento de entrada. Esto incluye directamente las posturas históricas de encabezado y mano disponibles a través de TryGetPointerPose, junto con una marca de tiempo histórica que puede pasar a otras API para correlacionar con este evento.
Esto conduce a los siguientes procedimientos recomendados al representar y dirigir con manos y controladores cada fotograma:
- Para representar cada fotograma, la aplicación debe sondear la posición prevista hacia delante de cada origen de interacción en el momento de fotones del fotograma actual. Puede sondear todos los orígenes de interacción llamando a GetDetectedSourcesAtTimestamp cada fotograma, pasando la marca de tiempo prevista proporcionada por HolographicFrame::CurrentPrediction.
- En el caso de los controladores o manos dirigidos a una prensa o liberación, la aplicación debe controlar los eventos presionados o liberados, raycasting en función de la posición histórica de la cabeza o mano para ese evento. Para obtener este rayo de destino, controle el evento SourcePressed o SourceReleased , obtenga la propiedad State de los argumentos del evento y, a continuación, llame a su método TryGetPointerPose .
Propiedades de entrada entre dispositivos
La API SpatialInteractionSource admite controladores y sistemas de seguimiento de manos con una amplia gama de funcionalidades. Varias de estas funcionalidades son comunes entre los tipos de dispositivo. Por ejemplo, el seguimiento de manos y los controladores de movimiento proporcionan una acción "select" y una posición 3D. Siempre que sea posible, la API asigna estas funcionalidades comunes a las mismas propiedades en SpatialInteractionSource. Esto permite a las aplicaciones admitir más fácilmente una amplia gama de tipos de entrada. En la tabla siguiente se describen las propiedades que se admiten y cómo se comparan entre los tipos de entrada.
Propiedad | Descripción | Gestos de HoloLens(1.ª generación) | Controladores de movimiento | Manos articuladas |
---|---|---|---|---|
SpatialInteractionSource::Handedness | Controlador o mano derecha o izquierda. | No compatible | Compatible | Compatible |
SpatialInteractionSourceState::IsSelectPressed | Estado actual del botón principal. | Pulsación de aire | Desencadenador | Pulsación de aire relajado (pellizcar vertical) |
SpatialInteractionSourceState::IsGrasped | Estado actual del botón de toma. | No compatible | Botón Agarrar | Pellizcar o cerrar la mano |
SpatialInteractionSourceState::IsMenuPressed | Estado actual del botón de menú. | No compatible | Botón de menú | No compatible |
SpatialInteractionSourceLocation::Position | Ubicación XYZ de la posición de mano o agarre en el controlador. | Ubicación de Palm | Posición de posición de posición de agarre | Ubicación de Palm |
SpatialInteractionSourceLocation::Orientation | Cuaternión que representa la orientación de la mano o posición de agarre en el controlador. | No compatible | Orientación de posición de agarre | Orientación de la palma |
SpatialPointerInteractionSourcePose::Position | Origen del rayo señalador. | No compatible | Compatible | Compatible |
SpatialPointerInteractionSourcePose::ForwardDirection | Dirección del rayo señalador. | No compatible | Compatible | Compatible |
Algunas de las propiedades anteriores no están disponibles en todos los dispositivos y la API proporciona un medio para probar esto. Por ejemplo, puede inspeccionar la propiedad SpatialInteractionSource::IsGraspSupported para determinar si el origen proporciona una acción de comprensión.
Posición de agarre frente a posición apuntada
Windows Mixed Reality admite controladores de movimiento en diferentes factores de forma. También admite sistemas de seguimiento de manos articulados. Todos estos sistemas tienen relaciones diferentes entre la posición de la mano y la dirección "hacia delante" natural que las aplicaciones deben usar para apuntar o representar objetos en la mano del usuario. Para admitir todo esto, hay dos tipos de posturas 3D proporcionadas para el seguimiento de la mano y los controladores de movimiento. La primera es la posición de agarre, que representa la posición de la mano del usuario. La segunda es la posición señalador, que representa un rayo señalador que se origina en la mano o controlador del usuario. Por lo tanto, si desea representar la mano del usuario o un objeto mantenido en la mano del usuario, como una espada o un arma, use la posición de agarre. Si desea hacer raycast desde el controlador o la mano, por ejemplo, cuando el usuario está **apuntando a la interfaz de usuario, use la posición apuntada.
Puede acceder a la posición de agarre a través de SpatialInteractionSourceState::P roperties::TryGetLocation(...). Se define de la siguiente manera:
- Posición de agarre: el centroide de la palma cuando mantiene el controlador naturalmente, ajustado a la izquierda o derecha para centrar la posición dentro del agarre.
- Eje derecho de la orientación de agarre: cuando abres completamente la mano para formar una posición plana de 5 dedos, el rayo normal a la palma (hacia delante de la palma izquierda, hacia atrás de la palma derecha)
- Eje hacia delante de la orientación de agarre: al cerrar la mano parcialmente (como si sostenía el controlador), el rayo que apunta "hacia delante" a través del tubo formado por los dedos no pulgares.
- Eje hacia arriba de la orientación de agarre: eje Hacia arriba implícito en las definiciones Derecha y Adelante.
Puede acceder a la posición del puntero a través de SpatialInteractionSourceState::P roperties::TryGetLocation(...)::SourcePointerPose o SpatialInteractionSourceState::TryGetPointerPose(...)::TryGetInteractionSourcePose.
Propiedades de entrada específicas del controlador
En el caso de los controladores, SpatialInteractionSource tiene una propiedad Controller con funcionalidades adicionales.
- HasThumbstick: Si es true, el controlador tiene un stick analógico. Inspeccione la propiedad ControllerProperties de SpatialInteractionSourceState para adquirir los valores x e y (ThumbstickX y ThumbstickY), así como su estado presionado (IsThumbstickPressed).
- HasTouchpad: Si es true, el controlador tiene un panel táctil. Inspeccione la propiedad ControllerProperties de SpatialInteractionSourceState para adquirir los valores x e y del panel táctil (TouchpadX y TouchpadY) y para saber si el usuario está tocando el panel (IsTouchpadTouched) y si presiona el panel táctil hacia abajo (IsTouchpadPressed).
- SimpleHapticsController: La API SimpleHapticsController para el controlador permite inspeccionar las funcionalidades hápticas del controlador y también permite controlar los comentarios hápticos.
El intervalo para el panel táctil y el stick analógico es -1 a 1 para ambos ejes (de abajo a arriba y de izquierda a derecha). El intervalo del desencadenador analógico, al que se tiene acceso mediante la propiedad SpatialInteractionSourceState::SelectPressedValue, tiene un intervalo de 0 a 1. Un valor de 1 se correlaciona con IsSelectPressed siendo igual a true; cualquier otro valor se correlaciona con IsSelectPressed es igual a false.
Seguimiento de manos articulado
La API de Windows Mixed Reality proporciona compatibilidad completa con el seguimiento de manos articulado, por ejemplo, en HoloLens 2. El seguimiento de manos articulado se puede usar para implementar modelos de entrada de punto y confirmación directas y de manipulación en las aplicaciones. También se puede usar para crear interacciones totalmente personalizadas.
Esqueleto de mano
El seguimiento de manos articulado proporciona un esqueleto de 25 articulaciones que permite muchos tipos diferentes de interacciones. El esqueleto proporciona cinco articulaciones para los dedos índice, central, anillo y pequeño, cuatro articulaciones para el pulgar y una articulación de muñeca. La articulación de la muñeca sirve como base de la jerarquía. En la imagen siguiente se muestra el diseño del esqueleto.
En la mayoría de los casos, cada articulación se denomina en función del hueso que representa. Dado que hay dos huesos en cada articulación, usamos una convención de nomenclatura de cada articulación basada en el hueso secundario en esa ubicación. El hueso secundario se define como el hueso más allá de la muñeca. Por ejemplo, la articulación "Index Proximal" contiene la posición inicial del hueso proximal del índice y la orientación de ese hueso. No contiene la posición final del hueso. Si lo necesita, lo obtendría de la siguiente articulación de la jerarquía, la articulación "Index Intermediate".
Además de las 25 articulaciones jerárquicas, el sistema proporciona una articulación de palma. La palma no suele considerarse parte de la estructura esquelética. Se proporciona solo como una manera cómoda de obtener la posición y la orientación generales de la mano.
Se proporciona la siguiente información para cada articulación:
Nombre | Descripción |
---|---|
Posición | Posición 3D de la articulación, disponible en cualquier sistema de coordenadas solicitado. |
Orientación | Orientación 3D del hueso, disponible en cualquier sistema de coordenadas solicitado. |
Radio | Distancia a la superficie de la piel en la posición de la articulación. Resulta útil para optimizar las interacciones directas o las visualizaciones que se basan en el ancho del dedo. |
Precisión | Proporciona una sugerencia sobre la confianza que siente el sistema sobre la información de esta articulación. |
Puede acceder a los datos del esqueleto de la mano a través de una función en SpatialInteractionSourceState. La función se denomina TryGetHandPose y devuelve un objeto denominado HandPose. Si el origen no admite manos articuladas, esta función devolverá null. Una vez que tenga un HandPose, puede obtener los datos conjuntos actuales llamando a TryGetJoint, con el nombre de la articulación que le interesa. Los datos se devuelven como una estructura JointPose . El código siguiente obtiene la posición de la punta del dedo índice. La variable currentState representa una instancia 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
}
}
Malla de mano
La API de seguimiento de manos articulada permite una malla de mano de triángulo totalmente deformable. Esta malla puede deformar en tiempo real junto con el esqueleto de la mano, y es útil para las técnicas de visualización y física avanzada. Para acceder a la malla de mano, primero debe crear un objeto HandMeshObserver mediante una llamada a TryCreateHandMeshObserverAsync en SpatialInteractionSource. Esto solo debe realizarse una vez por origen, normalmente la primera vez que lo vea. Esto significa que llamará a esta función para crear un objeto HandMeshObserver cada vez que una mano entra en el FOV. Se trata de una función asincrónica, por lo que tendrá que tratar con un poco de simultaneidad aquí. Una vez disponible, puede pedir al objeto HandMeshObserver el búfer de índice de triángulos llamando a GetTriangleIndices. Los índices no cambian el marco sobre el marco, por lo que puede obtenerlos una vez y almacenarlos en caché durante la vigencia del origen. Los índices se proporcionan en orden de viento en sentido de las agujas del reloj.
El código siguiente pone en marcha un std::thread separado para crear el observador de malla y extrae el búfer de índice una vez que el observador de malla está disponible. Se inicia a partir de una variable denominada currentState, que es una instancia de SpatialInteractionSourceState que representa una mano con seguimiento.
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 un subproceso desasociado es solo una opción para controlar las llamadas asincrónicas. Como alternativa, puede usar la nueva funcionalidad de co_await compatible con C++/WinRT.
Una vez que tenga un objeto HandMeshObserver, debe contenerlo mientras esté activo su spatialInteractionSource correspondiente. A continuación, cada fotograma, puede pedirle el búfer de vértices más reciente que representa la mano llamando a GetVertexStateForPose y pasando una instancia handPose que representa la posición para la que desea vértices. Cada vértice del búfer tiene una posición y una normal. Este es un ejemplo de cómo obtener el conjunto actual de vértices para una malla de mano. Como antes, la variable currentState representa una instancia 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
}
}
A diferencia de las articulaciones maestras, la API de malla de mano no permite especificar un sistema de coordenadas para los vértices. En su lugar, HandMeshVertexState especifica el sistema de coordenadas en el que se proporcionan los vértices. Después, puede obtener una transformación de malla llamando a TryGetTransformTo y especificando el sistema de coordenadas que desee. Deberá usar esta transformación de malla cada vez que trabaje con los vértices. Este enfoque reduce la sobrecarga de CPU, especialmente si solo usa la malla con fines de representación.
Gestos compuestos de mirada y confirmación
En el caso de las aplicaciones que usan el modelo de entrada de mirada y confirmación, especialmente en HoloLens (primera generación), la API de entrada espacial proporciona un spatialGestureRecognizer opcional que se puede usar para habilitar gestos compuestos basados en el evento "select". Al enrutar las interacciones desde SpatialInteractionManager a spatialGestureRecognizer de un holograma, las aplicaciones pueden detectar eventos Tap, Hold, Manipulation y Navigation uniformemente entre manos, voz y dispositivos de entrada espacial, sin tener que controlar las presiones y las versiones manualmente.
SpatialGestureRecognizer solo realiza la desambiguación mínima entre el conjunto de gestos que solicita. Por ejemplo, si solicita solo Pulsar, el usuario puede mantener el dedo hacia abajo siempre que quiera y se seguirá produciendo una pulsación. Si solicita pulsar y mantener pulsado, después de aproximadamente un segundo de mantener presionado el dedo, el gesto se promoverá a una suspensión y ya no se producirá una pulsación.
Para usar SpatialGestureRecognizer, controle el evento InteractionDetected de SpatialInteractionManager y tome el SpatialPointerPose expuesto allí. Use el rayo de mirada con la cabeza del usuario desde esta posición para intersecr con los hologramas y las mallas de superficie en el entorno del usuario para determinar con qué pretende interactuar el usuario. A continuación, enrute spatialInteraction en los argumentos de evento al spatialGestureRecognizer del holograma de destino mediante su método CaptureInteraction . Esto comienza a interpretar esa interacción según spatialGestureSettings establecido en ese reconocedor en el momento de la creación o por TrySetGestureSettings.
En HoloLens (primera generación), las interacciones y los gestos deben derivar su destino de la mirada en la cabeza del usuario, en lugar de representar o interactuar en la ubicación de la mano. Una vez iniciada una interacción, se pueden usar movimientos relativos de la mano para controlar el gesto, como con el gesto Manipulación o Navegación.