/****************************************************************************
* Copyright 2019 Nreal Techonology Limited. All rights reserved.
*
* This file is part of NRSDK.
*
* https://www.nreal.ai/
*
*****************************************************************************/
namespace NRKernal.Experimental.Persistence
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Assertions;
/// A nr world anchor store.
public class NRWorldAnchorStore : IDisposable
{
/// Gets the number of anchors.
/// The number of anchors.
public int anchorCount
{
get
{
return m_AnchorDict.Count;
}
}
/// Gets or sets the native mapping.
/// The m native mapping.
private NativeMapping m_NativeMapping { get; set; }
/// Dictionary of anchors.
private Dictionary m_AnchorDict = new Dictionary();
/// Dictionary of key and anchor id.
private Dictionary m_Anchor2ObjectDict = new Dictionary();
/// The nr world anchor store.
private static NRWorldAnchorStore m_NRWorldAnchorStore;
/// Pathname of the map folder.
public const string MapFolder = "NrealMaps";
/// The map file.
public const string MapFile = "nreal_map.dat";
/// The anchor 2 object file.
public const string Anchor2ObjectFile = "anchor2object.json";
public const string AnchorsFile = "nreal_map_anchors.json";
/// The root anchor.
public const string AnchorRootKey = "root";
/// Anchors update interval.
private const float Update_Interval = 0.2f;
private float currentUpdateTime = 0f;
/// Gets the WorldAnchorStore instance.
/// .
public static void GetAsync(GetAsyncDelegate onCompleted)
{
NRKernalUpdater.Instance.StartCoroutine(GetWorldAnchorStore(onCompleted));
}
/// Gets world anchor store.
/// .
/// The world anchor store.
private static IEnumerator GetWorldAnchorStore(GetAsyncDelegate onCompleted)
{
// Wait for slam ready.
while (NRFrame.LostTrackingReason != LostTrackingReason.NONE ||
NRFrame.SessionStatus != SessionState.Running ||
!NRFrame.isHeadPoseReady)
{
NRDebugger.Info("[NRWorldAnchorStore] Wait for slam ready...");
yield return new WaitForEndOfFrame();
}
yield return new WaitForSeconds(0.5f);
if (m_NRWorldAnchorStore == null)
{
m_NRWorldAnchorStore = new NRWorldAnchorStore();
}
NRDebugger.Info("[NRWorldAnchorStore] : GetWorldAnchorStore true");
onCompleted?.Invoke(m_NRWorldAnchorStore);
}
/// Default constructor.
internal NRWorldAnchorStore()
{
#if !UNITY_EDITOR
m_NativeMapping = new NativeMapping(NRSessionManager.Instance.NativeAPI);
#endif
EnsurePath(Path.Combine(Application.persistentDataPath, MapFolder));
LoadWorldMap(Path.Combine(Application.persistentDataPath, MapFolder, MapFile));
string path = Path.Combine(Application.persistentDataPath, MapFolder, Anchor2ObjectFile);
if (File.Exists(path))
{
string json = File.ReadAllText(path);
NRDebugger.Info("[NRWorldAnchorStore] Anchor2Object json:" + json);
m_Anchor2ObjectDict = LitJson.JsonMapper.ToObject>(json);
}
// Add a root anchor for default.
if (!m_Anchor2ObjectDict.ContainsKey(AnchorRootKey))
{
m_Anchor2ObjectDict.Add(AnchorRootKey, 0);
}
NRKernalUpdater.OnUpdate -= OnUpdate;
NRKernalUpdater.OnUpdate += OnUpdate;
this.UpdateAnchors();
}
/// Executes the 'update' action.
private void OnUpdate()
{
currentUpdateTime += Time.deltaTime;
if (currentUpdateTime < Update_Interval)
{
return;
}
currentUpdateTime = 0f;
UpdateAnchors();
foreach (var item in m_AnchorDict)
{
item.Value.OnUpdateState();
}
}
/// Loads world map.
/// Full pathname of the file.
/// True if it succeeds, false if it fails.
private bool LoadWorldMap(string path)
{
#if !UNITY_EDITOR
Assert.IsTrue(File.Exists(path), "[NRWorldAnchorStore] World map File is not exit!!!");
m_NativeMapping.CreateDataBase();
return m_NativeMapping.LoadMap(path);
#else
return true;
#endif
}
/// Writes the world map.
/// True if it succeeds, false if it fails.
private bool WriteWorldMap()
{
string basepath = Path.Combine(Application.persistentDataPath, MapFolder);
EnsurePath(basepath);
#if !UNITY_EDITOR
return m_NativeMapping.SaveMap(Path.Combine(basepath, MapFile));
#else
return true;
#endif
}
/// Clears all persisted NRWorldAnchors.
public void Clear()
{
foreach (var item in m_AnchorDict)
{
if (item.Value != null)
{
#if !UNITY_EDITOR
m_NativeMapping.DestroyAnchor(item.Value.AnchorNativeHandle);
#endif
GameObject.Destroy(item.Value.gameObject);
}
}
m_AnchorDict.Clear();
m_Anchor2ObjectDict.Clear();
this.Save();
}
/// Reset
/// True if it succeeds, false if it fails.
public bool Reset()
{
#if !UNITY_EDITOR
return m_NativeMapping.Reset();
#else
return true;
#endif
}
/// Deletes a persisted NRWorldAnchor from the store.
/// .
/// True if it succeeds, false if it fails.
public bool Delete(string key)
{
if (string.IsNullOrEmpty(key))
{
NRDebugger.Warning("[NRWorldAnchorStore] Can not delete a null key");
return false;
}
NRWorldAnchor anchor;
if (TryGetValue(key, out anchor))
{
Assert.IsTrue(anchor != null);
#if !UNITY_EDITOR
m_NativeMapping.DestroyAnchor(anchor.AnchorNativeHandle);
#endif
m_AnchorDict.Remove(anchor.GetNativeSpatialAnchorPtr());
m_Anchor2ObjectDict.Remove(key);
GameObject.Destroy(anchor.gameObject);
this.Save();
}
return false;
}
/// Cleans up the WorldAnchorStore and releases memory.
public void Dispose()
{
if (m_NativeMapping == null)
{
return;
}
#if !UNITY_EDITOR
m_NativeMapping.DestroyDataBase();
m_NativeMapping = null;
#endif
m_NRWorldAnchorStore = null;
NRKernalUpdater.OnUpdate -= OnUpdate;
}
/// Gets all of the identifiers of the currently persisted NRWorldAnchors.
/// An array of string.
public string[] GetAllIds()
{
if (m_Anchor2ObjectDict == null || m_Anchor2ObjectDict.Count == 0)
{
return null;
}
string[] ids = new string[m_Anchor2ObjectDict.Count];
int index = 0;
foreach (var item in m_Anchor2ObjectDict)
{
ids[index++] = item.Key;
}
return ids;
}
/// Update all NRWorldAnchors.
///
/// ### .
private void UpdateAnchors()
{
#if !UNITY_EDITOR
var listhandle = m_NativeMapping.CreateAnchorList();
m_NativeMapping.UpdateAnchor(listhandle);
var size = m_NativeMapping.GetAnchorListSize(listhandle);
for (int i = 0; i < size; i++)
{
var anchorhandle = m_NativeMapping.AcquireItem(listhandle, i);
if (!m_AnchorDict.ContainsKey(NativeMapping.GetAnchorNativeID(anchorhandle)))
{
CreateAnchor(anchorhandle);
}
}
m_NativeMapping.DestroyAnchorList(listhandle);
#endif
}
///
/// Loads a WorldAnchor from disk for given identifier and attaches it to the GameObject. If the
/// GameObject has a WorldAnchor, that WorldAnchor will be updated. If the anchor is not found, a
/// new anchor will be add in the position of go. If AddAnchor failed, null will be returned. The
/// GameObject and any existing NRWorldAnchor attached to it will not be modified.
/// .
/// .
/// A NRWorldAnchor.
public NRWorldAnchor Load(string key, GameObject go)
{
if (string.IsNullOrEmpty(key) || go == null)
{
return null;
}
NRWorldAnchor anchor;
if (TryGetValue(key, out anchor))
{
NRDebugger.Info("[NRWorldAnchorStore] load the cached anchor:" + key);
go.transform.parent = anchor.transform;
go.transform.localPosition = Vector3.zero;
go.transform.localRotation = Quaternion.identity;
return anchor;
}
NRDebugger.Warning("[NRWorldAnchorStore] can not find the anchor:" + key);
return null;
}
/// Attempts to get value a NRWorldAnchor from the given string.
/// .
/// [out] The out anchor.
/// True if it succeeds, false if it fails.
private bool TryGetValue(string key, out NRWorldAnchor out_anchor)
{
NRWorldAnchor anchor;
int anchorID;
if (m_Anchor2ObjectDict.TryGetValue(key, out anchorID))
{
if (m_AnchorDict.TryGetValue(anchorID, out anchor))
{
out_anchor = anchor;
return out_anchor != null;
}
}
out_anchor = null;
return false;
}
public NRWorldAnchor AddAnchor(string key, GameObject go)
{
if (go == null)
{
NRDebugger.Error("[NRWorldAnchorStore] Can not add a null gameobject as a anchor.");
return null;
}
NRWorldAnchor anchor = this.AddAnchor(key, new Pose(go.transform.position, go.transform.rotation));
go.transform.SetParent(anchor.transform);
go.transform.localPosition = Vector3.zero;
go.transform.localRotation = Quaternion.identity;
return anchor;
}
/// Adds an anchor to 'worldPose'.
/// .
/// The world pose.
/// A NRWorldAnchor.
public NRWorldAnchor AddAnchor(string key, Pose worldPose)
{
NRDebugger.Info("[NRWorldAnchorStore] Add a new worldanchor");
if (string.IsNullOrEmpty(key))
{
NRDebugger.Error("[NRWorldAnchorStore] Can not add a null string as the key.");
return null;
}
if (m_Anchor2ObjectDict.ContainsKey(key))
{
this.Delete(key);
}
#if !UNITY_EDITOR
var handle = m_NativeMapping.AddAnchor(worldPose);
#else
var handle = (UInt64)UnityEngine.Random.Range(1, 100000);
#endif
if (handle == 0)
{
NRDebugger.Error("[NRWorldAnchorStore] Add anchor failed for illegal anchor handle:" + handle);
return null;
}
NRWorldAnchor anchor = CreateAnchor(handle, key);
this.Save();
return anchor;
}
/// Creates an anchor.
/// The handler.
/// (Optional) The anchorkey.
/// The new anchor.
private NRWorldAnchor CreateAnchor(UInt64 handler, string anchorkey = null)
{
NRDebugger.Info("[NRWorldAnchorStore] Create a new worldanchor handle:" + handler);
var anchor = new GameObject("NRWorldAnchor").AddComponent();
// Make sure anchor would not be destroied when change the scene.
GameObject.DontDestroyOnLoad(anchor);
anchor.SetNativeSpatialAnchorPtr(handler, m_NativeMapping);
int anchorID = anchor.GetNativeSpatialAnchorPtr();
m_AnchorDict[anchorID] = anchor;
if (!string.IsNullOrEmpty(anchorkey))
{
m_Anchor2ObjectDict[anchorkey] = anchorID;
}
return anchor;
}
///
/// Saves the provided NRWorldAnchor with the provided identifier. If the identifier is already
/// in use, the method will return false.
/// .
/// .
/// True if it succeeds, false if it fails.
private bool Save(string key, NRWorldAnchor anchor)
{
NRDebugger.Info("[NRWorldAnchorStore] Save a new worldanchor:" + key);
if (m_Anchor2ObjectDict.ContainsKey(key))
{
NRDebugger.Warning("[NRWorldAnchorStore] Save a new anchor faild for repeated key:" + key);
return false;
}
try
{
m_Anchor2ObjectDict.Add(key, anchor.GetNativeSpatialAnchorPtr());
string json = LitJson.JsonMapper.ToJson(m_Anchor2ObjectDict);
string path = Path.Combine(Application.persistentDataPath, MapFolder, Anchor2ObjectFile);
NRDebugger.Info("[NRWorldAnchorStore] Save to the path:" + path + " json:" + json);
File.WriteAllText(path, json);
return true;
}
catch (Exception e)
{
NRDebugger.Warning("Write anchor to object dict exception:" + e.ToString());
return false;
}
}
/// Saves all NRWorldAnchor.
/// True if it succeeds, false if it fails.
public bool Save()
{
if (m_Anchor2ObjectDict == null)
{
return false;
}
NRDebugger.Info("[NRWorldAnchorStore] Save all worldanchor");
try
{
string path = Path.Combine(Application.persistentDataPath, MapFolder, Anchor2ObjectFile);
if (File.Exists(path))
{
File.Delete(path);
}
if (m_Anchor2ObjectDict.Count != 0)
{
string json = LitJson.JsonMapper.ToJson(m_Anchor2ObjectDict);
NRDebugger.Info("[NRWorldAnchorStore] Save to the path:" + path + " json:" + json);
File.WriteAllText(path, json);
}
return this.WriteWorldMap();
}
catch (Exception e)
{
NRDebugger.Warning("Write anchor to object dict exception:" + e.ToString());
return false;
}
}
/// Ensures that path.
/// Full pathname of the file.
private void EnsurePath(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
/// The handler for when getting the WorldAnchorStore from GetAsync.
/// .
public delegate void GetAsyncDelegate(NRWorldAnchorStore store);
}
}