Układy współrzędnych w directx

Uwaga

Ten artykuł dotyczy starszych natywnych interfejsów API winRT. W przypadku nowych projektów aplikacji natywnych zalecamy używanie interfejsu API OpenXR.

Układy współrzędnych stanowią podstawę zrozumienia przestrzennego oferowanego przez interfejsy API Windows Mixed Reality.

Dzisiejsze siedzące urządzenia VR lub VR w jednym pokoju ustanawiają jeden podstawowy system współrzędnych dla ich śledzonej przestrzeni. Mixed Reality urządzenia, takie jak HoloLens, są przeznaczone dla dużych niezdefiniowanych środowisk, a urządzenie odnajduje i uczy się o jego otoczeniu, gdy użytkownik idzie. Urządzenie dostosowuje się do ciągłego ulepszania wiedzy na temat pomieszczeń użytkownika, ale powoduje koordynowanie systemów, które zmieniają ich relację ze sobą w okresie istnienia aplikacji. Windows Mixed Reality obsługuje szerokie spektrum urządzeń, począwszy od siedzących immersywnych zestawów słuchawkowych przez dołączone do świata ramki referencyjne.

Uwaga

Fragmenty kodu w tym artykule pokazują obecnie użycie języka C++/CX, a nie C++17 zgodnego z językiem C++/WinRT używanego w szablonie projektu holograficznego języka C++. Pojęcia są równoważne projektowi C++/WinRT, chociaż trzeba będzie przetłumaczyć kod.

Systemy współrzędnych przestrzennych w systemie Windows

Podstawowym typem używanym do wnioskowania o rzeczywistych systemach współrzędnych w systemie Windows jest SpatialCoordinateSystem. Wystąpienie tego typu reprezentuje dowolny układ współrzędnych, zapewniając metodę uzyskiwania danych macierzy przekształcania, których można użyć do przekształcania między dwoma systemami współrzędnych bez zrozumienia szczegółów każdego z nich.

Metody zwracające informacje przestrzenne akceptują parametr SpatialCoordinateSystem, aby umożliwić podjęcie decyzji o układzie współrzędnych, w którym najbardziej przydatne jest zwrócenie tych współrzędnych. Informacje przestrzenne są reprezentowane jako punkty, promienie lub woluminy w otoczeniu użytkownika, a jednostki dla tych współrzędnych zawsze będą w metrach.

System SpatialCoordinateSystem ma dynamiczną relację z innymi systemami współrzędnych, w tym tymi, które reprezentują położenie urządzenia. W dowolnym momencie urządzenie może zlokalizować niektóre układy współrzędnych, a nie inne. W przypadku większości systemów współrzędnych aplikacja musi być gotowa do obsługi okresów czasu, w których nie można ich znaleźć.

Aplikacja nie powinna bezpośrednio tworzyć systemów SpatialCoordinateSystems — raczej powinny być używane za pośrednictwem interfejsów API percepcji. Istnieją trzy podstawowe źródła systemów współrzędnych w interfejsach API percepcji, z których każda jest mapowana na koncepcję opisaną na stronie Współrzędnych systemów :

Wszystkie układy współrzędnych zwracane przez te obiekty są praworęczne, z +y w górę, +x w prawo i +z do tyłu. Można pamiętać, który kierunek dodatnich punktów osi z, wskazując palce lewej lub prawej ręki w dodatnim kierunku x i zwijając je w pozytywny kierunek y. Kierunek wskazywania kciuka w kierunku lub od ciebie to kierunek, w którym dodatnia oś z wskazuje ten układ współrzędnych. Na poniższej ilustracji przedstawiono te dwa układy współrzędnych.

Układy współrzędnych po lewej i prawej stronie
Układy współrzędnych po lewej i prawej stronie

Użyj klasy SpatialLocator , aby utworzyć dołączoną lub nieruchomą ramę odniesienia do bootstrap w systemie SpatialCoordinateSystem na podstawie położenia urządzenia HoloLens. Przejdź do następnej sekcji, aby dowiedzieć się więcej o tym procesie.

Umieszczanie hologramów na świecie przy użyciu etapu przestrzennego

System współrzędnych nieprzezroczystych Windows Mixed Reality immersywnych zestawów nagłownych jest uzyskiwany przy użyciu właściwości static SpatialStageFrameOfReference::Current. Ten interfejs API zapewnia:

  • Układ współrzędnych
  • Informacje o tym, czy gracz jest siedzący, czy mobilny
  • Granica bezpiecznego obszaru do spaceru, jeśli gracz jest mobilny
  • Wskazanie, czy zestaw słuchawkowy jest kierunkowy.
  • Procedura obsługi zdarzeń na potrzeby aktualizacji etapu przestrzennego.

Najpierw uzyskujemy etap przestrzenny i subskrybujemy aktualizacje:

Kod inicjowania etapu przestrzennego

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

W metodzie OnCurrentChanged aplikacja powinna sprawdzać etap przestrzenny i aktualizować środowisko odtwarzacza. W tym przykładzie udostępniamy wizualizację granicy etapu i pozycję początkową określoną przez użytkownika oraz zakres widoków i zakres właściwości ruchu etapu. Wracamy również do naszego nieruchomego układu współrzędnych, gdy nie można zapewnić etapu.

Kod aktualizacji etapu przestrzennego

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

Zestaw wierzchołków definiujących granicę etapu jest dostarczany w kolejności zgodnie z ruchem wskazówek zegara. Powłoka Windows Mixed Reality rysuje ogrodzenie na granicy, gdy użytkownik zbliża się do niego, ale możesz chcieć uściślać obszar spacerowy do własnych celów. Poniższy algorytm może służyć do triangularyzacji etapu.

Kod triangularyzacji etapu przestrzennego

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

Umieść hologramy na świecie przy użyciu stacjonarnej ramy odniesienia

Klasa SpatialStationaryFrameOfReference reprezentuje ramę odwołania , która pozostaje nieruchoma w stosunku do otoczenia użytkownika podczas poruszania się użytkownika. Ta ramka odniesienia określa priorytet utrzymania współrzędnych stabilnych w pobliżu urządzenia. Jednym z kluczowych zastosowań elementu SpatialStationaryFrameOfReference jest działanie jako podstawowy system współrzędnych świata w aucie renderowania podczas renderowania hologramów.

Aby uzyskać element SpatialStationaryFrameOfReference, użyj klasy SpatialLocator i wywołaj metodę CreateStationaryFrameOfReferenceAtCurrentLocation.

Z poziomu kodu szablonu aplikacji Windows Holographic:

           // The simplest way to render world-locked holograms is to create a stationary reference frame
           // when the app is launched. This is roughly analogous to creating a "world" coordinate system
           // with the origin placed at the device's position as the app is launched.
           referenceFrame = locator.CreateStationaryFrameOfReferenceAtCurrentLocation();
  • Nieruchome ramy odniesienia zaprojektowano w celu zapewnienia najlepszej pozycji w stosunku do całej przestrzeni. Poszczególne pozycje w tej ramce odniesienia mogą nieznacznie dryfować. Jest to normalne, ponieważ urządzenie dowie się więcej o środowisku.
  • Gdy wymagane jest dokładne rozmieszczenie pojedynczych hologramów, należy użyć elementu SpatialAnchor do zakotwiczenia pojedynczego hologramu w pozycji w świecie rzeczywistym — na przykład punkt, który użytkownik wskazuje na szczególne zainteresowanie. Położenia kotwicy nie dryfują, ale można je poprawić; kotwica będzie używać poprawionej pozycji rozpoczynającej się w następnej ramce po wystąpieniu korekty.

Umieszczanie hologramów na świecie przy użyciu kotwic przestrzennych

Kotwice przestrzenne to doskonały sposób umieszczania hologramów w określonym miejscu w świecie rzeczywistym, dzięki czemu system zapewnia, że kotwica pozostaje w miejscu w czasie. W tym temacie opisano sposób tworzenia i używania kotwicy oraz pracy z danymi zakotwiczenia.

Możesz utworzyć element SpatialAnchor w dowolnej pozycji i orientacji w wybranym obszarze SpatialCoordinateSystem. Urządzenie musi mieć możliwość zlokalizowania tego systemu współrzędnych w tej chwili, a system nie może osiągnąć limitu zakotwiczeń przestrzennych.

Po zdefiniowaniu system współrzędnych elementu SpatialAnchor dostosowuje się stale, aby zachować dokładną pozycję i orientację jej początkowej lokalizacji. Następnie możesz użyć tej funkcji SpatialAnchor do renderowania hologramów, które będą wyświetlane na stałe w otoczeniu użytkownika w tej dokładnej lokalizacji.

Efekty korekt, które utrzymują kotwicę na miejscu, są powiększane w miarę wzrostu odległości od kotwicy. Należy unikać renderowania zawartości względem kotwicy, która jest większa niż około 3 metrów od źródła tej kotwicy.

Właściwość CoordinateSystem pobiera system współrzędnych, który umożliwia umieszczenie zawartości względem kotwicy z złagodzeniem zastosowanym, gdy urządzenie dostosowuje dokładną lokalizację kotwicy.

Użyj właściwości RawCoordinateSystem i odpowiadającego mu zdarzenia RawCoordinateSystemAdjusted , aby samodzielnie zarządzać tymi korektami.

Utrwalanie i udostępnianie kotwic przestrzennych

Możesz utrwalać obiekt SpatialAnchor lokalnie przy użyciu klasy SpatialAnchorStore , a następnie wrócić do przyszłej sesji aplikacji na tym samym urządzeniu HoloLens.

Za pomocą usługi Azure Spatial Anchors możesz utworzyć trwałą kotwicę chmury na podstawie lokalnej usługi SpatialAnchor, którą aplikacja może następnie zlokalizować na wielu urządzeniach HoloLens, iOS i Android. Udostępniając wspólną kotwicę przestrzenną na wielu urządzeniach, każdy użytkownik może zobaczyć zawartość renderowaną względem tej kotwicy w tej samej lokalizacji fizycznej w czasie rzeczywistym.

Możesz również użyć usługi Azure Spatial Anchors na potrzeby asynchronicznej trwałości hologramu na urządzeniach HoloLens, iOS i Android. Dzięki udostępnieniu trwałej kotwicy przestrzennej chmury wiele urządzeń może obserwować ten sam utrwalony hologram w czasie, nawet jeśli te urządzenia nie są obecne razem w tym samym czasie.

Aby rozpocząć tworzenie udostępnionych środowisk w aplikacji HoloLens, wypróbuj 5-minutowy przewodnik Szybki start dotyczący usługi Azure Spatial Anchors dla urządzenia HoloLens.

Po rozpoczęciu pracy z usługą Azure Spatial Anchors możesz utworzyć i zlokalizować kotwice na urządzeniu HoloLens. Przewodniki są również dostępne dla systemów Android i iOS , co umożliwia udostępnianie tych samych kotwic na wszystkich urządzeniach.

Tworzenie elementów SpatialAnchors dla zawartości holograficznej

W tym przykładzie kodu zmodyfikowaliśmy szablon aplikacji Windows Holographic w celu utworzenia kotwic po wykryciu gestu naciśnięcia . Moduł jest następnie umieszczany na kotwicy podczas przekazywania renderowania.

Ponieważ wiele kotwic jest obsługiwanych przez klasę pomocnika, możemy umieścić tyle modułów, ile chcemy użyć tego przykładu kodu.

Uwaga

Identyfikatory kotwic to coś, co kontrolujesz w aplikacji. W tym przykładzie utworzyliśmy schemat nazewnictwa, który jest sekwencyjny na podstawie liczby kotwic przechowywanych obecnie w kolekcji kotwic aplikacji.

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

Asynchroniczne ładowanie i buforowanie— spatialAnchorStore

Zobaczmy, jak napisać klasę SampleSpatialAnchorHelper, która pomaga obsługiwać tę trwałość, w tym:

  • Przechowywanie kolekcji kotwic w pamięci indeksowanych przez klucz Platform::String.
  • Ładowanie kotwic z magazynu SpatialAnchorStore systemu, który jest oddzielony od lokalnej kolekcji w pamięci.
  • Zapisywanie lokalnej kolekcji zakotwiczeń w pamięci w magazynie SpatialAnchorStore, gdy aplikacja wybierze to zrobić.

Poniżej przedstawiono sposób zapisywania obiektów SpatialAnchor w magazynie SpatialAnchorStore.

Po uruchomieniu klasy żądamy asynchronicznego obiektu SpatialAnchorStore. Obejmuje to operacje we/wy systemu, ponieważ interfejs API ładuje magazyn kotwicy, a ten interfejs API jest asynchroniczny, tak aby operacje we/wy nie blokowały.

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

Otrzymasz element SpatialAnchorStore, którego można użyć do zapisania kotwic. Jest to element IMapView, który kojarzy wartości kluczy, które są ciągami, z wartościami danych, które są spatialAnchors. W naszym przykładowym kodzie przechowujemy to w prywatnej zmiennej składowej klasy, która jest dostępna za pośrednictwem funkcji publicznej klasy pomocniczej.

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

Uwaga

Nie zapomnij podłączyć zdarzeń wstrzymania/wznowienia w celu zapisania i załadowania magazynu zakotwiczenia.

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

Zapisywanie zawartości w magazynie zakotwiczenia

Po zawieszeniu aplikacji przez system należy zapisać kotwice przestrzenne w magazynie zakotwiczenia. Możesz również zapisywać kotwice w magazynie zakotwiczenia w innym czasie, ponieważ okaże się, że jest to konieczne dla implementacji aplikacji.

Gdy wszystko będzie gotowe do zapisania kotwic w pamięci w magazynie SpatialAnchorStore, możesz wykonać pętlę w kolekcji i spróbować zapisać poszczególne z nich.

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

Ładowanie zawartości ze sklepu zakotwiczonego po wznowieniu aplikacji

Zapisane kotwice można przywrócić w magazynie AnchorStore, przenosząc je z elementu IMapView magazynu kotwicy do własnej bazy danych funkcji SpatialAnchors w pamięci, gdy aplikacja zostanie wznowiona lub w dowolnym momencie.

Aby przywrócić kotwice z magazynu SpatialAnchorStore, przywróć te, które cię interesują, do własnej kolekcji w pamięci.

Aby skojarzyć ciągi z utworzonymi elementami SpatialAnchors, potrzebna jest własna baza danych w pamięci. W naszym przykładowym kodzie wybieramy użycie elementu Windows::Foundation::Collections::IMap do przechowywania kotwic, co ułatwia używanie tego samego klucza i wartości danych dla magazynu 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;

Uwaga

Przywrócona kotwica może nie być od razu lokalizowana. Na przykład może to być kotwica w oddzielnym pomieszczeniu lub w innym budynku. Kotwice pobrane z magazynu kotwic należy przetestować pod kątem lokalizowania przed ich użyciem.


Uwaga

W tym przykładowym kodzie pobieramy wszystkie kotwice z magazynu anchorstore. Nie jest to wymagane; Aplikacja może równie dobrze wybrać i wybrać określony podzbiór kotwic przy użyciu wartości kluczy ciągów, które mają znaczenie dla implementacji.

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

Wyczyść magazyn zakotwiczenia, jeśli jest to konieczne

Czasami należy wyczyścić stan aplikacji i zapisać nowe dane. Oto jak to zrobić za pomocą magazynu SpatialAnchorStore.

Korzystając z naszej klasy pomocniczej, prawie nie trzeba opakowować funkcji Clear. Decydujemy się na to w naszej przykładowej implementacji, ponieważ nasza klasa pomocnika otrzymuje odpowiedzialność za posiadanie wystąpienia SpatialAnchorStore.

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

Przykład: Odnoszące się układy współrzędnych kotwicy do stacjonarnych układów współrzędnych ram odniesienia

Załóżmy, że masz kotwicę i chcesz powiązać coś w systemie współrzędnych kotwicy z elementem SpatialStationaryReferenceFrame, którego już używasz dla innej zawartości. Za pomocą funkcji TryGetTransformTo można uzyskać przekształcenie z układu współrzędnych kotwicy na ramkę odniesienia stacjonarnego:

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

Ten proces jest przydatny na dwa sposoby:

  1. Informuje o tym, czy dwie ramki referencyjne mogą być zrozumiałe względem siebie i;
  2. Jeśli tak, zapewnia przekształcenie, aby przejść bezpośrednio z jednego układu współrzędnych do drugiego.

Dzięki tym informacjom rozumiesz relację przestrzenną między obiektami między dwiema ramkami referencyjnymi.

W przypadku renderowania często można uzyskać lepsze wyniki, grupując obiekty zgodnie z ich oryginalną ramką odniesienia lub kotwicą. Wykonaj oddzielne przekazywanie rysunku dla każdej grupy. Macierze widoków są bardziej dokładne dla obiektów z przekształceniami modelu, które są tworzone początkowo przy użyciu tego samego układu współrzędnych.

Tworzenie hologramów przy użyciu dołączonej do urządzenia ramki odwołania

Czasami chcesz renderować hologram , który pozostaje dołączony do lokalizacji urządzenia, na przykład panel z informacjami o debugowaniu lub komunikatem informacyjnym, gdy urządzenie jest w stanie określić jego orientację, a nie jego położenie w przestrzeni. W tym celu użyjemy dołączonej ramki odwołania.

Klasa SpatialLocatorAttachedFrameOfReference definiuje układy współrzędnych, które są względem urządzenia, a nie ze światem rzeczywistym. Ta ramka ma stały nagłówek względem otoczenia użytkownika, który wskazuje kierunek, w którym użytkownik miał do czynienia podczas tworzenia ramki odniesienia. Od tego momentu wszystkie orientacje w tej ramce odwołania są powiązane z tym stałym nagłówkiem, nawet gdy użytkownik obraca urządzenie.

W przypadku urządzenia HoloLens początek układu współrzędnych tej ramki znajduje się w środku obrotu głowy użytkownika, dzięki czemu jego pozycja nie ma wpływu na obrót głowy. Aplikacja może określić przesunięcie względem tego punktu, aby umieścić hologramy przed użytkownikiem.

Aby uzyskać obiekt SpatialLocatorAttachedFrameOfReference, użyj klasy SpatialLocator i wywołaj metodę CreateAttachedFrameOfReferenceAtCurrentHeading.

Dotyczy to całego zakresu Windows Mixed Reality urządzeń.

Używanie ramki referencyjnej dołączonej do urządzenia

W tych sekcjach omówiono zmiany w szablonie aplikacji Windows Holographic w celu włączenia dołączonej do urządzenia ramki odwołania przy użyciu tego interfejsu API. Ten "dołączony" hologram będzie działać obok nieruchomych lub zakotwiczonych hologramów, a także może być używany, gdy urządzenie tymczasowo nie może znaleźć swojej pozycji na świecie.

Najpierw zmieniliśmy szablon tak, aby przechowywał element SpatialLocatorAttachedFrameOfReference zamiast elementu SpatialStationaryFrameOfReference:

Z holographicTagAlongSampleMain.h:

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

Z holographicTagAlongSampleMain.cpp:

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

Podczas aktualizacji uzyskujemy teraz układ współrzędnych w sygnaturze czasowej uzyskanej z przewidywania ramowego.

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

Uzyskiwanie pozowania wskaźnika przestrzennego i obserwowanie spojrzenia użytkownika

Chcemy, aby nasz przykład hologram był zgodny z spojrzeniem użytkownika, podobnie jak powłoka holograficzna może podążać za spojrzeniem użytkownika. W tym celu musimy pobrać element SpatialPointerPose z tej samej sygnatury czasowej.

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

Ten element SpatialPointerPose zawiera informacje potrzebne do pozycjonowania hologramu zgodnie z bieżącym nagłówkiem użytkownika.

Dla komfortu użytkownika używamy interpolacji liniowej ("lerp"), aby złagodzić zmianę pozycji w danym okresie. Jest to bardziej wygodne dla użytkownika niż blokowanie hologramu do spojrzenia. Lerping pozycji hologramu wzdłuż tagu pozwala również ustabilizować hologram poprzez tłumienie ruchu. Jeśli nie zrobiliśmy tego tłumienia, użytkownik zobaczy zakłócenia hologramu ze względu na to, co zwykle uważa się za niemożliwy ruch głowy użytkownika.

From NieruchomyQuadRenderer::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);
   }

Uwaga

W przypadku panelu debugowania można zmienić położenie hologramu z boku, aby nie utrudniało to widoku. Oto przykład tego, jak możesz to zrobić.

Dla stacjonarnegoquadRenderer::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));
       */

Obraca hologram, aby zmierzyć się z aparatem

Nie wystarczy umieścić hologram, który w tym przypadku jest czworokątem; Musimy również obrócić obiekt, aby zmierzyć się z użytkownikiem. Ta rotacja występuje w przestrzeni światowej, ponieważ ten typ billboardowania umożliwia hologramowi pozostanie częścią środowiska użytkownika. Billboardowanie w przestrzeni widokowej nie jest tak wygodne, ponieważ hologram staje się zablokowany w orientacji ekranu; w takim przypadku należy również interpolować między macierzami po lewej i prawej stronie, aby uzyskać transformację billboardu w przestrzeni widokowej, która nie zakłóca renderowania stereo. W tym miejscu obracamy się na osiach X i Z, aby zmierzyć się z użytkownikiem.

From NieruchomyQuadRenderer::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));

Renderowanie dołączonego hologramu

W tym przykładzie wybieramy również renderowanie hologramu w układzie współrzędnych elementu SpatialLocatorAttachedReferenceFrame, w którym umieściliśmy hologram. (Gdybyśmy zdecydowali się na renderowanie przy użyciu innego układu współrzędnych, musielibyśmy uzyskać przekształcenie z układu współrzędnych ramki odniesienia dołączonej do urządzenia do tego układu współrzędnych).

Z elementu 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)
       );

Gotowe. Hologram będzie teraz "ścigać" pozycję, która jest 2 metry przed kierunkiem wzroku użytkownika.

Uwaga

Ten przykład ładuje również dodatkową zawartość — zobacz NieruchomyQuadRenderer.cpp.

Obsługa śledzenia utraty

Gdy urządzenie nie może zlokalizować się na świecie, aplikacja doświadcza "śledzenia utraty". Windows Mixed Reality aplikacje powinny mieć możliwość obsługi takich zakłóceń w systemie śledzenia pozycyjnego. Te zakłócenia można zaobserwować i utworzyć odpowiedzi przy użyciu zdarzenia LocatabilityChanged w domyślnym obiekcie SpatialLocator.

Z obszaru 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)
               );

Gdy aplikacja odbierze zdarzenie LocatabilityChanged, może zmienić zachowanie zgodnie z potrzebami. Na przykład w stanie PositionalTrackingInhibited aplikacja może wstrzymać normalne działanie i renderować hologram tagu, który wyświetla komunikat ostrzegawczy.

Szablon aplikacji Systemu Windows Holographic zawiera już utworzony program obsługi LocatabilityChanged. Domyślnie w konsoli debugowania jest wyświetlane ostrzeżenie, gdy śledzenie pozycyjne jest niedostępne. Możesz dodać kod do tej procedury obsługi, aby zapewnić odpowiedź zgodnie z potrzebami aplikacji.

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

Mapowanie przestrzenne

Interfejsy API mapowania przestrzennego korzystają z systemów współrzędnych w celu uzyskania przekształceń modelu dla siatki powierzchni.

Zobacz też