123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- using UnityEngine;
- using System.Collections.Generic;
- #if UNITY_5_5_OR_NEWER
- using UnityEngine.Profiling;
- #endif
- namespace Pathfinding.Util {
- /// <summary>
- /// Helper for drawing Gizmos in a performant way.
- /// This is a replacement for the Unity Gizmos class as that is not very performant
- /// when drawing very large amounts of geometry (for example a large grid graph).
- /// These gizmos can be persistent, so if the data does not change, the gizmos
- /// do not need to be updated.
- ///
- /// How to use
- /// - Create a Hasher object and hash whatever data you will be using to draw the gizmos
- /// Could be for example the positions of the vertices or something. Just as long as
- /// if the gizmos should change, then the hash changes as well.
- /// - Check if a cached mesh exists for that hash
- /// - If not, then create a Builder object and call the drawing methods until you are done
- /// and then call Finalize with a reference to a gizmos class and the hash you calculated before.
- /// - Call gizmos.Draw with the hash.
- /// - When you are done with drawing gizmos for this frame, call gizmos.FinalizeDraw
- ///
- /// <code>
- /// var a = Vector3.zero;
- /// var b = Vector3.one;
- /// var color = Color.red;
- /// var hasher = new RetainedGizmos.Hasher();
- /// hasher.AddHash(a.GetHashCode());
- /// hasher.AddHash(b.GetHashCode());
- /// hasher.AddHash(color.GetHashCode());
- /// if (!gizmos.Draw(hasher)) {
- /// using (var helper = gizmos.GetGizmoHelper(active, hasher)) {
- /// builder.DrawLine(a, b, color);
- /// builder.Finalize(gizmos, hasher);
- /// }
- /// }
- /// </code>
- /// </summary>
- public class RetainedGizmos {
- /// <summary>Combines hashes into a single hash value</summary>
- public struct Hasher {
- ulong hash;
- bool includePathSearchInfo;
- bool includeAreaInfo;
- PathHandler debugData;
- public Hasher (AstarPath active) {
- hash = 0;
- this.debugData = active.debugPathData;
- includePathSearchInfo = debugData != null && (active.debugMode == GraphDebugMode.F || active.debugMode == GraphDebugMode.G || active.debugMode == GraphDebugMode.H || active.showSearchTree);
- includeAreaInfo = active.debugMode == GraphDebugMode.Areas;
- AddHash((int)active.debugMode);
- AddHash(active.debugFloor.GetHashCode());
- AddHash(active.debugRoof.GetHashCode());
- AddHash(AstarColor.ColorHash());
- }
- public void AddHash (int hash) {
- this.hash = (1572869UL * this.hash) ^ (ulong)hash;
- }
- public void HashNode (GraphNode node) {
- AddHash(node.GetGizmoHashCode());
- if (includeAreaInfo) AddHash((int)node.Area);
- if (includePathSearchInfo) {
- var pathNode = debugData.GetPathNode(node.NodeIndex);
- AddHash((int)pathNode.pathID);
- AddHash(pathNode.pathID == debugData.PathID ? 1 : 0);
- AddHash((int) pathNode.F);
- }
- }
- public ulong Hash {
- get {
- return hash;
- }
- }
- }
- /// <summary>Helper for drawing gizmos</summary>
- public class Builder : IAstarPooledObject {
- List<Vector3> lines = new List<Vector3>();
- List<Color32> lineColors = new List<Color32>();
- List<Mesh> meshes = new List<Mesh>();
- public void DrawMesh (RetainedGizmos gizmos, Vector3[] vertices, List<int> triangles, Color[] colors) {
- var mesh = gizmos.GetMesh();
- // Set all data on the mesh
- mesh.vertices = vertices;
- mesh.SetTriangles(triangles, 0);
- mesh.colors = colors;
- // Upload all data and mark the mesh as unreadable
- mesh.UploadMeshData(true);
- meshes.Add(mesh);
- }
- /// <summary>Draws a wire cube after being transformed the specified transformation</summary>
- public void DrawWireCube (GraphTransform tr, Bounds bounds, Color color) {
- var min = bounds.min;
- var max = bounds.max;
- DrawLine(tr.Transform(new Vector3(min.x, min.y, min.z)), tr.Transform(new Vector3(max.x, min.y, min.z)), color);
- DrawLine(tr.Transform(new Vector3(max.x, min.y, min.z)), tr.Transform(new Vector3(max.x, min.y, max.z)), color);
- DrawLine(tr.Transform(new Vector3(max.x, min.y, max.z)), tr.Transform(new Vector3(min.x, min.y, max.z)), color);
- DrawLine(tr.Transform(new Vector3(min.x, min.y, max.z)), tr.Transform(new Vector3(min.x, min.y, min.z)), color);
- DrawLine(tr.Transform(new Vector3(min.x, max.y, min.z)), tr.Transform(new Vector3(max.x, max.y, min.z)), color);
- DrawLine(tr.Transform(new Vector3(max.x, max.y, min.z)), tr.Transform(new Vector3(max.x, max.y, max.z)), color);
- DrawLine(tr.Transform(new Vector3(max.x, max.y, max.z)), tr.Transform(new Vector3(min.x, max.y, max.z)), color);
- DrawLine(tr.Transform(new Vector3(min.x, max.y, max.z)), tr.Transform(new Vector3(min.x, max.y, min.z)), color);
- DrawLine(tr.Transform(new Vector3(min.x, min.y, min.z)), tr.Transform(new Vector3(min.x, max.y, min.z)), color);
- DrawLine(tr.Transform(new Vector3(max.x, min.y, min.z)), tr.Transform(new Vector3(max.x, max.y, min.z)), color);
- DrawLine(tr.Transform(new Vector3(max.x, min.y, max.z)), tr.Transform(new Vector3(max.x, max.y, max.z)), color);
- DrawLine(tr.Transform(new Vector3(min.x, min.y, max.z)), tr.Transform(new Vector3(min.x, max.y, max.z)), color);
- }
- public void DrawLine (Vector3 start, Vector3 end, Color color) {
- lines.Add(start);
- lines.Add(end);
- var col32 = (Color32)color;
- lineColors.Add(col32);
- lineColors.Add(col32);
- }
- public void Submit (RetainedGizmos gizmos, Hasher hasher) {
- SubmitLines(gizmos, hasher.Hash);
- SubmitMeshes(gizmos, hasher.Hash);
- }
- void SubmitMeshes (RetainedGizmos gizmos, ulong hash) {
- for (int i = 0; i < meshes.Count; i++) {
- gizmos.meshes.Add(new MeshWithHash { hash = hash, mesh = meshes[i], lines = false });
- gizmos.existingHashes.Add(hash);
- }
- }
- void SubmitLines (RetainedGizmos gizmos, ulong hash) {
- // Unity only supports 65535 vertices per mesh. 65532 used because MaxLineEndPointsPerBatch needs to be even.
- const int MaxLineEndPointsPerBatch = 65532/2;
- int batches = (lines.Count + MaxLineEndPointsPerBatch - 1)/MaxLineEndPointsPerBatch;
- for (int batch = 0; batch < batches; batch++) {
- int startIndex = MaxLineEndPointsPerBatch * batch;
- int endIndex = Mathf.Min(startIndex + MaxLineEndPointsPerBatch, lines.Count);
- int lineEndPointCount = endIndex - startIndex;
- UnityEngine.Assertions.Assert.IsTrue(lineEndPointCount % 2 == 0);
- // Use pooled lists to avoid excessive allocations
- var vertices = ListPool<Vector3>.Claim(lineEndPointCount*2);
- var colors = ListPool<Color32>.Claim(lineEndPointCount*2);
- var normals = ListPool<Vector3>.Claim(lineEndPointCount*2);
- var uv = ListPool<Vector2>.Claim(lineEndPointCount*2);
- var tris = ListPool<int>.Claim(lineEndPointCount*3);
- // Loop through each endpoint of the lines
- // and add 2 vertices for each
- for (int j = startIndex; j < endIndex; j++) {
- var vertex = (Vector3)lines[j];
- vertices.Add(vertex);
- vertices.Add(vertex);
- var color = (Color32)lineColors[j];
- colors.Add(color);
- colors.Add(color);
- uv.Add(new Vector2(0, 0));
- uv.Add(new Vector2(1, 0));
- }
- // Loop through each line and add
- // one normal for each vertex
- for (int j = startIndex; j < endIndex; j += 2) {
- var lineDir = (Vector3)(lines[j+1] - lines[j]);
- // Store the line direction in the normals.
- // A line consists of 4 vertices. The line direction will be used to
- // offset the vertices to create a line with a fixed pixel thickness
- normals.Add(lineDir);
- normals.Add(lineDir);
- normals.Add(lineDir);
- normals.Add(lineDir);
- }
- // Setup triangle indices
- // A triangle consists of 3 indices
- // A line (4 vertices) consists of 2 triangles, so 6 triangle indices
- for (int j = 0, v = 0; j < lineEndPointCount*3; j += 6, v += 4) {
- // First triangle
- tris.Add(v+0);
- tris.Add(v+1);
- tris.Add(v+2);
- // Second triangle
- tris.Add(v+1);
- tris.Add(v+3);
- tris.Add(v+2);
- }
- var mesh = gizmos.GetMesh();
- // Set all data on the mesh
- mesh.SetVertices(vertices);
- mesh.SetTriangles(tris, 0);
- mesh.SetColors(colors);
- mesh.SetNormals(normals);
- mesh.SetUVs(0, uv);
- // Upload all data and mark the mesh as unreadable
- mesh.UploadMeshData(true);
- // Release the lists back to the pool
- ListPool<Vector3>.Release(ref vertices);
- ListPool<Color32>.Release(ref colors);
- ListPool<Vector3>.Release(ref normals);
- ListPool<Vector2>.Release(ref uv);
- ListPool<int>.Release(ref tris);
- gizmos.meshes.Add(new MeshWithHash { hash = hash, mesh = mesh, lines = true });
- gizmos.existingHashes.Add(hash);
- }
- }
- void IAstarPooledObject.OnEnterPool () {
- lines.Clear();
- lineColors.Clear();
- meshes.Clear();
- }
- }
- struct MeshWithHash {
- public ulong hash;
- public Mesh mesh;
- public bool lines;
- }
- List<MeshWithHash> meshes = new List<MeshWithHash>();
- HashSet<ulong> usedHashes = new HashSet<ulong>();
- HashSet<ulong> existingHashes = new HashSet<ulong>();
- Stack<Mesh> cachedMeshes = new Stack<Mesh>();
- public GraphGizmoHelper GetSingleFrameGizmoHelper (AstarPath active) {
- var uniqHash = new RetainedGizmos.Hasher();
- uniqHash.AddHash(Time.realtimeSinceStartup.GetHashCode());
- Draw(uniqHash);
- return GetGizmoHelper(active, uniqHash);
- }
- public GraphGizmoHelper GetGizmoHelper (AstarPath active, Hasher hasher) {
- var helper = ObjectPool<GraphGizmoHelper>.Claim();
- helper.Init(active, hasher, this);
- return helper;
- }
- void PoolMesh (Mesh mesh) {
- mesh.Clear();
- cachedMeshes.Push(mesh);
- }
- Mesh GetMesh () {
- if (cachedMeshes.Count > 0) {
- return cachedMeshes.Pop();
- } else {
- return new Mesh {
- hideFlags = HideFlags.DontSave
- };
- }
- }
- /// <summary>Material to use for the navmesh in the editor</summary>
- public Material surfaceMaterial;
- /// <summary>Material to use for the navmesh outline in the editor</summary>
- public Material lineMaterial;
- /// <summary>True if there already is a mesh with the specified hash</summary>
- public bool HasCachedMesh (Hasher hasher) {
- return existingHashes.Contains(hasher.Hash);
- }
- /// <summary>
- /// Schedules the meshes for the specified hash to be drawn.
- /// Returns: False if there is no cached mesh for this hash, you may want to
- /// submit one in that case. The draw command will be issued regardless of the return value.
- /// </summary>
- public bool Draw (Hasher hasher) {
- usedHashes.Add(hasher.Hash);
- return HasCachedMesh(hasher);
- }
- /// <summary>
- /// Schedules all meshes that were drawn the last frame (last time FinalizeDraw was called) to be drawn again.
- /// Also draws any new meshes that have been added since FinalizeDraw was last called.
- /// </summary>
- public void DrawExisting () {
- for (int i = 0; i < meshes.Count; i++) {
- usedHashes.Add(meshes[i].hash);
- }
- }
- /// <summary>Call after all <see cref="Draw"/> commands for the frame have been done to draw everything</summary>
- public void FinalizeDraw () {
- RemoveUnusedMeshes(meshes);
- #if UNITY_EDITOR
- // Make sure the material references are correct
- if (surfaceMaterial == null) surfaceMaterial = UnityEditor.AssetDatabase.LoadAssetAtPath(EditorResourceHelper.editorAssets + "/Materials/Navmesh.mat", typeof(Material)) as Material;
- if (lineMaterial == null) lineMaterial = UnityEditor.AssetDatabase.LoadAssetAtPath(EditorResourceHelper.editorAssets + "/Materials/NavmeshOutline.mat", typeof(Material)) as Material;
- #endif
- var cam = Camera.current;
- var planes = GeometryUtility.CalculateFrustumPlanes(cam);
- // Silently do nothing if the materials are not set
- if (surfaceMaterial == null || lineMaterial == null) return;
- Profiler.BeginSample("Draw Retained Gizmos");
- // First surfaces, then lines
- for (int matIndex = 0; matIndex <= 1; matIndex++) {
- var mat = matIndex == 0 ? surfaceMaterial : lineMaterial;
- for (int pass = 0; pass < mat.passCount; pass++) {
- mat.SetPass(pass);
- for (int i = 0; i < meshes.Count; i++) {
- if (meshes[i].lines == (mat == lineMaterial) && GeometryUtility.TestPlanesAABB(planes, meshes[i].mesh.bounds)) {
- Graphics.DrawMeshNow(meshes[i].mesh, Matrix4x4.identity);
- }
- }
- }
- }
- usedHashes.Clear();
- Profiler.EndSample();
- }
- /// <summary>
- /// Destroys all cached meshes.
- /// Used to make sure that no memory leaks happen in the Unity Editor.
- /// </summary>
- public void ClearCache () {
- usedHashes.Clear();
- RemoveUnusedMeshes(meshes);
- while (cachedMeshes.Count > 0) {
- Mesh.DestroyImmediate(cachedMeshes.Pop());
- }
- UnityEngine.Assertions.Assert.IsTrue(meshes.Count == 0);
- }
- void RemoveUnusedMeshes (List<MeshWithHash> meshList) {
- // Walk the array with two pointers
- // i pointing to the entry that should be filled with something
- // and j pointing to the entry that is a potential candidate for
- // filling the entry at i.
- // When j reaches the end of the list it will be reduced in size
- for (int i = 0, j = 0; i < meshList.Count; ) {
- if (j == meshList.Count) {
- j--;
- meshList.RemoveAt(j);
- } else if (usedHashes.Contains(meshList[j].hash)) {
- meshList[i] = meshList[j];
- i++;
- j++;
- } else {
- PoolMesh(meshList[j].mesh);
- existingHashes.Remove(meshList[j].hash);
- j++;
- }
- }
- }
- }
- }
|