// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
// Copyright (c) (2024) Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2
// Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein.
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
#if UNITY_OPENXR_1_9_0_OR_NEWER
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine.Scripting;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.MagicLeap;
using UnityEngine.XR.OpenXR.Features.MagicLeapSupport.NativeInterop;
using UnityEngine.XR.OpenXR.NativeTypes;
using UnityEngine.XR.OpenXR.Features.MagicLeapSupport.MagicLeapOpenXRFeatureNativeTypes;
namespace UnityEngine.XR.OpenXR.Features.MagicLeapSupport
{
using MagicLeapSpatialAnchorsNativeTypes;
///
/// The Magic Leap implementation of the XRAnchorSubsystem. Do not create this directly.
/// Use XRAnchorSubsystemDescriptor.Create() instead.
///
[Preserve]
[System.Obsolete("Type has been relocated to new namespace. Update reference to MagicLeap.OpenXR.Subsystems")]
public sealed partial class MLXrAnchorSubsystem : XRAnchorSubsystem
{
public enum AnchorConfidence
{
NotFound = 0,
Low = 1,
Medium = 2,
High = 3
}
internal struct AnchorCompletionStatus
{
internal Pose Pose;
internal ulong Id;
internal XrUUID AnchorStorageId;
internal byte FromStorage;
internal XrResult Result;
}
public AnchorConfidence GetAnchorConfidence(ARAnchor arAnchor)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(GetAnchorConfidence)} without \"Start\"ing the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
return magicLapProvider.GetAnchorConfidence(arAnchor.trackableId);
}
public ulong GetAnchorId(ARAnchor arAnchor)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(GetAnchorId)} without \"Start\"ing the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
return magicLapProvider.GetAnchorId(arAnchor.trackableId);
}
public Pose GetAnchorPoseFromID(ulong anchorId)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(GetAnchorPoseFromID)} without \"Start\"ing the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
return magicLapProvider.GetAnchorPoseFromID(anchorId);
}
[System.Obsolete("Type has been relocated to new namespace. Update reference to MagicLeap.OpenXR.Features.MagicLeapProvider")]
private class MagicLeapProvider : Provider
{
private MagicLeapSpatialAnchorsFeature anchorsFeature = null;
private Camera mainCamera;
private const ulong AnchorTrackableIdSalt = 0xf52b75076e45ad88;
private int anchorHashCounter = 0;
private TrackableId GenerateTrackableId()
{
int hash = anchorHashCounter++;
hash = hash.GetHashCode();
var trackableId = new TrackableId((ulong)hash, AnchorTrackableIdSalt);
return trackableId;
}
private struct AnchorsSubsystemUpdateType
{ }
private struct AnchorSubsystemAnchorData
{
public ulong AnchorId;
public XRAnchor AnchorObject;
}
private HashSet pendingFutures = new HashSet();
private HashSet readyFutures = new HashSet();
private List currentAnchors = new List();
private List addedAnchors = new List();
private List updatedAnchors = new List();
private List deletedAnchors = new List();
public MagicLeapProvider()
{
}
public override void Start()
{
if (OpenXRRuntime.IsExtensionEnabled("XR_ML_spatial_anchors"))
{
mainCamera = Camera.main;
anchorsFeature = OpenXRSettings.Instance.GetFeature();
if (!anchorsFeature)
{
Debug.LogError("XRAnchorSubsystem failed to retrieve the MagicLeapSpatialAnchorsFeature and will not function properly.");
}
}
else
{
Debug.LogError($"XR_ML_spatial_anchors is not enabled and is required for XRAnchorSubsystem");
}
}
public override void Stop()
{
}
public override void Destroy()
{
}
public override unsafe TrackableChanges GetChanges(XRAnchor defaultAnchor, Allocator allocator)
{
unsafe
{
if (anchorsFeature)
{
if (pendingFutures.Count > 0)
{
HashSet toRemove = new HashSet();
foreach (ulong future in pendingFutures)
{
anchorsFeature.SpatialAnchorsNativeFunctions.PollFuture(future, out XrFutureState pollState, out XrResult futureResult);
if (futureResult == (XrResult)Utils.MLXrResult.FutureInvalidEXT)
{
toRemove.Add(future);
}
if (pollState == XrFutureState.Ready)
{
readyFutures.Add(future);
toRemove.Add(future);
}
}
pendingFutures.ExceptWith(toRemove);
toRemove.Clear();
}
foreach (ulong future in readyFutures)
{
// Completion From Pose only has one result.
ulong currentSpace;
var completionData = new XrCreateSpatialAnchorsCompletion
{
Type = XrSpatialAnchorsStructTypes.XrTypeCreateSpatialAnchorsCompletion,
SpaceCount = 1,
Spaces = ¤tSpace
};
XrResult resultCode = anchorsFeature.SpatialAnchorsNativeFunctions.XrCreateSpatialAnchorsComplete(anchorsFeature.AppSession, future, out completionData);
bool result = Utils.DidXrCallSucceed(resultCode, nameof(anchorsFeature.SpatialAnchorsNativeFunctions.XrCreateSpatialAnchorsAsync));
if (result)
{
Pose currentPose = GetAnchorPoseFromID(currentSpace);
bool found = false;
for (int i = 0; i < currentAnchors.Count; i++)
{
if (currentAnchors[i].AnchorObject.pose == currentPose)
{
var updatedAnchor = new AnchorSubsystemAnchorData();
updatedAnchor.AnchorId = currentSpace;
updatedAnchor.AnchorObject = new XRAnchor(currentAnchors[i].AnchorObject.trackableId, currentPose, TrackingState.Tracking, IntPtr.Zero);
currentAnchors[i] = updatedAnchor;
updatedAnchors.Add(updatedAnchor.AnchorObject);
found = true;
break;
}
}
if (!found)
{
Debug.LogWarning("XRAnchorSubsystem: Anchor created outside of the Anchor Manager at " + currentPose.ToString());
TrackableId newId = GenerateTrackableId();
AnchorSubsystemAnchorData newAnchor = new AnchorSubsystemAnchorData();
newAnchor.AnchorId = currentSpace;
newAnchor.AnchorObject = new XRAnchor(newId, currentPose, TrackingState.Tracking, IntPtr.Zero);
currentAnchors.Add(newAnchor);
addedAnchors.Add(newAnchor.AnchorObject);
}
}
}
readyFutures.Clear();
}
}
var changes = new TrackableChanges(
addedAnchors.Count,
updatedAnchors.Count,
deletedAnchors.Count,
allocator);
changes.added.CopyFrom(addedAnchors.ToArray());
changes.updated.CopyFrom(updatedAnchors.ToArray());
changes.removed.CopyFrom(deletedAnchors.ToArray());
addedAnchors.Clear();
updatedAnchors.Clear();
deletedAnchors.Clear();
return changes;
}
public override unsafe bool TryAddAnchor(Pose pose, out XRAnchor xrAnchor)
{
bool result = false;
xrAnchor = default;
if (anchorsFeature)
{
var createInfo = new XrSpatialAnchorsCreateInfoFromPose
{
Type = XrSpatialAnchorsStructTypes.XrTypeSpatialAnchorsCreateInfoFromPose,
BaseSpace = anchorsFeature.AppSpace,
PoseInBaseSpace = XrPose.GetFromPose(pose),
Time = anchorsFeature.NextPredictedDisplayTime
};
var baseHeader = (XrSpatialAnchorsCreateInfoBaseHeader*)(&createInfo);
XrResult resultCode = anchorsFeature.SpatialAnchorsNativeFunctions.XrCreateSpatialAnchorsAsync(anchorsFeature.AppSession, baseHeader, out ulong pendingFuture);
result = Utils.DidXrCallSucceed(resultCode, nameof(anchorsFeature.SpatialAnchorsNativeFunctions.XrCreateSpatialAnchorsAsync));
if (!result)
{
Debug.LogError("XRAnchorSubsystem: Error creating Anchor at " + pose.ToString());
}
else
{
pendingFutures.Add(pendingFuture);
TrackableId newId = GenerateTrackableId();
AnchorSubsystemAnchorData newAnchor = new AnchorSubsystemAnchorData();
newAnchor.AnchorId = 0u;
newAnchor.AnchorObject = new XRAnchor(newId, pose, TrackingState.None, IntPtr.Zero);
xrAnchor = newAnchor.AnchorObject;
currentAnchors.Add(newAnchor);
addedAnchors.Add(newAnchor.AnchorObject);
}
}
return result;
}
public override bool TryRemoveAnchor(TrackableId trackableId)
{
bool anchorFound = false;
int foundIndex = 0;
for (int i = 0; i < currentAnchors.Count; i++)
{
if (currentAnchors[i].AnchorObject.trackableId == trackableId)
{
anchorFound = true;
foundIndex = i;
break;
}
}
if (anchorFound)
{
if(anchorsFeature.AppSession == default)
{
deletedAnchors.Add(currentAnchors[foundIndex].AnchorObject.trackableId);
return true;
}
bool deleteResult = false;
unsafe
{
ulong anchorId = currentAnchors[foundIndex].AnchorId;
XrResult resultCode = anchorsFeature.SpaceInfoNativeFunctions.XrDestroySpace(anchorId);
deleteResult = Utils.DidXrCallSucceed(resultCode, nameof(anchorsFeature.SpaceInfoNativeFunctions.XrDestroySpace));
}
if (!deleteResult)
{
Debug.LogError("XRAnchorSubsystem: Error Deleting local Anchor " + trackableId.ToString());
}
else
{
deletedAnchors.Add(currentAnchors[foundIndex].AnchorObject.trackableId);
currentAnchors.RemoveAt(foundIndex);
}
if (deleteResult)
{
return true;
}
}
Debug.LogError("XRAnchorSubsystem: Failed Deleting Anchor from Subsystem " + trackableId.ToString());
return false;
}
public AnchorConfidence GetAnchorConfidence(TrackableId xrAnchorId)
{
for (int i = 0; i < currentAnchors.Count; i++)
{
if (currentAnchors[i].AnchorObject.trackableId == xrAnchorId)
{
ulong anchorId = GetAnchorId(xrAnchorId);
if(anchorId == 0u)
{
return AnchorConfidence.NotFound;
}
unsafe
{
bool result = false;
uint anchorCon = 0;
var anchorState = new XrSpatialAnchorState
{
Type = XrSpatialAnchorsStructTypes.XrTypeSpatialAnchorState
};
XrResult resultCode = anchorsFeature.SpatialAnchorsNativeFunctions.XrGetSpatialAnchorState(anchorId, out anchorState);
result = Utils.DidXrCallSucceed(resultCode, nameof(anchorsFeature.SpatialAnchorsNativeFunctions.XrGetSpatialAnchorState));
if (!result)
{
Debug.LogError("XRAnchorSubsystem: GetAnchorConfidence failed at " + currentAnchors[i].AnchorId.ToString() + " with result: " + resultCode);
}
else
{
// MLXrAnchorSubsystem adds a confidence value of NotFound.
anchorCon = ((uint)anchorState.Confidence + 1);
}
return (AnchorConfidence)anchorCon;
}
}
}
return AnchorConfidence.NotFound;
}
public ulong GetAnchorId(TrackableId xrAnchorId)
{
for (int i = 0; i < currentAnchors.Count; i++)
{
if (currentAnchors[i].AnchorObject.trackableId == xrAnchorId)
{
return currentAnchors[i].AnchorId;
}
}
return 0u;
}
public Pose GetAnchorPoseFromID(ulong anchorId)
{
return anchorsFeature.SpaceInfoNativeFunctions.GetUnityPose(anchorId, anchorsFeature.AppSpace, anchorsFeature.NextPredictedDisplayTime);
}
}
public static void RegisterDescriptor()
{
Debug.Log("XRAnchorSubsystem: Registering Anchors Subsystem");
XRAnchorSubsystemDescriptor.Create(new XRAnchorSubsystemDescriptor.Cinfo
{
id = MagicLeapXrProvider.AnchorSubsystemId,
providerType = typeof(MagicLeapProvider),
subsystemTypeOverride = typeof(MLXrAnchorSubsystem)
});
}
}
}
#endif