using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using Digger.Modules.Core.Sources; using UnityEngine; using UnityEngine.AI; #if UNITY_EDITOR using UnityEditor; #endif namespace Digger.Modules.Runtime.Sources { /// /// This class contains some high-level methods to update NavMesh at runtime. /// /// Example of use public class DiggerNavMeshRuntime : MonoBehaviour { private DiggerSystem[] diggerSystems; private NavMeshSurface[] surfaces; private List[] initialNavMeshBuildSourcesPerSurface; private List[] navMeshBuildSources; private Bounds[] initialBoundsPerSurface; private Bounds[] boundsPerSurface; private void Awake() { diggerSystems = FindObjectsOfType(); surfaces = FindObjectsOfType(); initialNavMeshBuildSourcesPerSurface = new List[surfaces.Length]; navMeshBuildSources = new List[surfaces.Length]; initialBoundsPerSurface = new Bounds[surfaces.Length]; boundsPerSurface = new Bounds[surfaces.Length]; } /// /// Collects all NavMesh sources in the world. This should be called once, and only once, in the Start method of another MonoBehavior. /// public void CollectNavMeshSources() { var methodCollectSources = typeof(NavMeshSurface).GetMethod("CollectSources", BindingFlags.NonPublic | BindingFlags.Instance); if (methodCollectSources == null) { Debug.LogError("Cannot call method 'CollectSources' on NavMeshSurface. NavMesh support won't work."); return; } var methodCalculateWorldBounds = typeof(NavMeshSurface).GetMethod("CalculateWorldBounds", BindingFlags.NonPublic | BindingFlags.Instance); if (methodCalculateWorldBounds == null) { Debug.LogError("Cannot call method 'CalculateWorldBounds' on NavMeshSurface. NavMesh Digger support won't work."); return; } for (var i = 0; i < surfaces.Length; i++) { var surface = surfaces[i]; var initialNavMeshBuildSources = (List) methodCollectSources.Invoke(surface, null); initialNavMeshBuildSources.RemoveAll(x => x.component != null && x.component.gameObject.GetComponent() != null); #if UNITY_EDITOR if (surface.useGeometry == NavMeshCollectGeometry.RenderMeshes) { var batchingStaticObjects = initialNavMeshBuildSources .Where(x => x.component != null && !(x.component is Terrain) && GameObjectUtility.AreStaticEditorFlagsSet(x.component.gameObject, StaticEditorFlags.BatchingStatic)) .Select(x => x.component.gameObject) .Take(100); foreach (var batchingStaticObject in batchingStaticObjects) { Debug.LogWarning( $"NavMesh sources include object \"{batchingStaticObject.name}\" that has 'Batching Static' enabled. This will cause errors if NavMesh is " + "built or updated at runtime. You should either disable 'Batching Static' or change NavMeshSurface setting 'Use Geometry' to" + "'Physics Colliders'."); } } #endif initialNavMeshBuildSourcesPerSurface[i] = initialNavMeshBuildSources; initialBoundsPerSurface[i] = (Bounds) methodCalculateWorldBounds.Invoke(surface, new object[] {initialNavMeshBuildSources}); navMeshBuildSources[i] = new List(initialNavMeshBuildSources.Capacity + 100); } } /// /// Incrementally and asynchronously updates the NavMesh. Call this when you want the NavMesh to be refreshed, but avoid calling this every frame /// to limit the impact on performance. /// public void UpdateNavMeshAsync() { RefreshNavMeshSources(); StartCoroutine(UpdateNavMeshCoroutine(null)); } /// /// Incrementally and asynchronously updates the NavMesh. Call this when you want the NavMesh to be refreshed, but avoid calling this every frame /// to limit the impact on performance. /// /// Callback method to be invoked once NavMesh has been updated public void UpdateNavMeshAsync(Action callback) { RefreshNavMeshSources(); StartCoroutine(UpdateNavMeshCoroutine(callback)); } private void RefreshNavMeshSources() { for (var i = 0; i < surfaces.Length; i++) { var nmsrc = navMeshBuildSources[i]; nmsrc.Clear(); nmsrc.AddRange(initialNavMeshBuildSourcesPerSurface[i]); boundsPerSurface[i] = initialBoundsPerSurface[i]; foreach (var digger in diggerSystems) { digger.AddNavMeshSources(nmsrc); var b = digger.Bounds; boundsPerSurface[i] = ExpandBounds(boundsPerSurface[i], b.min, b.max); } } } private IEnumerator UpdateNavMeshCoroutine(Action callback) { for (var i = 0; i < surfaces.Length; i++) { var surface = surfaces[i]; var nmsrc = navMeshBuildSources[i]; if (nmsrc.Count == 0) { surface.RemoveData(); continue; } if (!surface.navMeshData) surface.navMeshData = InitializeBakeData(surface); var op = NavMeshBuilder.UpdateNavMeshDataAsync(surface.navMeshData, surface.GetBuildSettings(), nmsrc, boundsPerSurface[i]); yield return op; surface.RemoveData(); surface.AddData(); } callback?.Invoke(); } private static Bounds ExpandBounds(Bounds bounds, Vector3 min, Vector3 max) { if (bounds.min.x < min.x) { min.x = bounds.min.x; } if (bounds.min.y < min.y) { min.y = bounds.min.y; } if (bounds.min.z < min.z) { min.z = bounds.min.z; } if (bounds.max.x > max.x) { max.x = bounds.max.x; } if (bounds.max.y > max.y) { max.y = bounds.max.y; } if (bounds.max.z > max.z) { max.z = bounds.max.z; } bounds.SetMinMax(min, max); return bounds; } private static NavMeshData InitializeBakeData(NavMeshSurface surface) { var emptySources = new List(); var emptyBounds = new Bounds(); return NavMeshBuilder.BuildNavMeshData(surface.GetBuildSettings(), emptySources, emptyBounds , surface.transform.position, surface.transform.rotation); } } }