Skip to main content
Version: 20 Mar 2024

Marker Understanding Example

This section includes an example of detecting Fiducial Markers on the Magic Leap 2 headset.

Simple Example

This script detects markers and creates a cube at each of the target's location. The script does not handle runtime changes to the Marker Tracker settings.

caution

This feature requires the MARKER_TRACKING permission to be enabled in your project's Manifest Settings. (Edit > Project Settings > Magic Leap > Manifest Settings)

using System.Collections.Generic;
using Unity.XR.CoreUtils;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features.MagicLeapSupport;

public class MarkerTrackerExample : MonoBehaviour
{
[Tooltip("Set the XR Origin so that the marker appears relative to headset's origin. If null, the script will try to find the component automatically.")]
public XROrigin XROrigin;

[Tooltip("If Not Null, this is the object that will be created at the position of each detected marker.")]
public GameObject MarkerPrefab;

public MagicLeapMarkerUnderstandingFeature.ArucoType ArucoType =
MagicLeapMarkerUnderstandingFeature.ArucoType.Dictionary_5x5_50;

public MagicLeapMarkerUnderstandingFeature.MarkerDetectorProfile DetectorProfile =
MagicLeapMarkerUnderstandingFeature.MarkerDetectorProfile.Default;

private MagicLeapMarkerUnderstandingFeature.MarkerDetectorSettings _detectorSettings;
private MagicLeapMarkerUnderstandingFeature _markerFeature;
private readonly Dictionary<string, GameObject> _markerObjectById = new Dictionary<string, GameObject>();

private void OnValidate()
{
// Automatically find the XROrigin component if it's present in the scene
if (XROrigin == null)
{
XROrigin = FindAnyObjectByType<XROrigin>();
}
}

private void Start()
{
_markerFeature = OpenXRSettings.Instance.GetFeature<MagicLeapMarkerUnderstandingFeature>();

if (_markerFeature == null || _markerFeature.enabled == false)
{
Debug.LogError("The Magic Leap 2 Marker Understanding OpenXR Feature is missing or disabled enabled. Disabling Script.");
this.enabled = false;
return;
}

if (XROrigin == null)
{
Debug.LogError("No XR Origin Found, markers sample will not work. Disabling Script.");
this.enabled = false;
}

// Configure a generic detector with QR and Aruco Detector settings
_detectorSettings.QRSettings.EstimateQRLength = true;
_detectorSettings.ArucoSettings.EstimateArucoLength = true;
_detectorSettings.ArucoSettings.ArucoType = ArucoType;

_detectorSettings.MarkerDetectorProfile = DetectorProfile;

// We use the same settings on all 3 of the
// different detectors and target the specific marker by setting the Marker Type before creating the detector

// Create Aruco detector
_detectorSettings.MarkerType = MagicLeapMarkerUnderstandingFeature.MarkerType.Aruco;
_markerFeature.CreateMarkerDetector(_detectorSettings);

// Create QRCode Detector
_detectorSettings.MarkerType = MagicLeapMarkerUnderstandingFeature.MarkerType.QR;
_markerFeature.CreateMarkerDetector(_detectorSettings);

// Create UPCA Detector
_detectorSettings.MarkerType = MagicLeapMarkerUnderstandingFeature.MarkerType.UPCA;
_markerFeature.CreateMarkerDetector(_detectorSettings);
}

private void OnDestroy()
{
if (_markerFeature != null)
{
_markerFeature.DestroyAllMarkerDetectors();
}
}

void Update()
{
// Update the marker detector
_markerFeature.UpdateMarkerDetectors();

// Iterate through all of the marker detectors
for (int i = 0; i < _markerFeature.MarkerDetectors.Count; i++)
{
// Verify that the marker detector is running
if (_markerFeature.MarkerDetectors[i].Status == MagicLeapMarkerUnderstandingFeature.MarkerDetectorStatus.Ready)
{
// Cycle through the detector's data and log it to the debug log
MagicLeapMarkerUnderstandingFeature.MarkerDetector currentDetector = _markerFeature.MarkerDetectors[i];
OnUpdateDetector(currentDetector);
}
}
}

private void OnUpdateDetector(MagicLeapMarkerUnderstandingFeature.MarkerDetector detector)
{

for (int i = 0; i < detector.Data.Count; i++)
{
string id = "";
float markerSize = .01f;
var data = detector.Data[i];
switch (detector.Settings.MarkerType)
{
case MagicLeapMarkerUnderstandingFeature.MarkerType.Aruco:
id = data.MarkerNumber.ToString();
markerSize = data.MarkerLength;
break;
case MagicLeapMarkerUnderstandingFeature.MarkerType.QR:
id = data.MarkerString;
markerSize = data.MarkerLength;
break;
case MagicLeapMarkerUnderstandingFeature.MarkerType.UPCA:
Debug.Log("No pose is given for marker type UPCA, value is " + id);
break;
}

if (!string.IsNullOrEmpty(id) && markerSize > 0)
{
// If the marker ID has not been tracked create a new marker object
if (!_markerObjectById.ContainsKey(id))
{
// Create a primitive cube
if(MarkerPrefab){
GameObject marker = Instantiate(MarkerPrefab);
_markerObjectById.Add(id, marker);
}else{
GameObject newMarkerObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
_markerObjectById.Add(id, newMarkerObject);
}

}

GameObject marker = _markerObjectById[id];
SetTransformToMarkerPose(marker.transform, data.MarkerPose, markerSize);
}
}
}

private void SetTransformToMarkerPose(Transform marker, Pose markerPose, float markerSize)
{
Transform originTransform = XROrigin.CameraFloorOffsetObject.transform;

// Set the position of the marker. Since the pose is given relative to the XR Origin,
// we need to transform it to world coordinates.
marker.position = originTransform.TransformPoint(markerPose.position);
marker.rotation = originTransform.rotation * markerPose.rotation;

// When marker size estimation is enabled, markers may take a few frames to scale to their appropriate size.
if (marker.transform.localScale.x != markerSize)
{
marker.localScale = new Vector3(markerSize, markerSize, markerSize);
}
}
}