public static void TagMaskField(GUIContent label, int value, System.Action <int> callback) { GUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel(label, EditorStyles.layerMaskField); string[] tagNames = AstarPath.FindTagNames(); string text; if (value == 0) { text = "Nothing"; } else if (value == ~0) { text = "Everything"; } else { text = ""; for (int i = 0; i < 32; i++) { if (((value >> i) & 0x1) != 0) { // Add spacing between words if (text.Length > 0) { text += ", "; } text += tagNames[i]; // Too long, just give up if (text.Length > 40) { text = "Mixed..."; break; } } } } if (GUILayout.Button(text, EditorStyles.layerMaskField, GUILayout.ExpandWidth(true))) { GenericMenu.MenuFunction2 wrappedCallback = obj => callback((int)obj); var menu = new GenericMenu(); menu.AddItem(new GUIContent("Everything"), value == ~0, wrappedCallback, ~0); menu.AddItem(new GUIContent("Nothing"), value == 0, wrappedCallback, 0); for (int i = 0; i < tagNames.Length; i++) { bool on = (value >> i & 1) != 0; int result = on ? value & ~(1 << i) : value | 1 << i; menu.AddItem(new GUIContent(tagNames[i]), on, wrappedCallback, result); } // Shortcut to open the tag editor menu.AddItem(new GUIContent("Edit Tag Names..."), false, AstarPathEditor.EditTags); menu.ShowAsContext(); Event.current.Use(); } GUILayout.EndHorizontal(); }
public PointNode(AstarPath astar) : base(astar) { }
public void RecalculateCosts() { if (pivots == null) { RecalculatePivots(); } if (mode == HeuristicOptimizationMode.None) { return; } pivotCount = 0; for (int i = 0; i < pivots.Length; i++) { if (pivots[i] != null && (pivots[i].Destroyed || !pivots[i].Walkable)) { throw new System.Exception("Invalid pivot nodes (destroyed or unwalkable)"); } } if (mode != HeuristicOptimizationMode.RandomSpreadOut) { for (int i = 0; i < pivots.Length; i++) { if (pivots[i] == null) { throw new System.Exception("Invalid pivot nodes (null)"); } } } UnityEngine.Debug.Log("Recalculating costs..."); pivotCount = pivots.Length; System.Action <int> startCostCalculation = null; startCostCalculation = delegate(int k) { GraphNode pivot = pivots[k]; FloodPath fp = null; fp = FloodPath.Construct(pivot); fp.immediateCallback = delegate(Path _p) { // Handle path pooling _p.Claim(this); // When paths are calculated on navmesh based graphs // the costs are slightly modified to match the actual target and start points // instead of the node centers // so we have to remove the cost for the first and last connection // in each path MeshNode mn = pivot as MeshNode; uint costOffset = 0; if (mn != null && mn.connectionCosts != null) { for (int i = 0; i < mn.connectionCosts.Length; i++) { costOffset = System.Math.Max(costOffset, mn.connectionCosts[i]); } } var graphs = AstarPath.active.graphs; // Process graphs in reverse order to raise probability that we encounter large NodeIndex values quicker // to avoid resizing the internal array too often for (int j = graphs.Length - 1; j >= 0; j--) { graphs[j].GetNodes(delegate(GraphNode node) { int idx = node.NodeIndex * pivotCount + k; EnsureCapacity(idx); PathNode pn = fp.pathHandler.GetPathNode(node); if (costOffset > 0) { costs[idx] = pn.pathID == fp.pathID && pn.parent != null ? System.Math.Max(pn.parent.G - costOffset, 0) : 0; } else { costs[idx] = pn.pathID == fp.pathID ? pn.G : 0; } return(true); }); } if (mode == HeuristicOptimizationMode.RandomSpreadOut && k < pivots.Length - 1) { int best = -1; uint bestScore = 0; // Actual number of nodes int totCount = maxNodeIndex / pivotCount; // Loop through all nodes for (int j = 1; j < totCount; j++) { // Find the minimum distance from the node to all existing pivot points uint mx = 1 << 30; for (int p = 0; p <= k; p++) { mx = System.Math.Min(mx, costs[j * pivotCount + p]); } // Pick the node which has the largest minimum distance to the existing pivot points // (i.e pick the one furthest away from the existing ones) GraphNode node = fp.pathHandler.GetPathNode(j).node; if ((mx > bestScore || best == -1) && node != null && !node.Destroyed && node.Walkable) { best = j; bestScore = mx; } } if (best == -1) { Debug.LogError("Failed generating random pivot points for heuristic optimizations"); return; } pivots[k + 1] = fp.pathHandler.GetPathNode(best).node; Debug.Log("Found node at " + pivots[k + 1].position + " with score " + bestScore); startCostCalculation(k + 1); } // Handle path pooling _p.Release(this); }; AstarPath.StartPath(fp, true); }; if (mode != HeuristicOptimizationMode.RandomSpreadOut) { // All calculated in paralell for (int i = 0; i < pivots.Length; i++) { startCostCalculation(i); } } else { // Recursive and serial startCostCalculation(0); } dirty = false; }
public NodeLink3Node(AstarPath astar) { astar.InitializeNode(this); }
public GridNode(AstarPath astar) : base(astar) { }
/// <summary> /// Blocks until this path has been calculated and returned. /// Normally it takes a few frames for a path to be calculated and returned. /// This function will ensure that the path will be calculated when this function returns /// and that the callback for that path has been called. /// /// Use this function only if you really need to. /// There is a point to spreading path calculations out over several frames. /// It smoothes out the framerate and makes sure requesting a large /// number of paths at the same time does not cause lag. /// /// Note: Graph updates and other callbacks might get called during the execution of this function. /// /// <code> /// Path p = seeker.StartPath (transform.position, transform.position + Vector3.forward * 10); /// p.BlockUntilCalculated(); /// // The path is calculated now /// </code> /// /// See: This is equivalent to calling AstarPath.BlockUntilCalculated(Path) /// See: WaitForPath /// </summary> public void BlockUntilCalculated() { AstarPath.BlockUntilCalculated(this); }
public void RecalculateCosts() { if (this.pivots == null) { this.RecalculatePivots(); } if (this.mode == HeuristicOptimizationMode.None) { return; } this.pivotCount = 0; for (int i = 0; i < this.pivots.Length; i++) { if (this.pivots[i] != null && (this.pivots[i].Destroyed || !this.pivots[i].Walkable)) { throw new Exception("Invalid pivot nodes (destroyed or unwalkable)"); } } if (this.mode != HeuristicOptimizationMode.RandomSpreadOut) { for (int j = 0; j < this.pivots.Length; j++) { if (this.pivots[j] == null) { throw new Exception("Invalid pivot nodes (null)"); } } } Debug.Log("Recalculating costs..."); this.pivotCount = this.pivots.Length; Action <int> startCostCalculation = null; int numComplete = 0; OnPathDelegate onComplete = delegate(Path path) { numComplete++; if (numComplete == this.pivotCount) { Debug.Log("Grid graph special case!"); this.ApplyGridGraphEndpointSpecialCase(); } }; startCostCalculation = delegate(int k) { GraphNode pivot = this.pivots[k]; FloodPath fp = null; fp = FloodPath.Construct(pivot, onComplete); fp.immediateCallback = delegate(Path _p) { _p.Claim(this); MeshNode meshNode = pivot as MeshNode; uint costOffset = 0u; int k; if (meshNode != null && meshNode.connectionCosts != null) { for (k = 0; k < meshNode.connectionCosts.Length; k++) { costOffset = Math.Max(costOffset, meshNode.connectionCosts[k]); } } NavGraph[] graphs = AstarPath.active.graphs; for (int m = graphs.Length - 1; m >= 0; m--) { graphs[m].GetNodes(delegate(GraphNode node) { int num6 = node.NodeIndex * this.pivotCount + k; this.EnsureCapacity(num6); PathNode pathNode = fp.pathHandler.GetPathNode(node); if (costOffset > 0u) { this.costs[num6] = ((pathNode.pathID != fp.pathID || pathNode.parent == null) ? 0u : Math.Max(pathNode.parent.G - costOffset, 0u)); } else { this.costs[num6] = ((pathNode.pathID != fp.pathID) ? 0u : pathNode.G); } return(true); }); } if (this.mode == HeuristicOptimizationMode.RandomSpreadOut && k < this.pivots.Length - 1) { if (this.pivots[k + 1] == null) { int num = -1; uint num2 = 0u; int num3 = this.maxNodeIndex / this.pivotCount; for (int n = 1; n < num3; n++) { uint num4 = 1073741824u; for (int num5 = 0; num5 <= k; num5++) { num4 = Math.Min(num4, this.costs[n * this.pivotCount + num5]); } GraphNode node2 = fp.pathHandler.GetPathNode(n).node; if ((num4 > num2 || num == -1) && node2 != null && !node2.Destroyed && node2.Walkable) { num = n; num2 = num4; } } if (num == -1) { Debug.LogError("Failed generating random pivot points for heuristic optimizations"); return; } this.pivots[k + 1] = fp.pathHandler.GetPathNode(num).node; } startCostCalculation(k + 1); } _p.Release(this, false); }; AstarPath.StartPath(fp, true); }; if (this.mode != HeuristicOptimizationMode.RandomSpreadOut) { for (int l = 0; l < this.pivots.Length; l++) { startCostCalculation(l); } } else { startCostCalculation(0); } this.dirty = false; }
protected MeshNode(AstarPath astar) : base(astar) { }
//public override Int3 Position {get { return position; } } public QuadtreeNode(AstarPath astar) : base(astar) { }
public ConvexMeshNode(AstarPath astar) : base(astar) { this.indices = new int[0]; }
protected GridNodeBase(AstarPath astar) : base(astar) { }
public GridNode(AstarPath astar) { astar.InitializeNode(this); }
public void SafeOnDestroy() { AstarPath.RegisterSafeUpdate(new OnVoidDelegate(this.OnDestroy)); }
/// <summary> /// SafeOnDestroy should be used when there is a risk that the pathfinding is searching through this graph when called /// </summary> public void SafeOnDestroy() { AstarPath.RegisterSafeNodeUpdate(OnDestroy); }
public MeshNode(AstarPath astar) : base(astar) { }
public ConvexMeshNode(AstarPath astar) : base(astar) { indices = new int[0]; //\todo Set indices to some reasonable value }
public PointNode(AstarPath astar) { astar.InitializeNode(this); }
public void RecalculateCosts() { if (pivots == null) { RecalculatePivots(); } if (mode == HeuristicOptimizationMode.None) { return; } pivotCount = 0; for (int i = 0; i < pivots.Length; i++) { if (pivots[i] != null && (pivots[i].Destroyed || !pivots[i].Walkable)) { throw new System.Exception("Invalid pivot nodes (destroyed or unwalkable)"); } } if (mode != HeuristicOptimizationMode.RandomSpreadOut) { for (int i = 0; i < pivots.Length; i++) { if (pivots[i] == null) { throw new System.Exception("Invalid pivot nodes (null)"); } } } Debug.Log("Recalculating costs..."); pivotCount = pivots.Length; System.Action <int> startCostCalculation = null; int numComplete = 0; OnPathDelegate onComplete = (Path path) => { numComplete++; if (numComplete == pivotCount) { // Last completed path ApplyGridGraphEndpointSpecialCase(); } }; startCostCalculation = (int pivotIndex) => { GraphNode pivot = pivots[pivotIndex]; FloodPath floodPath = null; floodPath = FloodPath.Construct(pivot, onComplete); floodPath.immediateCallback = (Path _p) => { // Handle path pooling _p.Claim(this); // When paths are calculated on navmesh based graphs // the costs are slightly modified to match the actual target and start points // instead of the node centers // so we have to remove the cost for the first and last connection // in each path var meshNode = pivot as MeshNode; uint costOffset = 0; if (meshNode != null && meshNode.connections != null) { for (int i = 0; i < meshNode.connections.Length; i++) { costOffset = System.Math.Max(costOffset, meshNode.connections[i].cost); } } var graphs = AstarPath.active.graphs; // Process graphs in reverse order to raise probability that we encounter large NodeIndex values quicker // to avoid resizing the internal array too often for (int j = graphs.Length - 1; j >= 0; j--) { graphs[j].GetNodes(node => { int idx = node.NodeIndex * pivotCount + pivotIndex; EnsureCapacity(idx); PathNode pn = ((IPathInternals)floodPath).PathHandler.GetPathNode(node); if (costOffset > 0) { costs[idx] = pn.pathID == floodPath.pathID && pn.parent != null ? System.Math.Max(pn.parent.G - costOffset, 0) : 0; } else { costs[idx] = pn.pathID == floodPath.pathID ? pn.G : 0; } }); } if (mode == HeuristicOptimizationMode.RandomSpreadOut && pivotIndex < pivots.Length - 1) { // If the next pivot is null // then find the node which is furthest away from the earlier // pivot points if (pivots[pivotIndex + 1] == null) { int best = -1; uint bestScore = 0; // Actual number of nodes int totCount = maxNodeIndex / pivotCount; // Loop through all nodes for (int j = 1; j < totCount; j++) { // Find the minimum distance from the node to all existing pivot points uint mx = 1 << 30; for (int p = 0; p <= pivotIndex; p++) { mx = System.Math.Min(mx, costs[j * pivotCount + p]); } // Pick the node which has the largest minimum distance to the existing pivot points // (i.e pick the one furthest away from the existing ones) GraphNode node = ((IPathInternals)floodPath).PathHandler.GetPathNode(j).node; if ((mx > bestScore || best == -1) && node != null && !node.Destroyed && node.Walkable) { best = j; bestScore = mx; } } if (best == -1) { Debug.LogError("Failed generating random pivot points for heuristic optimizations"); return; } pivots[pivotIndex + 1] = ((IPathInternals)floodPath).PathHandler.GetPathNode(best).node; } // Start next path startCostCalculation(pivotIndex + 1); } // Handle path pooling _p.Release(this); }; AstarPath.StartPath(floodPath, true); }; if (mode != HeuristicOptimizationMode.RandomSpreadOut) { // All calculated in parallel for (int i = 0; i < pivots.Length; i++) { startCostCalculation(i); } } else { // Recursive and serial startCostCalculation(0); } dirty = false; }
public WorkItemProcessor(AstarPath astar) { this.astar = astar; }
// Token: 0x0600254E RID: 9550 RVA: 0x001A26ED File Offset: 0x001A08ED public LevelGridNode(AstarPath astar) : base(astar) { }
public PathThreadInfo(int index, AstarPath astar, PathHandler runData) { this.threadIndex = index; this.astar = astar; this.runData = runData; }
protected override void Inspector() { base.Inspector(); scripts.Clear(); foreach (var script in targets) { scripts.Add(script as Seeker); } Undo.RecordObjects(targets, "Modify settings on Seeker"); var startEndModifierProp = FindProperty("startEndModifier"); startEndModifierProp.isExpanded = EditorGUILayout.Foldout(startEndModifierProp.isExpanded, startEndModifierProp.displayName); if (startEndModifierProp.isExpanded) { EditorGUI.indentLevel++; Popup("startEndModifier.exactStartPoint", exactnessLabels, "Start Point Snapping"); Popup("startEndModifier.exactEndPoint", exactnessLabels, "End Point Snapping"); PropertyField("startEndModifier.addPoints", "Add Points"); if (FindProperty("startEndModifier.exactStartPoint").enumValueIndex == (int)StartEndModifier.Exactness.Original || FindProperty("startEndModifier.exactEndPoint").enumValueIndex == (int)StartEndModifier.Exactness.Original) { if (PropertyField("startEndModifier.useRaycasting", "Physics Raycasting")) { EditorGUI.indentLevel++; PropertyField("startEndModifier.mask", "Layer Mask"); EditorGUI.indentLevel--; EditorGUILayout.HelpBox("Using raycasting to snap the start/end points has largely been superseded by the 'ClosestOnNode' snapping option. It is both faster and usually closer to what you want to achieve.", MessageType.Info); } if (PropertyField("startEndModifier.useGraphRaycasting", "Graph Raycasting")) { EditorGUILayout.HelpBox("Using raycasting to snap the start/end points has largely been superseded by the 'ClosestOnNode' snapping option. It is both faster and usually closer to what you want to achieve.", MessageType.Info); } } EditorGUI.indentLevel--; } tagPenaltiesOpen = EditorGUILayout.Foldout(tagPenaltiesOpen, new GUIContent("Tags", "Settings for each tag")); if (tagPenaltiesOpen) { EditorGUI.indentLevel++; string[] tagNames = AstarPath.FindTagNames(); if (tagNames.Length != 32) { tagNames = new string[32]; for (int i = 0; i < tagNames.Length; i++) { tagNames[i] = "" + i; } } EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField("Tag", EditorStyles.boldLabel, GUILayout.MaxWidth(120)); for (int i = 0; i < tagNames.Length; i++) { EditorGUILayout.LabelField(tagNames[i], GUILayout.MaxWidth(120)); } // Make sure the arrays are all of the correct size for (int i = 0; i < scripts.Count; i++) { if (scripts[i].tagPenalties == null || scripts[i].tagPenalties.Length != tagNames.Length) { scripts[i].tagPenalties = new int[tagNames.Length]; } } if (GUILayout.Button("Edit names", EditorStyles.miniButton)) { AstarPathEditor.EditTags(); } EditorGUILayout.EndVertical(); EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField("Penalty", EditorStyles.boldLabel, GUILayout.MaxWidth(100)); var prop = FindProperty("tagPenalties").FindPropertyRelative("Array"); prop.Next(true); for (int i = 0; i < tagNames.Length; i++) { prop.Next(false); EditorGUILayout.PropertyField(prop, GUIContent.none, false, GUILayout.MinWidth(100)); // Penalties should not be negative if (prop.intValue < 0) { prop.intValue = 0; } } if (GUILayout.Button("Reset all", EditorStyles.miniButton)) { for (int i = 0; i < tagNames.Length; i++) { for (int j = 0; j < scripts.Count; j++) { scripts[j].tagPenalties[i] = 0; } } } EditorGUILayout.EndVertical(); EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField("Traversable", EditorStyles.boldLabel, GUILayout.MaxWidth(100)); for (int i = 0; i < tagNames.Length; i++) { var anyFalse = false; var anyTrue = false; for (int j = 0; j < scripts.Count; j++) { var prevTraversable = ((scripts[j].traversableTags >> i) & 0x1) != 0; anyTrue |= prevTraversable; anyFalse |= !prevTraversable; } EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = anyTrue & anyFalse; var newTraversable = EditorGUILayout.Toggle(anyTrue); EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) { for (int j = 0; j < scripts.Count; j++) { scripts[j].traversableTags = (scripts[j].traversableTags & ~(1 << i)) | ((newTraversable ? 1 : 0) << i); } } } if (GUILayout.Button("Set all/none", EditorStyles.miniButton)) { for (int j = scripts.Count - 1; j >= 0; j--) { scripts[j].traversableTags = (scripts[0].traversableTags & 0x1) == 0 ? -1 : 0; } } EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); } }
public GraphUpdateProcessor(AstarPath astar) { this.astar = astar; }
/** * SafeOnDestroy should be used when there is a risk that the pathfinding is searching through this graph when called */ public void SafeOnDestroy() { AstarPath.RegisterSafeUpdate(OnDestroy, false); }
public NodeLink3Node(AstarPath active) : base(active) { }
public void OnGUI() { if (!show || (!Application.isPlaying && !showInEditor)) { return; } if (style == null) { style = new GUIStyle(); style.normal.textColor = Color.white; style.padding = new RectOffset(5, 5, 5, 5); } if (Time.realtimeSinceStartup - lastUpdate > 0.5f || cachedText == null || !Application.isPlaying) { lastUpdate = Time.realtimeSinceStartup; boxRect = new Rect(5, yOffset, 310, 40); text.Length = 0; text.AppendLine("A* Pathfinding Project Debugger"); text.Append("A* Version: ").Append(AstarPath.Version.ToString()); if (showMemProfile) { boxRect.height += 200; text.AppendLine(); text.AppendLine(); text.Append("Currently allocated".PadRight(25)); text.Append((allocMem / 1000000F).ToString("0.0 MB")); text.AppendLine(); text.Append("Peak allocated".PadRight(25)); text.Append((peakAlloc / 1000000F).ToString("0.0 MB")).AppendLine(); text.Append("Last collect peak".PadRight(25)); text.Append((collectAlloc / 1000000F).ToString("0.0 MB")).AppendLine(); text.Append("Allocation rate".PadRight(25)); text.Append((allocRate / 1000000F).ToString("0.0 MB")).AppendLine(); text.Append("Collection frequency".PadRight(25)); text.Append(delta.ToString("0.00")); text.Append("s\n"); text.Append("Last collect fps".PadRight(25)); text.Append((1F / lastDeltaTime).ToString("0.0 fps")); text.Append(" ("); text.Append(lastDeltaTime.ToString("0.000 s")); text.Append(")"); } if (showFPS) { text.AppendLine(); text.AppendLine(); var delayedFPS = delayedDeltaTime > 0.00001f ? 1F / delayedDeltaTime : 0; text.Append("FPS".PadRight(25)).Append(delayedFPS.ToString("0.0 fps")); float minFps = Mathf.Infinity; for (int i = 0; i < fpsDrops.Length; i++) { if (fpsDrops[i] < minFps) { minFps = fpsDrops[i]; } } text.AppendLine(); text.Append(("Lowest fps (last " + fpsDrops.Length + ")").PadRight(25)).Append(minFps.ToString("0.0")); } if (showPathProfile) { AstarPath astar = AstarPath.active; text.AppendLine(); if (astar == null) { text.Append("\nNo AstarPath Object In The Scene"); } else { #if ProfileAstar double searchSpeed = (double)AstarPath.TotalSearchedNodes * 10000 / (double)AstarPath.TotalSearchTime; text.Append("\nSearch Speed (nodes/ms) ").Append(searchSpeed.ToString("0")).Append(" (" + AstarPath.TotalSearchedNodes + " / ").Append(((double)AstarPath.TotalSearchTime / 10000F).ToString("0") + ")"); #endif if (Pathfinding.Util.ListPool <Vector3> .GetSize() > maxVecPool) { maxVecPool = Pathfinding.Util.ListPool <Vector3> .GetSize(); } if (Pathfinding.Util.ListPool <Pathfinding.GraphNode> .GetSize() > maxNodePool) { maxNodePool = Pathfinding.Util.ListPool <Pathfinding.GraphNode> .GetSize(); } text.Append("\nPool Sizes (size/total created)"); for (int i = 0; i < debugTypes.Length; i++) { debugTypes[i].Print(text); } } } cachedText = text.ToString(); } if (font != null) { style.font = font; style.fontSize = fontSize; } boxRect.height = style.CalcHeight(new GUIContent(cachedText), boxRect.width); GUI.Box(boxRect, ""); GUI.Label(boxRect, cachedText, style); if (showGraph) { float minMem = float.PositiveInfinity, maxMem = 0, minFPS = float.PositiveInfinity, maxFPS = 0; for (int i = 0; i < graph.Length; i++) { minMem = Mathf.Min(graph[i].memory, minMem); maxMem = Mathf.Max(graph[i].memory, maxMem); minFPS = Mathf.Min(graph[i].fps, minFPS); maxFPS = Mathf.Max(graph[i].fps, maxFPS); } float line; GUI.color = Color.blue; // Round to nearest x.x MB line = Mathf.RoundToInt(maxMem / (100.0f * 1000)); GUI.Label(new Rect(5, Screen.height - AstarMath.MapTo(minMem, maxMem, 0 + graphOffset, graphHeight + graphOffset, line * 1000 * 100) - 10, 100, 20), (line / 10.0f).ToString("0.0 MB")); line = Mathf.Round(minMem / (100.0f * 1000)); GUI.Label(new Rect(5, Screen.height - AstarMath.MapTo(minMem, maxMem, 0 + graphOffset, graphHeight + graphOffset, line * 1000 * 100) - 10, 100, 20), (line / 10.0f).ToString("0.0 MB")); GUI.color = Color.green; // Round to nearest x.x MB line = Mathf.Round(maxFPS); GUI.Label(new Rect(55, Screen.height - AstarMath.MapTo(minFPS, maxFPS, 0 + graphOffset, graphHeight + graphOffset, line) - 10, 100, 20), line.ToString("0 FPS")); line = Mathf.Round(minFPS); GUI.Label(new Rect(55, Screen.height - AstarMath.MapTo(minFPS, maxFPS, 0 + graphOffset, graphHeight + graphOffset, line) - 10, 100, 20), line.ToString("0 FPS")); } }
public TriangleMeshNode(AstarPath astar) : base(astar) { }
protected IEnumerable <Progress> ScanAllTiles() { transform = CalculateTransform(); InitializeTileInfo(); // If this is true, just fill the graph with empty tiles if (scanEmptyGraph) { FillWithEmptyTiles(); yield break; } // A walkableClimb higher than walkableHeight can cause issues when generating the navmesh since then it can in some cases // Both be valid for a character to walk under an obstacle and climb up on top of it (and that cannot be handled with navmesh without links) // The editor scripts also enforce this but we enforce it here too just to be sure walkableClimb = Mathf.Min(walkableClimb, walkableHeight); yield return(new Progress(0, "Finding Meshes")); var bounds = transform.Transform(new Bounds(forcedBoundsSize * 0.5f, forcedBoundsSize)); var meshes = CollectMeshes(bounds); var buckets = PutMeshesIntoTileBuckets(meshes); Queue <Int2> tileQueue = new Queue <Int2>(); // Put all tiles in the queue for (int z = 0; z < tileZCount; z++) { for (int x = 0; x < tileXCount; x++) { tileQueue.Enqueue(new Int2(x, z)); } } #if UNITY_WEBGL && !UNITY_EDITOR // WebGL does not support multithreading so we will do everything synchronously instead BuildTiles(tileQueue, buckets, null, 0); #else // Fire up a bunch of threads to scan the graph in parallel int threadCount = Mathf.Min(tileQueue.Count, Mathf.Max(1, AstarPath.CalculateThreadCount(ThreadCount.AutomaticHighLoad))); var waitEvents = new ManualResetEvent[threadCount]; for (int i = 0; i < waitEvents.Length; i++) { waitEvents[i] = new ManualResetEvent(false); #if NETFX_CORE // Need to make a copy here, otherwise it may refer to some other index when the task actually runs var threadIndex = i; System.Threading.Tasks.Task.Run(() => BuildTiles(tileQueue, buckets, waitEvents[threadIndex], threadIndex)); #else ThreadPool.QueueUserWorkItem(state => BuildTiles(tileQueue, buckets, waitEvents[(int)state], (int)state), i); #endif } // Prioritize responsiveness while playing // but when not playing prioritize throughput // (the Unity progress bar is also pretty slow to update) int timeoutMillis = Application.isPlaying ? 1 : 200; while (!WaitHandle.WaitAll(waitEvents, timeoutMillis)) { int count; lock (tileQueue) count = tileQueue.Count; yield return(new Progress(Mathf.Lerp(0.1f, 0.9f, (tiles.Length - count + 1) / (float)tiles.Length), "Generating Tile " + (tiles.Length - count + 1) + "/" + tiles.Length)); } #endif yield return(new Progress(0.9f, "Assigning Graph Indices")); // Assign graph index to nodes uint graphIndex = (uint)AstarPath.active.data.GetGraphIndex(this); GetNodes(node => node.GraphIndex = graphIndex); #if UNITY_WEBGL && !UNITY_EDITOR // Put all tiles in the queue to be connected for (int i = 0; i < tiles.Length; i++) { tileQueue.Enqueue(new Int2(tiles[i].x, tiles[i].z)); } // Calculate synchronously ConnectTiles(tileQueue, null, true, true); #else // First connect all tiles with an EVEN coordinate sum // This would be the white squares on a chess board. // Then connect all tiles with an ODD coordinate sum (which would be all black squares). // This will prevent the different threads that do all // this in parallel from conflicting with each other. // The directions are also done separately // first they are connected along the X direction and then along the Z direction. // Looping over 0 and then 1 for (int coordinateSum = 0; coordinateSum <= 1; coordinateSum++) { for (int direction = 0; direction <= 1; direction++) { for (int i = 0; i < tiles.Length; i++) { if ((tiles[i].x + tiles[i].z) % 2 == coordinateSum) { tileQueue.Enqueue(new Int2(tiles[i].x, tiles[i].z)); } } int numTilesInQueue = tileQueue.Count; for (int i = 0; i < waitEvents.Length; i++) { waitEvents[i].Reset(); #if NETFX_CORE var waitEvent = waitEvents[i]; System.Threading.Tasks.Task.Run(() => ConnectTiles(tileQueue, waitEvent, direction == 0, direction == 1)); #else ThreadPool.QueueUserWorkItem(state => ConnectTiles(tileQueue, state as ManualResetEvent, direction == 0, direction == 1), waitEvents[i]); #endif } while (!WaitHandle.WaitAll(waitEvents, timeoutMillis)) { int count; lock (tileQueue) { count = tileQueue.Count; } yield return(new Progress(0.95f, "Connecting Tile " + (numTilesInQueue - count) + "/" + numTilesInQueue + " (Phase " + (direction + 1 + 2 * coordinateSum) + " of 4)")); } } } #endif for (int i = 0; i < meshes.Count; i++) { meshes[i].Pool(); } ListPool <RasterizationMesh> .Release(meshes); // This may be used by the TileHandlerHelper script to update the tiles // while taking NavmeshCuts into account after the graph has been completely recalculated. if (OnRecalculatedTiles != null) { OnRecalculatedTiles(tiles.Clone() as NavmeshTile[]); } }