Systèmes de coordonnées dans DirectX
Notes
Cet article concerne les API winRT natives héritées. Pour les nouveaux projets d’application native, nous vous recommandons d’utiliser l’API OpenXR.
Les systèmes de coordonnées constituent la base de la compréhension spatiale offerte par Windows Mixed Reality API.
Aujourd’hui, les appareils VR assis ou à pièce unique établissent un système de coordonnées principal pour leur espace suivi. Mixed Reality appareils comme HoloLens sont conçus pour de grands environnements non définis, l’appareil découvrant et apprenant son environnement à mesure que l’utilisateur se déplace. L’appareil s’adapte pour améliorer continuellement les connaissances sur les salles de l’utilisateur, mais aboutit à des systèmes de coordonnées qui changent leur relation les uns avec les autres au cours de la durée de vie des applications. Windows Mixed Reality prend en charge un large éventail d’appareils, allant des casques immersifs assis aux cadres de référence attachés au monde entier.
Notes
Les extraits de code de cet article illustrent actuellement l’utilisation de C++/CX plutôt que C++/WinRT conforme À C++/WinRT, tel qu’il est utilisé dans le modèle de projet holographique C++. Les concepts sont équivalents pour un projet C++/WinRT, mais vous devez traduire le code.
Systèmes de coordonnées spatiales dans Windows
Le type principal utilisé pour raisonner les systèmes de coordonnées réels dans Windows est SpatialCoordinateSystem. Une instance de ce type représente un système de coordonnées arbitraire, fournissant une méthode pour obtenir des données de matrice de transformation que vous pouvez utiliser pour transformer entre deux systèmes de coordonnées sans comprendre les détails de chacun.
Les méthodes qui retournent des informations spatiales acceptent un paramètre SpatialCoordinateSystem pour vous permettre de déterminer le système de coordonnées dans lequel il est le plus utile pour ces coordonnées à retourner. Les informations spatiales sont représentées sous forme de points, de rayons ou de volumes dans l’environnement de l’utilisateur, et les unités de ces coordonnées sont toujours exprimées en mètres.
Un SpatialCoordinateSystem a une relation dynamique avec d’autres systèmes de coordonnées, y compris ceux qui représentent la position de l’appareil. À tout moment, l’appareil peut localiser certains systèmes de coordonnées et non d’autres. Pour la plupart des systèmes de coordonnées, votre application doit être prête à gérer des périodes pendant lesquelles ils ne peuvent pas être localisés.
Votre application ne doit pas créer directement SpatialCoordinateSystems, mais plutôt les utiliser via les API Perception. Il existe trois sources principales de systèmes de coordonnées dans les API Perception, chacune correspondant à un concept décrit dans la page Systèmes de coordonnées :
- Pour obtenir une trame de référence stationnaire, créez un SpatialStationaryFrameOfReference ou obtenez-en un à partir du SpatialStageFrameOfReference actuel.
- Pour obtenir une ancre spatiale, créez un SpatialAnchor.
- Pour obtenir un cadre de référence attaché, créez un SpatialLocatorAttachedFrameOfReference.
Tous les systèmes de coordonnées retournés par ces objets sont droitiers, avec +y vers le haut, +x vers la droite et +z vers l’arrière. Vous pouvez vous souvenir de la direction dans laquelle l’axe z positif pointe en pointant les doigts de votre main gauche ou droite dans la direction positive x et en les frisant dans la direction y positive. La direction que pointe votre pouce, vers ou loin de vous, est la direction que l’axe Z positif pointe pour ce système de coordonnées. L’illustration suivante montre ces deux systèmes de coordonnées.
Systèmes de coordonnées de gauche et de droite
Utilisez la classe SpatialLocator pour créer une trame de référence attachée ou stationnaire pour l’amorçage dans un SpatialCoordinateSystem basé sur la position HoloLens. Passez à la section suivante pour en savoir plus sur ce processus.
Placer les hologrammes dans le monde à l’aide d’une scène spatiale
Le système de coordonnées des casques opaques Windows Mixed Reality immersifs est accessible à l’aide de la propriété statique SpatialStageFrameOfReference::Current. Cette API fournit :
- Un système de coordonnées
- Informations indiquant si le joueur est assis ou mobile
- Limite d’une zone sécurisée pour se promener si le joueur est mobile
- Indique si le casque est directionnel.
- Gestionnaire d’événements pour les mises à jour de la phase spatiale.
Tout d’abord, nous obtenons l’étape spatiale et nous nous abonneons aux mises à jour :
Code pour l’initialisation de la phase spatiale
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);
}
Dans la méthode OnCurrentChanged, votre application doit inspecter la phase spatiale et mettre à jour l’expérience du joueur. Dans cet exemple, nous fournissons une visualisation de la limite d’étape et de la position de début spécifiée par l’utilisateur, ainsi que la plage de vues et la plage de propriétés de mouvement de la phase. Nous revenons également à notre propre système de coordonnées stationnaire, lorsqu’une étape ne peut pas être fournie.
Code pour la mise à jour de la phase spatiale
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);
}
}
}
L’ensemble des sommets qui définissent la limite d’étape est fourni dans le sens des aiguilles d’une montre. L’interpréteur de commandes Windows Mixed Reality dessine une clôture à la limite lorsque l’utilisateur l’approche, mais vous souhaiterez peut-être triangulairer la zone accessible à pied pour vos propres besoins. L’algorithme suivant peut être utilisé pour triangulairer la phase.
Code pour la triangulaire des phases spatiales
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;
}
Placer les hologrammes dans le monde à l’aide d’un cadre de référence stationnaire
La classe SpatialStationaryFrameOfReference représente une trame de référence qui reste stationnaire par rapport à l’environnement de l’utilisateur à mesure que l’utilisateur se déplace. Ce cadre de référence privilégie la stabilité des coordonnées près de l’appareil. Une utilisation clé d’un SpatialStationaryFrameOfReference consiste à agir en tant que système de coordonnées du monde sous-jacent dans un moteur de rendu lors du rendu des hologrammes.
Pour obtenir un SpatialStationaryFrameOfReference, utilisez la classe SpatialLocator et appelez CreateStationaryFrameOfReferenceAtCurrentLocation.
À partir du code du modèle d’application Holographique 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();
- Les cadres de référence stationnaires sont conçus pour fournir une position optimale par rapport à l’espace global. Les positions individuelles dans ce cadre de référence sont autorisées à dériver légèrement. C’est normal, car l’appareil en apprend davantage sur l’environnement.
- Lorsque le placement précis d’hologrammes individuels est requis, un SpatialAnchor doit être utilisé pour ancrer l’hologramme individuel à une position dans le monde réel, par exemple, un point que l’utilisateur indique être d’un intérêt particulier. Les positions d’ancrage ne dérivent pas, mais peuvent être corrigées; l’ancre utilisera la position corrigée en commençant dans l’image suivante une fois la correction effectuée.
Placer des hologrammes dans le monde à l’aide d’ancres spatiales
Les ancres spatiales sont un excellent moyen de placer des hologrammes à un endroit spécifique dans le monde réel, le système garantissant que l’ancre reste en place au fil du temps. Cette rubrique explique comment créer et utiliser une ancre, et comment utiliser des données d’ancre.
Vous pouvez créer un SpatialAnchor à n’importe quelle position et orientation dans le SpatialCoordinateSystem de votre choix. L’appareil doit être en mesure de localiser ce système de coordonnées pour le moment, et le système ne doit pas avoir atteint sa limite d’ancres spatiales.
Une fois défini, le système de coordonnées d’un SpatialAnchor s’ajuste continuellement pour conserver la position et l’orientation précises de son emplacement initial. Vous pouvez ensuite utiliser ce SpatialAnchor pour afficher les hologrammes qui apparaîtront fixes dans l’environnement de l’utilisateur à cet emplacement exact.
Les effets des ajustements qui maintiennent l’ancre en place sont amplifiés à mesure que la distance de l’ancre augmente. Vous devez éviter de rendre le contenu relatif à une ancre qui se trouve à plus de 3 mètres de l’origine de cette ancre.
La propriété CoordinateSystem obtient un système de coordonnées qui vous permet de placer le contenu par rapport à l’ancre, avec l’accélération appliquée lorsque l’appareil ajuste l’emplacement précis de l’ancre.
Utilisez la propriété RawCoordinateSystem et l’événement RawCoordinateSystemAdjusted correspondant pour gérer ces ajustements vous-même.
Conserver et partager des ancres spatiales
Vous pouvez conserver un SpatialAnchor localement à l’aide de la classe SpatialAnchorStore , puis le récupérer dans une session d’application ultérieure sur le même appareil HoloLens.
À l’aide d’Azure Spatial Anchors, vous pouvez créer une ancre cloud durable à partir d’un SpatialAnchor local, que votre application peut ensuite localiser sur plusieurs appareils HoloLens, iOS et Android. En partageant une ancre spatiale commune sur plusieurs appareils, chaque utilisateur peut voir le contenu rendu par rapport à cette ancre dans le même emplacement physique en temps réel.
Vous pouvez également utiliser Azure Spatial Anchors pour la persistance asynchrone des hologrammes sur les appareils HoloLens, iOS et Android. En partageant une ancre spatiale cloud durable, plusieurs appareils peuvent observer le même hologramme persistant au fil du temps, même si ces appareils ne sont pas présents ensemble en même temps.
Pour commencer à créer des expériences partagées dans votre application HoloLens, essayez le démarrage rapide Azure Spatial Anchors HoloLens de 5 minutes.
Une fois que vous êtes opérationnel avec Azure Spatial Anchors, vous pouvez créer et localiser des ancres sur HoloLens. Des procédures pas à pas sont également disponibles pour Android et iOS , ce qui vous permet de partager les mêmes ancres sur tous les appareils.
Créer SpatialAnchors pour le contenu holographique
Pour cet exemple de code, nous avons modifié le modèle d’application Holographique Windows pour créer des ancres lorsque le mouvement Appuyé est détecté. Le cube est ensuite placé à l’ancre pendant la passe de rendu.
Étant donné que plusieurs ancres sont prises en charge par la classe d’assistance, nous pouvons placer autant de cubes que nous le souhaitons pour utiliser cet exemple de code !
Notes
Les ID des ancres sont quelque chose que vous contrôlez dans votre application. Dans cet exemple, nous avons créé un schéma d’affectation de noms séquentiel en fonction du nombre d’ancres actuellement stockées dans la collection d’ancres de l’application.
// 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);
}
}
}
Charger et mettre en cache de manière asynchrone le SpatialAnchorStore
Voyons comment écrire une classe SampleSpatialAnchorHelper qui permet de gérer cette persistance, notamment :
- Stockage d’une collection d’ancres en mémoire, indexées par une clé Platform::String.
- Chargement des ancres à partir du SpatialAnchorStore du système, qui est conservé séparément de la collection locale en mémoire.
- Enregistrement de la collection locale en mémoire d’ancres dans le SpatialAnchorStore lorsque l’application choisit de le faire.
Voici comment enregistrer des objets SpatialAnchor dans spatialAnchorStore.
Lorsque la classe démarre, nous demandons le SpatialAnchorStore de manière asynchrone. Cela implique des E/S système lorsque l’API charge le magasin d’ancres, et cette API est rendue asynchrone de sorte que les E/S ne bloquent pas.
// 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;
});
Vous recevrez un SpatialAnchorStore que vous pouvez utiliser pour enregistrer les ancres. Il s’agit d’un IMapView qui associe des valeurs clés qui sont des chaînes à des valeurs de données qui sont SpatialAnchors. Dans notre exemple de code, nous stockons cela dans une variable membre de classe privée accessible via une fonction publique de notre classe d’assistance.
SampleSpatialAnchorHelper::SampleSpatialAnchorHelper(SpatialAnchorStore^ anchorStore)
{
m_anchorStore = anchorStore;
m_anchorMap = ref new Platform::Collections::Map<String^, SpatialAnchor^>();
}
Notes
N’oubliez pas de raccorder les événements de suspension/reprise pour enregistrer et charger le magasin d’ancres.
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();
}
Enregistrer du contenu dans le magasin d’ancres
Lorsque le système suspend votre application, vous devez enregistrer vos ancres spatiales dans le magasin d’ancres. Vous pouvez également choisir d’enregistrer les ancres dans le magasin d’ancres à d’autres moments, car vous le trouvez nécessaire pour l’implémentation de votre application.
Lorsque vous êtes prêt à essayer d’enregistrer les ancres en mémoire dans SpatialAnchorStore, vous pouvez effectuer une boucle dans votre collection et essayer d’enregistrer chacune d’elles.
// 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;
}
Charger du contenu à partir du magasin d’ancres lorsque l’application reprend
Vous pouvez restaurer les ancres enregistrées dans AnchorStore en les transférant du IMapView du magasin d’ancres vers votre propre base de données en mémoire de SpatialAnchors lorsque votre application reprendra ou à tout moment.
Pour restaurer des ancres à partir du SpatialAnchorStore, restaurez chacune d’elles qui vous intéressent dans votre propre collection en mémoire.
Vous avez besoin de votre propre base de données en mémoire de SpatialAnchors pour associer strings aux SpatialAnchors que vous créez. Dans notre exemple de code, nous choisissons d’utiliser un windows::Foundation::Collections::IMap pour stocker les ancres, ce qui facilite l’utilisation de la même clé et de la même valeur de données pour 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;
Notes
Une ancre restaurée peut ne pas être immédiatement locatable. Par exemple, il peut s’agir d’une ancre dans une pièce distincte ou dans un autre bâtiment. Les ancres récupérées à partir du AnchorStore doivent être testées pour leur locatabilité avant de les utiliser.
Notes
Dans cet exemple de code, nous récupérons toutes les ancres du AnchorStore. Ce n’est pas une exigence; Votre application peut tout aussi bien choisir un certain sous-ensemble d’ancres à l’aide de valeurs de clé de chaîne qui sont significatives pour votre implémentation.
// 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);
}
}
}
Effacer le magasin d’ancres, si nécessaire
Parfois, vous devez effacer l’état de l’application et écrire de nouvelles données. Voici comment procéder avec spatialAnchorStore.
À l’aide de notre classe d’assistance, il est presque inutile d’encapsuler la fonction Clear. Nous choisissons de le faire dans notre exemple d’implémentation, car notre classe d’assistance est chargée de posséder l’instance 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();
}
}
Exemple : Association de systèmes de coordonnées d’ancre à des systèmes de coordonnées de trame de référence stationnaires
Supposons que vous disposez d’une ancre et que vous souhaitez lier quelque chose dans le système de coordonnées de votre ancre à l’élément SpatialStationaryReferenceFrame que vous utilisez déjà pour votre autre contenu. Vous pouvez utiliser TryGetTransformTo pour obtenir une transformation du système de coordonnées de l’ancre en celui du cadre de référence stationnaire :
// 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;
}
Ce processus vous est utile de deux manières :
- Il vous indique si les deux cadres de référence peuvent être compris l’un par rapport à l’autre, et ;
- Si c’est le cas, il vous fournit une transformation pour passer directement d’un système de coordonnées à l’autre.
Avec ces informations, vous avez une compréhension de la relation spatiale entre les objets entre les deux cadres de référence.
Pour le rendu, vous pouvez souvent obtenir de meilleurs résultats en regroupant les objets en fonction de leur cadre de référence ou de leur ancre d’origine. Effectuez une passe de dessin distincte pour chaque groupe. Les matrices de vue sont plus précises pour les objets avec des transformations de modèle créées initialement à l’aide du même système de coordonnées.
Créer des hologrammes à l’aide d’un cadre de référence attaché à un appareil
Il arrive que vous souhaitiez afficher un hologramme qui reste attaché à l’emplacement de l’appareil, par exemple un panneau avec des informations de débogage ou un message d’information lorsque l’appareil peut uniquement déterminer son orientation et non sa position dans l’espace. Pour ce faire, nous utilisons un cadre de référence attaché.
La classe SpatialLocatorAttachedFrameOfReference définit des systèmes de coordonnées, qui sont relatifs à l’appareil plutôt qu’au monde réel. Ce cadre a un en-tête fixe relatif à l’environnement de l’utilisateur qui pointe dans la direction vers laquelle l’utilisateur faisait face lors de la création du cadre de référence. À partir de là, toutes les orientations de ce cadre de référence sont relatives à ce titre fixe, même lorsque l’utilisateur fait pivoter l’appareil.
Pour HoloLens, l’origine du système de coordonnées de ce frame se trouve au centre de la rotation de la tête de l’utilisateur, de sorte que sa position n’est pas affectée par la rotation de la tête. Votre application peut spécifier un décalage par rapport à ce point pour positionner les hologrammes devant l’utilisateur.
Pour obtenir un SpatialLocatorAttachedFrameOfReference, utilisez la classe SpatialLocator et appelez CreateAttachedFrameOfReferenceAtCurrentHeading.
Cela s’applique à l’ensemble des appareils Windows Mixed Reality.
Utiliser un cadre de référence attaché à l’appareil
Ces sections décrivent ce que nous avons modifié dans le modèle d’application Holographique Windows pour activer un cadre de référence attaché à l’appareil à l’aide de cette API. Cet hologramme « attaché » fonctionne avec les hologrammes stationnaires ou ancrés, et peut également être utilisé lorsque l’appareil est temporairement incapable de trouver sa position dans le monde.
Tout d’abord, nous avons modifié le modèle pour stocker un SpatialLocatorAttachedFrameOfReference au lieu d’un SpatialStationaryFrameOfReference :
À partir de HolographicTagAlongSampleMain.h :
// A reference frame attached to the holographic camera.
Windows::Perception::Spatial::SpatialLocatorAttachedFrameOfReference^ m_referenceFrame;
À partir de HolographicTagAlongSampleMain.cpp :
// In this example, we create a reference frame attached to the device.
m_referenceFrame = m_locator->CreateAttachedFrameOfReferenceAtCurrentHeading();
Pendant la mise à jour, nous obtenons maintenant le système de coordonnées à l’horodatage obtenu à partir de avec la prédiction de trame.
// 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);
Obtenir une pose de pointeur spatial et suivre le regard de l’utilisateur
Nous voulons que notre exemple d’hologramme suive le regard de l’utilisateur, de la même façon que l’interpréteur de commandes holographique peut suivre le regard de l’utilisateur. Pour cela, nous devons obtenir spatialPointerPose à partir du même horodatage.
SpatialPointerPose^ pose = SpatialPointerPose::TryGetAtTimestamp(currentCoordinateSystem, prediction->Timestamp);
Ce SpatialPointerPose contient les informations nécessaires pour positionner l’hologramme en fonction de l’en-tête actuel de l’utilisateur.
Pour le confort de l’utilisateur, nous utilisons l’interpolation linéaire (« lerp ») pour lisser le changement de position sur une période donnée. Cela est plus confortable pour l’utilisateur que de verrouiller l’hologramme sur son regard. Le lerping de la position de l’hologramme de balise nous permet également de stabiliser l’hologramme en amortissant le mouvement. Si nous n’avons pas fait cette atténuation, l’utilisateur verrait l’hologramme gigue en raison de ce qui est normalement considéré comme des mouvements imperceptibles de la tête de l’utilisateur.
À partir 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);
}
Notes
Dans le cas d’un panneau de débogage, vous pouvez choisir de repositionner un peu l’hologramme sur le côté afin qu’il n’obstrue pas votre vue. Voici un exemple de la façon dont vous pouvez procéder.
Pour 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));
*/
Faire pivoter l’hologramme pour faire face à la caméra
Il ne suffit pas de positionner l’hologramme, qui dans ce cas est un quad ; nous devons également faire pivoter l’objet pour faire face à l’utilisateur. Cette rotation se produit dans l’espace mondial, car ce type de panneaux d’affichage permet à l’hologramme de rester une partie de l’environnement de l’utilisateur. Le panneau d’affichage de l’espace d’affichage n’est pas aussi confortable, car l’hologramme devient verrouillé à l’orientation de l’affichage ; dans ce cas, vous devrez également interpoler entre les matrices d’affichage gauche et droite pour acquérir une transformation de panneau d’affichage d’espace d’affichage qui ne perturbe pas le rendu stéréo. Ici, nous pivotons sur les axes X et Z pour faire face à l’utilisateur.
À partir de StationaryQuadRenderer::Update :
// Seconds elapsed since previous frame.
const float& dTime = static_cast<float>(timer.GetElapsedSeconds());
// Create a direction normal from the hologram's position to the origin of person space.
// This is the z-axis rotation.
XMVECTOR facingNormal = XMVector3Normalize(-XMLoadFloat3(&m_position));
// Rotate the x-axis around the y-axis.
// This is a 90-degree angle from the normal, in the xz-plane.
// This is the x-axis rotation.
XMVECTOR xAxisRotation = XMVector3Normalize(XMVectorSet(XMVectorGetZ(facingNormal), 0.f, -XMVectorGetX(facingNormal), 0.f));
// Create a third normal to satisfy the conditions of a rotation matrix.
// The cross product of the other two normals is at a 90-degree angle to
// both normals. (Normalize the cross product to avoid floating-point math
// errors.)
// Note how the cross product will never be a zero-matrix because the two normals
// are always at a 90-degree angle from one another.
XMVECTOR yAxisRotation = XMVector3Normalize(XMVector3Cross(facingNormal, xAxisRotation));
// Construct the 4x4 rotation matrix.
// Rotate the quad to face the user.
XMMATRIX rotationMatrix = XMMATRIX(
xAxisRotation,
yAxisRotation,
facingNormal,
XMVectorSet(0.f, 0.f, 0.f, 1.f)
);
// Position the quad.
const XMMATRIX modelTranslation = XMMatrixTranslationFromVector(XMLoadFloat3(&m_position));
// The view and projection matrices are provided by the system; they are associated
// with holographic cameras, and updated on a per-camera basis.
// Here, we provide the model transform for the sample hologram. The model transform
// matrix is transposed to prepare it for the shader.
XMStoreFloat4x4(&m_modelConstantBufferData.model, XMMatrixTranspose(rotationMatrix * modelTranslation));
Afficher l’hologramme attaché
Pour cet exemple, nous choisissons également d’afficher l’hologramme dans le système de coordonnées de SpatialLocatorAttachedReferenceFrame, où nous avons positionné l’hologramme. (Si nous avions décidé d’effectuer le rendu à l’aide d’un autre système de coordonnées, nous devions acquérir une transformation du système de coordonnées du cadre de référence attaché au périphérique vers ce système de coordonnées.)
À partir de HolographicTagAlongSampleMain::Render :
// The view and projection matrices for each holographic camera will change
// every frame. This function refreshes the data in the constant buffer for
// the holographic camera indicated by cameraPose.
pCameraResources->UpdateViewProjectionBuffer(
m_deviceResources,
cameraPose,
m_referenceFrame->GetStationaryCoordinateSystemAtTimestamp(prediction->Timestamp)
);
Et voilà ! L’hologramme va maintenant « chasser » une position de 2 mètres devant la direction du regard de l’utilisateur.
Notes
Cet exemple charge également du contenu supplémentaire ( consultez StationaryQuadRenderer.cpp).
Gestion des pertes de suivi
Lorsque l’appareil ne peut pas se localiser dans le monde, l’application subit une « perte de suivi ». Windows Mixed Reality applications doivent être en mesure de gérer ces interruptions du système de suivi positionnel. Ces interruptions peuvent être observées et des réponses créées à l’aide de l’événement LocatabilityChanged sur le SpatialLocator par défaut.
À partir de AppMain::SetHolographicSpace :
// Be able to respond to changes in the positional tracking state.
m_locatabilityChangedToken =
m_locator->LocatabilityChanged +=
ref new Windows::Foundation::TypedEventHandler<SpatialLocator^, Object^>(
std::bind(&HolographicApp1Main::OnLocatabilityChanged, this, _1, _2)
);
Lorsque votre application reçoit un événement LocatabilityChanged, elle peut changer de comportement en fonction des besoins. Par exemple, dans l’état PositionalTrackingInhibited, votre application peut suspendre le fonctionnement normal et afficher un hologramme de balise qui affiche un message d’avertissement.
Le modèle d’application Windows Holographique est fourni avec un gestionnaire LocatabilityChanged déjà créé pour vous. Par défaut, il affiche un avertissement dans la console de débogage lorsque le suivi positionnel n’est pas disponible. Vous pouvez ajouter du code à ce gestionnaire pour fournir une réponse en fonction des besoins de votre application.
À partir d’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;
}
}
Mappage spatial
Les API de mappage spatial utilisent des systèmes de coordonnées pour obtenir des transformations de modèle pour les maillages de surface.