123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- using UnityEngine;
- using System.Collections.Generic;
- using Pathfinding.Util;
- using Pathfinding.Serialization;
- using UnityEngine.Profiling;
- namespace Pathfinding {
- /// <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;
- /// <summary>Last time navmesh cuts were applied</summary>
- float lastUpdateTime = float.NegativeInfinity;
- /// <summary>Stores navmesh cutting related data for a single graph</summary>
- internal 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 Refresh (bool forceCreate = false) {
- if (!graph.enableNavmeshCutting) {
- if (handler != null) {
- handler.cuts.Clear();
- handler.ReloadInBounds(new IntRect(int.MinValue, int.MinValue, int.MaxValue, int.MaxValue));
- // 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.
- AstarPath.active.FlushGraphUpdates();
- AstarPath.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));
- }
- }
- /// <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);
- }
- /// <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 graphSpaceBounds = obj.GetBounds(handler.graph.transform);
- 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);
- }
- /// <summary>Discards all pending updates caused by moved or modified navmesh cuts</summary>
- public void DiscardPending () {
- for (int i = 0; i < NavmeshClipper.allEnabled.Count; i++) {
- NavmeshClipper.allEnabled[i].NotifyUpdated();
- }
- var graphs = AstarPath.active.graphs;
- for (int i = 0; i < graphs.Length; i++) {
- var navmeshBase = graphs[i] as NavmeshBase;
- if (navmeshBase != null) navmeshBase.navmeshUpdateData.forcedReloadRects.Clear();
- }
- }
- /// <summary>Called when a NavmeshCut or NavmeshAdd is enabled</summary>
- void HandleOnEnableCallback (NavmeshClipper obj) {
- var graphs = AstarPath.active.graphs;
- for (int i = 0; i < graphs.Length; i++) {
- var navmeshBase = graphs[i] as NavmeshBase;
- if (navmeshBase != null) navmeshBase.navmeshUpdateData.AddClipper(obj);
- }
- obj.ForceUpdate();
- }
- /// <summary>Called when a NavmeshCut or NavmeshAdd is disabled</summary>
- void HandleOnDisableCallback (NavmeshClipper obj) {
- var graphs = AstarPath.active.graphs;
- for (int i = 0; i < graphs.Length; i++) {
- var navmeshBase = graphs[i] as NavmeshBase;
- if (navmeshBase != null) navmeshBase.navmeshUpdateData.RemoveClipper(obj);
- }
- lastUpdateTime = float.NegativeInfinity;
- }
- /// <summary>Update is called once per frame</summary>
- internal void Update () {
- if (AstarPath.active.isScanning) return;
- Profiler.BeginSample("Navmesh cutting");
- bool anyInvalidHandlers = false;
- var graphs = AstarPath.active.graphs;
- 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;
- List<NavmeshClipper> hasBeenUpdated = null;
- var graphs = AstarPath.active.graphs;
- 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()) {
- 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();
- if (hasBeenUpdated == null) hasBeenUpdated = ListPool<NavmeshClipper>.Claim();
- // 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()) {
- // Make sure the tile where it was is updated
- handler.ReloadInBounds(cut.previousBounds);
- var newGraphSpaceBounds = cut.obj.GetBounds(handler.graph.transform);
- var newTouchingTiles = handler.graph.GetTouchingTilesInGraphSpace(newGraphSpaceBounds);
- handler.cuts.Move(cut.obj, newTouchingTiles);
- handler.ReloadInBounds(newTouchingTiles);
- hasBeenUpdated.Add(cut.obj);
- }
- }
- handler.EndBatchLoad();
- }
- if (hasBeenUpdated != null) {
- // Notify navmesh cuts that they have been updated
- // This will cause RequiresUpdate to return false
- // until it is changed again.
- // Note: This is not as efficient as it could be when multiple graphs are used
- // because every navmesh cut will be added to the list once for every graph.
- for (int i = 0; i < hasBeenUpdated.Count; i++) {
- hasBeenUpdated[i].NotifyUpdated();
- }
- ListPool<NavmeshClipper>.Release(ref hasBeenUpdated);
- }
- }
- }
- }
|