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);
}
}
}