当前位置:   article > 正文

Hololens入门之空间映射(放置物体)_hololens boundscontrol

hololens boundscontrol

Hololens入门之空间映射(放置物体)

本文讲述怎样使用HoloToolkit构建工程,实现在空间映射后,将网格转换为平面,然后构建游戏对象,将游戏对象放置到垂直平面或者水平平面的功能。

本文示例在 Hololens入门之凝视 示例的基础上进行修改

1、在Manager上添加GestureManager.cs脚本组件(直接使用Holotoolkit中的脚本,该脚本在本文中不在讲述,可前往手势识别章节进行查看)


2、在HoloToolkit->SpatialMapping->Prefabs 中找到并添加SpatialMapping Prefabs
SpatialMapping 中脚本在本文中不再描述,可以前往 Hololens入门之空间映射 进行查看


3、创建空的游戏对象 SpatialProcessing
在SpatialProcessing上添加SurfaceMeshesToPlanes.cs 和 RemoveSurfaceVertices.cs脚本组件 (可以直接在HoloToolkit->SpatialMapping->Scripts->SpatialProcessing中找到)
SurfaceMeshesToPlanes.cs脚本主要是用来将扫描空间后生成的网格转换成平面,添加完该脚本后,在HoloToolkit->SpatialMapping->Prefabs中找到SurfacePlane,将其拖拽到SurfaceMeshesToPlanes.cs的SurfacePlanePrefab上

  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License. See LICENSE in the project root for license information.
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using UnityEngine;
  7. #if !UNITY_EDITOR
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. #else
  11. using UnityEditor;
  12. #endif
  13. namespace HoloToolkit.Unity
  14. {
  15. /// <summary>
  16. /// 根据SpatialMappingManager's Observer返回的网格信息,找到并创建平面.
  17. /// </summary>
  18. public class SurfaceMeshesToPlanes : Singleton<SurfaceMeshesToPlanes>
  19. {
  20. [Tooltip("Currently active planes found within the Spatial Mapping Mesh.")]
  21. public List<GameObject> ActivePlanes;
  22. [Tooltip("Object used for creating and rendering Surface Planes.")]
  23. public GameObject SurfacePlanePrefab;
  24. [Tooltip("Minimum area required for a plane to be created.")]
  25. public float MinArea = 0.025f;
  26. //配置那种类型的平面被渲染
  27. [HideInInspector]
  28. public PlaneTypes drawPlanesMask =
  29. (PlaneTypes.Wall | PlaneTypes.Floor | PlaneTypes.Ceiling | PlaneTypes.Table);
  30. // 配置哪种类型的平面被丢弃
  31. [HideInInspector]
  32. public PlaneTypes destroyPlanesMask = PlaneTypes.Unknown;
  33. // 地面的y坐标,处于用户头部位置以下的面积最大的水平区域
  34. public float FloorYPosition { get; private set; }
  35. //天花板y坐标,处于用户头部位置以上的面积最大的水平区域
  36. public float CeilingYPosition { get; private set; }
  37. /// <summary>
  38. /// 当平面创建完成进行触发
  39. /// </summary>
  40. /// <param name="source"></param>
  41. /// <param name="args"></param>
  42. public delegate void EventHandler(object source, EventArgs args);
  43. /// <summary>
  44. /// 当MakePlanesRoutine完成,进行触发
  45. /// </summary>
  46. public event EventHandler MakePlanesComplete;
  47. /// <summary>
  48. /// Empty game object used to contain all planes created by the SurfaceToPlanes class.
  49. /// </summary>
  50. private GameObject planesParent;
  51. /// <summary>
  52. /// Used to align planes with gravity so that they appear more level.
  53. /// </summary>
  54. private float snapToGravityThreshold = 5.0f;
  55. /// <summary>
  56. /// 标记当前是否正在创建平面
  57. /// </summary>
  58. private bool makingPlanes = false;
  59. #if UNITY_EDITOR
  60. /// <summary>
  61. /// How much time (in sec), while running in the Unity Editor, to allow RemoveSurfaceVertices to consume before returning control to the main program.
  62. /// </summary>
  63. private static readonly float FrameTime = .016f;
  64. #else
  65. /// <summary>
  66. /// How much time (in sec) to allow RemoveSurfaceVertices to consume before returning control to the main program.
  67. /// </summary>
  68. private static readonly float FrameTime = .008f;
  69. #endif
  70. private void Start()
  71. {
  72. makingPlanes = false;
  73. ActivePlanes = new List<GameObject>();
  74. planesParent = new GameObject("SurfacePlanes");
  75. planesParent.transform.position = Vector3.zero;
  76. planesParent.transform.rotation = Quaternion.identity;
  77. }
  78. /// <summary>
  79. /// 根据SpatialMappingManager's SurfaceObserver.生成的网格信息创建平面
  80. /// </summary>
  81. public void MakePlanes()
  82. {
  83. if (!makingPlanes)
  84. {
  85. makingPlanes = true;
  86. //为了不影响帧率,使用协程将工作分割到不同帧中完成
  87. StartCoroutine(MakePlanesRoutine());
  88. }
  89. }
  90. /// <summary>
  91. /// 返回所有指定的平面类型的平面列表
  92. /// </summary>
  93. /// <param name="planeTypes">平面类型</param>
  94. /// <returns>预期的平面类型的平面列表</returns>
  95. public List<GameObject> GetActivePlanes(PlaneTypes planeTypes)
  96. {
  97. List<GameObject> typePlanes = new List<GameObject>();
  98. foreach (GameObject plane in ActivePlanes)
  99. {
  100. SurfacePlane surfacePlane = plane.GetComponent<SurfacePlane>();
  101. if (surfacePlane != null)
  102. {
  103. if ((planeTypes & surfacePlane.PlaneType) == surfacePlane.PlaneType)
  104. {
  105. typePlanes.Add(plane);
  106. }
  107. }
  108. }
  109. return typePlanes;
  110. }
  111. /// <summary>
  112. /// 分析网格信息,找到并用新的对象替换每个平面
  113. /// </summary>
  114. /// <returns>Yield result.</returns>
  115. private IEnumerator MakePlanesRoutine()
  116. {
  117. //删除之前生成的平面信息
  118. for (int index = 0; index < ActivePlanes.Count; index++)
  119. {
  120. Destroy(ActivePlanes[index]);
  121. }
  122. // 暂停任务,等待下一帧处理下面的代码
  123. yield return null;
  124. float start = Time.realtimeSinceStartup;
  125. ActivePlanes.Clear();
  126. // 从SpatialMappingManager中获取最新的网格信息
  127. List<PlaneFinding.MeshData> meshData = new List<PlaneFinding.MeshData>();
  128. List<MeshFilter> filters = SpatialMappingManager.Instance.GetMeshFilters();
  129. for (int index = 0; index < filters.Count; index++)
  130. {
  131. MeshFilter filter = filters[index];
  132. if (filter != null && filter.sharedMesh != null)
  133. {
  134. //修复表面网格法线,得到正确的平面方向
  135. filter.mesh.RecalculateNormals();
  136. meshData.Add(new PlaneFinding.MeshData(filter));
  137. }
  138. //当处理时间超出设置的每帧的时间,暂停任务,等待下一帧进行处理
  139. if ((Time.realtimeSinceStartup - start) > FrameTime)
  140. {
  141. // 暂停任务,等待下一帧处理下面的代码
  142. yield return null;
  143. start = Time.realtimeSinceStartup;
  144. }
  145. }
  146. // 暂停任务,等待下一帧处理下面的代码
  147. yield return null;
  148. #if !UNITY_EDITOR
  149. // When not in the unity editor we can use a cool background task to help manage FindPlanes().
  150. Task<BoundedPlane[]> planeTask = Task.Run(() => PlaneFinding.FindPlanes(meshData, snapToGravityThreshold, MinArea));
  151. while (planeTask.IsCompleted == false)
  152. {
  153. yield return null;
  154. }
  155. BoundedPlane[] planes = planeTask.Result;
  156. #else
  157. // In the unity editor, the task class isn't available, but perf is usually good, so we'll just wait for FindPlanes to complete.
  158. BoundedPlane[] planes = PlaneFinding.FindPlanes(meshData, snapToGravityThreshold, MinArea);
  159. #endif
  160. // 暂停任务,等待下一帧处理下面的代码
  161. yield return null;
  162. start = Time.realtimeSinceStartup;
  163. float maxFloorArea = 0.0f;
  164. float maxCeilingArea = 0.0f;
  165. FloorYPosition = 0.0f;
  166. CeilingYPosition = 0.0f;
  167. float upNormalThreshold = 0.9f;
  168. if (SurfacePlanePrefab != null && SurfacePlanePrefab.GetComponent<SurfacePlane>() != null)
  169. {
  170. upNormalThreshold = SurfacePlanePrefab.GetComponent<SurfacePlane>().UpNormalThreshold;
  171. }
  172. //找到地面及天花板
  173. //定义用户头部位置以下的面积最大的水平区域为地面
  174. //定义用户头部位置以上的面积最大的水平区域为天花板
  175. for (int i = 0; i < planes.Length; i++)
  176. {
  177. BoundedPlane boundedPlane = planes[i];
  178. if (boundedPlane.Bounds.Center.y < 0 && boundedPlane.Plane.normal.y >= upNormalThreshold)
  179. {
  180. maxFloorArea = Mathf.Max(maxFloorArea, boundedPlane.Area);
  181. if (maxFloorArea == boundedPlane.Area)
  182. {
  183. FloorYPosition = boundedPlane.Bounds.Center.y;
  184. }
  185. }
  186. else if (boundedPlane.Bounds.Center.y > 0 && boundedPlane.Plane.normal.y <= -(upNormalThreshold))
  187. {
  188. maxCeilingArea = Mathf.Max(maxCeilingArea, boundedPlane.Area);
  189. if (maxCeilingArea == boundedPlane.Area)
  190. {
  191. CeilingYPosition = boundedPlane.Bounds.Center.y;
  192. }
  193. }
  194. }
  195. // Create SurfacePlane objects to represent each plane found in the Spatial Mapping mesh.
  196. for (int index = 0; index < planes.Length; index++)
  197. {
  198. GameObject destPlane;
  199. BoundedPlane boundedPlane = planes[index];
  200. // Instantiate a SurfacePlane object, which will have the same bounds as our BoundedPlane object.
  201. if (SurfacePlanePrefab != null && SurfacePlanePrefab.GetComponent<SurfacePlane>() != null)
  202. {
  203. destPlane = Instantiate(SurfacePlanePrefab);
  204. }
  205. else
  206. {
  207. destPlane = GameObject.CreatePrimitive(PrimitiveType.Cube);
  208. destPlane.AddComponent<SurfacePlane>();
  209. destPlane.GetComponent<Renderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
  210. }
  211. destPlane.transform.parent = planesParent.transform;
  212. SurfacePlane surfacePlane = destPlane.GetComponent<SurfacePlane>();
  213. // Set the Plane property to adjust transform position/scale/rotation and determine plane type.
  214. surfacePlane.Plane = boundedPlane;
  215. SetPlaneVisibility(surfacePlane);
  216. if ((destroyPlanesMask & surfacePlane.PlaneType) == surfacePlane.PlaneType)
  217. {
  218. DestroyImmediate(destPlane);
  219. }
  220. else
  221. {
  222. // Set the plane to use the same layer as the SpatialMapping mesh.
  223. destPlane.layer = SpatialMappingManager.Instance.PhysicsLayer;
  224. ActivePlanes.Add(destPlane);
  225. }
  226. // If too much time has passed, we need to return control to the main game loop.
  227. if ((Time.realtimeSinceStartup - start) > FrameTime)
  228. {
  229. // Pause our work here, and continue making additional planes on the next frame.
  230. yield return null;
  231. start = Time.realtimeSinceStartup;
  232. }
  233. }
  234. Debug.Log("Finished making planes.");
  235. //平面创建完成,触发事件
  236. EventHandler handler = MakePlanesComplete;
  237. if (handler != null)
  238. {
  239. handler(this, EventArgs.Empty);
  240. }
  241. makingPlanes = false;
  242. }
  243. /// <summary>
  244. /// Sets visibility of planes based on their type.
  245. /// </summary>
  246. /// <param name="surfacePlane"></param>
  247. private void SetPlaneVisibility(SurfacePlane surfacePlane)
  248. {
  249. surfacePlane.IsVisible = ((drawPlanesMask & surfacePlane.PlaneType) == surfacePlane.PlaneType);
  250. }
  251. }
  252. #if UNITY_EDITOR
  253. /// <summary>
  254. /// Editor extension class to enable multi-selection of the 'Draw Planes' and 'Destroy Planes' options in the Inspector.
  255. /// </summary>
  256. [CustomEditor(typeof(SurfaceMeshesToPlanes))]
  257. public class PlaneTypesEnumEditor : Editor
  258. {
  259. public SerializedProperty drawPlanesMask;
  260. public SerializedProperty destroyPlanesMask;
  261. void OnEnable()
  262. {
  263. drawPlanesMask = serializedObject.FindProperty("drawPlanesMask");
  264. destroyPlanesMask = serializedObject.FindProperty("destroyPlanesMask");
  265. }
  266. public override void OnInspectorGUI()
  267. {
  268. base.OnInspectorGUI();
  269. serializedObject.Update();
  270. drawPlanesMask.intValue = (int)((PlaneTypes)EditorGUILayout.EnumMaskField
  271. ("Draw Planes", (PlaneTypes)drawPlanesMask.intValue));
  272. destroyPlanesMask.intValue = (int)((PlaneTypes)EditorGUILayout.EnumMaskField
  273. ("Destroy Planes", (PlaneTypes)destroyPlanesMask.intValue));
  274. serializedObject.ApplyModifiedProperties();
  275. }
  276. }
  277. #endif
  278. }

RemoveSurfaceVertices.cs 主要用来删除网格三角型

  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License. See LICENSE in the project root for license information.
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using UnityEngine;
  7. namespace HoloToolkit.Unity
  8. {
  9. /// <summary>
  10. /// RemoveSurfaceVertices will remove any vertices from the Spatial Mapping Mesh that fall within the bounding volume.
  11. /// This can be used to create holes in the environment, or to help reduce triangle count after finding planes.
  12. /// </summary>
  13. public class RemoveSurfaceVertices : Singleton<RemoveSurfaceVertices>
  14. {
  15. [Tooltip("The amount, if any, to expand each bounding volume by.")]
  16. public float BoundsExpansion = 0.0f;
  17. /// <summary>
  18. /// Delegate which is called when the RemoveVerticesComplete event is triggered.
  19. /// </summary>
  20. /// <param name="source"></param>
  21. /// <param name="args"></param>
  22. public delegate void EventHandler(object source, EventArgs args);
  23. /// <summary>
  24. /// EventHandler which is triggered when the RemoveSurfaceVertices is finished.
  25. /// </summary>
  26. public event EventHandler RemoveVerticesComplete;
  27. /// <summary>
  28. /// Indicates if RemoveSurfaceVertices is currently removing vertices from the Spatial Mapping Mesh.
  29. /// </summary>
  30. private bool removingVerts = false;
  31. /// <summary>
  32. /// Queue of bounding objects to remove surface vertices from.
  33. /// Bounding objects are queued so that RemoveSurfaceVerticesWithinBounds can be called even when the previous task has not finished.
  34. /// </summary>
  35. private Queue<Bounds> boundingObjectsQueue;
  36. #if UNITY_EDITOR
  37. /// <summary>
  38. /// How much time (in sec), while running in the Unity Editor, to allow RemoveSurfaceVertices to consume before returning control to the main program.
  39. /// </summary>
  40. private static readonly float FrameTime = .016f;
  41. #else
  42. /// <summary>
  43. /// How much time (in sec) to allow RemoveSurfaceVertices to consume before returning control to the main program.
  44. /// </summary>
  45. private static readonly float FrameTime = .008f;
  46. #endif
  47. // GameObject initialization.
  48. private void Start()
  49. {
  50. boundingObjectsQueue = new Queue<Bounds>();
  51. removingVerts = false;
  52. }
  53. /// <summary>
  54. /// Removes portions of the surface mesh that exist within the bounds of the boundingObjects.
  55. /// </summary>
  56. /// <param name="boundingObjects">Collection of GameObjects that define the bounds where spatial mesh vertices should be removed.</param>
  57. public void RemoveSurfaceVerticesWithinBounds(IEnumerable<GameObject> boundingObjects)
  58. {
  59. if (boundingObjects == null)
  60. {
  61. return;
  62. }
  63. if (!removingVerts)
  64. {
  65. removingVerts = true;
  66. AddBoundingObjectsToQueue(boundingObjects);
  67. // We use Coroutine to split the work across multiple frames and avoid impacting the frame rate too much.
  68. StartCoroutine(RemoveSurfaceVerticesWithinBoundsRoutine());
  69. }
  70. else
  71. {
  72. // Add new boundingObjects to end of queue.
  73. AddBoundingObjectsToQueue(boundingObjects);
  74. }
  75. }
  76. /// <summary>
  77. /// Adds new bounding objects to the end of the Queue.
  78. /// </summary>
  79. /// <param name="boundingObjects">Collection of GameObjects which define the bounds where spatial mesh vertices should be removed.</param>
  80. private void AddBoundingObjectsToQueue(IEnumerable<GameObject> boundingObjects)
  81. {
  82. foreach (GameObject item in boundingObjects)
  83. {
  84. Bounds bounds = new Bounds();
  85. Collider boundingCollider = item.GetComponent<Collider>();
  86. if (boundingCollider != null)
  87. {
  88. bounds = boundingCollider.bounds;
  89. // Expand the bounds, if requested.
  90. if (BoundsExpansion > 0.0f)
  91. {
  92. bounds.Expand(BoundsExpansion);
  93. }
  94. boundingObjectsQueue.Enqueue(bounds);
  95. }
  96. }
  97. }
  98. /// <summary>
  99. /// Iterator block, analyzes surface meshes to find vertices existing within the bounds of any boundingObject and removes them.
  100. /// </summary>
  101. /// <returns>Yield result.</returns>
  102. private IEnumerator RemoveSurfaceVerticesWithinBoundsRoutine()
  103. {
  104. List<MeshFilter> meshFilters = SpatialMappingManager.Instance.GetMeshFilters();
  105. float start = Time.realtimeSinceStartup;
  106. while (boundingObjectsQueue.Count > 0)
  107. {
  108. // Get the current boundingObject.
  109. Bounds bounds = boundingObjectsQueue.Dequeue();
  110. foreach (MeshFilter filter in meshFilters)
  111. {
  112. // Since this is amortized across frames, the filter can be destroyed by the time
  113. // we get here.
  114. if (filter == null)
  115. {
  116. continue;
  117. }
  118. Mesh mesh = filter.sharedMesh;
  119. if (mesh != null && !mesh.bounds.Intersects(bounds))
  120. {
  121. // We don't need to do anything to this mesh, move to the next one.
  122. continue;
  123. }
  124. // Remove vertices from any mesh that intersects with the bounds.
  125. Vector3[] verts = mesh.vertices;
  126. List<int> vertsToRemove = new List<int>();
  127. // Find which mesh vertices are within the bounds.
  128. for (int i = 0; i < verts.Length; ++i)
  129. {
  130. if (bounds.Contains(verts[i]))
  131. {
  132. // These vertices are within bounds, so mark them for removal.
  133. vertsToRemove.Add(i);
  134. }
  135. // If too much time has passed, we need to return control to the main game loop.
  136. if ((Time.realtimeSinceStartup - start) > FrameTime)
  137. {
  138. // Pause our work here, and continue finding vertices to remove on the next frame.
  139. yield return null;
  140. start = Time.realtimeSinceStartup;
  141. }
  142. }
  143. if (vertsToRemove.Count == 0)
  144. {
  145. // We did not find any vertices to remove, so move to the next mesh.
  146. continue;
  147. }
  148. // We found vertices to remove, so now we need to remove any triangles that reference these vertices.
  149. int[] indices = mesh.GetTriangles(0);
  150. List<int> updatedIndices = new List<int>();
  151. for (int index = 0; index < indices.Length; index += 3)
  152. {
  153. // Each triangle utilizes three slots in the index buffer, check to see if any of the
  154. // triangle indices contain a vertex that should be removed.
  155. if (vertsToRemove.Contains(indices[index]) ||
  156. vertsToRemove.Contains(indices[index + 1]) ||
  157. vertsToRemove.Contains(indices[index + 2]))
  158. {
  159. // Do nothing, we don't want to save this triangle...
  160. }
  161. else
  162. {
  163. // Every vertex in this triangle is good, so let's save it.
  164. updatedIndices.Add(indices[index]);
  165. updatedIndices.Add(indices[index + 1]);
  166. updatedIndices.Add(indices[index + 2]);
  167. }
  168. // If too much time has passed, we need to return control to the main game loop.
  169. if ((Time.realtimeSinceStartup - start) > FrameTime)
  170. {
  171. // Pause our work, and continue making additional planes on the next frame.
  172. yield return null;
  173. start = Time.realtimeSinceStartup;
  174. }
  175. }
  176. if (indices.Length == updatedIndices.Count)
  177. {
  178. // None of the verts to remove were being referenced in the triangle list.
  179. continue;
  180. }
  181. // Update mesh to use the new triangles.
  182. mesh.SetTriangles(updatedIndices.ToArray(), 0);
  183. mesh.RecalculateBounds();
  184. yield return null;
  185. start = Time.realtimeSinceStartup;
  186. // Reset the mesh collider to fit the new mesh.
  187. MeshCollider collider = filter.gameObject.GetComponent<MeshCollider>();
  188. if (collider != null)
  189. {
  190. collider.sharedMesh = null;
  191. collider.sharedMesh = mesh;
  192. }
  193. }
  194. }
  195. Debug.Log("Finished removing vertices.");
  196. // We are done removing vertices, trigger an event.
  197. EventHandler handler = RemoveVerticesComplete;
  198. if (handler != null)
  199. {
  200. handler(this, EventArgs.Empty);
  201. }
  202. removingVerts = false;
  203. }
  204. }
  205. }


新增PlanesManager.cs脚本组件,该脚本主要是用来控制生成平面,以及生成平面后创建游戏对象
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEngine.Windows.Speech;
  4. using HoloToolkit.Unity;
  5. public class PlanesManager : Singleton<PlanesManager>
  6. {
  7. [Tooltip("When checked, the SurfaceObserver will stop running after a specified amount of time.")]
  8. public bool limitScanningByTime = true;
  9. [Tooltip("How much time (in seconds) that the SurfaceObserver will run after being started; used when 'Limit Scanning By Time' is checked.")]
  10. public float scanTime = 30.0f;
  11. [Tooltip("Material to use when rendering Spatial Mapping meshes while the observer is running.")]
  12. public Material defaultMaterial;
  13. [Tooltip("Optional Material to use when rendering Spatial Mapping meshes after the observer has been stopped.")]
  14. public Material secondaryMaterial;
  15. [Tooltip("Minimum number of floor planes required in order to exit scanning/processing mode.")]
  16. public uint minimumFloors = 1;
  17. [Tooltip("Minimum number of wall planes required in order to exit scanning/processing mode.")]
  18. public uint minimumWalls = 1;
  19. /// <summary>
  20. /// 标记表面网格是否处理完成
  21. /// </summary>
  22. private bool meshesProcessed = false;
  23. private void Start()
  24. {
  25. //设置Surface表面材质
  26. SpatialMappingManager.Instance.SetSurfaceMaterial(defaultMaterial);
  27. //注册生成平面后的回调事件
  28. SurfaceMeshesToPlanes.Instance.MakePlanesComplete += SurfaceMeshesToPlanes_MakePlanesComplete;
  29. }
  30. /// <summary>
  31. /// 每帧调用
  32. /// </summary>
  33. private void Update()
  34. {
  35. //检查表面网格是否处理完成,且扫描空间的时间是否有限制
  36. if (!meshesProcessed && limitScanningByTime)
  37. {
  38. //检查是否超出了扫描上限时间
  39. if (limitScanningByTime && ((Time.time - SpatialMappingManager.Instance.StartTime) < scanTime))
  40. {
  41. }
  42. else
  43. {
  44. //当达到扫描的时间,停止对空间的扫描映射
  45. if(SpatialMappingManager.Instance.IsObserverRunning())
  46. {
  47. SpatialMappingManager.Instance.StopObserver();
  48. }
  49. //创建平面
  50. CreatePlanes();
  51. //标记表面网格处理完成
  52. meshesProcessed = true;
  53. }
  54. }
  55. }
  56. /// <summary>
  57. /// 当SurfaceMeshesToPlanes中 生成Planes处理完成,调用该事件
  58. /// </summary>
  59. /// <param name="source">事件源</param>
  60. /// <param name="args">事件参数</param>
  61. private void SurfaceMeshesToPlanes_MakePlanesComplete(object source, System.EventArgs args)
  62. {
  63. //水平平面列表(地面,桌面等)
  64. List<GameObject> horizontal = new List<GameObject>();
  65. //垂直平面列表(墙面等垂直面)
  66. List<GameObject> vertical = new List<GameObject>();
  67. //获取所有的水平平面
  68. horizontal = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Table | PlaneTypes.Floor);
  69. //获取所有的垂直平面
  70. vertical = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Wall);
  71. //检查垂直平面和水平平面的数量是否达到了最少设置的数量,如果未达到,重新进行空间扫描
  72. if (horizontal.Count >= minimumFloors && vertical.Count >= minimumWalls)
  73. {
  74. //删除顶点
  75. RemoveVertices(SurfaceMeshesToPlanes.Instance.ActivePlanes);
  76. //设置第二表面材质
  77. SpatialMappingManager.Instance.SetSurfaceMaterial(secondaryMaterial);
  78. //获取平面后,在世界中生成物体,传入参数为上面获取到的,水平平面,垂直平面的列表
  79. ObjectCollectionManager.Instance.GenerateItemsInWorld(horizontal, vertical);
  80. }
  81. else
  82. {
  83. //未扫描到可以放置物体的垂直平面和水平平面,重新进行空间扫描
  84. SpatialMappingManager.Instance.StartObserver();
  85. //重新标记表面网格未处理完成
  86. meshesProcessed = false;
  87. }
  88. }
  89. /// <summary>
  90. /// 将扫描得到的空间映射信息转换成平面
  91. /// </summary>
  92. private void CreatePlanes()
  93. {
  94. SurfaceMeshesToPlanes surfaceToPlanes = SurfaceMeshesToPlanes.Instance;
  95. if (surfaceToPlanes != null && surfaceToPlanes.enabled)
  96. {
  97. surfaceToPlanes.MakePlanes();
  98. }
  99. }
  100. /// <summary>
  101. /// 从空间映射中删除生成的三角形
  102. /// </summary>
  103. /// <param name="boundingObjects"></param>
  104. private void RemoveVertices(IEnumerable<GameObject> boundingObjects)
  105. {
  106. RemoveSurfaceVertices removeVerts = RemoveSurfaceVertices.Instance;
  107. if (removeVerts != null && removeVerts.enabled)
  108. {
  109. removeVerts.RemoveSurfaceVerticesWithinBounds(boundingObjects);
  110. }
  111. }
  112. /// <summary>
  113. /// 释放资源
  114. /// </summary>
  115. private void OnDestroy()
  116. {
  117. if (SurfaceMeshesToPlanes.Instance != null)
  118. {
  119. SurfaceMeshesToPlanes.Instance.MakePlanesComplete -= SurfaceMeshesToPlanes_MakePlanesComplete;
  120. }
  121. }
  122. }


4、新增Placeable.cs脚本组件

该脚本用于判断虚拟物体在某区域内是否可以放置

  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using HoloToolkit.Unity;
  4. public enum PlacementSurfaces
  5. {
  6. Horizontal = 1,
  7. Vertical = 2,
  8. }
  9. /// <summary>
  10. /// The Placeable class implements the logic used to determine if a GameObject
  11. /// can be placed on a target surface. Constraints for placement include:
  12. /// * No part of the GameObject's box collider impacts with another object in the scene
  13. /// * The object lays flat (within specified tolerances) against the surface
  14. /// * The object would not fall off of the surface if gravity were enabled.
  15. /// This class also provides the following visualizations.
  16. /// * A transparent cube representing the object's box collider.
  17. /// * Shadow on the target surface indicating whether or not placement is valid.
  18. /// </summary>
  19. public class Placeable : MonoBehaviour
  20. {
  21. [Tooltip("The base material used to render the bounds asset when placement is allowed.")]
  22. public Material PlaceableBoundsMaterial = null;
  23. [Tooltip("The base material used to render the bounds asset when placement is not allowed.")]
  24. public Material NotPlaceableBoundsMaterial = null;
  25. [Tooltip("The material used to render the placement shadow when placement it allowed.")]
  26. public Material PlaceableShadowMaterial = null;
  27. [Tooltip("The material used to render the placement shadow when placement it not allowed.")]
  28. public Material NotPlaceableShadowMaterial = null;
  29. [Tooltip("The type of surface on which the object can be placed.")]
  30. public PlacementSurfaces PlacementSurface = PlacementSurfaces.Horizontal;
  31. [Tooltip("The child object(s) to hide during placement.")]
  32. public List<GameObject> ChildrenToHide = new List<GameObject>();
  33. //标记是否正在被放置
  34. public bool IsPlacing { get; private set; }
  35. // The most recent distance to the surface. This is used to
  36. // locate the object when the user's gaze does not intersect
  37. // with the Spatial Mapping mesh.
  38. private float lastDistance = 2.0f;
  39. // 当物体处于正在被放置状态时,离开物体表面的距离
  40. private float hoverDistance = 0.15f;
  41. // Threshold (the closer to 0, the stricter the standard) used to determine if a surface is flat.
  42. private float distanceThreshold = 0.02f;
  43. // Threshold (the closer to 1, the stricter the standard) used to determine if a surface is vertical.
  44. private float upNormalThreshold = 0.9f;
  45. // Maximum distance, from the object, that placement is allowed.
  46. // This is used when raycasting to see if the object is near a placeable surface.
  47. private float maximumPlacementDistance = 5.0f;
  48. // Speed (1.0 being fastest) at which the object settles to the surface upon placement.
  49. private float placementVelocity = 0.06f;
  50. // Indicates whether or not this script manages the object's box collider.
  51. private bool managingBoxCollider = false;
  52. // The box collider used to determine of the object will fit in the desired location.
  53. // It is also used to size the bounding cube.
  54. private BoxCollider boxCollider = null;
  55. // Visible asset used to show the dimensions of the object. This asset is sized
  56. // using the box collider's bounds.
  57. private GameObject boundsAsset = null;
  58. // Visible asset used to show the where the object is attempting to be placed.
  59. // This asset is sized using the box collider's bounds.
  60. private GameObject shadowAsset = null;
  61. //物体被放置的目标位置
  62. private Vector3 targetPosition;
  63. private void Awake()
  64. {
  65. targetPosition = gameObject.transform.position;
  66. //获取或创建对撞机
  67. boxCollider = gameObject.GetComponent<BoxCollider>();
  68. if (boxCollider == null)
  69. {
  70. managingBoxCollider = true;
  71. boxCollider = gameObject.AddComponent<BoxCollider>();
  72. boxCollider.enabled = false;
  73. }
  74. //创建对象表名gameObject的界限
  75. boundsAsset = GameObject.CreatePrimitive(PrimitiveType.Cube);
  76. boundsAsset.transform.parent = gameObject.transform;
  77. boundsAsset.SetActive(false);
  78. //创建gameObject的阴影对象
  79. shadowAsset = GameObject.CreatePrimitive(PrimitiveType.Quad);
  80. shadowAsset.transform.parent = gameObject.transform;
  81. shadowAsset.SetActive(false);
  82. }
  83. //gameObject被选中时调用
  84. public void OnSelect()
  85. {
  86. if (!IsPlacing)
  87. {
  88. //开始放置物体
  89. OnPlacementStart();
  90. }
  91. else
  92. {
  93. //停止移动,放置物体
  94. OnPlacementStop();
  95. }
  96. }
  97. private void Update()
  98. {
  99. if (IsPlacing)
  100. {
  101. //处于移动被放置状态,移动物体
  102. Move();
  103. Vector3 targetPosition;
  104. Vector3 surfaceNormal;
  105. //检查当前位置是否可以放置该物体
  106. bool canBePlaced = ValidatePlacement(out targetPosition, out surfaceNormal);
  107. //显示边界
  108. DisplayBounds(canBePlaced);
  109. //显示阴影
  110. DisplayShadow(targetPosition, surfaceNormal, canBePlaced);
  111. }
  112. else
  113. {
  114. //隐藏边界和阴影
  115. boundsAsset.SetActive(false);
  116. shadowAsset.SetActive(false);
  117. // 将该对象物体放置在物体表面
  118. float dist = (gameObject.transform.position - targetPosition).magnitude;
  119. if (dist > 0)
  120. {
  121. gameObject.transform.position = Vector3.Lerp(gameObject.transform.position, targetPosition, placementVelocity / dist);
  122. }
  123. else
  124. {
  125. //显示物体的子物体
  126. for (int i = 0; i < ChildrenToHide.Count; i++)
  127. {
  128. ChildrenToHide[i].SetActive(true);
  129. }
  130. }
  131. }
  132. }
  133. /// <summary>
  134. ///检查物体是否可以被放置
  135. /// </summary>
  136. /// <param name="position">
  137. /// 目标位置
  138. /// </param>
  139. /// <param name="surfaceNormal">
  140. /// 物体被放置的平面的法向量
  141. /// </param>
  142. /// <returns>
  143. /// 可以被放置返回true,不能被放置,返回false
  144. /// </returns>
  145. private bool ValidatePlacement(out Vector3 position, out Vector3 surfaceNormal)
  146. {
  147. Vector3 raycastDirection = gameObject.transform.forward;
  148. if (PlacementSurface == PlacementSurfaces.Horizontal)
  149. {
  150. // Placing on horizontal surfaces.
  151. // Raycast from the bottom face of the box collider.
  152. raycastDirection = -(Vector3.up);
  153. }
  154. position = Vector3.zero;
  155. surfaceNormal = Vector3.zero;
  156. Vector3[] facePoints = GetColliderFacePoints();
  157. // The origin points we receive are in local space and we
  158. // need to raycast in world space.
  159. for (int i = 0; i < facePoints.Length; i++)
  160. {
  161. facePoints[i] = gameObject.transform.TransformVector(facePoints[i]) + gameObject.transform.position;
  162. }
  163. // Cast a ray from the center of the box collider face to the surface.
  164. RaycastHit centerHit;
  165. if (!Physics.Raycast(facePoints[0],
  166. raycastDirection,
  167. out centerHit,
  168. maximumPlacementDistance,
  169. SpatialMappingManager.Instance.LayerMask))
  170. {
  171. // If the ray failed to hit the surface, we are done.
  172. return false;
  173. }
  174. // We have found a surface. Set position and surfaceNormal.
  175. position = centerHit.point;
  176. surfaceNormal = centerHit.normal;
  177. // Cast a ray from the corners of the box collider face to the surface.
  178. for (int i = 1; i < facePoints.Length; i++)
  179. {
  180. RaycastHit hitInfo;
  181. if (Physics.Raycast(facePoints[i],
  182. raycastDirection,
  183. out hitInfo,
  184. maximumPlacementDistance,
  185. SpatialMappingManager.Instance.LayerMask))
  186. {
  187. // To be a valid placement location, each of the corners must have a similar
  188. // enough distance to the surface as the center point
  189. if (!IsEquivalentDistance(centerHit.distance, hitInfo.distance))
  190. {
  191. return false;
  192. }
  193. }
  194. else
  195. {
  196. // The raycast failed to intersect with the target layer.
  197. return false;
  198. }
  199. }
  200. return true;
  201. }
  202. /// <summary>
  203. /// Determine the coordinates, in local space, of the box collider face that
  204. /// will be placed against the target surface.
  205. /// </summary>
  206. /// <returns>
  207. /// Vector3 array with the center point of the face at index 0.
  208. /// </returns>
  209. private Vector3[] GetColliderFacePoints()
  210. {
  211. // Get the collider extents.
  212. // The size values are twice the extents.
  213. Vector3 extents = boxCollider.size / 2;
  214. // Calculate the min and max values for each coordinate.
  215. float minX = boxCollider.center.x - extents.x;
  216. float maxX = boxCollider.center.x + extents.x;
  217. float minY = boxCollider.center.y - extents.y;
  218. float maxY = boxCollider.center.y + extents.y;
  219. float minZ = boxCollider.center.z - extents.z;
  220. float maxZ = boxCollider.center.z + extents.z;
  221. Vector3 center;
  222. Vector3 corner0;
  223. Vector3 corner1;
  224. Vector3 corner2;
  225. Vector3 corner3;
  226. if (PlacementSurface == PlacementSurfaces.Horizontal)
  227. {
  228. // Placing on horizontal surfaces.
  229. center = new Vector3(boxCollider.center.x, minY, boxCollider.center.z);
  230. corner0 = new Vector3(minX, minY, minZ);
  231. corner1 = new Vector3(minX, minY, maxZ);
  232. corner2 = new Vector3(maxX, minY, minZ);
  233. corner3 = new Vector3(maxX, minY, maxZ);
  234. }
  235. else
  236. {
  237. // Placing on vertical surfaces.
  238. center = new Vector3(boxCollider.center.x, boxCollider.center.y, maxZ);
  239. corner0 = new Vector3(minX, minY, maxZ);
  240. corner1 = new Vector3(minX, maxY, maxZ);
  241. corner2 = new Vector3(maxX, minY, maxZ);
  242. corner3 = new Vector3(maxX, maxY, maxZ);
  243. }
  244. return new Vector3[] { center, corner0, corner1, corner2, corner3 };
  245. }
  246. /// <summary>
  247. /// Put the object into placement mode.
  248. /// </summary>
  249. public void OnPlacementStart()
  250. {
  251. // If we are managing the collider, enable it.
  252. if (managingBoxCollider)
  253. {
  254. boxCollider.enabled = true;
  255. }
  256. // Hide the child object(s) to make placement easier.
  257. for (int i = 0; i < ChildrenToHide.Count; i++)
  258. {
  259. ChildrenToHide[i].SetActive(false);
  260. }
  261. // Tell the gesture manager that it is to assume
  262. // all input is to be given to this object.
  263. GestureManager.Instance.OverrideFocusedObject = gameObject;
  264. // Enter placement mode.
  265. IsPlacing = true;
  266. }
  267. /// <summary>
  268. /// Take the object out of placement mode.
  269. /// </summary>
  270. /// <remarks>
  271. /// This method will leave the object in placement mode if called while
  272. /// the object is in an invalid location. To determine whether or not
  273. /// the object has been placed, check the value of the IsPlacing property.
  274. /// </remarks>
  275. public void OnPlacementStop()
  276. {
  277. Vector3 position;
  278. Vector3 surfaceNormal;
  279. if (!ValidatePlacement(out position, out surfaceNormal))
  280. {
  281. return;
  282. }
  283. // The object is allowed to be placed.
  284. // We are placing at a small buffer away from the surface.
  285. targetPosition = position + (0.01f * surfaceNormal);
  286. OrientObject(true, surfaceNormal);
  287. // If we are managing the collider, disable it.
  288. if (managingBoxCollider)
  289. {
  290. boxCollider.enabled = false;
  291. }
  292. // Tell the gesture manager that it is to resume
  293. // its normal behavior.
  294. GestureManager.Instance.OverrideFocusedObject = null;
  295. // Exit placement mode.
  296. IsPlacing = false;
  297. }
  298. /// <summary>
  299. /// Positions the object along the surface toward which the user is gazing.
  300. /// </summary>
  301. /// <remarks>
  302. /// If the user's gaze does not intersect with a surface, the object
  303. /// will remain at the most recently calculated distance.
  304. /// </remarks>
  305. private void Move()
  306. {
  307. Vector3 moveTo = gameObject.transform.position;
  308. Vector3 surfaceNormal = Vector3.zero;
  309. RaycastHit hitInfo;
  310. bool hit = Physics.Raycast(Camera.main.transform.position,
  311. Camera.main.transform.forward,
  312. out hitInfo,
  313. 20f,
  314. SpatialMappingManager.Instance.LayerMask);
  315. if (hit)
  316. {
  317. float offsetDistance = hoverDistance;
  318. // Place the object a small distance away from the surface while keeping
  319. // the object from going behind the user.
  320. if (hitInfo.distance <= hoverDistance)
  321. {
  322. offsetDistance = 0f;
  323. }
  324. moveTo = hitInfo.point + (offsetDistance * hitInfo.normal);
  325. lastDistance = hitInfo.distance;
  326. surfaceNormal = hitInfo.normal;
  327. }
  328. else
  329. {
  330. // The raycast failed to hit a surface. In this case, keep the object at the distance of the last
  331. // intersected surface.
  332. moveTo = Camera.main.transform.position + (Camera.main.transform.forward * lastDistance);
  333. }
  334. // Follow the user's gaze.
  335. float dist = Mathf.Abs((gameObject.transform.position - moveTo).magnitude);
  336. gameObject.transform.position = Vector3.Lerp(gameObject.transform.position, moveTo, placementVelocity / dist);
  337. // Orient the object.
  338. // We are using the return value from Physics.Raycast to instruct
  339. // the OrientObject function to align to the vertical surface if appropriate.
  340. OrientObject(hit, surfaceNormal);
  341. }
  342. /// <summary>
  343. /// Orients the object so that it faces the user.
  344. /// </summary>
  345. /// <param name="alignToVerticalSurface">
  346. /// If true and the object is to be placed on a vertical surface,
  347. /// orient parallel to the target surface. If false, orient the object
  348. /// to face the user.
  349. /// </param>
  350. /// <param name="surfaceNormal">
  351. /// The target surface's normal vector.
  352. /// </param>
  353. /// <remarks>
  354. /// The aligntoVerticalSurface parameter is ignored if the object
  355. /// is to be placed on a horizontalSurface
  356. /// </remarks>
  357. private void OrientObject(bool alignToVerticalSurface, Vector3 surfaceNormal)
  358. {
  359. Quaternion rotation = Camera.main.transform.localRotation;
  360. // If the user's gaze does not intersect with the Spatial Mapping mesh,
  361. // orient the object towards the user.
  362. if (alignToVerticalSurface && (PlacementSurface == PlacementSurfaces.Vertical))
  363. {
  364. // We are placing on a vertical surface.
  365. // If the normal of the Spatial Mapping mesh indicates that the
  366. // surface is vertical, orient parallel to the surface.
  367. if (Mathf.Abs(surfaceNormal.y) <= (1 - upNormalThreshold))
  368. {
  369. rotation = Quaternion.LookRotation(-surfaceNormal, Vector3.up);
  370. }
  371. }
  372. else
  373. {
  374. rotation.x = 0f;
  375. rotation.z = 0f;
  376. }
  377. gameObject.transform.rotation = rotation;
  378. }
  379. /// <summary>
  380. /// Displays the bounds asset.
  381. /// </summary>
  382. /// <param name="canBePlaced">
  383. /// Specifies if the object is in a valid placement location.
  384. /// </param>
  385. private void DisplayBounds(bool canBePlaced)
  386. {
  387. // Ensure the bounds asset is sized and positioned correctly.
  388. boundsAsset.transform.localPosition = boxCollider.center;
  389. boundsAsset.transform.localScale = boxCollider.size;
  390. boundsAsset.transform.rotation = gameObject.transform.rotation;
  391. // Apply the appropriate material.
  392. if (canBePlaced)
  393. {
  394. boundsAsset.GetComponent<Renderer>().sharedMaterial = PlaceableBoundsMaterial;
  395. }
  396. else
  397. {
  398. boundsAsset.GetComponent<Renderer>().sharedMaterial = NotPlaceableBoundsMaterial;
  399. }
  400. // Show the bounds asset.
  401. boundsAsset.SetActive(true);
  402. }
  403. /// <summary>
  404. /// Displays the placement shadow asset.
  405. /// </summary>
  406. /// <param name="position">
  407. /// The position at which to place the shadow asset.
  408. /// </param>
  409. /// <param name="surfaceNormal">
  410. /// The normal of the surface on which the asset will be placed
  411. /// </param>
  412. /// <param name="canBePlaced">
  413. /// Specifies if the object is in a valid placement location.
  414. /// </param>
  415. private void DisplayShadow(Vector3 position,
  416. Vector3 surfaceNormal,
  417. bool canBePlaced)
  418. {
  419. // Rotate the shadow so that it is displayed on the correct surface and matches the object.
  420. float rotationX = 0.0f;
  421. if (PlacementSurface == PlacementSurfaces.Horizontal)
  422. {
  423. rotationX = 90.0f;
  424. }
  425. Quaternion rotation = Quaternion.Euler(rotationX, gameObject.transform.rotation.eulerAngles.y, 0);
  426. shadowAsset.transform.localScale = boxCollider.size;
  427. shadowAsset.transform.rotation = rotation;
  428. // Apply the appropriate material.
  429. if (canBePlaced)
  430. {
  431. shadowAsset.GetComponent<Renderer>().sharedMaterial = PlaceableShadowMaterial;
  432. }
  433. else
  434. {
  435. shadowAsset.GetComponent<Renderer>().sharedMaterial = NotPlaceableShadowMaterial;
  436. }
  437. // Show the shadow asset as appropriate.
  438. if (position != Vector3.zero)
  439. {
  440. // Position the shadow a small distance from the target surface, along the normal.
  441. shadowAsset.transform.position = position + (0.01f * surfaceNormal);
  442. shadowAsset.SetActive(true);
  443. }
  444. else
  445. {
  446. shadowAsset.SetActive(false);
  447. }
  448. }
  449. /// <summary>
  450. /// Determines if two distance values should be considered equivalent.
  451. /// </summary>
  452. /// <param name="d1">
  453. /// Distance to compare.
  454. /// </param>
  455. /// <param name="d2">
  456. /// Distance to compare.
  457. /// </param>
  458. /// <returns>
  459. /// True if the distances are within the desired tolerance, otherwise false.
  460. /// </returns>
  461. private bool IsEquivalentDistance(float d1, float d2)
  462. {
  463. float dist = Mathf.Abs(d1 - d2);
  464. return (dist <= distanceThreshold);
  465. }
  466. /// <summary>
  467. /// Called when the GameObject is unloaded.
  468. /// </summary>
  469. private void OnDestroy()
  470. {
  471. // Unload objects we have created.
  472. Destroy(boundsAsset);
  473. boundsAsset = null;
  474. Destroy(shadowAsset);
  475. shadowAsset = null;
  476. }
  477. }

5、新增测试对象

新增两个cube,并添加上面创建的Placeable.cs脚本组件

Cube1用于测试放置在墙面上,设置PlacementSurface为Vertical

Cube2用于测试放置在地面上,设置PlacementSurface为Horizontal


其中涉及到的这些材质,可以自行进行新增几个材质球

PlaceableBounds用于在可放置时显示的边界

NotPlaceableBounds 不可放置时显示的边界

PlaceableShadow 可放置时显示的阴影

NotPlaceableShadow 不可放置时显示的阴影


6、新增游戏对象集合ObjectCollection

创建空对象,并且新增ObjectCollectionManager.cs脚本组件,将上面创建的两个cube拖拽进去


ObjectCollectionManager.cs如下,该脚本主要是用来在空间中生成游戏对象,放置游戏对象

  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using HoloToolkit.Unity;
  4. /// <summary>
  5. /// Called by PlaySpaceManager after planes have been generated from the Spatial Mapping Mesh.
  6. /// This class will create a collection of prefab objects that have the 'Placeable' component and
  7. /// will attempt to set their initial location on planes that are close to the user.
  8. /// </summary>
  9. public class ObjectCollectionManager : Singleton<ObjectCollectionManager>
  10. {
  11. [Tooltip("A collection of Placeable space object prefabs to generate in the world.")]
  12. public List<GameObject> spaceObjectPrefabs;
  13. /// <summary>
  14. /// Generates a collection of Placeable objects in the world and sets them on planes that match their affinity.
  15. /// </summary>
  16. /// <param name="horizontalSurfaces">Horizontal surface planes (floors, tables).</param>
  17. /// <param name="verticalSurfaces">Vertical surface planes (walls).</param>
  18. public void GenerateItemsInWorld(List<GameObject> horizontalSurfaces, List<GameObject> verticalSurfaces)
  19. {
  20. List<GameObject> horizontalObjects = new List<GameObject>();
  21. List<GameObject> verticalObjects = new List<GameObject>();
  22. foreach (GameObject spacePrefab in spaceObjectPrefabs)
  23. {
  24. Placeable placeable = spacePrefab.GetComponent<Placeable>();
  25. if (placeable.PlacementSurface == PlacementSurfaces.Horizontal)
  26. {
  27. horizontalObjects.Add(spacePrefab);
  28. }
  29. else
  30. {
  31. verticalObjects.Add(spacePrefab);
  32. }
  33. }
  34. if (horizontalObjects.Count > 0)
  35. {
  36. CreateSpaceObjects(horizontalObjects, horizontalSurfaces, PlacementSurfaces.Horizontal);
  37. }
  38. if (verticalObjects.Count > 0)
  39. {
  40. CreateSpaceObjects(verticalObjects, verticalSurfaces, PlacementSurfaces.Vertical);
  41. }
  42. }
  43. /// <summary>
  44. /// Creates and positions a collection of Placeable space objects on SurfacePlanes in the environment.
  45. /// </summary>
  46. /// <param name="spaceObjects">Collection of prefab GameObjects that have the Placeable component.</param>
  47. /// <param name="surfaces">Collection of SurfacePlane objects in the world.</param>
  48. /// <param name="surfaceType">Type of objects and planes that we are trying to match-up.</param>
  49. private void CreateSpaceObjects(List<GameObject> spaceObjects, List<GameObject> surfaces, PlacementSurfaces surfaceType)
  50. {
  51. List<int> UsedPlanes = new List<int>();
  52. // Sort the planes by distance to user.
  53. surfaces.Sort((lhs, rhs) =>
  54. {
  55. Vector3 headPosition = Camera.main.transform.position;
  56. Collider rightCollider = rhs.GetComponent<Collider>();
  57. Collider leftCollider = lhs.GetComponent<Collider>();
  58. // This plane is big enough, now we will evaluate how far the plane is from the user's head.
  59. // Since planes can be quite large, we should find the closest point on the plane's bounds to the
  60. // user's head, rather than just taking the plane's center position.
  61. Vector3 rightSpot = rightCollider.ClosestPointOnBounds(headPosition);
  62. Vector3 leftSpot = leftCollider.ClosestPointOnBounds(headPosition);
  63. return Vector3.Distance(leftSpot, headPosition).CompareTo(Vector3.Distance(rightSpot, headPosition));
  64. });
  65. foreach (GameObject item in spaceObjects)
  66. {
  67. int index = -1;
  68. Collider collider = item.GetComponent<Collider>();
  69. if (surfaceType == PlacementSurfaces.Vertical)
  70. {
  71. index = FindNearestPlane(surfaces, collider.bounds.size, UsedPlanes, true);
  72. }
  73. else
  74. {
  75. index = FindNearestPlane(surfaces, collider.bounds.size, UsedPlanes, false);
  76. }
  77. // If we can't find a good plane we will put the object floating in space.
  78. Vector3 position = Camera.main.transform.position + Camera.main.transform.forward * 2.0f + Camera.main.transform.right * (Random.value - 1.0f) * 2.0f;
  79. Quaternion rotation = Quaternion.identity;
  80. // If we do find a good plane we can do something smarter.
  81. if (index >= 0)
  82. {
  83. UsedPlanes.Add(index);
  84. GameObject surface = surfaces[index];
  85. SurfacePlane plane = surface.GetComponent<SurfacePlane>();
  86. position = surface.transform.position + (plane.PlaneThickness * plane.SurfaceNormal);
  87. position = AdjustPositionWithSpatialMap(position, plane.SurfaceNormal);
  88. rotation = Camera.main.transform.localRotation;
  89. if (surfaceType == PlacementSurfaces.Vertical)
  90. {
  91. // Vertical objects should face out from the wall.
  92. rotation = Quaternion.LookRotation(surface.transform.forward, Vector3.up);
  93. }
  94. else
  95. {
  96. // Horizontal objects should face the user.
  97. rotation = Quaternion.LookRotation(Camera.main.transform.position);
  98. rotation.x = 0f;
  99. rotation.z = 0f;
  100. }
  101. }
  102. //Vector3 finalPosition = AdjustPositionWithSpatialMap(position, surfaceType);
  103. GameObject spaceObject = Instantiate(item, position, rotation) as GameObject;
  104. spaceObject.transform.parent = gameObject.transform;
  105. }
  106. }
  107. /// <summary>
  108. /// Attempts to find a the closest plane to the user which is large enough to fit the object.
  109. /// </summary>
  110. /// <param name="planes">List of planes to consider for object placement.</param>
  111. /// <param name="minSize">Minimum size that the plane is required to be.</param>
  112. /// <param name="startIndex">Index in the planes collection that we want to start at (to help avoid double-placement of objects).</param>
  113. /// <param name="isVertical">True, if we are currently evaluating vertical surfaces.</param>
  114. /// <returns></returns>
  115. private int FindNearestPlane(List<GameObject> planes, Vector3 minSize, List<int> usedPlanes, bool isVertical)
  116. {
  117. int planeIndex = -1;
  118. for(int i = 0; i < planes.Count; i++)
  119. {
  120. if (usedPlanes.Contains(i))
  121. {
  122. continue;
  123. }
  124. Collider collider = planes[i].GetComponent<Collider>();
  125. if (isVertical && (collider.bounds.size.x < minSize.x || collider.bounds.size.y < minSize.y))
  126. {
  127. // This plane is too small to fit our vertical object.
  128. continue;
  129. }
  130. else if(!isVertical && (collider.bounds.size.x < minSize.x || collider.bounds.size.y < minSize.y))
  131. {
  132. // This plane is too small to fit our horizontal object.
  133. continue;
  134. }
  135. return i;
  136. }
  137. return planeIndex;
  138. }
  139. /// <summary>
  140. /// Adjusts the initial position of the object if it is being occluded by the spatial map.
  141. /// </summary>
  142. /// <param name="position">Position of object to adjust.</param>
  143. /// <param name="surfaceNormal">Normal of surface that the object is positioned against.</param>
  144. /// <returns></returns>
  145. private Vector3 AdjustPositionWithSpatialMap(Vector3 position, Vector3 surfaceNormal)
  146. {
  147. Vector3 newPosition = position;
  148. RaycastHit hitInfo;
  149. float distance = 0.5f;
  150. // Check to see if there is a SpatialMapping mesh occluding the object at its current position.
  151. if(Physics.Raycast(position, surfaceNormal, out hitInfo, distance, SpatialMappingManager.Instance.LayerMask))
  152. {
  153. // If the object is occluded, reset its position.
  154. newPosition = hitInfo.point;
  155. }
  156. return newPosition;
  157. }
  158. }


7、为了使用空间映射数据,SpatialPerception能力必须被启用


8、运行测试

可以放置,阴影显示绿色


不能放置,阴影显示红色


放置后效果


能够被放置


不能被放置


放置后效果


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/89363
推荐阅读
相关标签
  

闽ICP备14008679号