Gestion des inputs avec HoloLens et C++
Après cette brève introduction au développement 3D avec HoloLens, concentrons-nous sur une partie importante et, finalement, simple comme bonjour: la gestion des entrées utilisateur avec HoloLens.
L’inputs manager
Créons une classe qui s’occupera pour nous de récupérer les évènements, tels que le clic (ou pincé).
Afin d’intercepter les entrées utilisateur et d’en récupérer les informations associées (position du curseur du HoloLens lors du clic par exemple), les APIs HoloLens nous fournissent une classe appelée SpatialInteractionManager qui va nous permettre de nous abonner à tous les évènements disponibles.
Chaque évènement nous fournit un état (_state) qui nous permettra d’accéder aux informations associées.
Pour finir, notre instance _coordinatesSystem n’est déclarée ici que parce que nous l’utiliserons afin de récupérer la position du curseur dans notre environnement.
class CInputHandler { public: //! Constructor CInputHandler(); //! Destructor (empty) virtual ~CInputHandler(); //! Sets the coordinates system inline void SetSpatialCoordinatesSystem(Windows::Perception::Spatial::SpatialCoordinateSystem^ coordinatesSystem) { _coordinatesSystem = coordinatesSystem; } private: //! Holographic Windows::UI::Input::Spatial::SpatialInteractionManager^ _manager; Windows::UI::Input::Spatial::SpatialInteractionSourceState^ _state = nullptr; Windows::Foundation::EventRegistrationToken _pressedEventToken; Windows::Perception::Spatial:: SpatialCoordinateSystem^ _coordinatesSystem = nullptr; //! Events void OnPressed(Windows::UI::Input::Spatial::SpatialInteractionManager^ sender, Windows::UI::Input::Spatial::SpatialInteractionSourceEventArgs^ args); //! Utils functions void _getCursorPosition(math::vector3f& vec); };
Le constructeur
Le constructeur peut nous permettre de nous abonner à ces différents évènements. Regardons de plus près la méthode à suivre:
//! Constructor CInputHandler::CInputHandler() { //! Initialize _manager = SpatialInteractionManager::GetForCurrentView(); //! Bind events _pressedEventToken = _manager->SourcePressed += ref new TypedEventHandler<SpatialInteractionManager^, SpatialInteractionSourceEventArgs^>(bind(&CInputHandler::OnPressed, this, _1, _2)); }
Dans un premier temps, il s’agit de récupérer une instance de SpatialInteractionManager en appelant la méthode statique GetForCurrentView(), vue courante qui est en fait notre instance de IFrameworkViewSource :(https://msdn.microsoft.com/library/windows/apps/hh700482).
Puis, dans un deuxième temps, il ne nous reste plus qu’à nous abonner aux évènements (ici le clic) en utilisant les TypedEventHandler (https://msdn.microsoft.com/fr-fr/library/windows/apps/br225997.aspx). Finalement nous récupérons le Token renvoyé par l’opérateur += qui contient la référence du delegate.
Note: Les arguments _1 et _2 sont juste des placeholders utilisés pour définir les positions des arguments dans la fonction bind. Plus d’informations ici : http://en.cppreference.com/w/cpp/utility/functional/placeholders
A partir de ce moment-là, à chaque clic, notre méthode OnPressed sera appelée par l’instance de SpatialInteractionManager.
La méthode OnPressed
Cette méthode prend deux arguments : un pointeur vers l’objet source (sender) et un pointeur vers les informations liées à l’évènement (args).
C’est l’argument args qui nous permettra de récupérer l’état de l’évènement et, au final, nous permettre de récupérer la position du curseur à partir du clic:
//! On source pressed (right click equivalence) void CInputHandler::OnPressed(SpatialInteractionManager^ sender, SpatialInteractionSourceEventArgs^ args) { math::vector3f cursorPosition; _state = args->State; _getCursorPosition(cursorPosition); std::cout << cursorPosition.X << ", " << cursorPosition.y << ", " << cursorPosition.Z << std::endl; }
Dans cet exemple, la classe vector3f correspond à un vecteur 3D qui contient les membres à virgule flottante X, Y, et Z et qui ici correspond à la position du curseur.
La méthode _getCursorPosition (ci-dessous) nous permet de récupérer la position du curseur en complétant l’objet cursorPosition.
Récupérer la position du curseur
Récupérer la position du curseur revient à déterminer “où et jusqu’où” l’utilisateur regarde, c’est-à-dire le regard. En anglais, cela correspond au fameux “gaze” : https://developer.microsoft.com/en-us/windows/holographic/gaze
La méthode consiste à récupérer deux informations essentielles : la position du HoloLens dans l’espace (soit la tête de l’utilisateur) et la direction dans laquelle il regarde, ces deux informations essentielles qui sont des vecteurs 3D. Une fois ces deux vecteurs additionnés, le résultat a une fâcheuse tendance à nous donner la position du curseur et ça tombe bien, c’est exactement ce que l’on veut !
void CInputHandler::_getPositionPosition(vector3f& vec) { // Get pointer pose (head direction and position in space) auto position = _state->TryGetPointerPose(_coordinatesSystem); if (position != nullptr) { const float3 headPosition = position->Head->Position; const float3 headDirection = position->Head->ForwardDirection; static const float distanceFromUser = 2.f; const float3 gaze = headPosition + (distanceFromUser * headDirection); vec.X = gaze.x; vec.Y = gaze.y; vec.Z = gaze.z; } }
La formule suivante:
float3 gaze = headPosition + headDirection;
Revient à dire que l’utilisateur regarde toujours un point situé à 1 mètre de lui (si l’on part du principe que l’on ne regarde jamais dans la vide). C’est-à-dire:
float3 gaze = headPosition + (1.f * headDirection);
Dans l’exemple ci-dessus, on part du principe que l’utilisateur regarde toujours un point situé à 2 mètres de lui (float distanceFromUser = 2.f). Afin de récupérer la position réelle du curseur, il nous faut récupérer les informations liées à l’analyse de l’environnement par l’HoloLens (Spatial Mapping : https://developer.microsoft.com/en-us/windows/holographic/spatial_mapping). Récupérer et utiliser ces informations nécessite un peu plus d’efforts mais sera présenté prochainement.
Conclusion
Gérer les entrées utilisateurs avec un HoloLens ne change finalement en rien par rapport à une application UWP classique. Les différences ici sont seulement les informations liées aux évènements et les usages qui en sont faits. C’est-à-dire qu’au lieu d’appeler une méthode sur un service lorsque l’utilisateur clique, ici nous additionnons deux vecteurs pour récupérer la position d’un curseur
Commentaires