protected override void Awake() { base.Awake(); AstarPath.active = this; if (UnityEngine.Object.FindObjectsOfType(typeof(AstarPath)).Length > 1) { UnityEngine.Debug.LogError("You should NOT have more than one AstarPath component in the scene at any time.\nThis can cause serious errors since the AstarPath component builds around a singleton pattern."); } base.useGUILayout = false; if (!Application.isPlaying) { return; } if (AstarPath.OnAwakeSettings != null) { AstarPath.OnAwakeSettings(); } GraphModifier.FindAllModifiers(); RelevantGraphSurface.FindAllGraphSurfaces(); this.InitializePathProcessor(); this.InitializeProfiler(); this.ConfigureReferencesInternal(); this.InitializeAstarData(); this.FlushWorkItems(); this.euclideanEmbedding.dirty = true; if (this.scanOnStartup && (!this.data.cacheStartup || this.data.file_cachedStartup == null)) { this.Scan(null); } }
// Token: 0x060005EB RID: 1515 RVA: 0x00037D0C File Offset: 0x0003610C public void LoadTile(TileHandler.TileType tile, int x, int z, int rotation, int yoffset) { if (tile == null) { throw new ArgumentNullException("tile"); } if (AstarPath.active == null) { return; } int index = x + z * this.graph.tileXCount; rotation %= 4; if (this.isBatching && this.reloadedInBatch[index] && this.activeTileOffsets[index] == yoffset && this.activeTileRotations[index] == rotation && this.activeTileTypes[index] == tile) { return; } if (this.isBatching) { this.reloadedInBatch[index] = true; } this.activeTileOffsets[index] = yoffset; this.activeTileRotations[index] = rotation; this.activeTileTypes[index] = tile; AstarPath.active.AddWorkItem(new AstarPath.AstarWorkItem(delegate(bool force) { if (this.activeTileOffsets[index] != yoffset || this.activeTileRotations[index] != rotation || this.activeTileTypes[index] != tile) { return(true); } GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate); Int3[] verts; int[] tris; tile.Load(out verts, out tris, rotation, yoffset); Bounds tileBounds = this.graph.GetTileBounds(x, z, tile.Width, tile.Depth); Int3 @int = (Int3)tileBounds.min; @int = -@int; Int3[] array = null; int[] array2 = null; int num; int num2; this.CutPoly(verts, tris, ref array, ref array2, out num, out num2, null, @int, tileBounds, (TileHandler.CutMode) 3, 0); this.DelaunayRefinement(array, array2, ref num, ref num2, true, false, -@int); if (num2 != array2.Length) { array2 = TileHandler.ShrinkArray <int>(array2, num2); } if (num != array.Length) { array = TileHandler.ShrinkArray <Int3>(array, num); } int w = (rotation % 2 != 0) ? tile.Depth : tile.Width; int d = (rotation % 2 != 0) ? tile.Width : tile.Depth; this.graph.ReplaceTile(x, z, w, d, array, array2, false); GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate); AstarPath.active.QueueWorkItemFloodFill(); return(true); })); }
static public void Initialize() { cache = new CacheCore(); modifier = new GraphModifier(); hacker = new RespHacker(); _AppendToFiddler(); }
public void LoadTile(List <NavmeshCut> navmeshCuts, MyTileHandler.TileType tile, int x, int z, int rotation, int yoffset) { if (tile == null) { throw new ArgumentNullException("tile"); } int num = x + z * this.graph.tileXCount; rotation %= 4; if (this.isBatching && this.reloadedInBatch[num] && this.activeTileOffsets[num] == yoffset && this.activeTileRotations[num] == rotation && this.activeTileTypes[num] == tile) { return; } if (this.isBatching) { this.reloadedInBatch[num] = true; } this.activeTileOffsets[num] = yoffset; this.activeTileRotations[num] = rotation; this.activeTileTypes[num] = tile; if (this.activeTileOffsets[num] != yoffset || this.activeTileRotations[num] != rotation || this.activeTileTypes[num] != tile) { return; } GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate); Int3[] verts; int[] tris; tile.Load(out verts, out tris, rotation, yoffset); Bounds tileBounds = this.graph.GetTileBounds(x, z, tile.Width, tile.Depth); Int3 vInt = (Int3)tileBounds.min; vInt = -vInt; Int3[] array = null; int[] array2 = null; int num2; int num3; this.CutPoly(navmeshCuts, verts, tris, ref array, ref array2, out num2, out num3, null, vInt, tileBounds, (MyTileHandler.CutMode) 3, 0); this.DelaunayRefinement(array, array2, ref num2, ref num3, true, false, -vInt); if (num3 != array2.Length) { array2 = MyTileHandler.ShrinkArray <int>(array2, num3); } if (num2 != array.Length) { array = MyTileHandler.ShrinkArray <Int3>(array, num2); } int w = (rotation % 2 != 0) ? tile.Depth : tile.Width; int d = (rotation % 2 != 0) ? tile.Width : tile.Depth; this.graph.ReplaceTile(x, z, w, d, array, array2, false); GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate); }
// Token: 0x060029BC RID: 10684 RVA: 0x001C4000 File Offset: 0x001C2200 public void LoadTile(TileHandler.TileType tile, int x, int z, int rotation, int yoffset) { if (tile == null) { throw new ArgumentNullException("tile"); } if (AstarPath.active == null) { return; } int index = x + z * this.tileXCount; rotation %= 4; if (this.isBatching && this.reloadedInBatch[index] && this.activeTileOffsets[index] == yoffset && this.activeTileRotations[index] == rotation && this.activeTileTypes[index] == tile) { return; } this.reloadedInBatch[index] |= this.isBatching; this.activeTileOffsets[index] = yoffset; this.activeTileRotations[index] = rotation; this.activeTileTypes[index] = tile; AstarPath.active.AddWorkItem(new AstarWorkItem(delegate(IWorkItemContext context, bool force) { if (this.activeTileOffsets[index] != yoffset || this.activeTileRotations[index] != rotation || this.activeTileTypes[index] != tile) { return(true); } GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate); Int3[] verts; int[] tris; tile.Load(out verts, out tris, rotation, yoffset); TileHandler.CuttingResult cuttingResult = this.CutPoly(verts, tris, null, this.graph.transform, new IntRect(x, z, x + tile.Width - 1, z + tile.Depth - 1), TileHandler.CutMode.CutAll | TileHandler.CutMode.CutDual, -1); int num = cuttingResult.tris.Length; this.DelaunayRefinement(cuttingResult.verts, cuttingResult.tris, ref num, true, false); if (num != cuttingResult.tris.Length) { cuttingResult.tris = Memory.ShrinkArray <int>(cuttingResult.tris, num); } int num2 = (rotation % 2 == 0) ? tile.Width : tile.Depth; int num3 = (rotation % 2 == 0) ? tile.Depth : tile.Width; if (num2 != 1 || num3 != 1) { throw new Exception("Only tiles of width = depth = 1 are supported at this time"); } this.graph.ReplaceTile(x, z, cuttingResult.verts, cuttingResult.tris); if (!this.isBatching) { GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate); } context.QueueFloodFill(); return(true); })); }
/// <summary> /// Sets up all needed variables and scans the graphs. /// Calls Initialize, starts the ReturnPaths coroutine and scans all graphs. /// Also starts threads if using multithreading /// See: <see cref="OnAwakeSettings"/> /// </summary> protected override void Awake() { base.Awake(); // Very important to set this. Ensures the singleton pattern holds active = this; if (FindObjectsOfType(typeof(AstarPath)).Length > 1) { Debug.LogError("You should NOT have more than one AstarPath component in the scene at any time.\n" + "This can cause serious errors since the AstarPath component builds around a singleton pattern."); } // Disable GUILayout to gain some performance, it is not used in the OnGUI call useGUILayout = false; // This class uses the [ExecuteInEditMode] attribute // So Awake is called even when not playing // Don't do anything when not in play mode if (!Application.isPlaying) { return; } if (OnAwakeSettings != null) { OnAwakeSettings(); } // To make sure all graph modifiers have been enabled before scan (to avoid script execution order issues) GraphModifier.FindAllModifiers(); RelevantGraphSurface.FindAllGraphSurfaces(); InitializePathProcessor(); InitializeProfiler(); ConfigureReferencesInternal(); InitializeAstarData(); // Flush work items, possibly added in InitializeAstarData to load graph data FlushWorkItems(); euclideanEmbedding.dirty = true; navmeshUpdates.OnEnable(); if (scanOnStartup && (!data.cacheStartup || data.file_cachedStartup == null)) { Scan(); } }
// Token: 0x060029AE RID: 10670 RVA: 0x001C2A8C File Offset: 0x001C0C8C public void EndBatchLoad() { if (!this.isBatching) { throw new Exception("Ending batching when batching has not been started"); } for (int i = 0; i < this.reloadedInBatch.Length; i++) { this.reloadedInBatch[i] = false; } this.isBatching = false; AstarPath.active.AddWorkItem(new AstarWorkItem(delegate(bool force) { this.graph.EndBatchTileUpdate(); GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate); return(true); })); }
public void ClearTile(int x, int z) { if (AstarPath.active == null) { return; } if (x < 0 || z < 0 || x >= this.tileXCount || z >= this.tileZCount) { return; } AstarPath.active.AddWorkItem(new AstarWorkItem(delegate(IWorkItemContext context, bool force) { this.graph.ReplaceTile(x, z, new Int3[0], new int[0]); this.activeTileTypes[x + z * this.tileXCount] = null; GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate); context.QueueFloodFill(); return(true); })); }
public IEnumerable <Progress> ScanAsync(NavGraph[] graphsToScan = null) { if (graphsToScan == null) { graphsToScan = this.graphs; } if (graphsToScan == null) { yield break; } if (this.isScanning) { throw new InvalidOperationException("Another async scan is already running"); } this.isScanning = true; this.VerifyIntegrity(); PathProcessor.GraphUpdateLock graphUpdateLock = this.PausePathfinding(); this.pathReturnQueue.ReturnPaths(false); if (!Application.isPlaying) { this.data.FindGraphTypes(); GraphModifier.FindAllModifiers(); } yield return(new Progress(0.05f, "Pre processing graphs")); if (AstarPath.OnPreScan != null) { AstarPath.OnPreScan(this); } GraphModifier.TriggerEvent(GraphModifier.EventType.PreScan); this.data.LockGraphStructure(false); Stopwatch watch = Stopwatch.StartNew(); for (int j = 0; j < graphsToScan.Length; j++) { if (graphsToScan[j] != null) { graphsToScan[j].DestroyAllNodesInternal(); } } int num; for (int i = 0; i < graphsToScan.Length; i = num + 1) { if (graphsToScan[i] != null) { float minp = Mathf.Lerp(0.1f, 0.8f, (float)i / (float)graphsToScan.Length); float maxp = Mathf.Lerp(0.1f, 0.8f, ((float)i + 0.95f) / (float)graphsToScan.Length); string progressDescriptionPrefix = string.Concat(new object[] { "Scanning graph ", i + 1, " of ", graphsToScan.Length, " - " }); IEnumerator <Progress> coroutine = this.ScanGraph(graphsToScan[i]).GetEnumerator(); for (;;) { try { if (!coroutine.MoveNext()) { break; } } catch { this.isScanning = false; this.data.UnlockGraphStructure(); graphUpdateLock.Release(); throw; } yield return(new Progress(Mathf.Lerp(minp, maxp, coroutine.Current.progress), progressDescriptionPrefix + coroutine.Current.description)); } progressDescriptionPrefix = null; coroutine = null; } num = i; } this.data.UnlockGraphStructure(); yield return(new Progress(0.8f, "Post processing graphs")); if (AstarPath.OnPostScan != null) { AstarPath.OnPostScan(this); } GraphModifier.TriggerEvent(GraphModifier.EventType.PostScan); this.FlushWorkItems(); yield return(new Progress(0.9f, "Computing areas")); this.FloodFill(); yield return(new Progress(0.95f, "Late post processing")); this.isScanning = false; if (AstarPath.OnLatePostScan != null) { AstarPath.OnLatePostScan(this); } GraphModifier.TriggerEvent(GraphModifier.EventType.LatePostScan); this.euclideanEmbedding.dirty = true; this.euclideanEmbedding.RecalculatePivots(); this.FlushWorkItems(); graphUpdateLock.Release(); watch.Stop(); this.lastScanTime = (float)watch.Elapsed.TotalSeconds; GC.Collect(); this.Log("Scanning - Process took " + (this.lastScanTime * 1000f).ToString("0") + " ms to complete"); yield break; yield break; }
/** Sets up all needed variables and scans the graphs. * Calls Initialize, starts the ReturnPaths coroutine and scans all graphs. * Also starts threads if using multithreading * \see #OnAwakeSettings */ void Awake() { //Very important to set this. Ensures the singleton pattern holds active = this; if (FindObjectsOfType(typeof(AstarPath)).Length > 1) { EB.Debug.LogError("You should NOT have more than one AstarPath component in the scene at any time.\n" + "This can cause serious errors since the AstarPath component builds around a singleton pattern."); } //Disable GUILayout to gain some performance, it is not used in the OnGUI call useGUILayout = false; isEditor = Application.isEditor; // This class uses the [ExecuteInEditMode] attribute // So Awake is called even when not playing // Don't do anything when not in play mode if (!Application.isPlaying) { return; } if (OnAwakeSettings != null) { OnAwakeSettings(); } //To make sure all graph modifiers have been enabled before scan (to avoid script run order issues) GraphModifier.FindAllModifiers(); RelevantGraphSurface.FindAllGraphSurfaces(); int numThreads = CalculateThreadCount(threadCount); threads = new Thread[numThreads]; //Thread info, will contain at least one item since the coroutine "thread" is thought of as a real thread in this case threadInfos = new PathThreadInfo[System.Math.Max(numThreads, 1)]; //Set up path queue with the specified number of receivers pathQueue = new ThreadControlQueue(threadInfos.Length); for (int i = 0; i < threadInfos.Length; i++) { threadInfos[i] = new PathThreadInfo(i, this, new PathHandler(i, threadInfos.Length)); } //Start coroutine if not using multithreading if (numThreads == 0) { threadEnumerator = CalculatePaths(threadInfos[0]); } else { threadEnumerator = null; } #if !UNITY_WEBGL for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(new ParameterizedThreadStart(CalculatePathsThreaded)); threads[i].Name = "Pathfinding Thread " + i; threads[i].IsBackground = true; } //Start pathfinding threads for (int i = 0; i < threads.Length; i++) { if (logPathResults == PathLog.Heavy) { EB.Debug.Log("Starting pathfinding thread {0}", i); } threads[i].Start(threadInfos[i]); } if (numThreads != 0) { graphUpdateThread = new Thread(new ParameterizedThreadStart(ProcessGraphUpdatesAsync)); graphUpdateThread.IsBackground = true; // Set the thread priority for graph updates // Unless compiling for windows store or windows phone which does not support it #if !UNITY_WINRT graphUpdateThread.Priority = System.Threading.ThreadPriority.Lowest; #endif graphUpdateThread.Start(this); } #endif Initialize(); // Flush work items, possibly added in initialize to load graph data FlushWorkItems(); euclideanEmbedding.dirty = true; #if BNICKSON_UPDATED // added !AStarPathfindingUtils.WillNavMeshBeBuiltFromInputMeshOnLevelBegin() if (scanOnStartup && !AStarPathfindingUtils.WillNavMeshBeBuiltFromInputMeshOnLevelBegin()) #else if (scanOnStartup) #endif { if (!astarData.cacheStartup || astarData.file_cachedStartup == null) { Scan(); #if BNICKSON_UPDATED EventManager.instance.Raise(new NavMeshScanEvent()); #endif } } }
public IEnumerable <Progress> ScanAsync() { if (this.graphs == null) { yield break; } this.isScanning = true; this.euclideanEmbedding.dirty = false; this.VerifyIntegrity(); this.BlockUntilPathQueueBlocked(); this.pathReturnQueue.ReturnPaths(false); this.BlockUntilPathQueueBlocked(); if (!Application.isPlaying) { GraphModifier.FindAllModifiers(); RelevantGraphSurface.FindAllGraphSurfaces(); } RelevantGraphSurface.UpdateAllPositions(); this.astarData.UpdateShortcuts(); yield return(new Progress(0.05f, "Pre processing graphs")); if (AstarPath.OnPreScan != null) { AstarPath.OnPreScan(this); } GraphModifier.TriggerEvent(GraphModifier.EventType.PreScan); Stopwatch watch = Stopwatch.StartNew(); for (int j = 0; j < this.graphs.Length; j++) { if (this.graphs[j] != null) { this.graphs[j].GetNodes(delegate(GraphNode node) { node.Destroy(); return(true); }); } } for (int i = 0; i < this.graphs.Length; i++) { if (this.graphs[i] != null) { float minp = Mathf.Lerp(0.1f, 0.8f, (float)i / (float)this.graphs.Length); float maxp = Mathf.Lerp(0.1f, 0.8f, ((float)i + 0.95f) / (float)this.graphs.Length); string progressDescriptionPrefix = string.Concat(new object[] { "Scanning graph ", i + 1, " of ", this.graphs.Length, " - " }); foreach (Progress progress in this.ScanGraph(this.graphs[i])) { yield return(new Progress(Mathf.Lerp(minp, maxp, progress.progress), progressDescriptionPrefix + progress.description)); } } } yield return(new Progress(0.8f, "Post processing graphs")); if (AstarPath.OnPostScan != null) { AstarPath.OnPostScan(this); } GraphModifier.TriggerEvent(GraphModifier.EventType.PostScan); try { this.FlushWorkItemsInternal(false); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } yield return(new Progress(0.9f, "Computing areas")); this.FloodFill(); this.VerifyIntegrity(); yield return(new Progress(0.95f, "Late post processing")); this.isScanning = false; if (AstarPath.OnLatePostScan != null) { AstarPath.OnLatePostScan(this); } GraphModifier.TriggerEvent(GraphModifier.EventType.LatePostScan); this.euclideanEmbedding.dirty = true; this.euclideanEmbedding.RecalculatePivots(); this.PerformBlockingActions(true, true); watch.Stop(); this.lastScanTime = (float)watch.Elapsed.TotalSeconds; GC.Collect(); this.Log("Scanning - Process took " + (this.lastScanTime * 1000f).ToString("0") + " ms to complete"); yield break; }
public IEnumerable <Progress> ScanAsync(NavGraph[] graphsToScan = null) { if (graphsToScan == null) { graphsToScan = graphs; } if (graphsToScan == null) { yield break; } if (isScanning) { throw new System.InvalidOperationException("Another async scan is already running"); } isScanning = true; VerifyIntegrity(); var graphUpdateLock = PausePathfinding(); // Make sure all paths that are in the queue to be returned // are returned immediately // Some modifiers (e.g the funnel modifier) rely on // the nodes being valid when the path is returned pathReturnQueue.ReturnPaths(false); if (!Application.isPlaying) { data.FindGraphTypes(); GraphModifier.FindAllModifiers(); } int startFrame = Time.frameCount; yield return(new Progress(0.05F, "Pre processing graphs")); if (Time.frameCount != startFrame) { throw new System.Exception("Async scanning can only be done in the pro version of the A* Pathfinding Project"); } if (OnPreScan != null) { OnPreScan(this); } GraphModifier.TriggerEvent(GraphModifier.EventType.PreScan); data.LockGraphStructure(); var watch = System.Diagnostics.Stopwatch.StartNew(); // Destroy previous nodes for (int i = 0; i < graphsToScan.Length; i++) { if (graphsToScan[i] != null) { ((IGraphInternals)graphsToScan[i]).DestroyAllNodes(); } } // Loop through all graphs and scan them one by one for (int i = 0; i < graphsToScan.Length; i++) { // Skip null graphs if (graphsToScan[i] == null) { continue; } // Just used for progress information // This graph will advance the progress bar from minp to maxp float minp = Mathf.Lerp(0.1F, 0.8F, (float)(i) / (graphsToScan.Length)); float maxp = Mathf.Lerp(0.1F, 0.8F, (float)(i + 0.95F) / (graphsToScan.Length)); var progressDescriptionPrefix = "Scanning graph " + (i + 1) + " of " + graphsToScan.Length + " - "; // Like a foreach loop but it gets a little complicated because of the exception // handling (it is not possible to yield inside try-except clause). var coroutine = ScanGraph(graphsToScan[i]).GetEnumerator(); while (true) { try { if (!coroutine.MoveNext()) { break; } } catch { isScanning = false; data.UnlockGraphStructure(); graphUpdateLock.Release(); throw; } yield return(coroutine.Current.MapTo(minp, maxp, progressDescriptionPrefix)); } } data.UnlockGraphStructure(); yield return(new Progress(0.8F, "Post processing graphs")); if (OnPostScan != null) { OnPostScan(this); } GraphModifier.TriggerEvent(GraphModifier.EventType.PostScan); FlushWorkItems(); yield return(new Progress(0.9F, "Computing areas")); hierarchicalGraph.RecalculateIfNecessary(); yield return(new Progress(0.95F, "Late post processing")); // Signal that we have stopped scanning here // Note that no yields can happen after this point // since then other parts of the system can start to interfere isScanning = false; if (OnLatePostScan != null) { OnLatePostScan(this); } GraphModifier.TriggerEvent(GraphModifier.EventType.LatePostScan); euclideanEmbedding.dirty = true; euclideanEmbedding.RecalculatePivots(); // Perform any blocking actions FlushWorkItems(); // Resume pathfinding threads graphUpdateLock.Release(); watch.Stop(); lastScanTime = (float)watch.Elapsed.TotalSeconds; System.GC.Collect(); if (logPathResults != PathLog.None && logPathResults != PathLog.OnlyErrors) { Debug.Log("Scanning - Process took " + (lastScanTime * 1000).ToString("0") + " ms to complete"); } }
/** Load a tile at tile coordinate \a x, \a z. * * \param tile Tile type to load * \param x Tile x coordinate (first tile is at (0,0), second at (1,0) etc.. ). * \param z Tile z coordinate. * \param rotation Rotate tile by 90 degrees * value. * \param yoffset Offset Y coordinates by this amount. In Int3 space, so if you have a world space * offset, multiply by Int3.Precision and round to the nearest integer before calling this function. */ public void LoadTile(TileType tile, int x, int z, int rotation, int yoffset) { if (tile == null) { throw new ArgumentNullException("tile"); } if (AstarPath.active == null) { return; } int index = x + z * graph.tileXCount; rotation = rotation % 4; // If loaded during this batch with the same settings, skip it if (isBatching && reloadedInBatch[index] && activeTileOffsets[index] == yoffset && activeTileRotations[index] == rotation && activeTileTypes[index] == tile) { return; } reloadedInBatch[index] |= isBatching; activeTileOffsets[index] = yoffset; activeTileRotations[index] = rotation; activeTileTypes[index] = tile; //Add a work item //This will pause pathfinding as soon as possible //and call the delegate when it is safe to update graphs AstarPath.active.AddWorkItem(new AstarPath.AstarWorkItem(delegate(bool force) { // If this was not the correct settings to load with, ignore if (!(activeTileOffsets[index] == yoffset && activeTileRotations[index] == rotation && activeTileTypes[index] == tile)) { return(true); } GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate); Int3[] verts; int[] tris; tile.Load(out verts, out tris, rotation, yoffset); //Calculate tile bounds so that the correct cutting offset can be used //The tile will be cut in local space (i.e it is at the world origin) so cuts need to be translated //to that point from their world space coordinates Bounds r = graph.GetTileBounds(x, z, tile.Width, tile.Depth); var cutOffset = (Int3)r.min; cutOffset = -cutOffset; Int3[] outVerts = null; int[] outTris = null; int vCount, tCount; //Cut the polygon CutPoly(verts, tris, ref outVerts, ref outTris, out vCount, out tCount, null, cutOffset, r); //Refine to remove bad triangles DelaunayRefinement(outVerts, outTris, ref vCount, ref tCount, true, false, -cutOffset); if (tCount != outTris.Length) { outTris = ShrinkArray(outTris, tCount); } if (vCount != outVerts.Length) { outVerts = ShrinkArray(outVerts, vCount); } // Rotate the mask correctly // and update width and depth to match rotation // (width and depth will swap if rotated 90 or 270 degrees ) int newWidth = rotation % 2 == 0 ? tile.Width : tile.Depth; int newDepth = rotation % 2 == 0 ? tile.Depth : tile.Width; //Replace the tile using the final vertices and triangles //The vertices are still in local space graph.ReplaceTile(x, z, newWidth, newDepth, outVerts, outTris, false); //Trigger post update event //This can trigger for example recalculation of navmesh links GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate); #if BNICKSON_UPDATED AStarPathfindingAbilityBridge.NavMeshHasBeenUpdated(); #endif //Flood fill everything to make sure graph areas are still valid //This tends to take more than 50% of the calculation time AstarPath.active.QueueWorkItemFloodFill(); return(true); })); }