Condividi tramite


Sistemi di coordinate in DirectX

Nota

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

I sistemi di coordinate costituiscono la base per la comprensione spaziale offerta dalle API di Windows Realtà mista.

I dispositivi VR seduti o vr a camera singola stabiliscono un sistema di coordinate principale per il loro spazio monitorato. Realtà mista dispositivi come HoloLens sono progettati per ambienti indefiniti di grandi dimensioni, con l'individuazione e l'apprendimento dell'ambiente circostante mentre l'utente cammina in giro. Il dispositivo si adatta a migliorare continuamente le conoscenze sulle stanze dell'utente, ma comporta sistemi di coordinate che cambiano la relazione tra loro nel corso della durata delle app. Windows Realtà mista supporta un'ampia gamma di dispositivi, che vanno da visori VR immersive seduti a frame di riferimento collegati al mondo.

Nota

I frammenti di codice in questo articolo illustrano attualmente l'uso di C++/CX anziché C++17 conformi a C++/WinRT come usato nel modello di progetto olografico C++. I concetti sono equivalenti per un progetto C++/WinRT, anche se sarà necessario tradurre il codice.

Sistemi di coordinate spaziali in Windows

Il tipo principale usato per ragionare sui sistemi di coordinate reali in Windows è SpatialCoordinateSystem. Un'istanza di questo tipo rappresenta un sistema di coordinate arbitrario, fornendo un metodo per ottenere i dati della matrice di trasformazione che è possibile usare per trasformare tra due sistemi di coordinate senza comprendere i dettagli di ognuno di essi.

I metodi che restituiscono informazioni spaziali accetteranno un parametro SpatialCoordinateSystem per consentire di decidere il sistema di coordinate in cui è più utile per restituire tali coordinate. Le informazioni spaziali vengono rappresentate come punti, raggi o volumi nell'ambiente circostante dell'utente e le unità per queste coordinate saranno sempre in metri.

SpatialCoordinateSystem ha una relazione dinamica con altri sistemi di coordinate, inclusi quelli che rappresentano la posizione del dispositivo. In qualsiasi momento, il dispositivo può individuare alcuni sistemi di coordinate e non altri. Per la maggior parte dei sistemi di coordinate, l'app deve essere pronta per gestire i periodi di tempo durante i quali non possono trovarsi.

L'applicazione non deve creare direttamente SpatialCoordinateSystems, ma deve essere usata tramite le API Perception. Esistono tre fonti principali di sistemi di coordinate nelle API Perception, ognuna delle quali è mappata a un concetto descritto nella pagina Sistemi di coordinate:

Tutti i sistemi di coordinate restituiti da questi oggetti sono di destra, con +y su, +x a destra e +z indietro. È possibile ricordare quale direzione punta l'asse z positivo puntando le dita della mano sinistra o destra nella direzione x positiva e curlingli nella direzione y positiva. La direzione in cui punta il pollice, verso o lontano dall'utente, è la direzione in cui è rivolto l'asse z positivo per il sistema di coordinate. La figura seguente illustra questi due sistemi di coordinate.

Sistemi di coordinate sinistro e destro
Sistemi di coordinate sinistro e destro

Usare la classe SpatialLocator per creare un frame di riferimento collegato o fisso per eseguire il bootstrap in un spatialCoordinateSystem basato sulla posizione di HoloLens. Continuare con la sezione successiva per altre informazioni su questo processo.

Posizionare gli ologrammi nel mondo usando una fase spaziale

È possibile accedere al sistema di coordinate per Windows opaco Realtà mista visori VR immersive usando la proprietà static SpatialStageFrameOfReference::Current. Questa API fornisce:

  • Un sistema di coordinate
  • Informazioni sul fatto che il giocatore sia seduto o mobile
  • Il confine di un'area sicura per camminare in giro se il giocatore è mobile
  • Indicazione del fatto che l'auricolare sia direzionale.
  • Gestore eventi per gli aggiornamenti alla fase spaziale.

Prima di tutto, si ottiene la fase spaziale e si sottoscrive per gli aggiornamenti:

Codice per l'inizializzazione della fase spaziale

SpatialStageManager::SpatialStageManager(
    const std::shared_ptr<DX::DeviceResources>& deviceResources, 
    const std::shared_ptr<SceneController>& sceneController)
    : m_deviceResources(deviceResources), m_sceneController(sceneController)
{
    // Get notified when the stage is updated.
    m_spatialStageChangedEventToken = SpatialStageFrameOfReference::CurrentChanged +=
        ref new EventHandler<Object^>(std::bind(&SpatialStageManager::OnCurrentChanged, this, _1));

    // Make sure to get the current spatial stage.
    OnCurrentChanged(nullptr);
}

Nel metodo OnCurrentChanged, l'app deve esaminare la fase spaziale e aggiornare l'esperienza del giocatore. In questo esempio viene offerta una visualizzazione del limite di fase e della posizione iniziale specificata dall'utente e dell'intervallo di visualizzazione e intervallo di proprietà di spostamento della fase. Rientreremo anche nel nostro sistema di coordinate stazionarie, quando non è possibile fornire una fase.

Codice per l'aggiornamento della fase spaziale

void SpatialStageManager::OnCurrentChanged(Object^ /*o*/)
{
    // The event notifies us that a new stage is available.
    // Get the current stage.
    m_currentStage = SpatialStageFrameOfReference::Current;

    // Clear previous content.
    m_sceneController->ClearSceneObjects();

    if (m_currentStage != nullptr)
    {
        // Obtain stage geometry.
        auto stageCoordinateSystem = m_currentStage->CoordinateSystem;
        auto boundsVertexArray = m_currentStage->TryGetMovementBounds(stageCoordinateSystem);

        // Visualize the area where the user can move around.
        std::vector<float3> boundsVertices;
        boundsVertices.resize(boundsVertexArray->Length);
        memcpy(boundsVertices.data(), boundsVertexArray->Data, boundsVertexArray->Length * sizeof(float3));
        std::vector<unsigned short> indices = TriangulatePoints(boundsVertices);
        m_stageBoundsShape =
            std::make_shared<SceneObject>(
                    m_deviceResources,
                    reinterpret_cast<std::vector<XMFLOAT3>&>(boundsVertices),
                    indices,
                    XMFLOAT3(DirectX::Colors::SeaGreen),
                    stageCoordinateSystem);
        m_sceneController->AddSceneObject(m_stageBoundsShape);

        // In this sample, we draw a visual indicator for some spatial stage properties.
        // If the view is forward-only, the indicator is a half circle pointing forward - otherwise, it
        // is a full circle.
        // If the user can walk around, the indicator is blue. If the user is seated, it is red.

        // The indicator is rendered at the origin - which is where the user declared the center of the
        // stage to be during setup - above the plane of the stage bounds object.
        float3 visibleAreaCenter = float3(0.f, 0.001f, 0.f);

        // Its shape depends on the look direction range.
        std::vector<float3> visibleAreaIndicatorVertices;
        if (m_currentStage->LookDirectionRange == SpatialLookDirectionRange::ForwardOnly)
        {
            // Half circle for forward-only look direction range.
            visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.25f, 9, XM_PI);
        }
        else
        {
            // Full circle for omnidirectional look direction range.
            visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.25f, 16, XM_2PI);
        }

        // Its color depends on the movement range.
        XMFLOAT3 visibleAreaColor;
        if (m_currentStage->MovementRange == SpatialMovementRange::NoMovement)
        {
            visibleAreaColor = XMFLOAT3(DirectX::Colors::OrangeRed);
        }
        else
        {
            visibleAreaColor = XMFLOAT3(DirectX::Colors::Aqua);
        }

        std::vector<unsigned short> visibleAreaIndicatorIndices = TriangulatePoints(visibleAreaIndicatorVertices);

        // Visualize the look direction range.
        m_stageVisibleAreaIndicatorShape =
            std::make_shared<SceneObject>(
                    m_deviceResources,
                    reinterpret_cast<std::vector<XMFLOAT3>&>(visibleAreaIndicatorVertices),
                    visibleAreaIndicatorIndices,
                    visibleAreaColor,
                    stageCoordinateSystem);
        m_sceneController->AddSceneObject(m_stageVisibleAreaIndicatorShape);
    }
    else
    {
        // No spatial stage was found.
        // Fall back to a stationary coordinate system.
        auto locator = SpatialLocator::GetDefault();
        if (locator)
        {
            m_stationaryFrameOfReference = locator->CreateStationaryFrameOfReferenceAtCurrentLocation();

            // Render an indicator, so that we know we fell back to a mode without a stage.
            std::vector<float3> visibleAreaIndicatorVertices;
            float3 visibleAreaCenter = float3(0.f, -2.0f, 0.f);
            visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.125f, 16, XM_2PI);
            std::vector<unsigned short> visibleAreaIndicatorIndices = TriangulatePoints(visibleAreaIndicatorVertices);
            m_stageVisibleAreaIndicatorShape =
                std::make_shared<SceneObject>(
                    m_deviceResources,
                    reinterpret_cast<std::vector<XMFLOAT3>&>(visibleAreaIndicatorVertices),
                    visibleAreaIndicatorIndices,
                    XMFLOAT3(DirectX::Colors::LightSlateGray),
                    m_stationaryFrameOfReference->CoordinateSystem);
            m_sceneController->AddSceneObject(m_stageVisibleAreaIndicatorShape);
        }
    }
}

Il set di vertici che definiscono il limite di fase viene fornito in senso orario. La shell di Windows Realtà mista disegna una recinzione al limite quando l'utente si avvicina, ma può essere necessario triangolare l'area a piedi per scopi personalizzati. L'algoritmo seguente può essere usato per triangolare la fase.

Codice per la triangolazione della fase spaziale

std::vector<unsigned short> SpatialStageManager::TriangulatePoints(std::vector<float3> const& vertices)
{
    size_t const& vertexCount = vertices.size();

    // Segments of the shape are removed as they are triangularized.
    std::vector<bool> vertexRemoved;
    vertexRemoved.resize(vertexCount, false);
    unsigned int vertexRemovedCount = 0;

    // Indices are used to define triangles.
    std::vector<unsigned short> indices;

    // Decompose into convex segments.
    unsigned short currentVertex = 0;
    while (vertexRemovedCount < (vertexCount - 2))
    {
        // Get next triangle:
        // Start with the current vertex.
        unsigned short index1 = currentVertex;

        // Get the next available vertex.
        unsigned short index2 = index1 + 1;

        // This cycles to the next available index.
        auto CycleIndex = [=](unsigned short indexToCycle, unsigned short stopIndex)
        {
            // Make sure the index does not exceed bounds.
            if (indexToCycle >= unsigned short(vertexCount))
            {
                indexToCycle -= unsigned short(vertexCount);
            }

            while (vertexRemoved[indexToCycle])
            {
                // If the vertex is removed, go to the next available one.
                ++indexToCycle;

                // Make sure the index does not exceed bounds.
                if (indexToCycle >= unsigned short(vertexCount))
                {
                    indexToCycle -= unsigned short(vertexCount);
                }

                // Prevent cycling all the way around.
                // Should not be needed, as we limit with the vertex count.
                if (indexToCycle == stopIndex)
                {
                    break;
                }
            }

            return indexToCycle;
        };
        index2 = CycleIndex(index2, index1);

        // Get the next available vertex after that.
        unsigned short index3 = index2 + 1;
        index3 = CycleIndex(index3, index1);

        // Vertices that may define a triangle inside the 2D shape.
        auto& v1 = vertices[index1];
        auto& v2 = vertices[index2];
        auto& v3 = vertices[index3];

        // If the projection of the first segment (in clockwise order) onto the second segment is 
        // positive, we know that the clockwise angle is less than 180 degrees, which tells us 
        // that the triangle formed by the two segments is contained within the bounding shape.
        auto v2ToV1 = v1 - v2;
        auto v2ToV3 = v3 - v2;
        float3 normalToV2ToV3 = { -v2ToV3.z, 0.f, v2ToV3.x };
        float projectionOntoNormal = dot(v2ToV1, normalToV2ToV3);
        if (projectionOntoNormal >= 0)
        {
            // Triangle is contained within the 2D shape.

            // Remove peak vertex from the list.
            vertexRemoved[index2] = true;
            ++vertexRemovedCount;

            // Create the triangle.
            indices.push_back(index1);
            indices.push_back(index2);
            indices.push_back(index3);

            // Continue on to the next outer triangle.
            currentVertex = index3;
        }
        else
        {
            // Triangle is a cavity in the 2D shape.
            // The next triangle starts at the inside corner.
            currentVertex = index2;
        }
    }

    indices.shrink_to_fit();
    return indices;
}

Posizionare gli ologrammi nel mondo usando una cornice di riferimento fissa

La classe SpatialStationaryFrameOfReference rappresenta un frame di riferimento che rimane fisso rispetto all'ambiente circostante dell'utente man mano che l'utente si sposta. Questo frame di riferimento assegna priorità mantenendo stabili le coordinate vicino al dispositivo. Un uso chiave di spatialStationaryFrameOfReference consiste nell'agire come sistema di coordinate globale sottostante all'interno di un motore di rendering durante il rendering degli ologrammi.

Per ottenere un oggetto SpatialStationaryFrameOfReference, usare la classe SpatialLocator e chiamare CreateStationaryFrameOfReferenceAtCurrentLocation.

Dal codice del modello di app Windows Holographic:

           // The simplest way to render world-locked holograms is to create a stationary reference frame
           // when the app is launched. This is roughly analogous to creating a "world" coordinate system
           // with the origin placed at the device's position as the app is launched.
           referenceFrame = locator.CreateStationaryFrameOfReferenceAtCurrentLocation();
  • I fotogrammi di riferimento stazionari sono progettati per fornire una posizione più adatta rispetto allo spazio complessivo. Le singole posizioni all'interno di tale frame di riferimento possono derivare leggermente. Questo è normale perché il dispositivo apprende di più sull'ambiente.
  • Quando è necessaria una posizione precisa dei singoli ologrammi, è necessario usare un oggetto SpatialAnchor per ancorare l'ologramma singolo a una posizione nel mondo reale, ad esempio un punto che l'utente indica di essere di particolare interesse. Le posizioni di ancoraggio non derivano, ma possono essere corrette; l'ancoraggio userà la posizione corretta a partire dal fotogramma successivo dopo che si è verificata la correzione.

Posizionare gli ologrammi nel mondo usando ancoraggi nello spazio

Gli ancoraggi nello spazio sono un ottimo modo per posizionare gli ologrammi in un luogo specifico nel mondo reale, con il sistema che garantisce che l'ancoraggio rimanga sul posto nel tempo. Questo argomento illustra come creare e usare un ancoraggio e come usare i dati di ancoraggio.

È possibile creare un oggetto SpatialAnchor in qualsiasi posizione e orientamento all'interno di SpatialCoordinateSystem scelto. Il dispositivo deve essere in grado di individuare il sistema di coordinate al momento e il sistema non deve aver raggiunto il limite di ancoraggi nello spazio.

Una volta definito, il sistema di coordinate di un oggetto SpatialAnchor regola continuamente per mantenere la posizione e l'orientamento precisi della posizione iniziale. È quindi possibile usare questo spatialAnchor per eseguire il rendering degli ologrammi che verranno visualizzati fissi nell'ambiente circostante dell'utente in tale posizione esatta.

Gli effetti delle regolazioni che mantengono l'ancoraggio sul posto vengono amplificati man mano che aumenta la distanza dall'ancoraggio. È consigliabile evitare di eseguire il rendering del contenuto relativo a un ancoraggio superiore a circa 3 metri dall'origine dell'ancoraggio.

La proprietà CoordinateSystem ottiene un sistema di coordinate che consente di posizionare il contenuto relativo all'ancoraggio, con interpolazione applicata quando il dispositivo regola la posizione precisa dell'ancoraggio.

Utilizzare la proprietà RawCoordinateSystem e l'evento RawCoordinateSystemAdjusted corrispondente per gestire manualmente queste modifiche.

È possibile rendere persistente un oggetto SpatialAnchor in locale usando la classe SpatialAnchorStore e quindi recuperarlo nuovamente in una sessione di app futura nello stesso dispositivo HoloLens.

Creare spatialAnchors per il contenuto olografico

Per questo esempio di codice, è stato modificato il modello di app Windows Holographic per creare ancoraggi quando viene rilevato il movimento Premuto . Il cubo viene quindi posizionato all'ancoraggio durante il passaggio di rendering.

Poiché più ancoraggi sono supportati dalla classe helper, è possibile inserire tutti i cubi che si vuole usare questo esempio di codice.

Nota

Gli ID per gli ancoraggi sono elementi che controlli nell'app. In questo esempio è stato creato uno schema di denominazione sequenziale in base al numero di ancoraggi attualmente archiviati nella raccolta di ancoraggi dell'app.

   // Check for new input state since the last frame.
   SpatialInteractionSourceState^ pointerState = m_spatialInputHandler->CheckForInput();
   if (pointerState != nullptr)
   {
       // Try to get the pointer pose relative to the SpatialStationaryReferenceFrame.
       SpatialPointerPose^ pointerPose = pointerState->TryGetPointerPose(currentCoordinateSystem);
       if (pointerPose != nullptr)
       {
           // When a Pressed gesture is detected, the anchor will be created two meters in front of the user.

           // Get the gaze direction relative to the given coordinate system.
           const float3 headPosition = pointerPose->Head->Position;
           const float3 headDirection = pointerPose->Head->ForwardDirection;

           // The anchor position in the StationaryReferenceFrame.
           static const float distanceFromUser = 2.0f; // meters
           const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * headDirection);

           // Create the anchor at position.
           SpatialAnchor^ anchor = SpatialAnchor::TryCreateRelativeTo(currentCoordinateSystem, gazeAtTwoMeters);

           if ((anchor != nullptr) && (m_spatialAnchorHelper != nullptr))
           {
               // In this example, we store the anchor in an IMap.
               auto anchorMap = m_spatialAnchorHelper->GetAnchorMap();

               // Create an identifier for the anchor.
               String^ id = ref new String(L"HolographicSpatialAnchorStoreSample_Anchor") + anchorMap->Size;

               anchorMap->Insert(id->ToString(), anchor);
           }
       }
   }

Caricare e memorizzare nella cache in modo asincrono SpatialAnchorStore

Vediamo come scrivere una classe SampleSpatialAnchorHelper che consente di gestire questa persistenza, tra cui:

  • Archiviazione di una raccolta di ancoraggi in memoria, indicizzati da una chiave Platform::String.
  • Caricamento di ancoraggi da SpatialAnchorStore del sistema, che viene mantenuto separato dalla raccolta in memoria locale.
  • Salvataggio della raccolta locale in memoria degli ancoraggi in SpatialAnchorStore quando l'app sceglie di farlo.

Ecco come salvare gli oggetti SpatialAnchor in SpatialAnchorStore.

All'avvio della classe, viene richiesto l'oggetto SpatialAnchorStore in modo asincrono. Ciò comporta l'I/O di sistema quando l'API carica l'archivio di ancoraggi e questa API viene resa asincrona in modo che l'I/O non blocchi.

   // Request the spatial anchor store, which is the WinRT object that will accept the imported anchor data.
   return create_task(SpatialAnchorManager::RequestStoreAsync())
       .then([](task<SpatialAnchorStore^> previousTask)
   {
       std::shared_ptr<SampleSpatialAnchorHelper> newHelper = nullptr;

       try
       {
           SpatialAnchorStore^ anchorStore = previousTask.get();

           // Once the SpatialAnchorStore has been loaded by the system, we can create our helper class.

           // Using "new" to access private constructor
           newHelper = std::shared_ptr<SampleSpatialAnchorHelper>(new SampleSpatialAnchorHelper(anchorStore));

           // Now we can load anchors from the store.
           newHelper->LoadFromAnchorStore();
       }
       catch (Exception^ exception)
       {
           PrintWstringToDebugConsole(
               std::wstring(L"Exception while loading the anchor store: ") +
               exception->Message->Data() +
               L"\n"
               );
       }

       // Return the initialized class instance.
       return newHelper;
   });

Verrà assegnato un oggetto SpatialAnchorStore che è possibile usare per salvare gli ancoraggi. Si tratta di un oggetto IMapView che associa i valori di chiave stringhe, ai valori di dati che sono SpatialAnchors. Nel codice di esempio questo viene archiviato in una variabile membro di classe privata accessibile tramite una funzione pubblica della classe helper.

   SampleSpatialAnchorHelper::SampleSpatialAnchorHelper(SpatialAnchorStore^ anchorStore)
   {
       m_anchorStore = anchorStore;
       m_anchorMap = ref new Platform::Collections::Map<String^, SpatialAnchor^>();
   }

Nota

Non dimenticare di associare gli eventi di sospensione/ripresa per salvare e caricare l'archivio di ancoraggi.

   void HolographicSpatialAnchorStoreSampleMain::SaveAppState()
   {
       // For example, store information in the SpatialAnchorStore.
       if (m_spatialAnchorHelper != nullptr)
       {
           m_spatialAnchorHelper->TrySaveToAnchorStore();
       }
   }
   void HolographicSpatialAnchorStoreSampleMain::LoadAppState()
   {
       // For example, load information from the SpatialAnchorStore.
       LoadAnchorStore();
   }

Salvare il contenuto nell'archivio di ancoraggi

Quando il sistema sospende l'app, devi salvare gli ancoraggi nello spazio nell'archivio ancoraggi. È anche possibile scegliere di salvare ancoraggi nell'archivio ancoraggi in altri momenti, perché risulta necessario per l'implementazione dell'app.

Quando si è pronti per provare a salvare gli ancoraggi in memoria in SpatialAnchorStore, è possibile scorrere la raccolta e provare a salvarli.

   // TrySaveToAnchorStore: Stores all anchors from memory into the app's anchor store.
   //
   // For each anchor in memory, this function tries to store it in the app's AnchorStore. The operation will fail if
   // the anchor store already has an anchor by that name.
   //
   bool SampleSpatialAnchorHelper::TrySaveToAnchorStore()
   {
       // This function returns true if all the anchors in the in-memory collection are saved to the anchor
       // store. If zero anchors are in the in-memory collection, we will still return true because the
       // condition has been met.
       bool success = true;

       // If access is denied, 'anchorStore' will not be obtained.
       if (m_anchorStore != nullptr)
       {
           for each (auto& pair in m_anchorMap)
           {
               auto const& id = pair->Key;
               auto const& anchor = pair->Value;

               // Try to save the anchors.
               if (!m_anchorStore->TrySave(id, anchor))
               {
                   // This may indicate the anchor ID is taken, or the anchor limit is reached for the app.
                   success=false;
               }
           }
       }

       return success;
   }

Caricare il contenuto dall'archivio di ancoraggi quando l'app riprende

È possibile ripristinare gli ancoraggi salvati in AnchorStore trasferendoli dall'oggetto IMapView dell'archivio di ancoraggio al database in memoria di SpatialAnchors quando l'app riprende o in qualsiasi momento.

Per ripristinare gli ancoraggi da SpatialAnchorStore, ripristinare ogni ancoraggio a cui si è interessati nella raccolta in memoria.

È necessario un database in memoria personalizzato di SpatialAnchors per associare stringhe ai spatialAnchor creati dall'utente. Nel codice di esempio scegliamo di usare windows::Foundation::Collections::IMap per archiviare gli ancoraggi, semplificando l'uso della stessa chiave e dello stesso valore di dati per SpatialAnchorStore.

   // This is an in-memory anchor list that is separate from the anchor store.
   // These anchors may be used, reasoned about, and so on before committing the collection to the store.
   Windows::Foundation::Collections::IMap<Platform::String^, Windows::Perception::Spatial::SpatialAnchor^>^ m_anchorMap;

Nota

Un ancoraggio ripristinato potrebbe non essere immediatamente aggiornabile. Ad esempio, potrebbe trattarsi di un ancoraggio in una stanza separata o in un edificio diverso. Gli ancoraggi recuperati da AnchorStore devono essere testati per verificare la individuabilità prima di usarli.


Nota

In questo codice di esempio vengono recuperati tutti gli ancoraggi da AnchorStore. Questo non è un requisito; la tua app può anche scegliere e scegliere un determinato subset di ancoraggi usando valori di chiave stringa significativi per l'implementazione.

   // LoadFromAnchorStore: Loads all anchors from the app's anchor store into memory.
   //
   // The anchors are stored in memory using an IMap, which stores anchors using a string identifier. Any string can be used as
   // the identifier; it can have meaning to the app, such as "Game_Leve1_CouchAnchor," or it can be a GUID that is generated
   // by the app.
   //
   void SampleSpatialAnchorHelper::LoadFromAnchorStore()
   {
       // If access is denied, 'anchorStore' will not be obtained.
       if (m_anchorStore != nullptr)
       {
           // Get all saved anchors.
           auto anchorMapView = m_anchorStore->GetAllSavedAnchors();
           for each (auto const& pair in anchorMapView)
           {
               auto const& id = pair->Key;
               auto const& anchor = pair->Value;
               m_anchorMap->Insert(id, anchor);
           }
       }
   }

Cancellare l'archivio di ancoraggio, se necessario

In alcuni casi, è necessario cancellare lo stato dell'app e scrivere nuovi dati. Ecco come eseguire questa operazione con SpatialAnchorStore.

Usando la classe helper, non è quasi necessario eseguire il wrapping della funzione Clear. Si sceglie di eseguire questa operazione nell'implementazione di esempio, perché alla classe helper viene assegnata la responsabilità di possedere l'istanza spatialAnchorStore.

   // ClearAnchorStore: Clears the AnchorStore for the app.
   //
   // This function clears the AnchorStore. It has no effect on the anchors stored in memory.
   //
   void SampleSpatialAnchorHelper::ClearAnchorStore()
   {
       // If access is denied, 'anchorStore' will not be obtained.
       if (m_anchorStore != nullptr)
       {
           // Clear all anchors from the store.
           m_anchorStore->Clear();
       }
   }

Esempio: Correlazione dei sistemi di coordinate di ancoraggio ai sistemi di coordinate del frame di riferimento stazionario

Supponiamo di avere un ancoraggio e di voler correlare qualcosa nel sistema di coordinate dell'ancoraggio al SpatialStationaryReferenceFrame già in uso per gli altri contenuti. È possibile usare TryGetTransformTo per ottenere una trasformazione dal sistema di coordinate dell'ancoraggio a quella del frame di riferimento stazionario:

   // In this code snippet, someAnchor is a SpatialAnchor^ that has been initialized and is valid in the current environment.
   float4x4 anchorSpaceToCurrentCoordinateSystem;
   SpatialCoordinateSystem^ anchorSpace = someAnchor->CoordinateSystem;
   const auto tryTransform = anchorSpace->TryGetTransformTo(currentCoordinateSystem);
   if (tryTransform != nullptr)
   {
       anchorSpaceToCurrentCoordinateSystem = tryTransform->Value;
   }

Questo processo è utile per l'utente in due modi:

  1. Indica se i due frame di riferimento possono essere compresi l'uno rispetto all'altro e;
  2. In tal caso, fornisce una trasformazione per passare direttamente da un sistema di coordinate all'altro.

Con queste informazioni, si ha una conoscenza della relazione spaziale tra gli oggetti tra i due frame di riferimento.

Per il rendering, è spesso possibile ottenere risultati migliori raggruppando gli oggetti in base al frame di riferimento o all'ancoraggio originale. Eseguire un passaggio di disegno separato per ogni gruppo. Le matrici di visualizzazione sono più accurate per gli oggetti con trasformazioni del modello create inizialmente usando lo stesso sistema di coordinate.

Creare ologrammi usando un frame di riferimento collegato al dispositivo

In alcuni casi si vuole eseguire il rendering di un ologramma che rimane collegato alla posizione del dispositivo, ad esempio un pannello con informazioni di debug o un messaggio informativo quando il dispositivo è in grado di determinarne solo l'orientamento e non la posizione nello spazio. A tale scopo, viene usato un frame di riferimento associato.

La classe SpatialLocatorAttachedFrameOfReference definisce i sistemi di coordinate, che sono relativi al dispositivo anziché al mondo reale. Questo frame ha un'intestazione fissa rispetto all'ambiente circostante dell'utente che punta nella direzione in cui si trovava l'utente quando è stato creato il frame di riferimento. Da quel momento in poi, tutti gli orientamenti in questo frame di riferimento sono relativi a tale intestazione fissa, anche quando l'utente ruota il dispositivo.

Per HoloLens, l'origine del sistema di coordinate di questo frame si trova al centro della rotazione della testa dell'utente, in modo che la sua posizione non sia influenzata dalla rotazione della testa. L'app può specificare un offset relativo a questo punto per posizionare gli ologrammi davanti all'utente.

Per ottenere un oggetto SpatialLocatorAttachedFrameOfReference, usare la classe SpatialLocator e chiamare CreateAttachedFrameOfReferenceAtCurrentHeading.

Questo vale per l'intera gamma di dispositivi Windows Realtà mista.

Usare un frame di riferimento collegato al dispositivo

Queste sezioni illustrano le modifiche apportate al modello di app Windows Holographic per abilitare un frame di riferimento collegato al dispositivo usando questa API. Questo ologramma "collegato" funzionerà insieme agli ologrammi fissi o ancorati e può essere usato anche quando il dispositivo non è temporaneamente in grado di trovarne la posizione nel mondo.

Prima di tutto, il modello è stato modificato per archiviare un oggetto SpatialLocatorAttachedFrameOfReference anziché spatialStationaryFrameOfReference:

Da HolographicTagAlongSampleMain.h:

   // A reference frame attached to the holographic camera.
   Windows::Perception::Spatial::SpatialLocatorAttachedFrameOfReference^   m_referenceFrame;

Da HolographicTagAlongSampleMain.cpp:

   // In this example, we create a reference frame attached to the device.
   m_referenceFrame = m_locator->CreateAttachedFrameOfReferenceAtCurrentHeading();

Durante l'aggiornamento, si ottiene ora il sistema di coordinate al timestamp ottenuto da con la stima del fotogramma.

   // Next, we get a coordinate system from the attached frame of reference that is
   // associated with the current frame. Later, this coordinate system is used for
   // for creating the stereo view matrices when rendering the sample content.
   SpatialCoordinateSystem^ currentCoordinateSystem =
       m_referenceFrame->GetStationaryCoordinateSystemAtTimestamp(prediction->Timestamp);

Ottenere una posizione del puntatore spaziale e seguire lo sguardo fisso dell'utente

Si vuole che l'ologramma di esempio segua lo sguardo fisso dell'utente , in modo analogo al modo in cui la shell olografica può seguire lo sguardo dell'utente. A questo scopo, è necessario ottenere SpatialPointerPose dallo stesso timestamp.

SpatialPointerPose^ pose = SpatialPointerPose::TryGetAtTimestamp(currentCoordinateSystem, prediction->Timestamp);

Questo oggetto SpatialPointerPose contiene le informazioni necessarie per posizionare l'ologramma in base all'intestazione corrente dell'utente.

Per il comfort dell'utente, usiamo l'interpolazione lineare ("lerp") per regolare il cambiamento di posizione in un periodo di tempo. Questo è più comodo per l'utente che bloccare l'ologramma allo sguardo. La posizione dell'ologramma tag-lungo ci permette anche di stabilizzare l'ologramma smorzando il movimento. Se non avessimo fatto questa attenuazione, l'utente visualizzerebbe l'instabilità dell'ologramma a causa di ciò che normalmente sono considerati impercettibili movimenti della testa dell'utente.

Da StationaryQuadRenderer::P ositionHologram:

   const float& dtime = static_cast<float>(timer.GetElapsedSeconds());

   if (pointerPose != nullptr)
   {
       // Get the gaze direction relative to the given coordinate system.
       const float3 headPosition  = pointerPose->Head->Position;
       const float3 headDirection = pointerPose->Head->ForwardDirection;

       // The tag-along hologram follows a point 2.0m in front of the user's gaze direction.
       static const float distanceFromUser = 2.0f; // meters
       const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * headDirection);

       // Lerp the position, to keep the hologram comfortably stable.
       auto lerpedPosition = lerp(m_position, gazeAtTwoMeters, dtime * c_lerpRate);

       // This will be used as the translation component of the hologram's
       // model transform.
       SetPosition(lerpedPosition);
   }

Nota

Nel caso di un pannello di debug, è possibile scegliere di riposizionare l'ologramma al lato in modo che non struissi la visualizzazione. Ecco un esempio di come eseguire questa operazione.

Per StationaryQuadRenderer::P ositionHologram:

       // If you're making a debug view, you might not want the tag-along to be directly in the
       // center of your field of view. Use this code to position the hologram to the right of
       // the user's gaze direction.
       /*
       const float3 offset = float3(0.13f, 0.0f, 0.f);
       static const float distanceFromUser = 2.2f; // meters
       const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * (headDirection + offset));
       */

Ruotare l'ologramma per affrontare la fotocamera

Non è sufficiente posizionare l'ologramma, che in questo caso è un quad; è anche necessario ruotare l'oggetto per affrontare l'utente. Questa rotazione si verifica nello spazio globale, perché questo tipo di tabellone consente all'ologramma di rimanere parte dell'ambiente dell'utente. L'affissione dello spazio di visualizzazione non è così confortevole perché l'ologramma diventa bloccato per l'orientamento dello schermo; in tal caso, è anche necessario interpolare tra le matrici di visualizzazione sinistra e destra per acquisire una trasformazione di tabellone dello spazio di visualizzazione che non interrompe il rendering stereo. In questo caso, ruotamo sugli assi X e Z per affrontare l'utente.

Da StationaryQuadRenderer::Update:

   // Seconds elapsed since previous frame.
   const float& dTime = static_cast<float>(timer.GetElapsedSeconds());

   // Create a direction normal from the hologram's position to the origin of person space.
   // This is the z-axis rotation.
   XMVECTOR facingNormal = XMVector3Normalize(-XMLoadFloat3(&m_position));

   // Rotate the x-axis around the y-axis.
   // This is a 90-degree angle from the normal, in the xz-plane.
   // This is the x-axis rotation.
   XMVECTOR xAxisRotation = XMVector3Normalize(XMVectorSet(XMVectorGetZ(facingNormal), 0.f, -XMVectorGetX(facingNormal), 0.f));

   // Create a third normal to satisfy the conditions of a rotation matrix.
   // The cross product  of the other two normals is at a 90-degree angle to
   // both normals. (Normalize the cross product to avoid floating-point math
   // errors.)
   // Note how the cross product will never be a zero-matrix because the two normals
   // are always at a 90-degree angle from one another.
   XMVECTOR yAxisRotation = XMVector3Normalize(XMVector3Cross(facingNormal, xAxisRotation));

   // Construct the 4x4 rotation matrix.

   // Rotate the quad to face the user.
   XMMATRIX rotationMatrix = XMMATRIX(
       xAxisRotation,
       yAxisRotation,
       facingNormal,
       XMVectorSet(0.f, 0.f, 0.f, 1.f)
       );

   // Position the quad.
   const XMMATRIX modelTranslation = XMMatrixTranslationFromVector(XMLoadFloat3(&m_position));

   // The view and projection matrices are provided by the system; they are associated
   // with holographic cameras, and updated on a per-camera basis.
   // Here, we provide the model transform for the sample hologram. The model transform
   // matrix is transposed to prepare it for the shader.
   XMStoreFloat4x4(&m_modelConstantBufferData.model, XMMatrixTranspose(rotationMatrix * modelTranslation));

Eseguire il rendering dell'ologramma associato

Per questo esempio, si sceglie anche di eseguire il rendering dell'ologramma nel sistema di coordinate di SpatialLocatorAttachedReferenceFrame, in cui è stato posizionato l'ologramma. Se avessimo deciso di eseguire il rendering usando un altro sistema di coordinate, dovremmo acquisire una trasformazione dal sistema di coordinate del frame di riferimento collegato al dispositivo a tale sistema di coordinate.

Da HolographicTagAlongSampleMain::Render:

   // The view and projection matrices for each holographic camera will change
   // every frame. This function refreshes the data in the constant buffer for
   // the holographic camera indicated by cameraPose.
   pCameraResources->UpdateViewProjectionBuffer(
       m_deviceResources,
       cameraPose,
       m_referenceFrame->GetStationaryCoordinateSystemAtTimestamp(prediction->Timestamp)
       );

Ecco fatto! L'ologramma ora "insegui" una posizione di 2 metri davanti alla direzione dello sguardo fisso dell'utente.

Nota

Questo esempio carica anche contenuto aggiuntivo. Vedere StationaryQuadRenderer.cpp.

Gestione della perdita di rilevamento

Quando il dispositivo non è in grado di individuarsi nel mondo, l'app riscontra "rilevamento della perdita". Le app di Windows Realtà mista devono essere in grado di gestire tali interruzioni nel sistema di rilevamento posizionale. Queste interruzioni possono essere osservate e le risposte create usando l'evento LocatabilityChanged nell'oggetto SpatialLocator predefinito.

Da AppMain::SetHolographicSpace:

   // Be able to respond to changes in the positional tracking state.
   m_locatabilityChangedToken =
       m_locator->LocatabilityChanged +=
           ref new Windows::Foundation::TypedEventHandler<SpatialLocator^, Object^>(
               std::bind(&HolographicApp1Main::OnLocatabilityChanged, this, _1, _2)
               );

Quando l'app riceve un evento LocatabilityChanged, può modificare il comportamento in base alle esigenze. Ad esempio, nello stato PositionalTrackingInhibited, l'app può sospendere l'operazione normale ed eseguire il rendering di un ologramma lungo tag che visualizza un messaggio di avviso.

Il modello di app Windows Holographic include un gestore LocatabilityChanged già creato automaticamente. Per impostazione predefinita, viene visualizzato un avviso nella console di debug quando il rilevamento posizionale non è disponibile. Puoi aggiungere codice a questo gestore per fornire una risposta in base alle esigenze dell'app.

Da AppMain.cpp:

   void HolographicApp1Main::OnLocatabilityChanged(SpatialLocator^ sender, Object^ args)
   {
       switch (sender->Locatability)
       {
       case SpatialLocatability::Unavailable:
           // Holograms cannot be rendered.
           {
               String^ message = L"Warning! Positional tracking is " +
                                           sender->Locatability.ToString() + L".\n";
               OutputDebugStringW(message->Data());
           }
           break;

       // In the following three cases, it is still possible to place holograms using a
       // SpatialLocatorAttachedFrameOfReference.
       case SpatialLocatability::PositionalTrackingActivating:
           // The system is preparing to use positional tracking.

       case SpatialLocatability::OrientationOnly:
           // Positional tracking has not been activated.

       case SpatialLocatability::PositionalTrackingInhibited:
           // Positional tracking is temporarily inhibited. User action may be required
           // in order to restore positional tracking.
           break;

       case SpatialLocatability::PositionalTrackingActive:
           // Positional tracking is active. World-locked content can be rendered.
           break;
       }
   }

Mapping spaziale

Le API di mapping spaziale usano i sistemi di coordinate per ottenere le trasformazioni del modello per le mesh di superficie.

Vedi anche