NavMeshSurface.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. //======= Copyright (c) Stereolabs Corporation, All rights reserved. ===============
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.AI;
  5. #if UNITY_EDITOR
  6. using UnityEditor;
  7. #endif
  8. /// <summary>
  9. /// Takes a mesh supplied by ZEDManager after a scan and converts it into a
  10. /// NavMesh at runtime that can be used for AI pathfinding.
  11. /// If this script is present, the process will happen automatically when a scan is completed.
  12. /// See the ZED spatial mapping tutorial for more info: https://docs.stereolabs.com/mixed-reality/unity/spatial-mapping-unity/
  13. /// </summary>
  14. //[RequireComponent(typeof(ZEDManager))]
  15. public class NavMeshSurface: MonoBehaviour
  16. {
  17. #if UNITY_5_6_OR_NEWER
  18. /// <summary>
  19. /// Reference to this GameObject's ZEDSpatialMappingManager component.
  20. /// </summary>
  21. [SerializeField]
  22. public ZEDManager zedManagerSpatialMapping = null;
  23. /// <summary>
  24. /// The ID of the agent type the NavMesh will be built for.
  25. /// See available agent types (or create a new one) in Unity's Navigation window.
  26. /// </summary>
  27. [SerializeField]
  28. public int agentTypeID = 0;
  29. /// <summary>
  30. /// List of all NavMeshBuildSource objects the script creates to make the final mesh.
  31. /// One is created for each 'chunk' of the scan.
  32. /// </summary>
  33. private List<NavMeshBuildSource> sources = new List<NavMeshBuildSource>();
  34. /// <summary>
  35. /// The final NavMesh.
  36. /// </summary>
  37. private NavMeshData navMesh;
  38. /// <summary>
  39. /// Outside bounds of the mesh. Computed after all the chunks have been collected.
  40. /// </summary>
  41. private Bounds bounds;
  42. /// <summary>
  43. /// Material used to display the final NavMesh in the editor.
  44. /// Normally Mat_ZED_Transparent_NavMesh.
  45. /// </summary>
  46. private Material materialTransparent;
  47. #endif
  48. /// <summary>
  49. /// Arguments passed by NavMeshSurface's OnNavMeshReady event, so a class that places NPCs
  50. /// (like EnemyManager) can place said NPC properly.
  51. /// </summary>
  52. public class PositionEventArgs : System.EventArgs
  53. {
  54. /// <summary>
  55. /// The world space position of the center of the new NavMesh.
  56. /// </summary>
  57. public Vector3 position;
  58. /// <summary>
  59. /// Whether the NavMesh created is valid for placement (and drawing) purposes.
  60. /// </summary>
  61. public bool valid = false;
  62. /// <summary>
  63. /// The ID of the NavMesh's agent type.
  64. /// </summary>
  65. public int agentTypeID = 0;
  66. }
  67. /// <summary>
  68. /// Event that gets called when the NavMesh is finished being created.
  69. /// </summary>
  70. public static event System.EventHandler<PositionEventArgs> OnNavMeshReady;
  71. /// <summary>
  72. /// Whether to display the NavMesh in the editor.
  73. /// </summary>
  74. [HideInInspector]
  75. [SerializeField]
  76. public bool hideFlag = true;
  77. /// <summary>
  78. /// Whether the NavMesh construction is finished or not.
  79. /// </summary>
  80. [HideInInspector]
  81. [SerializeField]
  82. public bool isOver = false;
  83. /// <summary>
  84. /// The GameObject that represents the NavMesh visually.
  85. /// </summary>
  86. private GameObject navMeshObject;
  87. /// <summary>
  88. /// World space position of the final NavMesh.
  89. /// </summary>
  90. private Vector3 navMeshPosition;
  91. /// <summary>
  92. /// Public accessor for the world space position of the final NavMesh.
  93. /// </summary>
  94. public Vector3 NavMeshPosition { get { return navMeshObject != null ? navMeshPosition : Vector3.zero; } }
  95. // Use this for initialization
  96. void Start()
  97. {
  98. if(!zedManagerSpatialMapping)
  99. {
  100. zedManagerSpatialMapping = FindObjectOfType<ZEDManager>();
  101. }
  102. #if UNITY_5_6_OR_NEWER
  103. navMesh = new NavMeshData();
  104. NavMesh.AddNavMeshData(navMesh);
  105. //Initialize a position for the bounds.
  106. bounds = new Bounds(transform.position, new Vector3(30, 30, 30));
  107. materialTransparent = Resources.Load("Materials/Mat_ZED_Transparent_NavMesh") as Material; //The material applied to the display object.
  108. #endif
  109. if (zedManagerSpatialMapping) {
  110. zedManagerSpatialMapping.GetSpatialMapping.OnMeshReady += MeshIsOver;
  111. zedManagerSpatialMapping.GetSpatialMapping.OnMeshStarted += NewNavMesh;
  112. }
  113. }
  114. /// <summary>
  115. /// Clears the existing display object, if one exists.
  116. /// </summary>
  117. void NewNavMesh()
  118. {
  119. if(navMeshObject != null)
  120. {
  121. Destroy(navMeshObject);
  122. }
  123. }
  124. void OnDisable()
  125. {
  126. //Unsubscribe from events, as they can otherwise still fire when this component is disabled.
  127. if (zedManagerSpatialMapping) {
  128. zedManagerSpatialMapping.GetSpatialMapping.OnMeshReady -= MeshIsOver;
  129. zedManagerSpatialMapping.GetSpatialMapping.OnMeshStarted -= NewNavMesh;
  130. }
  131. }
  132. /// <summary>
  133. /// Called when a new NavMesh has finished being processed.
  134. /// Updates several values and calls the OnMeshReady event with the proper arguments.
  135. /// </summary>
  136. void MeshIsOver()
  137. {
  138. #if UNITY_5_6_OR_NEWER
  139. UpdateNavMesh();
  140. //Nav mesh has been built. Clear the sources as we don't need them
  141. sources.Clear();
  142. //Draw the nav mesh is possible (triangulation from navmesh has return an area)
  143. bool isDrawn = Draw();
  144. #endif
  145. if (isDrawn)
  146. {
  147. PositionEventArgs args = new PositionEventArgs();
  148. args.position = NavMeshPosition;
  149. args.valid = isDrawn;
  150. args.agentTypeID = agentTypeID;
  151. System.EventHandler<PositionEventArgs> handler = OnNavMeshReady;
  152. if (handler != null)
  153. {
  154. handler(this, args);
  155. }
  156. }else
  157. {
  158. Debug.LogWarning(ZEDLogMessage.Error2Str(ZEDLogMessage.ERROR.NAVMESH_NOT_GENERATED));
  159. }
  160. isOver = true;
  161. }
  162. /// <summary>
  163. /// Creates a mesh with the same shape as the NavMesh for the display object.
  164. /// </summary>
  165. /// <returns>Whether the mesh is valid.</returns>
  166. bool Draw()
  167. {
  168. NavMeshTriangulation triangulation = NavMesh.CalculateTriangulation();
  169. if (triangulation.areas.Length == 0)
  170. return false;
  171. //Create a child object of this one that displays the NavMesh.
  172. if (navMeshObject == null)
  173. {
  174. navMeshObject = new GameObject("NavMesh");
  175. }
  176. navMeshObject.transform.parent = transform;
  177. MeshFilter meshFilter = navMeshObject.GetComponent<MeshFilter>();
  178. if (!meshFilter)
  179. {
  180. meshFilter = navMeshObject.AddComponent<MeshFilter>();
  181. }
  182. meshFilter.mesh.Clear();
  183. meshFilter.mesh.vertices = triangulation.vertices;
  184. meshFilter.mesh.triangles = triangulation.indices;
  185. meshFilter.mesh.RecalculateNormals();
  186. MeshRenderer r = navMeshObject.GetComponent<MeshRenderer>();
  187. if (!r)
  188. {
  189. r = navMeshObject.AddComponent<MeshRenderer>();
  190. }
  191. r.sharedMaterial = materialTransparent;
  192. navMeshPosition = r.bounds.center;
  193. navMeshObject.SetActive(false); //Hidden by default.
  194. return true;
  195. }
  196. /// <summary>
  197. /// Collect all the submeshes, or 'chunks', converts them into NavMeshBuildSource objects,
  198. /// then fills the sources list with them.
  199. /// </summary>
  200. void CollectSources()
  201. {
  202. #if UNITY_5_6_OR_NEWER
  203. sources.Clear();
  204. foreach (var o in zedManagerSpatialMapping.MappingChunkList)
  205. {
  206. MeshFilter m = o.o.GetComponent<MeshFilter>();
  207. if (m != null)
  208. {
  209. NavMeshBuildSource s = new NavMeshBuildSource();
  210. s.shape = NavMeshBuildSourceShape.Mesh;
  211. s.sourceObject = m.mesh;
  212. s.transform = o.o.transform.localToWorldMatrix;
  213. s.area = agentTypeID;
  214. sources.Add(s);
  215. }
  216. }
  217. #endif
  218. }
  219. /// <summary>
  220. /// Calculates the NavMesh's bounds and inflates them slightly.
  221. /// Called after all NavmeshBuildSource objects have been created.
  222. /// </summary>
  223. /// <param name="sources">Sources.</param>
  224. void CalculateBounds(List<NavMeshBuildSource> sources)
  225. {
  226. //Use the unscaled matrix for the NavMeshSurface.
  227. if (sources.Count != 0) {
  228. bounds.center = transform.position;
  229. //For each source, grows the bounds.
  230. foreach (var src in sources) {
  231. Mesh m = src.sourceObject as Mesh;
  232. bounds.Encapsulate (m.bounds);
  233. }
  234. }
  235. //Inflate the bounds a bit to avoid clipping co-planar sources.
  236. bounds.Expand(0.1f);
  237. }
  238. /// <summary>
  239. /// Collects the meshes and constructs a single NavMesh from them.
  240. /// </summary>
  241. void UpdateNavMesh()
  242. {
  243. isOver = false;
  244. //First collect all the sources (submeshes).
  245. CollectSources();
  246. #if UNITY_5_6_OR_NEWER
  247. if (sources.Count != 0)
  248. {
  249. //Adjust bounds.
  250. CalculateBounds(sources);
  251. //Update the NavMesh with sources and bounds.
  252. var defaultBuildSettings = NavMesh.GetSettingsByID(agentTypeID);
  253. NavMeshBuilder.UpdateNavMeshData(navMesh, defaultBuildSettings, sources, bounds);
  254. }
  255. #endif
  256. }
  257. #if UNITY_5_6_OR_NEWER
  258. /// <summary>
  259. /// Hide or display the NavMesh using the display object (navMeshObject).
  260. /// </summary>
  261. public void SwitchStateDisplayNavMesh()
  262. {
  263. hideFlag = !hideFlag;
  264. if (navMeshObject != null)
  265. {
  266. navMeshObject.SetActive(hideFlag);
  267. }
  268. }
  269. #endif
  270. private void OnApplicationQuit()
  271. {
  272. Destroy(navMeshObject);
  273. }
  274. }
  275. #if UNITY_5_6_OR_NEWER
  276. #if UNITY_EDITOR
  277. /// <summary>
  278. /// Custom editor for NavMeshSurface to extend how it's drawn in the Inspector.
  279. /// It adds a custom drop-down for agent types, and the Display button that toggles the NavMesh's visibility.
  280. /// </summary>
  281. [CustomEditor(typeof(NavMeshSurface))]
  282. class ZEDNavMeshEditor : Editor
  283. {
  284. //Represent the relevant properties as SerializedProperties.
  285. //This lets us manipulate and also save (serialize) the data in the scene.
  286. /// <summary>
  287. /// Bound to zedManager, manager of ZED Camera
  288. /// </summary>
  289. SerializedProperty managerID;
  290. /// <summary>
  291. /// Bound to agentTypeID, the agent type of the NavMesh.
  292. /// </summary>
  293. SerializedProperty agentID;
  294. /// <summary>
  295. /// Bound to hideFlag, whether the NavMesh is visible or not.
  296. /// </summary>
  297. SerializedProperty hideFlag;
  298. /// <summary>
  299. /// Bound to isOver, whether the NavMesh has been calculated or not.
  300. /// </summary>
  301. SerializedProperty isOver;
  302. private void OnEnable()
  303. {
  304. //Bind the serialized properties to the relevant properties in NavMeshSurface.
  305. managerID = serializedObject.FindProperty("zedManagerSpatialMapping");
  306. agentID = serializedObject.FindProperty("agentTypeID");
  307. hideFlag = serializedObject.FindProperty("hideFlag");
  308. isOver = serializedObject.FindProperty("isOver");
  309. }
  310. /// <summary>
  311. /// Creates a custom drop-down for the Agent Type enum, which includes a button to
  312. /// open the Navigation window for creating/looking up agent types.
  313. /// </summary>
  314. /// <param name="labelName">Text of the label beside the drop-down. Usually "Agent Type".</param>
  315. /// <param name="agentTypeID">SerializedProperty that the drop-down reads/writes.</param>
  316. public static void AgentTypePopup(string labelName, SerializedProperty agentTypeID)
  317. {
  318. var index = -1;
  319. var count = NavMesh.GetSettingsCount();
  320. //var agentTypeNames = new string[count + 2];
  321. GUIContent[] agentTypeNames = new GUIContent[count + 2];
  322. for (var i = 0; i < count; i++)
  323. {
  324. var id = NavMesh.GetSettingsByIndex(i).agentTypeID;
  325. var name = NavMesh.GetSettingsNameFromID(id);
  326. agentTypeNames[i] = new GUIContent(name);
  327. if (id == agentTypeID.intValue)
  328. index = i;
  329. }
  330. agentTypeNames[count] = new GUIContent("");
  331. agentTypeNames[count + 1] = new GUIContent("Open Agent Settings...");
  332. bool validAgentType = index != -1;
  333. if (!validAgentType)
  334. {
  335. EditorGUILayout.HelpBox("Agent Type invalid.", MessageType.Warning);
  336. }
  337. var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
  338. EditorGUI.BeginProperty(rect, GUIContent.none, agentTypeID);
  339. EditorGUI.BeginChangeCheck();
  340. GUIContent text = new GUIContent(labelName, "The ID of the agent type the NavMesh will be built for.");
  341. index = EditorGUI.Popup(rect, text, index, agentTypeNames);
  342. if (EditorGUI.EndChangeCheck())
  343. {
  344. if (index >= 0 && index < count)
  345. {
  346. var id = NavMesh.GetSettingsByIndex(index).agentTypeID;
  347. agentTypeID.intValue = id;
  348. }
  349. else if (index == count + 1)
  350. {
  351. UnityEditor.AI.NavMeshEditorHelpers.OpenAgentSettings(-1);
  352. }
  353. }
  354. EditorGUI.EndProperty();
  355. }
  356. public override void OnInspectorGUI()
  357. {
  358. NavMeshSurface obj = (NavMeshSurface)target;
  359. serializedObject.Update();
  360. GUIContent zedManagerLink = new GUIContent("ZED Rig", "ZEDManager (Rig) to link to NavMesh generation");
  361. obj.zedManagerSpatialMapping = (ZEDManager)EditorGUILayout.ObjectField(zedManagerLink, obj.zedManagerSpatialMapping , typeof(ZEDManager), true);
  362. AgentTypePopup("Agent Type", agentID);
  363. GUI.enabled = isOver.boolValue; //Only let the user click the button if the NavMesh is finished.
  364. GUIContent text = new GUIContent(hideFlag.boolValue ? "Hide" : "Display", "Toggle the visibility of the NavMesh.");
  365. if (GUILayout.Button(text))
  366. {
  367. obj.SwitchStateDisplayNavMesh(); //Switch the setting to whatever it wasn't before.
  368. }
  369. serializedObject.ApplyModifiedProperties(); //Applies everything we just changed.
  370. }
  371. }
  372. #endif
  373. #endif