您的位置:首页 > 移动开发

HoloToolkit项目源码剖析 - Spatial Mapping功能实现

2016-07-01 08:37 267 查看
就像我之前所描述的,HoloToolkit项目是微软基于Unity内置的底层API封装的一套工具集合,帮助我们快速使用Unity集成开发HoloLens应用。

本文主要通过源码研究其中Spatial Mapping的实现,关于底层的API细节,请阅读我前一篇文章:HoloLens开发手记 - Unity之Spatial mapping 空间映射

0x00 组件结构

Spatial Mapping目录下有很多内容,其中Prefabs目录里有我们可以直接使用的预置组件,本文关注的重点是Scripts目录的脚本。组件目录结构如下:



本文重点研究SpatialMappingObserver.cs和SpatialMappingSource.cs,这是当前组件的核心内容。



0x01 SpatialMappingObserver.cs

源码地址:https://github.com/Microsoft/HoloToolkit-Unity/blob/master/Assets/HoloToolkit/SpatialMapping/Scripts/SpatialMappingObserver.cs

我们先分析其属性,如下:

//每立方米网格三角形数量,控制mesh质量
public float TrianglesPerCubicMeter = 500f;

//当前Observer检测的空间范围
public Vector3 Extents = Vector3.one * 10.0f;

//刷新时间间隔
public float TimeBetweenUpdates = 3.5f;

//用于扫描空间平面的核心组件
private SurfaceObserver observer;

//存储已构建的空间网格对象
private Dictionary<int, GameObject> surfaces = new Dictionary<int, GameObject>();

//SurfaceData队列,用于生成空间mesh
private Queue<SurfaceData> surfaceWorkQueue = new Queue<SurfaceData>();

//为了避免同一时刻生成太多mesh,保证同一时刻只生成一个mesh,这个变量用于判断当前时刻是否有mesh正在创建
private bool surfaceWorkOutstanding = false;

//用于追踪Observer对象上次更新时间
private float updateTime;

//表示当前扫描状态,Running和Stopped两种状态
public ObserverStates ObserverState { get; private set; }


再分析其程序逻辑及流程:

Awake()方法最先被执行,这里对SurfaceObserver对象进行了初始化。

private void Awake()
{
observer = new SurfaceObserver();
ObserverState = ObserverStates.Stopped;
}


接下来是Start()方法,里面设定了扫描的空间范围。

private void Start()
{
observer.SetVolumeAsAxisAlignedBox(Vector3.zero, Extents);
}


Update()方法中则会根据当前状态来调用API请求空间表面信息或者生成mesh对象

private void Update()
{

if (ObserverState == ObserverStates.Running)
{
// 如果当前没有再生成mesh,且SurfaceData中有需要生成mesh的对象
if (surfaceWorkOutstanding == false && surfaceWorkQueue.Count > 0)
{

SurfaceData surfaceData = surfaceWorkQueue.Dequeue();

// 如果能成功请求到mesh对象,当前任务状态变为生成mesh中
surfaceWorkOutstanding = observer.RequestMeshAsync(surfaceData, SurfaceObserver_OnDataReady);
}
//如果当前没有任务运行且上次更新距现在大于时间间隔,则重新请求SurfaceData数据
else if (surfaceWorkOutstanding == false && (Time.time - updateTime) >= TimeBetweenUpdates)
{
observer.Update(SurfaceObserver_OnSurfaceChanged);
updateTime = Time.time;
}
}
}


SurfaceObserver_OnDataReady()事件方法用于处理使用SurfaceData请求到的mesh对象信息,用于后续的使用,比如处理其材质效果等。

private void SurfaceObserver_OnDataReady(SurfaceData cookedData, bool outputWritten, float elapsedCookTimeSeconds)
{
GameObject surface;
if (surfaces.TryGetValue(cookedData.id.handle, out surface))
{
// 设置 renderer组件的材质.
MeshRenderer renderer = surface.GetComponent<MeshRenderer>();
renderer.sharedMaterial = SpatialMappingManager.Instance.SurfaceMaterial;
//是否渲染mesh对象
renderer.enabled = SpatialMappingManager.Instance.DrawVisualMeshes;

if (SpatialMappingManager.Instance.CastShadows == false)
{
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
}
}

surfaceWorkOutstanding = false;
}


SurfaceObserver_OnSurfaceChanged()事件方法用于处理SurfaceObserver获取到的空间表面数据,用于后续的请求mesh对象操作。

private void SurfaceObserver_OnSurfaceChanged(SurfaceId id, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime)
{
// 判断当前扫描状态
if (ObserverState != ObserverStates.Running)
{
return;
}

GameObject surface;

switch (changeType)
{

case SurfaceChange.Added:
case SurfaceChange.Updated:
// 检测当前表面是否已被扫描过
if (!surfaces.TryGetValue(id.handle, out surface))
{
// 创建一个和当前表面关联的mesh对象
surface = AddSurfaceObject(null, string.Format("Surface-{0}", id.handle), transform);

surface.AddComponent<WorldAnchor>();

// 将surface对象加入已知空间表面字典
surfaces.Add(id.handle, surface);
}

// 请求生成或更新对应的mesh对象
QueueSurfaceDataRequest(id, surface);
break;

case SurfaceChange.Removed:
// 移除关联的mesh对象
if (surfaces.TryGetValue(id.handle, out surface))
{
surfaces.Remove(id.handle);
Destroy(surface);
}
break;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: