summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/RecastTileUpdateHandler.cs
blob: d9dcdb8e1b2cd3886a0ee516588d4fc244bd3654 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
using UnityEngine;

namespace Pathfinding {
	/// <summary>
	/// Helper for easier fast updates to recast graphs.
	///
	/// When updating recast graphs, you might just have plonked down a few
	/// GraphUpdateScene objects or issued a few GraphUpdateObjects to the
	/// system. This works fine if you are only issuing a few but the problem
	/// is that they don't have any coordination in between themselves. So if
	/// you have 10 GraphUpdateScene objects in one tile, they will all update
	/// that tile (10 times in total) instead of just updating it once which
	/// is all that is required (meaning it will be 10 times slower than just
	/// updating one tile). This script exists to help with updating only the
	/// tiles that need updating and only updating them once instead of
	/// multiple times.
	///
	/// It is coupled with the RecastTileUpdate component, which works a bit
	/// like the GraphUpdateScene component, just with fewer options. You can
	/// attach the RecastTileUpdate to any GameObject to have it schedule an
	/// update for the tile(s) that contain the GameObject. E.g if you are
	/// creating a new building somewhere, you can attach the RecastTileUpdate
	/// component to it to make it update the graph when it is instantiated.
	///
	/// If a single tile contains multiple RecastTileUpdate components and
	/// many try to update the graph at the same time, only one tile update
	/// will be done, which greatly improves performance.
	///
	/// If you have objects that are instantiated at roughly the same time
	/// but not exactly the same frame, you can use the maxThrottlingDelay
	/// field. It will delay updates up to that number of seconds to allow
	/// more updates to be batched together.
	///
	/// Note: You should only have one instance of this script in the scene
	/// if you only have a single recast graph. If you have more than one
	/// graph you can have more than one instance of this script but you need
	/// to manually call the SetGraph method to configure it with the correct
	/// graph.
	///
	/// Note: This does not use navmesh cutting. If you only ever add
	/// obstacles, but never add any new walkable surfaces then you might
	/// want to use navmesh cutting instead. See navmeshcutting (view in online documentation for working links).
	///
	/// Deprecated: Since version 5.0, this component is not necessary, since normal graph updates on recast graphs are now batched together if they update the same tiles.
	/// Use e.g. the <see cref="DynamicGridObstacle"/> component instead.
	/// </summary>
	[HelpURL("https://arongranberg.com/astar/documentation/stable/recasttileupdatehandler.html")]
	public class RecastTileUpdateHandler : MonoBehaviour {
		/// <summary>Graph that handles the updates</summary>
		RecastGraph graph;

		/// <summary>True for a tile if it needs updating</summary>
		bool[] dirtyTiles;

		/// <summary>True if any elements in dirtyTiles are true</summary>
		bool anyDirtyTiles = false;

		/// <summary>Earliest update request we are handling right now</summary>
		float earliestDirty = float.NegativeInfinity;

		/// <summary>All tile updates will be performed within (roughly) this number of seconds</summary>
		public float maxThrottlingDelay = 0.5f;

		public void SetGraph (RecastGraph graph) {
			this.graph = graph;

			if (graph == null)
				return;

			dirtyTiles = new bool[graph.tileXCount*graph.tileZCount];
			anyDirtyTiles = false;
		}

		/// <summary>Requests an update to all tiles which touch the specified bounds</summary>
		public void ScheduleUpdate (Bounds bounds) {
			if (graph == null) {
				// If no graph has been set, use the first graph available
				if (AstarPath.active != null) {
					SetGraph(AstarPath.active.data.recastGraph);
				}

				if (graph == null) {
					Debug.LogError("Received tile update request (from RecastTileUpdate), but no RecastGraph could be found to handle it");
					return;
				}
			}

			// Make sure that tiles which do not strictly
			// contain this bounds object but which still
			// might need to be updated are actually updated
			int voxelCharacterRadius = Mathf.CeilToInt(graph.characterRadius/graph.cellSize);
			int borderSize = voxelCharacterRadius + 3;

			// Expand borderSize voxels on each side
			bounds.Expand(new Vector3(borderSize, 0, borderSize)*graph.cellSize*2);

			var touching = graph.GetTouchingTiles(bounds);

			if (touching.Width * touching.Height > 0) {
				if (!anyDirtyTiles) {
					earliestDirty = Time.time;
					anyDirtyTiles = true;
				}

				for (int z = touching.ymin; z <= touching.ymax; z++) {
					for (int x = touching.xmin; x <= touching.xmax; x++) {
						dirtyTiles[z*graph.tileXCount + x] = true;
					}
				}
			}
		}

		void OnEnable () {
			RecastTileUpdate.OnNeedUpdates += ScheduleUpdate;
		}

		void OnDisable () {
			RecastTileUpdate.OnNeedUpdates -= ScheduleUpdate;
		}

		void Update () {
			if (anyDirtyTiles && Time.time - earliestDirty >= maxThrottlingDelay && graph != null) {
				UpdateDirtyTiles();
			}
		}

		/// <summary>Update all dirty tiles now</summary>
		public void UpdateDirtyTiles () {
			if (graph == null) {
				new System.InvalidOperationException("No graph is set on this object");
			}

			if (graph.tileXCount * graph.tileZCount != dirtyTiles.Length) {
				Debug.LogError("Graph has changed dimensions. Clearing queued graph updates and resetting.");
				SetGraph(graph);
				return;
			}

			for (int z = 0; z < graph.tileZCount; z++) {
				for (int x = 0; x < graph.tileXCount; x++) {
					if (dirtyTiles[z*graph.tileXCount + x]) {
						dirtyTiles[z*graph.tileXCount + x] = false;

						var bounds = graph.GetTileBounds(x, z);

						// Shrink it a bit to make sure other tiles
						// are not included because of rounding errors
						bounds.extents *= 0.5f;

						var guo = new GraphUpdateObject(bounds);
						guo.nnConstraint.graphMask = 1 << (int)graph.graphIndex;

						AstarPath.active.UpdateGraphs(guo);
					}
				}
			}

			anyDirtyTiles = false;
		}
	}
}