Spatial Anchor API Overview
This section provides an overview and API references for the the Magic Leap 2 Spatial Anchor Subsystem OpenXR Feature.
Magic Leap 2 allows developers to place Spatial Anchors that can be used as reference points inside Applications. These anchors are can be created using AR Foundation's ARAnchor
and AnchorManager
. This section elaborates on creating and destroying these anchors, providing a foundation for managing anchored content effectively. If you are operating the headset inside a recognized space, you can also choose to publish the Anchors so they persist across multiple sessions. For more information on how to save ARAnchors to a specific location and have their data persist across multiple sessions, see Magic Leap 2's Spatial Anchor Storage API.
This feature requires the SPATIAL_ANCHORS
permission to be enabled in your project's Manifest Settings. (Edit > Project Settings > Magic Leap > Manifest Settings)
Prerequisites
Before using spatial anchors, ensure your project has the necessary permissions and subsystems activated:
- Spatial Anchors Permission: Activate
SPATIAL_ANCHORS
in Edit > Project Settings > Magic Leap > Manifest Settings. - OpenXR Feature: Enable the Magic Leap 2 Spatial Anchor Subsystem in Window > XR Plugin Manager > OpenXR Settings.
- AR Anchor Manager: For Spatial Anchor to be tracked properly, add the
ARAnchorManager
component to theXROrigin
in your scene.
NameSpaces
The following namespaces are
// To access AR Anchor Functions through ARFoundation
using UnityEngine.XR.ARFoundation;
// Provides access to the data that is passed into AR Foundation such as Tracking State
using UnityEngine.XR.ARSubsystems;
// Contains classes that allow developers references the active subsystem
using UnityEngine.XR.Management;
// Contains Magic Leap Specific functionality
using MagicLeap.OpenXR.Subsystems;
Creating Anchors
To add a new spatial anchor to your scene, simply add the ARAnchor
component to any GameObject
using AddComponent<ARAnchor>()
. This can be done programmatically to either existing game objects or to new instances of prefabs once they are positioned inside your scene.
void AddAnchor(Vector3 position, GameObject content)
{
// Position the object at the anchor location
content.transform.position = position;
// Add an anchor to your content
content.AddComponent<ARAnchor>();
}
void CreateAnchor(Vector3 position, GameObject prefab)
{
// Create an instance of the prefab
var instance = Instantiate(prefab, position, Quaternion.identity);
// Add an ARAnchor component if it doesn't have one already.
if (instance.GetComponent<ARAnchor>() == null)
{
instance.AddComponent<ARAnchor>();
}
}
Newly created anchors may initially be in a "pending" state and not immediately tracked. Use the pending property to check their status.
public ARAnchorManager anchorManager;
public void PublishActiveAnchors()
{
foreach (var localAnchor in anchorManager.trackables)
{
if (localAnchor.pending == false && localAnchor.trackingState == TrackingState.Tracking)
{
// The anchor is now actively tracked
}
}
}
Deleting Anchors
To remove an anchor, simply remove the ARAnchor
component from the GameObject
it's attached to. This action unanchors the content from the physical world, allowing its position and orientation to be changed freely.
void RemoveAnchorFromGameObject(GameObject anchoredGameObject)
{
ARAnchor anchor = anchoredGameObject.GetComponent<ARAnchor>();
if (anchor != null)
{
Destroy(anchor);
}
}
Monitoring Anchor Updates
To monitor updates to anchors, poll the ARAnchor's trackingState
to determine its current status.
Example method for anchor state monitoring:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
public class AnchorUpdateManager : MonoBehaviour
{
private List<ARAnchor> activeAnchors = new List<ARAnchor>();
void Update()
{
foreach (var anchor in activeAnchors)
{
switch (anchor.trackingState)
{
case TrackingState.Tracking:
// Handle tracked anchor
break;
case TrackingState.Limited:
// Handle limited tracking
break;
case TrackingState.None:
// Handle lost tracking
break;
}
}
}
}
Magic Leap Anchor Properties
This section demonstrates how to obtain Magic Leap specific properties from the anchors created by AR Foundations such as their Magic Leap ID, the Anchor Pose and the Anchor Confidence.
ML XR Anchor Subsystem
The Magic Leap 2 uses the MLXrAnchorSubsystem
to manage anchors inside Unity. This Subsystem extends the standard XRAnchorSubsystem
and adds Magic Leap Specific APIs such as anchor confidence. Additionally, anchors created with the MLXrAnchorSubsystem
contain a platform specific Anchor ID. So they can be identified for platform specific functions.
The example code below returns true if the Magic Leap XR Anchor Subsystem is loaded.
using MagicLeap.OpenXR.Subsystems;
private bool AreSubsystemsLoaded(out MLXrAnchorSubsystem activeSubsystem)
{
activeSubsystem = null;
if (XRGeneralSettings.Instance == null) return false;
if (XRGeneralSettings.Instance.Manager == null) return false;
var activeLoader = XRGeneralSettings.Instance.Manager.activeLoader;
if (activeLoader == null) return false;
activeSubsystem = activeLoader.GetLoadedSubsystem<XRAnchorSubsystem>() as MLXrAnchorSubsystem;
return activeSubsystem != null;
}
This subsystem can then be used to query Magic Leap specific properties such as anchor confidence
void Update()
{
if (AreSubsystemsLoaded(out var activeSubsystem))
{
foreach (ARAnchor anchor in anchorManager.trackables)
{
if (anchor.trackingState == TrackingState.Tracking && activeSubsystem != null)
{
ulong mlAnchorID = activeSubsystem.GetAnchorId(anchor);
//Get the anchor pose using the Anchor ID, for demonstrative purposes only
Pose anchorPose = activeSubsystem.GetAnchorPoseFromID(mlAnchorID);
//Get The anchor confidence
MLXrAnchorSubsystem.AnchorConfidence anchorConfidence = activeSubsystem.GetAnchorConfidence(anchor);
Debug.Log($"ML Anchor : {mlAnchorID} , Pose: {anchorPose} , Confidence: {anchorConfidence}");
}
}
}
}
Example
This example iterates through all of the anchors tracked by the anchor manager and logs their ML Anchor ID, Pose and Confidence. To use the script add it to a GameObject and assign the Anchor Manager property.
using System.Collections;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR.Features.MagicLeapSupport;
public class SpatialAnchorsTest : MonoBehaviour
{
[SerializeField]
private ARAnchorManager anchorManager;
private MLXrAnchorSubsystem activeSubsystem;
private bool isSubsystemLoaded;
private IEnumerator Start()
{
yield return new WaitUntil(AreSubsystemsLoaded);
isSubsystemLoaded = true;
}
void Update()
{
if (isSubsystemLoaded)
{
foreach (ARAnchor anchor in anchorManager.trackables)
{
if (anchor.trackingState == TrackingState.Tracking && activeSubsystem != null)
{
ulong mlAnchorID = activeSubsystem.GetAnchorId(anchor);
//Get the anchor pose using the Anchor ID, for demonstrative purposes only
Pose anchorPose = activeSubsystem.GetAnchorPoseFromID(mlAnchorID);
//Get The anchor confidence
MLXrAnchorSubsystem.AnchorConfidence anchorConfidence = activeSubsystem.GetAnchorConfidence(anchor);
Debug.Log($"ML Anchor : {mlAnchorID} , Pose: {anchorPose} , Confidence: {anchorConfidence}");
}
}
}
}
private bool AreSubsystemsLoaded()
{
if (XRGeneralSettings.Instance == null) return false;
if (XRGeneralSettings.Instance.Manager == null) return false;
var activeLoader = XRGeneralSettings.Instance.Manager.activeLoader;
if (activeLoader == null) return false;
activeSubsystem = activeLoader.GetLoadedSubsystem<XRAnchorSubsystem>() as MLXrAnchorSubsystem;
return activeSubsystem != null;
}
}