Partager via


Mains et contrôleurs de mouvement dans DirectX

Notes

Cet article concerne les API winRT natives héritées. Pour les nouveaux projets d’application native, nous vous recommandons d’utiliser l’API OpenXR.

Dans Windows Mixed Reality, l’entrée du contrôleur de mouvement et de main est gérée par le biais des API d’entrée spatiale, qui se trouvent dans l’espace de noms Windows.UI.Input.Spatial. Cela vous permet de gérer facilement des actions courantes telles que select appuie de la même façon sur les mains et les contrôleurs de mouvement.

Prise en main

Pour accéder à l’entrée spatiale dans Windows Mixed Reality, commencez par l’interface SpatialInteractionManager. Vous pouvez accéder à cette interface en appelant SpatialInteractionManager::GetForCurrentView, généralement au démarrage de l’application.

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

SpatialInteractionManager interactionManager = SpatialInteractionManager::GetForCurrentView();

Le travail de SpatialInteractionManager consiste à fournir l’accès à SpatialInteractionSources, qui représentent une source d’entrée. Il existe trois types de SpatialInteractionSources disponibles dans le système.

  • Hand représente la main détectée d’un utilisateur. Les sources de main offrent différentes fonctionnalités basées sur l’appareil, allant des mouvements de base sur HoloLens au suivi de main entièrement articulé sur HoloLens 2.
  • Le contrôleur représente un contrôleur de mouvement appairé. Les contrôleurs de mouvement peuvent offrir différentes fonctionnalités, par exemple sélectionner des déclencheurs, des boutons de menu, des boutons Saisir, des pavés tactiles et des manettes.
  • Voix représente les mots clés détectés par le système vocal de l’utilisateur. Par exemple, cette source injecte une sélection de presse et de mise en production chaque fois que l’utilisateur dit « Sélectionner ».

Les données par image d’une source sont représentées par l’interface SpatialInteractionSourceState . Il existe deux façons différentes d’accéder à ces données, selon que vous souhaitez utiliser un modèle basé sur les événements ou basé sur l’interrogation dans votre application.

Entrée pilotée par les événements

SpatialInteractionManager fournit un certain nombre d’événements que votre application peut écouter. SourcePressed, [SourceReleased et SourceUpdated, en voici quelques exemples.

Par exemple, le code suivant connecte un gestionnaire d’événements appelé MyApp::OnSourcePressed à l’événement SourcePressed. Cela permet à votre application de détecter les pressions sur n’importe quel type de source d’interaction.

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

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

Cet événement appuyé est envoyé à votre application de manière asynchrone, ainsi que l’objet SpatialInteractionSourceState correspondant au moment où l’appui s’est produit. Votre application ou moteur de jeu souhaite peut-être commencer le traitement immédiatement ou mettre en file d’attente les données d’événement dans votre routine de traitement d’entrée. Voici une fonction de gestionnaire d’événements pour l’événement SourcePressed, qui vérifie si le bouton de sélection a été appuyé.

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

Le code ci-dessus recherche uniquement l’appui « Sélectionner », qui correspond à l’action principale sur l’appareil. Par exemple, l’exécution d’un AirTap sur HoloLens ou l’extraction du déclencheur sur un contrôleur de mouvement. Les pressions « Sélectionner » représentent l’intention de l’utilisateur d’activer l’hologramme qu’il cible. L’événement SourcePressed se déclenche pour un certain nombre de boutons et de mouvements différents, et vous pouvez inspecter d’autres propriétés sur SpatialInteractionSource pour tester ces cas.

Entrée basée sur l’interrogation

Vous pouvez également utiliser SpatialInteractionManager pour interroger l’état actuel de chaque image d’entrée. Pour ce faire, appelez GetDetectedSourcesAtTimestamp chaque frame. Cette fonction retourne un tableau contenant un SpatialInteractionSourceState pour chaque SpatialInteractionSource actif. Cela signifie un pour chaque contrôleur de mouvement actif, un pour chaque main suivie et un pour la voix si une commande « select » a été récemment prononcée. Vous pouvez ensuite inspecter les propriétés de chaque SpatialInteractionSourceState pour générer une entrée dans votre application.

Voici un exemple de case activée pour l’action « sélectionner » à l’aide de la méthode d’interrogation. La variable de prédiction représente un objet HolographicFramePrediction , qui peut être obtenu à partir de l’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
	}
}

Chaque SpatialInteractionSource a un ID, que vous pouvez utiliser pour identifier de nouvelles sources et mettre en corrélation les sources existantes d’une image à l’autre. Les mains obtiennent un nouvel ID chaque fois qu’elles quittent et entrent dans le FOV, mais les ID de contrôleur restent statiques pendant la durée de la session. Vous pouvez utiliser les événements sur SpatialInteractionManager, tels que SourceDetected et SourceLost, pour réagir lorsque les mains entrent ou quittent la vue de l’appareil, ou lorsque les contrôleurs de mouvement sont activés/désactivés ou sont couplés/non appariés.

Poses prédites et historiques

GetDetectedSourcesAtTimestamp a un paramètre timestamp. Cela vous permet de demander des données d’état et de pose qui sont prédites ou historiques, ce qui vous permet de mettre en corrélation les interactions spatiales avec d’autres sources d’entrée. Par exemple, lors du rendu de la position de la main dans l’image actuelle, vous pouvez passer l’horodatage prédit fourni par l’HolographicFrame. Cela permet au système de prédire à l’avance la position de la main pour qu’elle s’aligne étroitement sur la sortie de l’image rendue, ce qui réduit la latence perçue.

Toutefois, une telle pose prédite ne produit pas un rayon de pointage idéal pour le ciblage avec une source d’interaction. Par exemple, lorsque vous appuyez sur un bouton du contrôleur de mouvement, il peut falloir jusqu’à 20 ms pour que cet événement se propage par le biais du Bluetooth au système d’exploitation. De même, une fois qu’un utilisateur a fait un mouvement de la main, un certain temps peut s’écouler avant que le système détecte le mouvement et que votre application l’interroge ensuite. Au moment où votre application interroge un changement d’état, les postures de tête et de main utilisées pour cibler cette interaction se sont réellement produites dans le passé. Si vous ciblez en passant l’horodatage de votre HolographicFrame actuel à GetDetectedSourcesAtTimestamp, la pose sera plutôt prédite vers le rayon de ciblage au moment où l’image sera affichée, ce qui pourrait être supérieur à 20 ms à l’avenir. Cette pose future est utile pour le rendu de la source d’interaction, mais aggrave notre problème de temps pour cibler l’interaction, car le ciblage de l’utilisateur s’est produit dans le passé.

Heureusement, les événements SourcePressed, [SourceReleased, et SourceUpdated fournissent l’état historique associé à chaque événement d’entrée. Cela inclut directement les poses historiques de tête et de main disponibles via TryGetPointerPose, ainsi qu’un horodatage historique que vous pouvez passer à d’autres API pour mettre en corrélation avec cet événement.

Cela conduit aux meilleures pratiques suivantes lors du rendu et du ciblage avec des mains et des contrôleurs chaque image :

  • Pour le rendu à la main/au contrôleur de chaque image, votre application doit interroger la pose prédite avant de chaque source d’interaction à l’heure du photon de l’image actuelle. Vous pouvez interroger toutes les sources d’interaction en appelant GetDetectedSourcesAtTimestamp chaque image, en passant l’horodatage prédit fourni par HolographicFrame::CurrentPrediction.
  • Pour le ciblage de main/de contrôleur lors d’une presse ou d’une mise en production, votre application doit gérer les événements pressés/libérés, le raycasting en fonction de l’historique de la tête ou de la pose de la main pour cet événement. Vous obtenez ce rayon de ciblage en gérant l’événement SourcePressed ou SourceReleased , en obtenant la propriété State à partir des arguments de l’événement, puis en appelant sa méthode TryGetPointerPose .

Propriétés d’entrée inter-appareils

L’API SpatialInteractionSource prend en charge les contrôleurs et les systèmes de suivi des mains avec un large éventail de fonctionnalités. Un certain nombre de ces fonctionnalités sont courantes entre les types d’appareils. Par exemple, le suivi de la main et les contrôleurs de mouvement fournissent une action de sélection et une position 3D. Dans la mesure du possible, l’API mappe ces fonctionnalités communes aux mêmes propriétés sur SpatialInteractionSource. Cela permet aux applications de prendre en charge plus facilement un large éventail de types d’entrée. Le tableau suivant décrit les propriétés prises en charge et la façon dont elles se comparent entre les types d’entrée.

Propriété Description Mouvements HoloLens(1ère génération) Contrôleurs de mouvement Mains articulées
SpatialInteractionSource::Handedness Main droite ou gauche / contrôleur. Non pris en charge Prise en charge Prise en charge
SpatialInteractionSourceState::IsSelectPressed État actuel du bouton principal. Air Tap Déclencheur Relaxed Air Tap (pincement droit)
SpatialInteractionSourceState::IsGrasped État actuel du bouton de saisie. Non pris en charge Bouton Récupérer Pincement ou main fermée
SpatialInteractionSourceState::IsMenuPressed État actuel du bouton de menu. Non pris en charge Bouton menu Non pris en charge
SpatialInteractionSourceLocation::Position Position xyz de la main ou de la poignée sur le contrôleur. Emplacement de la paume Position de pose de la poignée Emplacement de la paume
SpatialInteractionSourceLocation::Orientation Quaternion représentant l’orientation de la main ou de la poignée sur le contrôleur. Non pris en charge Orientation de la pose de la poignée Orientation de la paume
SpatialPointerInteractionSourcePose::Position Origine du rayon pointant. Non pris en charge Prise en charge Prise en charge
SpatialPointerInteractionSourcePose::ForwardDirection Direction du rayon pointant. Non pris en charge Prise en charge Prise en charge

Certaines des propriétés ci-dessus ne sont pas disponibles sur tous les appareils, et l’API fournit un moyen de le tester. Par exemple, vous pouvez inspecter la propriété SpatialInteractionSource::IsGraspSupported pour déterminer si la source fournit une action de saisie.

Pose de poignée ou pose de pointage

Windows Mixed Reality prend en charge les contrôleurs de mouvement dans différents facteurs de forme. Il prend également en charge les systèmes de suivi des mains articulés. Tous ces systèmes ont des relations différentes entre la position de la main et la direction « avant » naturelle que les applications doivent utiliser pour pointer ou restituer des objets dans la main de l’utilisateur. Pour prendre en charge tout cela, il existe deux types de poses 3D fournies pour le suivi de la main et les contrôleurs de mouvement. La première est la pose de poignée, qui représente la position de la main de l’utilisateur. La seconde est la pose de pointage, qui représente un rayon pointant provenant de la main ou du contrôleur de l’utilisateur. Ainsi, si vous souhaitez rendre la main de l’utilisateur ou un objet tenu dans la main de l’utilisateur, tel qu’une épée ou un pistolet, utilisez la pose de poignée. Si vous souhaitez raycast à partir du contrôleur ou de la main, par exemple lorsque l’utilisateur pointe **vers l’interface utilisateur, utilisez la pose de pointage.

Vous pouvez accéder à la prise en main via SpatialInteractionSourceState::P roperties::TryGetLocation (...). Il est défini comme suit :

  • Position de la poignée : le centroïde de la paume lors de la tenue du contrôleur naturellement, ajusté à gauche ou à droite pour centrer la position à l’intérieur de la poignée.
  • Axe droit de l’orientation de la poignée : Lorsque vous ouvrez complètement votre main pour former une position plate à 5 doigts, le rayon qui est normal à votre paume (avant de la paume gauche, vers l’arrière de la paume droite)
  • Axe avant de l’orientation de la poignée : lorsque vous fermez votre main partiellement (comme si vous teniez le contrôleur), le rayon qui pointe « vers l’avant » à travers le tube formé par vos doigts qui ne sont pas du pouce.
  • Axe haut de l’orientation de la poignée : axe haut impliqué par les définitions Droite et Avant.

Vous pouvez accéder à la pose du pointeur via SpatialInteractionSourceState::P roperties::TryGetLocation(...)::SourcePointerPose ou SpatialInteractionSourceState::TryGetPointerPose(...)::TryGetInteractionSourcePose.

Propriétés d’entrée spécifiques au contrôleur

Pour les contrôleurs, SpatialInteractionSource a une propriété Controller avec des fonctionnalités supplémentaires.

  • HasThumbstick : Si la valeur est true, le contrôleur a une manette. Inspectez la propriété ControllerProperties de SpatialInteractionSourceState pour acquérir les valeurs x et y de la manette (ThumbstickX et ThumbstickY), ainsi que son état enfoncé (IsThumbstickPressed).
  • HasTouchpad : Si la valeur est true, le contrôleur a un pavé tactile. Inspectez la propriété ControllerProperties de SpatialInteractionSourceState pour acquérir les valeurs x et y du pavé tactile (TouchpadX et TouchpadY), et pour savoir si l’utilisateur touche le pavé (IsTouchpadTouched) et s’il appuie sur le pavé tactile (IsTouchpadPressed).
  • SimpleHapticsController : L’API SimpleHapticsController pour le contrôleur vous permet d’inspecter les fonctionnalités haptiques du contrôleur, et vous permet également de contrôler les commentaires haptiques.

La plage du pavé tactile et de la manette est de -1 à 1 pour les deux axes (de bas en haut et de gauche à droite). La plage du déclencheur analogique, accessible à l’aide de la propriété SpatialInteractionSourceState::SelectPressedValue, est comprise entre 0 et 1. La valeur 1 est corrélée avec IsSelectPressed égal à true ; toute autre valeur est corrélée avec IsSelectPressed égal à false.

Suivi de la main articulé

L’API Windows Mixed Reality fournit une prise en charge complète du suivi de main articulé, par exemple sur HoloLens 2. Le suivi de la main articulé peut être utilisé pour implémenter des modèles d’entrée de manipulation directe et de point et de validation dans vos applications. Il peut également être utilisé pour créer des interactions entièrement personnalisées.

Squelette de la main

Le suivi de la main articulé fournit un squelette de 25 articulations qui permet de nombreux types d’interactions différents. Le squelette fournit cinq articulations pour l’index/milieu/anneau/petits doigts, quatre articulations pour le pouce, et une articulation du poignet. L’articulation du poignet sert de base de la hiérarchie. L’image suivante illustre la disposition du squelette.

Squelette de la main

Dans la plupart des cas, chaque articulation est nommée en fonction de l’os qu’elle représente. Comme il y a deux os à chaque articulation, nous utilisons une convention de nommage de chaque articulation en fonction de l’os enfant à cet emplacement. L’os enfant est défini comme l’os plus éloigné du poignet. Par exemple, l’articulation « Index Proximal » contient la position de début de l’os proximal index et l’orientation de cet os. Il ne contient pas la position de fin de l’os. Si vous en avez besoin, vous l’obtiendrez à partir de la jointure suivante dans la hiérarchie, la jointure « Index Intermediate ».

En plus des 25 articulations hiérarchiques, le système fournit un joint de paume. La paume n’est généralement pas considérée comme faisant partie de la structure squelettique. Il est fourni uniquement comme un moyen pratique d’obtenir la position globale et l’orientation de la main.

Les informations suivantes sont fournies pour chaque jointure :

Nom Description
Position Position 3D de l’articulation, disponible dans n’importe quel système de coordonnées demandé.
Orientation Orientation 3D de l’os, disponible dans n’importe quel système de coordonnées demandé.
Radius Distance à la surface de la peau à la position de l’articulation. Utile pour régler les interactions directes ou les visualisations qui s’appuient sur la largeur du doigt.
Précision Fournit une indication sur la confiance du système à l’sujet des informations de cette articulation.

Vous pouvez accéder aux données du squelette de la main par le biais d’une fonction sur SpatialInteractionSourceState. La fonction est appelée TryGetHandPose et retourne un objet appelé HandPose. Si la source ne prend pas en charge les mains articulées, cette fonction retourne null. Une fois que vous avez un HandPose, vous pouvez obtenir les données de joint actuelles en appelant TryGetJoint, avec le nom de la jointure qui vous intéresse. Les données sont retournées en tant que structure JointPose . Le code suivant obtient la position de l’extrémité de l’index. La variable currentState représente une instance 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
	}
}

Maillage à la main

L’API de suivi de la main articulée permet un maillage de main entièrement déformable. Ce maillage peut se déformer en temps réel avec le squelette de la main, et est utile pour la visualisation et les techniques de physique avancées. Pour accéder au maillage de main, vous devez d’abord créer un objet HandMeshObserver en appelant TryCreateHandMeshObserverAsync sur SpatialInteractionSource. Cette opération ne doit être effectuée qu’une seule fois par source, généralement la première fois que vous la voyez. Cela signifie que vous allez appeler cette fonction pour créer un objet HandMeshObserver chaque fois qu’une main entre dans l’objet FOV. Il s’agit d’une fonction asynchrone. Vous devrez donc gérer un peu d’accès concurrentiel ici. Une fois disponible, vous pouvez demander à l’objet HandMeshObserver la mémoire tampon d’index triangle en appelant GetTriangleIndices. Les index ne changent pas frame over frame. Vous pouvez donc les obtenir une fois et les mettre en cache pendant toute la durée de vie de la source. Les index sont fournis dans l’ordre de remontage dans le sens des aiguilles d’une montre.

Le code suivant lance un std::thread détaché pour créer l’observateur de maillage et extrait la mémoire tampon d’index une fois que l’observateur de maillage est disponible. Il commence à partir d’une variable appelée currentState, qui est une instance de SpatialInteractionSourceState représentant une main suivie.

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();

Le démarrage d’un thread détaché n’est qu’une option pour gérer les appels asynchrones. Vous pouvez également utiliser la nouvelle fonctionnalité co_await prise en charge par C++/WinRT.

Une fois que vous avez un objet HandMeshObserver, vous devez le conserver pendant la durée d’activité de l’objet SpatialInteractionSource correspondant. Ensuite, chaque image, vous pouvez lui demander la dernière mémoire tampon de vertex qui représente la main en appelant GetVertexStateForPose et en transmettant une instance HandPose qui représente la pose pour laquelle vous souhaitez obtenir des sommets. Chaque sommet de la mémoire tampon a une position et une normale. Voici un exemple d’obtention de l’ensemble actuel de sommets pour un maillage de main. Comme précédemment, la variable currentState représente un instance 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
    }
}

Contrairement aux articulations squelettes, l’API de maillage de main ne vous permet pas de spécifier un système de coordonnées pour les sommets. Au lieu de cela, handMeshVertexState spécifie le système de coordonnées dans lequel les sommets sont fournis. Vous pouvez ensuite obtenir une transformation de maillage en appelant TryGetTransformTo et en spécifiant le système de coordonnées souhaité. Vous devez utiliser cette transformation de maillage chaque fois que vous travaillez avec les sommets. Cette approche réduit la surcharge du processeur, en particulier si vous utilisez uniquement le maillage à des fins de rendu.

Regarder et valider des mouvements composites

Pour les applications qui utilisent le modèle d’entrée de reconnaissance et de validation, en particulier sur HoloLens (première génération), l’API d’entrée spatiale fournit un spatialGestureRecognizer facultatif qui peut être utilisé pour activer des mouvements composites basés sur l’événement « select ». En acheminant les interactions de SpatialInteractionManager vers le spatialGestureRecognizer d’un hologramme, les applications peuvent détecter uniformément les événements d’appui, de conservation, de manipulation et de navigation entre les appareils d’entrée, voix et spatial, sans avoir à gérer manuellement les pressions et les relâchements.

SpatialGestureRecognizer effectue uniquement l’ambiguïté minimale entre l’ensemble de mouvements que vous demandez. Par exemple, si vous demandez simplement appuyez, l’utilisateur peut maintenir son doigt vers le bas tant qu’il le souhaite et un appui se produit toujours. Si vous demandez à la fois appuyez et maintenez enfoncé, après environ une seconde de maintien du doigt, le mouvement est promu en maintien et un appui ne se produit plus.

Pour utiliser SpatialGestureRecognizer, gérez l’événement InteractionDetected de SpatialInteractionManager et récupérez le SpatialPointerPose qui y est exposé. Utilisez le rayon du regard de la tête de l’utilisateur de cette pose pour croiser les hologrammes et les maillages de surface dans l’environnement de l’utilisateur pour déterminer avec quoi l’utilisateur a l’intention d’interagir. Ensuite, routez spatialInteraction dans les arguments d’événement vers le SpatialGestureRecognizer de l’hologramme cible, à l’aide de sa méthode CaptureInteraction . Cela commence à interpréter cette interaction en fonction de l’élément SpatialGestureSettings défini sur ce module de reconnaissance au moment de la création , ou par TrySetGestureSettings.

Sur HoloLens (première génération), les interactions et les mouvements doivent dériver leur ciblage du regard de la tête de l’utilisateur, plutôt que de rendre ou d’interagir à l’emplacement de la main. Une fois qu’une interaction a commencé, les mouvements relatifs de la main peuvent être utilisés pour contrôler le mouvement, comme avec le mouvement Manipulation ou Navigation.

Voir aussi