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;
}
}
}
}