Udostępnij za pośrednictwem


Układy współrzędnych w programie DirectX

Uwaga

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

Systemy koordynujące tworzą 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. Urządzenia rzeczywistości mieszanej, takie jak HoloLens, są zaprojektowane z myślą o dużych niezdefiniowanych środowiskach, a urządzenie odnajduje i uczy się o swoim otoczeniu podczas poruszania się po okolicy. Urządzenie dostosowuje się do ciągłego ulepszania wiedzy o pokojach użytkownika, ale powoduje koordynowanie systemów, które zmieniają relację między sobą w okresie istnienia aplikacji. Windows Mixed Reality obsługuje szerokie spektrum urządzeń, od siedzących immersyjnych zestawów nagłownych poprzez 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 ze standardem C++/WinRT używanego w szablonie projektu holograficznego języka C++. Koncepcje 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 zaakceptują 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ć obiektów SpatialCoordinateSystems — raczej powinny być używane za pośrednictwem interfejsów API percepcji. Istnieją trzy podstawowe źródła układów współrzędnych w interfejsach API percepcji, z których każda jest mapowana na koncepcję opisaną na stronie Układy współrzędnych:

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 zapamiętać kierunek dodatnich punktów osi Z, wskazując palce lewej lub prawej strony w dodatnim kierunku x i zwijając je w pozytywny kierunek y. Kierunek, w którym punkty kciuka, w kierunku lub z dala od Ciebie, to kierunek, w którym punkty dodatniej osi Z dla tego układu 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 stacjonarną ramę odwołania w celu uruchomienia elementu 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

Do systemu współrzędnych nieprzezroczystych zestawów słuchawkowych windows Mixed Reality uzyskuje się dostęp przy użyciu statycznej właściwości SpatialStageFrameOfReference::Current . Ten interfejs API zapewnia:

  • Układ współrzędnych
  • Informacje o tym, czy gracz jest siedzony, czy mobilny
  • Granica bezpiecznego obszaru do poruszania się, jeśli gracz jest mobilny
  • Wskazanie, czy zestaw słuchawkowy jest kierunkowy.
  • Procedura obsługi zdarzeń dla 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 sprawdzić etap przestrzenny i zaktualizować środowisko odtwarzacza. W tym przykładzie udostępniamy wizualizację granicy etapu i pozycję początkową określoną przez użytkownika oraz zakres właściwości widoku i zakresu ruchu etapu. Wracamy również do naszego układu współrzędnych stacjonarnych, 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, które definiują granicę etapu, są podane 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 niej, ale możesz chcieć tryangularyzować obszar przeznaczony do chodzenia 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 nieruchomej ramy odniesienia

Klasa SpatialStationaryFrameOfReference reprezentuje ramę odwołania, która pozostaje nieruchoma w stosunku do otoczenia użytkownika, gdy użytkownik porusza się wokół. 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 są zaprojektowane tak, aby zapewnić najlepszą pozycję w stosunku do całej przestrzeni. Poszczególne pozycje w tej ramce referencyjnej mogą nieznacznie dryfować. Jest to normalne, ponieważ urządzenie dowie się więcej o środowisku.
  • Gdy wymagane jest dokładne rozmieszczenie poszczególnych hologramów, należy użyć elementu SpatialAnchor do zakotwiczenia pojedynczego hologramu do pozycji w świecie rzeczywistym — na przykład punkt, który użytkownik wskazuje na szczególne zainteresowanie. Położenia zakotwiczenia 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 na umieszczenie 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 być w stanie zlokalizować ten układ współrzędnych w tej chwili, a system nie może osiągnąć limitu kotwic przestrzennych.

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

Efekty korekt, które utrzymują zakotwiczenie 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, przy użyciu złagodzenia stosowanego, 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.

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

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ęcie . 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 są czymś, 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 pamięć podręczna— SpatialAnchorStore

Zobaczmy, jak napisać klasę SampleSpatialAnchorHelper, która ułatwia obsługę tej trwałości, w tym:

  • Przechowywanie kolekcji kotwic w pamięci indeksowanych przez klucz Platform::String.
  • Ładowanie kotwic z magazynu SpatialAnchorStore systemu, które jest oddzielone od lokalnej kolekcji w pamięci.
  • Zapisywanie lokalnej kolekcji kotwic 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 magazyn 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 zmiennej składowej klasy prywatnej, 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, aby zapisać i załadować magazyn kotwicy.

   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

Gdy system zawiesza aplikację, musisz zapisać kotwice przestrzenne w magazynie kotwicy. Możesz również zapisać kotwice w magazynie kotwicy w innym czasie, ponieważ okaże się, że jest to konieczne w przypadku implementacji aplikacji.

Gdy wszystko będzie gotowe do zapisania zakotwiczeń w pamięci w magazynie SpatialAnchorStore, możesz przeprowadzić pętlę w kolekcji i spróbować zapisać każdy 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 kotwicy 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 w pamięci obiektów SpatialAnchors, 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.

Potrzebujesz własnej bazy danych w pamięci obiektów SpatialAnchors, aby skojarzyć ciągi z utworzonymi elementami SpatialAnchors. 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ć lokalizowana od razu. Może to być na przykład kotwica w osobnym pomieszczeniu lub w innym budynku. Kotwice pobrane z magazynu anchorstore powinny być testowane pod kątem lokalizowania przed ich użyciem.


Uwaga

W tym przykładowym kodzie pobieramy wszystkie kotwice z anchorStore. Nie jest to wymagane; Aplikacja może równie dobrze wybrać i wybrać określony podzbiór kotwic przy użyciu wartości klucza ciągu, 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 kotwicy w razie potrzeby

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

Korzystając z naszej klasy pomocniczej, prawie nie trzeba opakowować funkcji Clear. Wybieramy to w naszej przykładowej implementacji, ponieważ nasza klasa pomocnika ponosi 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: Powiązanie układów współrzędnych kotwicy ze stacjonarnymi układami współrzędnych ramek 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. Możesz użyć metody TryGetTransformTo , aby 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 odwołania 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 odniesienia.

W przypadku renderowania często można uzyskać lepsze wyniki, grupując obiekty zgodnie z ich oryginalną ramką odwołania lub kotwicą. Wykonaj oddzielne przekazywanie rysunku dla każdej grupy. Macierze widoku są dokładniejsze 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 debugowania 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 z rzeczywistym światem. 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ą względem tego stałego nagłówka, 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 położenie 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 urządzeń Windows Mixed Reality.

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 stacjonarnych 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 system 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 pozy 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 tego samego 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.

W celu zapewnienia komfortu użytkownika używamy interpolacji liniowej ("lerp"), aby złagodzić zmianę położenia w danym okresie. Jest to bardziej wygodne dla użytkownika niż blokowanie hologramu do wzroku. 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 niezauważalne ruchy głowy użytkownika.

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

Uwaga

W przypadku panelu debugowania można zmienić położenie hologramu na bok trochę, aby nie utrudniał widoku. Oto przykład tego, jak 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));
       */

Obracanie hologramu w celu stawienia czoła aparatowi

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

Od stacjonarnegoQuadRenderer::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ę renderować przy użyciu innego układu współrzędnych, musielibyśmy uzyskać przekształcenie z układu współrzędnych dołączonej do urządzenia ramki odniesienia 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)
       );

I już! Hologram będzie teraz "ścigać" pozycję, która jest 2 metrów przed kierunkiem wzroku użytkownika.

Uwaga

W tym przykładzie ładuje się również dodatkową zawartość — zobacz StationaryQuadRenderer.cpp.

Obsługa śledzenia utraty

Gdy urządzenie nie może zlokalizować się na świecie, aplikacja doświadcza "śledzenia utraty". Aplikacje Windows Mixed Reality 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 odbiera zdarzenie LocatabilityChanged, może zmienić zachowanie zgodnie z potrzebami. Na przykład w stanie PositionalTrackingInhibited aplikacja może wstrzymać normalną operację i renderować hologram tagu, który wyświetla komunikat ostrzegawczy.

Szablon aplikacji Windows Holographic jest dostarczany z programem obsługi LocatabilityChanged już utworzonym dla Ciebie. Domyślnie wyświetla ostrzeżenie w konsoli debugowania, gdy śledzenie pozycyjne jest niedostępne. Możesz dodać kod do tej procedury obsługi, aby dostarczyć odpowiedź zgodnie z potrzebami z aplikacji.

Z 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ż