本帖主要描写扫描场景的功能实现,以及一些需要注意的问题。跟上层贴有所关联,想要更多了解请移步链接。
场景中有几个重要的预设需要添加。目录如下:
其中:
SparseSpatialMap | 用于扫描空间成成点云信息,点云可以将空间数据以点的信息保存下来 | SparseSpatialMapController |
WorldRoot | 点云的空间位置的基本参照 | WorldRootController |
EasyAR_SparseSpatialMapWorker | 点云保存上传等一些操作的入口 | ARSession/SparseSpatialMapWorkerFrameFilter |
场景中,我用Text来debug一些信息,可以显示当前的进度。
操作流程:进入场景开始扫描云点,然后点击保存之后弹出窗口,开始编辑地图名字,编辑完成后,点击上传。
然后创建SceneMaster来对场景进行管理。添加脚本BuildMapController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using easyar;
using System;
using SpatialMap_SparseSpatialMap;
using UnityEngine.SceneManagement;
public class BuildMapController : MonoBehaviour
{
//稀疏空间地图相关对象
private ARSession session;
private SparseSpatialMapWorkerFrameFilter mapWorker;
private SparseSpatialMapController map;
/// <summary>
/// 保存按钮
/// </summary>
private Button btnSave; //保存按钮
private Button btnUpload; //输入名字后上传
private GameObject SavePanel;
/// <summary>
/// 显示文本
/// </summary>
private Text text;
private InputField inputField;
private void Awake()
{
//稀疏空间地图初始
session = FindObjectOfType<ARSession>();
mapWorker = FindObjectOfType<SparseSpatialMapWorkerFrameFilter>();
map = FindObjectOfType<SparseSpatialMapController>();
SavePanel = GameObject.Find("MapName");
inputField = SavePanel.transform.Find("Panel/InputField").GetComponent<InputField>();
btnUpload = SavePanel.transform.Find("Panel/Btn_Upload").GetComponent<Button>();
btnSave = GameObject.Find("Canvas/Map/Btn_SaveMap").GetComponent<Button>();
//显示文本
text = GameObject.Find("Canvas/Map/Text").GetComponent<Text>();
}
void Start()
{
map.MapWorker = mapWorker;
//注册追踪状态变化事件
session.WorldRootController.TrackingStatusChanged += OnTrackingStatusChanged;
SavePanel.SetActive(false);
//初始化保存按钮
btnSave.onClick.AddListener(OpenUploadPanel);
btnUpload.onClick.AddListener(Save);
btnSave.interactable = false;
if (session.WorldRootController.TrackingStatus == MotionTrackingStatus.Tracking)
{
btnSave.interactable = true;
}
else
{
btnSave.interactable = false;
}
}
/// <summary>
/// 保存地图方法
/// </summary>
private void OpenUploadPanel()
{
btnSave.interactable = false;
SavePanel.SetActive(true);
}
string mapName = "";
private void Save()
{
//注册地图保存结果反馈事件
mapWorker.BuilderMapController.MapHost += SaveMapHostBack;
//mapWorker.BuilderMapController.MapLoad += LoadMapHostBack;
//保存地图
try
{
if (!inputField.text.Equals(string.Empty))
{
mapName = "Map" + inputField.text;
}
else
{
mapName = "Map" + DateTime.Now.ToString("YYYY-MM-DD-HH-mm-ss");
}
//保存地图
mapWorker.BuilderMapController.Host(mapName, null);
text.text = "开始保存地图,请稍等。";
}
catch (Exception ex)
{
btnSave.interactable = true;
text.text = "保存出错:" + ex.Message;
}
}
/// <summary>
/// 保存地图反馈
/// </summary>
/// <param name="mapInfo">地图信息</param>
/// <param name="isSuccess">成功标识</param>
/// <param name="error">错误信息</param>
private void SaveMapHostBack(SparseSpatialMapController.SparseSpatialMapInfo mapInfo, bool isSuccess, string error)
{
if (isSuccess)
{
SavePanel.SetActive(false);
PlayerPrefs.SetString("MapID", mapInfo.ID);
PlayerPrefs.SetString("MapName", mapInfo.Name);
text.text = "地图保存成功。\r\nMapID:" + mapInfo.ID + "\r\nMapName:" + mapInfo.Name;
MapMetaManager.Save(new MapMeta(mapInfo, new List<MapMeta.PropInfo>()), MapMetaManager.FileNameType.Name);
Invoke("BackMain", 3);
}
else
{
btnSave.interactable = true;
text.text = "地图保存出错:" + error;
}
}
public void BackMain()
{
SceneManager.LoadScene("MainMapScene");
}
/// <summary>
/// 摄像机状态变化
/// </summary>
/// <param name="status">状态</param>
private void OnTrackingStatusChanged(MotionTrackingStatus status)
{
if (status == MotionTrackingStatus.Tracking)
{
btnSave.interactable = true;
text.text = "进入跟踪状态。";
}
else
{
btnSave.interactable = false;
text.text = "退出跟踪状态。" + status.ToString();
}
}
}
代码相关:
在Awake获取到三个主要的脚本,然后在start中完成初始化。
1.将 mapWorker 填充进 mapController 中的MapWorker属性中。这一步也可以在场景中直接拖拽也行。
2.在session.WorldRootController.TrackingStatusChanged 注册事件。
///注册追踪状态变化事件
session.WorldRootController.TrackingStatusChanged += OnTrackingStatusChanged;
这个事件的用处是可以监听当前的追踪状态。拿到追踪状态就可做一些事情。例如我用来提示,也可以用来操作一些游戏对象的状态等,或者弹出UI提醒用户做一些操作等。
private void OnTrackingStatusChanged(MotionTrackingStatus status)
{
if (status == MotionTrackingStatus.Tracking)
{
btnSave.interactable = true;
text.text = "进入跟踪状态。";
}
else
{
btnSave.interactable = false;
text.text = "退出跟踪状态。" + status.ToString();
}
}
3.扫描场景。扫描的过程中,随着点云数量的增加,手机会变的逐渐卡顿,最大的场景支持100平方米,其实也不大,就10*10的地块。实际使用下来一般50平方左右的空间最舒服。
4.点击保存按钮弹出框框,编辑名字后点击上传。
string mapName = "";
private void Save()
{
//注册地图保存结果反馈事件
mapWorker.BuilderMapController.MapHost += SaveMapHostBack;
//mapWorker.BuilderMapController.MapLoad += LoadMapHostBack;
//保存地图
try
{
if (!inputField.text.Equals(string.Empty))
{
mapName = "Map" + inputField.text;
}
else
{
mapName = "Map" + DateTime.Now.ToString("YYYY-MM-DD-HH-mm-ss");
}
//保存地图
mapWorker.BuilderMapController.Host(mapName, null);
text.text = "开始保存地图,请稍等。";
}
catch (Exception ex)
{
btnSave.interactable = true;
text.text = "保存出错:" + ex.Message;
}
}
首先注册一个保存地图后的回调。该回调在完成上传后调用,不管成功与否。
mapWorker.BuilderMapController.MapHost += SaveMapHostBack;
然后 调用 host 方法来上传地图。
//保存地图
mapWorker.BuilderMapController.Host(mapName, null);
然后是对回调信息的处理。处理很简单,就是将这个地图的ID和名字用 PlayerPrefs 存到本地,这个东西自己的觉得方便就用了。大家可以用自己喜欢的方法Json或者其他本地化的方法实现。
/// <summary>
/// 保存地图回调
/// </summary>
/// <param name="mapInfo">地图信息</param>
/// <param name="isSuccess">成功标识</param>
/// <param name="error">错误信息</param>
private void SaveMapHostBack(SparseSpatialMapController.SparseSpatialMapInfo mapInfo, bool isSuccess, string error)
{
if (isSuccess)
{
SavePanel.SetActive(false);
PlayerPrefs.SetString("MapID", mapInfo.ID);
PlayerPrefs.SetString("MapName", mapInfo.Name);
text.text = "地图保存成功。\r\nMapID:" + mapInfo.ID + "\r\nMapName:" + mapInfo.Name;
MapMetaManager.Save(new MapMeta(mapInfo, new List<MapMeta.PropInfo>()), MapMetaManager.FileNameType.Name);
Invoke("BackMain", 3);
}
else
{
btnSave.interactable = true;
text.text = "地图保存出错:" + error;
}
}
然后是 MapMetaManager 这个脚本。这个脚本用来处理点云信息。将meta 信息存储到本地先,用来下一步场景编辑时使用。
//================================================================================================================================
//
// Copyright (c) 2015-2020 VisionStar Information Technology (Shanghai) Co., Ltd. All Rights Reserved.
// EasyAR is the registered trademark or trademark of VisionStar Information Technology (Shanghai) Co., Ltd in China
// and other countries for the augmented reality technology developed by VisionStar Information Technology (Shanghai) Co., Ltd.
//
//================================================================================================================================
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using UnityEngine;
namespace SpatialMap_SparseSpatialMap
{
public class MapMetaManager
{
public enum FileNameType
{
ID, Name
}
private static readonly string root = Application.persistentDataPath + "/SparseSpatialMap";
public static List<MapMeta> LoadAll()
{
//Debug.Log(root);
var metas = new List<MapMeta>();
var dirRoot = GetRootPath();
try
{
foreach (var path in Directory.GetFiles(dirRoot, "*.meta"))
{
try
{
metas.Add(JsonUtility.FromJson<MapMeta>(File.ReadAllText(path)));
}
catch (System.Exception e)
{
Debug.LogError(e.Message);
}
}
}
catch (System.Exception e)
{
Debug.LogError(e.Message);
}
return metas;
}
public static bool Save(MapMeta meta, FileNameType fileNameType = FileNameType.ID)
{
try
{
switch (fileNameType)
{
case FileNameType.ID:
File.WriteAllText(GetPath(meta.Map.ID), JsonUtility.ToJson(meta, true));
break;
case FileNameType.Name:
File.WriteAllText(GetPath(meta.Map.Name), JsonUtility.ToJson(meta, true));
break;
}
}
catch (System.Exception e)
{
Debug.LogError(e.Message);
return false;
}
return true;
}
public static bool Save(PointData data)
{
try
{
File.WriteAllText(GetPathTxt(data.mapName + "_PointCloud"), JsonConvert.SerializeObject(data));
}
catch (System.Exception e)
{
Debug.LogError(e.Message);
return false;
}
return true;
}
public static List<PointData> Load_PointCloud<PointData>()
{
var dirRoot = GetRootPath();
var datas = new List<PointData>();
try
{
foreach (var path in Directory.GetFiles(dirRoot, "*.txt"))
{
try
{
//Debug.Log(path);
datas.Add(JsonConvert.DeserializeObject<PointData>(File.ReadAllText(path)));
}
catch (System.Exception e)
{
Debug.LogError(e.Message);
}
}
}
catch (System.Exception e)
{
Debug.LogError(e.Message);
}
return datas;
}
public static bool Delete(MapMeta meta)
{
if (!File.Exists(GetPath(meta.Map.ID)))
{
return false;
}
try
{
File.Delete(GetPath(meta.Map.ID));
}
catch (System.Exception e)
{
Debug.LogError(e.Message);
return false;
}
return true;
}
private static string GetRootPath()
{
var path = root;
if (!File.Exists(path))
{
Directory.CreateDirectory(path);
}
return path;
}
private static string GetPath(string id)
{
return GetRootPath() + "/" + id + ".meta";
}
private static string GetPathTxt(string id)
{
return GetRootPath() + "/" + id + ".txt";
}
}
}