Coordenar sistemas em DirectX
Nota
Este artigo está relacionado às APIs nativas herdadas do WinRT. Para novos projetos de aplicativos nativos, recomendamos o uso da API OpenXR.
Os sistemas de coordenadas formam a base para a compreensão espacial oferecida pelas APIs do Windows Mixed Reality.
Os dispositivos VR sentados ou VR de sala única de hoje estabelecem um sistema de coordenadas principal para seu espaço rastreado. Dispositivos de realidade mista como o HoloLens são projetados para grandes ambientes indefinidos, com o dispositivo descobrindo e aprendendo sobre seus arredores enquanto o usuário caminha. O dispositivo se adapta para melhorar continuamente o conhecimento sobre as salas do usuário, mas resulta em sistemas de coordenadas que mudam seu relacionamento uns com os outros ao longo da vida útil dos aplicativos. O Windows Mixed Reality suporta um amplo espectro de dispositivos, desde fones de ouvido imersivos sentados até quadros de referência conectados ao mundo.
Nota
Os trechos de código neste artigo atualmente demonstram o uso de C++/CX em vez de C++/WinRT compatível com C++17 como usado no modelo de projeto holográfico C++. Os conceitos são equivalentes para 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 é o 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 retornar essas coordenadas. A informação espacial é representada como pontos, raios ou volumes no entorno do usuário, e as unidades para essas coordenadas serão sempre 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 perceção. Existem três fontes primárias de sistemas de coordenadas nas APIs de perceção, cada uma das quais mapeada para um conceito descrito na página Sistemas de coordenadas :
- Para obter um quadro de referência estacionário, crie um SpatialStationaryFrameOfReference ou obtenha um do SpatialStageFrameOfReference atual.
- Para obter uma âncora espacial, crie uma SpatialAnchor.
- Para obter um quadro de referência anexado, crie um SpatialLocatorAttachedFrameOfReference.
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 apontando os dedos da sua mão esquerda ou direita na direção x positiva e enrolando-os na direção y positiva. A direção que o polegar aponta, para você ou para longe, é a direção 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 do lado esquerdo e do lado direito
Use a classe SpatialLocator para criar um quadro de referência anexado ou estacionário para inicializar em um SpatialCoordinateSystem com base na posição HoloLens. Continue para a próxima seção para saber mais sobre esse processo.
Coloque hologramas no mundo usando um palco espacial
O sistema de coordenadas para fones de ouvido imersivos opacos do Windows Mixed Reality é acessado usando a propriedade estática SpatialStageFrameOfReference::Current . Esta API fornece:
- Um sistema de coordenadas
- Informações sobre se o jogador está sentado ou móvel
- O limite de uma área segura para passear se o jogador for móvel
- Uma indicação de se o fone de ouvido é direcional.
- Um manipulador de eventos para atualizações do estágio espacial.
Primeiro, obtemos o estágio espacial e nos inscrevemos para atualizações para ele:
Código para inicialização do 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 do estágio e da posição inicial especificada pelo usuário e das propriedades de intervalo de visão e intervalo de movimento do estágio. Também recorremos ao nosso próprio sistema de coordenadas estacionárias, quando um estágio não pode ser fornecido.
Código para atualização do 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 são fornecidos em ordem horária. O shell de Realidade Mista do Windows desenha uma cerca no limite quando o usuário se aproxima dele, mas você pode querer triangularizar a área caminhável para seus próprios propósitos. O seguinte algoritmo pode ser usado para triangular o palco.
Código para triangularizaçã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;
}
Coloque 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 ele se move. Este quadro de referência prioriza manter as coordenadas estáveis perto do dispositivo. Um uso chave 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.
A partir do código do 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 quadros de referência estacionários são projetados para fornecer uma posição mais adequada em relação ao espaço total. As posições individuais dentro desse quadro de referência podem desviar-se ligeiramente. Isso é normal à medida que o dispositivo aprende mais sobre o ambiente.
- Quando a colocação precisa de hologramas individuais é necessária, 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 ancoragem não se desviam, mas podem ser corrigidas; A âncora usará a posição corrigida a partir do próximo quadro após a correção ter ocorrido.
Coloque hologramas no mundo usando âncoras espaciais
As âncoras espaciais são uma ótima maneira de colocar hologramas em um lugar 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 o seu limite de ancoragens espaciais.
Uma vez definido, o sistema de coordenadas de um SpatialAnchor ajusta-se continuamente para manter a posição e orientação precisas da sua localização inicial. Em seguida, você pode usar esse SpatialAnchor para renderizar hologramas que aparecerão fixos nos arredores do usuário naquele 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 relativo 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 o conteúdo relativo à âncora, com flexibilização aplicada quando o dispositivo ajusta a localização precisa da âncora.
Use a propriedade RawCoordinateSystem e o evento RawCoordinateSystemAdjusted correspondente para gerenciar esses ajustes você mesmo.
Você pode persistir um SpatialAnchor localmente usando a classe SpatialAnchorStore e, em seguida, recuperá-lo em uma sessão futura do aplicativo no mesmo dispositivo HoloLens.
Criar SpatialAnchors para conteúdo holográfico
Para este exemplo de código, modificamos o modelo de aplicativo holográfico do Windows para criar âncoras quando o gesto pressionado for detetado. O cubo é então colocado na âncora durante a passagem de renderização.
Como várias âncoras são suportadas pela classe auxiliar, podemos colocar quantos cubos quisermos usar este exemplo de código!
Nota
Os IDs para âncoras são algo que você controla em seu aplicativo. Neste exemplo, criamos um esquema de nomenclatura que é sequencial com base no número de âncoras atualmente armazenadas 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);
}
}
}
Carregue e armazene 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.
- Salvar a coleção local na memória de âncoras na SpatialAnchorStore quando o aplicativo optar por fazê-lo.
Veja como salvar objetos SpatialAnchor no SpatialAnchorStore.
Quando a classe é iniciada, solicitamos o SpatialAnchorStore de forma assíncrona. Isso envolve E/S do sistema à medida que a API carrega o armazenamento âncora, 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á uma SpatialAnchorStore que você pode usar para salvar as âncoras. Este é um IMapView que associa valores-chave que são Strings, 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 é acessível 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^>();
}
Nota
Não se esqueça de conectar os eventos suspender/retomar para salvar e carregar o armazenamento â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();
}
Salvar conteúdo na loja âncora
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 seu aplicativo.
Quando estiver pronto para tentar salvar as âncoras na memória na SpatialAnchorStore, você pode percorrer 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 da loja âncora quando o aplicativo for retomado
Você pode restaurar âncoras salvas na AnchorStore transferindo-as do IMapView da loja âncora para seu próprio banco de dados na memória de SpatialAnchors quando seu aplicativo for retomado ou a qualquer momento.
Para restaurar âncoras da SpatialAnchorStore, restaure cada uma das que lhe interessam para a sua própria coleção na memória.
Você precisa de seu próprio banco de dados na memória de SpatialAnchors para associar Strings com as SpatialAnchors que você cria. 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 do mesmo 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 ser localizável imediatamente. Por exemplo, pode ser uma âncora em uma sala separada ou em um prédio completamente diferente. As âncoras recuperadas da AnchorStore devem ser testadas quanto à sua localização antes de serem utilizadas.
Nota
Neste código de exemplo, recuperamos todas as âncoras da AnchorStore. Não se trata de um requisito; seu aplicativo também pode escolher um determinado subconjunto de âncoras usando valores de chave String 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);
}
}
}
Limpe a loja âncora, quando necessário
Às vezes, você precisa limpar o estado do aplicativo e gravar novos dados. Veja como você faz isso com a SpatialAnchorStore.
Usando nossa classe auxiliar, é quase desnecessário envolver a função Clear. Optamos por fazê-lo em nossa implementação de exemplo, porque 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: Relacionar sistemas de coordenadas de ancoragem com sistemas de coordenadas de quadro de referência estacionário
Digamos que você tenha uma âncora e queira relacionar algo no sistema de coordenadas da sua âncora com o 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 o 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;
}
Este processo é útil para si de duas formas:
- Diz-lhe se os dois quadros de referência podem ser entendidos um em relação ao outro, e;
- Em caso afirmativo, 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 um passo de desenho separado para cada grupo. As matrizes de exibição são mais precisas para objetos com transformações de modelo que são criadas inicialmente usando o mesmo sistema de coordenadas.
Crie hologramas usando um quadro de referência conectado ao dispositivo
Há momentos em que você deseja renderizar um holograma que permanece conectado à localização 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 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. Este quadro tem um título fixo relativo ao entorno do usuário que aponta na direção que o usuário estava enfrentando quando o quadro de referência foi criado. A partir de então, todas as orientações neste quadro de referência são relativas a esse cabeçalho fixo, mesmo quando o usuário gira o dispositivo.
Para o HoloLens, a origem do sistema de coordenadas deste 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 relativo a esse ponto para posicionar hologramas na frente do usuário.
Para obter um SpatialLocatorAttachedFrameOfReference, use a classe SpatialLocator e chame CreateAttachedFrameOfReferenceAtCurrentHeading.
Isto aplica-se a toda a gama de dispositivos Windows Mixed Reality.
Usar um quadro de referência anexado ao dispositivo
Estas seções falam sobre o que mudamos no modelo de aplicativo holográfico do Windows para habilitar um quadro de referência anexado ao dispositivo usando essa API. Este holograma "acoplado" funcionará ao lado de hologramas fixos ou ancorados, e também pode ser usado quando o dispositivo é 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;
Do 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 do 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);
Obtenha uma pose de ponteiro espacial e siga o Olhar do usuário
Queremos que o nosso holograma de exemplo siga o olhar do utilizador, semelhante à forma como a concha holográfica pode seguir o olhar do utilizador. 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 conforto do usuário, usamos interpolação linear ("lerp") para suavizar a mudança de posição durante um período de tempo. Isso é mais confortável para o usuário do que prender o holograma ao seu olhar. Ler a posição do holograma tag-along também nos permite estabilizar o holograma, atenuando o movimento. Se não fizéssemos esse amortecimento, o usuário veria o jitter do holograma por causa do que normalmente são considerados movimentos impercetí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);
}
Nota
No caso de um painel de depuração, você pode optar por reposicionar o holograma para o lado um pouco para que ele não obstrua sua visã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));
*/
Rode o holograma de frente para a câmara
Não basta posicionar o holograma, que neste caso é um quadriciclo; Também devemos girar o objeto para enfrentar o usuário. Esta rotação ocorre no espaço mundial, porque este tipo de billboard permite que o holograma continue a fazer parte do ambiente do utilizador. O painel do espaço de visualização não é tão confortável porque o holograma fica bloqueado na orientação do ecrã; Nesse caso, você também teria que interpolar entre as matrizes de visualização esquerda e direita para adquirir uma transformação de outdoor de espaço de visualizaçã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)
);
Está feito! O holograma agora vai "perseguir" uma posição que está 2 metros à frente da direção do olhar do usuário.
Nota
Este exemplo também carrega conteúdo adicional - consulte StationaryQuadRenderer.cpp.
Lidando com a perda de rastreamento
Quando o dispositivo não consegue se localizar no mundo, o aplicativo experimenta "perda de rastreamento". Os aplicativos do Windows Mixed Reality devem ser capazes de lidar com essas interrupções no sistema de rastreamento 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 tag along que exibe uma mensagem de aviso.
O modelo de aplicativo holográfico do Windows vem com um manipulador LocatabilityChanged já criado para você. Por padrão, ele exibe um aviso no console de depuração quando o rastreamento posicional não está disponível. Você pode adicionar código a esse manipulador para fornecer uma resposta conforme necessário do seu aplicativo.
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 usam sistemas de coordenadas para obter transformações de modelo para malhas de superfície.