123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 |
- //======= Copyright (c) Stereolabs Corporation, All rights reserved. ===============
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.AI;
- #if UNITY_EDITOR
- using UnityEditor;
- #endif
- /// <summary>
- /// Takes a mesh supplied by ZEDManager after a scan and converts it into a
- /// NavMesh at runtime that can be used for AI pathfinding.
- /// If this script is present, the process will happen automatically when a scan is completed.
- /// See the ZED spatial mapping tutorial for more info: https://docs.stereolabs.com/mixed-reality/unity/spatial-mapping-unity/
- /// </summary>
- //[RequireComponent(typeof(ZEDManager))]
- public class NavMeshSurface: MonoBehaviour
- {
- #if UNITY_5_6_OR_NEWER
- /// <summary>
- /// Reference to this GameObject's ZEDSpatialMappingManager component.
- /// </summary>
- [SerializeField]
- public ZEDManager zedManagerSpatialMapping = null;
- /// <summary>
- /// The ID of the agent type the NavMesh will be built for.
- /// See available agent types (or create a new one) in Unity's Navigation window.
- /// </summary>
- [SerializeField]
- public int agentTypeID = 0;
- /// <summary>
- /// List of all NavMeshBuildSource objects the script creates to make the final mesh.
- /// One is created for each 'chunk' of the scan.
- /// </summary>
- private List<NavMeshBuildSource> sources = new List<NavMeshBuildSource>();
- /// <summary>
- /// The final NavMesh.
- /// </summary>
- private NavMeshData navMesh;
- /// <summary>
- /// Outside bounds of the mesh. Computed after all the chunks have been collected.
- /// </summary>
- private Bounds bounds;
- /// <summary>
- /// Material used to display the final NavMesh in the editor.
- /// Normally Mat_ZED_Transparent_NavMesh.
- /// </summary>
- private Material materialTransparent;
- #endif
- /// <summary>
- /// Arguments passed by NavMeshSurface's OnNavMeshReady event, so a class that places NPCs
- /// (like EnemyManager) can place said NPC properly.
- /// </summary>
- public class PositionEventArgs : System.EventArgs
- {
- /// <summary>
- /// The world space position of the center of the new NavMesh.
- /// </summary>
- public Vector3 position;
- /// <summary>
- /// Whether the NavMesh created is valid for placement (and drawing) purposes.
- /// </summary>
- public bool valid = false;
- /// <summary>
- /// The ID of the NavMesh's agent type.
- /// </summary>
- public int agentTypeID = 0;
- }
- /// <summary>
- /// Event that gets called when the NavMesh is finished being created.
- /// </summary>
- public static event System.EventHandler<PositionEventArgs> OnNavMeshReady;
- /// <summary>
- /// Whether to display the NavMesh in the editor.
- /// </summary>
- [HideInInspector]
- [SerializeField]
- public bool hideFlag = true;
- /// <summary>
- /// Whether the NavMesh construction is finished or not.
- /// </summary>
- [HideInInspector]
- [SerializeField]
- public bool isOver = false;
- /// <summary>
- /// The GameObject that represents the NavMesh visually.
- /// </summary>
- private GameObject navMeshObject;
- /// <summary>
- /// World space position of the final NavMesh.
- /// </summary>
- private Vector3 navMeshPosition;
- /// <summary>
- /// Public accessor for the world space position of the final NavMesh.
- /// </summary>
- public Vector3 NavMeshPosition { get { return navMeshObject != null ? navMeshPosition : Vector3.zero; } }
- // Use this for initialization
- void Start()
- {
- if(!zedManagerSpatialMapping)
- {
- zedManagerSpatialMapping = FindObjectOfType<ZEDManager>();
- }
- #if UNITY_5_6_OR_NEWER
- navMesh = new NavMeshData();
- NavMesh.AddNavMeshData(navMesh);
- //Initialize a position for the bounds.
- bounds = new Bounds(transform.position, new Vector3(30, 30, 30));
- materialTransparent = Resources.Load("Materials/Mat_ZED_Transparent_NavMesh") as Material; //The material applied to the display object.
- #endif
- if (zedManagerSpatialMapping) {
- zedManagerSpatialMapping.GetSpatialMapping.OnMeshReady += MeshIsOver;
- zedManagerSpatialMapping.GetSpatialMapping.OnMeshStarted += NewNavMesh;
- }
- }
-
- /// <summary>
- /// Clears the existing display object, if one exists.
- /// </summary>
- void NewNavMesh()
- {
- if(navMeshObject != null)
- {
- Destroy(navMeshObject);
- }
- }
- void OnDisable()
- {
- //Unsubscribe from events, as they can otherwise still fire when this component is disabled.
- if (zedManagerSpatialMapping) {
- zedManagerSpatialMapping.GetSpatialMapping.OnMeshReady -= MeshIsOver;
- zedManagerSpatialMapping.GetSpatialMapping.OnMeshStarted -= NewNavMesh;
- }
- }
- /// <summary>
- /// Called when a new NavMesh has finished being processed.
- /// Updates several values and calls the OnMeshReady event with the proper arguments.
- /// </summary>
- void MeshIsOver()
- {
- #if UNITY_5_6_OR_NEWER
- UpdateNavMesh();
- //Nav mesh has been built. Clear the sources as we don't need them
- sources.Clear();
- //Draw the nav mesh is possible (triangulation from navmesh has return an area)
- bool isDrawn = Draw();
- #endif
- if (isDrawn)
- {
- PositionEventArgs args = new PositionEventArgs();
- args.position = NavMeshPosition;
- args.valid = isDrawn;
- args.agentTypeID = agentTypeID;
- System.EventHandler<PositionEventArgs> handler = OnNavMeshReady;
- if (handler != null)
- {
- handler(this, args);
- }
- }else
- {
- Debug.LogWarning(ZEDLogMessage.Error2Str(ZEDLogMessage.ERROR.NAVMESH_NOT_GENERATED));
- }
- isOver = true;
-
- }
- /// <summary>
- /// Creates a mesh with the same shape as the NavMesh for the display object.
- /// </summary>
- /// <returns>Whether the mesh is valid.</returns>
- bool Draw()
- {
- NavMeshTriangulation triangulation = NavMesh.CalculateTriangulation();
- if (triangulation.areas.Length == 0)
- return false;
-
- //Create a child object of this one that displays the NavMesh.
- if (navMeshObject == null)
- {
- navMeshObject = new GameObject("NavMesh");
- }
- navMeshObject.transform.parent = transform;
-
-
- MeshFilter meshFilter = navMeshObject.GetComponent<MeshFilter>();
- if (!meshFilter)
- {
- meshFilter = navMeshObject.AddComponent<MeshFilter>();
- }
- meshFilter.mesh.Clear();
- meshFilter.mesh.vertices = triangulation.vertices;
- meshFilter.mesh.triangles = triangulation.indices;
- meshFilter.mesh.RecalculateNormals();
- MeshRenderer r = navMeshObject.GetComponent<MeshRenderer>();
- if (!r)
- {
- r = navMeshObject.AddComponent<MeshRenderer>();
- }
- r.sharedMaterial = materialTransparent;
- navMeshPosition = r.bounds.center;
- navMeshObject.SetActive(false); //Hidden by default.
- return true;
- }
- /// <summary>
- /// Collect all the submeshes, or 'chunks', converts them into NavMeshBuildSource objects,
- /// then fills the sources list with them.
- /// </summary>
- void CollectSources()
- {
- #if UNITY_5_6_OR_NEWER
- sources.Clear();
- foreach (var o in zedManagerSpatialMapping.MappingChunkList)
- {
- MeshFilter m = o.o.GetComponent<MeshFilter>();
- if (m != null)
- {
- NavMeshBuildSource s = new NavMeshBuildSource();
- s.shape = NavMeshBuildSourceShape.Mesh;
- s.sourceObject = m.mesh;
- s.transform = o.o.transform.localToWorldMatrix;
- s.area = agentTypeID;
- sources.Add(s);
- }
- }
- #endif
- }
- /// <summary>
- /// Calculates the NavMesh's bounds and inflates them slightly.
- /// Called after all NavmeshBuildSource objects have been created.
- /// </summary>
- /// <param name="sources">Sources.</param>
- void CalculateBounds(List<NavMeshBuildSource> sources)
- {
- //Use the unscaled matrix for the NavMeshSurface.
- if (sources.Count != 0) {
- bounds.center = transform.position;
-
- //For each source, grows the bounds.
- foreach (var src in sources) {
- Mesh m = src.sourceObject as Mesh;
- bounds.Encapsulate (m.bounds);
- }
- }
- //Inflate the bounds a bit to avoid clipping co-planar sources.
- bounds.Expand(0.1f);
- }
- /// <summary>
- /// Collects the meshes and constructs a single NavMesh from them.
- /// </summary>
- void UpdateNavMesh()
- {
- isOver = false;
-
- //First collect all the sources (submeshes).
- CollectSources();
- #if UNITY_5_6_OR_NEWER
- if (sources.Count != 0)
- {
- //Adjust bounds.
- CalculateBounds(sources);
- //Update the NavMesh with sources and bounds.
- var defaultBuildSettings = NavMesh.GetSettingsByID(agentTypeID);
- NavMeshBuilder.UpdateNavMeshData(navMesh, defaultBuildSettings, sources, bounds);
-
- }
- #endif
- }
- #if UNITY_5_6_OR_NEWER
- /// <summary>
- /// Hide or display the NavMesh using the display object (navMeshObject).
- /// </summary>
- public void SwitchStateDisplayNavMesh()
- {
- hideFlag = !hideFlag;
- if (navMeshObject != null)
- {
- navMeshObject.SetActive(hideFlag);
- }
- }
- #endif
- private void OnApplicationQuit()
- {
- Destroy(navMeshObject);
- }
- }
- #if UNITY_5_6_OR_NEWER
- #if UNITY_EDITOR
- /// <summary>
- /// Custom editor for NavMeshSurface to extend how it's drawn in the Inspector.
- /// It adds a custom drop-down for agent types, and the Display button that toggles the NavMesh's visibility.
- /// </summary>
- [CustomEditor(typeof(NavMeshSurface))]
- class ZEDNavMeshEditor : Editor
- {
- //Represent the relevant properties as SerializedProperties.
- //This lets us manipulate and also save (serialize) the data in the scene.
- /// <summary>
- /// Bound to zedManager, manager of ZED Camera
- /// </summary>
- SerializedProperty managerID;
- /// <summary>
- /// Bound to agentTypeID, the agent type of the NavMesh.
- /// </summary>
- SerializedProperty agentID;
- /// <summary>
- /// Bound to hideFlag, whether the NavMesh is visible or not.
- /// </summary>
- SerializedProperty hideFlag;
- /// <summary>
- /// Bound to isOver, whether the NavMesh has been calculated or not.
- /// </summary>
- SerializedProperty isOver;
- private void OnEnable()
- {
- //Bind the serialized properties to the relevant properties in NavMeshSurface.
- managerID = serializedObject.FindProperty("zedManagerSpatialMapping");
- agentID = serializedObject.FindProperty("agentTypeID");
- hideFlag = serializedObject.FindProperty("hideFlag");
- isOver = serializedObject.FindProperty("isOver");
- }
- /// <summary>
- /// Creates a custom drop-down for the Agent Type enum, which includes a button to
- /// open the Navigation window for creating/looking up agent types.
- /// </summary>
- /// <param name="labelName">Text of the label beside the drop-down. Usually "Agent Type".</param>
- /// <param name="agentTypeID">SerializedProperty that the drop-down reads/writes.</param>
- public static void AgentTypePopup(string labelName, SerializedProperty agentTypeID)
- {
- var index = -1;
- var count = NavMesh.GetSettingsCount();
- //var agentTypeNames = new string[count + 2];
- GUIContent[] agentTypeNames = new GUIContent[count + 2];
- for (var i = 0; i < count; i++)
- {
- var id = NavMesh.GetSettingsByIndex(i).agentTypeID;
- var name = NavMesh.GetSettingsNameFromID(id);
- agentTypeNames[i] = new GUIContent(name);
- if (id == agentTypeID.intValue)
- index = i;
- }
- agentTypeNames[count] = new GUIContent("");
- agentTypeNames[count + 1] = new GUIContent("Open Agent Settings...");
- bool validAgentType = index != -1;
- if (!validAgentType)
- {
- EditorGUILayout.HelpBox("Agent Type invalid.", MessageType.Warning);
- }
- var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
- EditorGUI.BeginProperty(rect, GUIContent.none, agentTypeID);
- EditorGUI.BeginChangeCheck();
- GUIContent text = new GUIContent(labelName, "The ID of the agent type the NavMesh will be built for.");
- index = EditorGUI.Popup(rect, text, index, agentTypeNames);
- if (EditorGUI.EndChangeCheck())
- {
- if (index >= 0 && index < count)
- {
- var id = NavMesh.GetSettingsByIndex(index).agentTypeID;
- agentTypeID.intValue = id;
- }
- else if (index == count + 1)
- {
- UnityEditor.AI.NavMeshEditorHelpers.OpenAgentSettings(-1);
- }
- }
- EditorGUI.EndProperty();
- }
- public override void OnInspectorGUI()
- {
- NavMeshSurface obj = (NavMeshSurface)target;
- serializedObject.Update();
- GUIContent zedManagerLink = new GUIContent("ZED Rig", "ZEDManager (Rig) to link to NavMesh generation");
- obj.zedManagerSpatialMapping = (ZEDManager)EditorGUILayout.ObjectField(zedManagerLink, obj.zedManagerSpatialMapping , typeof(ZEDManager), true);
- AgentTypePopup("Agent Type", agentID);
- GUI.enabled = isOver.boolValue; //Only let the user click the button if the NavMesh is finished.
- GUIContent text = new GUIContent(hideFlag.boolValue ? "Hide" : "Display", "Toggle the visibility of the NavMesh.");
- if (GUILayout.Button(text))
- {
- obj.SwitchStateDisplayNavMesh(); //Switch the setting to whatever it wasn't before.
- }
- serializedObject.ApplyModifiedProperties(); //Applies everything we just changed.
- }
- }
- #endif
- #endif
|