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 begrip dat wordt aangeboden door Windows Mixed Reality API's.

De huidige virtuele vr- of eenkamer-VR-apparaten zorgen voor één primair coördinaatsysteem voor hun bijgehouden ruimte. Mixed Reality apparaten zoals HoloLens zijn ontworpen voor grote niet-gedefinieerde omgevingen, waarbij het apparaat de omgeving ontdekt en leert terwijl de gebruiker rondloopt. Het apparaat wordt aangepast aan het voortdurend verbeteren van de kennis over de ruimten van de gebruiker, maar resulteert in het coördineren van systemen die hun relatie met elkaar wijzigen gedurende de levensduur van de apps. Windows Mixed Reality ondersteunt een breed scala aan apparaten, variërend van zitsluitende headsets tot wereldgebonden referentieframes.

Notitie

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

Ruimtelijke coördinaatsystemen in Windows

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

Methoden die ruimtelijke informatie retourneren, accepteren een parameter SpatialCoördinaatSystem, zodat u het coördinatensysteem kunt bepalen waarin deze coördinaten het nuttigst zijn om te worden geretourneerd. Ruimtelijke informatie wordt weergegeven als punten, stralen of volumes in de omgeving van de gebruiker, en de eenheden voor deze coördinaten bevinden zich altijd in meters.

Een SpatialCoordinateSystem heeft een dynamische relatie met andere coördinaatsystemen, inclusief systemen die de positie van het apparaat vertegenwoordigen. Op elk moment kan het apparaat enkele coördinatensystemen vinden en niet andere. Voor de meeste coördinaatsystemen moet uw app gereed zijn voor het afhandelen van perioden waarin ze zich niet kunnen bevinden.

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

Alle coördinatensystemen die door deze objecten worden geretourneerd, zijn rechtshandig, met +y omhoog, +x rechts en +z achteruit. U kunt onthouden welke richting de positieve z-as punten door de vingers van uw linker- of rechterhand in de positieve x-richting aan te wijzen en ze in de positieve y-richting te krullen. De richting van uw duimpunten, naar of van u af, is de richting die de positieve z-aspunten voor dat coördinaatsysteem. In de volgende afbeelding ziet u deze twee coördinaatsystemen.

Left-hand and right-hand coordinate systems
Systemen voor linker- en rechtercoördinaat

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

Hologrammen in de wereld plaatsen met behulp van een ruimtelijk stadium

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

  • Een coördinaatsysteem
  • 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 of de headset directioneel is.
  • Een gebeurtenis-handler voor updates voor 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 is opgegeven door de gebruiker en het bereik van weergave- en bewegingseigenschappen van de fase. We vallen ook terug op ons eigen stationaire coördinaatsysteem, wanneer er geen fase 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, worden in de klokgewijze volgorde aangeboden. De Windows Mixed Reality shell tekent een omheining bij de grens wanneer de gebruiker deze benadert, maar u kunt het beloopgebied voor uw eigen doeleinden triangulariseren. Het volgende algoritme kan worden gebruikt om de fase te triangulariseren.

Code voor triangularisatie van ruimtelijke fases

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 ter wereld plaatsen met behulp van een stationair referentiekader

De klasse SpatialStationaryFrameOfReference vertegenwoordigt een referentieframe dat stationair blijft ten opzichte van de omgeving van de gebruiker terwijl de gebruiker zich verplaatst. In dit referentiekader wordt prioriteit gegeven 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.

In 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 best passende positie te bieden ten opzichte van de totale ruimte. Afzonderlijke posities binnen dat referentieframe mogen enigszins zweven. Dit is normaal, omdat het apparaat meer informatie krijgt over de omgeving.
  • Wanneer nauwkeurige plaatsing van afzonderlijke hologrammen is vereist, moet een SpatialAnchor worden gebruikt om het afzonderlijke hologram te verankeren in een positie in de echte wereld, bijvoorbeeld een punt dat de gebruiker van bijzonder belang aangeeft. Ankerposities zweven niet, maar kunnen worden gecorrigeerd; het anker gebruikt de gecorrigeerde positie die begint in het volgende frame nadat de correctie is opgetreden.

Hologrammen in de wereld plaatsen met behulp van ruimtelijke ankers

Ruimtelijke ankers zijn een geweldige manier om hologrammen op een specifieke plek 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 gewenste positie en oriëntatie binnen het SpatialCoordinateSystem van uw keuze. Het apparaat moet dat coördinaatsysteem op dit moment kunnen vinden en het systeem mag de limiet van ruimtelijke ankers niet hebben bereikt.

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

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

De eigenschap CoordinateSystem krijgt een coördinaatsysteem 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 RawCoordinateSystemAdjusted-gebeurtenis om deze aanpassingen zelf te beheren.

Ruimtelijke ankers behouden en delen

U kunt een SpatialAnchor lokaal persistent maken met behulp van de Klasse SpatialAnchorStore en deze vervolgens terugzetten in een toekomstige app-sessie op hetzelfde HoloLens apparaat.

Met behulp van Azure Spatial Anchors kunt u een duurzaam cloudanker maken vanuit een lokaal SpatialAnchor, dat 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 inhoud zien die ten opzichte van dat anker in realtime 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 van 5 minuten HoloLens 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 sjabloon voor de Windows Holographic-app gewijzigd om ankers te maken wanneer het gebaar Ingedrukt wordt gedetecteerd. De kubus wordt vervolgens op het anker geplaatst tijdens de renderpas.

Omdat meerdere ankers worden ondersteund door de helperklasse, kunnen we zoveel kubussen plaatsen als we dit codevoorbeeld willen 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);
           }
       }
   }

Asynchroon laden en cachen, de SpatialAnchorStore

Laten we eens kijken hoe u een SampleSpatialAnchorHelper-klasse schrijft waarmee u deze persistentie kunt afhandelen, waaronder:

  • Het opslaan van een verzameling ankers in het geheugen, geïndexeerd door een Platform::String-sleutel.
  • Ankers laden uit de SpatialAnchorStore van het systeem, die gescheiden blijft van de lokale in-memory verzameling.
  • Sla de lokale verzameling ankers in het geheugen op in de SpatialAnchorStore wanneer de app ervoor kiest om dit te doen.

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 wanneer de API de ankeropslag laadt en deze API asynchroon wordt gemaakt, zodat de I/O niet wordt geblokkeerd.

   // 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 koppelt die Tekenreeksen zijn, met gegevenswaarden die SpatialAnchors zijn. In onze voorbeeldcode slaan we deze op in een variabele voor privéklasseleden 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 de gebeurtenissen te onderbreken/hervatten 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 de ankeropslag. U kunt er ook voor kiezen om ankers op andere momenten op te slaan in de ankeropslag, omdat u het nodig vindt voor de implementatie van uw app.

Wanneer u klaar bent om de ankers in het geheugen op te slaan in de SpatialAnchorStore, kunt u uw verzameling doorlopen en proberen om elk anker 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 uit de ankeropslag laden wanneer de app wordt hervat

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

Als u ankers uit de SpatialAnchorStore wilt herstellen, herstelt u elk anker waarin u geïnteresseerd bent in 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 voor de SpatialAnchorStore kunt gebruiken.

   // 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 wordt hersteld, is mogelijk niet meteen te locatable. Het kan bijvoorbeeld een anker zijn in een aparte kamer of in een ander gebouw. Ankers die zijn opgehaald uit de AnchorStore moeten worden getest op afkakaatbaarheid 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 en kiezen met behulp van sleutelwaarden voor tekenreeksen 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);
           }
       }
   }

Wis het ankerarchief, 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 voorbeeldimplementatie, 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: Het koppelen van ankercoördinaatsystemen aan stationaire referentieframecoördinaatsystemen

Stel dat u een anker hebt en u iets in het coördinaatsysteem van uw anker wilt koppelen aan het SpatialStationaryReferenceFrame dat u al gebruikt voor uw andere inhoud. U kunt TryGetTransformTo gebruiken om een transformatie op te halen 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 handig voor u op twee manieren:

  1. Het geeft aan of de twee referentieframes ten opzichte van elkaar kunnen worden begrepen, en;
  2. Zo ja, dan biedt het u een transformatie om rechtstreeks van het ene coördinaatsysteem 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 het oorspronkelijke referentieframe of anker. Voer voor elke groep een afzonderlijke tekenpas uit. De weergavematrices zijn nauwkeuriger voor objecten met modeltransformaties die in eerste instantie met hetzelfde coördinaatsysteem worden gemaakt.

Hologrammen maken met behulp van een referentiekader dat aan het apparaat is gekoppeld

Er zijn momenten waarop u een hologram wilt weergeven dat is gekoppeld aan de locatie van het apparaat, bijvoorbeeld een paneel met foutopsporingsgegevens of een informatief bericht wanneer het apparaat alleen de afdrukstand kan bepalen en niet de positie ervan in de ruimte. Hiervoor gebruiken we een bijgevoegd referentiekader.

De klasse SpatialLocatorAttachedFrameOfReference definieert coördinatensystemen die relatief zijn ten opzichte van het apparaat in plaats van in de echte wereld. Dit frame heeft een vaste kop ten opzichte van de omgeving van de gebruiker die verwijst naar de richting waarmee de gebruiker te maken had toen het referentieframe werd gemaakt. Vanaf dat jaar zijn alle oriëntaties in dit referentiekader relatief ten opzichte van die vaste kop, zelfs als de gebruiker het apparaat roteert.

Voor HoloLens bevindt de oorsprong van het coördinaatsysteem van dit frame zich in het midden van de draaiing van het hoofd van de gebruiker, zodat de positie ervan niet wordt beïnvloed door hoofdrotatie. Uw app kan een offset opgeven ten opzichte van dit punt om hologrammen vóór de gebruiker te plaatsen.

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

Dit geldt voor het hele bereik van Windows Mixed Reality apparaten.

Een referentieframe gebruiken dat is gekoppeld aan het apparaat

In deze secties wordt besproken wat we hebben gewijzigd in de sjabloon voor de holographic-app Windows om een referentiekader dat is gekoppeld aan het apparaat 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ördinaatsysteem op het tijdstempel dat is verkregen 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);

Haal een ruimtelijke aanwijzer op en volg de gaze van de gebruiker

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 hetzelfde tijdstempel.

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

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

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

Van 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 foutopsporingspaneel kunt u ervoor kiezen om het hologram een beetje naar de zijkant te verplaatsen, zodat deze uw 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 te zien

Het is niet voldoende om het hologram te plaatsen, wat in dit geval een quad is; we moeten het object ook draaien om de gebruiker te zien. Deze rotatie vindt plaats in de wereld, omdat dit type reclameborden het hologram in staat stelt een deel van de omgeving van de gebruiker te blijven. Het reclamebord voor de weergaveruimte is niet zo comfortabel omdat het hologram wordt vergrendeld voor de weergavestand; In dat geval moet u ook interpoleren tussen de matrices van de linker- en rechterweergave om een view-space reclametransformatie te verkrijgen die de stereoweergave niet verstoort. Hier draaien we op de X- en Z-assen om de gebruiker 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

In dit voorbeeld kiezen we er ook voor om het hologram weer te geven in het coördinaatsysteem van het SpatialLocatorAttachedReferenceFrame, waar we het hologram hebben positioneerd. (Als we hadden besloten om te renderen met behulp van een ander coördinaatsysteem, moeten we een transformatie verkrijgen van het coördinaatsysteem van het apparaat gekoppelde referentieframe naar dat coördinatensysteem.)

Van 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 zal nu een positie "achtervolgen" die 2 meter voor de richting van de gebruiker ligt.

Notitie

In dit voorbeeld wordt ook extra inhoud geladen: zie StationaryQuadRenderer.cpp.

Traceringsverlies verwerken

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

Van 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 deze het gedrag zo nodig wijzigen. In de status PositionalTrackingInhibited kan uw app bijvoorbeeld de normale werking onderbreken en een tag-along hologram weergeven waarin een waarschuwingsbericht wordt weergegeven.

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

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ördinaatsystemen om modeltransformaties voor oppervlaktenetten op te halen.

Zie ook