using System; using System.Collections.Generic; using System.Text; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.XR.OpenXR.Features.MagicLeapSupport.MagicLeapOpenXRFeatureNativeTypes; using UnityEngine.XR.OpenXR.NativeTypes; namespace UnityEngine.XR.OpenXR.Features.MagicLeapSupport { using MarkerUnderstandingNativeTypes; public partial class MagicLeapMarkerUnderstandingFeature { /// /// Used to detect data from a specified type of marker tracker based on specific settings. /// [System.Obsolete("Type has been relocated to new namespace. Update reference to MagicLeap.OpenXR.Features.MarkerDetector")] public class MarkerDetector { private ulong handle; private MarkerData[] data; private List markers; private readonly Dictionary markerSpaces; /// /// The current settings associated with the marker detector. /// public MarkerDetectorSettings Settings { get; } /// /// The current status of the readiness of the marker detector. /// public MarkerDetectorStatus Status { get; private set; } /// /// The data retrieved from the marker detector. /// /// A readonly collection of data retrieved from the marker detector. public IReadOnlyList Data => Array.AsReadOnly(data); private bool activeSnapshot; private MagicLeapMarkerUnderstandingNativeFunctions NativeFunctions { get; } private MagicLeapMarkerUnderstandingFeature MarkerUnderstandingFeature { get; } /// /// Creates a marker detector based on specific settings and initializes the data values. /// /// The marker detector settings to be associated with the marker detector to be created. /// The native OpenXR function pointers /// The MagicLeapMarkerUnderstandingFeature reference internal MarkerDetector(MarkerDetectorSettings settings, MagicLeapMarkerUnderstandingNativeFunctions nativeFunctions, MagicLeapMarkerUnderstandingFeature markerUnderstandingFeature) { NativeFunctions = nativeFunctions; MarkerUnderstandingFeature = markerUnderstandingFeature; Settings = settings; var resultCode = CreateMarkerDetectorInternal(); var success = Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrCreateMarkerDetector)); Settings = settings; Status = success? MarkerDetectorStatus.Pending : MarkerDetectorStatus.Error; data = Array.Empty(); markers = new List(); markerSpaces = new(); } private unsafe XrResult CreateMarkerDetectorInternal() { var infoContainer = XrMarkerDetectorInfoContainer.Create(); infoContainer.CreateInfo.MarkerType = (XrMarkerType)Settings.MarkerType; infoContainer.CreateInfo.Profile = (XrMarkerDetectorProfile)Settings.MarkerDetectorProfile; ref var createInfo = ref infoContainer.CreateInfo; ref var arucoInfo = ref infoContainer.ArucoInfo; ref var aprilInfo = ref infoContainer.AprilTagInfo; ref var customInfo = ref infoContainer.CustomInfo; ref var sizeInfo = ref infoContainer.SizeInfo; var markerType = createInfo.MarkerType; var chainStart = &infoContainer.CreateInfo.Next; var currentChain = chainStart; if (createInfo.Profile == XrMarkerDetectorProfile.Custom) { customInfo.ConvertCustomProfile(Settings.CustomProfileSettings); *currentChain = new IntPtr(&infoContainer.CustomInfo); currentChain = &infoContainer.CustomInfo.Next; } var shouldAppendSizeInfo = false; var length = 0f; if (markerType == XrMarkerType.Aruco) { arucoInfo.ArucoDict = (XrMarkerArucoDict)Settings.ArucoSettings.ArucoType; shouldAppendSizeInfo = !Settings.ArucoSettings.EstimateArucoLength; length = Settings.ArucoSettings.ArucoLength; *currentChain = new IntPtr(&infoContainer.ArucoInfo); currentChain = &infoContainer.ArucoInfo.Next; } if (markerType == XrMarkerType.AprilTag) { aprilInfo.AprilTagType = (XrAprilTagType)Settings.AprilTagSettings.AprilTagType; shouldAppendSizeInfo = !Settings.AprilTagSettings.EstimateAprilTagLength; length = Settings.AprilTagSettings.AprilTagLength; *currentChain = new IntPtr(&infoContainer.AprilTagInfo); currentChain = &infoContainer.AprilTagInfo.Next; } if (markerType == XrMarkerType.QR) { shouldAppendSizeInfo = !Settings.QRSettings.EstimateQRLength; length = Settings.QRSettings.QRLength; } if (shouldAppendSizeInfo) { sizeInfo.MarkerLength = length; *currentChain = new IntPtr(&infoContainer.SizeInfo); } var xrResult = NativeFunctions.XrCreateMarkerDetector(MarkerUnderstandingFeature.AppSession, in infoContainer.CreateInfo, out handle); Utils.DidXrCallSucceed(xrResult, nameof(NativeFunctions.XrCreateMarkerDetector)); return xrResult; } /// /// Updates the status readiness of the marker detector and collects the current data values if it is in a ready state. /// internal void UpdateData() { Status = GetMarkerDetectorState(); if (Status == MarkerDetectorStatus.Ready) { activeSnapshot = false; data = GetMarkersData(); } } /// /// Destroys this marker detector and clears the associated data. /// internal unsafe void Destroy() { var resultCode = NativeFunctions.XrDestroyMarkerDetector(handle); Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrDestroyMarkerDetector)); data = Array.Empty(); markers = new List(); markerSpaces.Clear(); } /// /// Takes a snapshot of the active marker detector and gets the current status of it. /// /// The status of the marker detector, as either Pending, Ready, or Error. private unsafe MarkerDetectorStatus GetMarkerDetectorState() { if (!activeSnapshot) { SnapshotMarkerDetector(); } var xrMarkerState = new XrMarkerDetectorState { Type = XrMarkerUnderstandingStructTypes.XrTypeMarkerDetectorState, }; var resultCode = NativeFunctions.XrGetMarkerDetectorState(handle, out xrMarkerState); Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrGetMarkerDetectorState)); return (MarkerDetectorStatus)xrMarkerState.Status; } /// /// Gets the relevant marker data of all markers associated with the active marker detector. /// /// An array representing all data retrieved from the active marker detector for all markers. private MarkerData[] GetMarkersData() { GetMarkers(); var markersData = new MarkerData[markers.Count]; for (var i = 0; i < markers.Count; ++i) { markersData[i] = GetMarkerData(markers[i]); } if (markers.Count == 0) { // clear cached data markerSpaces.Clear(); } return markersData; } private unsafe void SnapshotMarkerDetector() { var snapshotInfo = new XrMarkerDetectorSnapshotInfo { Type = XrMarkerUnderstandingStructTypes.XrTypeMarkerDetectorSnapshotInfo, }; var resultCode = NativeFunctions.XrSnapshotMarkerDetector(handle, ref snapshotInfo); if (Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrSnapshotMarkerDetector))) { activeSnapshot = true; } } private unsafe void GetMarkers() { markers.Clear(); // call first time to get marker count var resultCode = NativeFunctions.XrGetMarkers(handle, 0, out var markerCount, null); if (!Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrGetMarkers))) { return; } // call second time to get markers var markersArray = new NativeArray((int)markerCount, Allocator.Temp); resultCode = NativeFunctions.XrGetMarkers(handle, markerCount, out markerCount, (ulong*)markersArray.GetUnsafePtr()); foreach (var marker in markersArray) { if (marker == 0) { continue; } markers.Add(marker); } Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrGetMarkers)); } private MarkerData GetMarkerData(ulong marker) { if (marker == 0) { return default; } MarkerData markerData; markerData.MarkerLength = GetMarkerLength(marker); if (Settings.MarkerType == MarkerType.QR || Settings.MarkerType == MarkerType.Code128 || Settings.MarkerType == MarkerType.EAN13 || Settings.MarkerType == MarkerType.UPCA) { markerData.ReprojectionErrorMeters = 0; markerData.MarkerNumber = null; markerData.MarkerString = GetMarkerString(marker); } else { markerData.ReprojectionErrorMeters = GetMarkerReprojectionError(marker); markerData.MarkerNumber = GetMarkerNumber(marker); markerData.MarkerString = null; } if (Settings.MarkerType == MarkerType.Aruco || Settings.MarkerType == MarkerType.QR || Settings.MarkerType == MarkerType.AprilTag) { markerData.MarkerPose = CreateMarkerSpace(marker); } else { markerData.MarkerPose = null; } return markerData; } private unsafe float GetMarkerReprojectionError(ulong marker) { var resultCode = NativeFunctions.XrGetMarkerReprojectionError(handle, marker, out var reprojectionError); Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrGetMarkerReprojectionError)); return reprojectionError; } private unsafe float GetMarkerLength(ulong marker) { var resultCode = NativeFunctions.XrGetMarkerLength(handle, marker, out var meters); Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrGetMarkerLength)); return meters; } private unsafe ulong GetMarkerNumber(ulong marker) { var resultCode = NativeFunctions.XrGetMarkerNumber(handle, marker, out var number); Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrGetMarkerNumber)); return number; } private unsafe string GetMarkerString(ulong marker) { var resultCode = NativeFunctions.XrGetMarkerString(handle, marker, 0, out var markerLength, null); if (!Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrGetMarkerString))) { return ""; } var byteArray = new NativeArray((int)markerLength, Allocator.Temp); resultCode = NativeFunctions.XrGetMarkerString(handle, marker, markerLength, out markerLength, (byte*) byteArray.GetUnsafePtr()); if (!Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrGetMarkerString))) { return ""; } var result = Encoding.UTF8.GetString((byte*)byteArray.GetUnsafePtr(), (int)markerLength).Trim('\0'); return result; } private unsafe Pose CreateMarkerSpace(ulong marker) { Pose pose = default; // a marker space is not created if one already exists for that marker if (markerSpaces.TryGetValue(marker, out var space)) { pose = NativeFunctions.GetUnityPose(space, MarkerUnderstandingFeature.AppSpace, MarkerUnderstandingFeature.NextPredictedDisplayTime); return pose; } var markerCreateSpaceInfo = new XrMarkerSpaceCreateInfo { Type = XrMarkerUnderstandingStructTypes.XrTypeMarkerSpaceCreateInfo, MarkerDetector = handle, Marker = marker, PoseInMarkerSpace = XrPose.GetFromPose(new Pose(Vector3.zero, Quaternion.identity)) }; var resultCode = NativeFunctions.XrCreateMarkerSpace(MarkerUnderstandingFeature.AppSession, in markerCreateSpaceInfo, out var xrSpace); if (Utils.DidXrCallSucceed(resultCode, nameof(NativeFunctions.XrCreateMarkerSpace))) { markerSpaces.Add(marker, xrSpace); } return pose; } } } }