Koordinatsystem i DirectX

Anteckning

Den här artikeln handlar om äldre inbyggda WinRT-API:er. För nya interna appprojekt rekommenderar vi att du använder OpenXR-API:et.

Koordinatsystem utgör grunden för rumslig förståelse som erbjuds av Windows Mixed Reality API:er.

Dagens sittande VR- eller enrums VR-enheter etablerar ett primärt koordinatsystem för sitt spårade utrymme. Mixed Reality enheter som HoloLens är utformade för stora odefinierade miljöer, där enheten upptäcker och lär sig om sin omgivning när användaren går omkring. Enheten anpassar sig till att kontinuerligt förbättra kunskapen om användarens rum, men resulterar i koordinatsystem som ändrar deras relation till varandra under apparnas livslängd. Windows Mixed Reality stöder ett brett spektrum av enheter, allt från sittande integrerande headset till världsanslutna referensramar.

Anteckning

Kodfragmenten i den här artikeln visar för närvarande användningen av C++/CX i stället för C++17-kompatibel C++/WinRT som används i C++-holografisk projektmall. Begreppen motsvarar ett C++/WinRT-projekt, men du måste översätta koden.

Rumsliga koordinatsystem i Windows

Den kärntyp som används för att resonera kring verkliga koordinatsystem i Windows är SpatialCoordinateSystem. En instans av den här typen representerar ett godtyckligt koordinatsystem, som tillhandahåller en metod för att hämta transformeringsmatrisdata som du kan använda för att transformera mellan två koordinatsystem utan att förstå detaljerna för var och en.

Metoder som returnerar rumslig information accepterar en SpatialCoordinateSystem-parameter så att du kan bestämma det koordinatsystem där det är mest användbart att dessa koordinater returneras. Rumslig information representeras som punkter, strålar eller volymer i användarens omgivning, och enheterna för dessa koordinater kommer alltid att vara i meter.

Ett SpatialCoordinateSystem har en dynamisk relation med andra koordinatsystem, inklusive de som representerar enhetens position. När som helst kan enheten hitta vissa koordinatsystem och inte andra. För de flesta koordinatsystem måste din app vara redo att hantera perioder då de inte kan hittas.

Programmet bör inte skapa SpatialCoordinateSystems direkt – de bör snarare användas via API:erna för uppfattning. Det finns tre primära källor till koordinatsystem i API:erna för uppfattning, som var och en mappar till ett begrepp som beskrivs på sidan Koordinatsystem :

Alla koordinatsystem som returneras av dessa objekt är högerhänta, med +y upp, +x till höger och +z bakåt. Du kan komma ihåg vilken riktning den positiva z-axeln pekar genom att peka fingrarna på antingen vänster eller höger hand i positiv x riktning och curling dem i positiv y riktning. Riktningen tummen pekar, antingen mot eller bort från dig, är den riktning som den positiva z-axeln pekar för det koordinatsystemet. Följande bild visar dessa två koordinatsystem.

Vänster- och högerkoordinatsystem
Vänster- och högerkoordinatsystem

Använd klassen SpatialLocator för att skapa antingen en bifogad eller stationär referensram för bootstrap till ett SpatialCoordinateSystem baserat på HoloLens-positionen. Fortsätt till nästa avsnitt om du vill veta mer om den här processen.

Placera hologram i världen med hjälp av en rumslig fas

Koordinatsystemet för täckande Windows Mixed Reality integrerande headset nås med hjälp av den statiska egenskapen SpatialStageFrameOfReference::Current. Det här API:et tillhandahåller:

  • Ett koordinatsystem
  • Information om huruvida spelaren sitter eller är mobil
  • Gränsen för ett säkert område för att gå runt om spelaren är mobil
  • En indikation på om headsetet är riktningsrikt.
  • En händelsehanterare för uppdateringar av den rumsliga fasen.

Först hämtar vi den rumsliga fasen och prenumererar på uppdateringar av den:

Kod för initiering av rumsliga steg

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

I metoden OnCurrentChanged bör appen inspektera den rumsliga fasen och uppdatera spelarupplevelsen. I det här exemplet tillhandahåller vi en visualisering av fasgränsen och den startposition som anges av användaren och fasens visningsintervall och rörelseegenskaper. Vi återgår också till vårt eget stationära koordinatsystem när det inte går att tillhandahålla en fas.

Kod för uppdatering av rumsliga steg

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

Den uppsättning hörn som definierar steggränsen anges i medurs ordning. Det Windows Mixed Reality gränssnittet ritar ett staket vid gränsen när användaren närmar sig det, men du kanske vill triangulärisera det gångbara området för dina egna syften. Följande algoritm kan användas för att triangularisera fasen.

Kod för triangularisering av rumsliga steg

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

Placera hologram i världen med hjälp av en stationär referensram

Klassen SpatialStationaryFrameOfReference representerar en referensram som förblir stationär i förhållande till användarens omgivning när användaren rör sig. Den här referensramen prioriterar att hålla koordinaterna stabila nära enheten. En viktig användning av en SpatialStationaryFrameOfReference är att fungera som det underliggande världskoordinatsystemet i en återgivningsmotor vid återgivning av hologram.

Om du vill hämta en SpatialStationaryFrameOfReference använder du klassen SpatialLocator och anropar CreateStationaryFrameOfReferenceAtCurrentLocation.

Från mallkoden för Windows Holographic-appen:

           // 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();
  • Stationära referensramar är utformade för att ge bästa möjliga position i förhållande till det totala utrymmet. Enskilda positioner inom referensramen får glida något. Detta är normalt eftersom enheten lär sig mer om miljön.
  • När exakt placering av enskilda hologram krävs ska en SpatialAnchor användas för att förankra det enskilda hologrammet till en position i verkligheten – till exempel en punkt som användaren anger vara av särskilt intresse. Fästpunktspositioner driver inte, men kan korrigeras; fästpunkten använder den korrigerade positionen från och med nästa bildruta efter att korrigeringen har inträffat.

Placera hologram i världen med hjälp av spatiala fästpunkter

Rumsliga fästpunkter är ett bra sätt att placera hologram på en specifik plats i den verkliga världen, där systemet säkerställer att fästpunkten förblir på plats över tid. Det här avsnittet beskriver hur du skapar och använder en fästpunkt och hur du arbetar med fästpunktsdata.

Du kan skapa en SpatialAnchor på valfri position och orientering inom det SpatialCoordinateSystem som du väljer. Enheten måste kunna hitta koordinatsystemet för tillfället och systemet får inte ha nått gränsen för rumsliga fästpunkter.

När det har definierats justeras koordinatsystemet för en SpatialAnchor kontinuerligt för att hålla den exakta positionen och orienteringen för den ursprungliga platsen. Du kan sedan använda den här SpatialAnchor för att rendera hologram som kommer att visas fasta i användarens omgivning på den exakta platsen.

Effekterna av de justeringar som håller fästpunkten på plats förstoras när avståndet från fästpunkten ökar. Du bör undvika att återge innehåll i förhållande till en fästpunkt som är mer än cirka 3 meter från fästpunktens ursprung.

Egenskapen CoordinateSystem hämtar ett koordinatsystem som gör att du kan placera innehåll i förhållande till fästpunkten, med lättnader som tillämpas när enheten justerar fästpunktens exakta plats.

Använd egenskapen RawCoordinateSystem och motsvarande RawCoordinateSystemAdjusted-händelse för att hantera dessa justeringar själv.

Spara och dela spatiala fästpunkter

Du kan spara en SpatialAnchor lokalt med hjälp av klassen SpatialAnchorStore och sedan få tillbaka den i en framtida appsession på samma HoloLens-enhet.

Genom att använda Azure Spatial Anchors kan du skapa en beständig molnankare från en lokal SpatialAnchor som appen sedan kan hitta på flera HoloLens-, iOS- och Android-enheter. Genom att dela en gemensam rumslig fästpunkt mellan flera enheter kan varje användare se innehåll som återges i förhållande till fästpunkten på samma fysiska plats i realtid.

Du kan också använda Azure Spatial Anchors för asynkron hologrampersistence på HoloLens-, iOS- och Android-enheter. Genom att dela en beständig molns rumslig fästpunkt kan flera enheter observera samma bevarade hologram över tid, även om enheterna inte finns tillsammans samtidigt.

Om du vill komma igång med att skapa delade upplevelser i din HoloLens-app kan du prova snabbstarten för Azure Spatial Anchors HoloLens på 5 minuter.

När du är igång med Azure Spatial Anchors kan du sedan skapa och hitta fästpunkter på HoloLens. Genomgångar är också tillgängliga för Android och iOS , så att du kan dela samma fästpunkter på alla enheter.

Skapa SpatialAnchors för holografiskt innehåll

I det här kodexemplet har vi ändrat windows Holographic-appmallen för att skapa fästpunkter när gesten Nedtryckt identifieras. Kuben placeras sedan vid fästpunkten under återgivningspasset.

Eftersom flera fästpunkter stöds av hjälpklassen kan vi placera så många kuber som vi vill använda det här kodexemplet!

Anteckning

ID:t för fästpunkter är något som du styr i din app. I det här exemplet har vi skapat ett namngivningsschema som är sekventiellt baserat på antalet fästpunkter som för närvarande lagras i appens samling fästpunkter.

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

Läs in och cachelagrat asynkront, SpatialAnchorStore

Nu ska vi se hur du skriver en SampleSpatialAnchorHelper-klass som hjälper dig att hantera den här beständigheten, inklusive:

  • Lagra en samling minnesinterna fästpunkter som indexerats av en plattform::Strängnyckel.
  • Läser in fästpunkter från systemets SpatialAnchorStore, som hålls separat från den lokala minnesintern samlingen.
  • Spara den lokala minnesinterna samlingen fästpunkter i SpatialAnchorStore när appen väljer att göra det.

Så här sparar du SpatialAnchor-objekt i SpatialAnchorStore.

När klassen startas begär vi SpatialAnchorStore asynkront. Detta omfattar system-I/O när API:et läser in fästpunktsarkivet, och det här API:et görs asynkront så att I/O inte blockerar.

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

Du får en SpatialAnchorStore som du kan använda för att spara fästpunkterna. Det här är en IMapView som associerar nyckelvärden som är Strängar med datavärden som är SpatialAnchors. I vår exempelkod lagrar vi detta i en privat klassmedlemsvariabel som är tillgänglig via en offentlig funktion i vår hjälpklass.

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

Anteckning

Glöm inte att ansluta paus-/återupptagningshändelserna för att spara och läsa in fästpunktsarkivet.

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

Spara innehåll i fästpunktsarkivet

När systemet pausar appen måste du spara dina spatiala fästpunkter i fästpunktsarkivet. Du kan också välja att spara fästpunkter i fästpunktsarkivet vid andra tillfällen, eftersom det är nödvändigt för appens implementering.

När du är redo att försöka spara de minnesinterna fästpunkterna i SpatialAnchorStore kan du loopa igenom samlingen och försöka spara var och en.

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

Läs in innehåll från fästpunktsarkivet när appen återupptas

Du kan återställa sparade fästpunkter i AnchorStore genom att överföra dem från fästpunktsarkivets IMapView till din egen minnesintern databas med SpatialAnchors när din app återupptas eller när som helst.

Om du vill återställa fästpunkter från SpatialAnchorStore återställer du var och en som du är intresserad av till din egen minnesintern samling.

Du behöver en egen minnesintern databas med SpatialAnchors för att associera strängar med de SpatialAnchors som du skapar. I vår exempelkod väljer vi att använda en Windows::Foundation::Collections::IMap för att lagra fästpunkterna, vilket gör det enkelt att använda samma nyckel- och datavärde för 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;

Anteckning

En fästpunkt som återställs kanske inte är locatable direkt. Det kan till exempel vara en fästpunkt i ett separat rum eller i en helt annan byggnad. Fästpunkter som hämtats från AnchorStore bör testas för locatability innan de används.


Anteckning

I den här exempelkoden hämtar vi alla fästpunkter från AnchorStore. Detta är inte ett krav. din app kan lika gärna välja och välja en viss delmängd fästpunkter med hjälp av strängnyckelvärden som är meningsfulla för implementeringen.

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

Rensa fästpunktsarkivet när det behövs

Ibland måste du rensa apptillståndet och skriva nya data. Så här gör du med SpatialAnchorStore.

Med hjälp av vår hjälpklass är det nästan onödigt att omsluta clear-funktionen. Vi väljer att göra det i vår exempelimplementering eftersom vår hjälpklass får ansvaret att äga SpatialAnchorStore-instansen.

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

Exempel: Relatera koordinatsystem för fästpunkter till stationära referensramkoordinatsystem

Anta att du har en fästpunkt och vill relatera något i fästpunktens koordinatsystem till den SpatialStationaryReferenceFrame som du redan använder för ditt andra innehåll. Du kan använda TryGetTransformTo för att hämta en transformering från fästpunktens koordinatsystem till den stationära referensramen:

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

Den här processen är användbar för dig på två sätt:

  1. Den anger om de två referensramarna kan tolkas i förhållande till varandra och;
  2. I så fall får du en transformering som går direkt från ett koordinatsystem till det andra.

Med den här informationen har du en förståelse för den rumsliga relationen mellan objekt mellan de två referensramarna.

För återgivning kan du ofta få bättre resultat genom att gruppera objekt enligt deras ursprungliga referensram eller fästpunkt. Utför ett separat ritningspass för varje grupp. Vymatriserna är mer exakta för objekt med modelltransformeringar som skapas från början med samma koordinatsystem.

Skapa hologram med en enhetsansluten referensram

Det finns tillfällen när du vill återge ett hologram som förblir kopplat till enhetens plats, till exempel en panel med felsökningsinformation eller ett informationsmeddelande när enheten bara kan fastställa dess orientering och inte dess position i utrymmet. För att åstadkomma detta använder vi en bifogad referensram.

Klassen SpatialLocatorAttachedFrameOfReference definierar koordinatsystem som är relativa till enheten i stället för till verkligheten. Den här ramen har en fast rubrik i förhållande till användarens omgivning som pekar i den riktning som användaren var vänd mot när referensramen skapades. Från och med då är alla orienteringar i den här referensramen relativa till den fasta rubriken, även när användaren roterar enheten.

För HoloLens finns ursprunget för den här ramens koordinatsystem i mitten av rotationen av användarens huvud, så att dess position inte påverkas av huvudrotation. Din app kan ange en förskjutning i förhållande till den här punkten för att placera hologram framför användaren.

Om du vill hämta en SpatialLocatorAttachedFrameOfReference använder du klassen SpatialLocator och anropar CreateAttachedFrameOfReferenceAtCurrentHeading.

Detta gäller för hela Windows Mixed Reality enheter.

Använd en referensram som är kopplad till enheten

De här avsnitten handlar om vad vi har ändrat i Windows Holographic-appmallen för att aktivera en enhetsansluten referensram med hjälp av det här API:et. Det här "bifogade" hologrammet fungerar tillsammans med stationära eller förankrade hologram och kan också användas när enheten tillfälligt inte kan hitta sin position i världen.

Först ändrade vi mallen för att lagra en SpatialLocatorAttachedFrameOfReference i stället för en SpatialStationaryFrameOfReference:

Från HolographicTagAlongSampleMain.h:

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

Från HolographicTagAlongSampleMain.cpp:

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

Under uppdateringen hämtar vi nu koordinatsystemet vid den tidsstämpel som erhålls från med bildruteförutsägelserna.

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

Hämta en spatial pekare och följ användarens Blick

Vi vill att vårt exempelhologram ska följa användarens blick, ungefär som det holografiska gränssnittet kan följa användarens blick. För detta behöver vi hämta SpatialPointerPose från samma tidsstämpel.

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

Denna SpatialPointerPose har den information som behövs för att placera hologrammet enligt användarens aktuella rubrik.

För användarkomfort använder vi linjär interpolering ("lerp") för att jämna ut positionsförändringen under en viss tidsperiod. Detta är mer bekvämt för användaren än att låsa hologrammet till blicken. Genom att rensa hologrammets position kan vi också stabilisera hologrammet genom att dämpa rörelsen. Om vi inte gjorde den här dämpningen skulle användaren se hologram jitter på grund av vad som normalt anses vara omärkbara förflyttningar av användarens huvud.

Från 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);
   }

Anteckning

När det gäller en felsökningspanel kan du välja att flytta hologrammet lite åt sidan så att det inte hindrar vyn. Här är ett exempel på hur du kan göra det.

För 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));
       */

Rotera hologrammet mot kameran

Det räcker inte att placera hologrammet, vilket i det här fallet är en quad. Vi måste också rotera objektet för att möta användaren. Den här rotationen sker i världsrymden, eftersom den här typen av skyltning gör att hologrammet kan förbli en del av användarens miljö. Visningsutrymmet är inte lika bekvämt eftersom hologrammet blir låst till visningsorienteringen. I så fall skulle du också behöva interpolera mellan vänster och höger vymatriser för att få en skylttransformering i visningsutrymmet som inte stör stereorendering. Här roterar vi på X- och Z-axlarna för att möta användaren.

Från 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));

Rendera det bifogade hologrammet

I det här exemplet väljer vi också att rendera hologrammet i koordinatsystemet för SpatialLocatorAttachedReferenceFrame, där vi placerade hologrammet. (Om vi hade valt att rendera med ett annat koordinatsystem skulle vi behöva hämta en transformering från den enhetsanslutna referensramens koordinatsystem till det koordinatsystemet.)

Från 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)
       );

Klart! Hologrammet kommer nu att "jaga" en position som är 2 meter framför användarens blickriktning.

Anteckning

Det här exemplet läser också in ytterligare innehåll – se StationaryQuadRenderer.cpp.

Hantering av spårningsförlust

När enheten inte kan hitta sig själv i världen upplever appen "spårningsförlust". Windows Mixed Reality appar ska kunna hantera sådana störningar i positioneringssystemet. Dessa avbrott kan observeras och svar skapas med hjälp av locatabilityChanged-händelsen på standard-SpatialLocator.

Från 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)
               );

När din app tar emot en LocatabilityChanged-händelse kan den ändra beteendet efter behov. I tillståndet PositionalTrackingInhibited kan din app till exempel pausa normal drift och rendera ett hologram med taggar som visar ett varningsmeddelande.

Windows Holographic-appmallen levereras med en LocatabilityChanged-hanterare som redan har skapats åt dig. Som standard visas en varning i felsökningskonsolen när positioneringsspårning inte är tillgänglig. Du kan lägga till kod i den här hanteraren för att tillhandahålla ett svar efter behov från din app.

Från 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;
       }
   }

Rumslig mappning

API:erna för rumslig mappning använder koordinatsystem för att hämta modelltransformeringar för ytnät.

Se även