Coordenar sistemas no DirectX

Nota

Este artigo está relacionado com as APIs nativas do WinRT legadas. Para novos projetos de aplicações nativas, recomendamos que utilize a API OpenXR.

Os sistemas de coordenação constituem a base para a compreensão espacial oferecida pelas APIs Windows Mixed Reality.

Os dispositivos VR ou VR de sala única atuais estabelecem um sistema de coordenadas principal para o seu espaço controlado. Mixed Reality dispositivos como o HoloLens foram concebidos para grandes ambientes indefinidos, com o dispositivo a descobrir e a aprender sobre o seu ambiente à medida que o utilizador anda por aí. O dispositivo adapta-se para melhorar continuamente o conhecimento sobre as salas do utilizador, mas resulta em sistemas de coordenação que alteram a relação entre si ao longo da duração das aplicações. Windows Mixed Reality suporta um vasto espectro de dispositivos, desde auscultadores envolventes sentados até molduras de referência ligadas ao mundo.

Nota

Os fragmentos de código neste artigo demonstram atualmente a utilização de C+++/CX em vez de C++17 compatíveis com C++/WinRT, conforme utilizado no modelo de projeto holográfico C++. Os conceitos são equivalentes para um projeto C++/WinRT, embora tenha de traduzir o código.

Sistemas de coordenadas espaciais no Windows

O tipo principal utilizado para fundamentar os sistemas de coordenadas do mundo real no Windows é o SpatialCoordinateSystem. Uma instância deste tipo representa um sistema de coordenadas arbitrário, fornecendo um método para obter dados de matriz de transformação que pode utilizar para transformar entre dois sistemas de coordenadas sem compreender os detalhes de cada um.

Os métodos que devolvem informações espaciais aceitarão um parâmetro SpatialCoordinateSystem para que decida o sistema de coordenadas no qual é mais útil devolver essas coordenadas. As informações espaciais são representadas como pontos, raios ou volumes no ambiente do utilizador e as unidades destas coordenadas estarão sempre em metros.

Um SpatialCoordinateSystem tem uma relação dinâmica com outros sistemas de coordenadas, incluindo os que representam a posição do dispositivo. A qualquer momento, o dispositivo pode localizar alguns sistemas de coordenadas e não outros. Para a maioria dos sistemas de coordenadas, a sua aplicação tem de estar pronta para lidar com períodos de tempo durante os quais não podem ser localizados.

A sua aplicação não deve criar spatialCoordinateSystems diretamente, mas sim ser consumida através das APIs de Perceção. Existem três origens primárias de sistemas de coordenadas nas APIs de Percepção, cada uma das quais mapeia um conceito descrito na página Coordenar sistemas :

Todos os sistemas de coordenadas devolvidos por estes objetos são destros, com +y para cima, +x para a direita e +z para trás. Lembre-se de que direção o eixo z positivo aponta apontando os dedos da mão esquerda ou direita na direção x positiva e enrolando-os na direção positiva y. A direção que o polegar aponta, quer na direção ou longe de si, é a direção que o eixo z positivo aponta para esse sistema de coordenadas. A ilustração seguinte mostra estes dois sistemas de coordenadas.

Sistemas de coordenadas à esquerda e à direita
Sistemas de coordenadas à esquerda e à direita

Utilize a classe SpatialLocator para criar uma moldura fixa ou estacionária de referência para bootstrap num SpatialCoordinateSystem com base na posição holoLens. Avance para a secção seguinte para saber mais sobre este processo.

Colocar hologramas no mundo com uma fase espacial

O sistema de coordenadas para headsets opacos Windows Mixed Reality envolventes é acedido com a propriedade estática SpatialStageFrameOfReference::Current. Esta API fornece:

  • Um sistema de coordenadas
  • Informações sobre se o leitor está sentado ou móvel
  • O limite de uma área segura para andar por aí se o jogador for móvel
  • Uma indicação de se o headset é direcional.
  • Um processador de eventos para atualizações à fase espacial.

Primeiro, obtemos a fase espacial e subscrevemos as atualizações da mesma:

Código para inicialização da fase espacial

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

No método OnCurrentChanged, a sua aplicação deve inspecionar a fase espacial e atualizar a experiência do leitor. Neste exemplo, fornecemos uma visualização do limite de fase e da posição de início especificada pelo utilizador e pelo intervalo de vistas e intervalo de propriedades de movimento da fase. Também recuamos para o nosso próprio sistema de coordenadas estacionárias, quando não é possível fornecer uma fase.

Código para atualização da fase espacial

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

O conjunto de vértices que definem o limite de fase são fornecidos por ordem dos ponteiros do relógio. A Windows Mixed Reality shell desenha uma vedação no limite quando o utilizador se aproxima da mesma, mas poderá querer triangularizar a área walkable para os seus próprios fins. O algoritmo seguinte pode ser utilizado para triangularizar a fase.

Código para triangularização da fase espacial

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

Colocar hologramas no mundo com um fotograma estacionário de referência

A classe SpatialStationaryFrameOfReference representa um quadro de referência que permanece estacionário em relação ao ambiente do utilizador à medida que o utilizador se desloca. Este quadro de referência dá prioridade a manter as coordenadas estáveis perto do dispositivo. Uma das principais utilizações de um SpatialStationaryFrameOfReference é agir como o sistema de coordenadas do mundo subjacente dentro de um motor de composição ao compor hologramas.

Para obter um SpatialStationaryFrameOfReference, utilize a classe SpatialLocator e chame CreateStationaryFrameOfReferenceAtCurrentLocation.

No código de modelo da aplicação Holográfica do Windows:

           // 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();
  • Os fotogramas de referência estacionária foram concebidos para fornecer uma posição mais adequada em relação ao espaço geral. As posições individuais dentro desse quadro de referência podem deslizar ligeiramente. Isto é normal, uma vez que o dispositivo aprende mais sobre o ambiente.
  • Quando é necessária uma colocação precisa de hologramas individuais, deve ser utilizado um SpatialAnchor para ancorar o holograma individual numa posição no mundo real - por exemplo, um ponto que o utilizador indica ser de especial interesse. As posições de âncora não desfasam, mas podem ser corrigidas; a âncora utilizará a posição corrigida a partir do fotograma seguinte após a correção ter ocorrido.

Colocar hologramas no mundo com âncoras espaciais

As âncoras espaciais são uma excelente forma de colocar hologramas num local específico no mundo real, com o sistema a garantir que a âncora permanece no lugar ao longo do tempo. Este tópico explica como criar e utilizar uma âncora e como trabalhar com dados de âncora.

Pode criar um SpatialAnchor em qualquer posição e orientação no SpatialCoordinateSystem à sua escolha. Neste momento, o dispositivo tem de conseguir localizar esse sistema de coordenadas e o sistema não deve ter atingido o limite de âncoras espaciais.

Uma vez definido, o sistema de coordenadas de um SpatialAnchor ajusta-se continuamente para manter a posição e a orientação precisas da localização inicial. Em seguida, pode utilizar este SpatialAnchor para compor hologramas que serão apresentados fixos no ambiente do utilizador nessa localização exata.

Os efeitos dos ajustes que mantêm a âncora no lugar são ampliados à medida que a distância da âncora aumenta. Deve evitar a composição de conteúdos relativos a uma âncora que esteja a mais de 3 metros da origem dessa âncora.

A propriedade CoordinateSystem obtém um sistema de coordenadas que lhe permite colocar conteúdo em relação à âncora, com a facilidade aplicada quando o dispositivo ajusta a localização precisa da âncora.

Utilize a propriedade RawCoordinateSystem e o evento RawCoordinateSystemAdjusted correspondente para gerir estes ajustes.

Manter e partilhar âncoras espaciais

Pode manter um SpatialAnchor localmente com a classe SpatialAnchorStore e, em seguida, recuperá-lo numa futura sessão de aplicação no mesmo dispositivo HoloLens.

Ao utilizar o Azure Spatial Anchors, pode criar uma âncora de cloud durável a partir de um SpatialAnchor local, que a sua aplicação pode localizar em vários dispositivos HoloLens, iOS e Android. Ao partilhar uma âncora espacial comum em vários dispositivos, cada utilizador pode ver conteúdo composto em relação a essa âncora na mesma localização física em tempo real.

Também pode utilizar âncoras espaciais do Azure para persistência de hologramas assíncronos em dispositivos HoloLens, iOS e Android. Ao partilhar uma âncora espacial na cloud durável, vários dispositivos podem observar o mesmo holograma persistente ao longo do tempo, mesmo que esses dispositivos não estejam presentes em conjunto ao mesmo tempo.

Para começar a criar experiências partilhadas na sua aplicação HoloLens, experimente o início rápido do HoloLens das Âncoras Espaciais do Azure de 5 minutos.

Assim que estiver em execução com as Âncoras Espaciais do Azure, pode criar e localizar âncoras no HoloLens. As instruções também estão disponíveis para Android e iOS , permitindo-lhe partilhar as mesmas âncoras em todos os dispositivos.

Criar SpatialAnchors para conteúdo holográfico

Para este exemplo de código, modificámos o modelo da aplicação Holográfica do Windows para criar âncoras quando o gesto Premido é detetado. Em seguida, o cubo é colocado na âncora durante o passe de composição.

Uma vez que várias âncoras são suportadas pela classe auxiliar, podemos colocar o número de cubos que quisermos para utilizar este exemplo de código!

Nota

Os IDs das âncoras são algo que controla na sua aplicação. Neste exemplo, criámos um esquema de nomenclatura sequencial com base no número de âncoras atualmente armazenadas na coleção de âncoras da aplicação.

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

Carregamento assíncrono e cache, spatialAnchorStore

Vamos ver como escrever uma classe SampleSpatialAnchorHelper que ajuda a lidar com esta persistência, incluindo:

  • Armazenar uma coleção de âncoras dentro da memória, indexadas por uma chave Plataforma::Cadeia.
  • A carregar âncoras da SpatialAnchorStore do sistema, que é mantida separada da coleção local dentro da memória.
  • Guardar a coleção local dentro da memória de âncoras na SpatialAnchorStore quando a aplicação optar por fazê-lo.

Eis como guardar objetos SpatialAnchor na SpatialAnchorStore.

Quando a classe é iniciada, solicitamos o SpatialAnchorStore de forma assíncrona. Isto envolve a E/S do sistema à medida que a API carrega o arquivo de âncora e esta API é tornada assíncrona para que a E/S não esteja a bloquear.

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

Receberá um SpatialAnchorStore que pode utilizar para guardar as âncoras. Este é um IMapView que associa valores-chave que são Cadeias, com valores de dados que são SpatialAnchors. No nosso código de exemplo, armazenamo-lo numa variável de membro de classe privada acessível através de uma função pública da nossa classe auxiliar.

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

Nota

Não se esqueça de ligar os eventos suspend/resume para guardar e carregar o arquivo de âncora.

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

Guardar conteúdo no arquivo de âncora

Quando o sistema suspender a sua aplicação, terá de guardar as âncoras espaciais no arquivo de âncoras. Também pode optar por guardar âncoras no arquivo de âncora noutras alturas, uma vez que considera necessário para a implementação da sua aplicação.

Quando estiver pronto para tentar guardar as âncoras dentro da memória na SpatialAnchorStore, pode percorrer a sua coleção e tentar guardar cada uma delas.

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

Carregar conteúdo da loja de âncora quando a aplicação for retomada

Pode restaurar âncoras guardadas na AnchorStore ao transferi-las do IMapView do arquivo de âncora para a sua própria base de dados na memória do SpatialAnchors quando a sua aplicação for retomada ou em qualquer altura.

Para restaurar âncoras a partir da SpatialAnchorStore, restaure cada uma das que lhe interessam para a sua própria coleção dentro da memória.

Precisa da sua própria base de dados na memória de SpatialAnchors para associar Cadeias aos SpatialAnchors que criar. No nosso código de exemplo, optamos por utilizar um Windows::Foundation::Collections::IMap para armazenar as âncoras, o que facilita a utilização da mesma chave e valor de dados para o 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;

Nota

Uma âncora restaurada pode não estar localizada imediatamente. Por exemplo, pode ser uma âncora numa sala separada ou num edifício diferente. As âncoras obtidas a partir do AnchorStore devem ser testadas para localizabilidade antes de as utilizar.


Nota

Neste código de exemplo, obtemos todas as âncoras da AnchorStore. Isto não é um requisito; A sua aplicação pode escolher um determinado subconjunto de âncoras ao utilizar valores de chave de cadeia que sejam relevantes para a sua implementação.

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

Limpar o arquivo de âncora, quando necessário

Por vezes, tem de limpar o estado da aplicação e escrever novos dados. Eis como o faz com a SpatialAnchorStore.

Ao utilizar a nossa classe auxiliar, é quase desnecessário moldar a função Limpar. Optamos por fazê-lo na nossa implementação de exemplo, porque a nossa classe auxiliar tem a responsabilidade de possuir a instância 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();
       }
   }

Exemplo: Relacionar sistemas de coordenadas de âncora com sistemas de coordenadas de molduras de referência estacionária

Digamos que tem uma âncora e que pretende relacionar algo no sistema de coordenadas da âncora com o SpatialStationaryReferenceFrame que já está a utilizar para o seu outro conteúdo. Pode utilizar TryGetTransformTo para obter uma transformação do sistema de coordenadas da âncora para o da moldura de referência estacionária:

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

Este processo é útil para si de duas formas:

  1. Indica se os dois fotogramas de referência podem ser compreendidos em relação uns aos outros e;
  2. Em caso afirmativo, fornece-lhe uma transformação para ir diretamente de um sistema de coordenadas para o outro.

Com estas informações, tem uma compreensão da relação espacial entre objetos entre os dois fotogramas de referência.

Para a composição, muitas vezes pode obter melhores resultados ao agrupar objetos de acordo com a respetiva moldura de referência ou âncora original. Execute um passe de desenho separado para cada grupo. As matrizes de vista são mais precisas para objetos com transformações de modelos que são criadas inicialmente com o mesmo sistema de coordenadas.

Criar hologramas com uma moldura de referência anexada ao dispositivo

Há alturas em que pretende compor um holograma que permanece ligado à localização do dispositivo, por exemplo, um painel com informações de depuração ou uma mensagem informativa quando o dispositivo só consegue determinar a sua orientação e não a sua posição no espaço. Para tal, utilizamos uma moldura de referência anexada.

A classe SpatialLocatorAttachedFrameOfReference define sistemas de coordenadas, que são relativos ao dispositivo e não ao mundo real. Este fotograma tem um cabeçalho fixo relativo ao ambiente do utilizador que aponta na direção que o utilizador estava a enfrentar quando a moldura de referência foi criada. A partir daí, todas as orientações neste quadro de referência são relativas a esse cabeçalho fixo, mesmo quando o utilizador roda o dispositivo.

Para o HoloLens, a origem do sistema de coordenadas desta moldura está localizada no centro da rotação da cabeça do utilizador, para que a sua posição não seja afetada pela rotação da cabeça. A sua aplicação pode especificar um desvio relativamente a este ponto para posicionar hologramas à frente do utilizador.

Para obter um SpatialLocatorAttachedFrameOfReference, utilize a classe SpatialLocator e chame CreateAttachedFrameOfReferenceAtCurrentHeading.

Isto aplica-se a toda a gama de dispositivos Windows Mixed Reality.

Utilizar uma moldura de referência anexada ao dispositivo

Estas secções falam sobre o que alterámos no modelo da aplicação Holográfica do Windows para ativar uma moldura de referência anexada ao dispositivo com esta API. Este holograma "anexado" funcionará ao lado de hologramas estacionários ou ancorados e também poderá ser utilizado quando o dispositivo não conseguir encontrar temporariamente a sua posição no mundo.

Primeiro, alterámos o modelo para armazenar um SpatialLocatorAttachedFrameOfReference em vez de um SpatialStationaryFrameOfReference:

A partir de HolographicTagAlongSampleMain.h:

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

A partir de HolographicTagAlongSampleMain.cpp:

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

Durante a atualização, obtemos agora o sistema de coordenadas no carimbo de data/hora obtido com a predição da moldura.

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

Obter uma pose de ponteiro espacial e seguir o Olhar do utilizador

Queremos que o nosso holograma de exemplo siga o olhar do utilizador, semelhante à forma como a shell holográfica pode seguir o olhar do utilizador. Para tal, precisamos de obter o SpatialPointerPose a partir do mesmo carimbo de data/hora.

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

Este SpatialPointerPose tem as informações necessárias para posicionar o holograma de acordo com o cabeçalho atual do utilizador.

Para o conforto do utilizador, utilizamos a interpolação linear ("lerp") para suavizar a alteração de posição durante um período de tempo. Isto é mais confortável para o utilizador do que bloquear o holograma ao olhar. Lerping a posição do holograma ao longo da etiqueta também nos permite estabilizar o holograma atenuando o movimento. Se não fizê-lo, o utilizador veria o holograma nervoso devido ao que normalmente são considerados movimentos imperceptíveis da cabeça do utilizador.

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

Nota

No caso de um painel de depuração, pode optar por reposicionar um pouco o holograma para o lado para que não obstrua a sua vista. Eis um exemplo de como pode fazê-lo.

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

Rodar o holograma para encarar a câmara

Não é suficiente posicionar o holograma, que neste caso é um quad; também temos de rodar o objeto para enfrentar o utilizador. Esta rotação ocorre no espaço mundial, uma vez que este tipo de outdoors permite que o holograma permaneça parte do ambiente do utilizador. O outdoor do espaço de visualização não é tão confortável porque o holograma fica bloqueado na orientação do ecrã; nesse caso, também teria de interpolar entre as matrizes de vista esquerda e direita para adquirir uma transformação de outdoor view-space que não perturbe a composição estéreo. Aqui, rodamos nos eixos X e Z para enfrentar o utilizador.

A partir de 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));

Compor o holograma anexado

Neste exemplo, também escolhemos compor o holograma no sistema de coordenadas do SpatialLocatorAttachedReferenceFrame, que foi onde posicionamos o holograma. (Se tivéssemos decidido compor com outro sistema de coordenadas, teríamos de adquirir uma transformação do sistema de coordenadas da moldura de referência anexada ao dispositivo para esse sistema de coordenadas.)

A partir de 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)
       );

Já está! O holograma irá agora "perseguir" uma posição que está 2 metros à frente da direção do olhar do utilizador.

Nota

Este exemplo também carrega conteúdo adicional – veja StationaryQuadRenderer.cpp.

Lidar com a perda de controlo

Quando o dispositivo não consegue localizar-se no mundo, a aplicação experimenta "perda de controlo". Windows Mixed Reality aplicações devem conseguir lidar com tais interrupções no sistema de controlo posicional. Estas interrupções podem ser observadas e as respostas criadas através do evento LocatabilityChanged no SpatialLocator predefinido.

A partir de 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)
               );

Quando a aplicação recebe um evento LocatabilityChanged, pode alterar o comportamento conforme necessário. Por exemplo, no estado PositionalTrackingInhibited, a sua aplicação pode interromper o funcionamento normal e compor um holograma de etiquetas que apresenta uma mensagem de aviso.

O modelo da aplicação Do Windows Holographic inclui um processador LocatabilityChanged já criado para si. Por predefinição, apresenta um aviso na consola de depuração quando o controlo posicional está indisponível. Pode adicionar código a este processador para fornecer uma resposta conforme necessário a partir da sua aplicação.

A partir de 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;
       }
   }

Mapeamento espacial

As APIs de mapeamento espacial utilizam sistemas de coordenadas para obter transformações de modelos para malhas de superfície.

Ver também