using System.Collections;
using System.Collections.Generic;
using System.IO;
using Unity.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
#if UNITY_IOS
using UnityEngine.XR.ARKit;
#endif
///
/// Demonstrates the saving and loading of an
/// ARWorldMap
///
///
/// ARWorldMaps are only supported by ARKit, so this API is in the
/// UntyEngine.XR.ARKit namespace.
///
public class ARWorldMapController : MonoBehaviour
{
[Tooltip("The ARSession component controlling the session from which to generate ARWorldMaps.")]
[SerializeField]
ARSession m_ARSession;
///
/// The ARSession component controlling the session from which to generate ARWorldMaps.
///
public ARSession arSession
{
get { return m_ARSession; }
set { m_ARSession = value; }
}
[Tooltip("UI Text component to display error messages")]
[SerializeField]
Text m_ErrorText;
///
/// The UI Text component used to display error messages
///
public Text errorText
{
get { return m_ErrorText; }
set { m_ErrorText = value; }
}
[Tooltip("The UI Text element used to display log messages.")]
[SerializeField]
Text m_LogText;
///
/// The UI Text element used to display log messages.
///
public Text logText
{
get { return m_LogText; }
set { m_LogText = value; }
}
[Tooltip("The UI Text element used to display the current AR world mapping status.")]
[SerializeField]
Text m_MappingStatusText;
///
/// The UI Text element used to display the current AR world mapping status.
///
public Text mappingStatusText
{
get { return m_MappingStatusText; }
set { m_MappingStatusText = value; }
}
[Tooltip("A UI button component which will generate an ARWorldMap and save it to disk.")]
[SerializeField]
Button m_SaveButton;
///
/// A UI button component which will generate an ARWorldMap and save it to disk.
///
public Button saveButton
{
get { return m_SaveButton; }
set { m_SaveButton = value; }
}
[Tooltip("A UI button component which will load a previously saved ARWorldMap from disk and apply it to the current session.")]
[SerializeField]
Button m_LoadButton;
///
/// A UI button component which will load a previously saved ARWorldMap from disk and apply it to the current session.
///
public Button loadButton
{
get { return m_LoadButton; }
set { m_LoadButton = value; }
}
///
/// Create an ARWorldMap and save it to disk.
///
public void OnSaveButton()
{
#if UNITY_IOS
StartCoroutine(Save());
#endif
}
///
/// Load an ARWorldMap from disk and apply it
/// to the current session.
///
public void OnLoadButton()
{
#if UNITY_IOS
StartCoroutine(Load());
#endif
}
///
/// Reset the ARSession, destroying any existing trackables,
/// such as planes. Upon loading a saved ARWorldMap, saved
/// trackables will be restored.
///
public void OnResetButton()
{
m_ARSession.Reset();
}
#if UNITY_IOS
IEnumerator Save()
{
var sessionSubsystem = (ARKitSessionSubsystem)m_ARSession.subsystem;
if (sessionSubsystem == null)
{
Log("No session subsystem available. Could not save.");
yield break;
}
var request = sessionSubsystem.GetARWorldMapAsync();
while (!request.status.IsDone())
yield return null;
if (request.status.IsError())
{
Log(string.Format("Session serialization failed with status {0}", request.status));
yield break;
}
var worldMap = request.GetWorldMap();
request.Dispose();
SaveAndDisposeWorldMap(worldMap);
}
IEnumerator Load()
{
var sessionSubsystem = (ARKitSessionSubsystem)m_ARSession.subsystem;
if (sessionSubsystem == null)
{
Log("No session subsystem available. Could not load.");
yield break;
}
var file = File.Open(path, FileMode.Open);
if (file == null)
{
Log(string.Format("File {0} does not exist.", path));
yield break;
}
Log(string.Format("Reading {0}...", path));
int bytesPerFrame = 1024 * 10;
var bytesRemaining = file.Length;
var binaryReader = new BinaryReader(file);
var allBytes = new List();
while (bytesRemaining > 0)
{
var bytes = binaryReader.ReadBytes(bytesPerFrame);
allBytes.AddRange(bytes);
bytesRemaining -= bytesPerFrame;
yield return null;
}
var data = new NativeArray(allBytes.Count, Allocator.Temp);
data.CopyFrom(allBytes.ToArray());
Log(string.Format("Deserializing to ARWorldMap...", path));
ARWorldMap worldMap;
if (ARWorldMap.TryDeserialize(data, out worldMap))
data.Dispose();
if (worldMap.valid)
{
Log("Deserialized successfully.");
}
else
{
Debug.LogError("Data is not a valid ARWorldMap.");
yield break;
}
Log("Apply ARWorldMap to current session.");
sessionSubsystem.ApplyWorldMap(worldMap);
}
void SaveAndDisposeWorldMap(ARWorldMap worldMap)
{
Log("Serializing ARWorldMap to byte array...");
var data = worldMap.Serialize(Allocator.Temp);
Log(string.Format("ARWorldMap has {0} bytes.", data.Length));
var file = File.Open(path, FileMode.Create);
var writer = new BinaryWriter(file);
writer.Write(data.ToArray());
writer.Close();
data.Dispose();
worldMap.Dispose();
Log(string.Format("ARWorldMap written to {0}", path));
}
#endif
string path
{
get
{
return Path.Combine(Application.persistentDataPath, "my_session.worldmap");
}
}
bool supported
{
get
{
#if UNITY_IOS
var sessionSubsystem = (ARKitSessionSubsystem)m_ARSession.subsystem;
if (sessionSubsystem != null)
return sessionSubsystem.worldMapSupported;
#endif
return false;
}
}
void Awake()
{
m_LogMessages = new List();
}
void Log(string logMessage)
{
m_LogMessages.Add(logMessage);
}
static void SetActive(Button button, bool active)
{
if (button != null)
button.gameObject.SetActive(active);
}
static void SetActive(Text text, bool active)
{
if (text != null)
text.gameObject.SetActive(active);
}
static void SetText(Text text, string value)
{
if (text != null)
text.text = value;
}
void Update()
{
if (supported)
{
SetActive(errorText, false);
SetActive(saveButton, true);
SetActive(loadButton, true);
SetActive(mappingStatusText, true);
}
else
{
SetActive(errorText, true);
SetActive(saveButton, false);
SetActive(loadButton, false);
SetActive(mappingStatusText, false);
}
#if UNITY_IOS
var sessionSubsystem = (ARKitSessionSubsystem)m_ARSession.subsystem;
#else
XRSessionSubsystem sessionSubsystem = null;
#endif
if (sessionSubsystem == null)
return;
var numLogsToShow = 20;
string msg = "";
for (int i = Mathf.Max(0, m_LogMessages.Count - numLogsToShow); i < m_LogMessages.Count; ++i)
{
msg += m_LogMessages[i];
msg += "\n";
}
SetText(logText, msg);
#if UNITY_IOS
SetText(mappingStatusText, string.Format("Mapping Status: {0}", sessionSubsystem.worldMappingStatus));
#endif
}
List m_LogMessages;
}