NRWorldAnchorStore.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /****************************************************************************
  2. * Copyright 2019 Nreal Techonology Limited. All rights reserved.
  3. *
  4. * This file is part of NRSDK.
  5. *
  6. * https://www.nreal.ai/
  7. *
  8. *****************************************************************************/
  9. namespace NRKernal.Experimental.Persistence
  10. {
  11. using System;
  12. using System.Collections;
  13. using System.Collections.Generic;
  14. using System.IO;
  15. using UnityEngine;
  16. using UnityEngine.Assertions;
  17. /// <summary> A nr world anchor store. </summary>
  18. public class NRWorldAnchorStore : IDisposable
  19. {
  20. /// <summary> Gets the number of anchors. </summary>
  21. /// <value> The number of anchors. </value>
  22. public int anchorCount
  23. {
  24. get
  25. {
  26. return m_AnchorDict.Count;
  27. }
  28. }
  29. /// <summary> Gets or sets the native mapping. </summary>
  30. /// <value> The m native mapping. </value>
  31. private NativeMapping m_NativeMapping { get; set; }
  32. /// <summary> Dictionary of anchors. </summary>
  33. private Dictionary<int, NRWorldAnchor> m_AnchorDict = new Dictionary<int, NRWorldAnchor>();
  34. /// <summary> Dictionary of key and anchor id. </summary>
  35. private Dictionary<string, int> m_Anchor2ObjectDict = new Dictionary<string, int>();
  36. /// <summary> The nr world anchor store. </summary>
  37. private static NRWorldAnchorStore m_NRWorldAnchorStore;
  38. /// <summary> Pathname of the map folder. </summary>
  39. public const string MapFolder = "NrealMaps";
  40. /// <summary> The map file. </summary>
  41. public const string MapFile = "nreal_map.dat";
  42. /// <summary> The anchor 2 object file. </summary>
  43. public const string Anchor2ObjectFile = "anchor2object.json";
  44. public const string AnchorsFile = "nreal_map_anchors.json";
  45. /// <summary> The root anchor. </summary>
  46. public const string AnchorRootKey = "root";
  47. /// <summary> Anchors update interval. </summary>
  48. private const float Update_Interval = 0.2f;
  49. private float currentUpdateTime = 0f;
  50. /// <summary> Gets the WorldAnchorStore instance. </summary>
  51. /// <param name="onCompleted"> .</param>
  52. public static void GetAsync(GetAsyncDelegate onCompleted)
  53. {
  54. NRKernalUpdater.Instance.StartCoroutine(GetWorldAnchorStore(onCompleted));
  55. }
  56. /// <summary> Gets world anchor store. </summary>
  57. /// <param name="onCompleted"> .</param>
  58. /// <returns> The world anchor store. </returns>
  59. private static IEnumerator GetWorldAnchorStore(GetAsyncDelegate onCompleted)
  60. {
  61. // Wait for slam ready.
  62. while (NRFrame.LostTrackingReason != LostTrackingReason.NONE ||
  63. NRFrame.SessionStatus != SessionState.Running ||
  64. !NRFrame.isHeadPoseReady)
  65. {
  66. NRDebugger.Info("[NRWorldAnchorStore] Wait for slam ready...");
  67. yield return new WaitForEndOfFrame();
  68. }
  69. yield return new WaitForSeconds(0.5f);
  70. if (m_NRWorldAnchorStore == null)
  71. {
  72. m_NRWorldAnchorStore = new NRWorldAnchorStore();
  73. }
  74. NRDebugger.Info("[NRWorldAnchorStore] : GetWorldAnchorStore true");
  75. onCompleted?.Invoke(m_NRWorldAnchorStore);
  76. }
  77. /// <summary> Default constructor. </summary>
  78. internal NRWorldAnchorStore()
  79. {
  80. #if !UNITY_EDITOR
  81. m_NativeMapping = new NativeMapping(NRSessionManager.Instance.NativeAPI);
  82. #endif
  83. EnsurePath(Path.Combine(Application.persistentDataPath, MapFolder));
  84. LoadWorldMap(Path.Combine(Application.persistentDataPath, MapFolder, MapFile));
  85. string path = Path.Combine(Application.persistentDataPath, MapFolder, Anchor2ObjectFile);
  86. if (File.Exists(path))
  87. {
  88. string json = File.ReadAllText(path);
  89. NRDebugger.Info("[NRWorldAnchorStore] Anchor2Object json:" + json);
  90. m_Anchor2ObjectDict = LitJson.JsonMapper.ToObject<Dictionary<string, int>>(json);
  91. }
  92. // Add a root anchor for default.
  93. if (!m_Anchor2ObjectDict.ContainsKey(AnchorRootKey))
  94. {
  95. m_Anchor2ObjectDict.Add(AnchorRootKey, 0);
  96. }
  97. NRKernalUpdater.OnUpdate -= OnUpdate;
  98. NRKernalUpdater.OnUpdate += OnUpdate;
  99. this.UpdateAnchors();
  100. }
  101. /// <summary> Executes the 'update' action. </summary>
  102. private void OnUpdate()
  103. {
  104. currentUpdateTime += Time.deltaTime;
  105. if (currentUpdateTime < Update_Interval)
  106. {
  107. return;
  108. }
  109. currentUpdateTime = 0f;
  110. UpdateAnchors();
  111. foreach (var item in m_AnchorDict)
  112. {
  113. item.Value.OnUpdateState();
  114. }
  115. }
  116. /// <summary> Loads world map. </summary>
  117. /// <param name="path"> Full pathname of the file.</param>
  118. /// <returns> True if it succeeds, false if it fails. </returns>
  119. private bool LoadWorldMap(string path)
  120. {
  121. #if !UNITY_EDITOR
  122. Assert.IsTrue(File.Exists(path), "[NRWorldAnchorStore] World map File is not exit!!!");
  123. m_NativeMapping.CreateDataBase();
  124. return m_NativeMapping.LoadMap(path);
  125. #else
  126. return true;
  127. #endif
  128. }
  129. /// <summary> Writes the world map. </summary>
  130. /// <returns> True if it succeeds, false if it fails. </returns>
  131. private bool WriteWorldMap()
  132. {
  133. string basepath = Path.Combine(Application.persistentDataPath, MapFolder);
  134. EnsurePath(basepath);
  135. #if !UNITY_EDITOR
  136. return m_NativeMapping.SaveMap(Path.Combine(basepath, MapFile));
  137. #else
  138. return true;
  139. #endif
  140. }
  141. /// <summary> Clears all persisted NRWorldAnchors. </summary>
  142. public void Clear()
  143. {
  144. foreach (var item in m_AnchorDict)
  145. {
  146. if (item.Value != null)
  147. {
  148. #if !UNITY_EDITOR
  149. m_NativeMapping.DestroyAnchor(item.Value.AnchorNativeHandle);
  150. #endif
  151. GameObject.Destroy(item.Value.gameObject);
  152. }
  153. }
  154. m_AnchorDict.Clear();
  155. m_Anchor2ObjectDict.Clear();
  156. this.Save();
  157. }
  158. /// <summary> Reset </summary>
  159. /// <returns> True if it succeeds, false if it fails. </returns>
  160. public bool Reset()
  161. {
  162. #if !UNITY_EDITOR
  163. return m_NativeMapping.Reset();
  164. #else
  165. return true;
  166. #endif
  167. }
  168. /// <summary> Deletes a persisted NRWorldAnchor from the store. </summary>
  169. /// <param name="key"> .</param>
  170. /// <returns> True if it succeeds, false if it fails. </returns>
  171. public bool Delete(string key)
  172. {
  173. if (string.IsNullOrEmpty(key))
  174. {
  175. NRDebugger.Warning("[NRWorldAnchorStore] Can not delete a null key");
  176. return false;
  177. }
  178. NRWorldAnchor anchor;
  179. if (TryGetValue(key, out anchor))
  180. {
  181. Assert.IsTrue(anchor != null);
  182. #if !UNITY_EDITOR
  183. m_NativeMapping.DestroyAnchor(anchor.AnchorNativeHandle);
  184. #endif
  185. m_AnchorDict.Remove(anchor.GetNativeSpatialAnchorPtr());
  186. m_Anchor2ObjectDict.Remove(key);
  187. GameObject.Destroy(anchor.gameObject);
  188. this.Save();
  189. }
  190. return false;
  191. }
  192. /// <summary> Cleans up the WorldAnchorStore and releases memory. </summary>
  193. public void Dispose()
  194. {
  195. if (m_NativeMapping == null)
  196. {
  197. return;
  198. }
  199. #if !UNITY_EDITOR
  200. m_NativeMapping.DestroyDataBase();
  201. m_NativeMapping = null;
  202. #endif
  203. m_NRWorldAnchorStore = null;
  204. NRKernalUpdater.OnUpdate -= OnUpdate;
  205. }
  206. /// <summary> Gets all of the identifiers of the currently persisted NRWorldAnchors. </summary>
  207. /// <returns> An array of string. </returns>
  208. public string[] GetAllIds()
  209. {
  210. if (m_Anchor2ObjectDict == null || m_Anchor2ObjectDict.Count == 0)
  211. {
  212. return null;
  213. }
  214. string[] ids = new string[m_Anchor2ObjectDict.Count];
  215. int index = 0;
  216. foreach (var item in m_Anchor2ObjectDict)
  217. {
  218. ids[index++] = item.Key;
  219. }
  220. return ids;
  221. }
  222. /// <summary> Update all NRWorldAnchors. </summary>
  223. ///
  224. /// ### <param name="anchorlist"> .</param>
  225. private void UpdateAnchors()
  226. {
  227. #if !UNITY_EDITOR
  228. var listhandle = m_NativeMapping.CreateAnchorList();
  229. m_NativeMapping.UpdateAnchor(listhandle);
  230. var size = m_NativeMapping.GetAnchorListSize(listhandle);
  231. for (int i = 0; i < size; i++)
  232. {
  233. var anchorhandle = m_NativeMapping.AcquireItem(listhandle, i);
  234. if (!m_AnchorDict.ContainsKey(NativeMapping.GetAnchorNativeID(anchorhandle)))
  235. {
  236. CreateAnchor(anchorhandle);
  237. }
  238. }
  239. m_NativeMapping.DestroyAnchorList(listhandle);
  240. #endif
  241. }
  242. /// <summary>
  243. /// Loads a WorldAnchor from disk for given identifier and attaches it to the GameObject. If the
  244. /// GameObject has a WorldAnchor, that WorldAnchor will be updated. If the anchor is not found, a
  245. /// new anchor will be add in the position of go. If AddAnchor failed, null will be returned. The
  246. /// GameObject and any existing NRWorldAnchor attached to it will not be modified. </summary>
  247. /// <param name="key"> .</param>
  248. /// <param name="go"> .</param>
  249. /// <returns> A NRWorldAnchor. </returns>
  250. public NRWorldAnchor Load(string key, GameObject go)
  251. {
  252. if (string.IsNullOrEmpty(key) || go == null)
  253. {
  254. return null;
  255. }
  256. NRWorldAnchor anchor;
  257. if (TryGetValue(key, out anchor))
  258. {
  259. NRDebugger.Info("[NRWorldAnchorStore] load the cached anchor:" + key);
  260. go.transform.parent = anchor.transform;
  261. go.transform.localPosition = Vector3.zero;
  262. go.transform.localRotation = Quaternion.identity;
  263. return anchor;
  264. }
  265. NRDebugger.Warning("[NRWorldAnchorStore] can not find the anchor:" + key);
  266. return null;
  267. }
  268. /// <summary> Attempts to get value a NRWorldAnchor from the given string. </summary>
  269. /// <param name="key"> .</param>
  270. /// <param name="out_anchor"> [out] The out anchor.</param>
  271. /// <returns> True if it succeeds, false if it fails. </returns>
  272. private bool TryGetValue(string key, out NRWorldAnchor out_anchor)
  273. {
  274. NRWorldAnchor anchor;
  275. int anchorID;
  276. if (m_Anchor2ObjectDict.TryGetValue(key, out anchorID))
  277. {
  278. if (m_AnchorDict.TryGetValue(anchorID, out anchor))
  279. {
  280. out_anchor = anchor;
  281. return out_anchor != null;
  282. }
  283. }
  284. out_anchor = null;
  285. return false;
  286. }
  287. public NRWorldAnchor AddAnchor(string key, GameObject go)
  288. {
  289. if (go == null)
  290. {
  291. NRDebugger.Error("[NRWorldAnchorStore] Can not add a null gameobject as a anchor.");
  292. return null;
  293. }
  294. NRWorldAnchor anchor = this.AddAnchor(key, new Pose(go.transform.position, go.transform.rotation));
  295. go.transform.SetParent(anchor.transform);
  296. go.transform.localPosition = Vector3.zero;
  297. go.transform.localRotation = Quaternion.identity;
  298. return anchor;
  299. }
  300. /// <summary> Adds an anchor to 'worldPose'. </summary>
  301. /// <param name="key"> .</param>
  302. /// <param name="worldPose"> The world pose.</param>
  303. /// <returns> A NRWorldAnchor. </returns>
  304. public NRWorldAnchor AddAnchor(string key, Pose worldPose)
  305. {
  306. NRDebugger.Info("[NRWorldAnchorStore] Add a new worldanchor");
  307. if (string.IsNullOrEmpty(key))
  308. {
  309. NRDebugger.Error("[NRWorldAnchorStore] Can not add a null string as the key.");
  310. return null;
  311. }
  312. if (m_Anchor2ObjectDict.ContainsKey(key))
  313. {
  314. this.Delete(key);
  315. }
  316. #if !UNITY_EDITOR
  317. var handle = m_NativeMapping.AddAnchor(worldPose);
  318. #else
  319. var handle = (UInt64)UnityEngine.Random.Range(1, 100000);
  320. #endif
  321. if (handle == 0)
  322. {
  323. NRDebugger.Error("[NRWorldAnchorStore] Add anchor failed for illegal anchor handle:" + handle);
  324. return null;
  325. }
  326. NRWorldAnchor anchor = CreateAnchor(handle, key);
  327. this.Save();
  328. return anchor;
  329. }
  330. /// <summary> Creates an anchor. </summary>
  331. /// <param name="handler"> The handler.</param>
  332. /// <param name="anchorkey"> (Optional) The anchorkey.</param>
  333. /// <returns> The new anchor. </returns>
  334. private NRWorldAnchor CreateAnchor(UInt64 handler, string anchorkey = null)
  335. {
  336. NRDebugger.Info("[NRWorldAnchorStore] Create a new worldanchor handle:" + handler);
  337. var anchor = new GameObject("NRWorldAnchor").AddComponent<NRWorldAnchor>();
  338. // Make sure anchor would not be destroied when change the scene.
  339. GameObject.DontDestroyOnLoad(anchor);
  340. anchor.SetNativeSpatialAnchorPtr(handler, m_NativeMapping);
  341. int anchorID = anchor.GetNativeSpatialAnchorPtr();
  342. m_AnchorDict[anchorID] = anchor;
  343. if (!string.IsNullOrEmpty(anchorkey))
  344. {
  345. m_Anchor2ObjectDict[anchorkey] = anchorID;
  346. }
  347. return anchor;
  348. }
  349. /// <summary>
  350. /// Saves the provided NRWorldAnchor with the provided identifier. If the identifier is already
  351. /// in use, the method will return false. </summary>
  352. /// <param name="key"> .</param>
  353. /// <param name="anchor"> .</param>
  354. /// <returns> True if it succeeds, false if it fails. </returns>
  355. private bool Save(string key, NRWorldAnchor anchor)
  356. {
  357. NRDebugger.Info("[NRWorldAnchorStore] Save a new worldanchor:" + key);
  358. if (m_Anchor2ObjectDict.ContainsKey(key))
  359. {
  360. NRDebugger.Warning("[NRWorldAnchorStore] Save a new anchor faild for repeated key:" + key);
  361. return false;
  362. }
  363. try
  364. {
  365. m_Anchor2ObjectDict.Add(key, anchor.GetNativeSpatialAnchorPtr());
  366. string json = LitJson.JsonMapper.ToJson(m_Anchor2ObjectDict);
  367. string path = Path.Combine(Application.persistentDataPath, MapFolder, Anchor2ObjectFile);
  368. NRDebugger.Info("[NRWorldAnchorStore] Save to the path:" + path + " json:" + json);
  369. File.WriteAllText(path, json);
  370. return true;
  371. }
  372. catch (Exception e)
  373. {
  374. NRDebugger.Warning("Write anchor to object dict exception:" + e.ToString());
  375. return false;
  376. }
  377. }
  378. /// <summary> Saves all NRWorldAnchor. </summary>
  379. /// <returns> True if it succeeds, false if it fails. </returns>
  380. public bool Save()
  381. {
  382. if (m_Anchor2ObjectDict == null)
  383. {
  384. return false;
  385. }
  386. NRDebugger.Info("[NRWorldAnchorStore] Save all worldanchor");
  387. try
  388. {
  389. string path = Path.Combine(Application.persistentDataPath, MapFolder, Anchor2ObjectFile);
  390. if (File.Exists(path))
  391. {
  392. File.Delete(path);
  393. }
  394. if (m_Anchor2ObjectDict.Count != 0)
  395. {
  396. string json = LitJson.JsonMapper.ToJson(m_Anchor2ObjectDict);
  397. NRDebugger.Info("[NRWorldAnchorStore] Save to the path:" + path + " json:" + json);
  398. File.WriteAllText(path, json);
  399. }
  400. return this.WriteWorldMap();
  401. }
  402. catch (Exception e)
  403. {
  404. NRDebugger.Warning("Write anchor to object dict exception:" + e.ToString());
  405. return false;
  406. }
  407. }
  408. /// <summary> Ensures that path. </summary>
  409. /// <param name="path"> Full pathname of the file.</param>
  410. private void EnsurePath(string path)
  411. {
  412. if (!Directory.Exists(path))
  413. {
  414. Directory.CreateDirectory(path);
  415. }
  416. }
  417. /// <summary> The handler for when getting the WorldAnchorStore from GetAsync. </summary>
  418. /// <param name="store"> .</param>
  419. public delegate void GetAsyncDelegate(NRWorldAnchorStore store);
  420. }
  421. }