Sistemi di coordinate in DirectX
Nota
Questo articolo è correlato alle API native WinRT legacy. Per i nuovi progetti di app native, è consigliabile usare l'API OpenXR.
I sistemi di coordinate costituiscono la base per la comprensione spaziale offerta dalle API Windows Mixed Reality.
I dispositivi VR o VR a stanza singola di oggi stabiliscono un sistema di coordinate principale per il loro spazio monitorato. Realtà mista dispositivi come HoloLens sono progettati per ambienti indefiniti di grandi dimensioni, con l'individuazione e l'apprendimento dell'ambiente circostante mentre l'utente cammina. Il dispositivo si adatta a migliorare continuamente le conoscenze sulle stanze dell'utente, ma comporta sistemi di coordinate che cambiano la relazione tra loro nel corso della durata delle app. Windows Mixed Reality supporta un'ampia gamma di dispositivi, che vanno da visori VR immersive seduti a fotogrammi di riferimento collegati al mondo.
Nota
I frammenti di codice in questo articolo illustrano attualmente l'uso di C++/CX anziché C++17 conforme a C++/WinRT come usato nel modello di progetto olografico C++. I concetti sono equivalenti per un progetto C++/WinRT, anche se sarà necessario tradurre il codice.
Sistemi di coordinate spaziali in Windows
Il tipo di base usato per ragionare sui sistemi di coordinate reali in Windows è SpatialCoordinateSystem. Un'istanza di questo tipo rappresenta un sistema di coordinate arbitrario, fornendo un metodo per ottenere i dati della matrice di trasformazione che è possibile usare per trasformare tra due sistemi di coordinate senza comprendere i dettagli di ognuno.
I metodi che restituiscono informazioni spaziali accetteranno un parametro SpatialCoordinateSystem per consentire di decidere il sistema di coordinate in cui è più utile per le coordinate da restituire. Le informazioni spaziali sono rappresentate come punti, raggi o volumi nell'ambiente circostante dell'utente e le unità per queste coordinate saranno sempre in metri.
SpatialCoordinateSystem ha una relazione dinamica con altri sistemi di coordinate, inclusi quelli che rappresentano la posizione del dispositivo. In qualsiasi momento, il dispositivo può individuare alcuni sistemi di coordinate e non altri. Per la maggior parte dei sistemi di coordinate, l'app deve essere pronta per gestire i periodi di tempo durante i quali non possono trovarsi.
L'applicazione non deve creare direttamente SpatialCoordinateSystems, ma deve essere usata tramite le API Perception. Esistono tre origini principali dei sistemi di coordinate nelle API Perception, ognuna delle quali è mappata a un concetto descritto nella pagina Sistemi di coordinate :
- Per ottenere un frame fisso di riferimento, creare un oggetto SpatialStationaryFrameOfReference o recuperarne uno dall'oggetto SpatialStageFrameOfReference corrente.
- Per ottenere un ancoraggio spaziale, creare un oggetto SpatialAnchor.
- Per ottenere un frame di riferimento collegato, creare un oggetto SpatialLocatorAttachedFrameOfReference.
Tutti i sistemi di coordinate restituiti da questi oggetti sono destrorse, con +y su, +x a destra e +z indietro. È possibile ricordare quale direzione punta l'asse z positivo puntando le dita della mano sinistra o destra nella direzione x positiva e curlingle nella direzione y positiva. La direzione in cui punta il pollice, verso o lontano da te, è la direzione in cui punta l'asse z positivo per il sistema di coordinate. La figura seguente mostra questi due sistemi di coordinate.
Sistemi di coordinate sinistro e destro
Usare la classe SpatialLocator per creare un frame di riferimento collegato o stazionario da eseguire in un oggetto SpatialCoordinateSystem basato sulla posizione di HoloLens. Continuare con la sezione successiva per altre informazioni su questo processo.
Posizionare gli ologrammi nel mondo usando una fase spaziale
È possibile accedere al sistema di coordinate per Windows Mixed Reality visori VR immersive opachi usando la proprietà static SpatialStageFrameOfReference::Current. Questa API fornisce:
- Un sistema di coordinate
- Informazioni sul fatto che il giocatore sia seduto o mobile
- Il confine di un'area sicura per camminare in giro se il giocatore è mobile
- Indica se il visore VR è direzionale.
- Gestore eventi per gli aggiornamenti alla fase spaziale.
Prima di tutto, si ottiene la fase spaziale e si sottoscrive per gli aggiornamenti:
Codice per l'inizializzazione della fase spaziale
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);
}
Nel metodo OnCurrentChanged, l'app deve esaminare la fase spaziale e aggiornare l'esperienza del giocatore. In questo esempio viene specificata una visualizzazione del limite della fase e della posizione iniziale specificata dall'utente e dall'intervallo di visualizzazione e intervallo di proprietà di spostamento della fase. Torniamo anche al nostro sistema di coordinate stazionarie, quando non è possibile fornire una fase.
Codice per l'aggiornamento della fase spaziale
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);
}
}
}
Il set di vertici che definiscono il limite di fase viene fornito in senso orario. Il Windows Mixed Reality shell disegna una recinzione al limite quando l'utente si avvicina, ma può essere necessario triangolare l'area camminabile per scopi personalizzati. L'algoritmo seguente può essere usato per triangolare la fase.
Codice per la triangolazione della fase spaziale
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;
}
Posizionare gli ologrammi nel mondo usando un frame fisso di riferimento
La classe SpatialStationaryFrameOfReference rappresenta un frame di riferimento che rimane fisso rispetto all'ambiente circostante dell'utente man mano che l'utente si sposta. Questo frame di riferimento assegna priorità a mantenere stabili le coordinate vicino al dispositivo. Un uso chiave di spatialStationaryFrameOfReference consiste nell'agire come sistema di coordinate globale sottostante all'interno di un motore di rendering durante il rendering degli ologrammi.
Per ottenere un oggetto SpatialStationaryFrameOfReference, usare la classe SpatialLocator e chiamare CreateStationaryFrameOfReferenceAtCurrentLocation.
Dal codice modello dell'app Windows Holographic:
// The simplest way to render world-locked holograms is to create a stationary reference frame
// when the app is launched. This is roughly analogous to creating a "world" coordinate system
// with the origin placed at the device's position as the app is launched.
referenceFrame = locator.CreateStationaryFrameOfReferenceAtCurrentLocation();
- I frame di riferimento stazionari sono progettati per fornire una posizione ottimale rispetto allo spazio complessivo. Le singole posizioni all'interno di tale frame di riferimento possono derivare leggermente. Questa situazione è normale perché il dispositivo apprende altre informazioni sull'ambiente.
- Quando è necessaria una posizione precisa dei singoli ologrammi, è necessario usare un oggetto SpatialAnchor per ancorare il singolo ologramma a una posizione nel mondo reale, ad esempio un punto che l'utente indica di essere di particolare interesse. Le posizioni di ancoraggio non si allontanano, ma possono essere corrette; l'ancoraggio userà la posizione corretta a partire dal fotogramma successivo dopo che la correzione è stata eseguita.
Posizionare gli ologrammi nel mondo usando ancoraggi nello spazio
Gli ancoraggi nello spazio sono un ottimo modo per posizionare gli ologrammi in un luogo specifico nel mondo reale, con il sistema che garantisce che l'ancoraggio rimanga sul posto nel tempo. Questo argomento illustra come creare e usare un ancoraggio e come usare i dati di ancoraggio.
È possibile creare un oggetto SpatialAnchor in qualsiasi posizione e orientamento all'interno di SpatialCoordinateSystem scelto. Il dispositivo deve essere in grado di individuare il sistema di coordinate al momento e il sistema non deve aver raggiunto il limite di ancoraggi nello spazio.
Una volta definito, il sistema di coordinate di un oggetto SpatialAnchor regola continuamente per mantenere la posizione e l'orientamento precisi della posizione iniziale. È quindi possibile usare questo oggetto SpatialAnchor per eseguire il rendering degli ologrammi che verranno visualizzati fissi nell'ambiente circostante dell'utente in tale posizione esatta.
Gli effetti delle regolazioni che mantengono l'ancoraggio sul posto vengono ingrandimentati man mano che aumenta la distanza dall'ancoraggio. È consigliabile evitare di eseguire il rendering del contenuto rispetto a un ancoraggio superiore a circa 3 metri dall'origine dell'ancoraggio.
La proprietà CoordinateSystem ottiene un sistema di coordinate che consente di posizionare il contenuto relativo all'ancoraggio, con l'interpolazione applicata quando il dispositivo regola la posizione precisa dell'ancoraggio.
Utilizzare la proprietà RawCoordinateSystem e l'evento RawCoordinateSystemAdjusted corrispondente per gestire manualmente queste modifiche.
Rendere persistenti e condividere ancoraggi nello spazio
È possibile salvare in modo permanente un oggetto SpatialAnchor localmente usando la classe SpatialAnchorStore e quindi recuperarlo nuovamente in una sessione di app futura nello stesso dispositivo HoloLens.
Usando Ancoraggi nello spazio di Azure, è possibile creare un ancoraggio cloud durevole da un elemento SpatialAnchor locale, che l'app può quindi individuare tra più dispositivi HoloLens, iOS e Android. Condividendo un ancoraggio spaziale comune tra più dispositivi, ogni utente può visualizzare il contenuto di cui è stato eseguito il rendering rispetto a tale ancoraggio nella stessa posizione fisica in tempo reale.
È anche possibile usare Ancoraggi nello spazio di Azure per la persistenza asincrona degli ologrammi nei dispositivi HoloLens, iOS e Android. Condividendo un ancoraggio nello spazio cloud durevole, più dispositivi possono osservare lo stesso ologramma persistente nel tempo, anche se tali dispositivi non sono presenti insieme contemporaneamente.
Per iniziare a creare esperienze condivise nell'app HoloLens, provare l'avvio rapido di 5 minuti di Ancoraggi nello spazio di Azure HoloLens.
Una volta eseguiti con Ancoraggi nello spazio di Azure, è possibile creare e individuare ancoraggi in HoloLens. Le procedure dettagliate sono disponibili anche per Android e iOS , consentendo di condividere gli stessi ancoraggi in tutti i dispositivi.
Creare spatialAnchors per il contenuto olografico
Per questo esempio di codice, è stato modificato il modello di app Windows Holographic per creare ancoraggi quando viene rilevato il movimento Premuto . Il cubo viene quindi posizionato all'ancoraggio durante il passaggio di rendering.
Poiché più ancoraggi sono supportati dalla classe helper, è possibile inserire tutti i cubi che si vuole usare questo esempio di codice.
Nota
Gli ID per gli ancoraggi sono elementi che si controllano nell'app. In questo esempio è stato creato uno schema di denominazione sequenziale in base al numero di ancoraggi attualmente archiviati nella raccolta di ancoraggi dell'app.
// 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);
}
}
}
Caricare e memorizzare nella cache in modo asincrono SpatialAnchorStore
Vediamo come scrivere una classe SampleSpatialAnchorHelper che consente di gestire questa persistenza, tra cui:
- Archiviazione di una raccolta di ancoraggi in memoria, indicizzati da una chiave Platform::String.
- Caricamento degli ancoraggi da SpatialAnchorStore del sistema, che viene mantenuto separato dalla raccolta locale in memoria.
- Salvataggio della raccolta locale in memoria di ancoraggi in SpatialAnchorStore quando l'app sceglie di farlo.
Ecco come salvare gli oggetti SpatialAnchor in SpatialAnchorStore.
All'avvio della classe viene richiesto l'oggetto SpatialAnchorStore in modo asincrono. Questo implica l'I/O di sistema quando l'API carica l'archivio di ancoraggio e questa API viene resa asincrona in modo che l'I/O non blocchi.
// 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;
});
Verrà assegnato un oggetto SpatialAnchorStore che è possibile usare per salvare gli ancoraggi. Si tratta di un IMapView che associa i valori chiave che sono String, con i valori di dati che sono SpatialAnchors. Nel codice di esempio viene archiviato in una variabile membro della classe privata accessibile tramite una funzione pubblica della classe helper.
SampleSpatialAnchorHelper::SampleSpatialAnchorHelper(SpatialAnchorStore^ anchorStore)
{
m_anchorStore = anchorStore;
m_anchorMap = ref new Platform::Collections::Map<String^, SpatialAnchor^>();
}
Nota
Non dimenticare di collegare gli eventi di sospensione/ripresa per salvare e caricare l'archivio ancoraggi.
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();
}
Salvare il contenuto nell'archivio di ancoraggio
Quando il sistema sospende l'app, è necessario salvare gli ancoraggi spaziali nell'archivio ancoraggi. È anche possibile scegliere di salvare ancoraggi nello store di ancoraggio in altre occasioni, in quanto è necessario per l'implementazione dell'app.
Quando si è pronti per provare a salvare gli ancoraggi in memoria in SpatialAnchorStore, è possibile eseguire il ciclo attraverso la raccolta e provare a salvare ognuno di essi.
// 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;
}
Caricare il contenuto dall'archivio di ancoraggio quando l'app riprende
È possibile ripristinare gli ancoraggi salvati in AnchorStore trasferendoli dall'IMapView dell'archivio di ancoraggio al database in memoria di SpatialAnchors quando l'app riprende o in qualsiasi momento.
Per ripristinare ancoraggi da SpatialAnchorStore, ripristinare ognuno di essi interessato alla propria raccolta in memoria.
È necessario un database in memoria personalizzato di SpatialAnchors per associare stringhe a SpatialAnchors create. Nel codice di esempio si sceglie di usare windows::Foundation::Collections::IMap per archiviare gli ancoraggi, che semplifica l'uso dello stesso valore di chiave e dati per 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
Un ancoraggio ripristinato potrebbe non essere locabile immediatamente. Ad esempio, potrebbe essere un ancoraggio in una stanza separata o in un edificio diverso. Gli ancoraggi recuperati da AnchorStore devono essere testati per la individuabilità prima di usarli.
Nota
In questo codice di esempio vengono recuperati tutti gli ancoraggi da AnchorStore. Questo non è un requisito; L'app può scegliere e scegliere un determinato subset di ancoraggi usando i valori di chiave Stringa significativi per l'implementazione.
// 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);
}
}
}
Cancellare l'archivio ancoraggi, se necessario
A volte, è necessario cancellare lo stato dell'app e scrivere nuovi dati. Ecco come si esegue questa operazione con SpatialAnchorStore.
Usando la classe helper, è quasi non necessario eseguire il wrapping della funzione Clear. Si sceglie di farlo nell'implementazione di esempio, perché la classe helper viene assegnata alla responsabilità di possedere l'istanza di 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();
}
}
Esempio: Correlazione dei sistemi di coordinate di ancoraggio ai sistemi di coordinate di riferimento stazioni
Si supponga di avere un ancoraggio e si vuole correlare qualcosa nel sistema di coordinate dell'ancoraggio al SpatialStationaryReferenceFrame che si sta già usando per l'altro contenuto. È possibile usare TryGetTransformTo per ottenere una trasformazione dal sistema di coordinate dell'ancoraggio a quello del frame di riferimento stazioni:
// 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;
}
Questo processo è utile per l'utente in due modi:
- Indica se i due fotogrammi di riferimento possono essere compresi tra loro e;
- In tal caso, fornisce una trasformazione da passare direttamente da un sistema di coordinate all'altro.
Con queste informazioni, si ha una comprensione della relazione spaziale tra oggetti tra i due fotogrammi di riferimento.
Per il rendering, è spesso possibile ottenere risultati migliori raggruppando gli oggetti in base al frame di riferimento o all'ancoraggio originali. Eseguire un passaggio di disegno separato per ogni gruppo. Le matrici di visualizzazione sono più accurate per gli oggetti con trasformazioni del modello create inizialmente usando lo stesso sistema di coordinate.
Creare ologrammi usando un frame di riferimento collegato al dispositivo
Quando si vuole eseguire il rendering di un ologramma che rimane collegato alla posizione del dispositivo, ad esempio un pannello con informazioni di debug o un messaggio informativo quando il dispositivo è in grado di determinare l'orientamento e non la posizione nello spazio. A questo scopo, viene usato un frame collegato di riferimento.
La classe SpatialLocatorAttachedFrameOfReference definisce i sistemi di coordinate, che sono relativi al dispositivo anziché al mondo reale. Questo frame ha un'intestazione fissa rispetto all'ambiente dell'utente che punta nella direzione in cui l'utente è stato risolto quando è stato creato il frame di riferimento. Da allora, tutti gli orientamenti in questo frame di riferimento sono relativi a tale intestazione fissa, anche se l'utente ruota il dispositivo.
Per HoloLens, l'origine del sistema di coordinate di questo frame si trova al centro della rotazione della testa dell'utente, in modo che la sua posizione non sia influenzata dalla rotazione della testa. L'app può specificare un offset relativo a questo punto per posizionare gli ologrammi davanti all'utente.
Per ottenere un oggetto SpatialLocatorAttachedFrameOfReference, usare la classe SpatialLocator e chiamare CreateAttachedFrameOfReferenceAtCurrentHeading.
Ciò si applica all'intera gamma di dispositivi Windows Mixed Reality.
Usare un frame di riferimento collegato al dispositivo
Queste sezioni illustrano cosa è stato modificato nel modello di app Windows Holographic per abilitare un frame di riferimento collegato al dispositivo usando questa API. Questo ologramma "collegato" funzionerà insieme a ologrammi stazioni o ancorati e può essere usato anche quando il dispositivo non riesce temporaneamente a trovare la sua posizione nel mondo.
Prima di tutto, è stato modificato il modello per archiviare un oggetto SpatialLocatorAttachedFrameOfReference anziché uno SpatialStationaryFrameOfReference:
Da HolographicTagAlongSampleMain.h:
// A reference frame attached to the holographic camera.
Windows::Perception::Spatial::SpatialLocatorAttachedFrameOfReference^ m_referenceFrame;
Da HolographicTagAlongSampleMain.cpp:
// In this example, we create a reference frame attached to the device.
m_referenceFrame = m_locator->CreateAttachedFrameOfReferenceAtCurrentHeading();
Durante l'aggiornamento si ottiene ora il sistema di coordinate ottenuto dal timestamp ottenuto dalla stima del frame.
// 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);
Ottenere una posa del puntatore spaziale e seguire lo sguardo dell'utente
Si vuole che l'ologramma di esempio segui lo sguardo dell'utente, simile al modo in cui la shell olografica può seguire lo sguardo dell'utente. Per questo motivo, è necessario ottenere l'oggetto SpatialPointerPose dallo stesso timestamp.
SpatialPointerPose^ pose = SpatialPointerPose::TryGetAtTimestamp(currentCoordinateSystem, prediction->Timestamp);
Questo oggetto SpatialPointerPose contiene le informazioni necessarie per posizionare l'ologramma in base all'intestazione corrente dell'utente.
Per il comfort dell'utente, viene usata l'interpolazione lineare ("lerp") per regolare la posizione in un periodo di tempo. Questo è più comodo per l'utente che bloccare l'ologramma allo sguardo. Lerping della posizione dell'ologramma lungo tag consente anche di stabilizzare l'ologramma attenuando il movimento. Se non abbiamo fatto questa attenuazione, l'utente visualizzerebbe il jitter dell'ologramma a causa di ciò che normalmente sono considerati movimenti impercettibili della testa dell'utente.
Da 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
Nel caso di un pannello di debug, è possibile scegliere di riposizionare l'ologramma sul lato un po' in modo da non impedire la visualizzazione. Ecco un esempio di come si potrebbe eseguire questa operazione.
Per 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));
*/
Ruotare l'ologramma per affrontare la fotocamera
Non è sufficiente posizionare l'ologramma, che in questo caso è un quad; è necessario ruotare anche l'oggetto per affrontare l'utente. Questa rotazione si verifica nello spazio mondiale, perché questo tipo di cartellone consente all'ologramma di rimanere parte dell'ambiente dell'utente. Il tabellone dello spazio di visualizzazione non è comodo perché l'ologramma diventa bloccato per l'orientamento dello schermo; In questo caso, è anche necessario interpolare tra le matrici di visualizzazione sinistra e destra per acquisire una trasformazione del cartellone dello spazio di visualizzazione che non interrompe il rendering stereo. In questo caso, si ruotano sugli assi X e Z per affrontare l'utente.
Da 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));
Eseguire il rendering dell'ologramma collegato
Per questo esempio, si sceglie anche di eseguire il rendering dell'ologramma nel sistema di coordinate di SpatialLocatorAttachedReferenceFrame, che è il punto in cui è stato posizionato l'ologramma. Se si è deciso di eseguire il rendering usando un altro sistema di coordinate, è necessario acquisire una trasformazione dal sistema di coordinate collegato al dispositivo a tale sistema di coordinate.
Da 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)
);
L'operazione è terminata. L'ologramma ora "inseguirà" una posizione che è 2 metri davanti alla direzione dello sguardo dell'utente.
Nota
Questo esempio carica anche contenuto aggiuntivo: vedere StationaryQuadRenderer.cpp.
Gestione della perdita di rilevamento
Quando il dispositivo non riesce a individuarsi nel mondo, l'app sperimenta "rilevamento della perdita". Windows Mixed Reality le app devono essere in grado di gestire tali interruzioni al sistema di rilevamento posizione. Queste interruzioni possono essere osservate e le risposte create usando l'evento LocatabilityChanged nel valore predefinito di SpatialLocator.
Da 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 l'app riceve un evento LocatabilityChanged, può modificare il comportamento in base alle esigenze. Ad esempio, nello stato PositionalTrackingInhibited l'app può sospendere l'operazione normale e eseguire il rendering di un ologramma lungo tag che visualizza un messaggio di avviso.
Il modello di app Windows Holographic include un gestore LocatabilityChanged già creato. Per impostazione predefinita, viene visualizzato un avviso nella console di debug quando il rilevamento posizionale non è disponibile. È possibile aggiungere codice a questo gestore per fornire una risposta in base alle esigenze dell'app.
Da 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;
}
}
Mapping spaziale
Le API di mapping spaziale usano sistemi di coordinate per ottenere trasformazioni del modello per le mesh di superficie.