version 2.5.0

This commit is contained in:
Sean Lu
2025-01-10 17:17:03 +08:00
parent ddc3c4c6d8
commit 2372c9429a
1086 changed files with 290974 additions and 77367 deletions

View File

@@ -3,8 +3,11 @@ using System;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using VIVE.OpenXR.Feature;
using VIVE.OpenXR.Anchor;
using static VIVE.OpenXR.Anchor.ViveAnchor;
using static VIVE.OpenXR.Feature.ViveAnchor;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
namespace VIVE.OpenXR.Toolkits.Anchor
{
@@ -12,8 +15,9 @@ namespace VIVE.OpenXR.Toolkits.Anchor
{
static ViveAnchor feature = null;
static bool isSupported = false;
static bool isPersistedAnchorSupported = false;
static void CheckFeature()
static void EnsureFeature()
{
if (feature != null) return;
@@ -22,28 +26,32 @@ namespace VIVE.OpenXR.Toolkits.Anchor
throw new NotSupportedException("ViveAnchor feature is not enabled");
}
static void EnsureCollection()
{
if (taskAcquirePAC != null)
{
Debug.Log("AnchorManager: Wait for AcquirePersistedAnchorCollection task.");
taskAcquirePAC.Wait();
}
if (persistedAnchorCollection == IntPtr.Zero)
throw new Exception("Should create Persisted Anchor Collection first.");
}
/// <summary>
/// Helper to get the extention feature instance.
/// Helper to get the extension feature instance.
/// </summary>
/// <returns></returns>
/// <returns>Instance of ViveAnchor feature.</returns>
public static ViveAnchor GetFeature()
{
try
{
CheckFeature();
}
catch (NotSupportedException)
{
Debug.LogWarning("ViveAnchor feature is not enabled");
return null;
}
if (feature != null) return feature;
feature = OpenXRSettings.Instance.GetFeature<ViveAnchor>();
return feature;
}
/// <summary>
/// Check if the extension is supported.
/// Check if the extensions are supported. Should always check this before using the other functions.
/// </summary>
/// <returns></returns>
/// <returns>True if the extension is supported, false otherwise.</returns>
public static bool IsSupported()
{
if (GetFeature() == null) return false;
@@ -52,18 +60,32 @@ namespace VIVE.OpenXR.Toolkits.Anchor
var ret = false;
if (feature.GetProperties(out XrSystemAnchorPropertiesHTC properties) == XrResult.XR_SUCCESS)
{
Debug.Log("Anchor: IsSupported() properties.supportedFeatures: " + properties.supportsAnchor);
ret = properties.supportsAnchor;
Debug.Log("ViveAnchor: IsSupported() properties.supportedFeatures: " + properties.supportsAnchor);
ret = properties.supportsAnchor > 0;
isSupported = ret;
}
else
{
Debug.Log("Anchor: IsSupported() GetSystemProperties failed.");
Debug.Log("ViveAnchor: IsSupported() GetSystemProperties failed.");
}
return ret;
}
/// <summary>
/// Check if the persisted anchor extension is supported and enabled.
/// Should always check this before using the other persistance function.
/// </summary>
/// <returns>True if persisted anchor extension is supported, false otherwise.</returns>
public static bool IsPersistedAnchorSupported()
{
if (GetFeature() == null) return false;
if (isPersistedAnchorSupported) return true;
else
isPersistedAnchorSupported = feature.IsPersistedAnchorSupported();
return isPersistedAnchorSupported;
}
/// <summary>
/// Create a spatial anchor at tracking space (Camera Rig).
/// </summary>
@@ -71,38 +93,51 @@ namespace VIVE.OpenXR.Toolkits.Anchor
/// <returns>Anchor container</returns>
public static Anchor CreateAnchor(Pose pose, string name)
{
try
{
CheckFeature();
XrSpace baseSpace = feature.GetTrackingSpace();
XrSpatialAnchorCreateInfoHTC createInfo = new XrSpatialAnchorCreateInfoHTC();
createInfo.type = XrStructureType.XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_HTC;
createInfo.poseInSpace = new XrPosef();
createInfo.poseInSpace.position = pose.position.ToOpenXRVector();
createInfo.poseInSpace.orientation = pose.rotation.ToOpenXRQuaternion();
createInfo.name.name = name;
createInfo.space = baseSpace;
EnsureFeature();
if (feature.CreateSpatialAnchor(createInfo, out XrSpace anchor) == XrResult.XR_SUCCESS)
{
return new Anchor(anchor, name);
}
} catch (Exception) { }
if (string.IsNullOrEmpty(name))
throw new ArgumentException("The name should not be empty.");
XrSpace baseSpace = feature.GetTrackingSpace();
XrSpatialAnchorCreateInfoHTC createInfo = new XrSpatialAnchorCreateInfoHTC();
createInfo.type = XrStructureType.XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_HTC;
createInfo.poseInSpace = new XrPosef();
createInfo.poseInSpace.position = pose.position.ToOpenXRVector();
createInfo.poseInSpace.orientation = pose.rotation.ToOpenXRQuaternion();
createInfo.name = new XrSpatialAnchorNameHTC(name);
createInfo.space = baseSpace;
if (feature.CreateSpatialAnchor(createInfo, out XrSpace anchor) == XrResult.XR_SUCCESS)
{
return new Anchor(anchor, name);
}
return null;
}
/// <summary>
/// Get the name of the spatial anchor.
/// </summary>
/// <param name="anchor">The anchor instance.</param>
/// <param name="name">Output parameter to hold the name of the anchor.</param>
/// <returns>True if the name is successfully retrieved, false otherwise.</returns>
public static bool GetSpatialAnchorName(Anchor anchor, out string name)
{
return GetSpatialAnchorName(anchor.GetXrSpace(), out name);
}
/// <summary>
/// Get the name of the spatial anchor.
/// </summary>
/// <param name="anchor">The XrSpace representing the anchor.</param>
/// <param name="name">Output parameter to hold the name of the anchor.</param>
/// <returns>True if the name is successfully retrieved, false otherwise.</returns>
public static bool GetSpatialAnchorName(XrSpace anchor, out string name)
{
name = "";
CheckFeature();
EnsureFeature();
XrResult ret = feature.GetSpatialAnchorName(anchor, out XrSpatialAnchorNameHTC xrName);
if (ret == XrResult.XR_SUCCESS)
name = xrName.name;
name = xrName.ToString();
return ret == XrResult.XR_SUCCESS;
}
@@ -112,7 +147,7 @@ namespace VIVE.OpenXR.Toolkits.Anchor
/// <returns></returns>
public static XrSpace GetTrackingSpace()
{
CheckFeature();
EnsureFeature();
return feature.GetTrackingSpace();
}
@@ -128,9 +163,435 @@ namespace VIVE.OpenXR.Toolkits.Anchor
return anchor.GetRelatedPose(feature.GetTrackingSpace(), ViveInterceptors.Instance.GetPredictTime(), out pose);
}
// Use SemaphoreSlim to make sure only one anchor's task is running at the same time.
static readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
// Use lock to make sure taskAcquirePAC and persistedAnchorCollection assignment is atomic.
static readonly object asyncLock = new object();
static FutureTask<(XrResult, IntPtr)> taskAcquirePAC = null;
static IntPtr persistedAnchorCollection = System.IntPtr.Zero;
private static (XrResult, IntPtr) CompletePAC(IntPtr future)
{
Debug.Log("AnchorManager: AcquirePersistedAnchorCollectionComplete");
var ret = feature.AcquirePersistedAnchorCollectionComplete(future, out var completion);
lock (asyncLock)
{
taskAcquirePAC = null;
if (ret == XrResult.XR_SUCCESS)
{
ret = completion.futureResult;
Debug.Log("AnchorManager: AcquirePersistedAnchorCollection: Complete");
persistedAnchorCollection = completion.persistedAnchorCollection;
return (ret, persistedAnchorCollection);
}
else
{
//Debug.LogError("AcquirePersistedAnchorCollection: Complete: PersistedAnchorCollection=" + completion.persistedAnchorCollection);
persistedAnchorCollection = System.IntPtr.Zero;
return (ret, persistedAnchorCollection);
}
}
}
/// <summary>
/// Anchor is a named Space. It can be used to create a spatial anchor, or get the anchor's name.
/// After use it, you should call Dispose() to release the anchor.
/// Enable the persistance anchor feature. It will acquire a persisted anchor collection.
/// The first time PAC's acquiration may take time. You can to cancel the process by calling <see cref="ReleasePersistedAnchorCollection"/>.
/// You can wait for the returned task to complete, or by calling <see cref="IsPersistedAnchorCollectionAcquired"/> to check if the collection is ready.
/// Use <see cref="ReleasePersistedAnchorCollection"/> to free resource when no any persisted anchor operations are needed.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public static FutureTask<(XrResult, IntPtr)> AcquirePersistedAnchorCollection()
{
EnsureFeature();
if (!feature.IsPersistedAnchorSupported())
return FutureTask<(XrResult, IntPtr)>.FromResult((XrResult.XR_ERROR_EXTENSION_NOT_PRESENT, IntPtr.Zero));
lock (asyncLock)
{
if (persistedAnchorCollection != System.IntPtr.Zero)
return FutureTask<(XrResult, IntPtr)>.FromResult((XrResult.XR_SUCCESS, persistedAnchorCollection));
// If the persistedAnchorCollection is not ready, and the task is started, wait for it.
if (taskAcquirePAC != null)
return taskAcquirePAC;
}
Debug.Log("ViveAnchor: AcquirePersistedAnchorCollectionAsync");
var ret = feature.AcquirePersistedAnchorCollectionAsync(out IntPtr future);
if (ret != XrResult.XR_SUCCESS)
{
Debug.LogError("AcquirePersistedAnchorCollection failed: " + ret);
return FutureTask<(XrResult, IntPtr)>.FromResult((ret, IntPtr.Zero));
}
else
{
var task = new FutureTask<(XrResult, IntPtr)>(future, CompletePAC, 10, autoComplete: true);
lock (asyncLock)
{
taskAcquirePAC = task;
}
return task;
}
}
/// <summary>
/// Check if the persisted anchor collection is acquired.
/// </summary>
/// <returns>True if the persisted anchor collection is acquired, false otherwise.</returns>
public static bool IsPersistedAnchorCollectionAcquired()
{
return persistedAnchorCollection != System.IntPtr.Zero;
}
/// <summary>
/// Call this function when no any persisted anchor operations are needed.
/// Destroy the persisted anchor collection. If task is running, the task will be canceled.
/// </summary>
public static void ReleasePersistedAnchorCollection()
{
IntPtr tmp;
if (taskAcquirePAC != null)
{
taskAcquirePAC.Cancel();
taskAcquirePAC.Dispose();
taskAcquirePAC = null;
}
lock (asyncLock)
{
if (persistedAnchorCollection == System.IntPtr.Zero) return;
tmp = persistedAnchorCollection;
persistedAnchorCollection = System.IntPtr.Zero;
}
EnsureFeature();
Task.Run(async () =>
{
Debug.Log("ViveAnchor: ReleasePersistedAnchorCollection task is started.");
await semaphoreSlim.WaitAsync();
try
{
feature?.ReleasePersistedAnchorCollection(tmp);
}
finally
{
semaphoreSlim.Release();
}
Debug.Log("ViveAnchor: ReleasePersistedAnchorCollection task is done.");
});
}
private static XrResult CompletePA(IntPtr future) {
Debug.Log("AnchorManager: CompletePA");
var ret = feature.PersistSpatialAnchorComplete(future, out var completion);
if (ret == XrResult.XR_SUCCESS)
{
return completion.futureResult;
}
else
{
Debug.LogError("AcquirePersistedAnchorCollection failed: " + ret);
}
return ret;
}
/// <summary>
/// Persist an anchor with the given name. The persistanceAnchorName should be unique.
/// The persistance might fail if the anchor is not trackable. Check the result from the task.
/// </summary>
/// <param name="anchor">The anchor instance.</param>
/// <param name="persistanceAnchorName">The name of the persisted anchor.</param>
/// <param name="cts">PersistAnchor may take time. If you want to cancel it, use cts.</param>
/// <returns>The task to get persisted anchor's result.</returns>
public static FutureTask<XrResult> PersistAnchor(Anchor anchor, string persistanceAnchorName)
{
EnsureFeature();
EnsureCollection();
if (string.IsNullOrEmpty(persistanceAnchorName))
throw new ArgumentException("The persistanceAnchorName should not be empty.");
var name = new XrSpatialAnchorNameHTC(persistanceAnchorName);
var ret = feature.PersistSpatialAnchorAsync(persistedAnchorCollection, anchor.GetXrSpace(), name, out IntPtr future);
if (ret == XrResult.XR_SUCCESS)
{
// If no auto complete, you can cancel the task and no need to free resouce.
// Once it completed, you need handle the result.
return new FutureTask<XrResult>(future, CompletePA, 10, autoComplete: false);
}
return FutureTask<XrResult>.FromResult(ret);
}
/// <summary>
/// Unpersist the anchor by the name. The anchor created from persisted anchor will still be trackable.
/// </summary>
/// <param name="persistanceAnchorName">The name of the persisted anchor to be removed.</param>
/// <returns>The result of the operation.</returns>
public static XrResult UnpersistAnchor(string persistanceAnchorName)
{
EnsureFeature();
EnsureCollection();
if (string.IsNullOrEmpty(persistanceAnchorName))
throw new ArgumentException("The persistanceAnchorName should not be empty.");
var name = new XrSpatialAnchorNameHTC(persistanceAnchorName);
var ret = feature.UnpersistSpatialAnchor(persistedAnchorCollection, name);
return ret;
}
/// <summary>
/// Get the number of persisted anchors.
/// </summary>
/// <param name="count">Output parameter to hold the number of persisted anchors.</param>
/// <returns>The result of the operation.</returns>
public static XrResult GetNumberOfPersistedAnchors(out int count)
{
EnsureFeature();
EnsureCollection();
XrSpatialAnchorNameHTC[] xrNames = null;
uint xrCount = 0;
XrResult ret = feature.EnumeratePersistedAnchorNames(persistedAnchorCollection, 0, ref xrCount, ref xrNames);
if (ret != XrResult.XR_SUCCESS)
count = 0;
else
count = (int)xrCount;
return ret;
}
/// <summary>
/// List all persisted anchors.
/// </summary>
/// <param name="names">Output parameter to hold the names of the persisted anchors.</param>
/// <returns>The result of the operation.</returns>
public static XrResult EnumeratePersistedAnchorNames(out string[] names)
{
EnsureFeature();
EnsureCollection();
XrSpatialAnchorNameHTC[] xrNames = null;
uint countOut = 0;
uint countIn = 0;
XrResult ret = feature.EnumeratePersistedAnchorNames(persistedAnchorCollection, countIn, ref countOut, ref xrNames);
if (ret != XrResult.XR_SUCCESS)
{
names = null;
return ret;
}
// If Insufficient size, try again.
do
{
countIn = countOut;
xrNames = new XrSpatialAnchorNameHTC[countIn];
ret = feature.EnumeratePersistedAnchorNames(persistedAnchorCollection, countIn, ref countOut, ref xrNames);
}
while (ret == XrResult.XR_ERROR_SIZE_INSUFFICIENT);
if (ret != XrResult.XR_SUCCESS)
{
names = null;
return ret;
}
names = new string[countIn];
for (int i = 0; i < countIn; i++)
{
string v = xrNames[i].ToString();
names[i] = v;
}
return ret;
}
private static (XrResult, Anchor) CompleteCreateSAfromPA(IntPtr future)
{
Debug.Log("AnchorManager: CompleteCreateSAfromPA");
var ret = feature.CreateSpatialAnchorFromPersistedAnchorComplete(future, out var completion);
if (ret == XrResult.XR_SUCCESS)
{
var anchor = new Anchor(completion.anchor);
anchor.isTrackable = true;
return (completion.futureResult, anchor);
}
else
{
Debug.LogError("CreateSpatialAnchorFromPersistedAnchor failed: " + ret);
return (ret, new Anchor(0));
}
}
/// <summary>
/// Create a spatial anchor from a persisted anchor. This will also mark the anchor as trackable.
/// </summary>
/// <param name="persistanceAnchorName">The name of the persisted anchor.</param>
/// <param name="spatialAnchorName">The name of the new spatial anchor.</param>
/// <param name="anchor">Output parameter to hold the new anchor instance.</param>
/// <returns>The result of the operation.</returns>
public static FutureTask<(XrResult, Anchor)> CreateSpatialAnchorFromPersistedAnchor(string persistanceAnchorName, string spatialAnchorName)
{
EnsureFeature();
EnsureCollection();
Debug.Log("AnchorManager: CreateSpatialAnchorFromPersistedAnchor: " + persistanceAnchorName + " -> " + spatialAnchorName);
if (string.IsNullOrEmpty(persistanceAnchorName) || string.IsNullOrEmpty(spatialAnchorName))
throw new ArgumentException("The persistanceAnchorName and spatialAnchorName should not be empty.");
var createInfo = new XrSpatialAnchorFromPersistedAnchorCreateInfoHTC() {
type = XrStructureType.XR_TYPE_SPATIAL_ANCHOR_FROM_PERSISTED_ANCHOR_CREATE_INFO_HTC,
persistedAnchorCollection = persistedAnchorCollection,
persistedAnchorName = new XrSpatialAnchorNameHTC(persistanceAnchorName),
spatialAnchorName = new XrSpatialAnchorNameHTC(spatialAnchorName)
};
var ret = feature.CreateSpatialAnchorFromPersistedAnchorAsync(createInfo, out var future);
if (ret == XrResult.XR_SUCCESS)
{
// If no auto complete, you can cancel the task and no need to free resouce.
// Once it completed, you need handle the result.
return new FutureTask<(XrResult, Anchor)>(future, CompleteCreateSAfromPA, 10, autoComplete: false);
}
else
{
return FutureTask<(XrResult, Anchor)>.FromResult((ret, new Anchor(0)));
}
}
/// <summary>
/// Clear all persisted anchors. Those anchors created from or to the persisted anchor will still be trackable.
/// </summary>
/// <returns>The result of the operation.</returns>
public static XrResult ClearPersistedAnchors()
{
EnsureFeature();
EnsureCollection();
return feature.ClearPersistedAnchors(persistedAnchorCollection);
}
/// <summary>
/// Get the properties of the persisted anchor.
/// maxPersistedAnchorCount in XrPersistedAnchorPropertiesGetInfoHTC will be set to the max count of the persisted anchor.
/// </summary>
/// <param name="properties">Output parameter to hold the properties of the persisted anchor.</param>
/// <returns>The result of the operation.</returns>
public static XrResult GetPersistedAnchorProperties(out XrPersistedAnchorPropertiesGetInfoHTC properties)
{
EnsureFeature();
EnsureCollection();
return feature.GetPersistedAnchorProperties(persistedAnchorCollection, out properties);
}
/// <summary>
/// Export the persisted anchor to a buffer. The buffer can be used to import the anchor later or save it to a file.
/// Export takes time, so it is an async function. The buffer will be null if the export failed.
/// </summary>
/// <param name="persistanceAnchorName">The name of the persisted anchor to be exported.</param>
/// <param name="buffer">Output parameter to hold the buffer containing the exported anchor.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task<(XrResult, string, byte[])> ExportPersistedAnchor(string persistanceAnchorName)
{
EnsureFeature();
EnsureCollection();
if (string.IsNullOrEmpty(persistanceAnchorName))
return Task.FromResult<(XrResult, string, byte[])>((XrResult.XR_ERROR_HANDLE_INVALID, "", null));
var name = new XrSpatialAnchorNameHTC(persistanceAnchorName);
return Task.Run(async () =>
{
Debug.Log($"ExportPersistedAnchor({persistanceAnchorName}) task is started.");
XrResult ret = XrResult.XR_ERROR_VALIDATION_FAILURE;
await semaphoreSlim.WaitAsync();
try
{
lock (asyncLock)
{
if (persistedAnchorCollection == System.IntPtr.Zero)
{
return (XrResult.XR_ERROR_HANDLE_INVALID, "", null);
}
}
ret = feature.ExportPersistedAnchor(persistedAnchorCollection, name, out var buffer);
Debug.Log($"ExportPersistedAnchor({persistanceAnchorName}) task is done. ret=" + ret);
lock (asyncLock)
{
if (ret != XrResult.XR_SUCCESS)
{
buffer = null;
return (ret, "", null);
}
return (ret, persistanceAnchorName, buffer);
}
}
finally
{
semaphoreSlim.Release();
}
});
}
/// <summary>
/// Import the persisted anchor from a buffer. The buffer should be created by ExportPersistedAnchor.
/// Import takes time, so it is an async function. Check imported anchor by EnumeratePersistedAnchorNames.
/// </summary>
/// <param name="buffer">The buffer containing the persisted anchor data.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task<XrResult> ImportPersistedAnchor(byte[] buffer) {
EnsureFeature();
EnsureCollection();
return Task.Run(async () =>
{
Debug.Log($"ImportPersistedAnchor task is started.");
XrResult ret = XrResult.XR_ERROR_VALIDATION_FAILURE;
await semaphoreSlim.WaitAsync();
try
{
lock (asyncLock)
{
if (persistedAnchorCollection == System.IntPtr.Zero)
return XrResult.XR_ERROR_HANDLE_INVALID;
ret = feature.ImportPersistedAnchor(persistedAnchorCollection, buffer);
return ret;
}
}
finally
{
semaphoreSlim.Release();
Debug.Log($"ImportPersistedAnchor task is done. ret=" + ret);
}
});
}
/// <summary>
/// Get the persisted anchor name from the buffer. The buffer should be created by ExportPersistedAnchor.
/// </summary>
/// <returns>True if the name is successfully retrieved, false otherwise.</returns>
public static bool GetPersistedAnchorNameFromBuffer(byte[] buffer, out string name)
{
EnsureFeature();
EnsureCollection();
var ret = feature.GetPersistedAnchorNameFromBuffer(persistedAnchorCollection, buffer, out var xrName);
if (ret == XrResult.XR_SUCCESS)
name = xrName.ToString();
else
name = "";
return ret == XrResult.XR_SUCCESS;
}
/// <summary>
/// Anchor is a named Space. It can be used to create a spatial anchor, or get the anchor's name.
/// After use, you should call Dispose() to release the anchor.
/// IsTrackable is true if the anchor is created persisted anchor or created from persisted anchor.
/// IsPersisted is true if the anchor is ever persisted.
/// </summary>
public class Anchor : VIVE.OpenXR.Feature.Space
{
@@ -152,29 +613,60 @@ namespace VIVE.OpenXR.Toolkits.Anchor
}
}
internal bool isTrackable = false;
/// <summary>
/// If the anchor is created persisted anchor or created from persisted anchor, it will be trackable.
/// </summary>
public bool IsTrackable => isTrackable;
internal bool isPersisted = false;
/// <summary>
/// If the anchor is ever persisted, it will be true.
/// </summary>
public bool IsPersisted => isPersisted;
internal Anchor(XrSpace anchor, string name) : base(anchor)
{
Debug.Log($"Anchor: new Anchor({anchor}, {name})"); // Remove this line later.
// Get the current tracking space.
this.name = name;
}
internal Anchor(XrSpace anchor) : base(anchor)
{
Debug.Log($"Anchor: new Anchor({anchor})"); // Remove this line later.
// Get the current tracking space.
name = GetSpatialAnchorName();
}
internal Anchor(Anchor other) : base(other.space)
{
// Get the current tracking space.
name = other.name;
isTrackable = other.isTrackable;
isPersisted = other.isPersisted;
}
/// <summary>
/// Get the anchor's name by using this anchor's handle, instead of the anchor's Name. This will update the anchor's Name.
/// </summary>
/// <returns></returns>
/// <returns>Anchor's name. Always return non null string.</returns>
public string GetSpatialAnchorName()
{
AnchorManager.CheckFeature();
if (space == 0)
{
Debug.LogError("Anchor: GetSpatialAnchorName: The anchor is invalid.");
return "";
}
AnchorManager.EnsureFeature();
if (AnchorManager.GetSpatialAnchorName(this, out string name))
return name;
return null;
Debug.LogError("Anchor: GetSpatialAnchorName: Failed to get Anchor name.");
return "";
}
}
}
}
}