Systemen coördineren in DirectX

Notitie

Dit artikel heeft betrekking op de verouderde systeemeigen WinRT-API's. Voor nieuwe systeemeigen app-projecten raden we u aan de OpenXR-API te gebruiken.

Coördinaatsystemen vormen de basis voor ruimtelijk inzicht dat wordt geboden door Windows Mixed Reality API's.

De huidige zittende VR- of VR-apparaten met één ruimte vormen één primair coördinatensysteem voor hun bijgehouden ruimte. Mixed Reality apparaten zoals HoloLens zijn ontworpen voor grote niet-gedefinieerde omgevingen, waarbij het apparaat de omgeving ontdekt en leert kennen terwijl de gebruiker rondloopt. Het apparaat past zich aan om de kennis over de ruimten van de gebruiker voortdurend te verbeteren, maar resulteert in coördinatiesystemen die hun relatie met elkaar gedurende de levensduur van de apps veranderen. Windows Mixed Reality ondersteunt een breed scala aan apparaten, variërend van zittende immersive headsets tot wereldverhechte referentieframes.

Notitie

De codefragmenten in dit artikel demonstreren momenteel het gebruik van C++/CX in plaats van C++17-compatibele C++/WinRT zoals gebruikt in de C++-holografische projectsjabloon. De concepten zijn equivalent voor een C++/WinRT-project, hoewel u de code moet vertalen.

Ruimtelijke coördinaatsystemen in Windows

Het kerntype dat wordt gebruikt om te redeneert over de werkelijke coördinaatsystemen in Windows, is het SpatialCoordinateSystem. Een exemplaar van dit type vertegenwoordigt een willekeurig coördinatensysteem, dat een methode biedt voor het ophalen van transformatiematrixgegevens die u kunt gebruiken om te transformeren tussen twee coördinatensystemen zonder de details van elk systeem te begrijpen.

Methoden die ruimtelijke informatie retourneren, accepteren de parameter SpatialCoordinateSystem, zodat u kunt bepalen in welk coördinatensysteem het nuttigst is om deze coördinaten te retourneren. Ruimtelijke informatie wordt weergegeven als punten, stralen of volumes in de omgeving van de gebruiker en de eenheden voor deze coördinaten zijn altijd in meters.

Een SpatialCoordinateSystem heeft een dynamische relatie met andere coördinatensystemen, waaronder systemen die de positie van het apparaat vertegenwoordigen. Het apparaat kan op elk gewenst moment bepaalde coördinatensystemen vinden en andere niet. Voor de meeste coördinaatsystemen moet uw app gereed zijn voor het verwerken van perioden waarin ze niet kunnen worden gevonden.

Uw toepassing mag SpatialCoordinateSystems niet rechtstreeks maken, maar ze moeten worden gebruikt via de Perception-API's. Er zijn drie primaire bronnen van coördinatensystemen in de Perception-API's, die elk betrekking hebben op een concept dat wordt beschreven op de pagina Coördinatensystemen :

Alle coördinatensystemen die door deze objecten worden geretourneerd, zijn rechtshandig, met +y omhoog, +x naar rechts en +z naar achteren. U kunt onthouden welke richting de positieve z-as wijst door de vingers van uw linker- of rechterhand in de positieve x-richting te wijzen en ze in de positieve y-richting te krullen. De richting die uw duim naar u toe of vandaan wijst, is de richting die de positieve z-as wijst voor dat coördinatensysteem. In de volgende afbeelding ziet u deze twee coördinatensystemen.

Coördinaatsystemen links en rechts
Coördinaatsystemen links en rechts

Gebruik de klasse SpatialLocator om een gekoppeld of stationair referentieframe te maken voor bootstrap in een SpatialCoordinateSystem op basis van de HoloLens-positie. Ga door naar de volgende sectie voor meer informatie over dit proces.

Hologrammen in de wereld plaatsen met behulp van een ruimtelijk podium

Het coördinatensysteem voor ondoorzichtige Windows Mixed Reality immersive headsets wordt geopend met behulp van de statische eigenschap SpatialStageFrameOfReference::Current. Deze API biedt:

  • Een coördinatensysteem
  • Informatie over of de speler zit of mobiel is
  • De grens van een veilig gebied om rond te lopen als de speler mobiel is
  • Een indicatie van de richting van de headset.
  • Een gebeurtenis-handler voor updates van de ruimtelijke fase.

Eerst krijgen we de ruimtelijke fase en abonneren we ons op updates:

Code voor initialisatie van ruimtelijke fase

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

In de methode OnCurrentChanged moet uw app de ruimtelijke fase inspecteren en de spelerervaring bijwerken. In dit voorbeeld bieden we een visualisatie van de fasegrens en de beginpositie die door de gebruiker zijn opgegeven, en het weergavebereik en het bereik van de verplaatsingseigenschappen van de fase. We vallen ook terug op ons eigen stationaire coördinatensysteem, wanneer een fase niet kan worden verstrekt.

Code voor ruimtelijke fase-update

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

De set hoekpunten die de fasegrens definiëren, wordt met de klok mee opgegeven. De Windows Mixed Reality shell tekent een hek bij de grens wanneer de gebruiker deze nadert, maar u kunt het beloopbare gebied voor uw eigen doeleinden triangulariseren. Het volgende algoritme kan worden gebruikt om de fase te triangulariseren.

Code voor driehoeksvorming in ruimtelijke fase

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

Hologrammen in de wereld plaatsen met behulp van een stationair referentiekader

De klasse SpatialStationaryFrameOfReference vertegenwoordigt een referentiekader dat stationair blijft ten opzichte van de omgeving van de gebruiker terwijl de gebruiker zich verplaatst. Dit referentiekader geeft prioriteit aan het stabiel houden van coördinaten in de buurt van het apparaat. Een belangrijk gebruik van een SpatialStationaryFrameOfReference is om te fungeren als het onderliggende wereldcoördinaatsysteem binnen een rendering-engine bij het weergeven van hologrammen.

Als u een SpatialStationaryFrameOfReference wilt ophalen, gebruikt u de klasse SpatialLocator en roept u CreateStationaryFrameOfReferenceAtCurrentLocation aan.

Vanuit de sjablooncode van de Windows Holographic-app:

           // 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();
  • Stationaire referentieframes zijn ontworpen om een optimale positie te bieden ten opzichte van de totale ruimte. Afzonderlijke posities binnen dat referentiekader mogen enigszins afwijken. Dit is normaal omdat het apparaat meer te weten komt over de omgeving.
  • Wanneer een nauwkeurige plaatsing van afzonderlijke hologrammen vereist is, moet een SpatialAnchor worden gebruikt om het individuele hologram te verankeren aan een positie in de echte wereld, bijvoorbeeld een punt dat de gebruiker aangeeft als van bijzonder belang. Ankerposities drijven niet af, maar kunnen worden gecorrigeerd; het anker gebruikt de gecorrigeerde positie te beginnen in het volgende frame nadat de correctie heeft plaatsgevonden.

Hologrammen in de wereld plaatsen met behulp van ruimtelijke ankers

Ruimtelijke ankers zijn een uitstekende manier om hologrammen op een specifieke plaats in de echte wereld te plaatsen, waarbij het systeem ervoor zorgt dat het anker in de loop van de tijd op zijn plaats blijft. In dit onderwerp wordt uitgelegd hoe u een anker maakt en gebruikt en hoe u met ankergegevens werkt.

U kunt een SpatialAnchor maken op elke positie en richting binnen het SpatialCoordinateSystem van uw keuze. Het apparaat moet dat coördinatensysteem op dit moment kunnen vinden en het systeem mag de limiet van ruimteankers niet hebben bereikt.

Zodra dit is gedefinieerd, past het coördinatensysteem van een SpatialAnchor zich voortdurend aan om de exacte positie en oriëntatie van de oorspronkelijke locatie te behouden. U kunt dit SpatialAnchor vervolgens gebruiken om hologrammen weer te geven die vast worden weergegeven in de omgeving van de gebruiker op die exacte locatie.

De effecten van de aanpassingen die het anker op zijn plaats houden, worden vergroot naarmate de afstand tot het anker toeneemt. U moet voorkomen dat inhoud wordt weergegeven ten opzichte van een anker dat zich meer dan ongeveer 3 meter van de oorsprong van dat anker bevindt.

De eigenschap CoördinaatSysteem krijgt een coördinatensysteem waarmee u inhoud ten opzichte van het anker kunt plaatsen, waarbij versoepeling wordt toegepast wanneer het apparaat de exacte locatie van het anker aanpast.

Gebruik de eigenschap RawCoordinateSystem en de bijbehorende gebeurtenis RawCoordinateSystemAdjusted om deze aanpassingen zelf te beheren.

Ruimtelijke ankers behouden en delen

U kunt een SpatialAnchor lokaal behouden met behulp van de klasse SpatialAnchorStore en deze vervolgens terughalen in een toekomstige app-sessie op hetzelfde HoloLens-apparaat.

Met behulp van Azure Spatial Anchors kunt u een duurzaam cloudanker maken op basis van een lokale SpatialAnchor, die uw app vervolgens kan vinden op meerdere HoloLens-, iOS- en Android-apparaten. Door een gemeenschappelijk ruimtelijk anker te delen op meerdere apparaten, kan elke gebruiker in realtime inhoud zien die ten opzichte van dat anker op dezelfde fysieke locatie wordt weergegeven.

U kunt Azure Spatial Anchors ook gebruiken voor asynchrone hologrampersistentie op HoloLens-, iOS- en Android-apparaten. Door een duurzaam ruimtelijk anker in de cloud te delen, kunnen meerdere apparaten in de loop van de tijd hetzelfde persistente hologram observeren, zelfs als deze apparaten niet tegelijkertijd aanwezig zijn.

Als u aan de slag wilt gaan met het bouwen van gedeelde ervaringen in uw HoloLens-app, kunt u de quickstart voor Azure Spatial Anchors HoloLens van 5 minuten uitproberen.

Zodra u aan de slag bent met Azure Spatial Anchors, kunt u ankers maken en zoeken op HoloLens. Walkthroughs zijn ook beschikbaar voor Android en iOS , zodat u dezelfde ankers op alle apparaten kunt delen.

SpatialAnchors maken voor holografische inhoud

Voor dit codevoorbeeld hebben we de app-sjabloon Windows Holographic aangepast om ankers te maken wanneer de ingedrukt-beweging wordt gedetecteerd. De kubus wordt vervolgens bij het anker geplaatst tijdens de renderpass.

Omdat meerdere ankers worden ondersteund door de helperklasse, kunnen we zoveel kubussen plaatsen als we willen om dit codevoorbeeld te gebruiken.

Notitie

De id's voor ankers zijn iets dat u in uw app beheert. In dit voorbeeld hebben we een naamgevingsschema gemaakt dat opeenvolgend is op basis van het aantal ankers dat momenteel is opgeslagen in de verzameling ankers van de 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);
           }
       }
   }

De SpatialAnchorStore asynchroon laden en in de cache opslaan

Laten we eens kijken hoe u een SampleSpatialAnchorHelper-klasse schrijft die helpt bij het afhandelen van deze persistentie, waaronder:

  • Het opslaan van een verzameling ankers in het geheugen, geïndexeerd door een Platform::String-sleutel.
  • Het laden van ankers uit de SpatialAnchorStore van het systeem, die gescheiden wordt gehouden van de lokale in-memory verzameling.
  • De lokale in-memory verzameling ankers opslaan in de SpatialAnchorStore wanneer de app hiervoor kiest.

U kunt als volgt SpatialAnchor-objecten opslaan in de SpatialAnchorStore.

Wanneer de klasse wordt gestart, vragen we de SpatialAnchorStore asynchroon aan. Dit omvat systeem-I/O omdat de API het ankerarchief laadt en deze API asynchroon wordt gemaakt, zodat de I/O niet blokkeert.

   // 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;
   });

U krijgt een SpatialAnchorStore die u kunt gebruiken om de ankers op te slaan. Dit is een IMapView die sleutelwaarden die tekenreeksen zijn, koppelt aan gegevenswaarden die SpatialAnchors zijn. In onze voorbeeldcode slaan we dit op in een variabele van een privéklasselid die toegankelijk is via een openbare functie van onze helperklasse.

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

Notitie

Vergeet niet om de gebeurtenissen voor onderbreken/hervatten te koppelen om het ankerarchief op te slaan en te laden.

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

Inhoud opslaan in het ankerarchief

Wanneer het systeem uw app onderbreekt, moet u uw ruimtelijke ankers opslaan in het ankerarchief. U kunt er ook voor kiezen om ankers op andere momenten op te slaan in het ankerarchief, omdat dit nodig is voor de implementatie van uw app.

Wanneer u klaar bent om te proberen de in-memory ankers op te slaan in de SpatialAnchorStore, kunt u uw verzameling doorlopen en proberen elke op te slaan.

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

Inhoud laden uit de ankeropslag wanneer de app wordt hervat

U kunt opgeslagen ankers in de AnchorStore herstellen door ze over te brengen van de IMapView van de ankeropslag naar uw eigen in-memory database van SpatialAnchors wanneer uw app wordt hervat of op elk gewenst moment.

Als u ankers wilt herstellen vanuit de SpatialAnchorStore, herstelt u elke die u interesseert naar uw eigen in-memory verzameling.

U hebt uw eigen in-memory database van SpatialAnchors nodig om Tekenreeksen te koppelen aan de SpatialAnchors die u maakt. In onze voorbeeldcode kiezen we ervoor om een Windows::Foundation::Collections::IMap te gebruiken om de ankers op te slaan, waardoor u eenvoudig dezelfde sleutel en gegevenswaarde kunt gebruiken voor de 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;

Notitie

Een anker dat is hersteld, kan mogelijk niet meteen worden verwijderd. Het kan bijvoorbeeld een anker zijn in een aparte ruimte of in een ander gebouw. Ankers die zijn opgehaald uit de AnchorStore, moeten worden getest op betrouwbaarheid voordat ze worden gebruikt.


Notitie

In deze voorbeeldcode halen we alle ankers op uit de AnchorStore. Dit is geen vereiste; uw app kan net zo goed een bepaalde subset van ankers kiezen met behulp van tekenreekssleutelwaarden die zinvol zijn voor uw implementatie.

   // 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);
           }
       }
   }

Het ankerarchief wissen, indien nodig

Soms moet u de app-status wissen en nieuwe gegevens schrijven. Dit doet u als volgt met de SpatialAnchorStore.

Met behulp van onze helperklasse is het bijna niet nodig om de functie Clear te verpakken. We kiezen ervoor om dit te doen in onze voorbeeld-implementatie, omdat onze helperklasse de verantwoordelijkheid krijgt om eigenaar te zijn van het SpatialAnchorStore-exemplaar.

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

Voorbeeld: Ankercoördinaatsystemen relateeren aan stationaire referentieframecoördinaatsystemen

Stel dat u een anker hebt en dat u iets in het coördinatensysteem van uw anker wilt relateren aan het SpatialStationaryReferenceFrame dat u al gebruikt voor uw andere inhoud. U kunt TryGetTransformTo gebruiken om een transformatie te krijgen van het coördinatensysteem van het anker naar dat van het stationaire referentieframe:

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

Dit proces is op twee manieren nuttig voor u:

  1. Het vertelt u of de twee referentieframes ten opzichte van elkaar kunnen worden begrepen, en;
  2. Als dat het zo is, biedt het u een transformatie om rechtstreeks van het ene coördinatensysteem naar het andere te gaan.

Met deze informatie hebt u inzicht in de ruimtelijke relatie tussen objecten tussen de twee referentieframes.

Voor rendering kunt u vaak betere resultaten verkrijgen door objecten te groeperen op basis van hun oorspronkelijke referentieframe of anker. Voer een afzonderlijke tekenpas uit voor elke groep. De weergavematrices zijn nauwkeuriger voor objecten met modeltransformaties die in eerste instantie worden gemaakt met hetzelfde coördinatensysteem.

Hologrammen maken met behulp van een aan het apparaat gekoppeld referentiekader

Soms wilt u een hologram weergeven dat gekoppeld blijft aan de locatie van het apparaat, bijvoorbeeld een paneel met foutopsporingsinformatie of een informatief bericht wanneer het apparaat alleen de richting kan bepalen en niet de positie in de ruimte. Hiervoor gebruiken we een gekoppeld referentiekader.

De klasse SpatialLocatorAttachedFrameOfReference definieert coördinatensystemen, die relatief zijn ten opzichte van het apparaat in plaats van de echte wereld. Dit frame heeft een vaste kop ten opzichte van de omgeving van de gebruiker die verwijst in de richting waar de gebruiker zich bevond toen het referentieframe werd gemaakt. Vanaf dan zijn alle standen in dit referentiekader relatief ten opzichte van die vaste kop, zelfs wanneer de gebruiker het apparaat draait.

Voor HoloLens bevindt de oorsprong van het coördinatensysteem van dit frame zich in het draaipunt van het hoofd van de gebruiker, zodat de positie ervan niet wordt beïnvloed door hoofdrotatie. Uw app kan een verschuiving ten opzichte van dit punt opgeven om hologrammen voor de gebruiker te plaatsen.

Als u een SpatialLocatorAttachedFrameOfReference wilt ophalen, gebruikt u de klasse SpatialLocator en roept u CreateAttachedFrameOfReferenceAtCurrentHeading aan.

Dit geldt voor alle Windows Mixed Reality apparaten.

Een referentieframe gebruiken dat aan het apparaat is gekoppeld

In deze secties wordt beschreven wat we hebben gewijzigd in de App-sjabloon Windows Holographic om een aan het apparaat gekoppeld referentiekader in te schakelen met behulp van deze API. Dit 'gekoppelde' hologram werkt naast stationaire of verankerde hologrammen en kan ook worden gebruikt wanneer het apparaat tijdelijk zijn positie in de wereld niet kan vinden.

Eerst hebben we de sjabloon gewijzigd om een SpatialLocatorAttachedFrameOfReference op te slaan in plaats van een SpatialStationaryFrameOfReference:

Van HolographicTagAlongSampleMain.h:

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

Van HolographicTagAlongSampleMain.cpp:

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

Tijdens de update verkrijgen we nu het coördinatensysteem op de tijdstempel die is verkregen van met de framevoorspelling.

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

Een ruimtelijke aanwijzerpositie krijgen en de gaze van de gebruiker volgen

We willen dat ons voorbeeld hologram de blik van de gebruiker volgt, vergelijkbaar met hoe de holografische shell de blik van de gebruiker kan volgen. Hiervoor moeten we de SpatialPointerPose ophalen uit dezelfde tijdstempel.

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

Deze SpatialPointerPose bevat de informatie die nodig is om het hologram te positioneren volgens de huidige kop van de gebruiker.

Voor het comfort van de gebruiker gebruiken we lineaire interpolatie ('lerp') om de verandering in positie gedurende een bepaalde periode soepeler te laten verlopen. Dit is comfortabeler voor de gebruiker dan het hologram te vergrendelen voor zijn of haar blik. Door de positie van het tag-along hologram te lereren, kunnen we ook het hologram stabiliseren door de beweging te dempen. Als we deze demping niet zouden uitvoeren, zou de gebruiker de hologram-jitter zien vanwege wat normaal gesproken wordt beschouwd als onmerkbare bewegingen van het hoofd van de gebruiker.

Uit 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);
   }

Notitie

In het geval van een foutopsporingsvenster kunt u ervoor kiezen om het hologram een beetje naar de zijkant te verplaatsen, zodat het de weergave niet belemmert. Hier volgt een voorbeeld van hoe u dat kunt doen.

Voor 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));
       */

Het hologram draaien om de camera onder ogen te zien

Het is niet voldoende om het hologram te plaatsen, wat in dit geval een quad is; we moeten ook het object draaien om de gebruiker onder ogen te zien. Deze rotatie vindt plaats in de wereldruimte, omdat dit type aanplakbord het hologram in staat stelt om deel uit te maken van de omgeving van de gebruiker. Het aanplakbord met de weergaveruimte is niet zo comfortabel omdat het hologram wordt vergrendeld aan de weergaverichting; in dat geval zou u ook moeten interpoleren tussen de linker- en rechteraanzichtmatrices om een weergaveruimte-reclamebordtransformatie te verkrijgen die de stereoweergave niet verstoort. Hier draaien we op de X- en Z-as om de gebruiker onder ogen te zien.

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

Het bijgevoegde hologram weergeven

Voor dit voorbeeld kiezen we er ook voor om het hologram weer te geven in het coördinatensysteem van het SpatialLocatorAttachedReferenceFrame, waar we het hologram hebben gepositioneerd. (Als we hadden besloten om te renderen met een ander coördinatensysteem, zouden we een transformatie moeten verkrijgen van het coördinatensysteem van het apparaatgekoppelde referentieframe naar dat coördinatensysteem.)

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

Dat is alles. Het hologram 'achtervolgt' nu een positie die zich 2 meter voor de staarrichting van de gebruiker bevindt.

Notitie

In dit voorbeeld wordt ook aanvullende inhoud geladen. Zie StationaryQuadRenderer.cpp.

Traceringsverlies verwerken

Wanneer het apparaat zichzelf niet in de wereld kan vinden, ondervindt de app 'traceringsverlies'. Windows Mixed Reality apps moeten dergelijke onderbrekingen van het positietraceringssysteem kunnen verwerken. Deze onderbrekingen kunnen worden waargenomen en reacties worden gemaakt met behulp van de gebeurtenis LocatabilityChanged op de standaard SpatialLocator.

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

Wanneer uw app een LocatabilityChanged-gebeurtenis ontvangt, kan het gedrag naar behoefte worden gewijzigd. In de status PositionalTrackingInhibited kan uw app bijvoorbeeld de normale bewerking onderbreken en een tag-along hologram weergeven waarin een waarschuwingsbericht wordt weergegeven.

De app-sjabloon Windows Holographic wordt geleverd met een LocatabilityChanged-handler die al voor u is gemaakt. Standaard wordt in de console voor foutopsporing een waarschuwing weergegeven wanneer positietracering niet beschikbaar is. U kunt code toevoegen aan deze handler om zo nodig een antwoord van uw app te geven.

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

Ruimtelijke toewijzing

De API's voor ruimtelijke toewijzing maken gebruik van coördinatensystemen om modeltransformaties voor oppervlaktegaas te verkrijgen.

Zie ook