Files
VIVE-OpenXR-Unity/com.htc.upm.vive.openxr/Samples~/Samples/Samples/Anchor/Scripts/AnchorTestHandle.cs
2025-01-08 10:28:35 +08:00

1469 lines
54 KiB
C#

// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR;
using VIVE.OpenXR.Feature;
using VIVE.OpenXR.Toolkits;
using VIVE.OpenXR.Toolkits.Anchor;
namespace VIVE.OpenXR.Samples.Anchor
{
/// <summary>
/// This script is used to test AnchorManager.
///
/// Every frame, it will check if persisted anchors are existed.
/// If existed, it will create anchors from them. If the anchor is existed, it will dispose it first.
///
/// Anchors created by AnchorManager.CreateAnchor() didn't have tracking ability.
/// Only Anchors created by AnchorManager.CreateSpatialAnchorFromPersistedAnchor() have tracking ability.
/// If Anchors has tracking ability, it will update pose over time.
///
/// The panchorObj1 and panchorObj2 are used to show the pose of the trackable anchors.
///
/// </summary>
public class AnchorTestHandle : MonoBehaviour
{
public Transform rig;
public Transform anchorPoseD;
public Transform anchorPose1;
public Transform anchorPose2;
public Transform obj;
public GameObject panchorObj1;
public GameObject panchorObj2;
public AnchorManager.Anchor anchor1;
public AnchorManager.Anchor anchor2;
string anchor1FromPA;
string anchor2FromPA;
public TextMeshProUGUI statusResponse;
public TextMeshProUGUI statusOrigin;
public TextMeshProUGUI statusAnchor;
public Button btnCreateAnchor1;
public Button btnCreateAnchor2;
public Button btnFollowAnchor1;
public Button btnFollowAnchor2;
public Button btnResetObj;
public Button btnClearSpatialAnchors;
public Button btnPersistAnchor1;
public Button btnPersistAnchor2;
public Button btnUnpersistAnchor1;
public Button btnUnpersistAnchor2;
public Button btnClearPersistedAnchors;
public Button btnAcquirePAC;
public Button btnReleasePAC;
public Button btnExportAll;
public Button btnImportAll;
public Button btnClearExportedAnchors;
public Button btnFloor;
public Button btnDevice;
public Button btnReloadScene;
public Button btnQuitApp;
// For Editor test
public bool testCreateAnchor1;
public bool testCreateAnchor2;
public bool testFollowAnchor1;
public bool testFollowAnchor2;
public bool testResetObj;
public bool testClearSpatialAnchors;
public bool testCreatePAC;
public bool testDestroyPAC;
public bool testPersistAnchor1;
public bool testPersistAnchor2;
public bool testUnpersistAnchor1;
public bool testUnpersistAnchor2;
public bool testClearPersistedAnchors;
public bool testExportAll;
public bool testImportAll;
public bool testClearExportedAnchors;
public bool testFloor;
public bool testDevice;
public bool testReloadScene;
public XRInputSubsystem xrInputSubsystem;
private int lastPACount = -1;
private int lastFoundExportedFilesCount = -1;
private FutureTask<(XrResult, IntPtr)> taskPAC = null;
bool isAnchorSupported;
bool isPAnchorSupported;
bool isPACollectionAcquired;
// 持 means "hold", "persisted".
const string utf8Word = "持";
// In Update, check enumerated spatial anchors to update these flags
bool hasPersistedAnchor1;
string persistedAnchor1Name;
bool hasPersistedAnchor2;
string persistedAnchor2Name;
// Any anchor file in the app's local storage set flag to true
bool hasPersistedAnchorFiles;
bool doesUIInteractionsNeedUpdated = false;
float lastUpdateTime = 0;
readonly FutureTaskManager<XrSpace, XrResult> tmPA = new FutureTaskManager<XrSpace, XrResult>();
readonly FutureTaskManager<string, (XrResult, AnchorManager.Anchor)> tmFPA = new FutureTaskManager<string, (XrResult, AnchorManager.Anchor)>();
readonly List<Task> tasks = new List<Task>();
void GetXRInputSubsystem()
{
List<XRInputSubsystem> xrSubsystemList = new List<XRInputSubsystem>();
SubsystemManager.GetSubsystems(xrSubsystemList);
foreach (var xrSubsystem in xrSubsystemList)
{
if (xrSubsystem.running)
{
xrInputSubsystem = xrSubsystem;
break;
}
}
}
private void OnEnable()
{
btnCreateAnchor1.onClick.AddListener(OnCreateAnchor1);
btnCreateAnchor2.onClick.AddListener(OnCreateAnchor2);
btnFollowAnchor1.onClick.AddListener(OnFollowAnchor1);
btnFollowAnchor2.onClick.AddListener(OnFollowAnchor2);
btnResetObj.onClick.AddListener(OnResetObj);
btnClearSpatialAnchors.onClick.AddListener(OnClearAllAnchors);
btnAcquirePAC.onClick.AddListener(OnAcquirePersistedAnchorCollection);
btnReleasePAC.onClick.AddListener(OnReleasePersistedAnchorCollection);
btnPersistAnchor1.onClick.AddListener(OnPersistAnchor1);
btnPersistAnchor2.onClick.AddListener(OnPersistAnchor2);
btnUnpersistAnchor1.onClick.AddListener(OnUnpersistAnchor1);
btnUnpersistAnchor2.onClick.AddListener(OnUnpersistAnchor2);
btnClearPersistedAnchors.onClick.AddListener(OnClearPersistedAnchors);
btnExportAll.onClick.AddListener(OnExportAll);
btnImportAll.onClick.AddListener(OnImportAll);
btnClearExportedAnchors.onClick.AddListener(OnClearExportedAnchors);
btnFloor.onClick.AddListener(OnFloor);
btnDevice.onClick.AddListener(OnDevice);
btnReloadScene.onClick.AddListener(OnReloadScene);
btnQuitApp.onClick.AddListener(Application.Quit);
}
private void OnAcquirePersistedAnchorCollection()
{
if (AnchorManager.IsPersistedAnchorCollectionAcquired()) return;
if (taskPAC != null) return;
taskPAC = AnchorManager.AcquirePersistedAnchorCollection();
// Acturally, it will auto complete by default. Just make sure we can continue with AutoCompleteTask
taskPAC.AutoComplete();
taskPAC.AutoCompleteTask.ContinueWith((act) => {
taskPAC = null;
CheckSupported();
UINeedUpdate();
});
}
private void OnReleasePersistedAnchorCollection()
{
// the taskPAC will be canceled in ReleasePersistedAnchorCollection
taskPAC = null;
anchor1FromPA = null;
anchor2FromPA = null;
hasPersistedAnchor1 = false;
hasPersistedAnchor2 = false;
persistedAnchor1Name = null;
persistedAnchor2Name = null;
//hasPersistedAnchorFiles = false;
AnchorManager.ReleasePersistedAnchorCollection();
CheckSupported();
UINeedUpdate();
}
private void OnDisable()
{
// Remove listeners
btnCreateAnchor1.onClick.RemoveListener(OnCreateAnchor1);
btnCreateAnchor2.onClick.RemoveListener(OnCreateAnchor2);
btnFollowAnchor1.onClick.RemoveListener(OnFollowAnchor1);
btnFollowAnchor2.onClick.RemoveListener(OnFollowAnchor2);
btnResetObj.onClick.RemoveListener(OnResetObj);
btnClearSpatialAnchors.onClick.RemoveListener(OnClearAllAnchors);
btnAcquirePAC.onClick.RemoveListener(OnAcquirePersistedAnchorCollection);
btnReleasePAC.onClick.RemoveListener(OnReleasePersistedAnchorCollection);
btnPersistAnchor1.onClick.RemoveListener(OnPersistAnchor1);
btnPersistAnchor2.onClick.RemoveListener(OnPersistAnchor2);
btnUnpersistAnchor1.onClick.RemoveListener(OnUnpersistAnchor1);
btnUnpersistAnchor2.onClick.RemoveListener(OnUnpersistAnchor2);
btnClearPersistedAnchors.onClick.RemoveListener(OnClearPersistedAnchors);
btnExportAll.onClick.RemoveListener(OnExportAll);
btnImportAll.onClick.RemoveListener(OnImportAll);
btnClearExportedAnchors.onClick.RemoveListener(OnClearExportedAnchors);
btnFloor.onClick.RemoveListener(OnFloor);
btnDevice.onClick.RemoveListener(OnDevice);
btnReloadScene.onClick.RemoveListener(OnReloadScene);
btnQuitApp.onClick.RemoveListener(Application.Quit);
// No need to clear all persistance anchor. Reload scene can test the persist.
// Disable Persistance Anchor
AnchorManager.ReleasePersistedAnchorCollection();
// Dispose all anchors
OnClearAllAnchors();
// Dispose all tasks
foreach (var task in tasks)
{
task.Dispose();
}
tasks.Clear();
// Clear all task managers. It's readonly, so no need to dispose.
tmPA.Clear();
tmFPA.Clear();
}
private void OnDestroy()
{
// Dispose all anchors
OnClearAllAnchorsInner(true);
// Dispose all tasks
foreach (var task in tasks)
{
task.Dispose();
}
tasks.Clear();
// Clear all task managers. It's readonly, so no need to dispose.
tmPA.Clear();
tmFPA.Clear();
}
void UINeedUpdate()
{
doesUIInteractionsNeedUpdated = true;
}
void UpdateUIInteractions()
{
if ((Time.time - lastUpdateTime) > 2.0f)
{
lastUpdateTime = Time.time;
doesUIInteractionsNeedUpdated = true;
}
if (!doesUIInteractionsNeedUpdated)
return;
doesUIInteractionsNeedUpdated = false;
bool hasAnchor1 = anchor1 != null;
bool hasAnchor2 = anchor2 != null;
bool hasAnchor = hasAnchor1 || hasAnchor2;
// Create / Follow / Reset / Dispose
// If created, can created again.
// If no Anchor, cannot follow.
// however these interactions no need block
btnCreateAnchor1.interactable = isAnchorSupported && !hasPersistedAnchor1;
btnCreateAnchor2.interactable = isAnchorSupported && !hasPersistedAnchor2;
btnFollowAnchor1.interactable = hasAnchor1 && !anchor1.IsPersisted;
btnFollowAnchor2.interactable = hasAnchor2 && !anchor2.IsPersisted;
btnResetObj.interactable = true;
btnClearSpatialAnchors.interactable = hasAnchor;
// Create Persisted Anchor Collection / Destroy Persisted Anchor Collection
// If has created, can destroy
// If has not created, can create
btnAcquirePAC.interactable = isPAnchorSupported && !isPACollectionAcquired && taskPAC == null;
btnReleasePAC.interactable = isPAnchorSupported && isPACollectionAcquired;
// Persist / Unpersist / Clear
// If has anchor, can persist
// If has persisted anchor, can unpersist
// If has persisted anchor, can clear
btnPersistAnchor1.interactable = isPACollectionAcquired && hasAnchor1;
btnPersistAnchor2.interactable = isPACollectionAcquired && hasAnchor2;
btnUnpersistAnchor1.interactable = isPACollectionAcquired && hasPersistedAnchor1;
btnUnpersistAnchor2.interactable = isPACollectionAcquired && hasPersistedAnchor2;
btnClearPersistedAnchors.interactable = isPACollectionAcquired && (hasPersistedAnchor1 || hasPersistedAnchor2);
// Export / Import / Clear
// If has persisted anchor, can export
// If has exported anchor, can import
// If has exported anchor, can clear
btnExportAll.interactable = isPACollectionAcquired && (hasPersistedAnchor1 || hasPersistedAnchor2) && tasks.Count == 0;
btnImportAll.interactable = isPACollectionAcquired && hasPersistedAnchorFiles && tasks.Count == 0;
// Not to clear when exporting or importing
btnClearExportedAnchors.interactable = hasPersistedAnchorFiles && tasks.Count == 0;
string sa1 = "", sa2 = "";
if (hasAnchor1)
sa1 = "A1: " + anchor1.Name + (anchor1.IsTrackable ? " T" : "") + (anchor1.IsPersisted ? " P" : "") + "\n";
if (hasAnchor2)
sa2 = "A2: " + anchor2.Name + (anchor2.IsTrackable ? " T" : "") + (anchor2.IsPersisted ? " P" : "") + "\n";
statusAnchor.text = sa1 + sa2 +
(hasPersistedAnchor1 ? "PA1: " + persistedAnchor1Name + "\n" : "") +
(hasPersistedAnchor2 ? "PA2: " + persistedAnchor2Name + "\n" : "");
}
void CheckSupported()
{
isAnchorSupported = AnchorManager.IsSupported();
Debug.Log("AnchorTestHandle: Is Anchor supported: " + isAnchorSupported);
isPAnchorSupported = AnchorManager.IsPersistedAnchorSupported();
Debug.Log("AnchorTestHandle: Is Persisted Anchor supported: " + isPAnchorSupported);
isPACollectionAcquired = AnchorManager.IsPersistedAnchorCollectionAcquired();
Debug.Log("AnchorTestHandle: Is Persisted Anchor Collection acquired: " + isPACollectionAcquired);
}
IEnumerator Start()
{
CheckSupported();
UINeedUpdate();
UpdateUIInteractions();
yield return null; // yield and let Time.unscaledTime to be updated
float t = Time.unscaledTime;
while (xrInputSubsystem == null)
{
yield return null;
GetXRInputSubsystem();
if (Time.unscaledTime - t > 5)
{
#if UNITY_EDITOR
Debug.LogError("Get XRInputSubsystem timeout. Check if you acturally enable the OpenXR's Mock Runtime.");
#else
Debug.LogError("Get XRInputSubsystem timeout. Check if you acturally enable any VR runtime.");
#endif
statusResponse.text = "No VR runtime";
yield break;
}
}
// Check again
CheckSupported();
UINeedUpdate();
if (!isPAnchorSupported) yield break;
Debug.Log("AnchorTestHandle: AcquirePersistedAnchorCollection");
taskPAC = AnchorManager.AcquirePersistedAnchorCollection();
taskPAC.Debug = true;
while (!AnchorManager.IsPersistedAnchorCollectionAcquired())
{
Debug.Log("AnchorTestHandle: Wait PersistedAnchorCollection acquired");
yield return null;
}
taskPAC = null;
AnchorManager.GetPersistedAnchorProperties(out ViveAnchor.XrPersistedAnchorPropertiesGetInfoHTC properties);
Debug.Log("AnchorTestHandle: PersistAnchorProperties.maxPersistedAnchorCount=" + properties.maxPersistedAnchorCount);
CheckSupported();
Debug.Log("AnchorTestHandle: Start() finished");
UINeedUpdate();
UpdateUIInteractions();
}
public Pose GetRelatedPoseToRig(Transform t)
{
return new Pose(rig.InverseTransformPoint(t.position), Quaternion.Inverse(rig.rotation) * t.rotation);
}
string MakeAnchorName(string userName)
{
// userName means user defined name. Add frame count to make it unique.
return userName + " (" + Time.frameCount + ")";
}
string MakePersistedAnchorName(string anchorName)
{
// the anchor name support UTF-8 encoding.
return anchorName + utf8Word;
}
string MakeSpatialAnchorName(string persistedName)
{
// Remove the utf8Word from the anchor name
return persistedName.Substring(0, persistedName.Length - utf8Word.Length);
}
/// <summary>
/// Help create anchor by anchor manager
/// </summary>
/// <param name="relatedPose">pose related to camera rig</param>
/// <param name="name">the anchor's name</param>
/// <returns></returns>
AnchorManager.Anchor CreateAnchor(Pose relatedPose, string name)
{
if (!AnchorManager.IsSupported())
{
Debug.LogError("AnchorManager: Anchor is not supported.");
statusResponse.text = "Anchor is not supported.";
return null;
}
var anchor = AnchorManager.CreateAnchor(relatedPose, MakeAnchorName(name));
if (anchor == null)
{
statusResponse.text = "Create " + name + " failed";
Debug.LogError("Create " + name + " failed");
// Even error, still got. Use fake data.
return anchor;
}
else
{
string msg = "Create Anchor n=" + anchor.Name + " space=" + anchor.GetXrSpace() + " at p=" + relatedPose.position + " & r=" + relatedPose.rotation.eulerAngles;
statusResponse.text = msg;
Debug.Log(msg);
return anchor;
}
}
public void OnCreateAnchor1()
{
if (anchor1 != null)
{
anchor1.Dispose();
anchor1 = null;
}
anchor1 = CreateAnchor(GetRelatedPoseToRig(anchorPose1), "anchor1");
UINeedUpdate();
}
public void OnCreateAnchor2()
{
if (anchor2 != null)
{
anchor2.Dispose();
anchor2 = null;
}
anchor2 = CreateAnchor(GetRelatedPoseToRig(anchorPose2), "anchor2");
UINeedUpdate();
}
public void MoveObjToAnchor(AnchorManager.Anchor anchor)
{
if (!AnchorManager.IsSupported())
return;
if (anchor == null)
{
statusResponse.text = "anchor is null";
return;
}
if (AnchorManager.GetTrackingSpacePose(anchor, out Pose pose))
{
// Convert tracking space pose to world space pose
obj.position = rig.TransformPoint(pose.position);
obj.rotation = rig.rotation * pose.rotation;
statusResponse.text = "Obj move to " + anchor.GetSpatialAnchorName();
}
else
{
statusResponse.text = "Fail to get anchor's pose";
}
}
public void OnFollowAnchor1()
{
Debug.Log("AnchorTestHandle: OnFollowAnchor1()");
MoveObjToAnchor(anchor1);
}
public void OnFollowAnchor2()
{
Debug.Log("AnchorTestHandle: OnFollowAnchor2()");
MoveObjToAnchor(anchor2);
}
public void OnResetObj()
{
Debug.Log("AnchorTestHandle: OnResetObj()");
if (obj != null && anchorPoseD != null)
{
obj.position = anchorPoseD.position;
obj.rotation = anchorPoseD.rotation;
}
if (statusResponse != null)
statusResponse.text = "Obj move to default pose";
}
private void OnClearAllAnchorsInner(bool isDestroy = false)
{
Debug.Log("AnchorTestHandle: OnClearAllAnchors()");
try
{
if (!isDestroy)
{
// Not to touch object when destroy
if (obj != null && anchorPoseD != null)
{
obj.position = anchorPoseD.position;
obj.rotation = anchorPoseD.rotation;
}
if (statusResponse != null)
{
if (hasPersistedAnchor1 || hasPersistedAnchor2)
statusResponse.text = "Dispose spatial anchors but will create again by persisted";
else
statusResponse.text = "Dispose spatial anchors";
}
}
}
catch (System.Exception e)
{
Debug.LogError("AnchorTestHandle: OnClearAllAnchorsInner: " + e.Message);
}
if (anchor1 != null)
{
anchor1.Dispose();
anchor1 = null;
}
if (anchor2 != null)
{
anchor2.Dispose();
anchor2 = null;
}
}
public void OnClearAllAnchors()
{
OnClearAllAnchorsInner();
}
public void OnPersistAnchor1()
{
Debug.Log("AnchorTestHandle: OnPersistAnchor1()");
if (anchor1 == null)
{
statusResponse.text = "anchor1 is null";
return;
}
// Create new persisted anchor name by frame count. Not to follow the original anchor name.
var newName = MakePersistedAnchorName(MakeAnchorName("anchor1"));
Debug.Log($"AnchorTestHandle: Persist {anchor1.Name} to {newName}");
var task = tmPA.GetTask(anchor1.GetXrSpace());
if (task != null)
{
statusResponse.text = "Persist " + newName + " is running, please wait";
return;
}
task = AnchorManager.PersistAnchor(anchor1, newName);
tmPA.AddTask(anchor1.GetXrSpace(), task);
statusResponse.text = "Persist " + newName + " task is running, please wait";
}
public void OnPersistAnchor2()
{
Debug.Log("AnchorTestHandle: OnPersistAnchor2()");
if (anchor2 == null)
{
statusResponse.text = "anchor2 is null";
return;
}
// Create new persisted anchor name by frame count. Not to follow the original anchor name.
var newName = MakePersistedAnchorName(MakeAnchorName("anchor2"));
Debug.Log($"AnchorTestHandle: Persist {anchor2.Name} to {newName}");
var task = tmPA.GetTask(anchor2.GetXrSpace());
if (task != null)
{
statusResponse.text = "Persist " + newName + " is running, please wait";
return;
}
task = AnchorManager.PersistAnchor(anchor2, newName);
tmPA.AddTask(anchor2.GetXrSpace(), task);
statusResponse.text = "Persist " + newName + " task is running, please wait";
}
public void OnUnpersistAnchor1()
{
Debug.Log("AnchorTestHandle: OnUnpersistAnchor1()");
if (!hasPersistedAnchor1 || string.IsNullOrEmpty(persistedAnchor1Name))
{
statusResponse.text = "persist anchor1 not exist";
return;
}
var ret = AnchorManager.UnpersistAnchor(persistedAnchor1Name) == XrResult.XR_SUCCESS;
if (ret)
statusResponse.text = "Unpersist " + persistedAnchor1Name + " success";
else
statusResponse.text = "Unpersist " + persistedAnchor1Name + " failed: " + ret;
hasPersistedAnchor1 = false;
persistedAnchor1Name = null;
UINeedUpdate();
}
public void OnUnpersistAnchor2()
{
Debug.Log("AnchorTestHandle: OnUnpersistAnchor2()");
if (!hasPersistedAnchor2 || string.IsNullOrEmpty(persistedAnchor2Name))
{
statusResponse.text = "persist anchor2 not exist";
return;
}
var ret = AnchorManager.UnpersistAnchor(persistedAnchor2Name) == XrResult.XR_SUCCESS;
if (ret)
statusResponse.text = "Unpersist " + persistedAnchor2Name + " success";
else
statusResponse.text = "Unpersist " + persistedAnchor2Name + " failed: " + ret;
hasPersistedAnchor2 = false;
persistedAnchor2Name = null;
UINeedUpdate();
}
public void OnClearPersistedAnchors()
{
Debug.Log("AnchorTestHandle: OnClearPersistedAnchors()");
var ret = AnchorManager.ClearPersistedAnchors() == XrResult.XR_SUCCESS;
if (ret)
{
hasPersistedAnchor1 = false;
hasPersistedAnchor2 = false;
persistedAnchor1Name = null;
persistedAnchor2Name = null;
statusResponse.text = "Clear persisted anchors success";
}
else
statusResponse.text = "Clear persisted anchors failed: " + ret;
}
public IEnumerator WaitExportFinish()
{
while (tasks.Count > 0)
{
Debug.Log("AnchorTestHandle: WaitExportFinish: " + tasks.Count);
foreach (var task in tasks)
{
if (task.IsCompleted)
{
Task<(XrResult, string, byte[])> t = task as Task<(XrResult, string, byte[])>;
if (t == null)
{
tasks.Remove(task);
break;
}
if (t.Result.Item1 == XrResult.XR_SUCCESS)
{
// write to file
string path = GetExportPath();
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
string paname = t.Result.Item2;
if (!paname.EndsWith(utf8Word))
throw new System.Exception("Anchor name is not ended with " + utf8Word);
paname = paname.Substring(0, paname.Length - 1);
string file = Path.Combine(path, paname + ".pa");
Debug.Log("AnchorTestHandle: Data length: " + t.Result.Item3.Length);
File.WriteAllBytes(file, t.Result.Item3);
Debug.Log("AnchorTestHandle: Exported anchor to " + file);
statusResponse.text = "Exported anchor to " + file;
}
else
{
Debug.LogError("AnchorTestHandle: Export persisted anchor failed: " + t.Result.Item1);
statusResponse.text = "Export persisted anchor failed: " + t.Result.Item1;
}
tasks.Remove(task);
break;
}
}
yield return null;
}
UINeedUpdate();
statusResponse.text = "All persisted anchors exported";
}
public void OnExportAll()
{
Debug.Log("AnchorTestHandle: OnExportAll()");
// Create export folder
string path = GetExportPath();
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
OnClearExportedAnchors();
if (tasks.Count > 0)
{
statusResponse.text = "Other task is running, please wait";
return;
}
statusResponse.text = "Exporting anchors";
if (hasPersistedAnchor1)
{
Task t = AnchorManager.ExportPersistedAnchor(persistedAnchor1Name);
tasks.Add(t);
}
if (hasPersistedAnchor2)
{
Task t = AnchorManager.ExportPersistedAnchor(persistedAnchor2Name);
tasks.Add(t);
}
// Has tasks, export/import/clear buttons should be disabled
UINeedUpdate();
StartCoroutine(WaitExportFinish());
}
public IEnumerator WaitImportFinish()
{
while (tasks.Count > 0)
{
foreach (var task in tasks)
{
if (task.IsCompleted)
{
tasks.Remove(task);
break;
}
}
yield return null;
}
// No tasks, export/import/clear buttons should be enabled
UINeedUpdate();
statusResponse.text = "All persisted anchors imported";
}
public void OnImportAll()
{
Debug.Log("AnchorTestHandle: OnImportAll()");
if (tasks.Count > 0)
{
statusResponse.text = "Other task is running, please wait";
return;
}
var list = GetExportedFilesList();
if (list == null)
{
statusResponse.text = "No exported anchor files";
return;
}
string names = "";
try
{
foreach (var file in list)
{
Task tread = new Task(async () =>
{
try
{
Debug.Log("AnchorTestHandle: Read file: " + file);
var data = File.ReadAllBytes(file);
Debug.Log("AnchorTestHandle: data length: " + data.Length);
AnchorManager.GetPersistedAnchorNameFromBuffer(data, out string name);
Debug.Log("AnchorTestHandle: data is from " + name);
names += name + "\n";
Task<XrResult> importTask = AnchorManager.ImportPersistedAnchor(data);
await importTask;
if (importTask.Result == XrResult.XR_SUCCESS)
Debug.Log("AnchorTestHandle: Import persisted anchor success");
else
Debug.LogError("AnchorTestHandle: Import persisted anchor failed: " + importTask.Result);
}
catch (System.Exception e)
{
Debug.LogError("AnchorTestHandle: Read file " + file + " failed: " + e.Message);
statusResponse.text = "Read file " + file + " failed: " + e.Message;
}
});
tread.Start();
tasks.Add(tread);
}
}
catch (System.Exception e)
{
Debug.LogError("AnchorTestHandle: Import persisted anchor failed: " + e.Message);
statusResponse.text = "Import persisted anchor failed: " + e.Message;
}
// Has tasks, export/import/clear buttons should be disabled
UINeedUpdate();
statusResponse.text = "Importing " + list.Count + "persist anchor(s):\n" + names;
StartCoroutine(WaitImportFinish());
}
public void OnClearExportedAnchors()
{
Debug.Log("AnchorTestHandle: OnClearExportedAnchors()");
var list = GetExportedFilesList();
if (list != null)
{
foreach (var file in list)
{
Debug.Log("AnchorTestHandle: Delete file " + file);
File.Delete(file);
}
hasPersistedAnchorFiles = false;
statusResponse.text = "Del " + list.Count + " files";
}
UINeedUpdate();
}
string GetExportPath()
{
return Path.Combine(Application.persistentDataPath, "Anchors");
}
List<string> GetExportedFilesList()
{
// Get persistance storage path
// Check if any anchor file in the app's local storage
string path = GetExportPath();
if (!Directory.Exists(path)) return null;
string[] files = Directory.GetFiles(path);
List<string> anchorFiles = new List<string>();
if (files.Length == 0) return null;
foreach (string file in files ) {
if (file.EndsWith(".pa"))
anchorFiles.Add(file);
}
// Print log if files count changed
if (anchorFiles.Count != lastFoundExportedFilesCount)
{
Debug.Log("AnchorTestHandle: Found " + anchorFiles.Count + " anchor files");
foreach (var file in anchorFiles)
Debug.Log("AnchorTestHandle: " + file);
lastFoundExportedFilesCount = anchorFiles.Count;
}
hasPersistedAnchorFiles = anchorFiles.Count > 0;
return anchorFiles;
}
public void CheckExportedFiles()
{
if (hasPersistedAnchorFiles) return;
GetExportedFilesList();
}
public void EnumeratePersistedAnchors()
{
if (!isAnchorSupported || !isPAnchorSupported || !isPACollectionAcquired)
return;
if (AnchorManager.GetNumberOfPersistedAnchors(out int count) != XrResult.XR_SUCCESS) {
Debug.LogError("AnchorTestHandle: GetNumberOfPersistedAnchors failed");
return;
}
var tmpHasPA1 = hasPersistedAnchor1;
var tmpHasPA2 = hasPersistedAnchor2;
hasPersistedAnchor1 = false;
hasPersistedAnchor2 = false;
persistedAnchor1Name = null;
persistedAnchor2Name = null;
if (count != lastPACount)
{
Debug.Log("AnchorTestHandle: GetNumberOfPersistedAnchors=" + count);
lastPACount = count;
}
if (count == 0)
return;
// Enumerate persisted anchors
if (AnchorManager.EnumeratePersistedAnchorNames(out string[] names) != XrResult.XR_SUCCESS)
{
Debug.LogError("AnchorTestHandle: EnumeratePersistedAnchorNames failed");
return;
}
foreach (var name in names)
{
if (name.EndsWith(utf8Word))
{
if (name.StartsWith("anchor1"))
{
if (!tmpHasPA1)
Debug.Log("AnchorTestHandle: Found persisted anchor1: " + name);
hasPersistedAnchor1 = true;
persistedAnchor1Name = name;
}
else if (name.StartsWith("anchor2"))
{
if (!tmpHasPA2)
Debug.Log("AnchorTestHandle: Found persisted anchor2: " + name);
hasPersistedAnchor2 = true;
persistedAnchor2Name = name;
}
}
// Others don't care
}
if (hasPersistedAnchor1 != tmpHasPA1 && hasPersistedAnchor1 == false)
Debug.Log("AnchorTestHandle: Lost persisted anchor1");
if (hasPersistedAnchor2 != tmpHasPA2 && hasPersistedAnchor2 == false)
Debug.Log("AnchorTestHandle: Lost persisted anchor2");
if (hasPersistedAnchor1 != tmpHasPA1 || hasPersistedAnchor2 != tmpHasPA2)
UINeedUpdate();
}
/// <summary>
/// If both anchor and persited anchor are existed, check if the anchor is created from the persisted anchor. If not, destroy the anchor.
/// </summary>
/// <param name="hasPA">Persisted anchor exist</param>
/// <param name="paName">Persisted anchor's name</param>
/// <param name="anchor">if anchor is not null, anchor exist</param>
/// <param name="anchorFromPA">If the anchor is from a persisted anchor, the persisted anchor's name</param>
/// <returns>If the anchor need be destroyed</returns>
bool NeedDestroy(bool hasPA, string paName, AnchorManager.Anchor anchor, string anchorFromPA) {
// If no persisted anchor, no need to destroy the anchor.
if (!hasPA)
return false;
// If no anchor, no need to destroy the anchor.
if (anchor == null)
return false;
// If the anchor is created from the same persisted anchor, no need to destroy it.
if (anchorFromPA == paName)
return false;
// If the anchor is created from other persisted anchor, need to destroy it.
return true;
}
bool NeedCancelCFPA(bool hasPA, string paName, FutureTask<(XrResult, AnchorManager.Anchor)> task, string anchorFromPA) {
// If task is already created from the same persisted anchor, no need to cancel it.
if (anchorFromPA == paName)
return false;
// If task is already created from the same persisted anchor, no need to cancel it.
// If no persisted anchor, keep the task.
return hasPA && task != null;
}
// If persisted anchors are existed, create anchor from them.
void UpdateAnchorsIfPersistExist()
{
// If both anchor and persited anchor are existed, check if the anchor is created from the persisted anchor. If not, dispose the anchor.
// Check if the anchor is realted to the persisted anchor.
bool a1NeedDispose = NeedDestroy(hasPersistedAnchor1, persistedAnchor1Name, anchor1, anchor1FromPA);
bool a2NeedDispose = NeedDestroy(hasPersistedAnchor2, persistedAnchor2Name, anchor2, anchor2FromPA);
if (a1NeedDispose)
{
Debug.Log("AnchorTestHandle: Dispose existed anchor1 because we want to create it from persisted anchor");
anchor1.Dispose();
anchor1 = null;
anchor1FromPA = "";
}
if (a2NeedDispose)
{
Debug.Log("AnchorTestHandle: Dispose existed anchor2 because we want to create it from persisted anchor");
anchor2.Dispose();
anchor2 = null;
anchor2FromPA = "";
}
// Use TaskManager to keep the tasks
var task1 = tmFPA.GetTask(anchor1FromPA);
var task2 = tmFPA.GetTask(anchor2FromPA);
// Check if the task is related to the persisted anchor.
bool cfpa1NeedCancel = NeedCancelCFPA(hasPersistedAnchor1, persistedAnchor1Name, task1, anchor1FromPA);
bool cfpa2NeedCancel = NeedCancelCFPA(hasPersistedAnchor2, persistedAnchor2Name, task2, anchor2FromPA);
if (cfpa1NeedCancel)
{
tmFPA.RemoveTask(task1);
task1 = null;
anchor1FromPA = "";
}
if (cfpa2NeedCancel)
{
tmFPA.RemoveTask(task2);
task2 = null;
anchor2FromPA = "";
}
bool needCreateAnchor1 = hasPersistedAnchor1 && anchor1 == null && task1 == null && !string.IsNullOrEmpty(persistedAnchor1Name);
if (needCreateAnchor1)
{
task1 = AnchorManager.CreateSpatialAnchorFromPersistedAnchor(persistedAnchor1Name, MakeSpatialAnchorName(persistedAnchor1Name));
tmFPA.AddTask(persistedAnchor1Name, task1);
anchor1FromPA = persistedAnchor1Name;
}
bool needCreateAnchor2 = hasPersistedAnchor2 && anchor2 == null && task2 == null && !string.IsNullOrEmpty(persistedAnchor2Name);
if (needCreateAnchor2)
{
task2 = AnchorManager.CreateSpatialAnchorFromPersistedAnchor(persistedAnchor2Name, MakeSpatialAnchorName(persistedAnchor2Name));
tmFPA.AddTask(persistedAnchor2Name, task2);
anchor2FromPA = persistedAnchor2Name;
}
if (task1 != null && task1.IsPollCompleted)
{
var result = task1.Complete();
tmFPA.RemoveTask(task1);
if (result.Item1 != XrResult.XR_SUCCESS)
{
Debug.LogError("AnchorTestHandle: Create anchor1 from persisted anchor failed");
statusResponse.text = "Create anchor1 from persisted anchor failed";
anchor1FromPA = null;
}
else
{
anchor1 = result.Item2;
}
}
if (task2 != null && task2.IsPollCompleted)
{
var result = task2.Complete();
tmFPA.RemoveTask(task2);
if (result.Item1 != XrResult.XR_SUCCESS)
{
Debug.LogError("AnchorTestHandle: Create anchor2 from persisted anchor failed");
statusResponse.text = "Create anchor2 from persisted anchor failed";
anchor2FromPA = null;
}
else
{
anchor2 = result.Item2;
}
}
}
float lastUpdateTimeTAP = -1;
void UpdateTrackableAnchorsPose()
{
bool panchorObj1ShouldBeSeen = false;
bool panchorObj2ShouldBeSeen = false;
if (anchor1 == null || !anchor1.IsTrackable)
panchorObj1ShouldBeSeen = false;
if (anchor2 == null || !anchor2.IsTrackable)
panchorObj2ShouldBeSeen = false;
if (anchor1 != null && anchor1.IsTrackable)
{
if (AnchorManager.GetTrackingSpacePose(anchor1, out Pose pose))
{
panchorObj1ShouldBeSeen = true;
panchorObj1.transform.position = rig.TransformPoint(pose.position);
panchorObj1.transform.rotation = rig.rotation * pose.rotation;
}
else
{
panchorObj1ShouldBeSeen = false;
Debug.LogError("AnchorTestHandle: GetTrackingSpacePose for anchor1 is failed");
}
}
if (anchor2 != null && anchor2.IsTrackable)
{
if (AnchorManager.GetTrackingSpacePose(anchor2, out Pose pose))
{
panchorObj2ShouldBeSeen = true;
panchorObj2.transform.position = rig.TransformPoint(pose.position);
panchorObj2.transform.rotation = rig.rotation * pose.rotation;
}
else
{
panchorObj2ShouldBeSeen = false;
Debug.LogError("AnchorTestHandle: GetTrackingSpacePose for anchor2 is failed");
}
}
var switchObj1 = panchorObj1ShouldBeSeen != panchorObj1.activeInHierarchy;
if (switchObj1)
panchorObj1.SetActive(panchorObj1ShouldBeSeen);
var switchObj2 = panchorObj2ShouldBeSeen != panchorObj2.activeInHierarchy;
if (switchObj2)
panchorObj2.SetActive(panchorObj2ShouldBeSeen);
// force update log once if switchObj1 or switchObj2
if (switchObj1 || switchObj2)
lastUpdateTimeTAP = -1;
if (panchorObj1ShouldBeSeen || panchorObj2ShouldBeSeen)
{
if (Time.time - lastUpdateTimeTAP > 0.25f)
{
StackTraceLogType stackTraceLogTypeOrigin = Application.GetStackTraceLogType(LogType.Log);
Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.None);
if (panchorObj1ShouldBeSeen)
{
var pos = panchorObj1.transform.localPosition;
var rot = panchorObj1.transform.localRotation.eulerAngles;
Debug.Log("AnchorTestHandle: panchorObj1 pos=" + pos.x + "," + pos.y + "," + pos.z);
Debug.Log("AnchorTestHandle: panchorObj1 rot=" + rot.x + "," + rot.y + "," + rot.z);
}
if (panchorObj2ShouldBeSeen)
{
var pos = panchorObj2.transform.localPosition;
var rot = panchorObj2.transform.localRotation.eulerAngles;
Debug.Log("AnchorTestHandle: panchorObj2 pos=" + pos.x + "," + pos.y + "," + pos.z);
Debug.Log("AnchorTestHandle: panchorObj2 rot=" + rot.x + "," + rot.y + "," + rot.z);
}
Application.SetStackTraceLogType(LogType.Log, stackTraceLogTypeOrigin);
lastUpdateTimeTAP = Time.time;
}
}
}
void PerformEditorTest()
{
if (testCreateAnchor1)
{
testCreateAnchor1 = false;
OnCreateAnchor1();
}
if (testCreateAnchor2)
{
testCreateAnchor2 = false;
OnCreateAnchor2();
}
if (testFollowAnchor1)
{
testFollowAnchor1 = false;
OnFollowAnchor1();
}
if (testFollowAnchor2)
{
testFollowAnchor2 = false;
OnFollowAnchor2();
}
if (testResetObj)
{
testResetObj = false;
OnResetObj();
}
if (testClearSpatialAnchors)
{
testClearSpatialAnchors = false;
OnClearAllAnchors();
}
if (testCreatePAC)
{
testCreatePAC = false;
OnAcquirePersistedAnchorCollection();
}
if (testDestroyPAC)
{
testDestroyPAC = false;
OnReleasePersistedAnchorCollection();
}
if (testPersistAnchor1)
{
testPersistAnchor1 = false;
OnPersistAnchor1();
}
if (testPersistAnchor2)
{
testPersistAnchor2 = false;
OnPersistAnchor2();
}
if (testExportAll)
{
testExportAll = false;
OnExportAll();
}
if (testImportAll)
{
testImportAll = false;
OnImportAll();
}
if (testClearExportedAnchors)
{
testClearExportedAnchors = false;
OnClearExportedAnchors();
}
if (testUnpersistAnchor1)
{
testUnpersistAnchor1 = false;
OnUnpersistAnchor1();
}
if (testUnpersistAnchor2)
{
testUnpersistAnchor2 = false;
OnUnpersistAnchor2();
}
if (testClearPersistedAnchors)
{
testClearPersistedAnchors = false;
OnClearPersistedAnchors();
}
if (testFloor)
{
testFloor = false;
OnFloor();
}
if (testDevice)
{
testDevice = false;
OnDevice();
}
if (testReloadScene)
{
testReloadScene = false;
OnReloadScene();
}
}
List<FutureTask<XrResult>> toRemovePA = new List<FutureTask<XrResult>>();
List<FutureTask<(XrResult, AnchorManager.Anchor)>> toRemoveFPA = new List<FutureTask<(XrResult, AnchorManager.Anchor)>>();
void UpdateTasks()
{
toRemovePA.Clear();
// Check persist anchor tasks
foreach (var taskTuple in tmPA.GetTasks())
{
var anchor = taskTuple.Item1;
var task = taskTuple.Item2;
if (!AnchorManager.GetSpatialAnchorName(anchor, out string name))
Debug.LogError("Faild to get anchor name: " + anchor);
if (task.IsPollCompleted)
{
toRemovePA.Add(task);
if (task.PollResult == XrResult.XR_SUCCESS)
{
var result = task.Complete();
if (result == XrResult.XR_SUCCESS)
{
Debug.Log("AnchorTestHandle: Persist anchor " + anchor + "=" + name + " success");
}
else
{
Debug.LogError("AnchorTestHandle: Persist anchor " + anchor + "=" + name + " failed: " + result);
}
}
else
{
Debug.LogError("AnchorTestHandle: Persist anchor " + anchor + "=" + name + " failed: " + task.PollResult);
}
}
}
foreach (var task in toRemovePA)
{
tmPA.RemoveTask(task);
}
toRemovePA.Clear();
// Check create from persisted anchor tasks
toRemoveFPA.Clear();
foreach (var taskTuple in tmFPA.GetTasks())
{
var paName = taskTuple.Item1;
var task = taskTuple.Item2;
if (task.IsPollCompleted)
{
toRemoveFPA.Add(task);
if (task.PollResult == XrResult.XR_SUCCESS)
{
var result = task.Complete();
if (result.Item1 == XrResult.XR_SUCCESS)
{
Debug.Log("AnchorTestHandle: Create anchor from persisted anchor " + paName + " success");
if (paName == persistedAnchor1Name)
{
anchor1 = result.Item2;
}
else if (paName == persistedAnchor2Name)
{
anchor2 = result.Item2;
}
}
else
{
Debug.LogError("AnchorTestHandle: Create anchor from persisted anchor " + paName + " failed: " + result.Item1);
if (paName == persistedAnchor1Name)
{
anchor1FromPA = null;
}
else if (paName == persistedAnchor2Name)
{
anchor2FromPA = null;
}
}
}
else
{
Debug.LogError("AnchorTestHandle: Create anchor from persisted anchor " + paName + " failed: " + task.PollResult);
if (paName == persistedAnchor1Name)
{
anchor1FromPA = null;
}
else if (paName == persistedAnchor2Name)
{
anchor2FromPA = null;
}
}
}
}
foreach (var task in toRemoveFPA)
{
tmFPA.RemoveTask(task);
}
toRemoveFPA.Clear();
}
void Update()
{
PerformEditorTest();
if (!isAnchorSupported)
{
UpdateUIInteractions();
return;
}
UpdateTasks();
// Check persisted files
CheckExportedFiles();
// Enumerate persisted anchors
EnumeratePersistedAnchors();
// Update anchors if persisted anchors are existed
UpdateAnchorsIfPersistExist();
// Update trackable anchors' pose
UpdateTrackableAnchorsPose();
// Update UI interactions
UpdateUIInteractions();
}
public void OnFloor()
{
Debug.Log("AnchorTestHandle: OnFloor()");
if (xrInputSubsystem == null)
{
Debug.LogError("xrInputSubsystem is null");
statusOrigin.text = "xrInputSubsystem is null";
return;
}
OnResetObj();
OnClearAllAnchors();
if (xrInputSubsystem.TrySetTrackingOriginMode(TrackingOriginModeFlags.Floor))
{
statusOrigin.text = "Set tracking origin to floor. Reset Obj and anchors";
}
else
{
statusOrigin.text = "Fail to set tracking origin to floor";
}
}
public void OnDevice()
{
Debug.Log("AnchorTestHandle: OnDevice()");
if (xrInputSubsystem == null)
{
Debug.LogError("AnchorTestHandle: xrInputSubsystem is null");
statusOrigin.text = "xrInputSubsystem is null";
return;
}
OnResetObj();
OnClearAllAnchors();
if (xrInputSubsystem.TrySetTrackingOriginMode(TrackingOriginModeFlags.Device))
{
statusOrigin.text = "Set tracking origin to device. Reset Obj and anchors";
}
else
{
statusOrigin.text = "Fail to set tracking origin to device";
}
}
public void OnReloadScene()
{
Debug.Log("AnchorTestHandle: OnReloadScene()");
UnityEngine.SceneManagement.SceneManager.LoadScene(UnityEngine.SceneManagement.SceneManager.GetActiveScene().name);
}
}
}