RetainedGizmos.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. #if UNITY_5_5_OR_NEWER
  4. using UnityEngine.Profiling;
  5. #endif
  6. namespace Pathfinding.Util {
  7. /// <summary>
  8. /// Helper for drawing Gizmos in a performant way.
  9. /// This is a replacement for the Unity Gizmos class as that is not very performant
  10. /// when drawing very large amounts of geometry (for example a large grid graph).
  11. /// These gizmos can be persistent, so if the data does not change, the gizmos
  12. /// do not need to be updated.
  13. ///
  14. /// How to use
  15. /// - Create a Hasher object and hash whatever data you will be using to draw the gizmos
  16. /// Could be for example the positions of the vertices or something. Just as long as
  17. /// if the gizmos should change, then the hash changes as well.
  18. /// - Check if a cached mesh exists for that hash
  19. /// - If not, then create a Builder object and call the drawing methods until you are done
  20. /// and then call Finalize with a reference to a gizmos class and the hash you calculated before.
  21. /// - Call gizmos.Draw with the hash.
  22. /// - When you are done with drawing gizmos for this frame, call gizmos.FinalizeDraw
  23. ///
  24. /// <code>
  25. /// var a = Vector3.zero;
  26. /// var b = Vector3.one;
  27. /// var color = Color.red;
  28. /// var hasher = new RetainedGizmos.Hasher();
  29. /// hasher.AddHash(a.GetHashCode());
  30. /// hasher.AddHash(b.GetHashCode());
  31. /// hasher.AddHash(color.GetHashCode());
  32. /// if (!gizmos.Draw(hasher)) {
  33. /// using (var helper = gizmos.GetGizmoHelper(active, hasher)) {
  34. /// builder.DrawLine(a, b, color);
  35. /// builder.Finalize(gizmos, hasher);
  36. /// }
  37. /// }
  38. /// </code>
  39. /// </summary>
  40. public class RetainedGizmos {
  41. /// <summary>Combines hashes into a single hash value</summary>
  42. public struct Hasher {
  43. ulong hash;
  44. bool includePathSearchInfo;
  45. bool includeAreaInfo;
  46. PathHandler debugData;
  47. public Hasher (AstarPath active) {
  48. hash = 0;
  49. this.debugData = active.debugPathData;
  50. includePathSearchInfo = debugData != null && (active.debugMode == GraphDebugMode.F || active.debugMode == GraphDebugMode.G || active.debugMode == GraphDebugMode.H || active.showSearchTree);
  51. includeAreaInfo = active.debugMode == GraphDebugMode.Areas;
  52. AddHash((int)active.debugMode);
  53. AddHash(active.debugFloor.GetHashCode());
  54. AddHash(active.debugRoof.GetHashCode());
  55. AddHash(AstarColor.ColorHash());
  56. }
  57. public void AddHash (int hash) {
  58. this.hash = (1572869UL * this.hash) ^ (ulong)hash;
  59. }
  60. public void HashNode (GraphNode node) {
  61. AddHash(node.GetGizmoHashCode());
  62. if (includeAreaInfo) AddHash((int)node.Area);
  63. if (includePathSearchInfo) {
  64. var pathNode = debugData.GetPathNode(node.NodeIndex);
  65. AddHash((int)pathNode.pathID);
  66. AddHash(pathNode.pathID == debugData.PathID ? 1 : 0);
  67. AddHash((int) pathNode.F);
  68. }
  69. }
  70. public ulong Hash {
  71. get {
  72. return hash;
  73. }
  74. }
  75. }
  76. /// <summary>Helper for drawing gizmos</summary>
  77. public class Builder : IAstarPooledObject {
  78. List<Vector3> lines = new List<Vector3>();
  79. List<Color32> lineColors = new List<Color32>();
  80. List<Mesh> meshes = new List<Mesh>();
  81. public void DrawMesh (RetainedGizmos gizmos, Vector3[] vertices, List<int> triangles, Color[] colors) {
  82. var mesh = gizmos.GetMesh();
  83. // Set all data on the mesh
  84. mesh.vertices = vertices;
  85. mesh.SetTriangles(triangles, 0);
  86. mesh.colors = colors;
  87. // Upload all data and mark the mesh as unreadable
  88. mesh.UploadMeshData(true);
  89. meshes.Add(mesh);
  90. }
  91. /// <summary>Draws a wire cube after being transformed the specified transformation</summary>
  92. public void DrawWireCube (GraphTransform tr, Bounds bounds, Color color) {
  93. var min = bounds.min;
  94. var max = bounds.max;
  95. DrawLine(tr.Transform(new Vector3(min.x, min.y, min.z)), tr.Transform(new Vector3(max.x, min.y, min.z)), color);
  96. DrawLine(tr.Transform(new Vector3(max.x, min.y, min.z)), tr.Transform(new Vector3(max.x, min.y, max.z)), color);
  97. DrawLine(tr.Transform(new Vector3(max.x, min.y, max.z)), tr.Transform(new Vector3(min.x, min.y, max.z)), color);
  98. DrawLine(tr.Transform(new Vector3(min.x, min.y, max.z)), tr.Transform(new Vector3(min.x, min.y, min.z)), color);
  99. DrawLine(tr.Transform(new Vector3(min.x, max.y, min.z)), tr.Transform(new Vector3(max.x, max.y, min.z)), color);
  100. DrawLine(tr.Transform(new Vector3(max.x, max.y, min.z)), tr.Transform(new Vector3(max.x, max.y, max.z)), color);
  101. DrawLine(tr.Transform(new Vector3(max.x, max.y, max.z)), tr.Transform(new Vector3(min.x, max.y, max.z)), color);
  102. DrawLine(tr.Transform(new Vector3(min.x, max.y, max.z)), tr.Transform(new Vector3(min.x, max.y, min.z)), color);
  103. DrawLine(tr.Transform(new Vector3(min.x, min.y, min.z)), tr.Transform(new Vector3(min.x, max.y, min.z)), color);
  104. DrawLine(tr.Transform(new Vector3(max.x, min.y, min.z)), tr.Transform(new Vector3(max.x, max.y, min.z)), color);
  105. DrawLine(tr.Transform(new Vector3(max.x, min.y, max.z)), tr.Transform(new Vector3(max.x, max.y, max.z)), color);
  106. DrawLine(tr.Transform(new Vector3(min.x, min.y, max.z)), tr.Transform(new Vector3(min.x, max.y, max.z)), color);
  107. }
  108. public void DrawLine (Vector3 start, Vector3 end, Color color) {
  109. lines.Add(start);
  110. lines.Add(end);
  111. var col32 = (Color32)color;
  112. lineColors.Add(col32);
  113. lineColors.Add(col32);
  114. }
  115. public void Submit (RetainedGizmos gizmos, Hasher hasher) {
  116. SubmitLines(gizmos, hasher.Hash);
  117. SubmitMeshes(gizmos, hasher.Hash);
  118. }
  119. void SubmitMeshes (RetainedGizmos gizmos, ulong hash) {
  120. for (int i = 0; i < meshes.Count; i++) {
  121. gizmos.meshes.Add(new MeshWithHash { hash = hash, mesh = meshes[i], lines = false });
  122. gizmos.existingHashes.Add(hash);
  123. }
  124. }
  125. void SubmitLines (RetainedGizmos gizmos, ulong hash) {
  126. // Unity only supports 65535 vertices per mesh. 65532 used because MaxLineEndPointsPerBatch needs to be even.
  127. const int MaxLineEndPointsPerBatch = 65532/2;
  128. int batches = (lines.Count + MaxLineEndPointsPerBatch - 1)/MaxLineEndPointsPerBatch;
  129. for (int batch = 0; batch < batches; batch++) {
  130. int startIndex = MaxLineEndPointsPerBatch * batch;
  131. int endIndex = Mathf.Min(startIndex + MaxLineEndPointsPerBatch, lines.Count);
  132. int lineEndPointCount = endIndex - startIndex;
  133. UnityEngine.Assertions.Assert.IsTrue(lineEndPointCount % 2 == 0);
  134. // Use pooled lists to avoid excessive allocations
  135. var vertices = ListPool<Vector3>.Claim(lineEndPointCount*2);
  136. var colors = ListPool<Color32>.Claim(lineEndPointCount*2);
  137. var normals = ListPool<Vector3>.Claim(lineEndPointCount*2);
  138. var uv = ListPool<Vector2>.Claim(lineEndPointCount*2);
  139. var tris = ListPool<int>.Claim(lineEndPointCount*3);
  140. // Loop through each endpoint of the lines
  141. // and add 2 vertices for each
  142. for (int j = startIndex; j < endIndex; j++) {
  143. var vertex = (Vector3)lines[j];
  144. vertices.Add(vertex);
  145. vertices.Add(vertex);
  146. var color = (Color32)lineColors[j];
  147. colors.Add(color);
  148. colors.Add(color);
  149. uv.Add(new Vector2(0, 0));
  150. uv.Add(new Vector2(1, 0));
  151. }
  152. // Loop through each line and add
  153. // one normal for each vertex
  154. for (int j = startIndex; j < endIndex; j += 2) {
  155. var lineDir = (Vector3)(lines[j+1] - lines[j]);
  156. // Store the line direction in the normals.
  157. // A line consists of 4 vertices. The line direction will be used to
  158. // offset the vertices to create a line with a fixed pixel thickness
  159. normals.Add(lineDir);
  160. normals.Add(lineDir);
  161. normals.Add(lineDir);
  162. normals.Add(lineDir);
  163. }
  164. // Setup triangle indices
  165. // A triangle consists of 3 indices
  166. // A line (4 vertices) consists of 2 triangles, so 6 triangle indices
  167. for (int j = 0, v = 0; j < lineEndPointCount*3; j += 6, v += 4) {
  168. // First triangle
  169. tris.Add(v+0);
  170. tris.Add(v+1);
  171. tris.Add(v+2);
  172. // Second triangle
  173. tris.Add(v+1);
  174. tris.Add(v+3);
  175. tris.Add(v+2);
  176. }
  177. var mesh = gizmos.GetMesh();
  178. // Set all data on the mesh
  179. mesh.SetVertices(vertices);
  180. mesh.SetTriangles(tris, 0);
  181. mesh.SetColors(colors);
  182. mesh.SetNormals(normals);
  183. mesh.SetUVs(0, uv);
  184. // Upload all data and mark the mesh as unreadable
  185. mesh.UploadMeshData(true);
  186. // Release the lists back to the pool
  187. ListPool<Vector3>.Release(ref vertices);
  188. ListPool<Color32>.Release(ref colors);
  189. ListPool<Vector3>.Release(ref normals);
  190. ListPool<Vector2>.Release(ref uv);
  191. ListPool<int>.Release(ref tris);
  192. gizmos.meshes.Add(new MeshWithHash { hash = hash, mesh = mesh, lines = true });
  193. gizmos.existingHashes.Add(hash);
  194. }
  195. }
  196. void IAstarPooledObject.OnEnterPool () {
  197. lines.Clear();
  198. lineColors.Clear();
  199. meshes.Clear();
  200. }
  201. }
  202. struct MeshWithHash {
  203. public ulong hash;
  204. public Mesh mesh;
  205. public bool lines;
  206. }
  207. List<MeshWithHash> meshes = new List<MeshWithHash>();
  208. HashSet<ulong> usedHashes = new HashSet<ulong>();
  209. HashSet<ulong> existingHashes = new HashSet<ulong>();
  210. Stack<Mesh> cachedMeshes = new Stack<Mesh>();
  211. public GraphGizmoHelper GetSingleFrameGizmoHelper (AstarPath active) {
  212. var uniqHash = new RetainedGizmos.Hasher();
  213. uniqHash.AddHash(Time.realtimeSinceStartup.GetHashCode());
  214. Draw(uniqHash);
  215. return GetGizmoHelper(active, uniqHash);
  216. }
  217. public GraphGizmoHelper GetGizmoHelper (AstarPath active, Hasher hasher) {
  218. var helper = ObjectPool<GraphGizmoHelper>.Claim();
  219. helper.Init(active, hasher, this);
  220. return helper;
  221. }
  222. void PoolMesh (Mesh mesh) {
  223. mesh.Clear();
  224. cachedMeshes.Push(mesh);
  225. }
  226. Mesh GetMesh () {
  227. if (cachedMeshes.Count > 0) {
  228. return cachedMeshes.Pop();
  229. } else {
  230. return new Mesh {
  231. hideFlags = HideFlags.DontSave
  232. };
  233. }
  234. }
  235. /// <summary>Material to use for the navmesh in the editor</summary>
  236. public Material surfaceMaterial;
  237. /// <summary>Material to use for the navmesh outline in the editor</summary>
  238. public Material lineMaterial;
  239. /// <summary>True if there already is a mesh with the specified hash</summary>
  240. public bool HasCachedMesh (Hasher hasher) {
  241. return existingHashes.Contains(hasher.Hash);
  242. }
  243. /// <summary>
  244. /// Schedules the meshes for the specified hash to be drawn.
  245. /// Returns: False if there is no cached mesh for this hash, you may want to
  246. /// submit one in that case. The draw command will be issued regardless of the return value.
  247. /// </summary>
  248. public bool Draw (Hasher hasher) {
  249. usedHashes.Add(hasher.Hash);
  250. return HasCachedMesh(hasher);
  251. }
  252. /// <summary>
  253. /// Schedules all meshes that were drawn the last frame (last time FinalizeDraw was called) to be drawn again.
  254. /// Also draws any new meshes that have been added since FinalizeDraw was last called.
  255. /// </summary>
  256. public void DrawExisting () {
  257. for (int i = 0; i < meshes.Count; i++) {
  258. usedHashes.Add(meshes[i].hash);
  259. }
  260. }
  261. /// <summary>Call after all <see cref="Draw"/> commands for the frame have been done to draw everything</summary>
  262. public void FinalizeDraw () {
  263. RemoveUnusedMeshes(meshes);
  264. #if UNITY_EDITOR
  265. // Make sure the material references are correct
  266. if (surfaceMaterial == null) surfaceMaterial = UnityEditor.AssetDatabase.LoadAssetAtPath(EditorResourceHelper.editorAssets + "/Materials/Navmesh.mat", typeof(Material)) as Material;
  267. if (lineMaterial == null) lineMaterial = UnityEditor.AssetDatabase.LoadAssetAtPath(EditorResourceHelper.editorAssets + "/Materials/NavmeshOutline.mat", typeof(Material)) as Material;
  268. #endif
  269. var cam = Camera.current;
  270. var planes = GeometryUtility.CalculateFrustumPlanes(cam);
  271. // Silently do nothing if the materials are not set
  272. if (surfaceMaterial == null || lineMaterial == null) return;
  273. Profiler.BeginSample("Draw Retained Gizmos");
  274. // First surfaces, then lines
  275. for (int matIndex = 0; matIndex <= 1; matIndex++) {
  276. var mat = matIndex == 0 ? surfaceMaterial : lineMaterial;
  277. for (int pass = 0; pass < mat.passCount; pass++) {
  278. mat.SetPass(pass);
  279. for (int i = 0; i < meshes.Count; i++) {
  280. if (meshes[i].lines == (mat == lineMaterial) && GeometryUtility.TestPlanesAABB(planes, meshes[i].mesh.bounds)) {
  281. Graphics.DrawMeshNow(meshes[i].mesh, Matrix4x4.identity);
  282. }
  283. }
  284. }
  285. }
  286. usedHashes.Clear();
  287. Profiler.EndSample();
  288. }
  289. /// <summary>
  290. /// Destroys all cached meshes.
  291. /// Used to make sure that no memory leaks happen in the Unity Editor.
  292. /// </summary>
  293. public void ClearCache () {
  294. usedHashes.Clear();
  295. RemoveUnusedMeshes(meshes);
  296. while (cachedMeshes.Count > 0) {
  297. Mesh.DestroyImmediate(cachedMeshes.Pop());
  298. }
  299. UnityEngine.Assertions.Assert.IsTrue(meshes.Count == 0);
  300. }
  301. void RemoveUnusedMeshes (List<MeshWithHash> meshList) {
  302. // Walk the array with two pointers
  303. // i pointing to the entry that should be filled with something
  304. // and j pointing to the entry that is a potential candidate for
  305. // filling the entry at i.
  306. // When j reaches the end of the list it will be reduced in size
  307. for (int i = 0, j = 0; i < meshList.Count; ) {
  308. if (j == meshList.Count) {
  309. j--;
  310. meshList.RemoveAt(j);
  311. } else if (usedHashes.Contains(meshList[j].hash)) {
  312. meshList[i] = meshList[j];
  313. i++;
  314. j++;
  315. } else {
  316. PoolMesh(meshList[j].mesh);
  317. existingHashes.Remove(meshList[j].hash);
  318. j++;
  319. }
  320. }
  321. }
  322. }
  323. }