using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using Unity.AI.Navigation; using UnityEngine; using UnityEngine.AI; using VOID.Generic.Objects.Traversable; namespace VOID.Generic.Navigation { [RequireComponent(typeof(NavMeshSurface))] [DefaultExecutionOrder(-1)] public class NavigationManager : MonoBehaviour { public static NavigationManager current { get; private set; } private const float NavMeshLinkThreshold = 0.01f; // NavMesh data private NavMeshSurface _navMeshSurface; private NavMeshBuildSettings _navMeshBuildSettings; // Cached links private Dictionary> _linksPerTile; private Dictionary _isPositionStart; private void Awake() { current = this; _navMeshSurface = gameObject.GetComponent(); _navMeshBuildSettings = _navMeshSurface.GetBuildSettings(); _navMeshBuildSettings.preserveTilesOutsideBounds = true; _linksPerTile = new Dictionary>(); _isPositionStart = new Dictionary(); } public void BuildNavMesh() { _navMeshSurface.BuildNavMesh(); } [Button] public void UpdateNavMesh(Bounds bounds) { bounds.extents += Vector3.one * GetTileSize(); NavMeshBuilder.UpdateNavMeshData( _navMeshSurface.navMeshData, _navMeshBuildSettings, GetSources(bounds), bounds ); } private static List GetSources(Bounds bounds) { var sources = new List(); NavMeshBuilder.CollectSources( bounds, LayerManager.Static | LayerManager.NavModifier, NavMeshCollectGeometry.RenderMeshes, 0, GetMarkups(), sources ); AppendSourcesWithModifierVolume(ref sources); return sources; } private static List GetMarkups() { var markups = new List(); foreach (var modifier in NavMeshModifier.activeModifiers) { var markup = new NavMeshBuildMarkup(); markup.root = modifier.transform; markup.overrideArea = modifier.overrideArea; markup.area = modifier.area; markup.ignoreFromBuild = modifier.ignoreFromBuild; markups.Add(markup); } return markups; } private static void AppendSourcesWithModifierVolume(ref List sources) { foreach (var modifier in NavMeshModifierVolume.activeModifiers) { var mcenter = modifier.transform.TransformPoint(modifier.center); var scale = modifier.transform.lossyScale; var msize = new Vector3(modifier.size.x * Mathf.Abs(scale.x), modifier.size.y * Mathf.Abs(scale.y), modifier.size.z * Mathf.Abs(scale.z)); var src = new NavMeshBuildSource(); src.shape = NavMeshBuildSourceShape.ModifierBox; src.transform = Matrix4x4.TRS(mcenter, modifier.transform.rotation, Vector3.one); src.size = msize; src.area = modifier.area; sources.Add(src); } } /// /// Gets cached TraversableObject at given point. /// /// Check point for TraversableObject /// returns found traversable /// returns bool if this is point is from link's end /// Boolean if traversable is found. public bool GetCachedTraversable(Vector3 pos, out TraversableObject traversable, out bool isReverse) { var index = GetTileIndex(pos); if (!_linksPerTile.ContainsKey(index)) { traversable = null; isReverse = false; return false; } var foundPair = _linksPerTile[index].FirstOrDefault(pair => { var position = pair.Key; return Vector3.Distance(position, pos) < NavMeshLinkThreshold; }); traversable = foundPair.Value; isReverse = !_isPositionStart.GetValueOrDefault(foundPair.Key); return traversable; } /// /// Cache both points of TraversableObject. Alias for: /// /// public void RegisterTraversable(TraversableObject traversable) { var startPosition = traversable.traversableData.linkStartPosition; var endPosition = traversable.traversableData.linkEndPosition; RegisterTraversable(startPosition, traversable, true); RegisterTraversable(endPosition, traversable, false); } /// /// Cache given TraversableObject at given point. /// /// At which position, also it calculates into position index. /// Which traversable will be cached. /// Is given point start link? Otherwise it is end. public void RegisterTraversable(Vector3 position, TraversableObject traversable, bool isStart) { var index = GetTileIndex(position); if (!_linksPerTile.ContainsKey(index)) _linksPerTile[index] = new Dictionary(); _linksPerTile[index].Add(position, traversable); _isPositionStart.Add(position, isStart); } private float GetTileSize() { return _navMeshBuildSettings.tileSize * _navMeshBuildSettings.voxelSize; } private Vector2Int GetTileIndex(Vector3 pos) { return new Vector2Int() { x = Mathf.FloorToInt(pos.x / GetTileSize()), y = Mathf.FloorToInt(pos.z / GetTileSize()), }; } } }