Compartir a través de


Controladores de manos y movimiento en DirectX

Nota:

Este artículo está relacionado con las API nativas de WinRT heredadas. Para nuevos proyectos de aplicaciones nativas, se recomienda usar la API de OpenXR.

En Windows Mixed Reality, la entrada del controlador de mano y movimiento 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 a través de las manos y los controladores de movimiento.

Introducción

Para acceder a la entrada espacial en Windows Mixed Reality, comience con la interfaz SpatialInteractionManager. Para acceder a esta interfaz, llame a SpatialInteractionManager::GetForCurrentView, normalmente durante el inicio de la aplicación.

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

SpatialInteractionManager interactionManager = SpatialInteractionManager::GetForCurrentView();

El trabajo de SpatialInteractionManager consiste en proporcionar acceso a SpatialInteractionSources, que representan un origen de entrada. Hay tres tipos de SpatialInteractionSources disponibles en el sistema.

  • Hand representa la mano detectada por un usuario. Los orígenes de manos ofrecen diferentes características basadas en el dispositivo, que van desde gestos básicos en HoloLens hasta 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, controladores táctiles y sticks.
  • Voz representa las palabras clave detectadas por el sistema de voz del usuario. Por ejemplo, este origen insertará una tecla Seleccionar y liberar cada vez que el usuario diga "Seleccionar".

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 sondeos 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 pulsación. Es posible que la aplicación o el motor de juegos quieran iniciar el procesamiento de inmediato o poner en cola los datos del evento 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 seleccionar.

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 tecla "Seleccionar", que corresponde a la acción principal en el dispositivo. Algunos ejemplos son la realización de un AirTap en HoloLens o la extracción del desencadenador en un controlador de movimiento. Las presiones "Seleccionar" representan la intención del usuario de activar el holograma al que se dirige. El evento SourcePressed se desencadenará para una serie de 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 uno para cada controlador de movimiento activo, uno para cada mano de seguimiento y otra para voz si recientemente se ha pronunciado un comando "select". A continuación, puede inspeccionar las propiedades de cada SpatialInteractionSourceState para impulsar 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 un marco a otro. Las manos obtienen un nuevo identificador cada vez que salen y entran en el FOV, pero los identificadores del 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 salen de la vista del dispositivo, o cuando los controladores de movimiento están activados o desactivados o están emparejados o sin asignar.

Posturas previstas frente a históricas

GetDetectedSourcesAtTimestamp tiene un parámetro timestamp. Esto le permite solicitar el estado y plantear datos que se predicen o son 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 al sistema predecir hacia delante la posición de la mano para alinearse estrechamente con la salida del marco representado, lo que minimiza la latencia percibida.

Sin embargo, esta pose predicho no genera un rayo de apuntamiento ideal para el destino con un origen de interacción. Por ejemplo, cuando se presiona un botón de controlador de movimiento, ese evento 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, puede pasar cierto tiempo antes de que el sistema detecte el gesto y la aplicación sondee por él. En el momento en que la aplicación sondea un cambio de estado, la posición de la cabeza y la mano se usa para dirigirse a esa interacción que realmente ocurrió en el pasado. Si tiene como destino pasar la marca de tiempo actual de HolographicFrame 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 agrava nuestro problema de tiempo para dirigir la interacción, ya que la segmentación 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 de cabeza y mano históricas disponibles a través de TryGetPointerPose, junto con una marca de tiempo histórica que puede pasar a otras API para correlacionarse con este evento.

Esto conduce a los siguientes procedimientos recomendados al representar y seleccionar como destino con manos y controladores cada fotograma:

  • Para la representación manual/controlador de cada fotograma, la aplicación debe sondear la posición pronosticada hacia delante de cada origen de interacción en el tiempo 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 que la mano o el controlador se dirijan a una prensa o una versión, la aplicación debe controlar los eventos presionados o publicados, la difusión por rayos en función de la posición histórica de la cabeza o la 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

SpatialInteractionSource API admite controladores y sistemas de seguimiento manual 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 "seleccionar" y una posición 3D. Siempre que sea posible, la API asigna estas funcionalidades comunes a las mismas propiedades en SpatialInteractionSource. Esto permite que las aplicaciones admitan 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 Mano derecha o izquierda/controlador. No compatible Compatible Compatible
SpatialInteractionSourceState::IsSelectPressed Estado actual del botón principal. Pulsación de aire Trigger Toque de aire relajado (pizca vertical)
SpatialInteractionSourceState::IsGrasped Estado actual del botón de captura. No compatible Botón Agarrar Pellizcar o cerrar la mano
SpatialInteractionSourceState::IsMenuPressed Estado actual del botón de menú. No compatible Botón Menú No compatible
SpatialInteractionSourceLocation::Position Ubicación XYZ de la posición de la mano o del agarre en el controlador. Ubicación de la palma Posición de posición de agarre Ubicación de la palma
SpatialInteractionSourceLocation::Orientation Cuaternión que representa la orientación de la posición de mano o de agarre en el controlador. No compatible Orientación de la 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.

Pose de pinzamiento frente a pose de apuntamiento

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 natural "hacia delante" que las aplicaciones deben usar para señalar o representar objetos en la mano del usuario. Para admitir todo esto, hay dos tipos de poses 3D proporcionadas para el seguimiento manual 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 pose de apuntamiento, que representa un rayo de apuntamiento que se origina en la mano o el controlador del usuario. Por lo tanto, si desea representar la mano del usuario o un objeto en la mano del usuario, como una espada o un arma, use la postura de agarre. Si desea raycast desde el controlador o la mano, por ejemplo, cuando el usuario **apunta a la interfaz de usuario, use la posición de apuntando.

Puede acceder a la posición de control a través de SpatialInteractionSourceState::P roperties::TryGetLocation(...). Se define de la siguiente manera:

  • La posición del agarre: el centroide de la palma al sostener el controlador de forma natural, se ajusta a la izquierda o a la derecha para centrar la posición dentro del control.
  • Eje derecho de la orientación del agarre: cuando abres completamente la mano para formar una postura plana de 5 dedos, el rayo que es normal a tu palma (hacia delante de la palma izquierda, hacia atrás desde la palma derecha)
  • Eje hacia delante de la orientación del agarre: cuando cierras la mano parcialmente (como si sujetas el controlador), el rayo que apunta "hacia delante" a través del tubo formado por los dedos que no son pulgares.
  • Eje hacia arriba de la orientación del pinzamiento: eje hacia arriba implícito en las definiciones Derecha y Avance.

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 de pulgar. Inspeccione la propiedad ControllerProperties de SpatialInteractionSourceState para adquirir los valores de thumbstick 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 rango para touchpad y thumbstick es de -1 a 1 para ambos ejes (de abajo a arriba, y de izquierda a derecha). El intervalo del desencadenador analógico, al que se accede 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 siendo igual a false.

Seguimiento manual articulado

La API de Windows Mixed Reality proporciona compatibilidad completa con el seguimiento manual articulado, por ejemplo, en HoloLens 2. El seguimiento manual articulado se puede usar para implementar modelos de entrada de manipulación directa y de punto y confirmación en las aplicaciones. También se puede usar para crear interacciones totalmente personalizadas.

Esqueleto de la mano

El seguimiento articulado de manos proporciona un esqueleto articular de 25 que permite muchos tipos diferentes de interacciones. El esqueleto proporciona cinco articulaciones para el índice, el medio, el anillo o los dedos pequeños, cuatro articulaciones para el pulgar y una articulación de muñeca. La articulación de la muñeca actúa como base de la jerarquía. En la siguiente imagen se muestra el diseño del esqueleto.

Esqueleto de mano

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 infantil se define como el hueso más alejado de la muñeca. Por ejemplo, la articulación "Index Próximal" 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 unión de la jerarquía, la unión "Index Intermediate".

Además de las 25 juntas jerárquicas, el sistema proporciona una articulación de palma. La palma no suele considerarse parte de la estructura esquelética. Solo se proporciona como una manera cómoda de obtener la posición general y la orientación de la mano.

Se proporciona la siguiente información para cada articulación:

Nombre Descripción
Position Posición 3D de la articulación, disponible en cualquier sistema de coordenadas solicitado.
Orientation 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. Útil para ajustar interacciones directas o visualizaciones que dependen del ancho del dedo.
Precisión Proporciona una sugerencia sobre la confianza que el sistema siente sobre la información de esta articulación.

Puede acceder a los datos del esqueleto de 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 se puede deformar en tiempo real junto con el esqueleto de la mano y es útil para la visualización y las técnicas de física avanzadas. Para acceder a la malla manual, primero debe crear un objeto HandMeshObserver llamando a TryCreateHandMeshObserverAsync en SpatialInteractionSource. Esto solo debe realizarse una vez por origen, normalmente la primera vez que lo ve. Esto significa que llamará a esta función para crear un objeto HandMeshObserver cada vez que una mano entre 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 solicitar al objeto HandMeshObserver el búfer de índice de triángulo 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 duración del origen. Los índices se proporcionan en orden de cuerda en el sentido de las agujas del reloj.

El código siguiente inicia un std::thread desasociado 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 de 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 llamadas asincrónicas. Como alternativa, podría usar la nueva funcionalidad de co_await compatible con C++/WinRT.

Una vez que tenga un objeto HandMeshObserver, debe conservarlo mientras esté activo su spatialInteractionSource correspondiente. A continuación, cada fotograma, puede solicitarle el búfer de vértices más reciente que representa la mano llamando a GetVertexStateForPose y pasando una instancia de HandPose que representa la posición para la que desea vertices. 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 de esqueleto, la API de malla manual 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. A continuación, puede obtener una transformación de malla llamando a TryGetTransformTo y especificando el sistema de coordenadas que desee. Tendrá que usar esta transformación de malla siempre que trabaje con los vértices. Este enfoque reduce la sobrecarga de CPU, especialmente si solo usa la malla con fines de representación.

Mirada y confirmación de gestos compuestos

Para 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 de pulsación, suspensión, manipulación y navegación uniformemente entre dispositivos de entrada de manos, voz y espaciales, sin tener que controlar las presiones y las versiones manualmente.

SpatialGestureRecognizer solo realiza la mínima desambiguación entre el conjunto de gestos que se solicitan. Por ejemplo, si solo solicita pulsar, el usuario puede mantener el dedo hacia abajo mientras lo desee y se seguirá produciendo una pulsación. Si solicitas pulsar y mantener presionados, 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 de la cabeza del usuario desde esta posición para intersecar con los hologramas y las mallas de superficie del 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 el conjunto SpatialGestureSettings establecido en ese reconocedor en el momento de la creación, o bien por TrySetGestureSettings.

En HoloLens (primera generación), las interacciones y los gestos deben derivar su destino de la mirada de 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 de manipulación o navegación.

Vea también