Sistemas de coordenadas no DirectX

Observação

Este artigo está relacionado às APIs nativas herdadas do WinRT. Para novos projetos de aplicativo nativo, é recomendável usar a API OpenXR.

Os sistemas de coordenadas formam a base para a compreensão espacial oferecida pelas APIs Windows Mixed Reality.

Os dispositivos VR ou VR de sala única de hoje estabelecem um sistema de coordenadas primário para seu espaço rastreado. Realidade Misturada dispositivos como o HoloLens são projetados para grandes ambientes indefinidos, com o dispositivo descobrindo e aprendendo sobre seus arredores à medida que o usuário percorre. O dispositivo se adapta à melhoria contínua do conhecimento sobre as salas do usuário, mas resulta em sistemas de coordenadas que alteram sua relação entre si durante o tempo de vida dos aplicativos. Windows Mixed Reality dá suporte a um amplo espectro de dispositivos, desde headsets imersivos sentados até quadros de referência conectados ao mundo.

Observação

Os snippets de código neste artigo demonstram atualmente o uso do C++/CX em vez do C++17 compatível com C++/WinRT, conforme usado no modelo de projeto holográfico C++. Os conceitos são equivalentes a um projeto C++/WinRT, embora você precise traduzir o código.

Sistemas de coordenadas espaciais no Windows

O tipo principal usado para raciocinar sobre sistemas de coordenadas do mundo real no Windows é SpatialCoordinateSystem. Uma instância desse tipo representa um sistema de coordenadas arbitrário, fornecendo um método para obter dados de matriz de transformação que você pode usar para transformar entre dois sistemas de coordenadas sem entender os detalhes de cada um.

Os métodos que retornam informações espaciais aceitarão um parâmetro SpatialCoordinateSystem para permitir que você decida o sistema de coordenadas no qual é mais útil que essas coordenadas sejam retornadas. As informações espaciais são representadas como pontos, raios ou volumes nos arredores do usuário, e as unidades dessas coordenadas sempre estarão em metros.

Um SpatialCoordinateSystem tem uma relação dinâmica com outros sistemas de coordenadas, incluindo aqueles 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, seu aplicativo deve estar pronto para lidar com períodos de tempo durante os quais eles não podem ser localizados.

Seu aplicativo não deve criar SpatialCoordinateSystems diretamente, em vez disso, eles devem ser consumidos por meio das APIs de Percepção. Há três fontes primárias de sistemas de coordenadas nas APIs de Percepção, cada uma mapeada para um conceito descrito na página Sistemas de coordenadas :

Todos os sistemas de coordenadas retornados por esses objetos são destros, com +y para cima, +x para a direita e +z para trás. Você pode se lembrar de qual direção o eixo z positivo aponta para os dedos da mão esquerda ou direita na direção positiva x e enrolando-os na direção y positiva. A direção do seu polegar aponta em sua direção ou para longe de você, é a direção em que o eixo z positivo aponta para esse sistema de coordenadas. A ilustração a seguir mostra esses dois sistemas de coordenadas.

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

Use a classe SpatialLocator para criar um quadro anexado ou estacionário de referência para inicializar em um SpatialCoordinateSystem com base na posição do HoloLens. Prossiga para a próxima seção para saber mais sobre esse processo.

Colocar hologramas no mundo usando um estágio espacial

O sistema de coordenadas para headsets imersivos Windows Mixed Reality opacos é acessado usando a propriedade estática SpatialStageFrameOfReference::Current. Essa API fornece:

  • Um sistema de coordenadas
  • Informações sobre se o jogador 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 fone de ouvido é direcional.
  • Um manipulador de eventos para atualizações no estágio espacial.

Primeiro, obtemos o estágio espacial e assinamos atualizações para ele:

Código para inicialização de estágio 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, seu aplicativo deve inspecionar o estágio espacial e atualizar a experiência do jogador. Neste exemplo, fornecemos uma visualização do limite de estágio e a posição inicial especificada pelo usuário e o intervalo de exibição e intervalo de propriedades de movimento do estágio. Também voltamos ao nosso próprio sistema de coordenadas estacionárias, quando não é possível fornecer um estágio.

Código para atualização de estágio 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 do estágio é fornecido em ordem no sentido horário. O shell Windows Mixed Reality desenha uma cerca no limite quando o usuário se aproxima dele, mas talvez você queira triangular a área andável para seus próprios propósitos. O algoritmo a seguir pode ser usado para triangular o estágio.

Código para triangulação de estágio 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 usando um quadro de referência estacionário

A classe SpatialStationaryFrameOfReference representa um quadro de referência que permanece estacionário em relação ao ambiente do usuário à medida que o usuário se move. Esse quadro de referência prioriza manter as coordenadas estáveis perto do dispositivo. Um dos principais usos de um SpatialStationaryFrameOfReference é atuar como o sistema de coordenadas do mundo subjacente dentro de um mecanismo de renderização ao renderizar hologramas.

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

No código do modelo de aplicativo holográfico 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 quadros de referência estacionários são projetados para fornecer uma posição mais adequada em relação ao espaço geral. Posições individuais dentro desse quadro de referência têm permissão para descompasso ligeiramente. Isso é normal, pois o dispositivo aprende mais sobre o ambiente.
  • Quando o posicionamento preciso de hologramas individuais é necessário, um SpatialAnchor deve ser usado para ancorar o holograma individual a uma posição no mundo real , por exemplo, um ponto que o usuário indica ser de interesse especial. As posições de âncora não descompassam, mas podem ser corrigidas; a âncora usará a posição corrigida começando no próximo quadro após a correção.

Colocar hologramas no mundo usando âncoras espaciais

As âncoras espaciais são uma ótima maneira de colocar hologramas em um local específico no mundo real, com o sistema garantindo que a âncora permaneça no lugar ao longo do tempo. Este tópico explica como criar e usar uma âncora e como trabalhar com dados de âncora.

Você pode criar um SpatialAnchor em qualquer posição e orientação dentro do SpatialCoordinateSystem de sua escolha. O dispositivo deve ser capaz de localizar esse sistema de coordenadas no momento e o sistema não deve ter atingido seu limite de âncoras espaciais.

Uma vez definido, o sistema de coordenadas de um SpatialAnchor se ajusta continuamente para manter a posição e a orientação precisas de seu local inicial. Em seguida, você pode usar esse SpatialAnchor para renderizar hologramas que aparecerão fixos nos arredores do usuário nesse local exato.

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

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

Use a propriedade RawCoordinateSystem e o evento RawCoordinateSystemAdjusted correspondente para gerenciar esses ajustes por conta própria.

Persistir e compartilhar âncoras espaciais

Você pode persistir um SpatialAnchor localmente usando a classe SpatialAnchorStore e, em seguida, obtê-lo novamente em uma sessão de aplicativo futura no mesmo dispositivo HoloLens.

Usando as Âncoras Espaciais do Azure, você pode criar uma âncora de nuvem durável de um SpatialAnchor local, que seu aplicativo pode localizar em vários dispositivos HoloLens, iOS e Android. Ao compartilhar uma âncora espacial comum em vários dispositivos, cada usuário pode ver o conteúdo renderizado em relação a essa âncora no mesmo local físico em tempo real.

Você também pode usar âncoras espaciais do Azure para persistência de holograma assíncrona em dispositivos HoloLens, iOS e Android. Ao compartilhar uma âncora espacial de nuvem durável, vários dispositivos podem observar o mesmo holograma persistente ao longo do tempo, mesmo que esses dispositivos não estejam presentes juntos ao mesmo tempo.

Para começar a criar experiências compartilhadas em seu aplicativo HoloLens, experimente o início rápido do HoloLens das Âncoras Espaciais do Azure de 5 minutos.

Quando estiver em execução com as Âncoras Espaciais do Azure, você poderá criar e localizar âncoras no HoloLens. Os passo a passo também estão disponíveis para Android e iOS , permitindo que você compartilhe as mesmas âncoras em todos os dispositivos.

Criar SpatialAnchors para conteúdo holográfico

Para este exemplo de código, modificamos o modelo de aplicativo do Windows Holographic para criar âncoras quando o gesto Pressionado é detectado. Em seguida, o cubo é colocado na âncora durante a passagem de renderização.

Como há suporte para várias âncoras na classe auxiliar, podemos colocar quantos cubos quisermos usar este exemplo de código!

Observação

As IDs para âncoras são algo que você controla em seu aplicativo. Neste exemplo, criamos um esquema de nomenclatura sequencial com base no número de âncoras armazenadas atualmente na coleção de âncoras do aplicativo.

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

Carregar e armazenar em cache de forma assíncrona o SpatialAnchorStore

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

  • Armazenando uma coleção de âncoras na memória, indexadas por uma chave Platform::String.
  • Carregando âncoras do SpatialAnchorStore do sistema, que é mantido separado da coleção local na memória.
  • Salvando a coleção local na memória de âncoras no SpatialAnchorStore quando o aplicativo optar por fazer isso.

Veja como salvar objetos SpatialAnchor no SpatialAnchorStore.

Quando a classe é iniciada, solicitamos o SpatialAnchorStore de forma assíncrona. Isso envolve a E/S do sistema, pois a API carrega o repositório de âncoras e essa API é tornada assíncrona para que a E/S não seja bloqueada.

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

Você receberá um SpatialAnchorStore que pode ser usado para salvar as âncoras. Este é um IMapView que associa valores de chave que são Cadeias de caracteres, com valores de dados que são SpatialAnchors. Em nosso código de exemplo, armazenamos isso em uma variável de membro de classe privada que pode ser acessada por meio de uma função pública de nossa classe auxiliar.

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

Observação

Não se esqueça de conectar os eventos de suspensão/retomada para salvar e carregar o repositório de âncoras.

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

Salvar conteúdo no repositório de âncoras

Quando o sistema suspende seu aplicativo, você precisa salvar suas âncoras espaciais no repositório de âncoras. Você também pode optar por salvar âncoras no repositório de âncoras em outros momentos, conforme achar necessário para a implementação do aplicativo.

Quando estiver pronto para tentar salvar as âncoras na memória no SpatialAnchorStore, você poderá executar um loop em sua coleção e tentar salvar 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 do repositório de âncoras quando o aplicativo for retomado

Você pode restaurar âncoras salvas no AnchorStore transferindo-as do IMapView do repositório de âncoras para seu próprio banco de dados na memória de SpatialAnchors quando seu aplicativo for retomado ou a qualquer momento.

Para restaurar âncoras do SpatialAnchorStore, restaure cada uma das quais você está interessado em sua própria coleção na memória.

Você precisa de seu próprio banco de dados na memória de SpatialAnchors para associar Cadeias de Caracteres aos SpatialAnchors que você criar. Em nosso código de exemplo, optamos por usar um Windows::Foundation::Collections::IMap para armazenar as âncoras, o que facilita o uso da mesma chave e valor de dados para 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;

Observação

Uma âncora restaurada pode não ser localizada imediatamente. Por exemplo, pode ser uma âncora em uma sala separada ou em um prédio completamente diferente. As âncoras recuperadas do AnchorStore devem ser testadas quanto à localizabilidade antes de usá-las.


Observação

Neste código de exemplo, recuperamos todas as âncoras do AnchorStore. Isso não é um requisito; seu aplicativo poderia escolher um determinado subconjunto de âncoras usando valores de chave de cadeia de caracteres que são significativos para 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 repositório de âncoras, quando necessário

Às vezes, você precisa limpar o estado do aplicativo e gravar novos dados. Veja como fazer isso com o SpatialAnchorStore.

Usando nossa classe auxiliar, é quase desnecessário encapsular a função Clear. Optamos por fazer isso em nossa implementação de exemplo, pois nossa classe auxiliar recebe 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: relacionando sistemas de coordenadas de âncora a sistemas de coordenadas de quadro de referência estacionários

Digamos que você tenha uma âncora e queira relacionar algo no sistema de coordenadas da âncora ao SpatialStationaryReferenceFrame que você já está usando para seu outro conteúdo. Você pode usar TryGetTransformTo para obter uma transformação do sistema de coordenadas da âncora para a do quadro de referência estacionário:

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

Esse processo é útil para você de duas maneiras:

  1. Ele informa se os dois quadros de referência podem ser compreendidos um com o outro e;
  2. Nesse caso, ele fornece uma transformação para ir diretamente de um sistema de coordenadas para o outro.

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

Para renderização, muitas vezes você pode obter melhores resultados agrupando objetos de acordo com seu quadro de referência ou âncora original. Execute uma passagem de desenho separada para cada grupo. As matrizes de exibição são mais precisas para objetos com transformações de modelo criadas inicialmente usando o mesmo sistema de coordenadas.

Criar hologramas usando um quadro de referência anexado ao dispositivo

Há ocasiões em que você deseja renderizar um holograma que permanece anexado ao local do dispositivo, por exemplo, um painel com informações de depuração ou uma mensagem informativa quando o dispositivo só é capaz de determinar sua orientação e não sua posição no espaço. Para fazer isso, usamos um quadro de referência anexado.

A classe SpatialLocatorAttachedFrameOfReference define sistemas de coordenadas, que são relativos ao dispositivo e não ao mundo real. Esse quadro tem um título fixo relativo ao ambiente do usuário que aponta na direção que o usuário estava enfrentando quando o quadro de referência foi criado. A partir daí, todas as orientações nesse quadro de referência são relativas a esse título fixo, mesmo quando o usuário gira o dispositivo.

Para o HoloLens, a origem do sistema de coordenadas desse quadro está localizada no centro de rotação da cabeça do usuário, para que sua posição não seja afetada pela rotação da cabeça. Seu aplicativo pode especificar um deslocamento em relação a esse ponto para posicionar hologramas na frente do usuário.

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

Isso se aplica a todo o intervalo de dispositivos Windows Mixed Reality.

Usar um quadro de referência anexado ao dispositivo

Estas seções falam sobre o que alteramos no modelo de aplicativo holográfico do Windows para habilitar um quadro de referência anexado ao dispositivo usando essa API. Esse holograma "anexado" funcionará junto com hologramas estacionários ou ancorados e também poderá ser usado quando o dispositivo for temporariamente incapaz de encontrar sua posição no mundo.

Primeiro, alteramos o modelo para armazenar um SpatialLocatorAttachedFrameOfReference em vez de um SpatialStationaryFrameOfReference:

De HolographicTagAlongSampleMain.h:

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

De HolographicTagAlongSampleMain.cpp:

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

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

   // 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 Foco do usuário

Queremos que nosso holograma de exemplo siga o foco do usuário, semelhante a como o shell holográfico pode seguir o foco do usuário. Para isso, precisamos obter o SpatialPointerPose 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 título atual do usuário.

Para o conforto do usuário, usamos a interpolação linear ("lerp") para suavizar a alteração na posição durante um período de tempo. Isso é mais confortável para o usuário do que bloquear o holograma em seu olhar. Ler a posição do holograma de marcação também nos permite estabilizar o holograma amortecendo o movimento. Se não fizéssemos esse amortecimento, o usuário veria o holograma tremulando devido ao que normalmente são considerados movimentos imperceptíveis da cabeça do usuário.

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

Observação

No caso de um painel de depuração, você pode optar por reposicionar o holograma um pouco para o lado para que ele não obstrua sua exibição. Aqui está um exemplo de como você pode fazer isso.

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

Girar o holograma para enfrentar a câmera

Não é suficiente posicionar o holograma, que nesse caso é um quad; também devemos girar o objeto para enfrentar o usuário. Essa rotação ocorre no espaço do mundo, pois esse tipo de outdoor permite que o holograma permaneça como parte do ambiente do usuário. O outdoor de espaço de exibição não é tão confortável porque o holograma fica bloqueado para a orientação de exibição; Nesse caso, você também teria que interpolar entre as matrizes de exibição esquerda e direita para adquirir uma transformação de outdoor de espaço de exibição que não interrompa a renderização estéreo. Aqui, giramos nos eixos X e Z para enfrentar o usuário.

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

Renderizar o holograma anexado

Para este exemplo, também optamos por renderizar o holograma no sistema de coordenadas do SpatialLocatorAttachedReferenceFrame, que é onde posicionamos o holograma. (Se tivéssemos decidido renderizar usando outro sistema de coordenadas, precisaríamos adquirir uma transformação do sistema de coordenadas do quadro de referência anexado ao dispositivo para esse sistema de coordenadas.)

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

É isso! O holograma agora "perseguirá" uma posição de 2 metros na frente da direção do foco do usuário.

Observação

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

Manipulando a perda de rastreamento

Quando o dispositivo não consegue se localizar no mundo, o aplicativo experimenta a "perda de rastreamento". Windows Mixed Reality aplicativos devem ser capazes de lidar com essas interrupções no sistema de acompanhamento posicional. Essas interrupções podem ser observadas e as respostas criadas usando o evento LocatabilityChanged no SpatialLocator padrão.

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 seu aplicativo recebe um evento LocatabilityChanged, ele pode alterar o comportamento conforme necessário. Por exemplo, no estado PositionalTrackingInhibited, seu aplicativo pode pausar a operação normal e renderizar um holograma de marcação que exibe uma mensagem de aviso.

O modelo de aplicativo do Windows Holographic vem com um manipulador LocatabilityChanged já criado para você. Por padrão, ele exibe um aviso no console de depuração quando o acompanhamento posicional não está disponível. Você pode adicionar código a esse manipulador para fornecer uma resposta conforme necessário do seu aplicativo.

Em 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 usam sistemas de coordenadas para obter transformações de modelo para malhas de superfície.

Confira também