Delen via


Coördinaatsystemen 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 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 om voortdurend kennis over de ruimten van de gebruiker te verbeteren, 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 spectrum van 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 wordt 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 te redeneren over echte coördinaatsystemen in Windows, is het SpatialCoördinaatSysteem. Een exemplaar van dit type vertegenwoordigt een willekeurig coördinaatsysteem, dat een methode biedt voor het ophalen van transformatiematrixgegevens die u kunt gebruiken om tussen twee coördinaatsystemen te transformeren zonder de details van beide 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 zijn altijd in meters.

Een SpatialCoordinateSystem heeft een dynamische relatie met andere coördinatensystemen, 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 SpatialCoördinaSystems 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-aspunten zijn 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 duim wijst naar of weg van u, is de richting die de positieve z-aspunten voor dat coördinaatsysteem. In de volgende afbeelding ziet u deze twee coördinatensystemen.

Systemen voor linker- en rechtercoördinaat
Systemen voor linker- en rechtercoördinaat

Gebruik de Klasse SpatialLocator om een gekoppeld of stationair referentiekader te maken om te bootstrapen naar een SpatialCoördinaatSysteem 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 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ö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 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 geven we een visualisatie van de fasegrens en de beginpositie die is opgegeven door de gebruiker en het bereik van de weergave en het bereik van bewegingseigenschappen van de fase. We vallen ook terug op ons eigen stationaire coördinaatsysteem, wanneer er geen fase kan worden gegeven.

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 opgegeven. De Windows Mixed Reality-shell tekent een omheining op de grens wanneer de gebruiker deze benadert, maar u kunt het beloopgebied mogelijk triangulariseren voor uw eigen doeleinden. Het volgende algoritme kan worden gebruikt om de fase te triangulariseren.

Code voor ruimtelijke fase triangularisatie

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 best passende positie te bieden ten opzichte van de totale ruimte. Afzonderlijke posities binnen dat referentiekader mogen enigszins afdrijven. Dit is normaal omdat het apparaat meer leert 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 aangeeft dat het van bijzonder belang is. Ankerposities drijven niet af, 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 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 werkt met ankergegevens.

U kunt een SpatialAnchor maken op elke positie en oriëntatie binnen het SpatialCoördinaatSysteem 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.

Zodra het coördinaatsysteem van een SpatialAnchor is gedefinieerd, wordt het continu aangepast 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. Vermijd het weergeven van inhoud ten opzichte van een anker dat meer dan 3 meter van de oorsprong van dat anker is.

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

Gebruik de eigenschap RawCoördinaatSystem en de bijbehorende RawCoördinaatSystemAdjusted-gebeurtenis om deze aanpassingen zelf te beheren.

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.

SpatialAnchors maken voor holografische inhoud

Voor dit codevoorbeeld hebben we de sjabloon voor de Windows Holographic-app gewijzigd om ankers te maken wanneer de beweging 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 beheert in uw app. In dit voorbeeld hebben we een naamgevingsschema gemaakt dat opeenvolgend is gebaseerd op 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 in-memory verzameling ankers 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 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 koppelt aan tekenreeksen, met gegevenswaarden die SpatialAnchors zijn. In onze voorbeeldcode slaan we deze op in een privéklasselidvariabele 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 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 ze 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 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, zodat u eenvoudig dezelfde sleutel en gegevenswaarde voor 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 is hersteld, is mogelijk niet meteen te locatable. 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 verkaatsbaarheid 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 ankers kiezen en 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);
           }
       }
   }

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

Stel dat u een anker hebt en dat 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 van het coördinaatsysteem van het anker naar dat van het stationaire referentieframe op te halen:

   // 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 hun oorspronkelijke referentiekader of anker. Voer een afzonderlijke tekenpas uit voor elke groep. De weergavematrices zijn nauwkeuriger voor objecten met modeltransformaties die in eerste instantie met hetzelfde coördinaatsysteem worden gemaakt.

Hologrammen maken met behulp van een aan het apparaat gekoppeld referentiekader

Er zijn momenten waarop u een hologram wilt weergeven dat gekoppeld blijft aan de locatie van het apparaat, bijvoorbeeld een paneel met foutopsporingsgegevens of een informatief bericht wanneer het apparaat alleen de afdrukstand en niet de positie in de ruimte kan bepalen. Hiervoor gebruiken we een bijgevoegd 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 naar de richting die de gebruiker 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 windows Holographic-app-sjabloon om een aan het apparaat gekoppeld referentiekader in te schakelen met behulp van deze API. Dit 'bijgevoegde' hologram werkt naast stationaire of verankerde hologrammen en kan ook worden gebruikt wanneer het apparaat tijdelijk de positie ervan niet kan vinden in de wereld.

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;

Vanaf 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 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 aanwijzerpositie 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 positie gedurende een bepaalde periode te vereffenen. Dit is comfortabeler voor de gebruiker dan het hologram te vergrendelen naar hun blik. Door de positie van het tag-along hologram te lereren, kunnen we het hologram ook stabiliseren door de beweging te dempen. Als we dit niet dempen, zou de gebruiker de hologram jitter zien vanwege wat normaal gesproken als ondoordringbare 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 het de weergave niet belemmert. Hier volgt een voorbeeld van hoe u dit 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));
       */

Draai het hologram om de camera te zien

Het is niet genoeg om het hologram te positioneren, wat in dit geval een quad is; we moeten het object ook draaien om de gebruiker te zien. Deze rotatie vindt plaats in de wereldruimte, omdat dit type reclamebording het hologram in staat stelt een deel van de omgeving van de gebruiker te blijven. Weergaveruimte aanplakborden 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 reclamebordtransformatie 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 een ander coördinatensysteem te gebruiken, moeten we een transformatie verkrijgen van het door het apparaat gekoppelde referentieframe naar dat coördinatensysteem.)

Vanuit 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 het! In het hologram wordt nu een positie van 2 meter voor de richting van de gebruiker gezocht.

Notitie

In dit voorbeeld wordt ook extra 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 positionele trackingsysteem kunnen afhandelen. Deze onderbrekingen kunnen worden waargenomen en reacties die zijn 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 dat een waarschuwingsbericht weergeeft.

De Windows Holographic-app-sjabloon 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.

Vanuit 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