// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
// Copyright (c) (2021-2022) 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 MagicLeap.OpenXR.Constants;
using MagicLeap.OpenXR.Futures;
using MagicLeap.OpenXR.Features.SpatialAnchors;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Scripting;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.MagicLeap;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
namespace MagicLeap.OpenXR.Subsystems
{
///
/// The Magic Leap implementation of the XRAnchorSubsystem. Do not create this directly.
/// Use XRAnchorSubsystemDescriptor.Create() instead.
///
[Preserve]
public sealed partial class MLXrAnchorSubsystem : XRAnchorSubsystem
{
public enum AnchorConfidence
{
NotFound = 0,
Low = 1,
Medium = 2,
High = 3
}
internal struct AnchorStoragePending
{
internal Pose Pose;
internal ulong Id;
internal string mapPositionId;
}
public AnchorConfidence GetAnchorConfidence(ARAnchor arAnchor)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(GetAnchorConfidence)} without Starting 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 Starting the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
return magicLapProvider.GetAnchorId(arAnchor.trackableId);
}
public string GetAnchorMapPositionId(ARAnchor arAnchor)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(GetAnchorMapPositionId)} without Starting the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
return magicLapProvider.GetAnchorMapPositionId(arAnchor.trackableId);
}
[Obsolete("GetAnchorPoseFromID is obsolete. Use GetAnchorPoseFromId instead.", false)]
public Pose GetAnchorPoseFromID(ulong anchorId)
{
return GetAnchorPoseFromId(anchorId);
}
public Pose GetAnchorPoseFromId(ulong anchorId)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(GetAnchorPoseFromId)} without Starting the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
return magicLapProvider.GetAnchorPoseFromID(anchorId);
}
public Pose GetAnchorPose(ARAnchor arAnchor)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(GetAnchorPose)} without Starting the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
return magicLapProvider.GetAnchorPose(arAnchor.trackableId);
}
public bool IsStoredAnchor(ARAnchor arAnchor)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(IsStoredAnchor)} without Starting the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
return magicLapProvider.IsStoredAnchor(arAnchor.trackableId);
}
public TrackableId GetTrackableIdFromMapPositionId(string mapPositionId)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(GetTrackableIdFromMapPositionId)} without Starting the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
return magicLapProvider.GetTrackableIdFromMapPositionId(mapPositionId);
}
internal void AddStorageAnchorToSubsystem(Pose pose, ulong anchorId, string anchorMapPositionId)
{
if(!running)
throw new InvalidOperationException($"Can't call {nameof(AddStorageAnchorToSubsystem)} without Starting the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
magicLapProvider.AddPendingStorageAnchor(pose, anchorId, anchorMapPositionId);
}
internal void PublishLocalAnchor(ulong anchorId, string anchorMapPositionId)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(PublishLocalAnchor)} without Starting the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
magicLapProvider.PublishLocalAnchor(anchorId, anchorMapPositionId);
}
internal void DeleteStoredAnchors(List anchorMapPositionIds)
{
if (!running)
throw new InvalidOperationException($"Can't call {nameof(DeleteStoredAnchors)} without Starting the anchor subsystem!");
var magicLapProvider = provider as MagicLeapProvider;
foreach (string anchorMapPositionId in anchorMapPositionIds)
{
magicLapProvider.DeleteStoredAnchor(anchorMapPositionId);
}
}
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 string AnchorMapPositionId;
public XRAnchor AnchorObject;
}
private HashSet pendingFutures = new HashSet();
private HashSet readyFutures = new HashSet();
private List pendingDeleteStoredAnchors = new List();
private List currentAnchors = new List();
private List addedAnchors = new List();
private List updatedAnchors = new List();
private List deletedAnchors = new List();
private List pendingStorageAnchors = 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)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();
}
}
// Add Anchors From Storage
foreach (AnchorStoragePending storedAnchor in pendingStorageAnchors)
{
AnchorSubsystemAnchorData existingAnchor = currentAnchors.Find(anchor => anchor.AnchorId == storedAnchor.Id);
if (existingAnchor.AnchorId != 0u)
{
// Sanity check for the subsystem tracking an anchor and not knowing it is stored.
if (string.IsNullOrEmpty(existingAnchor.AnchorMapPositionId))
{
int index = currentAnchors.FindIndex(a => a.AnchorObject.trackableId == existingAnchor.AnchorObject.trackableId);
if (index != -1)
{
var updatedAnchor = new AnchorSubsystemAnchorData();
updatedAnchor.AnchorId = existingAnchor.AnchorId;
updatedAnchor.AnchorMapPositionId = storedAnchor.mapPositionId;
updatedAnchor.AnchorObject = new XRAnchor(currentAnchors[index].AnchorObject.trackableId, currentAnchors[index].AnchorObject.pose, TrackingState.Tracking, IntPtr.Zero);
currentAnchors[index] = updatedAnchor;
updatedAnchors.Add(existingAnchor.AnchorObject);
}
}
continue;
}
TrackableId newId = GenerateTrackableId();
AnchorSubsystemAnchorData newAnchor = new AnchorSubsystemAnchorData();
newAnchor.AnchorId = storedAnchor.Id;
newAnchor.AnchorMapPositionId = storedAnchor.mapPositionId;
newAnchor.AnchorObject = new XRAnchor(newId, storedAnchor.Pose, TrackingState.Tracking, IntPtr.Zero);
currentAnchors.Add(newAnchor);
addedAnchors.Add(newAnchor.AnchorObject);
}
pendingStorageAnchors.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)
{
// Stored Anchor is being deleted from the localization map and subsystem.
if (pendingDeleteStoredAnchors.Remove(trackableId))
{
return true;
}
// Stored Anchor is being removed from subsystem only.
if (IsStoredAnchor(trackableId))
{
AnchorSubsystemAnchorData foundAnchor = currentAnchors.Find(anchor => anchor.AnchorObject.trackableId == trackableId);
deletedAnchors.Add(trackableId);
currentAnchors.Remove(foundAnchor);
return true;
}
// Local Anchor is being removed from subsystem.
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 string GetAnchorMapPositionId(TrackableId xrAnchorId)
{
AnchorSubsystemAnchorData foundAnchor = currentAnchors.Find(anchor => anchor.AnchorObject.trackableId == xrAnchorId);
return foundAnchor.AnchorMapPositionId;
}
public Pose GetAnchorPoseFromID(ulong anchorId)
{
return anchorsFeature.SpaceInfoNativeFunctions.GetUnityPose(anchorId, anchorsFeature.AppSpace, anchorsFeature.NextPredictedDisplayTime);
}
public Pose GetAnchorPose(TrackableId xrAnchorId)
{
AnchorSubsystemAnchorData foundAnchor = currentAnchors.Find(anchor => anchor.AnchorObject.trackableId == xrAnchorId);
if(foundAnchor.AnchorId == 0u)
{
Debug.LogError("XRAnchorSubsystem: GetAnchorPose failed to find the requested Anchor in the subsystem.");
return Pose.identity;
}
return anchorsFeature.SpaceInfoNativeFunctions.GetUnityPose(foundAnchor.AnchorId, anchorsFeature.AppSpace, anchorsFeature.NextPredictedDisplayTime);
}
public bool IsStoredAnchor(TrackableId xrAnchorId)
{
AnchorSubsystemAnchorData foundAnchor = currentAnchors.Find(anchor => anchor.AnchorObject.trackableId == xrAnchorId);
return !string.IsNullOrEmpty(foundAnchor.AnchorMapPositionId);
}
public TrackableId GetTrackableIdFromMapPositionId(string mapPositionId)
{
AnchorSubsystemAnchorData foundAnchor = currentAnchors.Find(anchor => anchor.AnchorMapPositionId == mapPositionId);
return foundAnchor.AnchorObject.trackableId;
}
internal void AddPendingStorageAnchor(Pose pose, ulong anchorId, string anchorMapPositionId)
{
AnchorStoragePending newStorageAnchor = new AnchorStoragePending
{
Pose = pose,
Id = anchorId,
mapPositionId = anchorMapPositionId
};
pendingStorageAnchors.Add(newStorageAnchor);
}
internal void PublishLocalAnchor(ulong anchorId, string anchorMapPositionId)
{
for (int i = 0; i < currentAnchors.Count; i++)
{
if (currentAnchors[i].AnchorId == anchorId)
{
var updatedAnchor = new AnchorSubsystemAnchorData();
updatedAnchor.AnchorId = anchorId;
updatedAnchor.AnchorMapPositionId = anchorMapPositionId;
updatedAnchor.AnchorObject = new XRAnchor(currentAnchors[i].AnchorObject.trackableId, currentAnchors[i].AnchorObject.pose, TrackingState.Tracking, IntPtr.Zero);
currentAnchors[i] = updatedAnchor;
updatedAnchors.Add(updatedAnchor.AnchorObject);
break;
}
}
}
internal void DeleteStoredAnchor(string anchorMapPositionId)
{
AnchorSubsystemAnchorData foundAnchor = currentAnchors.Find(anchor => anchor.AnchorMapPositionId == anchorMapPositionId);
if(foundAnchor.AnchorId != 0u)
{
pendingDeleteStoredAnchors.Add(foundAnchor.AnchorObject.trackableId);
deletedAnchors.Add(foundAnchor.AnchorObject.trackableId);
currentAnchors.Remove(foundAnchor);
}
else
{
Debug.LogError("XRAnchorSubsystem: Failed to find the requested Anchor in the subsystem to delete.");
}
}
}
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