diff options
author | chai <215380520@qq.com> | 2024-05-23 10:08:29 +0800 |
---|---|---|
committer | chai <215380520@qq.com> | 2024-05-23 10:08:29 +0800 |
commit | 8722a9920c1f6119bf6e769cba270e63097f8e25 (patch) | |
tree | 2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Navmesh/NavmeshUpdates.cs | |
parent | 3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff) |
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Navmesh/NavmeshUpdates.cs')
-rw-r--r-- | Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Navmesh/NavmeshUpdates.cs | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Navmesh/NavmeshUpdates.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Navmesh/NavmeshUpdates.cs new file mode 100644 index 0000000..501c9a8 --- /dev/null +++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Navmesh/NavmeshUpdates.cs @@ -0,0 +1,336 @@ +using UnityEngine; +using System.Collections.Generic; +using Pathfinding.Util; +using Pathfinding.Serialization; +using UnityEngine.Profiling; + +namespace Pathfinding.Graphs.Navmesh { + /// <summary> + /// Helper for navmesh cut objects. + /// Responsible for keeping track of which navmesh cuts have moved and coordinating graph updates to account for those changes. + /// + /// See: navmeshcutting (view in online documentation for working links) + /// See: <see cref="AstarPath.navmeshUpdates"/> + /// See: <see cref="Pathfinding.NavmeshBase.enableNavmeshCutting"/> + /// </summary> + [System.Serializable] + public class NavmeshUpdates { + /// <summary> + /// How often to check if an update needs to be done (real seconds between checks). + /// For worlds with a very large number of NavmeshCut objects, it might be bad for performance to do this check every frame. + /// If you think this is a performance penalty, increase this number to check less often. + /// + /// For almost all games, this can be kept at 0. + /// + /// If negative, no updates will be done. They must be manually triggered using <see cref="ForceUpdate"/>. + /// + /// <code> + /// // Check every frame (the default) + /// AstarPath.active.navmeshUpdates.updateInterval = 0; + /// + /// // Check every 0.1 seconds + /// AstarPath.active.navmeshUpdates.updateInterval = 0.1f; + /// + /// // Never check for changes + /// AstarPath.active.navmeshUpdates.updateInterval = -1; + /// // You will have to schedule updates manually using + /// AstarPath.active.navmeshUpdates.ForceUpdate(); + /// </code> + /// + /// You can also find this in the AstarPath inspector under Settings. + /// [Open online documentation to see images] + /// </summary> + public float updateInterval; + internal AstarPath astar; + + /// <summary>Last time navmesh cuts were applied</summary> + float lastUpdateTime = float.NegativeInfinity; + + /// <summary>Stores navmesh cutting related data for a single graph</summary> + public class NavmeshUpdateSettings { + public TileHandler handler; + public readonly List<IntRect> forcedReloadRects = new List<IntRect>(); + readonly NavmeshBase graph; + + public NavmeshUpdateSettings(NavmeshBase graph) { + this.graph = graph; + } + + public void ReloadAllTiles () { + if (handler != null) handler.ReloadInBounds(new IntRect(int.MinValue, int.MinValue, int.MaxValue, int.MaxValue)); + } + + public void Refresh (bool forceCreate = false) { + if (!graph.enableNavmeshCutting) { + if (handler != null) { + handler.cuts.Clear(); + ReloadAllTiles(); + // Make sure the updates are applied immediately. + // This is important because if navmesh cutting is enabled immediately after this + // then it will call CreateTileTypesFromGraph, and we need to ensure that it is not + // calling that when the graph still has cuts in it as they will then be baked in. + graph.active.FlushGraphUpdates(); + graph.active.FlushWorkItems(); + + forcedReloadRects.ClearFast(); + handler = null; + } + } else if ((handler == null && (forceCreate || NavmeshClipper.allEnabled.Count > 0)) || (handler != null && !handler.isValid)) { + // Note: Only create a handler if there are any navmesh cuts in the scene. + // We don't want to waste a lot of memory if navmesh cutting isn't actually used for anything + // and even more important: we don't want to do any sporadic updates to the graph which + // may clear the graph's tags or change it's structure (e.g from the delaunay optimization in the TileHandler). + + // The tile handler is invalid (or doesn't exist), so re-create it + handler = new TileHandler(graph); + for (int i = 0; i < NavmeshClipper.allEnabled.Count; i++) AddClipper(NavmeshClipper.allEnabled[i]); + handler.CreateTileTypesFromGraph(); + + // Reload in huge bounds. This will cause all tiles to be updated. + forcedReloadRects.Add(new IntRect(int.MinValue, int.MinValue, int.MaxValue, int.MaxValue)); + } + } + + public void DiscardPending () { + if (handler != null) { + for (int j = 0; j < NavmeshClipper.allEnabled.Count; j++) { + var cut = NavmeshClipper.allEnabled[j]; + var root = handler.cuts.GetRoot(cut); + if (root != null) cut.NotifyUpdated(root); + } + } + + forcedReloadRects.Clear(); + } + + /// <summary>Called when the graph has been resized to a different tile count</summary> + public void OnResized (IntRect newTileBounds) { + if (handler == null) return; + + this.handler.Resize(newTileBounds); + + var characterRadius = graph.NavmeshCuttingCharacterRadius; + + // New tiles may have been created when resizing. If a cut was on the edge of the graph bounds, + // it may intersect with the new tiles and we will need to recalculate them in that case. + var allCuts = handler.cuts.AllItems; + for (var cut = allCuts; cut != null; cut = cut.next) { + var newGraphSpaceBounds = cut.obj.GetBounds(handler.graph.transform, characterRadius); + var newTouchingTiles = handler.graph.GetTouchingTilesInGraphSpace(newGraphSpaceBounds); + if (cut.previousBounds != newTouchingTiles) { + handler.cuts.Dirty(cut.obj); + handler.cuts.Move(cut.obj, newTouchingTiles); + } + } + } + + /// <summary>Called when some tiles in a recast graph have been completely recalculated (e.g from scanning the graph)</summary> + public void OnRecalculatedTiles (NavmeshTile[] tiles) { + Refresh(); + if (handler != null) handler.OnRecalculatedTiles(tiles); + + // If the whole graph was updated then mark all navmesh cuts as being up to date. + // If only a part of the graph was updated then a navmesh cut might be over the non-updated part + // as well, and in that case we don't want to mark it as fully updated. + if (graph.GetTiles().Length == tiles.Length) { + DiscardPending(); + } + } + + public void Dirty (NavmeshClipper obj) { + // If we have no handler then we can ignore this. If we would later create a handler the object would be automatically dirtied anyway. + if (handler == null) return; + handler.cuts.Dirty(obj); + } + + /// <summary>Called when a NavmeshCut or NavmeshAdd is enabled</summary> + public void AddClipper (NavmeshClipper obj) { + if (!obj.graphMask.Contains((int)graph.graphIndex)) return; + + // Without the forceCreate parameter set to true then no handler will be created + // because there are no clippers in the scene yet. However one is being added right now. + Refresh(true); + if (handler == null) return; + var characterRadius = graph.NavmeshCuttingCharacterRadius; + var graphSpaceBounds = obj.GetBounds(graph.transform, characterRadius); + var touchingTiles = handler.graph.GetTouchingTilesInGraphSpace(graphSpaceBounds); + handler.cuts.Add(obj, touchingTiles); + } + + /// <summary>Called when a NavmeshCut or NavmeshAdd is disabled</summary> + public void RemoveClipper (NavmeshClipper obj) { + Refresh(); + if (handler == null) return; + var root = handler.cuts.GetRoot(obj); + + if (root != null) { + forcedReloadRects.Add(root.previousBounds); + handler.cuts.Remove(obj); + } + } + } + + internal void OnEnable () { + NavmeshClipper.AddEnableCallback(HandleOnEnableCallback, HandleOnDisableCallback); + } + + internal void OnDisable () { + NavmeshClipper.RemoveEnableCallback(HandleOnEnableCallback, HandleOnDisableCallback); + } + + public void ForceUpdateAround (NavmeshClipper clipper) { + var graphs = astar.graphs; + + if (graphs == null) return; + + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] is NavmeshBase navmeshBase) navmeshBase.navmeshUpdateData.Dirty(clipper); + } + } + + /// <summary>Discards all pending updates caused by moved or modified navmesh cuts</summary> + public void DiscardPending () { + var graphs = astar.graphs; + + if (graphs == null) return; + + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] is NavmeshBase navmeshBase) navmeshBase.navmeshUpdateData.DiscardPending(); + } + } + + /// <summary>Called when a NavmeshCut or NavmeshAdd is enabled</summary> + void HandleOnEnableCallback (NavmeshClipper obj) { + var graphs = astar.graphs; + + if (graphs == null) return; + + for (int i = 0; i < graphs.Length; i++) { + // Add the clipper to the individual graphs. Note that this automatically marks the clipper as dirty for that particular graph. + if (graphs[i] is NavmeshBase navmeshBase) navmeshBase.navmeshUpdateData.AddClipper(obj); + } + } + + /// <summary>Called when a NavmeshCut or NavmeshAdd is disabled</summary> + void HandleOnDisableCallback (NavmeshClipper obj) { + var graphs = astar.graphs; + + if (graphs == null) return; + + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] is NavmeshBase navmeshBase) navmeshBase.navmeshUpdateData.RemoveClipper(obj); + } + lastUpdateTime = float.NegativeInfinity; + } + + /// <summary>Update is called once per frame</summary> + internal void Update () { + if (astar.isScanning) return; + Profiler.BeginSample("Navmesh cutting"); + bool anyInvalidHandlers = false; + var graphs = astar.graphs; + + if (graphs != null) { + for (int i = 0; i < graphs.Length; i++) { + var navmeshBase = graphs[i] as NavmeshBase; + if (navmeshBase != null) { + navmeshBase.navmeshUpdateData.Refresh(); + anyInvalidHandlers = navmeshBase.navmeshUpdateData.forcedReloadRects.Count > 0; + } + } + + if ((updateInterval >= 0 && Time.realtimeSinceStartup - lastUpdateTime > updateInterval) || anyInvalidHandlers) { + ForceUpdate(); + } + } + Profiler.EndSample(); + } + + /// <summary> + /// Checks all NavmeshCut instances and updates graphs if needed. + /// Note: This schedules updates for all necessary tiles to happen as soon as possible. + /// The pathfinding threads will continue to calculate the paths that they were calculating when this function + /// was called and then they will be paused and the graph updates will be carried out (this may be several frames into the + /// future and the graph updates themselves may take several frames to complete). + /// If you want to force all navmesh cutting to be completed in a single frame call this method + /// and immediately after call AstarPath.FlushWorkItems. + /// + /// <code> + /// // Schedule pending updates to be done as soon as the pathfinding threads + /// // are done with what they are currently doing. + /// AstarPath.active.navmeshUpdates.ForceUpdate(); + /// // Block until the updates have finished + /// AstarPath.active.FlushGraphUpdates(); + /// </code> + /// </summary> + public void ForceUpdate () { + lastUpdateTime = Time.realtimeSinceStartup; + + var graphs = astar.graphs; + if (graphs == null) return; + + for (int graphIndex = 0; graphIndex < graphs.Length; graphIndex++) { + var navmeshBase = graphs[graphIndex] as NavmeshBase; + if (navmeshBase == null) continue; + + // Done in Update as well, but users may call ForceUpdate directly + navmeshBase.navmeshUpdateData.Refresh(); + + var handler = navmeshBase.navmeshUpdateData.handler; + + if (handler == null) continue; + + var forcedReloadRects = navmeshBase.navmeshUpdateData.forcedReloadRects; + + // Get all navmesh cuts in the scene + var allCuts = handler.cuts.AllItems; + + if (forcedReloadRects.Count == 0) { + bool any = false; + + // Check if any navmesh cuts need updating + for (var cut = allCuts; cut != null; cut = cut.next) { + if (cut.obj.RequiresUpdate(cut)) { + any = true; + break; + } + } + + // Nothing needs to be done for now + if (!any) continue; + } + + // Start batching tile updates which is good for performance + // if we are updating a lot of them + handler.StartBatchLoad(); + + for (int i = 0; i < forcedReloadRects.Count; i++) { + handler.ReloadInBounds(forcedReloadRects[i]); + } + forcedReloadRects.ClearFast(); + + var characterRadius = handler.graph.NavmeshCuttingCharacterRadius; + // Reload all bounds touching the previous bounds and current bounds + // of navmesh cuts that have moved or changed in some other way + for (var cut = allCuts; cut != null; cut = cut.next) { + if (cut.obj.RequiresUpdate(cut)) { + // Make sure the tile where it was is updated + handler.ReloadInBounds(cut.previousBounds); + + var newGraphSpaceBounds = cut.obj.GetBounds(handler.graph.transform, characterRadius); + var newTouchingTiles = handler.graph.GetTouchingTilesInGraphSpace(newGraphSpaceBounds); + handler.cuts.Move(cut.obj, newTouchingTiles); + handler.ReloadInBounds(newTouchingTiles); + + // Notify the navmesh cut that it has been updated in this graph + // This will cause RequiresUpdate to return false + // until it is changed again. + cut.obj.NotifyUpdated(cut); + } + } + + handler.EndBatchLoad(); + } + } + } +} |