Hololens开发手记 - 空间映射(放置物体)

本文主要讲述在实现空间映射(SpatialMapping)后,将场景扫描的网格转换为平面,然后在此基础上创建游戏对象,并将游戏对象放置到转换后的垂直平面或者水平平面上。

本文示例是在空间映射示例的基础上进行进一步修改。

空间映射 - 放置物体


1.在场景中添加Cursor组件(直接使用HoloToolKit中的Cursor的prefab),添加后既可以看见Cursor组件集成了GazeManager.cs 、GestureManager.cs等一些必要的脚本。

2.向场景中添加SpatialMapping Prefab,具体在HoloToolKit -> SpatialMapping -> Prefabs中可以找到该预制体。

SpatialMapping 包含的脚本,在空间映射已有详细阐述,在此不加重复。

3.创建空的游戏对象 SpatialProcessing

在SpatialProcessing上添加 SurfaceMeshesToPlanes.csRemoveSurfaceVertices.cs脚本组件 (可以直接在HoloToolkit->SpatialMapping->Scripts中找到)

SurfaceMeshesToPlanes.cs脚本主要是用来将扫描空间后生成的网格转换成平面

添加完该脚本后,在HoloToolkit->SpatialMapping->Prefabs中找到SurfacePlane,将其拖拽到SurfaceMeshesToPlanes.cs的SurfacePlanePrefab上

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if !UNITY_EDITOR
using System.Threading;
using System.Threading.Tasks;
#else
using UnityEditor;
#endif

namespace HoloToolkit.Unity
{
    /// <summary>
    /// SurfaceMeshesToPlanes will find and create planes based on the meshes returned by the SpatialMappingManager's Observer.
    /// 根据 SpatialMappingManager's Observer 返回的网格信息,找到网格并基于网格信息创建plane进行替换
    /// </summary>
    public class SurfaceMeshesToPlanes : Singleton<SurfaceMeshesToPlanes>
    {
        [Tooltip("Currently active planes found within the Spatial Mapping Mesh.")]
        public List<GameObject> ActivePlanes;

        [Tooltip("Object used for creating and rendering Surface Planes.")]
        public GameObject SurfacePlanePrefab;

        [Tooltip("Minimum area required for a plane to be created.")]
        public float MinArea = 0.025f;

        /// <summary>
        /// Determines which plane types should be rendered.
        /// 确定应渲染哪些平面类型
        /// </summary>
        [HideInInspector]
        public PlaneTypes drawPlanesMask =
            (PlaneTypes.Wall | PlaneTypes.Floor | PlaneTypes.Ceiling | PlaneTypes.Table);

        /// <summary>
        /// Determines which plane types should be discarded.
        /// Use this when the spatial mapping mesh is a better fit for the surface (ex: round tables).
        /// 确定哪种类型的平面应该被丢弃
        /// </summary>
        [HideInInspector]
        public PlaneTypes destroyPlanesMask = PlaneTypes.Unknown;

        /// <summary>
        /// Floor y value, which corresponds to the maximum horizontal area found below the user's head position.
        /// 地板的Y坐标值,其对应于在用户头部位置下方找到的最大水平区域
        /// This value is reset by SurfaceMeshesToPlanes when the max floor plane has been found.
        /// </summary>
        public float FloorYPosition { get; private set; }

        /// <summary>
        /// Ceiling y value, which corresponds to the maximum horizontal area found above the user's head position.
        /// 天花板的Y坐标值,其对应于在用户头部位置上方找到的最大水平区域
        /// This value is reset by SurfaceMeshesToPlanes when the max ceiling plane has been found.
        /// </summary>
        public float CeilingYPosition { get; private set; }

        /// <summary>
        /// Delegate which is called when the MakePlanesCompleted event is triggered.
        /// 当平面创建完成后进行触发
        /// </summary>
        /// <param name="source"></param>
        /// <param name="args"></param>
        public delegate void EventHandler(object source, EventArgs args);

        /// <summary>
        /// EventHandler which is triggered when the MakePlanesRoutine is finished.
        /// 当MakePlanesRoutine完成,进行触发  
        /// </summary>
        public event EventHandler MakePlanesComplete;

        /// <summary>
        /// Empty game object used to contain all planes created by the SurfaceToPlanes class.
        /// </summary>
        private GameObject planesParent;

        /// <summary>
        /// Used to align planes with gravity so that they appear more level.
        /// 用于对齐具有重力的平面,以使它们看起来更平坦
        /// </summary>
        private float snapToGravityThreshold = 5.0f;

        /// <summary>
        /// Indicates if SurfaceToPlanes is currently creating planes based on the Spatial Mapping Mesh.
        /// 基于空间映射网格来标记当前是否正在创建平面
        /// </summary>
        private bool makingPlanes = false;

#if UNITY_EDITOR
        /// <summary>
        /// How much time (in sec), while running in the Unity Editor, to allow RemoveSurfaceVertices to consume before returning control to the main program.
        /// </summary>
        private static readonly float FrameTime = .016f;
#else
        /// <summary>
        /// How much time (in sec) to allow RemoveSurfaceVertices to consume before returning control to the main program.
        /// </summary>
        private static readonly float FrameTime = .008f;
#endif

        // GameObject initialization.
        private void Start()
        {
            makingPlanes = false;
            ActivePlanes = new List<GameObject>();
            planesParent = new GameObject("SurfacePlanes");
            planesParent.transform.position = Vector3.zero;
            planesParent.transform.rotation = Quaternion.identity;
        }

        /// <summary>
        /// 根据SpatialMappingManager's SurfaceObserver生成的网格信息创建平面
        /// </summary>
        public void MakePlanes()
        {
            if (!makingPlanes)
            {
                makingPlanes = true;
                // Processing the mesh can be expensive...
                // We use Coroutine to split the work across multiple frames and avoid impacting the frame rate too much.
                //使用协程将工作分割在多个帧中完成,避免影响帧率太多。
                StartCoroutine(MakePlanesRoutine());
            }
        }

        /// <summary>
        ///返回所有指定的平面类型的平面列表
        /// </summary>
        /// <param name="planeTypes">A flag which includes all plane type(s) that should be returned.</param>
        /// <returns>预期的平面类型的平面列表</returns>
        public List<GameObject> GetActivePlanes(PlaneTypes planeTypes)
        {
            List<GameObject> typePlanes = new List<GameObject>();

            foreach (GameObject plane in ActivePlanes)
            {
                SurfacePlane surfacePlane = plane.GetComponent<SurfacePlane>();

                if (surfacePlane != null)
                {
                    if((planeTypes & surfacePlane.PlaneType) == surfacePlane.PlaneType)
                    {
                        typePlanes.Add(plane);
                    }
                }
            }

            return typePlanes;
        }

        /// <summary>
        /// Iterator block, analyzes surface meshes to find planes and create new 3D cubes to represent each plane.
        /// 分析表面网格以找到平面并创建新的3D立方体来表示每个平面。
        /// </summary>
        /// <returns>Yield result.</returns>
        private IEnumerator MakePlanesRoutine()
        {
            //删除之前生成的平面信息
            for (int index = 0; index < ActivePlanes.Count; index++)
            {
                Destroy(ActivePlanes[index]);
            }

            // 暂停任务,等待下一帧处理下面的代码
            yield return null;
            float start = Time.realtimeSinceStartup;

            ActivePlanes.Clear();

            //从SpatialMappingManager中获取最新的网格信息
            List<PlaneFinding.MeshData> meshData = new List<PlaneFinding.MeshData>();
            List<MeshFilter> filters = SpatialMappingManager.Instance.GetMeshFilters();

            for (int index = 0; index < filters.Count; index++)
            {
                MeshFilter filter = filters[index];
                if (filter != null && filter.sharedMesh != null)
                {
                    // 修复表面网格法线,得到正确的平面方向
                    filter.mesh.RecalculateNormals();
                    meshData.Add(new PlaneFinding.MeshData(filter));
                }

                if ((Time.realtimeSinceStartup - start) > FrameTime)
                {
                    // 暂停任务,等待下一帧处理下面的代码
                    yield return null;
                    start = Time.realtimeSinceStartup;
                }
            }

            // 暂停任务,等待下一帧处理下面的代码
            yield return null;

#if !UNITY_EDITOR
            // When not in the unity editor we can use a cool background task to help manage FindPlanes().
            Task<BoundedPlane[]> planeTask = Task.Run(() => PlaneFinding.FindPlanes(meshData, snapToGravityThreshold, MinArea));

            while (planeTask.IsCompleted == false)
            {
                yield return null;
            }

            BoundedPlane[] planes = planeTask.Result;
#else
            // In the unity editor, the task class isn't available, but perf is usually good, so we'll just wait for FindPlanes to complete.
            BoundedPlane[] planes = PlaneFinding.FindPlanes(meshData, snapToGravityThreshold, MinArea);
#endif

            // Pause our work here, and continue on the next frame.
            yield return null;
            start = Time.realtimeSinceStartup;

            float maxFloorArea = 0.0f;
            float maxCeilingArea = 0.0f;
            FloorYPosition = 0.0f;
            CeilingYPosition = 0.0f;
            float upNormalThreshold = 0.9f;

            if(SurfacePlanePrefab != null && SurfacePlanePrefab.GetComponent<SurfacePlane>() != null)
            {
                upNormalThreshold = SurfacePlanePrefab.GetComponent<SurfacePlane>().UpNormalThreshold;
            }

            // 找到地面及天花板
            // 定义用户头部位置以下的面积最大的水平区域为地面
            // 定义用户头部位置以上的面积最大的水平区域为天花板
            for (int i = 0; i < planes.Length; i++)
            {
                BoundedPlane boundedPlane = planes[i];
                if (boundedPlane.Bounds.Center.y < 0 && boundedPlane.Plane.normal.y >= upNormalThreshold)
                {
                    maxFloorArea = Mathf.Max(maxFloorArea, boundedPlane.Area);
                    if (maxFloorArea == boundedPlane.Area)
                    {
                        FloorYPosition = boundedPlane.Bounds.Center.y;
                    }
                }
                else if (boundedPlane.Bounds.Center.y > 0 && boundedPlane.Plane.normal.y <= -(upNormalThreshold))
                {
                    maxCeilingArea = Mathf.Max(maxCeilingArea, boundedPlane.Area);
                    if (maxCeilingArea == boundedPlane.Area)
                    {
                        CeilingYPosition = boundedPlane.Bounds.Center.y;
                    }
                }
            }

            // 创建SurfacePlane对象以表示在空间映射网格中找到的每个平面。
            for (int index = 0; index < planes.Length; index++)
            {
                GameObject destPlane;
                BoundedPlane boundedPlane = planes[index];

                // 实例化一个平面对象,它将具有和BoundedPlane对象相同的边界
                if (SurfacePlanePrefab != null && SurfacePlanePrefab.GetComponent<SurfacePlane>() != null)
                {
                    destPlane = Instantiate(SurfacePlanePrefab);
                }
                else
                {
                    destPlane = GameObject.CreatePrimitive(PrimitiveType.C
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值