赞
踩
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上
- // Copyright (c) Microsoft Corporation. All rights reserved.
- // Licensed under the MIT License. See LICENSE in the project root for license information.
-
- 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>
- /// 根据SpatialMappingManager's Observer返回的网格信息,找到并创建平面.
- /// </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;
-
- //配置那种类型的平面被渲染
- [HideInInspector]
- public PlaneTypes drawPlanesMask =
- (PlaneTypes.Wall | PlaneTypes.Floor | PlaneTypes.Ceiling | PlaneTypes.Table);
- // 配置哪种类型的平面被丢弃
- [HideInInspector]
- public PlaneTypes destroyPlanesMask = PlaneTypes.Unknown;
- // 地面的y坐标,处于用户头部位置以下的面积最大的水平区域
- public float FloorYPosition { get; private set; }
- //天花板y坐标,处于用户头部位置以上的面积最大的水平区域
- public float CeilingYPosition { get; private set; }
-
- /// <summary>
- /// 当平面创建完成进行触发
- /// </summary>
- /// <param name="source"></param>
- /// <param name="args"></param>
- public delegate void EventHandler(object source, EventArgs args);
-
- /// <summary>
- /// 当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>
- /// 标记当前是否正在创建平面
- /// </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
-
- 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;
- //为了不影响帧率,使用协程将工作分割到不同帧中完成
- StartCoroutine(MakePlanesRoutine());
- }
- }
-
- /// <summary>
- /// 返回所有指定的平面类型的平面列表
- /// </summary>
- /// <param name="planeTypes">平面类型</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>
- /// 分析网格信息,找到并用新的对象替换每个平面
- /// </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
-
- // 暂停任务,等待下一帧处理下面的代码
- 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;
- }
- }
- }
-
- // Create SurfacePlane objects to represent each plane found in the Spatial Mapping mesh.
- for (int index = 0; index < planes.Length; index++)
- {
- GameObject destPlane;
- BoundedPlane boundedPlane = planes[index];
-
- // Instantiate a SurfacePlane object, which will have the same bounds as our BoundedPlane object.
- if (SurfacePlanePrefab != null && SurfacePlanePrefab.GetComponent<SurfacePlane>() != null)
- {
- destPlane = Instantiate(SurfacePlanePrefab);
- }
- else
- {
- destPlane = GameObject.CreatePrimitive(PrimitiveType.Cube);
- destPlane.AddComponent<SurfacePlane>();
- destPlane.GetComponent<Renderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
- }
-
- destPlane.transform.parent = planesParent.transform;
- SurfacePlane surfacePlane = destPlane.GetComponent<SurfacePlane>();
-
- // Set the Plane property to adjust transform position/scale/rotation and determine plane type.
- surfacePlane.Plane = boundedPlane;
-
- SetPlaneVisibility(surfacePlane);
-
- if ((destroyPlanesMask & surfacePlane.PlaneType) == surfacePlane.PlaneType)
- {
- DestroyImmediate(destPlane);
- }
- else
- {
- // Set the plane to use the same layer as the SpatialMapping mesh.
- destPlane.layer = SpatialMappingManager.Instance.PhysicsLayer;
- ActivePlanes.Add(destPlane);
- }
-
- // If too much time has passed, we need to return control to the main game loop.
- if ((Time.realtimeSinceStartup - start) > FrameTime)
- {
- // Pause our work here, and continue making additional planes on the next frame.
- yield return null;
- start = Time.realtimeSinceStartup;
- }
- }
-
- Debug.Log("Finished making planes.");
-
- //平面创建完成,触发事件
- EventHandler handler = MakePlanesComplete;
- if (handler != null)
- {
- handler(this, EventArgs.Empty);
- }
-
- makingPlanes = false;
- }
-
- /// <summary>
- /// Sets visibility of planes based on their type.
- /// </summary>
- /// <param name="surfacePlane"></param>
- private void SetPlaneVisibility(SurfacePlane surfacePlane)
- {
- surfacePlane.IsVisible = ((drawPlanesMask & surfacePlane.PlaneType) == surfacePlane.PlaneType);
- }
- }
-
- #if UNITY_EDITOR
- /// <summary>
- /// Editor extension class to enable multi-selection of the 'Draw Planes' and 'Destroy Planes' options in the Inspector.
- /// </summary>
- [CustomEditor(typeof(SurfaceMeshesToPlanes))]
- public class PlaneTypesEnumEditor : Editor
- {
- public SerializedProperty drawPlanesMask;
- public SerializedProperty destroyPlanesMask;
-
- void OnEnable()
- {
- drawPlanesMask = serializedObject.FindProperty("drawPlanesMask");
- destroyPlanesMask = serializedObject.FindProperty("destroyPlanesMask");
- }
-
- public override void OnInspectorGUI()
- {
- base.OnInspectorGUI();
- serializedObject.Update();
-
- drawPlanesMask.intValue = (int)((PlaneTypes)EditorGUILayout.EnumMaskField
- ("Draw Planes", (PlaneTypes)drawPlanesMask.intValue));
-
- destroyPlanesMask.intValue = (int)((PlaneTypes)EditorGUILayout.EnumMaskField
- ("Destroy Planes", (PlaneTypes)destroyPlanesMask.intValue));
-
- serializedObject.ApplyModifiedProperties();
- }
- }
- #endif
- }
RemoveSurfaceVertices.cs 主要用来删除网格三角型
- // Copyright (c) Microsoft Corporation. All rights reserved.
- // Licensed under the MIT License. See LICENSE in the project root for license information.
-
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- namespace HoloToolkit.Unity
- {
- /// <summary>
- /// RemoveSurfaceVertices will remove any vertices from the Spatial Mapping Mesh that fall within the bounding volume.
- /// This can be used to create holes in the environment, or to help reduce triangle count after finding planes.
- /// </summary>
- public class RemoveSurfaceVertices : Singleton<RemoveSurfaceVertices>
- {
- [Tooltip("The amount, if any, to expand each bounding volume by.")]
- public float BoundsExpansion = 0.0f;
-
- /// <summary>
- /// Delegate which is called when the RemoveVerticesComplete 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 RemoveSurfaceVertices is finished.
- /// </summary>
- public event EventHandler RemoveVerticesComplete;
-
- /// <summary>
- /// Indicates if RemoveSurfaceVertices is currently removing vertices from the Spatial Mapping Mesh.
- /// </summary>
- private bool removingVerts = false;
-
- /// <summary>
- /// Queue of bounding objects to remove surface vertices from.
- /// Bounding objects are queued so that RemoveSurfaceVerticesWithinBounds can be called even when the previous task has not finished.
- /// </summary>
- private Queue<Bounds> boundingObjectsQueue;
-
- #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()
- {
- boundingObjectsQueue = new Queue<Bounds>();
- removingVerts = false;
- }
-
- /// <summary>
- /// Removes portions of the surface mesh that exist within the bounds of the boundingObjects.
- /// </summary>
- /// <param name="boundingObjects">Collection of GameObjects that define the bounds where spatial mesh vertices should be removed.</param>
- public void RemoveSurfaceVerticesWithinBounds(IEnumerable<GameObject> boundingObjects)
- {
- if (boundingObjects == null)
- {
- return;
- }
-
- if (!removingVerts)
- {
- removingVerts = true;
- AddBoundingObjectsToQueue(boundingObjects);
-
- // We use Coroutine to split the work across multiple frames and avoid impacting the frame rate too much.
- StartCoroutine(RemoveSurfaceVerticesWithinBoundsRoutine());
- }
- else
- {
- // Add new boundingObjects to end of queue.
- AddBoundingObjectsToQueue(boundingObjects);
- }
- }
-
- /// <summary>
- /// Adds new bounding objects to the end of the Queue.
- /// </summary>
- /// <param name="boundingObjects">Collection of GameObjects which define the bounds where spatial mesh vertices should be removed.</param>
- private void AddBoundingObjectsToQueue(IEnumerable<GameObject> boundingObjects)
- {
- foreach (GameObject item in boundingObjects)
- {
- Bounds bounds = new Bounds();
-
- Collider boundingCollider = item.GetComponent<Collider>();
- if (boundingCollider != null)
- {
- bounds = boundingCollider.bounds;
-
- // Expand the bounds, if requested.
- if (BoundsExpansion > 0.0f)
- {
- bounds.Expand(BoundsExpansion);
- }
-
- boundingObjectsQueue.Enqueue(bounds);
- }
- }
- }
-
- /// <summary>
- /// Iterator block, analyzes surface meshes to find vertices existing within the bounds of any boundingObject and removes them.
- /// </summary>
- /// <returns>Yield result.</returns>
- private IEnumerator RemoveSurfaceVerticesWithinBoundsRoutine()
- {
- List<MeshFilter> meshFilters = SpatialMappingManager.Instance.GetMeshFilters();
- float start = Time.realtimeSinceStartup;
-
- while (boundingObjectsQueue.Count > 0)
- {
- // Get the current boundingObject.
- Bounds bounds = boundingObjectsQueue.Dequeue();
-
- foreach (MeshFilter filter in meshFilters)
- {
- // Since this is amortized across frames, the filter can be destroyed by the time
- // we get here.
- if (filter == null)
- {
- continue;
- }
-
- Mesh mesh = filter.sharedMesh;
-
- if (mesh != null && !mesh.bounds.Intersects(bounds))
- {
- // We don't need to do anything to this mesh, move to the next one.
- continue;
- }
-
- // Remove vertices from any mesh that intersects with the bounds.
- Vector3[] verts = mesh.vertices;
- List<int> vertsToRemove = new List<int>();
-
- // Find which mesh vertices are within the bounds.
- for (int i = 0; i < verts.Length; ++i)
- {
- if (bounds.Contains(verts[i]))
- {
- // These vertices are within bounds, so mark them for removal.
- vertsToRemove.Add(i);
- }
-
- // If too much time has passed, we need to return control to the main game loop.
- if ((Time.realtimeSinceStartup - start) > FrameTime)
- {
- // Pause our work here, and continue finding vertices to remove on the next frame.
- yield return null;
- start = Time.realtimeSinceStartup;
- }
- }
-
- if (vertsToRemove.Count == 0)
- {
- // We did not find any vertices to remove, so move to the next mesh.
- continue;
- }
-
- // We found vertices to remove, so now we need to remove any triangles that reference these vertices.
- int[] indices = mesh.GetTriangles(0);
- List<int> updatedIndices = new List<int>();
-
- for (int index = 0; index < indices.Length; index += 3)
- {
- // Each triangle utilizes three slots in the index buffer, check to see if any of the
- // triangle indices contain a vertex that should be removed.
- if (vertsToRemove.Contains(indices[index]) ||
- vertsToRemove.Contains(indices[index + 1]) ||
- vertsToRemove.Contains(indices[index + 2]))
- {
- // Do nothing, we don't want to save this triangle...
- }
- else
- {
- // Every vertex in this triangle is good, so let's save it.
- updatedIndices.Add(indices[index]);
- updatedIndices.Add(indices[index + 1]);
- updatedIndices.Add(indices[index + 2]);
- }
-
- // If too much time has passed, we need to return control to the main game loop.
- if ((Time.realtimeSinceStartup - start) > FrameTime)
- {
- // Pause our work, and continue making additional planes on the next frame.
- yield return null;
- start = Time.realtimeSinceStartup;
- }
- }
-
- if (indices.Length == updatedIndices.Count)
- {
- // None of the verts to remove were being referenced in the triangle list.
- continue;
- }
-
- // Update mesh to use the new triangles.
- mesh.SetTriangles(updatedIndices.ToArray(), 0);
- mesh.RecalculateBounds();
- yield return null;
- start = Time.realtimeSinceStartup;
-
- // Reset the mesh collider to fit the new mesh.
- MeshCollider collider = filter.gameObject.GetComponent<MeshCollider>();
- if (collider != null)
- {
- collider.sharedMesh = null;
- collider.sharedMesh = mesh;
- }
- }
- }
-
- Debug.Log("Finished removing vertices.");
-
- // We are done removing vertices, trigger an event.
- EventHandler handler = RemoveVerticesComplete;
- if (handler != null)
- {
- handler(this, EventArgs.Empty);
- }
-
- removingVerts = false;
- }
- }
- }
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Windows.Speech;
- using HoloToolkit.Unity;
-
- public class PlanesManager : Singleton<PlanesManager>
- {
- [Tooltip("When checked, the SurfaceObserver will stop running after a specified amount of time.")]
- public bool limitScanningByTime = true;
-
- [Tooltip("How much time (in seconds) that the SurfaceObserver will run after being started; used when 'Limit Scanning By Time' is checked.")]
- public float scanTime = 30.0f;
-
- [Tooltip("Material to use when rendering Spatial Mapping meshes while the observer is running.")]
- public Material defaultMaterial;
-
- [Tooltip("Optional Material to use when rendering Spatial Mapping meshes after the observer has been stopped.")]
- public Material secondaryMaterial;
-
- [Tooltip("Minimum number of floor planes required in order to exit scanning/processing mode.")]
- public uint minimumFloors = 1;
-
- [Tooltip("Minimum number of wall planes required in order to exit scanning/processing mode.")]
- public uint minimumWalls = 1;
-
- /// <summary>
- /// 标记表面网格是否处理完成
- /// </summary>
- private bool meshesProcessed = false;
-
- private void Start()
- {
- //设置Surface表面材质
- SpatialMappingManager.Instance.SetSurfaceMaterial(defaultMaterial);
- //注册生成平面后的回调事件
- SurfaceMeshesToPlanes.Instance.MakePlanesComplete += SurfaceMeshesToPlanes_MakePlanesComplete;
- }
-
- /// <summary>
- /// 每帧调用
- /// </summary>
- private void Update()
- {
- //检查表面网格是否处理完成,且扫描空间的时间是否有限制
- if (!meshesProcessed && limitScanningByTime)
- {
- //检查是否超出了扫描上限时间
- if (limitScanningByTime && ((Time.time - SpatialMappingManager.Instance.StartTime) < scanTime))
- {
- }
- else
- {
- //当达到扫描的时间,停止对空间的扫描映射
- if(SpatialMappingManager.Instance.IsObserverRunning())
- {
- SpatialMappingManager.Instance.StopObserver();
- }
- //创建平面
- CreatePlanes();
- //标记表面网格处理完成
- meshesProcessed = true;
- }
- }
- }
-
- /// <summary>
- /// 当SurfaceMeshesToPlanes中 生成Planes处理完成,调用该事件
- /// </summary>
- /// <param name="source">事件源</param>
- /// <param name="args">事件参数</param>
- private void SurfaceMeshesToPlanes_MakePlanesComplete(object source, System.EventArgs args)
- {
- //水平平面列表(地面,桌面等)
- List<GameObject> horizontal = new List<GameObject>();
- //垂直平面列表(墙面等垂直面)
- List<GameObject> vertical = new List<GameObject>();
- //获取所有的水平平面
- horizontal = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Table | PlaneTypes.Floor);
- //获取所有的垂直平面
- vertical = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Wall);
- //检查垂直平面和水平平面的数量是否达到了最少设置的数量,如果未达到,重新进行空间扫描
- if (horizontal.Count >= minimumFloors && vertical.Count >= minimumWalls)
- {
- //删除顶点
- RemoveVertices(SurfaceMeshesToPlanes.Instance.ActivePlanes);
- //设置第二表面材质
- SpatialMappingManager.Instance.SetSurfaceMaterial(secondaryMaterial);
- //获取平面后,在世界中生成物体,传入参数为上面获取到的,水平平面,垂直平面的列表
- ObjectCollectionManager.Instance.GenerateItemsInWorld(horizontal, vertical);
- }
- else
- {
- //未扫描到可以放置物体的垂直平面和水平平面,重新进行空间扫描
- SpatialMappingManager.Instance.StartObserver();
- //重新标记表面网格未处理完成
- meshesProcessed = false;
- }
- }
-
- /// <summary>
- /// 将扫描得到的空间映射信息转换成平面
- /// </summary>
- private void CreatePlanes()
- {
- SurfaceMeshesToPlanes surfaceToPlanes = SurfaceMeshesToPlanes.Instance;
- if (surfaceToPlanes != null && surfaceToPlanes.enabled)
- {
- surfaceToPlanes.MakePlanes();
- }
- }
-
- /// <summary>
- /// 从空间映射中删除生成的三角形
- /// </summary>
- /// <param name="boundingObjects"></param>
- private void RemoveVertices(IEnumerable<GameObject> boundingObjects)
- {
- RemoveSurfaceVertices removeVerts = RemoveSurfaceVertices.Instance;
- if (removeVerts != null && removeVerts.enabled)
- {
- removeVerts.RemoveSurfaceVerticesWithinBounds(boundingObjects);
- }
- }
-
- /// <summary>
- /// 释放资源
- /// </summary>
- private void OnDestroy()
- {
- if (SurfaceMeshesToPlanes.Instance != null)
- {
- SurfaceMeshesToPlanes.Instance.MakePlanesComplete -= SurfaceMeshesToPlanes_MakePlanesComplete;
- }
- }
- }
4、新增Placeable.cs脚本组件
该脚本用于判断虚拟物体在某区域内是否可以放置
- using System.Collections.Generic;
- using UnityEngine;
- using HoloToolkit.Unity;
-
- public enum PlacementSurfaces
- {
- Horizontal = 1,
- Vertical = 2,
- }
-
- /// <summary>
- /// The Placeable class implements the logic used to determine if a GameObject
- /// can be placed on a target surface. Constraints for placement include:
- /// * No part of the GameObject's box collider impacts with another object in the scene
- /// * The object lays flat (within specified tolerances) against the surface
- /// * The object would not fall off of the surface if gravity were enabled.
- /// This class also provides the following visualizations.
- /// * A transparent cube representing the object's box collider.
- /// * Shadow on the target surface indicating whether or not placement is valid.
- /// </summary>
- public class Placeable : MonoBehaviour
- {
- [Tooltip("The base material used to render the bounds asset when placement is allowed.")]
- public Material PlaceableBoundsMaterial = null;
-
- [Tooltip("The base material used to render the bounds asset when placement is not allowed.")]
- public Material NotPlaceableBoundsMaterial = null;
-
- [Tooltip("The material used to render the placement shadow when placement it allowed.")]
- public Material PlaceableShadowMaterial = null;
-
- [Tooltip("The material used to render the placement shadow when placement it not allowed.")]
- public Material NotPlaceableShadowMaterial = null;
-
- [Tooltip("The type of surface on which the object can be placed.")]
- public PlacementSurfaces PlacementSurface = PlacementSurfaces.Horizontal;
-
- [Tooltip("The child object(s) to hide during placement.")]
- public List<GameObject> ChildrenToHide = new List<GameObject>();
-
- //标记是否正在被放置
- public bool IsPlacing { get; private set; }
-
- // The most recent distance to the surface. This is used to
- // locate the object when the user's gaze does not intersect
- // with the Spatial Mapping mesh.
- private float lastDistance = 2.0f;
-
- // 当物体处于正在被放置状态时,离开物体表面的距离
- private float hoverDistance = 0.15f;
-
- // Threshold (the closer to 0, the stricter the standard) used to determine if a surface is flat.
- private float distanceThreshold = 0.02f;
-
- // Threshold (the closer to 1, the stricter the standard) used to determine if a surface is vertical.
- private float upNormalThreshold = 0.9f;
-
- // Maximum distance, from the object, that placement is allowed.
- // This is used when raycasting to see if the object is near a placeable surface.
- private float maximumPlacementDistance = 5.0f;
-
- // Speed (1.0 being fastest) at which the object settles to the surface upon placement.
- private float placementVelocity = 0.06f;
-
- // Indicates whether or not this script manages the object's box collider.
- private bool managingBoxCollider = false;
-
- // The box collider used to determine of the object will fit in the desired location.
- // It is also used to size the bounding cube.
- private BoxCollider boxCollider = null;
-
- // Visible asset used to show the dimensions of the object. This asset is sized
- // using the box collider's bounds.
- private GameObject boundsAsset = null;
-
- // Visible asset used to show the where the object is attempting to be placed.
- // This asset is sized using the box collider's bounds.
- private GameObject shadowAsset = null;
-
- //物体被放置的目标位置
- private Vector3 targetPosition;
-
- private void Awake()
- {
- targetPosition = gameObject.transform.position;
- //获取或创建对撞机
- boxCollider = gameObject.GetComponent<BoxCollider>();
- if (boxCollider == null)
- {
- managingBoxCollider = true;
- boxCollider = gameObject.AddComponent<BoxCollider>();
- boxCollider.enabled = false;
- }
-
- //创建对象表名gameObject的界限
- boundsAsset = GameObject.CreatePrimitive(PrimitiveType.Cube);
- boundsAsset.transform.parent = gameObject.transform;
- boundsAsset.SetActive(false);
-
- //创建gameObject的阴影对象
- shadowAsset = GameObject.CreatePrimitive(PrimitiveType.Quad);
- shadowAsset.transform.parent = gameObject.transform;
- shadowAsset.SetActive(false);
- }
-
- //gameObject被选中时调用
- public void OnSelect()
- {
- if (!IsPlacing)
- {
- //开始放置物体
- OnPlacementStart();
- }
- else
- {
- //停止移动,放置物体
- OnPlacementStop();
- }
- }
-
- private void Update()
- {
- if (IsPlacing)
- {
- //处于移动被放置状态,移动物体
- Move();
- Vector3 targetPosition;
- Vector3 surfaceNormal;
- //检查当前位置是否可以放置该物体
- bool canBePlaced = ValidatePlacement(out targetPosition, out surfaceNormal);
- //显示边界
- DisplayBounds(canBePlaced);
- //显示阴影
- DisplayShadow(targetPosition, surfaceNormal, canBePlaced);
- }
- else
- {
- //隐藏边界和阴影
- boundsAsset.SetActive(false);
- shadowAsset.SetActive(false);
-
- // 将该对象物体放置在物体表面
- float dist = (gameObject.transform.position - targetPosition).magnitude;
- if (dist > 0)
- {
- gameObject.transform.position = Vector3.Lerp(gameObject.transform.position, targetPosition, placementVelocity / dist);
- }
- else
- {
- //显示物体的子物体
- for (int i = 0; i < ChildrenToHide.Count; i++)
- {
- ChildrenToHide[i].SetActive(true);
- }
- }
- }
- }
-
- /// <summary>
- ///检查物体是否可以被放置
- /// </summary>
- /// <param name="position">
- /// 目标位置
- /// </param>
- /// <param name="surfaceNormal">
- /// 物体被放置的平面的法向量
- /// </param>
- /// <returns>
- /// 可以被放置返回true,不能被放置,返回false
- /// </returns>
- private bool ValidatePlacement(out Vector3 position, out Vector3 surfaceNormal)
- {
- Vector3 raycastDirection = gameObject.transform.forward;
-
- if (PlacementSurface == PlacementSurfaces.Horizontal)
- {
- // Placing on horizontal surfaces.
- // Raycast from the bottom face of the box collider.
- raycastDirection = -(Vector3.up);
- }
- position = Vector3.zero;
- surfaceNormal = Vector3.zero;
-
- Vector3[] facePoints = GetColliderFacePoints();
-
- // The origin points we receive are in local space and we
- // need to raycast in world space.
- for (int i = 0; i < facePoints.Length; i++)
- {
- facePoints[i] = gameObject.transform.TransformVector(facePoints[i]) + gameObject.transform.position;
- }
-
- // Cast a ray from the center of the box collider face to the surface.
- RaycastHit centerHit;
- if (!Physics.Raycast(facePoints[0],
- raycastDirection,
- out centerHit,
- maximumPlacementDistance,
- SpatialMappingManager.Instance.LayerMask))
- {
- // If the ray failed to hit the surface, we are done.
- return false;
- }
-
- // We have found a surface. Set position and surfaceNormal.
- position = centerHit.point;
- surfaceNormal = centerHit.normal;
-
- // Cast a ray from the corners of the box collider face to the surface.
- for (int i = 1; i < facePoints.Length; i++)
- {
- RaycastHit hitInfo;
- if (Physics.Raycast(facePoints[i],
- raycastDirection,
- out hitInfo,
- maximumPlacementDistance,
- SpatialMappingManager.Instance.LayerMask))
- {
- // To be a valid placement location, each of the corners must have a similar
- // enough distance to the surface as the center point
- if (!IsEquivalentDistance(centerHit.distance, hitInfo.distance))
- {
- return false;
- }
- }
- else
- {
- // The raycast failed to intersect with the target layer.
- return false;
- }
- }
-
- return true;
- }
-
- /// <summary>
- /// Determine the coordinates, in local space, of the box collider face that
- /// will be placed against the target surface.
- /// </summary>
- /// <returns>
- /// Vector3 array with the center point of the face at index 0.
- /// </returns>
- private Vector3[] GetColliderFacePoints()
- {
- // Get the collider extents.
- // The size values are twice the extents.
- Vector3 extents = boxCollider.size / 2;
-
- // Calculate the min and max values for each coordinate.
- float minX = boxCollider.center.x - extents.x;
- float maxX = boxCollider.center.x + extents.x;
- float minY = boxCollider.center.y - extents.y;
- float maxY = boxCollider.center.y + extents.y;
- float minZ = boxCollider.center.z - extents.z;
- float maxZ = boxCollider.center.z + extents.z;
-
- Vector3 center;
- Vector3 corner0;
- Vector3 corner1;
- Vector3 corner2;
- Vector3 corner3;
-
- if (PlacementSurface == PlacementSurfaces.Horizontal)
- {
- // Placing on horizontal surfaces.
- center = new Vector3(boxCollider.center.x, minY, boxCollider.center.z);
- corner0 = new Vector3(minX, minY, minZ);
- corner1 = new Vector3(minX, minY, maxZ);
- corner2 = new Vector3(maxX, minY, minZ);
- corner3 = new Vector3(maxX, minY, maxZ);
- }
- else
- {
- // Placing on vertical surfaces.
- center = new Vector3(boxCollider.center.x, boxCollider.center.y, maxZ);
- corner0 = new Vector3(minX, minY, maxZ);
- corner1 = new Vector3(minX, maxY, maxZ);
- corner2 = new Vector3(maxX, minY, maxZ);
- corner3 = new Vector3(maxX, maxY, maxZ);
- }
-
- return new Vector3[] { center, corner0, corner1, corner2, corner3 };
- }
-
- /// <summary>
- /// Put the object into placement mode.
- /// </summary>
- public void OnPlacementStart()
- {
- // If we are managing the collider, enable it.
- if (managingBoxCollider)
- {
- boxCollider.enabled = true;
- }
-
- // Hide the child object(s) to make placement easier.
- for (int i = 0; i < ChildrenToHide.Count; i++)
- {
- ChildrenToHide[i].SetActive(false);
- }
-
- // Tell the gesture manager that it is to assume
- // all input is to be given to this object.
- GestureManager.Instance.OverrideFocusedObject = gameObject;
-
- // Enter placement mode.
- IsPlacing = true;
- }
-
- /// <summary>
- /// Take the object out of placement mode.
- /// </summary>
- /// <remarks>
- /// This method will leave the object in placement mode if called while
- /// the object is in an invalid location. To determine whether or not
- /// the object has been placed, check the value of the IsPlacing property.
- /// </remarks>
- public void OnPlacementStop()
- {
- Vector3 position;
- Vector3 surfaceNormal;
-
- if (!ValidatePlacement(out position, out surfaceNormal))
- {
- return;
- }
-
- // The object is allowed to be placed.
- // We are placing at a small buffer away from the surface.
- targetPosition = position + (0.01f * surfaceNormal);
-
- OrientObject(true, surfaceNormal);
-
- // If we are managing the collider, disable it.
- if (managingBoxCollider)
- {
- boxCollider.enabled = false;
- }
-
- // Tell the gesture manager that it is to resume
- // its normal behavior.
- GestureManager.Instance.OverrideFocusedObject = null;
-
- // Exit placement mode.
- IsPlacing = false;
- }
-
- /// <summary>
- /// Positions the object along the surface toward which the user is gazing.
- /// </summary>
- /// <remarks>
- /// If the user's gaze does not intersect with a surface, the object
- /// will remain at the most recently calculated distance.
- /// </remarks>
- private void Move()
- {
- Vector3 moveTo = gameObject.transform.position;
- Vector3 surfaceNormal = Vector3.zero;
- RaycastHit hitInfo;
-
- bool hit = Physics.Raycast(Camera.main.transform.position,
- Camera.main.transform.forward,
- out hitInfo,
- 20f,
- SpatialMappingManager.Instance.LayerMask);
-
- if (hit)
- {
- float offsetDistance = hoverDistance;
-
- // Place the object a small distance away from the surface while keeping
- // the object from going behind the user.
- if (hitInfo.distance <= hoverDistance)
- {
- offsetDistance = 0f;
- }
-
- moveTo = hitInfo.point + (offsetDistance * hitInfo.normal);
-
- lastDistance = hitInfo.distance;
- surfaceNormal = hitInfo.normal;
- }
- else
- {
- // The raycast failed to hit a surface. In this case, keep the object at the distance of the last
- // intersected surface.
- moveTo = Camera.main.transform.position + (Camera.main.transform.forward * lastDistance);
- }
-
- // Follow the user's gaze.
- float dist = Mathf.Abs((gameObject.transform.position - moveTo).magnitude);
- gameObject.transform.position = Vector3.Lerp(gameObject.transform.position, moveTo, placementVelocity / dist);
-
- // Orient the object.
- // We are using the return value from Physics.Raycast to instruct
- // the OrientObject function to align to the vertical surface if appropriate.
- OrientObject(hit, surfaceNormal);
- }
-
- /// <summary>
- /// Orients the object so that it faces the user.
- /// </summary>
- /// <param name="alignToVerticalSurface">
- /// If true and the object is to be placed on a vertical surface,
- /// orient parallel to the target surface. If false, orient the object
- /// to face the user.
- /// </param>
- /// <param name="surfaceNormal">
- /// The target surface's normal vector.
- /// </param>
- /// <remarks>
- /// The aligntoVerticalSurface parameter is ignored if the object
- /// is to be placed on a horizontalSurface
- /// </remarks>
- private void OrientObject(bool alignToVerticalSurface, Vector3 surfaceNormal)
- {
- Quaternion rotation = Camera.main.transform.localRotation;
-
- // If the user's gaze does not intersect with the Spatial Mapping mesh,
- // orient the object towards the user.
- if (alignToVerticalSurface && (PlacementSurface == PlacementSurfaces.Vertical))
- {
- // We are placing on a vertical surface.
- // If the normal of the Spatial Mapping mesh indicates that the
- // surface is vertical, orient parallel to the surface.
- if (Mathf.Abs(surfaceNormal.y) <= (1 - upNormalThreshold))
- {
- rotation = Quaternion.LookRotation(-surfaceNormal, Vector3.up);
- }
- }
- else
- {
- rotation.x = 0f;
- rotation.z = 0f;
- }
-
- gameObject.transform.rotation = rotation;
- }
-
- /// <summary>
- /// Displays the bounds asset.
- /// </summary>
- /// <param name="canBePlaced">
- /// Specifies if the object is in a valid placement location.
- /// </param>
- private void DisplayBounds(bool canBePlaced)
- {
- // Ensure the bounds asset is sized and positioned correctly.
- boundsAsset.transform.localPosition = boxCollider.center;
- boundsAsset.transform.localScale = boxCollider.size;
- boundsAsset.transform.rotation = gameObject.transform.rotation;
-
- // Apply the appropriate material.
- if (canBePlaced)
- {
- boundsAsset.GetComponent<Renderer>().sharedMaterial = PlaceableBoundsMaterial;
- }
- else
- {
- boundsAsset.GetComponent<Renderer>().sharedMaterial = NotPlaceableBoundsMaterial;
- }
-
- // Show the bounds asset.
- boundsAsset.SetActive(true);
- }
-
- /// <summary>
- /// Displays the placement shadow asset.
- /// </summary>
- /// <param name="position">
- /// The position at which to place the shadow asset.
- /// </param>
- /// <param name="surfaceNormal">
- /// The normal of the surface on which the asset will be placed
- /// </param>
- /// <param name="canBePlaced">
- /// Specifies if the object is in a valid placement location.
- /// </param>
- private void DisplayShadow(Vector3 position,
- Vector3 surfaceNormal,
- bool canBePlaced)
- {
- // Rotate the shadow so that it is displayed on the correct surface and matches the object.
- float rotationX = 0.0f;
- if (PlacementSurface == PlacementSurfaces.Horizontal)
- {
- rotationX = 90.0f;
- }
- Quaternion rotation = Quaternion.Euler(rotationX, gameObject.transform.rotation.eulerAngles.y, 0);
-
- shadowAsset.transform.localScale = boxCollider.size;
- shadowAsset.transform.rotation = rotation;
-
- // Apply the appropriate material.
- if (canBePlaced)
- {
- shadowAsset.GetComponent<Renderer>().sharedMaterial = PlaceableShadowMaterial;
- }
- else
- {
- shadowAsset.GetComponent<Renderer>().sharedMaterial = NotPlaceableShadowMaterial;
- }
-
- // Show the shadow asset as appropriate.
- if (position != Vector3.zero)
- {
- // Position the shadow a small distance from the target surface, along the normal.
- shadowAsset.transform.position = position + (0.01f * surfaceNormal);
- shadowAsset.SetActive(true);
- }
- else
- {
- shadowAsset.SetActive(false);
- }
- }
-
- /// <summary>
- /// Determines if two distance values should be considered equivalent.
- /// </summary>
- /// <param name="d1">
- /// Distance to compare.
- /// </param>
- /// <param name="d2">
- /// Distance to compare.
- /// </param>
- /// <returns>
- /// True if the distances are within the desired tolerance, otherwise false.
- /// </returns>
- private bool IsEquivalentDistance(float d1, float d2)
- {
- float dist = Mathf.Abs(d1 - d2);
- return (dist <= distanceThreshold);
- }
-
- /// <summary>
- /// Called when the GameObject is unloaded.
- /// </summary>
- private void OnDestroy()
- {
- // Unload objects we have created.
- Destroy(boundsAsset);
- boundsAsset = null;
- Destroy(shadowAsset);
- shadowAsset = null;
- }
- }
新增两个cube,并添加上面创建的Placeable.cs脚本组件
Cube1用于测试放置在墙面上,设置PlacementSurface为Vertical
Cube2用于测试放置在地面上,设置PlacementSurface为Horizontal
其中涉及到的这些材质,可以自行进行新增几个材质球
PlaceableBounds用于在可放置时显示的边界
NotPlaceableBounds 不可放置时显示的边界
PlaceableShadow 可放置时显示的阴影
NotPlaceableShadow 不可放置时显示的阴影
6、新增游戏对象集合ObjectCollection
创建空对象,并且新增ObjectCollectionManager.cs脚本组件,将上面创建的两个cube拖拽进去
ObjectCollectionManager.cs如下,该脚本主要是用来在空间中生成游戏对象,放置游戏对象
- using System.Collections.Generic;
- using UnityEngine;
- using HoloToolkit.Unity;
-
- /// <summary>
- /// Called by PlaySpaceManager after planes have been generated from the Spatial Mapping Mesh.
- /// This class will create a collection of prefab objects that have the 'Placeable' component and
- /// will attempt to set their initial location on planes that are close to the user.
- /// </summary>
- public class ObjectCollectionManager : Singleton<ObjectCollectionManager>
- {
- [Tooltip("A collection of Placeable space object prefabs to generate in the world.")]
- public List<GameObject> spaceObjectPrefabs;
-
- /// <summary>
- /// Generates a collection of Placeable objects in the world and sets them on planes that match their affinity.
- /// </summary>
- /// <param name="horizontalSurfaces">Horizontal surface planes (floors, tables).</param>
- /// <param name="verticalSurfaces">Vertical surface planes (walls).</param>
- public void GenerateItemsInWorld(List<GameObject> horizontalSurfaces, List<GameObject> verticalSurfaces)
- {
- List<GameObject> horizontalObjects = new List<GameObject>();
- List<GameObject> verticalObjects = new List<GameObject>();
-
- foreach (GameObject spacePrefab in spaceObjectPrefabs)
- {
- Placeable placeable = spacePrefab.GetComponent<Placeable>();
- if (placeable.PlacementSurface == PlacementSurfaces.Horizontal)
- {
- horizontalObjects.Add(spacePrefab);
- }
- else
- {
- verticalObjects.Add(spacePrefab);
- }
- }
-
- if (horizontalObjects.Count > 0)
- {
- CreateSpaceObjects(horizontalObjects, horizontalSurfaces, PlacementSurfaces.Horizontal);
- }
-
- if (verticalObjects.Count > 0)
- {
- CreateSpaceObjects(verticalObjects, verticalSurfaces, PlacementSurfaces.Vertical);
- }
- }
-
- /// <summary>
- /// Creates and positions a collection of Placeable space objects on SurfacePlanes in the environment.
- /// </summary>
- /// <param name="spaceObjects">Collection of prefab GameObjects that have the Placeable component.</param>
- /// <param name="surfaces">Collection of SurfacePlane objects in the world.</param>
- /// <param name="surfaceType">Type of objects and planes that we are trying to match-up.</param>
- private void CreateSpaceObjects(List<GameObject> spaceObjects, List<GameObject> surfaces, PlacementSurfaces surfaceType)
- {
- List<int> UsedPlanes = new List<int>();
-
- // Sort the planes by distance to user.
- surfaces.Sort((lhs, rhs) =>
- {
- Vector3 headPosition = Camera.main.transform.position;
- Collider rightCollider = rhs.GetComponent<Collider>();
- Collider leftCollider = lhs.GetComponent<Collider>();
-
- // This plane is big enough, now we will evaluate how far the plane is from the user's head.
- // Since planes can be quite large, we should find the closest point on the plane's bounds to the
- // user's head, rather than just taking the plane's center position.
- Vector3 rightSpot = rightCollider.ClosestPointOnBounds(headPosition);
- Vector3 leftSpot = leftCollider.ClosestPointOnBounds(headPosition);
-
- return Vector3.Distance(leftSpot, headPosition).CompareTo(Vector3.Distance(rightSpot, headPosition));
- });
-
- foreach (GameObject item in spaceObjects)
- {
- int index = -1;
- Collider collider = item.GetComponent<Collider>();
-
- if (surfaceType == PlacementSurfaces.Vertical)
- {
- index = FindNearestPlane(surfaces, collider.bounds.size, UsedPlanes, true);
- }
- else
- {
- index = FindNearestPlane(surfaces, collider.bounds.size, UsedPlanes, false);
- }
-
- // If we can't find a good plane we will put the object floating in space.
- Vector3 position = Camera.main.transform.position + Camera.main.transform.forward * 2.0f + Camera.main.transform.right * (Random.value - 1.0f) * 2.0f;
- Quaternion rotation = Quaternion.identity;
-
- // If we do find a good plane we can do something smarter.
- if (index >= 0)
- {
- UsedPlanes.Add(index);
- GameObject surface = surfaces[index];
- SurfacePlane plane = surface.GetComponent<SurfacePlane>();
- position = surface.transform.position + (plane.PlaneThickness * plane.SurfaceNormal);
- position = AdjustPositionWithSpatialMap(position, plane.SurfaceNormal);
- rotation = Camera.main.transform.localRotation;
-
- if (surfaceType == PlacementSurfaces.Vertical)
- {
- // Vertical objects should face out from the wall.
- rotation = Quaternion.LookRotation(surface.transform.forward, Vector3.up);
- }
- else
- {
- // Horizontal objects should face the user.
- rotation = Quaternion.LookRotation(Camera.main.transform.position);
- rotation.x = 0f;
- rotation.z = 0f;
- }
- }
-
- //Vector3 finalPosition = AdjustPositionWithSpatialMap(position, surfaceType);
- GameObject spaceObject = Instantiate(item, position, rotation) as GameObject;
- spaceObject.transform.parent = gameObject.transform;
- }
- }
-
- /// <summary>
- /// Attempts to find a the closest plane to the user which is large enough to fit the object.
- /// </summary>
- /// <param name="planes">List of planes to consider for object placement.</param>
- /// <param name="minSize">Minimum size that the plane is required to be.</param>
- /// <param name="startIndex">Index in the planes collection that we want to start at (to help avoid double-placement of objects).</param>
- /// <param name="isVertical">True, if we are currently evaluating vertical surfaces.</param>
- /// <returns></returns>
- private int FindNearestPlane(List<GameObject> planes, Vector3 minSize, List<int> usedPlanes, bool isVertical)
- {
- int planeIndex = -1;
-
- for(int i = 0; i < planes.Count; i++)
- {
- if (usedPlanes.Contains(i))
- {
- continue;
- }
-
- Collider collider = planes[i].GetComponent<Collider>();
- if (isVertical && (collider.bounds.size.x < minSize.x || collider.bounds.size.y < minSize.y))
- {
- // This plane is too small to fit our vertical object.
- continue;
- }
- else if(!isVertical && (collider.bounds.size.x < minSize.x || collider.bounds.size.y < minSize.y))
- {
- // This plane is too small to fit our horizontal object.
- continue;
- }
-
- return i;
- }
-
- return planeIndex;
- }
-
- /// <summary>
- /// Adjusts the initial position of the object if it is being occluded by the spatial map.
- /// </summary>
- /// <param name="position">Position of object to adjust.</param>
- /// <param name="surfaceNormal">Normal of surface that the object is positioned against.</param>
- /// <returns></returns>
- private Vector3 AdjustPositionWithSpatialMap(Vector3 position, Vector3 surfaceNormal)
- {
- Vector3 newPosition = position;
- RaycastHit hitInfo;
- float distance = 0.5f;
-
- // Check to see if there is a SpatialMapping mesh occluding the object at its current position.
- if(Physics.Raycast(position, surfaceNormal, out hitInfo, distance, SpatialMappingManager.Instance.LayerMask))
- {
- // If the object is occluded, reset its position.
- newPosition = hitInfo.point;
- }
-
- return newPosition;
- }
- }
8、运行测试
可以放置,阴影显示绿色
不能放置,阴影显示红色
放置后效果
能够被放置
不能被放置
放置后效果
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。