Mani e controller del movimento in DirectX

Nota

Questo articolo è correlato alle API native WinRT legacy. Per i nuovi progetti di app native, è consigliabile usare l'API OpenXR.

In Windows Mixed Reality l'input del controller di movimento e della mano viene gestito tramite le API di input spaziale disponibili nello spazio dei nomi Windows.UI.Input.Spatial. In questo modo è possibile gestire facilmente azioni comuni come Select preme lo stesso modo tra le mani e i controller del movimento.

Introduzione

Per accedere all'input spaziale in Windows Mixed Reality, iniziare con l'interfaccia SpatialInteractionManager. È possibile accedere a questa interfaccia chiamando SpatialInteractionManager::GetForCurrentView, in genere durante l'avvio dell'app.

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

SpatialInteractionManager interactionManager = SpatialInteractionManager::GetForCurrentView();

Il processo di SpatialInteractionManager consiste nel fornire l'accesso a SpatialInteractionSources, che rappresenta un'origine di input. Nel sistema sono disponibili tre tipi di SpatialInteractionSources.

  • La mano rappresenta la mano rilevata da un utente. Le origini della mano offrono diverse funzionalità basate sul dispositivo, che vanno dai movimenti di base in HoloLens al tracciamento delle mani completamente articolato su HoloLens 2.
  • Il controller rappresenta un controller di movimento associato. I controller di movimento possono offrire funzionalità diverse, ad esempio Select triggers, Menu button, Grasp button, touchpads e thumbsticks.
  • La voce rappresenta le parole chiave rilevate dal sistema vocale dell'utente. Ad esempio, questa origine inserirà una selezione e un rilascio ogni volta che l'utente dice "Seleziona".

I dati per frame per un'origine sono rappresentati dall'interfaccia SpatialInteractionSourceState . Esistono due modi diversi per accedere a questi dati, a seconda che si voglia usare un modello basato su eventi o basato su polling nell'applicazione.

Input basato su eventi

SpatialInteractionManager fornisce diversi eventi che l'app può restare in ascolto. Alcuni esempi includono SourcePressed, [SourceReleased e SourceUpdated.

Ad esempio, il codice seguente associa un gestore eventi denominato MyApp::OnSourcePressed all'evento SourcePressed. In questo modo l'app può rilevare le pressioni su qualsiasi tipo di origine di interazione.

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

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

Questo evento premuto viene inviato all'app in modo asincrono, insieme all'oggetto SpatialInteractionSourceState corrispondente al momento in cui è stata eseguita la stampa. L'app o il motore di gioco potrebbe voler avviare subito l'elaborazione o accodare i dati dell'evento nella routine di elaborazione dell'input. Ecco una funzione del gestore eventi per l'evento SourcePressed, che controlla se il pulsante select è stato premuto.

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

Il codice precedente controlla solo la pressione "Seleziona", che corrisponde all'azione primaria nel dispositivo. Alcuni esempi includono l'operazione airtap in HoloLens o il pull del trigger su un controller di movimento. Le pressioni 'Select' rappresentano l'intenzione dell'utente di attivare l'ologramma di destinazione. L'evento SourcePressed verrà generato per diversi pulsanti e movimenti ed è possibile esaminare altre proprietà in SpatialInteractionSource per verificare tali casi.

Input basato sul polling

È anche possibile usare SpatialInteractionManager per eseguire il polling dello stato corrente di input ogni fotogramma. A tale scopo, chiama GetDetectedSourcesAtTimestamp ogni fotogramma. Questa funzione restituisce una matrice contenente un oggetto SpatialInteractionSourceState per ogni oggetto SpatialInteractionSource attivo. Ciò significa che uno per ogni controller di movimento attivo, uno per ogni mano rilevata e uno per il parlato se un comando 'select' è stato recentemente pronunciato. È quindi possibile esaminare le proprietà in ogni SpatialInteractionSourceState per eseguire l'input nell'applicazione.

Ecco un esempio di come verificare la presenza dell'azione 'select' usando il metodo di polling. La variabile di stima rappresenta un oggetto HolographicFramePrediction , che può essere ottenuto da 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
	}
}

Ogni SpatialInteractionSource ha un ID, che è possibile usare per identificare nuove origini e correlare le origini esistenti dal frame al frame. Le mani ottengono un nuovo ID ogni volta che lasciano e immettono il FOV, ma gli ID controller rimangono statici per la durata della sessione. È possibile usare gli eventi in SpatialInteractionManager, ad esempio SourceDetected e SourceLost, per reagire quando le mani entrano o lasciano la visualizzazione del dispositivo o quando i controller del movimento sono accesi o disattivati o non abbinati.

Posizioni storiche e stimate

GetDetectedSourcesAtTimestamp ha un parametro timestamp. In questo modo è possibile richiedere lo stato e la posa dei dati stimati o cronologici, consentendo di correlare le interazioni spaziali con altre origini di input. Ad esempio, quando si esegue il rendering della posizione della mano nel frame corrente, è possibile passare il timestamp stimato fornito da HolographicFrame. Ciò consente al sistema di stimare in avanti la posizione della mano in modo che sia strettamente allineata all'output del frame di cui è stato eseguito il rendering, riducendo al minimo la latenza percepita.

Tuttavia, tale posa stimata non produce un raggio di puntamento ideale per la destinazione con un'origine di interazione. Ad esempio, quando viene premuto un pulsante del controller di movimento, l'evento può richiedere fino a 20 ms per passare tramite Bluetooth al sistema operativo. Analogamente, dopo che un utente esegue un gesto della mano, un certo periodo di tempo può trascorrere prima che il sistema rilevi il movimento e l'app e ne esegue il polling. Quando l'app esegue il polling di una modifica dello stato, la posizione della testa e della mano usata per indirizzare l'interazione è effettivamente accaduta in passato. Se si specifica come destinazione passando il timestamp di HolographicFrame corrente a GetDetectedSourcesAtTimestamp, la posizione verrà invece inoltrata al raggio di destinazione al momento in cui verrà visualizzato il frame, che potrebbe essere superiore a 20 ms in futuro. Questa soluzione futura è valida per il rendering dell'origine di interazione, ma costituisce il problema di tempo per la destinazione dell'interazione, perché la destinazione dell'utente si è verificata in passato.

Fortunatamente, gli eventi SourcePressed, [SourceReleased e SourceUpdated forniscono lo stato cronologico associato a ogni evento di input. Questo include direttamente la posizione cronologica della testa e della mano disponibili tramite TryGetPointerPose, insieme a un timestamp cronologico che è possibile passare ad altre API per correlare con questo evento.

Ciò comporta le procedure consigliate seguenti quando si esegue il rendering e la destinazione con mani e controller ogni fotogramma:

  • Per il rendering manuale/controller di ogni fotogramma, l'app deve eseguire il polling per la posa stimata in avanti di ogni origine di interazione al momento della foton del fotogramma corrente. È possibile eseguire il polling di tutte le origini di interazione chiamando GetDetectedSourcesAtTimestamp ogni fotogramma, passando il timestamp stimato fornito da HolographicFrame::CurrentPrediction.
  • Per la destinazione della mano o del controller su una stampa o un rilascio, l'app deve gestire eventi stampati/rilasciati, raycasting in base alla posizione cronologica della testa o della mano per tale evento. Per ottenere questo raggio di destinazione, gestire l'evento SourcePressed o SourceReleased , ottenere la proprietà State dagli argomenti dell'evento e quindi chiamare il relativo metodo TryGetPointerPose .

Proprietà di input tra dispositivi

L'API SpatialInteractionSource supporta controller e sistemi di rilevamento delle mani con un'ampia gamma di funzionalità. Alcune di queste funzionalità sono comuni tra i tipi di dispositivo. Ad esempio, il tracciamento della mano e i controller del movimento forniscono sia un'azione di selezione che una posizione 3D. Laddove possibile, l'API esegue il mapping di queste funzionalità comuni alle stesse proprietà in SpatialInteractionSource. Ciò consente alle applicazioni di supportare più facilmente un'ampia gamma di tipi di input. Nella tabella seguente vengono descritte le proprietà supportate e il modo in cui vengono confrontate tra i tipi di input.

Proprietà Descrizione Movimenti di HoloLens(prima generazione) Controller del movimento Mani articolate
SpatialInteractionSource::Handedness Mano destra o sinistra/controller. Non supportato Supportato Supportato
SpatialInteractionSourceState::IsSelectPressed Stato corrente del pulsante primario. Tocco aria Trigger Tocco dell'aria rilassata (avvicinamento verticale)
SpatialInteractionSourceState::IsGrasped Stato corrente del pulsante di cattura. Non supportato Pulsante Afferra Avvicinamento delle dita o mano chiusa
SpatialInteractionSourceState::IsMenuPressed Stato corrente del pulsante di menu. Non supportato Pulsante menu Non supportato
SpatialInteractionSourceLocation::Position Posizione XYZ della posizione della mano o del grip sul controller. Posizione palmo Posizione della posizione del grip Posizione palmo
SpatialInteractionSourceLocation::Orientation Quaternione che rappresenta l'orientamento della mano o della posizione del grip sul controller. Non supportato Orientamento della posizione del grip Orientamento palmo
SpatialPointerInteractionSourcePose::Position Origine del raggio di puntamento. Non supportato Supportato Supportato
SpatialPointerInteractionSourcePose::ForwardDirection Direzione del raggio di puntamento. Non supportato Supportato Supportato

Alcune delle proprietà precedenti non sono disponibili in tutti i dispositivi e l'API fornisce un modo per testarlo. Ad esempio, è possibile esaminare la proprietà SpatialInteractionSource::IsGraspSupported per determinare se l'origine fornisce un'azione di comprensione.

Posizione del grip rispetto alla posa puntante

Windows Mixed Reality supporta controller di movimento in diversi fattori di forma. Supporta anche sistemi di tracciamento delle mani articolati. Tutti questi sistemi hanno relazioni diverse tra la posizione della mano e la direzione naturale "in avanti" che le app devono usare per puntare o eseguire il rendering di oggetti nella mano dell'utente. Per supportare tutto questo, ci sono due tipi di pose 3D disponibili sia per il tracciamento della mano che per i controller del movimento. La prima è la posa del grip, che rappresenta la posizione della mano dell'utente. Il secondo è la posa puntante, che rappresenta un raggio di puntamento proveniente dalla mano o dal controller dell'utente. Quindi, se vuoi eseguire il rendering della mano dell'utente o di un oggetto tenuto nella mano dell'utente, ad esempio una spada o una pistola, usa la posa del grip. Se si vuole eseguire il raycast dal controller o dalla mano, ad esempio quando l'utente punta all'interfaccia utente, usare la posizione di puntamento.

È possibile accedere alla posizione del grip tramite SpatialInteractionSourceState::P roperties::TryGetLocation(...). Viene definito come segue:

  • Posizione del grip: centro del palmo quando si tiene il controller naturalmente, regolato a sinistra o a destra per allineare al centro la posizione all'interno del grip.
  • Asse destro dell'orientamento del grip: quando si apre completamente la mano per formare una posa a 5 dita piatta, il raggio normale al palmo (avanti dal palmo sinistro, all'indietro dal palmo destro)
  • Asse avanti dell'orientamento del grip: quando chiudi la mano parzialmente (come se tieni premuto il controller), il raggio che punta "in avanti" attraverso il tubo formato dalle dita non del pollice.
  • Asse Up dell'orientamento del grip: asse Su implicito dalle definizioni Destra e Avanti.

È possibile accedere alla posizione del puntatore tramite SpatialInteractionSourceState::P roperties::TryGetLocation(...)::SourcePointerPose o SpatialInteractionSourceState::TryGetPointerPose(...)::TryGetInteractionSourcePose.

Proprietà di input specifiche del controller

Per i controller, SpatialInteractionSource ha una proprietà Controller con funzionalità aggiuntive.

  • HasThumbstick: Se true, il controller ha una levetta. Esaminare la proprietà ControllerProperties di SpatialInteractionSourceState per acquisire i valori x e y della levetta (ThumbstickX e ThumbstickY), nonché il relativo stato premuto (IsThumbstickPressed).
  • HasTouchpad: Se true, il controller ha un touchpad. Esaminare la proprietà ControllerProperties di SpatialInteractionSourceState per acquisire i valori x e y del touchpad (TouchpadX e TouchpadY) e per sapere se l'utente tocca il pad (IsTouchpadTouched) e se preme il touchpad verso il basso (IsTouchpadPressed).
  • SimpleHapticsController: L'API SimpleHapticsController per il controller consente di controllare le funzionalità aptici del controller e consente anche di controllare il feedback aptico.

L'intervallo per touchpad e levette è compreso tra -1 e 1 per entrambi gli assi (dal basso verso l'alto e da sinistra a destra). L'intervallo per il trigger analogico, accessibile tramite la proprietà SpatialInteractionSourceState::SelectPressedValue, ha un intervallo compreso tra 0 e 1. Il valore 1 è correlato a IsSelectPressed uguale a true; qualsiasi altro valore è correlato a IsSelectPressed uguale a false.

Tracciamento mano articolato

L'API Windows Mixed Reality offre il supporto completo per il tracciamento manuale articolato, ad esempio su HoloLens 2. Il tracciamento manuale articolato può essere usato per implementare modelli di input di manipolazione diretta e di puntamento e commit nelle applicazioni. Può anche essere usato per creare interazioni completamente personalizzate.

Scheletro della mano

Il tracciamento delle mani articolato fornisce uno scheletro 25 articolazioni che consente molti tipi diversi di interazioni. Lo scheletro fornisce cinque articolazioni per l'indice/centro/anello/piccole dita, quattro articolazioni per il pollice e un'articolazione del polso. L'articolazione del polso funge da base della gerarchia. L'immagine seguente illustra il layout dello scheletro.

Scheletro mano

Nella maggior parte dei casi, ogni articolazione è denominata in base all'osso che rappresenta. Poiché ci sono due ossa in ogni articolazione, usiamo una convenzione di denominazione di ogni articolazione basata sull'osso infantile in quella posizione. L'osso figlio è definito come l'osso più lontano dal polso. Ad esempio, l'articolazione "Index Proximal" contiene la posizione iniziale dell'osso prossimale dell'indice e l'orientamento dell'osso. Non contiene la posizione finale dell'osso. Se necessario, è necessario ottenerlo dall'articolazione successiva nella gerarchia, ovvero l'articolazione "Index Intermediate".

Oltre ai 25 giunti gerarchici, il sistema fornisce un giunto palmo. Il palmo non è in genere considerato parte della struttura scheletrica. Viene fornito solo come un modo pratico per ottenere la posizione e l'orientamento generali della mano.

Per ogni articolazione vengono fornite le informazioni seguenti:

Nome Descrizione
Position Posizione 3D del giunto, disponibile in qualsiasi sistema di coordinate richiesto.
Orientamento Orientamento 3D dell'osso, disponibile in qualsiasi sistema di coordinate richiesto.
Radius Distanza dalla superficie della pelle in corrispondenza della posizione dell'articolazione. Utile per ottimizzare le interazioni dirette o le visualizzazioni che si basano sulla larghezza del dito.
Accuratezza Fornisce un suggerimento sul modo in cui il sistema si sente sicuro delle informazioni di questa joint.

È possibile accedere alla struttura manuale dei dati tramite una funzione in SpatialInteractionSourceState. La funzione è denominata TryGetHandPose e restituisce un oggetto denominato HandPose. Se l'origine non supporta mani articolate, questa funzione restituirà Null. Dopo aver creato un HandPose, è possibile ottenere i dati comuni correnti chiamando TryGetJoint, con il nome del joint a cui si è interessati. I dati vengono restituiti come struttura JointPose . Il codice seguente ottiene la posizione della punta del dito indice. La variabile currentState rappresenta un'istanza di 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
	}
}

Mesh a mano

L'API di tracciamento della mano articolata consente una mesh a mano a triangolo completamente deformabile. Questa mesh può deformarsi in tempo reale insieme allo scheletro della mano ed è utile per la visualizzazione e le tecniche avanzate di fisica. Per accedere alla mesh manuale, è prima necessario creare un oggetto HandMeshObserver chiamando TryCreateHandMeshObserverAsync in SpatialInteractionSource. Questa operazione deve essere eseguita una sola volta per ogni origine, in genere la prima volta che viene visualizzata. Ciò significa che questa funzione verrà chiamata per creare un oggetto HandMeshObserver ogni volta che una mano entra nel FOV. Si tratta di una funzione asincrona, quindi è necessario gestire un po' di concorrenza qui. Una volta disponibile, è possibile chiedere all'oggetto HandMeshObserver il buffer di indice del triangolo chiamando GetTriangleIndices. Gli indici non cambiano frame su frame, quindi è possibile recuperarli una sola volta e memorizzarli nella cache per la durata dell'origine. Gli indici vengono forniti in ordine di avvolgimento in senso orario.

Il codice seguente attiva uno std::thread scollegato per creare l'osservatore mesh ed estrae il buffer di indice dopo che l'osservatore mesh è disponibile. Inizia da una variabile denominata currentState, che è un'istanza di SpatialInteractionSourceState che rappresenta una mano rilevata.

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

L'avvio di un thread scollegato è solo un'opzione per la gestione delle chiamate asincrone. In alternativa, è possibile usare la nuova funzionalità di co_await supportata da C++/WinRT.

Dopo aver creato un oggetto HandMeshObserver, è necessario tenerlo premuto per la durata in cui è attivo il corrispondente SpatialInteractionSource. È quindi possibile richiedere il buffer dei vertici più recente che rappresenta la mano chiamando GetVertexStateForPose e passando un'istanza handPose che rappresenta la posizione per cui si desiderano i vertici. Ogni vertice nel buffer ha una posizione e una normale. Ecco un esempio di come ottenere il set corrente di vertici per una mesh a mano. Come in precedenza, la variabile currentState rappresenta un'istanza di 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 differenza delle articolazioni dello scheletro, l'API mesh della mano non consente di specificare un sistema di coordinate per i vertici. Invece, HandMeshVertexState specifica il sistema di coordinate in cui sono disponibili i vertici. È quindi possibile ottenere una trasformazione mesh chiamando TryGetTransformTo e specificando il sistema di coordinate desiderato. È necessario usare questa trasformazione mesh ogni volta che si lavora con i vertici. Questo approccio riduce il sovraccarico della CPU, soprattutto se si usa solo la mesh a scopo di rendering.

Gesti compositi di sguardo e commit

Per le applicazioni che usano il modello di input gaze-and-commit, in particolare in HoloLens (prima generazione), l'API Input spaziale fornisce un oggetto SpatialGestureRecognizer facoltativo che può essere usato per abilitare i movimenti compositi basati sull'evento 'select'. Instradando le interazioni da SpatialInteractionManager a un hologram's SpatialGestureRecognizer, le app possono rilevare Tap, Hold, Manipulation e Navigation events in modo uniforme tra mani, voce e dispositivi di input spaziali, senza dover gestire manualmente le presse e le versioni.

SpatialGestureRecognizer esegue solo la disambiguazione minima tra il set di movimenti richiesto. Ad esempio, se si richiede solo Toccare, l'utente può tenere premuto il dito fino a quando piace e un tocco si verificherà ancora. Se si richiede il tocco e il blocco, dopo circa un secondo di tenere premuto il dito il movimento verrà promosso a un blocco e un tocco non si verificherà più.

Per usare SpatialGestureRecognizer, gestire l'evento InteractionDetected di SpatialInteractionManager e acquisire l'oggetto SpatialPointerPose esposto. Usare il raggio di sguardo della testa dell'utente da questa posizione per intersecare gli ologrammi e le mesh di superficie nell'ambiente dell'utente per determinare con quale utente intende interagire. Instradare quindi l'oggetto SpatialInteraction negli argomenti dell'evento all'ologramma di destinazione SpatialGestureRecognizer usando il relativo metodo CaptureInteraction . Questa operazione inizia a interpretare tale interazione in base al set SpatialGestureSettings su tale riconoscimento in fase di creazione oppure da TrySetGestureSettings.

In HoloLens (prima generazione), le interazioni e i movimenti devono derivare la destinazione dallo sguardo della testa dell'utente, anziché eseguire il rendering o l'interazione nella posizione della mano. Dopo l'avvio di un'interazione, i movimenti relativi della mano possono essere usati per controllare il movimento, come con il movimento Di manipolazione o navigazione.

Vedi anche