Adding A* Algorythm and carpet roads

This commit is contained in:
Nicolas SANS
2023-04-24 15:49:03 +02:00
parent 75018ee6cd
commit 690e3fbd0b
685 changed files with 134125 additions and 19 deletions

View File

@ -0,0 +1,122 @@
using UnityEngine;
using System.Collections.Generic;
namespace Pathfinding {
[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_animation_link.php")]
public class AnimationLink : NodeLink2 {
public string clip;
public float animSpeed = 1;
public bool reverseAnim = true;
public GameObject referenceMesh;
public LinkClip[] sequence;
public string boneRoot = "bn_COG_Root";
[System.Serializable]
public class LinkClip {
public AnimationClip clip;
public Vector3 velocity;
public int loopCount = 1;
public string name {
get {
return clip != null ? clip.name : "";
}
}
}
static Transform SearchRec (Transform tr, string name) {
int childCount = tr.childCount;
for (int i = 0; i < childCount; i++) {
Transform ch = tr.GetChild(i);
if (ch.name == name) return ch;
else {
Transform rec = SearchRec(ch, name);
if (rec != null) return rec;
}
}
return null;
}
public void CalculateOffsets (List<Vector3> trace, out Vector3 endPosition) {
//Vector3 opos = transform.position;
endPosition = transform.position;
if (referenceMesh == null) return;
GameObject ob = GameObject.Instantiate(referenceMesh, transform.position, transform.rotation) as GameObject;
ob.hideFlags = HideFlags.HideAndDontSave;
Transform root = SearchRec(ob.transform, boneRoot);
if (root == null) throw new System.Exception("Could not find root transform");
Animation anim = ob.GetComponent<Animation>();
if (anim == null) anim = ob.AddComponent<Animation>();
for (int i = 0; i < sequence.Length; i++) {
anim.AddClip(sequence[i].clip, sequence[i].clip.name);
}
Vector3 prevOffset = Vector3.zero;
Vector3 position = transform.position;
Vector3 firstOffset = Vector3.zero;
for (int i = 0; i < sequence.Length; i++) {
LinkClip c = sequence[i];
if (c == null) {
endPosition = position;
return;
}
anim[c.clip.name].enabled = true;
anim[c.clip.name].weight = 1;
for (int repeat = 0; repeat < c.loopCount; repeat++) {
anim[c.clip.name].normalizedTime = 0;
anim.Sample();
Vector3 soffset = root.position - transform.position;
if (i > 0) {
position += prevOffset - soffset;
} else {
firstOffset = soffset;
}
for (int t = 0; t <= 20; t++) {
float tf = t/20.0f;
anim[c.clip.name].normalizedTime = tf;
anim.Sample();
Vector3 tmp = position + (root.position-transform.position) + c.velocity*tf*c.clip.length;
trace.Add(tmp);
}
position = position + c.velocity*1*c.clip.length;
anim[c.clip.name].normalizedTime = 1;
anim.Sample();
Vector3 eoffset = root.position - transform.position;
prevOffset = eoffset;
}
anim[c.clip.name].enabled = false;
anim[c.clip.name].weight = 0;
}
position += prevOffset - firstOffset;
GameObject.DestroyImmediate(ob);
endPosition = position;
}
public override void OnDrawGizmosSelected () {
base.OnDrawGizmosSelected();
List<Vector3> buffer = Pathfinding.Util.ListPool<Vector3>.Claim();
Vector3 endPosition = Vector3.zero;
CalculateOffsets(buffer, out endPosition);
Gizmos.color = Color.blue;
for (int i = 0; i < buffer.Count-1; i++) {
Gizmos.DrawLine(buffer[i], buffer[i+1]);
}
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d2e8b1fd6fa484fc29f8a26fb5e8662b
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,198 @@
#if !UNITY_EDITOR
// Extra optimizations when not running in the editor, but less error checking
#define ASTAR_OPTIMIZE_POOLING
#endif
using System;
using System.Collections.Generic;
namespace Pathfinding.Util {
/// <summary>
/// Lightweight Array Pool.
/// Handy class for pooling arrays of type T.
///
/// Usage:
/// - Claim a new array using <code> SomeClass[] foo = ArrayPool<SomeClass>.Claim (capacity); </code>
/// - Use it and do stuff with it
/// - Release it with <code> ArrayPool<SomeClass>.Release (foo); </code>
///
/// Warning: Arrays returned from the Claim method may contain arbitrary data.
/// You cannot rely on it being zeroed out.
///
/// After you have released a array, you should never use it again, if you do use it
/// your code may modify it at the same time as some other code is using it which
/// will likely lead to bad results.
///
/// \since Version 3.8.6
/// See: Pathfinding.Util.ListPool
/// </summary>
public static class ArrayPool<T> {
#if !ASTAR_NO_POOLING
/// <summary>
/// Maximum length of an array pooled using ClaimWithExactLength.
/// Arrays with lengths longer than this will silently not be pooled.
/// </summary>
const int MaximumExactArrayLength = 256;
/// <summary>
/// Internal pool.
/// The arrays in each bucket have lengths of 2^i
/// </summary>
static readonly Stack<T[]>[] pool = new Stack<T[]>[31];
static readonly Stack<T[]>[] exactPool = new Stack<T[]>[MaximumExactArrayLength+1];
#if !ASTAR_OPTIMIZE_POOLING
static readonly HashSet<T[]> inPool = new HashSet<T[]>();
#endif
#endif
/// <summary>
/// Returns an array with at least the specified length.
/// Warning: Returned arrays may contain arbitrary data.
/// You cannot rely on it being zeroed out.
/// </summary>
public static T[] Claim (int minimumLength) {
if (minimumLength <= 0) {
return ClaimWithExactLength(0);
}
int bucketIndex = 0;
while ((1 << bucketIndex) < minimumLength && bucketIndex < 30) {
bucketIndex++;
}
if (bucketIndex == 30)
throw new System.ArgumentException("Too high minimum length");
#if !ASTAR_NO_POOLING
lock (pool) {
if (pool[bucketIndex] == null) {
pool[bucketIndex] = new Stack<T[]>();
}
if (pool[bucketIndex].Count > 0) {
var array = pool[bucketIndex].Pop();
#if !ASTAR_OPTIMIZE_POOLING
inPool.Remove(array);
#endif
return array;
}
}
#endif
return new T[1 << bucketIndex];
}
/// <summary>
/// Returns an array with the specified length.
/// Use with caution as pooling too many arrays with different lengths that
/// are rarely being reused will lead to an effective memory leak.
///
/// Use <see cref="Claim"/> if you just need an array that is at least as large as some value.
///
/// Warning: Returned arrays may contain arbitrary data.
/// You cannot rely on it being zeroed out.
/// </summary>
public static T[] ClaimWithExactLength (int length) {
#if !ASTAR_NO_POOLING
bool isPowerOfTwo = length != 0 && (length & (length - 1)) == 0;
if (isPowerOfTwo) {
// Will return the correct array length
return Claim(length);
}
if (length <= MaximumExactArrayLength) {
lock (pool) {
Stack<T[]> stack = exactPool[length];
if (stack != null && stack.Count > 0) {
var array = stack.Pop();
#if !ASTAR_OPTIMIZE_POOLING
inPool.Remove(array);
#endif
return array;
}
}
}
#endif
return new T[length];
}
/// <summary>
/// Pool an array.
/// If the array was got using the <see cref="ClaimWithExactLength"/> method then the allowNonPowerOfTwo parameter must be set to true.
/// The parameter exists to make sure that non power of two arrays are not pooled unintentionally which could lead to memory leaks.
/// </summary>
public static void Release (ref T[] array, bool allowNonPowerOfTwo = false) {
if (array == null) return;
if (array.GetType() != typeof(T[])) {
throw new System.ArgumentException("Expected array type " + typeof(T[]).Name + " but found " + array.GetType().Name + "\nAre you using the correct generic class?\n");
}
#if !ASTAR_NO_POOLING
bool isPowerOfTwo = array.Length != 0 && (array.Length & (array.Length - 1)) == 0;
if (!isPowerOfTwo && !allowNonPowerOfTwo && array.Length != 0) throw new System.ArgumentException("Length is not a power of 2");
lock (pool) {
#if !ASTAR_OPTIMIZE_POOLING
if (!inPool.Add(array)) {
throw new InvalidOperationException("You are trying to pool an array twice. Please make sure that you only pool it once.");
}
#endif
if (isPowerOfTwo) {
int bucketIndex = 0;
while ((1 << bucketIndex) < array.Length && bucketIndex < 30) {
bucketIndex++;
}
if (pool[bucketIndex] == null) {
pool[bucketIndex] = new Stack<T[]>();
}
pool[bucketIndex].Push(array);
} else if (array.Length <= MaximumExactArrayLength) {
Stack<T[]> stack = exactPool[array.Length];
if (stack == null) stack = exactPool[array.Length] = new Stack<T[]>();
stack.Push(array);
}
}
#endif
array = null;
}
}
/// <summary>Extension methods for List<T></summary>
public static class ListExtensions {
/// <summary>
/// Identical to ToArray but it uses ArrayPool<T> to avoid allocations if possible.
///
/// Use with caution as pooling too many arrays with different lengths that
/// are rarely being reused will lead to an effective memory leak.
/// </summary>
public static T[] ToArrayFromPool<T>(this List<T> list) {
var arr = ArrayPool<T>.ClaimWithExactLength(list.Count);
for (int i = 0; i < arr.Length; i++) {
arr[i] = list[i];
}
return arr;
}
/// <summary>
/// Clear a list faster than List<T>.Clear.
/// It turns out that the List<T>.Clear method will clear all elements in the underlaying array
/// not just the ones up to Count. If the list only has a few elements, but the capacity
/// is huge, this can cause performance problems. Using the RemoveRange method to remove
/// all elements in the list does not have this problem, however it is implemented in a
/// stupid way, so it will clear the elements twice (completely unnecessarily) so it will
/// only be faster than using the Clear method if the number of elements in the list is
/// less than half of the capacity of the list.
///
/// Hopefully this method can be removed when Unity upgrades to a newer version of Mono.
/// </summary>
public static void ClearFast<T>(this List<T> list) {
if (list.Count*2 < list.Capacity) {
list.RemoveRange(0, list.Count);
} else {
list.Clear();
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 787a564bf2d894ee09284b775074864c
timeCreated: 1470483941
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,337 @@
//#define ProfileAstar
using UnityEngine;
using System.Text;
namespace Pathfinding {
[AddComponentMenu("Pathfinding/Pathfinding Debugger")]
[ExecuteInEditMode]
/// <summary>
/// Debugger for the A* Pathfinding Project.
/// This class can be used to profile different parts of the pathfinding system
/// and the whole game as well to some extent.
///
/// Clarification of the labels shown when enabled.
/// All memory related things profiles <b>the whole game</b> not just the A* Pathfinding System.\n
/// - Currently allocated: memory the GC (garbage collector) says the application has allocated right now.
/// - Peak allocated: maximum measured value of the above.
/// - Last collect peak: the last peak of 'currently allocated'.
/// - Allocation rate: how much the 'currently allocated' value increases per second. This value is not as reliable as you can think
/// it is often very random probably depending on how the GC thinks this application is using memory.
/// - Collection frequency: how often the GC is called. Again, the GC might decide it is better with many small collections
/// or with a few large collections. So you cannot really trust this variable much.
/// - Last collect fps: FPS during the last garbage collection, the GC will lower the fps a lot.
///
/// - FPS: current FPS (not updated every frame for readability)
/// - Lowest FPS (last x): As the label says, the lowest fps of the last x frames.
///
/// - Size: Size of the path pool.
/// - Total created: Number of paths of that type which has been created. Pooled paths are not counted twice.
/// If this value just keeps on growing and growing without an apparent stop, you are are either not pooling any paths
/// or you have missed to pool some path somewhere in your code.
///
/// See: pooling
///
/// TODO: Add field showing how many graph updates are being done right now
/// </summary>
[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_astar_debugger.php")]
public class AstarDebugger : VersionedMonoBehaviour {
public int yOffset = 5;
public bool show = true;
public bool showInEditor = false;
public bool showFPS = false;
public bool showPathProfile = false;
public bool showMemProfile = false;
public bool showGraph = false;
public int graphBufferSize = 200;
/// <summary>
/// Font to use.
/// A monospaced font is the best
/// </summary>
public Font font = null;
public int fontSize = 12;
StringBuilder text = new StringBuilder();
string cachedText;
float lastUpdate = -999;
private GraphPoint[] graph;
struct GraphPoint {
public float fps, memory;
public bool collectEvent;
}
private float delayedDeltaTime = 1;
private float lastCollect = 0;
private float lastCollectNum = 0;
private float delta = 0;
private float lastDeltaTime = 0;
private int allocRate = 0;
private int lastAllocMemory = 0;
private float lastAllocSet = -9999;
private int allocMem = 0;
private int collectAlloc = 0;
private int peakAlloc = 0;
private int fpsDropCounterSize = 200;
private float[] fpsDrops;
private Rect boxRect;
private GUIStyle style;
private Camera cam;
float graphWidth = 100;
float graphHeight = 100;
float graphOffset = 50;
public void Start () {
useGUILayout = false;
fpsDrops = new float[fpsDropCounterSize];
cam = GetComponent<Camera>();
if (cam == null) {
cam = Camera.main;
}
graph = new GraphPoint[graphBufferSize];
if (Time.unscaledDeltaTime > 0) {
for (int i = 0; i < fpsDrops.Length; i++) {
fpsDrops[i] = 1F / Time.unscaledDeltaTime;
}
}
}
int maxVecPool = 0;
int maxNodePool = 0;
PathTypeDebug[] debugTypes = new PathTypeDebug[] {
new PathTypeDebug("ABPath", () => PathPool.GetSize(typeof(ABPath)), () => PathPool.GetTotalCreated(typeof(ABPath)))
};
struct PathTypeDebug {
string name;
System.Func<int> getSize;
System.Func<int> getTotalCreated;
public PathTypeDebug (string name, System.Func<int> getSize, System.Func<int> getTotalCreated) {
this.name = name;
this.getSize = getSize;
this.getTotalCreated = getTotalCreated;
}
public void Print (StringBuilder text) {
int totCreated = getTotalCreated();
if (totCreated > 0) {
text.Append("\n").Append((" " + name).PadRight(25)).Append(getSize()).Append("/").Append(totCreated);
}
}
}
public void LateUpdate () {
if (!show || (!Application.isPlaying && !showInEditor)) return;
if (Time.unscaledDeltaTime <= 0.0001f)
return;
int collCount = System.GC.CollectionCount(0);
if (lastCollectNum != collCount) {
lastCollectNum = collCount;
delta = Time.realtimeSinceStartup-lastCollect;
lastCollect = Time.realtimeSinceStartup;
lastDeltaTime = Time.unscaledDeltaTime;
collectAlloc = allocMem;
}
allocMem = (int)System.GC.GetTotalMemory(false);
bool collectEvent = allocMem < peakAlloc;
peakAlloc = !collectEvent ? allocMem : peakAlloc;
if (Time.realtimeSinceStartup - lastAllocSet > 0.3F || !Application.isPlaying) {
int diff = allocMem - lastAllocMemory;
lastAllocMemory = allocMem;
lastAllocSet = Time.realtimeSinceStartup;
delayedDeltaTime = Time.unscaledDeltaTime;
if (diff >= 0) {
allocRate = diff;
}
}
if (Application.isPlaying) {
fpsDrops[Time.frameCount % fpsDrops.Length] = Time.unscaledDeltaTime > 0.00001f ? 1F / Time.unscaledDeltaTime : 0;
int graphIndex = Time.frameCount % graph.Length;
graph[graphIndex].fps = Time.unscaledDeltaTime < 0.00001f ? 1F / Time.unscaledDeltaTime : 0;
graph[graphIndex].collectEvent = collectEvent;
graph[graphIndex].memory = allocMem;
}
if (Application.isPlaying && cam != null && showGraph) {
graphWidth = cam.pixelWidth*0.8f;
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);
}
int currentGraphIndex = Time.frameCount % graph.Length;
Matrix4x4 m = Matrix4x4.TRS(new Vector3((cam.pixelWidth - graphWidth)/2f, graphOffset, 1), Quaternion.identity, new Vector3(graphWidth, graphHeight, 1));
for (int i = 0; i < graph.Length-1; i++) {
if (i == currentGraphIndex) continue;
DrawGraphLine(i, m, i/(float)graph.Length, (i+1)/(float)graph.Length, Mathf.InverseLerp(minMem, maxMem, graph[i].memory), Mathf.InverseLerp(minMem, maxMem, graph[i+1].memory), Color.blue);
DrawGraphLine(i, m, i/(float)graph.Length, (i+1)/(float)graph.Length, Mathf.InverseLerp(minFPS, maxFPS, graph[i].fps), Mathf.InverseLerp(minFPS, maxFPS, graph[i+1].fps), Color.green);
}
}
}
void DrawGraphLine (int index, Matrix4x4 m, float x1, float x2, float y1, float y2, Color color) {
Debug.DrawLine(cam.ScreenToWorldPoint(m.MultiplyPoint3x4(new Vector3(x1, y1))), cam.ScreenToWorldPoint(m.MultiplyPoint3x4(new Vector3(x2, y2))), color);
}
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"));
}
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5103795af2d504ea693528e938005441
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,100 @@
using UnityEngine;
namespace Pathfinding {
using Pathfinding.Util;
/// <summary>
/// Policy for how often to recalculate an agent's path.
///
/// See: \reflink{AIBase.autoRepath}
/// See: \reflink{AILerp.autoRepath}
/// </summary>
[System.Serializable]
public class AutoRepathPolicy {
/// <summary>Policy mode for how often to recalculate an agent's path.</summary>
public enum Mode {
/// <summary>
/// Never automatically recalculate the path.
/// Paths can be recalculated manually by for example calling \reflink{IAstarAI.SearchPath} or \reflink{IAstarAI.SetPath}.
/// This mode is useful if you want full control of when the agent calculates its path.
/// </summary>
Never,
/// <summary>Recalculate the path every \reflink{interval} seconds</summary>
EveryNSeconds,
/// <summary>
/// Recalculate the path at least every \reflink{maximumInterval} seconds but more often if the destination moves a lot.
/// This mode is recommended since it allows the agent to quickly respond to new destinations without using up a lot of CPU power to calculate paths
/// when it doesn't have to.
///
/// More precisely:\n
/// Let C be a circle centered at the destination for the last calculated path with a radius equal to the distance to that point divided by \reflink{sensitivity}.\n
/// If the new destination is outside that circle the path will be immediately recalculated.\n
/// Otherwise let F be the 1 - (distance from the circle's center to the new destination divided by the circle's radius).\n
/// So F will be 1 if the new destination is the same as the old one and 0 if it is at the circle's edge.\n
/// Recalculate the path if the time since the last path recalculation is greater than \reflink{maximumInterval} multiplied by F.\n
///
/// Thus if the destination doesn't change the path will be recalculated every \reflink{maximumInterval} seconds.
/// </summary>
Dynamic,
}
/// <summary>Policy to use when recalculating paths</summary>
public Mode mode = Mode.Dynamic;
/// <summary>Number of seconds between each automatic path recalculation for Mode.EveryNSeconds</summary>
public float interval = 0.5f;
/// <summary>
/// How sensitive the agent should be to changes in its destination for Mode.Dynamic.
/// A higher value means the destination has to move less for the path to be recalculated.
///
/// See: \reflink{Mode}
/// </summary>
public float sensitivity = 10.0f;
/// <summary>Maximum number of seconds between each automatic path recalculation for Mode.Dynamic</summary>
public float maximumInterval = 2.0f;
/// <summary>If true the sensitivity will be visualized as a circle in the scene view when the game is playing</summary>
public bool visualizeSensitivity = false;
Vector3 lastDestination = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
float lastRepathTime = float.NegativeInfinity;
/// <summary>True if the path should be recalculated according to the policy</summary>
public virtual bool ShouldRecalculatePath (IAstarAI ai) {
if (mode == Mode.Never || float.IsPositiveInfinity(ai.destination.x)) return false;
float timeSinceLast = Time.time - lastRepathTime;
if (mode == Mode.EveryNSeconds) {
return timeSinceLast >= interval;
} else {
// cost = change in destination / max(distance to destination, radius)
float squaredCost = (ai.destination - lastDestination).sqrMagnitude / Mathf.Max((ai.position - lastDestination).sqrMagnitude, ai.radius*ai.radius);
float fraction = squaredCost * (sensitivity*sensitivity);
if (fraction > 1.0f || float.IsNaN(fraction)) return true;
if (timeSinceLast >= maximumInterval*(1 - Mathf.Sqrt(fraction))) return true;
return false;
}
}
/// <summary>Reset the runtime variables so that the policy behaves as if the game just started</summary>
public virtual void Reset () {
lastRepathTime = float.NegativeInfinity;
}
/// <summary>Must be called when a path request has been scheduled</summary>
public virtual void DidRecalculatePath (Vector3 destination) {
lastRepathTime = Time.time;
lastDestination = destination;
}
public void DrawGizmos (IAstarAI ai) {
if (visualizeSensitivity && !float.IsPositiveInfinity(lastDestination.x)) {
float r = Mathf.Sqrt(Mathf.Max((ai.position - lastDestination).sqrMagnitude, ai.radius*ai.radius)/(sensitivity*sensitivity));
Draw.Gizmos.CircleXZ(lastDestination, r, Color.magenta);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2664ef60fd2811ba280670298a6d312b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,324 @@
#pragma warning disable 162
#pragma warning disable 429
#define DECREASE_KEY
namespace Pathfinding {
/// <summary>
/// Binary heap implementation.
/// Binary heaps are really fast for ordering nodes in a way that
/// makes it possible to get the node with the lowest F score.
/// Also known as a priority queue.
///
/// This has actually been rewritten as a 4-ary heap
/// for performance, but it's the same principle.
///
/// See: http://en.wikipedia.org/wiki/Binary_heap
/// See: https://en.wikipedia.org/wiki/D-ary_heap
/// </summary>
public class BinaryHeap {
/// <summary>Number of items in the tree</summary>
public int numberOfItems;
/// <summary>The tree will grow by at least this factor every time it is expanded</summary>
public float growthFactor = 2;
/// <summary>
/// Number of children of each node in the tree.
/// Different values have been tested and 4 has been empirically found to perform the best.
/// See: https://en.wikipedia.org/wiki/D-ary_heap
/// </summary>
const int D = 4;
/// <summary>
/// Sort nodes by G score if there is a tie when comparing the F score.
/// Disabling this will improve pathfinding performance with around 2.5%
/// but may break ties between paths that have the same length in a less
/// desirable manner (only relevant for grid graphs).
/// </summary>
const bool SortGScores = true;
public const ushort NotInHeap = 0xFFFF;
/// <summary>Internal backing array for the heap</summary>
private Tuple[] heap;
/// <summary>True if the heap does not contain any elements</summary>
public bool isEmpty {
get {
return numberOfItems <= 0;
}
}
/// <summary>Item in the heap</summary>
private struct Tuple {
public PathNode node;
public uint F;
public Tuple (uint f, PathNode node) {
this.F = f;
this.node = node;
}
}
/// <summary>
/// Rounds up v so that it has remainder 1 when divided by D.
/// I.e it is of the form n*D + 1 where n is any non-negative integer.
/// </summary>
static int RoundUpToNextMultipleMod1 (int v) {
// I have a feeling there is a nicer way to do this
return v + (4 - ((v-1) % D)) % D;
}
/// <summary>Create a new heap with the specified initial capacity</summary>
public BinaryHeap (int capacity) {
// Make sure the size has remainder 1 when divided by D
// This allows us to always guarantee that indices used in the Remove method
// will never throw out of bounds exceptions
capacity = RoundUpToNextMultipleMod1(capacity);
heap = new Tuple[capacity];
numberOfItems = 0;
}
/// <summary>Removes all elements from the heap</summary>
public void Clear () {
#if DECREASE_KEY
// Clear all heap indices
// This is important to avoid bugs
for (int i = 0; i < numberOfItems; i++) {
heap[i].node.heapIndex = NotInHeap;
}
#endif
numberOfItems = 0;
}
internal PathNode GetNode (int i) {
return heap[i].node;
}
internal void SetF (int i, uint f) {
heap[i].F = f;
}
/// <summary>Expands to a larger backing array when the current one is too small</summary>
void Expand () {
// 65533 == 1 mod 4 and slightly smaller than 1<<16 = 65536
int newSize = System.Math.Max(heap.Length+4, System.Math.Min(65533, (int)System.Math.Round(heap.Length*growthFactor)));
// Make sure the size has remainder 1 when divided by D
// This allows us to always guarantee that indices used in the Remove method
// will never throw out of bounds exceptions
newSize = RoundUpToNextMultipleMod1(newSize);
// Check if the heap is really large
// Also note that heaps larger than this are not supported
// since PathNode.heapIndex is a ushort and can only store
// values up to 65535 (NotInHeap = 65535 is reserved however)
if (newSize > (1<<16) - 2) {
throw new System.Exception("Binary Heap Size really large (>65534). A heap size this large is probably the cause of pathfinding running in an infinite loop. ");
}
var newHeap = new Tuple[newSize];
heap.CopyTo(newHeap, 0);
#if ASTARDEBUG
UnityEngine.Debug.Log("Resizing binary heap to "+newSize);
#endif
heap = newHeap;
}
/// <summary>Adds a node to the heap</summary>
public void Add (PathNode node) {
if (node == null) throw new System.ArgumentNullException("node");
#if DECREASE_KEY
// Check if node is already in the heap
if (node.heapIndex != NotInHeap) {
DecreaseKey(heap[node.heapIndex], node.heapIndex);
return;
}
#endif
if (numberOfItems == heap.Length) {
Expand();
}
DecreaseKey(new Tuple(0, node), (ushort)numberOfItems);
numberOfItems++;
}
void DecreaseKey (Tuple node, ushort index) {
// This is where 'obj' is in the binary heap logically speaking
// (for performance reasons we don't actually store it there until
// we know the final index, that's just a waste of CPU cycles)
int bubbleIndex = index;
// Update F value, it might have changed since the node was originally added to the heap
uint nodeF = node.F = node.node.F;
uint nodeG = node.node.G;
while (bubbleIndex != 0) {
// Parent node of the bubble node
int parentIndex = (bubbleIndex-1) / D;
if (nodeF < heap[parentIndex].F || (SortGScores && nodeF == heap[parentIndex].F && nodeG > heap[parentIndex].node.G)) {
// Swap the bubble node and parent node
// (we don't really need to store the bubble node until we know the final index though
// so we do that after the loop instead)
heap[bubbleIndex] = heap[parentIndex];
#if DECREASE_KEY
heap[bubbleIndex].node.heapIndex = (ushort)bubbleIndex;
#endif
bubbleIndex = parentIndex;
} else {
break;
}
}
heap[bubbleIndex] = node;
#if DECREASE_KEY
node.node.heapIndex = (ushort)bubbleIndex;
#endif
}
/// <summary>Returns the node with the lowest F score from the heap</summary>
public PathNode Remove () {
PathNode returnItem = heap[0].node;
#if DECREASE_KEY
returnItem.heapIndex = NotInHeap;
#endif
numberOfItems--;
if (numberOfItems == 0) return returnItem;
// Last item in the heap array
var swapItem = heap[numberOfItems];
var swapItemG = swapItem.node.G;
int swapIndex = 0, parent;
// Trickle upwards
while (true) {
parent = swapIndex;
uint swapF = swapItem.F;
int pd = parent * D + 1;
// If this holds, then the indices used
// below are guaranteed to not throw an index out of bounds
// exception since we choose the size of the array in that way
if (pd <= numberOfItems) {
// Loading all F scores here instead of inside the if statements
// reduces data dependencies and improves performance
uint f0 = heap[pd+0].F;
uint f1 = heap[pd+1].F;
uint f2 = heap[pd+2].F;
uint f3 = heap[pd+3].F;
// The common case is that all children of a node are present
// so the first comparison in each if statement below
// will be extremely well predicted so it is essentially free
// (I tried optimizing for the common case, but it didn't affect performance at all
// at the expense of longer code, the CPU branch predictor is really good)
if (pd+0 < numberOfItems && (f0 < swapF || (SortGScores && f0 == swapF && heap[pd+0].node.G < swapItemG))) {
swapF = f0;
swapIndex = pd+0;
}
if (pd+1 < numberOfItems && (f1 < swapF || (SortGScores && f1 == swapF && heap[pd+1].node.G < (swapIndex == parent ? swapItemG : heap[swapIndex].node.G)))) {
swapF = f1;
swapIndex = pd+1;
}
if (pd+2 < numberOfItems && (f2 < swapF || (SortGScores && f2 == swapF && heap[pd+2].node.G < (swapIndex == parent ? swapItemG : heap[swapIndex].node.G)))) {
swapF = f2;
swapIndex = pd+2;
}
if (pd+3 < numberOfItems && (f3 < swapF || (SortGScores && f3 == swapF && heap[pd+3].node.G < (swapIndex == parent ? swapItemG : heap[swapIndex].node.G)))) {
swapIndex = pd+3;
}
}
// One if the parent's children are smaller or equal, swap them
// (actually we are just pretenting we swapped them, we hold the swapData
// in local variable and only assign it once we know the final index)
if (parent != swapIndex) {
heap[parent] = heap[swapIndex];
#if DECREASE_KEY
heap[parent].node.heapIndex = (ushort)parent;
#endif
} else {
break;
}
}
// Assign element to the final position
heap[swapIndex] = swapItem;
#if DECREASE_KEY
swapItem.node.heapIndex = (ushort)swapIndex;
#endif
// For debugging
// Validate ();
return returnItem;
}
void Validate () {
for (int i = 1; i < numberOfItems; i++) {
int parentIndex = (i-1)/D;
if (heap[parentIndex].F > heap[i].F) {
throw new System.Exception("Invalid state at " + i + ":" + parentIndex + " ( " + heap[parentIndex].F + " > " + heap[i].F + " ) ");
}
#if DECREASE_KEY
if (heap[i].node.heapIndex != i) {
throw new System.Exception("Invalid heap index");
}
#endif
}
}
/// <summary>
/// Rebuilds the heap by trickeling down all items.
/// Usually called after the hTarget on a path has been changed
/// </summary>
public void Rebuild () {
#if ASTARDEBUG
int changes = 0;
#endif
for (int i = 2; i < numberOfItems; i++) {
int bubbleIndex = i;
var node = heap[i];
uint nodeF = node.F;
while (bubbleIndex != 1) {
int parentIndex = bubbleIndex / D;
if (nodeF < heap[parentIndex].F) {
heap[bubbleIndex] = heap[parentIndex];
#if DECREASE_KEY
heap[bubbleIndex].node.heapIndex = (ushort)bubbleIndex;
#endif
heap[parentIndex] = node;
#if DECREASE_KEY
heap[parentIndex].node.heapIndex = (ushort)parentIndex;
#endif
bubbleIndex = parentIndex;
#if ASTARDEBUG
changes++;
#endif
} else {
break;
}
}
}
#if ASTARDEBUG
UnityEngine.Debug.Log("+++ Rebuilt Heap - "+changes+" changes +++");
#endif
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: eb4299e8747f44ad2b4e086752108ea3
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,91 @@
using UnityEngine;
namespace Pathfinding.Util {
/// <summary>Helper methods for drawing gizmos and debug lines</summary>
public class Draw {
public static readonly Draw Debug = new Draw { gizmos = false };
public static readonly Draw Gizmos = new Draw { gizmos = true };
bool gizmos;
Matrix4x4 matrix = Matrix4x4.identity;
void SetColor (Color color) {
if (gizmos && UnityEngine.Gizmos.color != color) UnityEngine.Gizmos.color = color;
}
public void Polyline (System.Collections.Generic.List<Vector3> points, Color color, bool cycle = false) {
for (int i = 0; i < points.Count - 1; i++) {
Line(points[i], points[i+1], color);
}
if (cycle && points.Count > 1) Line(points[points.Count - 1], points[0], color);
}
public void Line (Vector3 a, Vector3 b, Color color) {
SetColor(color);
if (gizmos) UnityEngine.Gizmos.DrawLine(matrix.MultiplyPoint3x4(a), matrix.MultiplyPoint3x4(b));
else UnityEngine.Debug.DrawLine(matrix.MultiplyPoint3x4(a), matrix.MultiplyPoint3x4(b), color);
}
public void CircleXZ (Vector3 center, float radius, Color color, float startAngle = 0f, float endAngle = 2*Mathf.PI) {
int steps = 40;
#if UNITY_EDITOR
if (gizmos) steps = (int)Mathf.Clamp(Mathf.Sqrt(radius / UnityEditor.HandleUtility.GetHandleSize((UnityEngine.Gizmos.matrix * matrix).MultiplyPoint3x4(center))) * 25, 4, 40);
#endif
while (startAngle > endAngle) startAngle -= 2*Mathf.PI;
Vector3 prev = new Vector3(Mathf.Cos(startAngle)*radius, 0, Mathf.Sin(startAngle)*radius);
for (int i = 0; i <= steps; i++) {
Vector3 c = new Vector3(Mathf.Cos(Mathf.Lerp(startAngle, endAngle, i/(float)steps))*radius, 0, Mathf.Sin(Mathf.Lerp(startAngle, endAngle, i/(float)steps))*radius);
Line(center + prev, center + c, color);
prev = c;
}
}
public void Cylinder (Vector3 position, Vector3 up, float height, float radius, Color color) {
var tangent = Vector3.Cross(up, Vector3.one).normalized;
matrix = Matrix4x4.TRS(position, Quaternion.LookRotation(tangent, up), new Vector3(radius, height, radius));
CircleXZ(Vector3.zero, 1, color);
if (height > 0) {
CircleXZ(Vector3.up, 1, color);
Line(new Vector3(1, 0, 0), new Vector3(1, 1, 0), color);
Line(new Vector3(-1, 0, 0), new Vector3(-1, 1, 0), color);
Line(new Vector3(0, 0, 1), new Vector3(0, 1, 1), color);
Line(new Vector3(0, 0, -1), new Vector3(0, 1, -1), color);
}
matrix = Matrix4x4.identity;
}
public void CrossXZ (Vector3 position, Color color, float size = 1) {
size *= 0.5f;
Line(position - Vector3.right*size, position + Vector3.right*size, color);
Line(position - Vector3.forward*size, position + Vector3.forward*size, color);
}
public void Bezier (Vector3 a, Vector3 b, Color color) {
Vector3 dir = b - a;
if (dir == Vector3.zero) return;
Vector3 normal = Vector3.Cross(Vector3.up, dir);
Vector3 normalUp = Vector3.Cross(dir, normal);
normalUp = normalUp.normalized;
normalUp *= dir.magnitude*0.1f;
Vector3 p1c = a + normalUp;
Vector3 p2c = b + normalUp;
Vector3 prev = a;
for (int i = 1; i <= 20; i++) {
float t = i/20.0f;
Vector3 p = AstarSplines.CubicBezier(a, p1c, p2c, b, t);
Line(prev, p, color);
prev = p;
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 506739df886ce4ebb9b14b16d86b5e13
timeCreated: 1492346087
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,105 @@
namespace Pathfinding {
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
/// <summary>Internal utility class for looking up editor resources</summary>
public static class EditorResourceHelper {
/// <summary>
/// Path to the editor assets folder for the A* Pathfinding Project. If this path turns out to be incorrect, the script will try to find the correct path
/// See: LoadStyles
/// </summary>
public static string editorAssets;
static EditorResourceHelper () {
// Look up editor assets directory when first accessed
LocateEditorAssets();
}
static Material surfaceMat, lineMat;
static Texture2D handlesAALineTex;
public static Material GizmoSurfaceMaterial {
get {
if (!surfaceMat) surfaceMat = UnityEditor.AssetDatabase.LoadAssetAtPath(EditorResourceHelper.editorAssets + "/Materials/Navmesh.mat", typeof(Material)) as Material;
return surfaceMat;
}
}
public static Material GizmoLineMaterial {
get {
if (!lineMat) lineMat = UnityEditor.AssetDatabase.LoadAssetAtPath(EditorResourceHelper.editorAssets + "/Materials/NavmeshOutline.mat", typeof(Material)) as Material;
return lineMat;
}
}
public static Texture2D HandlesAALineTexture {
get {
if (!handlesAALineTex) handlesAALineTex = Resources.Load<Texture2D>("handles_aaline");
return handlesAALineTex;
}
}
/// <summary>Locates the editor assets folder in case the user has moved it</summary>
public static bool LocateEditorAssets () {
#if UNITY_2019_3_OR_NEWER
var package = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(EditorResourceHelper).Assembly);
if (package != null) {
editorAssets = package.assetPath + "/Editor/EditorAssets";
if (System.IO.File.Exists(package.resolvedPath + "/Editor/EditorAssets/AstarEditorSkinLight.guiskin")) {
return true;
} else {
Debug.LogError("Could not find editor assets folder in package at " + editorAssets + ". Is the package corrupt?");
return false;
}
}
#endif
string projectPath = Application.dataPath;
if (projectPath.EndsWith("/Assets")) {
projectPath = projectPath.Remove(projectPath.Length-("Assets".Length));
}
editorAssets = "Assets/AstarPathfindingProject/Editor/EditorAssets";
if (!System.IO.File.Exists(projectPath + editorAssets + "/AstarEditorSkinLight.guiskin") && !System.IO.File.Exists(projectPath + editorAssets + "/AstarEditorSkin.guiskin")) {
//Initiate search
var sdir = new System.IO.DirectoryInfo(Application.dataPath);
var dirQueue = new Queue<System.IO.DirectoryInfo>();
dirQueue.Enqueue(sdir);
bool found = false;
while (dirQueue.Count > 0) {
System.IO.DirectoryInfo dir = dirQueue.Dequeue();
if (System.IO.File.Exists(dir.FullName + "/AstarEditorSkinLight.guiskin") || System.IO.File.Exists(dir.FullName + "/AstarEditorSkin.guiskin")) {
// Handle windows file paths
string path = dir.FullName.Replace('\\', '/');
found = true;
// Remove data path from string to make it relative
path = path.Replace(projectPath, "");
if (path.StartsWith("/")) {
path = path.Remove(0, 1);
}
editorAssets = path;
return true;
}
var dirs = dir.GetDirectories();
for (int i = 0; i < dirs.Length; i++) {
dirQueue.Enqueue(dirs[i]);
}
}
if (!found) {
Debug.LogWarning("Could not locate editor assets folder. Make sure you have imported the package correctly.\nA* Pathfinding Project");
return false;
}
}
return true;
}
}
#endif
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8127bc49e9e2d42dfa7a4e057842f165
timeCreated: 1480419306
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,10 @@
using Pathfinding.Serialization;
namespace Pathfinding {
[JsonOptIn]
/// <summary>Defined here only so non-editor classes can use the <see cref="target"/> field</summary>
public class GraphEditorBase {
/// <summary>NavGraph this editor is exposing</summary>
public NavGraph target;
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 704136724bc95455ebe477f42f5c5a84
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,204 @@
using UnityEngine;
using System.Collections.Generic;
namespace Pathfinding {
/// <summary>
/// GraphModifier is used for modifying graphs or processing graph data based on events.
/// This class is a simple container for a number of events.
///
/// Warning: Some events will be called both in play mode <b>and in editor mode</b> (at least the scan events).
/// So make sure your code handles both cases well. You may choose to ignore editor events.
/// See: Application.IsPlaying
/// </summary>
[ExecuteInEditMode]
public abstract class GraphModifier : VersionedMonoBehaviour {
/// <summary>All active graph modifiers</summary>
private static GraphModifier root;
private GraphModifier prev;
private GraphModifier next;
/// <summary>Unique persistent ID for this component, used for serialization</summary>
[SerializeField]
[HideInInspector]
protected ulong uniqueID;
/// <summary>Maps persistent IDs to the component that uses it</summary>
protected static Dictionary<ulong, GraphModifier> usedIDs = new Dictionary<ulong, GraphModifier>();
protected static List<T> GetModifiersOfType<T>() where T : GraphModifier {
var current = root;
var result = new List<T>();
while (current != null) {
var cast = current as T;
if (cast != null) result.Add(cast);
current = current.next;
}
return result;
}
public static void FindAllModifiers () {
var allModifiers = FindObjectsOfType(typeof(GraphModifier)) as GraphModifier[];
for (int i = 0; i < allModifiers.Length; i++) {
if (allModifiers[i].enabled) allModifiers[i].OnEnable();
}
}
/// <summary>GraphModifier event type</summary>
public enum EventType {
PostScan = 1 << 0,
PreScan = 1 << 1,
LatePostScan = 1 << 2,
PreUpdate = 1 << 3,
PostUpdate = 1 << 4,
PostCacheLoad = 1 << 5
}
/// <summary>Triggers an event for all active graph modifiers</summary>
public static void TriggerEvent (GraphModifier.EventType type) {
if (!Application.isPlaying) {
FindAllModifiers();
}
GraphModifier c = root;
switch (type) {
case EventType.PreScan:
while (c != null) { c.OnPreScan(); c = c.next; }
break;
case EventType.PostScan:
while (c != null) { c.OnPostScan(); c = c.next; }
break;
case EventType.LatePostScan:
while (c != null) { c.OnLatePostScan(); c = c.next; }
break;
case EventType.PreUpdate:
while (c != null) { c.OnGraphsPreUpdate(); c = c.next; }
break;
case EventType.PostUpdate:
while (c != null) { c.OnGraphsPostUpdate(); c = c.next; }
break;
case EventType.PostCacheLoad:
while (c != null) { c.OnPostCacheLoad(); c = c.next; }
break;
}
}
/// <summary>Adds this modifier to list of active modifiers</summary>
protected virtual void OnEnable () {
RemoveFromLinkedList();
AddToLinkedList();
ConfigureUniqueID();
}
/// <summary>Removes this modifier from list of active modifiers</summary>
protected virtual void OnDisable () {
RemoveFromLinkedList();
}
protected override void Awake () {
base.Awake();
ConfigureUniqueID();
}
void ConfigureUniqueID () {
// Check if any other object is using the same uniqueID
// In that case this object may have been duplicated
GraphModifier usedBy;
if (usedIDs.TryGetValue(uniqueID, out usedBy) && usedBy != this) {
Reset();
}
usedIDs[uniqueID] = this;
}
void AddToLinkedList () {
if (root == null) {
root = this;
} else {
next = root;
root.prev = this;
root = this;
}
}
void RemoveFromLinkedList () {
if (root == this) {
root = next;
if (root != null) root.prev = null;
} else {
if (prev != null) prev.next = next;
if (next != null) next.prev = prev;
}
prev = null;
next = null;
}
protected virtual void OnDestroy () {
usedIDs.Remove(uniqueID);
}
/// <summary>
/// Called right after all graphs have been scanned.
/// FloodFill and other post processing has not been done.
///
/// Warning: Since OnEnable and Awake are called roughly in the same time, the only way
/// to ensure that these scripts get this call when scanning in Awake is to
/// set the Script Execution Order for AstarPath to some time later than default time
/// (see Edit -> Project Settings -> Script Execution Order).
/// TODO: Is this still relevant? A call to FindAllModifiers should have before this method is called
/// so the above warning is probably not relevant anymore.
///
/// See: OnLatePostScan
/// </summary>
public virtual void OnPostScan () {}
/// <summary>
/// Called right before graphs are going to be scanned.
///
/// Warning: Since OnEnable and Awake are called roughly in the same time, the only way
/// to ensure that these scripts get this call when scanning in Awake is to
/// set the Script Execution Order for AstarPath to some time later than default time
/// (see Edit -> Project Settings -> Script Execution Order).
/// TODO: Is this still relevant? A call to FindAllModifiers should have before this method is called
/// so the above warning is probably not relevant anymore.
///
/// See: OnLatePostScan
/// </summary>
public virtual void OnPreScan () {}
/// <summary>
/// Called at the end of the scanning procedure.
/// This is the absolute last thing done by Scan.
/// </summary>
public virtual void OnLatePostScan () {}
/// <summary>
/// Called after cached graphs have been loaded.
/// When using cached startup, this event is analogous to OnLatePostScan and implementing scripts
/// should do roughly the same thing for both events.
/// </summary>
public virtual void OnPostCacheLoad () {}
/// <summary>Called before graphs are updated using GraphUpdateObjects</summary>
public virtual void OnGraphsPreUpdate () {}
/// <summary>
/// Called after graphs have been updated using GraphUpdateObjects.
/// Eventual flood filling has been done
/// </summary>
public virtual void OnGraphsPostUpdate () {}
protected override void Reset () {
base.Reset();
// Create a new random 64 bit value (62 bit actually because we skip negative numbers, but that's still enough by a huge margin)
var rnd1 = (ulong)Random.Range(0, int.MaxValue);
var rnd2 = ((ulong)Random.Range(0, int.MaxValue) << 32);
uniqueID = rnd1 | rnd2;
usedIDs[uniqueID] = this;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 39897fb482672480a817862c3909a4aa
timeCreated: 1490044676
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: -222
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,369 @@
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
#if UNITY_5_5_OR_NEWER
using UnityEngine.Profiling;
#endif
namespace Pathfinding {
using UnityEngine.Assertions;
#if NETFX_CORE
using Thread = Pathfinding.WindowsStore.Thread;
#else
using Thread = System.Threading.Thread;
#endif
class GraphUpdateProcessor {
public event System.Action OnGraphsUpdated;
/// <summary>Holds graphs that can be updated</summary>
readonly AstarPath astar;
#if !UNITY_WEBGL
/// <summary>
/// Reference to the thread which handles async graph updates.
/// See: ProcessGraphUpdatesAsync
/// </summary>
Thread graphUpdateThread;
#endif
/// <summary>Used for IsAnyGraphUpdateInProgress</summary>
bool anyGraphUpdateInProgress;
#if UNITY_2017_3_OR_NEWER && !UNITY_WEBGL
CustomSampler asyncUpdateProfilingSampler;
#endif
/// <summary>
/// Queue containing all waiting graph update queries. Add to this queue by using \link AddToQueue \endlink.
/// See: AddToQueue
/// </summary>
readonly Queue<GraphUpdateObject> graphUpdateQueue = new Queue<GraphUpdateObject>();
/// <summary>Queue of all async graph updates waiting to be executed</summary>
readonly Queue<GUOSingle> graphUpdateQueueAsync = new Queue<GUOSingle>();
/// <summary>Queue of all non-async graph update post events waiting to be executed</summary>
readonly Queue<GUOSingle> graphUpdateQueuePost = new Queue<GUOSingle>();
/// <summary>Queue of all non-async graph updates waiting to be executed</summary>
readonly Queue<GUOSingle> graphUpdateQueueRegular = new Queue<GUOSingle>();
readonly System.Threading.ManualResetEvent asyncGraphUpdatesComplete = new System.Threading.ManualResetEvent(true);
#if !UNITY_WEBGL
readonly System.Threading.AutoResetEvent graphUpdateAsyncEvent = new System.Threading.AutoResetEvent(false);
readonly System.Threading.AutoResetEvent exitAsyncThread = new System.Threading.AutoResetEvent(false);
#endif
/// <summary>Returns if any graph updates are waiting to be applied</summary>
public bool IsAnyGraphUpdateQueued { get { return graphUpdateQueue.Count > 0; } }
/// <summary>Returns if any graph updates are in progress</summary>
public bool IsAnyGraphUpdateInProgress { get { return anyGraphUpdateInProgress; } }
/// <summary>Order type for updating graphs</summary>
enum GraphUpdateOrder {
GraphUpdate,
// FloodFill
}
/// <summary>Holds a single update that needs to be performed on a graph</summary>
struct GUOSingle {
public GraphUpdateOrder order;
public IUpdatableGraph graph;
public GraphUpdateObject obj;
}
public GraphUpdateProcessor (AstarPath astar) {
this.astar = astar;
}
/// <summary>Work item which can be used to apply all queued updates</summary>
public AstarWorkItem GetWorkItem () {
return new AstarWorkItem(QueueGraphUpdatesInternal, ProcessGraphUpdates);
}
public void EnableMultithreading () {
#if !UNITY_WEBGL
if (graphUpdateThread == null || !graphUpdateThread.IsAlive) {
#if UNITY_2017_3_OR_NEWER && !UNITY_WEBGL
asyncUpdateProfilingSampler = CustomSampler.Create("Graph Update");
#endif
graphUpdateThread = new Thread(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();
}
#endif
}
public void DisableMultithreading () {
#if !UNITY_WEBGL
if (graphUpdateThread != null && graphUpdateThread.IsAlive) {
// Resume graph update thread, will cause it to terminate
exitAsyncThread.Set();
if (!graphUpdateThread.Join(5*1000)) {
Debug.LogError("Graph update thread did not exit in 5 seconds");
}
graphUpdateThread = null;
}
#endif
}
/// <summary>
/// Update all graphs using the GraphUpdateObject.
/// This can be used to, e.g make all nodes in an area unwalkable, or set them to a higher penalty.
/// The graphs will be updated as soon as possible (with respect to AstarPath.batchGraphUpdates)
///
/// See: FlushGraphUpdates
/// </summary>
public void AddToQueue (GraphUpdateObject ob) {
// Put the GUO in the queue
graphUpdateQueue.Enqueue(ob);
}
/// <summary>Schedules graph updates internally</summary>
void QueueGraphUpdatesInternal () {
while (graphUpdateQueue.Count > 0) {
GraphUpdateObject ob = graphUpdateQueue.Dequeue();
if (ob.internalStage != GraphUpdateObject.STAGE_PENDING) {
Debug.LogError("Expected remaining graph updates to be pending");
continue;
}
ob.internalStage = 0;
foreach (IUpdatableGraph g in astar.data.GetUpdateableGraphs()) {
NavGraph gr = g as NavGraph;
if (ob.nnConstraint == null || ob.nnConstraint.SuitableGraph(astar.data.GetGraphIndex(gr), gr)) {
var guo = new GUOSingle();
guo.order = GraphUpdateOrder.GraphUpdate;
guo.obj = ob;
guo.graph = g;
ob.internalStage += 1;
graphUpdateQueueRegular.Enqueue(guo);
}
}
}
GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate);
anyGraphUpdateInProgress = true;
}
/// <summary>
/// Updates graphs.
/// Will do some graph updates, possibly signal another thread to do them.
/// Will only process graph updates added by QueueGraphUpdatesInternal
///
/// Returns: True if all graph updates have been done and pathfinding (or other tasks) may resume.
/// False if there are still graph updates being processed or waiting in the queue.
/// </summary>
/// <param name="force">If true, all graph updates will be processed before this function returns. The return value
/// will be True.</param>
bool ProcessGraphUpdates (bool force) {
Assert.IsTrue(anyGraphUpdateInProgress);
if (force) {
asyncGraphUpdatesComplete.WaitOne();
} else {
#if !UNITY_WEBGL
if (!asyncGraphUpdatesComplete.WaitOne(0)) {
return false;
}
#endif
}
Assert.AreEqual(graphUpdateQueueAsync.Count, 0, "Queue should be empty at this stage");
ProcessPostUpdates();
if (!ProcessRegularUpdates(force)) {
return false;
}
GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate);
if (OnGraphsUpdated != null) OnGraphsUpdated();
Assert.AreEqual(graphUpdateQueueRegular.Count, 0, "QueueRegular should be empty at this stage");
Assert.AreEqual(graphUpdateQueueAsync.Count, 0, "QueueAsync should be empty at this stage");
Assert.AreEqual(graphUpdateQueuePost.Count, 0, "QueuePost should be empty at this stage");
anyGraphUpdateInProgress = false;
return true;
}
bool ProcessRegularUpdates (bool force) {
while (graphUpdateQueueRegular.Count > 0) {
GUOSingle s = graphUpdateQueueRegular.Peek();
GraphUpdateThreading threading = s.graph.CanUpdateAsync(s.obj);
#if UNITY_WEBGL
// Never use multithreading in WebGL
threading &= ~GraphUpdateThreading.SeparateThread;
#else
// When not playing or when not using a graph update thread (or if it has crashed), everything runs in the Unity thread
if (force || !Application.isPlaying || graphUpdateThread == null || !graphUpdateThread.IsAlive) {
// Remove the SeparateThread flag
threading &= ~GraphUpdateThreading.SeparateThread;
}
#endif
if ((threading & GraphUpdateThreading.UnityInit) != 0) {
// Process async graph updates first.
// Next call to this function will process this object so it is not dequeued now
if (StartAsyncUpdatesIfQueued()) {
return false;
}
s.graph.UpdateAreaInit(s.obj);
}
if ((threading & GraphUpdateThreading.SeparateThread) != 0) {
// Move GUO to async queue to be updated by another thread
graphUpdateQueueRegular.Dequeue();
graphUpdateQueueAsync.Enqueue(s);
// Don't start any more async graph updates because this update
// requires a Unity thread function to run after it has been completed
// but before the next update is started
if ((threading & GraphUpdateThreading.UnityPost) != 0) {
if (StartAsyncUpdatesIfQueued()) {
return false;
}
}
} else {
// Unity Thread
if (StartAsyncUpdatesIfQueued()) {
return false;
}
graphUpdateQueueRegular.Dequeue();
try {
s.graph.UpdateArea(s.obj);
} catch (System.Exception e) {
Debug.LogError("Error while updating graphs\n"+e);
}
if ((threading & GraphUpdateThreading.UnityPost) != 0) {
s.graph.UpdateAreaPost(s.obj);
}
s.obj.internalStage -= 1;
UnityEngine.Assertions.Assert.IsTrue(s.obj.internalStage >= 0);
}
}
if (StartAsyncUpdatesIfQueued()) {
return false;
}
return true;
}
/// <summary>
/// Signal the graph update thread to start processing graph updates if there are any in the <see cref="graphUpdateQueueAsync"/> queue.
/// Returns: True if the other thread was signaled.
/// </summary>
bool StartAsyncUpdatesIfQueued () {
if (graphUpdateQueueAsync.Count > 0) {
#if UNITY_WEBGL
throw new System.Exception("This should not happen in WebGL");
#else
asyncGraphUpdatesComplete.Reset();
graphUpdateAsyncEvent.Set();
return true;
#endif
}
return false;
}
void ProcessPostUpdates () {
while (graphUpdateQueuePost.Count > 0) {
GUOSingle s = graphUpdateQueuePost.Dequeue();
GraphUpdateThreading threading = s.graph.CanUpdateAsync(s.obj);
if ((threading & GraphUpdateThreading.UnityPost) != 0) {
try {
s.graph.UpdateAreaPost(s.obj);
} catch (System.Exception e) {
Debug.LogError("Error while updating graphs (post step)\n"+e);
}
}
s.obj.internalStage -= 1;
UnityEngine.Assertions.Assert.IsTrue(s.obj.internalStage >= 0);
}
}
#if !UNITY_WEBGL
/// <summary>
/// Graph update thread.
/// Async graph updates will be executed by this method in another thread.
/// </summary>
void ProcessGraphUpdatesAsync () {
#if UNITY_2017_3_OR_NEWER
Profiler.BeginThreadProfiling("Pathfinding", "Threaded Graph Updates");
#endif
var handles = new [] { graphUpdateAsyncEvent, exitAsyncThread };
while (true) {
// Wait for the next batch or exit event
var handleIndex = WaitHandle.WaitAny(handles);
if (handleIndex == 1) {
// Exit even was fired
// Abort thread and clear queue
while (graphUpdateQueueAsync.Count > 0) {
var s = graphUpdateQueueAsync.Dequeue();
s.obj.internalStage = GraphUpdateObject.STAGE_ABORTED;
}
asyncGraphUpdatesComplete.Set();
#if UNITY_2017_3_OR_NEWER
Profiler.EndThreadProfiling();
#endif
return;
}
while (graphUpdateQueueAsync.Count > 0) {
#if UNITY_2017_3_OR_NEWER
asyncUpdateProfilingSampler.Begin();
#endif
// Note that no locking is required here because the main thread
// cannot access it until asyncGraphUpdatesComplete is signaled
GUOSingle aguo = graphUpdateQueueAsync.Dequeue();
try {
if (aguo.order == GraphUpdateOrder.GraphUpdate) {
aguo.graph.UpdateArea(aguo.obj);
graphUpdateQueuePost.Enqueue(aguo);
} else {
throw new System.NotSupportedException("" + aguo.order);
}
} catch (System.Exception e) {
Debug.LogError("Exception while updating graphs:\n"+e);
}
#if UNITY_2017_3_OR_NEWER
asyncUpdateProfilingSampler.End();
#endif
}
// Done
asyncGraphUpdatesComplete.Set();
}
}
#endif
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b1798d8e7c7d54972ae8522558cbd27c
timeCreated: 1443114816
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,270 @@
using UnityEngine;
using System.Collections.Generic;
namespace Pathfinding {
using Pathfinding.Util;
/// <summary>
/// Contains utility methods for getting useful information out of graph.
/// This class works a lot with the <see cref="Pathfinding.GraphNode"/> class, a useful function to get nodes is <see cref="AstarPath.GetNearest"/>.
///
/// See: <see cref="AstarPath.GetNearest"/>
/// See: <see cref="Pathfinding.GraphUpdateUtilities"/>
/// See: <see cref="Pathfinding.PathUtilities"/>
///
/// \ingroup utils
/// </summary>
public static class GraphUtilities {
/// <summary>
/// Convenience method to get a list of all segments of the contours of a graph.
/// Returns: A list of segments. Every 2 elements form a line segment. The first segment is (result[0], result[1]), the second one is (result[2], result[3]) etc.
/// The line segments are oriented so that the navmesh is on the right side of the segments when seen from above.
///
/// This method works for navmesh, recast, grid graphs and layered grid graphs. For other graph types it will return an empty list.
///
/// If you need more information about how the contours are connected you can take a look at the other variants of this method.
///
/// <code>
/// // Get the first graph
/// var navmesh = AstarPath.active.graphs[0];
///
/// // Get all contours of the graph (works for grid, navmesh and recast graphs)
/// var segments = GraphUtilities.GetContours(navmesh);
///
/// // Every 2 elements form a line segment. The first segment is (segments[0], segments[1]), the second one is (segments[2], segments[3]) etc.
/// // The line segments are oriented so that the navmesh is on the right side of the segments when seen from above.
/// for (int i = 0; i < segments.Count; i += 2) {
/// var start = segments[i];
/// var end = segments[i+1];
/// Debug.DrawLine(start, end, Color.red, 3);
/// }
/// </code>
///
/// [Open online documentation to see images]
/// [Open online documentation to see images]
/// </summary>
public static List<Vector3> GetContours (NavGraph graph) {
List<Vector3> result = ListPool<Vector3>.Claim();
if (graph is INavmesh) {
GetContours(graph as INavmesh, (vertices, cycle) => {
for (int j = cycle ? vertices.Count - 1 : 0, i = 0; i < vertices.Count; j = i, i++) {
result.Add((Vector3)vertices[j]);
result.Add((Vector3)vertices[i]);
}
});
#if !ASTAR_NO_GRID_GRAPH
} else if (graph is GridGraph) {
GetContours(graph as GridGraph, vertices => {
for (int j = vertices.Length - 1, i = 0; i < vertices.Length; j = i, i++) {
result.Add((Vector3)vertices[j]);
result.Add((Vector3)vertices[i]);
}
}, 0);
#endif
}
return result;
}
/// <summary>
/// Traces the contour of a navmesh.
///
/// [Open online documentation to see images]
///
/// This image is just used to illustrate the difference between chains and cycles. That it shows a grid graph is not relevant.
/// [Open online documentation to see images]
///
/// See: <see cref="GetContours(NavGraph)"/>
/// </summary>
/// <param name="navmesh">The navmesh-like object to trace. This can be a recast or navmesh graph or it could be a single tile in one such graph.</param>
/// <param name="results">Will be called once for each contour with the contour as a parameter as well as a boolean indicating if the contour is a cycle or a chain (see second image).</param>
public static void GetContours (INavmesh navmesh, System.Action<List<Int3>, bool> results) {
// Assume 3 vertices per node
var uses = new bool[3];
var outline = new Dictionary<int, int>();
var vertexPositions = new Dictionary<int, Int3>();
var hasInEdge = new HashSet<int>();
navmesh.GetNodes(_node => {
var node = _node as TriangleMeshNode;
uses[0] = uses[1] = uses[2] = false;
if (node != null) {
// Find out which edges are shared with other nodes
for (int j = 0; j < node.connections.Length; j++) {
var other = node.connections[j].node as TriangleMeshNode;
// Not necessarily a TriangleMeshNode
if (other != null) {
int a = node.SharedEdge(other);
if (a != -1) uses[a] = true;
}
}
// Loop through all edges on the node
for (int j = 0; j < 3; j++) {
// The edge is not shared with any other node
// I.e it is an exterior edge on the mesh
if (!uses[j]) {
var i1 = j;
var i2 = (j+1) % node.GetVertexCount();
outline[node.GetVertexIndex(i1)] = node.GetVertexIndex(i2);
hasInEdge.Add(node.GetVertexIndex(i2));
vertexPositions[node.GetVertexIndex(i1)] = node.GetVertex(i1);
vertexPositions[node.GetVertexIndex(i2)] = node.GetVertex(i2);
}
}
}
});
Polygon.TraceContours(outline, hasInEdge, (chain, cycle) => {
List<Int3> vertices = ListPool<Int3>.Claim();
for (int i = 0; i < chain.Count; i++) vertices.Add(vertexPositions[chain[i]]);
results(vertices, cycle);
});
}
#if !ASTAR_NO_GRID_GRAPH
/// <summary>
/// Finds all contours of a collection of nodes in a grid graph.
///
/// <code>
/// var grid = AstarPath.active.data.gridGraph;
///
/// // Find all contours in the graph and draw them using debug lines
/// GraphUtilities.GetContours(grid, vertices => {
/// for (int i = 0; i < vertices.Length; i++) {
/// Debug.DrawLine(vertices[i], vertices[(i+1)%vertices.Length], Color.red, 4);
/// }
/// }, 0);
/// </code>
///
/// In the image below you can see the contour of a graph.
/// [Open online documentation to see images]
///
/// In the image below you can see the contour of just a part of a grid graph (when the nodes parameter is supplied)
/// [Open online documentation to see images]
///
/// Contour of a hexagon graph
/// [Open online documentation to see images]
///
/// See: <see cref="GetContours(NavGraph)"/>
/// </summary>
/// <param name="grid">The grid to find the contours of</param>
/// <param name="callback">The callback will be called once for every contour that is found with the vertices of the contour. The contour always forms a cycle.</param>
/// <param name="yMergeThreshold">Contours will be simplified if the y coordinates for adjacent vertices differ by no more than this value.</param>
/// <param name="nodes">Only these nodes will be searched. If this parameter is null then all nodes in the grid graph will be searched.</param>
public static void GetContours (GridGraph grid, System.Action<Vector3[]> callback, float yMergeThreshold, GridNodeBase[] nodes = null) {
// Set of all allowed nodes or null if all nodes are allowed
HashSet<GridNodeBase> nodeSet = nodes != null ? new HashSet<GridNodeBase>(nodes) : null;
// Use all nodes if the nodes parameter is null
nodes = nodes ?? grid.nodes;
int[] neighbourXOffsets = grid.neighbourXOffsets;
int[] neighbourZOffsets = grid.neighbourZOffsets;
var neighbourIndices = grid.neighbours == NumNeighbours.Six ? GridGraph.hexagonNeighbourIndices : new [] { 0, 1, 2, 3 };
var offsetMultiplier = grid.neighbours == NumNeighbours.Six ? 1/3f : 0.5f;
if (nodes != null) {
var trace = ListPool<Vector3>.Claim();
var seenStates = new HashSet<int>();
for (int i = 0; i < nodes.Length; i++) {
var startNode = nodes[i];
// The third check is a fast check for if the node has connections in all grid directions, if it has then we can skip processing it (unless the nodes parameter was used in which case we have to handle the edge cases)
if (startNode != null && startNode.Walkable && (!startNode.HasConnectionsToAllEightNeighbours || nodeSet != null)) {
for (int startDir = 0; startDir < neighbourIndices.Length; startDir++) {
int startState = (startNode.NodeIndex << 4) | startDir;
// Check if there is an obstacle in that direction
var startNeighbour = startNode.GetNeighbourAlongDirection(neighbourIndices[startDir]);
if ((startNeighbour == null || (nodeSet != null && !nodeSet.Contains(startNeighbour))) && !seenStates.Contains(startState)) {
// Start tracing a contour here
trace.ClearFast();
int dir = startDir;
GridNodeBase node = startNode;
while (true) {
int state = (node.NodeIndex << 4) | dir;
if (state == startState && trace.Count > 0) {
break;
}
seenStates.Add(state);
var neighbour = node.GetNeighbourAlongDirection(neighbourIndices[dir]);
if (neighbour == null || (nodeSet != null && !nodeSet.Contains(neighbour))) {
// Draw edge
var d0 = neighbourIndices[dir];
dir = (dir + 1) % neighbourIndices.Length;
var d1 = neighbourIndices[dir];
// Position in graph space of the vertex
Vector3 graphSpacePos = new Vector3(node.XCoordinateInGrid + 0.5f, 0, node.ZCoordinateInGrid + 0.5f);
// Offset along diagonal to get the correct XZ coordinates
graphSpacePos.x += (neighbourXOffsets[d0] + neighbourXOffsets[d1]) * offsetMultiplier;
graphSpacePos.z += (neighbourZOffsets[d0] + neighbourZOffsets[d1]) * offsetMultiplier;
graphSpacePos.y = grid.transform.InverseTransform((Vector3)node.position).y;
if (trace.Count >= 2) {
var v0 = trace[trace.Count-2];
var v1 = trace[trace.Count-1];
var v1d = v1 - v0;
var v2d = graphSpacePos - v0;
// Replace the previous point if it is colinear with the point just before it and just after it (the current point), because that point wouldn't add much information, but it would add CPU overhead
if (((Mathf.Abs(v1d.x) > 0.01f || Mathf.Abs(v2d.x) > 0.01f) && (Mathf.Abs(v1d.z) > 0.01f || Mathf.Abs(v2d.z) > 0.01f)) || (Mathf.Abs(v1d.y) > yMergeThreshold || Mathf.Abs(v2d.y) > yMergeThreshold)) {
trace.Add(graphSpacePos);
} else {
trace[trace.Count-1] = graphSpacePos;
}
} else {
trace.Add(graphSpacePos);
}
} else {
// Move
node = neighbour;
dir = (dir + neighbourIndices.Length/2 + 1) % neighbourIndices.Length;
}
}
// Simplify the contour a bit around the start point.
// Otherwise we might return a cycle which was not as simplified as possible and the number of vertices
// would depend on where in the cycle the algorithm started to traverse the contour.
if (trace.Count >= 3) {
var v0 = trace[trace.Count-2];
var v1 = trace[trace.Count-1];
var v1d = v1 - v0;
var v2d = trace[0] - v0;
// Replace the previous point if it is colinear with the point just before it and just after it (the current point), because that point wouldn't add much information, but it would add CPU overhead
if (!(((Mathf.Abs(v1d.x) > 0.01f || Mathf.Abs(v2d.x) > 0.01f) && (Mathf.Abs(v1d.z) > 0.01f || Mathf.Abs(v2d.z) > 0.01f)) || (Mathf.Abs(v1d.y) > yMergeThreshold || Mathf.Abs(v2d.y) > yMergeThreshold))) {
trace.RemoveAt(trace.Count - 1);
}
}
if (trace.Count >= 3) {
var v0 = trace[trace.Count-1];
var v1 = trace[0];
var v1d = v1 - v0;
var v2d = trace[1] - v0;
// Replace the previous point if it is colinear with the point just before it and just after it (the current point), because that point wouldn't add much information, but it would add CPU overhead
if (!(((Mathf.Abs(v1d.x) > 0.01f || Mathf.Abs(v2d.x) > 0.01f) && (Mathf.Abs(v1d.z) > 0.01f || Mathf.Abs(v2d.z) > 0.01f)) || (Mathf.Abs(v1d.y) > yMergeThreshold || Mathf.Abs(v2d.y) > yMergeThreshold))) {
trace.RemoveAt(0);
}
}
var result = trace.ToArray();
grid.transform.Transform(result);
callback(result);
}
}
}
}
ListPool<Vector3>.Release(ref trace);
}
}
#endif
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fd178834bf6c54cdb8fc5f76a039a91c
timeCreated: 1502889881
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,346 @@
using System.Collections.Generic;
using Pathfinding.Util;
using Pathfinding.Serialization;
using System.Linq;
using UnityEngine;
#if UNITY_5_5_OR_NEWER
using UnityEngine.Profiling;
#endif
namespace Pathfinding {
/// <summary>
/// Holds a hierarchical graph to speed up certain pathfinding queries.
///
/// A common type of query that needs to be very fast is on the form 'is this node reachable from this other node'.
/// This is for example used when picking the end node of a path. The end node is determined as the closest node to the end point
/// that can be reached from the start node.
///
/// This data structure's primary purpose is to keep track of which connected component each node is contained in, in order to make such queries fast.
///
/// See: https://en.wikipedia.org/wiki/Connected_component_(graph_theory)
///
/// A connected component is a set of nodes such that there is a valid path between every pair of nodes in that set.
/// Thus the query above can simply be answered by checking if they are in the same connected component.
/// The connected component is exposed on nodes as the <see cref="Pathfinding.GraphNode.Area"/> property and on this class using the <see cref="GetArea"/> method.
///
/// In the image below (showing a 200x200 grid graph) each connected component is colored using a separate color.
/// The actual color doesn't signify anything in particular however, only that they are different.
/// [Open online documentation to see images]
///
/// Prior to version 4.2 the connected components were just a number stored on each node, and when a graph was updated
/// the connected components were completely recalculated. This can be done relatively efficiently using a flood filling
/// algorithm (see https://en.wikipedia.org/wiki/Flood_fill) however it still requires a pass through every single node
/// which can be quite costly on larger graphs.
///
/// This class instead builds a much smaller graph that still respects the same connectivity as the original graph.
/// Each node in this hierarchical graph represents a larger number of real nodes that are one single connected component.
/// Take a look at the image below for an example. In the image each color is a separate hierarchical node, and the black connections go between the center of each hierarchical node.
///
/// [Open online documentation to see images]
///
/// With the hierarchical graph, the connected components can be calculated by flood filling the hierarchical graph instead of the real graph.
/// Then when we need to know which connected component a node belongs to, we look up the connected component of the hierarchical node the node belongs to.
///
/// The benefit is not immediately obvious. The above is just a bit more complicated way to accomplish the same thing. However the real benefit comes when updating the graph.
/// When the graph is updated, all hierarchical nodes which contain any node that was affected by the update is removed completely and then once all have been removed new hierarchical nodes are recalculated in their place.
/// Once this is done the connected components of the whole graph can be updated by flood filling only the hierarchical graph. Since the hierarchical graph is vastly smaller than the real graph, this is significantly faster.
///
/// [Open online documentation to see videos]
///
/// So finally using all of this, the connected components of the graph can be recalculated very quickly as the graph is updated.
/// The effect of this grows larger the larger the graph is, and the smaller the graph update is. Making a small update to a 1000x1000 grid graph is on the order of 40 times faster with these optimizations.
/// When scanning a graph or making updates to the whole graph at the same time there is however no speed boost. In fact due to the extra complexity it is a bit slower, however after profiling the extra time seems to be mostly insignificant compared to the rest of the cost of scanning the graph.
///
/// [Open online documentation to see videos]
///
/// See: <see cref="Pathfinding.PathUtilities.IsPathPossible"/>
/// See: <see cref="Pathfinding.NNConstraint"/>
/// See: <see cref="Pathfinding.GraphNode.Area"/>
/// </summary>
public class HierarchicalGraph {
const int Tiling = 16;
const int MaxChildrenPerNode = Tiling * Tiling;
const int MinChildrenPerNode = MaxChildrenPerNode/2;
List<GraphNode>[] children = new List<GraphNode>[0];
List<int>[] connections = new List<int>[0];
int[] areas = new int[0];
byte[] dirty = new byte[0];
public int version { get; private set; }
public System.Action onConnectedComponentsChanged;
System.Action<GraphNode> connectionCallback;
Queue<GraphNode> temporaryQueue = new Queue<GraphNode>();
List<GraphNode> currentChildren = null;
List<int> currentConnections = null;
int currentHierarchicalNodeIndex;
Stack<int> temporaryStack = new Stack<int>();
int numDirtyNodes = 0;
GraphNode[] dirtyNodes = new GraphNode[128];
Stack<int> freeNodeIndices = new Stack<int>();
int gizmoVersion = 0;
public HierarchicalGraph () {
// Cache this callback to avoid allocating a new one every time the FindHierarchicalNodeChildren method is called.
// It is a big ugly to have to use member variables for the state information in that method, but I see no better way.
connectionCallback = (GraphNode neighbour) => {
var hIndex = neighbour.HierarchicalNodeIndex;
if (hIndex == 0) {
if (currentChildren.Count < MaxChildrenPerNode && neighbour.Walkable /* && (((GridNode)currentChildren[0]).XCoordinateInGrid/Tiling == ((GridNode)neighbour).XCoordinateInGrid/Tiling) && (((GridNode)currentChildren[0]).ZCoordinateInGrid/Tiling == ((GridNode)neighbour).ZCoordinateInGrid/Tiling)*/) {
neighbour.HierarchicalNodeIndex = currentHierarchicalNodeIndex;
temporaryQueue.Enqueue(neighbour);
currentChildren.Add(neighbour);
}
} else if (hIndex != currentHierarchicalNodeIndex && !currentConnections.Contains(hIndex)) {
// The Contains call can in theory be very slow as an hierarchical node may be adjacent to an arbitrary number of nodes.
// However in practice due to how the nodes are constructed they will only be adjacent to a smallish (≈4-6) number of other nodes.
// So a Contains call will be much faster than say a Set lookup.
currentConnections.Add(hIndex);
}
};
Grow();
}
void Grow () {
var newChildren = new List<GraphNode>[System.Math.Max(64, children.Length*2)];
var newConnections = new List<int>[newChildren.Length];
var newAreas = new int[newChildren.Length];
var newDirty = new byte[newChildren.Length];
children.CopyTo(newChildren, 0);
connections.CopyTo(newConnections, 0);
areas.CopyTo(newAreas, 0);
dirty.CopyTo(newDirty, 0);
for (int i = children.Length; i < newChildren.Length; i++) {
newChildren[i] = ListPool<GraphNode>.Claim(MaxChildrenPerNode);
newConnections[i] = new List<int>();
if (i > 0) freeNodeIndices.Push(i);
}
children = newChildren;
connections = newConnections;
areas = newAreas;
dirty = newDirty;
}
int GetHierarchicalNodeIndex () {
if (freeNodeIndices.Count == 0) Grow();
return freeNodeIndices.Pop();
}
internal void OnCreatedNode (GraphNode node) {
if (node.NodeIndex >= dirtyNodes.Length) {
var newDirty = new GraphNode[System.Math.Max(node.NodeIndex + 1, dirtyNodes.Length*2)];
dirtyNodes.CopyTo(newDirty, 0);
dirtyNodes = newDirty;
}
AddDirtyNode(node);
}
internal void AddDirtyNode (GraphNode node) {
if (!node.IsHierarchicalNodeDirty) {
node.IsHierarchicalNodeDirty = true;
// While the dirtyNodes array is guaranteed to be large enough to hold all nodes in the graphs
// the array may also end up containing many destroyed nodes. This can in rare cases cause it to go out of bounds.
// In that case we need to go through the array and filter out any destroyed nodes while making sure to mark their
// corresponding hierarchical nodes as being dirty.
if (numDirtyNodes < dirtyNodes.Length) {
dirtyNodes[numDirtyNodes] = node;
numDirtyNodes++;
} else {
int maxIndex = 0;
for (int i = numDirtyNodes - 1; i >= 0; i--) {
if (dirtyNodes[i].Destroyed) {
numDirtyNodes--;
dirty[dirtyNodes[i].HierarchicalNodeIndex] = 1;
dirtyNodes[i] = dirtyNodes[numDirtyNodes];
dirtyNodes[numDirtyNodes] = null;
} else {
maxIndex = System.Math.Max(maxIndex, dirtyNodes[i].NodeIndex);
}
}
if (numDirtyNodes >= dirtyNodes.Length) throw new System.Exception("Failed to compactify dirty nodes array. This should not happen. " + maxIndex + " " + numDirtyNodes + " " + dirtyNodes.Length);
AddDirtyNode(node);
}
}
}
public int NumConnectedComponents { get; private set; }
/// <summary>Get the connected component index of a hierarchical node</summary>
public uint GetConnectedComponent (int hierarchicalNodeIndex) {
return (uint)areas[hierarchicalNodeIndex];
}
void RemoveHierarchicalNode (int hierarchicalNode, bool removeAdjacentSmallNodes) {
freeNodeIndices.Push(hierarchicalNode);
var conns = connections[hierarchicalNode];
for (int i = 0; i < conns.Count; i++) {
var adjacentHierarchicalNode = conns[i];
// If dirty, this node will be removed later anyway, so don't bother doing anything with it.
if (dirty[adjacentHierarchicalNode] != 0) continue;
if (removeAdjacentSmallNodes && children[adjacentHierarchicalNode].Count < MinChildrenPerNode) {
dirty[adjacentHierarchicalNode] = 2;
RemoveHierarchicalNode(adjacentHierarchicalNode, false);
} else {
// Remove the connection from the other node to this node as we are removing this node.
connections[adjacentHierarchicalNode].Remove(hierarchicalNode);
}
}
conns.Clear();
var nodeChildren = children[hierarchicalNode];
for (int i = 0; i < nodeChildren.Count; i++) {
AddDirtyNode(nodeChildren[i]);
}
nodeChildren.ClearFast();
}
/// <summary>Recalculate the hierarchical graph and the connected components if any nodes have been marked as dirty</summary>
public void RecalculateIfNecessary () {
if (numDirtyNodes > 0) {
Profiler.BeginSample("Recalculate Connected Components");
for (int i = 0; i < numDirtyNodes; i++) {
dirty[dirtyNodes[i].HierarchicalNodeIndex] = 1;
}
// Remove all hierarchical nodes and then build new hierarchical nodes in their place
// which take into account the new graph data.
for (int i = 1; i < dirty.Length; i++) {
if (dirty[i] == 1) RemoveHierarchicalNode(i, true);
}
for (int i = 1; i < dirty.Length; i++) dirty[i] = 0;
for (int i = 0; i < numDirtyNodes; i++) {
dirtyNodes[i].HierarchicalNodeIndex = 0;
}
for (int i = 0; i < numDirtyNodes; i++) {
var node = dirtyNodes[i];
// Be nice to the GC
dirtyNodes[i] = null;
node.IsHierarchicalNodeDirty = false;
if (node.HierarchicalNodeIndex == 0 && node.Walkable && !node.Destroyed) {
FindHierarchicalNodeChildren(GetHierarchicalNodeIndex(), node);
}
}
numDirtyNodes = 0;
// Recalculate the connected components of the hierarchical nodes
FloodFill();
Profiler.EndSample();
gizmoVersion++;
}
}
/// <summary>
/// Recalculate everything from scratch.
/// This is primarily to be used for legacy code for compatibility reasons, not for any new code.
///
/// See: <see cref="RecalculateIfNecessary"/>
/// </summary>
public void RecalculateAll () {
AstarPath.active.data.GetNodes(node => AddDirtyNode(node));
RecalculateIfNecessary();
}
/// <summary>Flood fills the graph of hierarchical nodes and assigns the same area ID to all hierarchical nodes that are in the same connected component</summary>
void FloodFill () {
for (int i = 0; i < areas.Length; i++) areas[i] = 0;
Stack<int> stack = temporaryStack;
int currentArea = 0;
for (int i = 1; i < areas.Length; i++) {
// Already taken care of
if (areas[i] != 0) continue;
currentArea++;
areas[i] = currentArea;
stack.Push(i);
while (stack.Count > 0) {
int node = stack.Pop();
var conns = connections[node];
for (int j = conns.Count - 1; j >= 0; j--) {
var otherNode = conns[j];
// Note: slightly important that this is != currentArea and not != 0 in case there are some connected, but not stongly connected components in the graph (this will happen in only veeery few types of games)
if (areas[otherNode] != currentArea) {
areas[otherNode] = currentArea;
stack.Push(otherNode);
}
}
}
}
NumConnectedComponents = System.Math.Max(1, currentArea + 1);
version++;
}
/// <summary>Run a BFS out from a start node and assign up to MaxChildrenPerNode nodes to the specified hierarchical node which are not already assigned to another hierarchical node</summary>
void FindHierarchicalNodeChildren (int hierarchicalNode, GraphNode startNode) {
// Set some state for the connectionCallback delegate to use
currentChildren = children[hierarchicalNode];
currentConnections = connections[hierarchicalNode];
currentHierarchicalNodeIndex = hierarchicalNode;
var que = temporaryQueue;
que.Enqueue(startNode);
startNode.HierarchicalNodeIndex = hierarchicalNode;
currentChildren.Add(startNode);
while (que.Count > 0) {
que.Dequeue().GetConnections(connectionCallback);
}
for (int i = 0; i < currentConnections.Count; i++) {
connections[currentConnections[i]].Add(hierarchicalNode);
}
que.Clear();
}
public void OnDrawGizmos (Pathfinding.Util.RetainedGizmos gizmos) {
var hasher = new Pathfinding.Util.RetainedGizmos.Hasher(AstarPath.active);
hasher.AddHash(gizmoVersion);
if (!gizmos.Draw(hasher)) {
var builder = ObjectPool<RetainedGizmos.Builder>.Claim();
var centers = ArrayPool<UnityEngine.Vector3>.Claim(areas.Length);
for (int i = 0; i < areas.Length; i++) {
Int3 center = Int3.zero;
var childs = children[i];
if (childs.Count > 0) {
for (int j = 0; j < childs.Count; j++) center += childs[j].position;
center /= childs.Count;
centers[i] = (UnityEngine.Vector3)center;
}
}
for (int i = 0; i < areas.Length; i++) {
if (children[i].Count > 0) {
for (int j = 0; j < connections[i].Count; j++) {
if (connections[i][j] > i) {
builder.DrawLine(centers[i], centers[connections[i][j]], UnityEngine.Color.black);
}
}
}
}
builder.Submit(gizmos, hasher);
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: aa5d6e4a6adb04d4ca9b7e735addcbd5
timeCreated: 1535050640
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,314 @@
using UnityEngine;
namespace Pathfinding {
/// <summary>Holds a coordinate in integers</summary>
public struct Int3 : System.IEquatable<Int3> {
public int x;
public int y;
public int z;
//These should be set to the same value (only PrecisionFactor should be 1 divided by Precision)
/// <summary>
/// Precision for the integer coordinates.
/// One world unit is divided into [value] pieces. A value of 1000 would mean millimeter precision, a value of 1 would mean meter precision (assuming 1 world unit = 1 meter).
/// This value affects the maximum coordinates for nodes as well as how large the cost values are for moving between two nodes.
/// A higher value means that you also have to set all penalty values to a higher value to compensate since the normal cost of moving will be higher.
/// </summary>
public const int Precision = 1000;
/// <summary><see cref="Precision"/> as a float</summary>
public const float FloatPrecision = 1000F;
/// <summary>1 divided by <see cref="Precision"/></summary>
public const float PrecisionFactor = 0.001F;
public static Int3 zero { get { return new Int3(); } }
public Int3 (Vector3 position) {
x = (int)System.Math.Round(position.x*FloatPrecision);
y = (int)System.Math.Round(position.y*FloatPrecision);
z = (int)System.Math.Round(position.z*FloatPrecision);
}
public Int3 (int _x, int _y, int _z) {
x = _x;
y = _y;
z = _z;
}
public static bool operator == (Int3 lhs, Int3 rhs) {
return lhs.x == rhs.x &&
lhs.y == rhs.y &&
lhs.z == rhs.z;
}
public static bool operator != (Int3 lhs, Int3 rhs) {
return lhs.x != rhs.x ||
lhs.y != rhs.y ||
lhs.z != rhs.z;
}
public static explicit operator Int3 (Vector3 ob) {
return new Int3(
(int)System.Math.Round(ob.x*FloatPrecision),
(int)System.Math.Round(ob.y*FloatPrecision),
(int)System.Math.Round(ob.z*FloatPrecision)
);
}
public static explicit operator Vector3 (Int3 ob) {
return new Vector3(ob.x*PrecisionFactor, ob.y*PrecisionFactor, ob.z*PrecisionFactor);
}
public static Int3 operator - (Int3 lhs, Int3 rhs) {
lhs.x -= rhs.x;
lhs.y -= rhs.y;
lhs.z -= rhs.z;
return lhs;
}
public static Int3 operator - (Int3 lhs) {
lhs.x = -lhs.x;
lhs.y = -lhs.y;
lhs.z = -lhs.z;
return lhs;
}
public static Int3 operator + (Int3 lhs, Int3 rhs) {
lhs.x += rhs.x;
lhs.y += rhs.y;
lhs.z += rhs.z;
return lhs;
}
public static Int3 operator * (Int3 lhs, int rhs) {
lhs.x *= rhs;
lhs.y *= rhs;
lhs.z *= rhs;
return lhs;
}
public static Int3 operator * (Int3 lhs, float rhs) {
lhs.x = (int)System.Math.Round(lhs.x * rhs);
lhs.y = (int)System.Math.Round(lhs.y * rhs);
lhs.z = (int)System.Math.Round(lhs.z * rhs);
return lhs;
}
public static Int3 operator * (Int3 lhs, double rhs) {
lhs.x = (int)System.Math.Round(lhs.x * rhs);
lhs.y = (int)System.Math.Round(lhs.y * rhs);
lhs.z = (int)System.Math.Round(lhs.z * rhs);
return lhs;
}
public static Int3 operator / (Int3 lhs, float rhs) {
lhs.x = (int)System.Math.Round(lhs.x / rhs);
lhs.y = (int)System.Math.Round(lhs.y / rhs);
lhs.z = (int)System.Math.Round(lhs.z / rhs);
return lhs;
}
public int this[int i] {
get {
return i == 0 ? x : (i == 1 ? y : z);
}
set {
if (i == 0) x = value;
else if (i == 1) y = value;
else z = value;
}
}
/// <summary>Angle between the vectors in radians</summary>
public static float Angle (Int3 lhs, Int3 rhs) {
double cos = Dot(lhs, rhs)/ ((double)lhs.magnitude*(double)rhs.magnitude);
cos = cos < -1 ? -1 : (cos > 1 ? 1 : cos);
return (float)System.Math.Acos(cos);
}
public static int Dot (Int3 lhs, Int3 rhs) {
return
lhs.x * rhs.x +
lhs.y * rhs.y +
lhs.z * rhs.z;
}
public static long DotLong (Int3 lhs, Int3 rhs) {
return
(long)lhs.x * (long)rhs.x +
(long)lhs.y * (long)rhs.y +
(long)lhs.z * (long)rhs.z;
}
/// <summary>
/// Normal in 2D space (XZ).
/// Equivalent to Cross(this, Int3(0,1,0) )
/// except that the Y coordinate is left unchanged with this operation.
/// </summary>
public Int3 Normal2D () {
return new Int3(z, y, -x);
}
/// <summary>
/// Returns the magnitude of the vector. The magnitude is the 'length' of the vector from 0,0,0 to this point. Can be used for distance calculations:
/// <code> Debug.Log ("Distance between 3,4,5 and 6,7,8 is: "+(new Int3(3,4,5) - new Int3(6,7,8)).magnitude); </code>
/// </summary>
public float magnitude {
get {
//It turns out that using doubles is just as fast as using ints with Mathf.Sqrt. And this can also handle larger numbers (possibly with small errors when using huge numbers)!
double _x = x;
double _y = y;
double _z = z;
return (float)System.Math.Sqrt(_x*_x+_y*_y+_z*_z);
}
}
/// <summary>
/// Magnitude used for the cost between two nodes. The default cost between two nodes can be calculated like this:
/// <code> int cost = (node1.position-node2.position).costMagnitude; </code>
///
/// This is simply the magnitude, rounded to the nearest integer
/// </summary>
public int costMagnitude {
get {
return (int)System.Math.Round(magnitude);
}
}
/// <summary>The squared magnitude of the vector</summary>
public float sqrMagnitude {
get {
double _x = x;
double _y = y;
double _z = z;
return (float)(_x*_x+_y*_y+_z*_z);
}
}
/// <summary>The squared magnitude of the vector</summary>
public long sqrMagnitudeLong {
get {
long _x = x;
long _y = y;
long _z = z;
return (_x*_x+_y*_y+_z*_z);
}
}
public static implicit operator string (Int3 obj) {
return obj.ToString();
}
/// <summary>Returns a nicely formatted string representing the vector</summary>
public override string ToString () {
return "( "+x+", "+y+", "+z+")";
}
public override bool Equals (System.Object obj) {
if (obj == null) return false;
var rhs = (Int3)obj;
return x == rhs.x &&
y == rhs.y &&
z == rhs.z;
}
#region IEquatable implementation
public bool Equals (Int3 other) {
return x == other.x && y == other.y && z == other.z;
}
#endregion
public override int GetHashCode () {
return x*73856093 ^ y*19349663 ^ z*83492791;
}
}
/// <summary>Two Dimensional Integer Coordinate Pair</summary>
public struct Int2 : System.IEquatable<Int2> {
public int x;
public int y;
public Int2 (int x, int y) {
this.x = x;
this.y = y;
}
public long sqrMagnitudeLong {
get {
return (long)x*(long)x+(long)y*(long)y;
}
}
public static Int2 operator + (Int2 a, Int2 b) {
return new Int2(a.x+b.x, a.y+b.y);
}
public static Int2 operator - (Int2 a, Int2 b) {
return new Int2(a.x-b.x, a.y-b.y);
}
public static bool operator == (Int2 a, Int2 b) {
return a.x == b.x && a.y == b.y;
}
public static bool operator != (Int2 a, Int2 b) {
return a.x != b.x || a.y != b.y;
}
/// <summary>Dot product of the two coordinates</summary>
public static long DotLong (Int2 a, Int2 b) {
return (long)a.x*(long)b.x + (long)a.y*(long)b.y;
}
public override bool Equals (System.Object o) {
if (o == null) return false;
var rhs = (Int2)o;
return x == rhs.x && y == rhs.y;
}
#region IEquatable implementation
public bool Equals (Int2 other) {
return x == other.x && y == other.y;
}
#endregion
public override int GetHashCode () {
return x*49157+y*98317;
}
public static Int2 Min (Int2 a, Int2 b) {
return new Int2(System.Math.Min(a.x, b.x), System.Math.Min(a.y, b.y));
}
public static Int2 Max (Int2 a, Int2 b) {
return new Int2(System.Math.Max(a.x, b.x), System.Math.Max(a.y, b.y));
}
public static Int2 FromInt3XZ (Int3 o) {
return new Int2(o.x, o.z);
}
public static Int3 ToInt3XZ (Int2 o) {
return new Int3(o.x, 0, o.y);
}
public override string ToString () {
return "("+x+", " +y+")";
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5826dd4a1809b448291582cd06deadc1
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,211 @@
#if !UNITY_EDITOR
// Extra optimizations when not running in the editor, but less error checking
#define ASTAR_OPTIMIZE_POOLING
#endif
using System;
using System.Collections.Generic;
namespace Pathfinding.Util {
/// <summary>
/// Lightweight List Pool.
/// Handy class for pooling lists of type T.
///
/// Usage:
/// - Claim a new list using <code> List<SomeClass> foo = ListPool<SomeClass>.Claim (); </code>
/// - Use it and do stuff with it
/// - Release it with <code> ListPool<SomeClass>.Release (foo); </code>
///
/// You do not need to clear the list before releasing it.
/// After you have released a list, you should never use it again, if you do use it, you will
/// mess things up quite badly in the worst case.
///
/// \since Version 3.2
/// See: Pathfinding.Util.StackPool
/// </summary>
public static class ListPool<T> {
/// <summary>Internal pool</summary>
static readonly List<List<T> > pool = new List<List<T> >();
#if !ASTAR_NO_POOLING
static readonly List<List<T> > largePool = new List<List<T> >();
static readonly HashSet<List<T> > inPool = new HashSet<List<T> >();
#endif
/// <summary>
/// When requesting a list with a specified capacity, search max this many lists in the pool before giving up.
/// Must be greater or equal to one.
/// </summary>
const int MaxCapacitySearchLength = 8;
const int LargeThreshold = 5000;
const int MaxLargePoolSize = 8;
/// <summary>
/// Claim a list.
/// Returns a pooled list if any are in the pool.
/// Otherwise it creates a new one.
/// After usage, this list should be released using the Release function (though not strictly necessary).
/// </summary>
public static List<T> Claim () {
#if ASTAR_NO_POOLING
return new List<T>();
#else
lock (pool) {
if (pool.Count > 0) {
List<T> ls = pool[pool.Count-1];
pool.RemoveAt(pool.Count-1);
inPool.Remove(ls);
return ls;
}
return new List<T>();
}
#endif
}
static int FindCandidate (List<List<T> > pool, int capacity) {
// Loop through the last MaxCapacitySearchLength items
// and check if any item has a capacity greater or equal to the one that
// is desired. If so return it.
// Otherwise take the largest one or if there are no lists in the pool
// then allocate a new one with the desired capacity
List<T> list = null;
int listIndex = -1;
for (int i = 0; i < pool.Count && i < MaxCapacitySearchLength; i++) {
// ith last item
var candidate = pool[pool.Count-1-i];
// Find the largest list that is not too large (arbitrary decision to try to prevent some memory bloat if the list was not just a temporary list).
if ((list == null || candidate.Capacity > list.Capacity) && candidate.Capacity < capacity*16) {
list = candidate;
listIndex = pool.Count-1-i;
if (list.Capacity >= capacity) {
return listIndex;
}
}
}
return listIndex;
}
/// <summary>
/// Claim a list with minimum capacity
/// Returns a pooled list if any are in the pool.
/// Otherwise it creates a new one.
/// After usage, this list should be released using the Release function (though not strictly necessary).
/// A subset of the pool will be searched for a list with a high enough capacity and one will be returned
/// if possible, otherwise the list with the largest capacity found will be returned.
/// </summary>
public static List<T> Claim (int capacity) {
#if ASTAR_NO_POOLING
return new List<T>(capacity);
#else
lock (pool) {
var currentPool = pool;
var listIndex = FindCandidate(pool, capacity);
if (capacity > LargeThreshold) {
var largeListIndex = FindCandidate(largePool, capacity);
if (largeListIndex != -1) {
currentPool = largePool;
listIndex = largeListIndex;
}
}
if (listIndex == -1) {
return new List<T>(capacity);
} else {
var list = currentPool[listIndex];
// Swap current item and last item to enable a more efficient removal
inPool.Remove(list);
currentPool[listIndex] = currentPool[currentPool.Count-1];
currentPool.RemoveAt(currentPool.Count-1);
return list;
}
}
#endif
}
/// <summary>
/// Makes sure the pool contains at least count pooled items with capacity size.
/// This is good if you want to do all allocations at start.
/// </summary>
public static void Warmup (int count, int size) {
lock (pool) {
var tmp = new List<T>[count];
for (int i = 0; i < count; i++) tmp[i] = Claim(size);
for (int i = 0; i < count; i++) Release(tmp[i]);
}
}
/// <summary>
/// Releases a list and sets the variable to null.
/// After the list has been released it should not be used anymore.
///
/// \throws System.InvalidOperationException
/// Releasing a list when it has already been released will cause an exception to be thrown.
///
/// See: <see cref="Claim"/>
/// </summary>
public static void Release (ref List<T> list) {
Release(list);
list = null;
}
/// <summary>
/// Releases a list.
/// After the list has been released it should not be used anymore.
///
/// \throws System.InvalidOperationException
/// Releasing a list when it has already been released will cause an exception to be thrown.
///
/// See: <see cref="Claim"/>
/// </summary>
public static void Release (List<T> list) {
#if !ASTAR_NO_POOLING
list.ClearFast();
lock (pool) {
#if !ASTAR_OPTIMIZE_POOLING
if (!inPool.Add(list)) {
throw new InvalidOperationException("You are trying to pool a list twice. Please make sure that you only pool it once.");
}
#endif
if (list.Capacity > LargeThreshold) {
largePool.Add(list);
// Remove the list which was used the longest time ago from the pool if it
// exceeds the maximum size as it probably just contributes to memory bloat
if (largePool.Count > MaxLargePoolSize) {
largePool.RemoveAt(0);
}
} else {
pool.Add(list);
}
}
#endif
}
/// <summary>
/// Clears the pool for lists of this type.
/// This is an O(n) operation, where n is the number of pooled lists.
/// </summary>
public static void Clear () {
lock (pool) {
#if !ASTAR_OPTIMIZE_POOLING && !ASTAR_NO_POOLING
inPool.Clear();
#endif
pool.Clear();
}
}
/// <summary>Number of lists of this type in the pool</summary>
public static int GetSize () {
// No lock required since int writes are atomic
return pool.Count;
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2b76b7593907b44d9a6ef1b186fee0a7
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,174 @@
using UnityEngine;
using System.Collections;
namespace Pathfinding.Util {
public static class MovementUtilities {
/// <summary>
/// Clamps the velocity to the max speed and optionally the forwards direction.
///
/// Note that all vectors are 2D vectors, not 3D vectors.
///
/// Returns: The clamped velocity in world units per second.
/// </summary>
/// <param name="velocity">Desired velocity of the character. In world units per second.</param>
/// <param name="maxSpeed">Max speed of the character. In world units per second.</param>
/// <param name="slowdownFactor">Value between 0 and 1 which determines how much slower the character should move than normal.
/// Normally 1 but should go to 0 when the character approaches the end of the path.</param>
/// <param name="slowWhenNotFacingTarget">Prevent the velocity from being too far away from the forward direction of the character
/// and slow the character down if the desired velocity is not in the same direction as the forward vector.</param>
/// <param name="forward">Forward direction of the character. Used together with the slowWhenNotFacingTarget parameter.</param>
public static Vector2 ClampVelocity (Vector2 velocity, float maxSpeed, float slowdownFactor, bool slowWhenNotFacingTarget, Vector2 forward) {
// Max speed to use for this frame
var currentMaxSpeed = maxSpeed * slowdownFactor;
// Check if the agent should slow down in case it is not facing the direction it wants to move in
if (slowWhenNotFacingTarget && (forward.x != 0 || forward.y != 0)) {
float currentSpeed;
var normalizedVelocity = VectorMath.Normalize(velocity, out currentSpeed);
float dot = Vector2.Dot(normalizedVelocity, forward);
// Lower the speed when the character's forward direction is not pointing towards the desired velocity
// 1 when velocity is in the same direction as forward
// 0.2 when they point in the opposite directions
float directionSpeedFactor = Mathf.Clamp(dot+0.707f, 0.2f, 1.0f);
currentMaxSpeed *= directionSpeedFactor;
currentSpeed = Mathf.Min(currentSpeed, currentMaxSpeed);
// Angle between the forwards direction of the character and our desired velocity
float angle = Mathf.Acos(Mathf.Clamp(dot, -1, 1));
// Clamp the angle to 20 degrees
// We cannot keep the velocity exactly in the forwards direction of the character
// because we use the rotation to determine in which direction to rotate and if
// the velocity would always be in the forwards direction of the character then
// the character would never rotate.
// Allow larger angles when near the end of the path to prevent oscillations.
angle = Mathf.Min(angle, (20f + 180f*(1 - slowdownFactor*slowdownFactor))*Mathf.Deg2Rad);
float sin = Mathf.Sin(angle);
float cos = Mathf.Cos(angle);
// Determine if we should rotate clockwise or counter-clockwise to move towards the current velocity
sin *= Mathf.Sign(normalizedVelocity.x*forward.y - normalizedVelocity.y*forward.x);
// Rotate the #forward vector by #angle radians
// The rotation is done using an inlined rotation matrix.
// See https://en.wikipedia.org/wiki/Rotation_matrix
return new Vector2(forward.x*cos + forward.y*sin, forward.y*cos - forward.x*sin) * currentSpeed;
} else {
return Vector2.ClampMagnitude(velocity, currentMaxSpeed);
}
}
/// <summary>Calculate an acceleration to move deltaPosition units and get there with approximately a velocity of targetVelocity</summary>
public static Vector2 CalculateAccelerationToReachPoint (Vector2 deltaPosition, Vector2 targetVelocity, Vector2 currentVelocity, float forwardsAcceleration, float rotationSpeed, float maxSpeed, Vector2 forwardsVector) {
// Guard against div by zero
if (forwardsAcceleration <= 0) return Vector2.zero;
float currentSpeed = currentVelocity.magnitude;
// Convert rotation speed to an acceleration
// See https://en.wikipedia.org/wiki/Centripetal_force
var sidewaysAcceleration = currentSpeed * rotationSpeed * Mathf.Deg2Rad;
// To avoid weird behaviour when the rotation speed is very low we allow the agent to accelerate sideways without rotating much
// if the rotation speed is very small. Also guards against division by zero.
sidewaysAcceleration = Mathf.Max(sidewaysAcceleration, forwardsAcceleration);
// Transform coordinates to local space where +X is the forwards direction
// This is essentially equivalent to Transform.InverseTransformDirection.
deltaPosition = VectorMath.ComplexMultiplyConjugate(deltaPosition, forwardsVector);
targetVelocity = VectorMath.ComplexMultiplyConjugate(targetVelocity, forwardsVector);
currentVelocity = VectorMath.ComplexMultiplyConjugate(currentVelocity, forwardsVector);
float ellipseSqrFactorX = 1 / (forwardsAcceleration*forwardsAcceleration);
float ellipseSqrFactorY = 1 / (sidewaysAcceleration*sidewaysAcceleration);
// If the target velocity is zero we can use a more fancy approach
// and calculate a nicer path.
// In particular, this is the case at the end of the path.
if (targetVelocity == Vector2.zero) {
// Run a binary search over the time to get to the target point.
float mn = 0.01f;
float mx = 10;
while (mx - mn > 0.01f) {
var time = (mx + mn) * 0.5f;
// Given that we want to move deltaPosition units from out current position, that our current velocity is given
// and that when we reach the target we want our velocity to be zero. Also assume that our acceleration will
// vary linearly during the slowdown. Then we can calculate what our acceleration should be during this frame.
//{ t = time
//{ deltaPosition = vt + at^2/2 + qt^3/6
//{ 0 = v + at + qt^2/2
//{ solve for a
// a = acceleration vector
// q = derivative of the acceleration vector
var a = (6*deltaPosition - 4*time*currentVelocity)/(time*time);
var q = 6*(time*currentVelocity - 2*deltaPosition)/(time*time*time);
// Make sure the acceleration is not greater than our maximum allowed acceleration.
// If it is we increase the time we want to use to get to the target
// and if it is not, we decrease the time to get there faster.
// Since the acceleration is described by acceleration = a + q*t
// we only need to check at t=0 and t=time.
// Note that the acceleration limit is described by an ellipse, not a circle.
var nextA = a + q*time;
if (a.x*a.x*ellipseSqrFactorX + a.y*a.y*ellipseSqrFactorY > 1.0f || nextA.x*nextA.x*ellipseSqrFactorX + nextA.y*nextA.y*ellipseSqrFactorY > 1.0f) {
mn = time;
} else {
mx = time;
}
}
var finalAcceleration = (6*deltaPosition - 4*mx*currentVelocity)/(mx*mx);
// Boosting
{
// The trajectory calculated above has a tendency to use very wide arcs
// and that does unfortunately not look particularly good in some cases.
// Here we amplify the component of the acceleration that is perpendicular
// to our current velocity. This will make the agent turn towards the
// target quicker.
// How much amplification to use. Value is unitless.
const float Boost = 1;
finalAcceleration.y *= 1 + Boost;
// Clamp the velocity to the maximum acceleration.
// Note that the maximum acceleration constraint is shaped like an ellipse, not like a circle.
float ellipseMagnitude = finalAcceleration.x*finalAcceleration.x*ellipseSqrFactorX + finalAcceleration.y*finalAcceleration.y*ellipseSqrFactorY;
if (ellipseMagnitude > 1.0f) finalAcceleration /= Mathf.Sqrt(ellipseMagnitude);
}
return VectorMath.ComplexMultiply(finalAcceleration, forwardsVector);
} else {
// Here we try to move towards the next waypoint which has been modified slightly using our
// desired velocity at that point so that the agent will more smoothly round the corner.
// How much to strive for making sure we reach the target point with the target velocity. Unitless.
const float TargetVelocityWeight = 0.5f;
// Limit to how much to care about the target velocity. Value is in seconds.
// This prevents the character from moving away from the path too much when the target point is far away
const float TargetVelocityWeightLimit = 1.5f;
float targetSpeed;
var normalizedTargetVelocity = VectorMath.Normalize(targetVelocity, out targetSpeed);
var distance = deltaPosition.magnitude;
var targetPoint = deltaPosition - normalizedTargetVelocity * System.Math.Min(TargetVelocityWeight * distance * targetSpeed / (currentSpeed + targetSpeed), maxSpeed*TargetVelocityWeightLimit);
// How quickly the agent will try to reach the velocity that we want it to have.
// We need this to prevent oscillations and jitter which is what happens if
// we let the constant go towards zero. Value is in seconds.
const float TimeToReachDesiredVelocity = 0.1f;
// TODO: Clamp to ellipse using more accurate acceleration (use rotation speed as well)
var finalAcceleration = (targetPoint.normalized*maxSpeed - currentVelocity) * (1f/TimeToReachDesiredVelocity);
// Clamp the velocity to the maximum acceleration.
// Note that the maximum acceleration constraint is shaped like an ellipse, not like a circle.
float ellipseMagnitude = finalAcceleration.x*finalAcceleration.x*ellipseSqrFactorX + finalAcceleration.y*finalAcceleration.y*ellipseSqrFactorY;
if (ellipseMagnitude > 1.0f) finalAcceleration /= Mathf.Sqrt(ellipseMagnitude);
return VectorMath.ComplexMultiply(finalAcceleration, forwardsVector);
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0a6cffd9895f94907aa43f18b0904587
timeCreated: 1490097740
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,157 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Pathfinding {
using Pathfinding.Util;
/// <summary>
/// Connects two nodes with a direct connection.
/// It is not possible to detect this link when following a path (which may be good or bad), for that you can use NodeLink2.
///
/// [Open online documentation to see images]
///
/// See: editing-graphs (view in online documentation for working links)
/// </summary>
[AddComponentMenu("Pathfinding/Link")]
[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_node_link.php")]
public class NodeLink : GraphModifier {
/// <summary>End position of the link</summary>
public Transform end;
/// <summary>
/// The connection will be this times harder/slower to traverse.
/// Note that values lower than one will not always make the pathfinder choose this path instead of another path even though this one should
/// lead to a lower total cost unless you also adjust the Heuristic Scale in A* Inspector -> Settings -> Pathfinding or disable the heuristic altogether.
/// </summary>
public float costFactor = 1.0f;
/// <summary>Make a one-way connection</summary>
public bool oneWay = false;
/// <summary>Delete existing connection instead of adding one</summary>
public bool deleteConnection = false;
public Transform Start {
get { return transform; }
}
public Transform End {
get { return end; }
}
public override void OnPostScan () {
if (AstarPath.active.isScanning) {
InternalOnPostScan();
} else {
AstarPath.active.AddWorkItem(new AstarWorkItem(force => {
InternalOnPostScan();
return true;
}));
}
}
public void InternalOnPostScan () {
Apply();
}
public override void OnGraphsPostUpdate () {
if (!AstarPath.active.isScanning) {
AstarPath.active.AddWorkItem(new AstarWorkItem(force => {
InternalOnPostScan();
return true;
}));
}
}
public virtual void Apply () {
if (Start == null || End == null || AstarPath.active == null) return;
GraphNode startNode = AstarPath.active.GetNearest(Start.position).node;
GraphNode endNode = AstarPath.active.GetNearest(End.position).node;
if (startNode == null || endNode == null) return;
if (deleteConnection) {
startNode.RemoveConnection(endNode);
if (!oneWay)
endNode.RemoveConnection(startNode);
} else {
uint cost = (uint)System.Math.Round((startNode.position-endNode.position).costMagnitude*costFactor);
startNode.AddConnection(endNode, cost);
if (!oneWay)
endNode.AddConnection(startNode, cost);
}
}
public void OnDrawGizmos () {
if (Start == null || End == null) return;
Draw.Gizmos.Bezier(Start.position, End.position, deleteConnection ? Color.red : Color.green);
}
#if UNITY_EDITOR
[UnityEditor.MenuItem("Edit/Pathfinding/Link Pair %&l")]
public static void LinkObjects () {
Transform[] tfs = Selection.transforms;
if (tfs.Length == 2) {
LinkObjects(tfs[0], tfs[1], false);
}
SceneView.RepaintAll();
}
[UnityEditor.MenuItem("Edit/Pathfinding/Unlink Pair %&u")]
public static void UnlinkObjects () {
Transform[] tfs = Selection.transforms;
if (tfs.Length == 2) {
LinkObjects(tfs[0], tfs[1], true);
}
SceneView.RepaintAll();
}
[UnityEditor.MenuItem("Edit/Pathfinding/Delete Links on Selected %&b")]
public static void DeleteLinks () {
Transform[] tfs = Selection.transforms;
for (int i = 0; i < tfs.Length; i++) {
NodeLink[] conns = tfs[i].GetComponents<NodeLink>();
for (int j = 0; j < conns.Length; j++) DestroyImmediate(conns[j]);
}
SceneView.RepaintAll();
}
public static void LinkObjects (Transform a, Transform b, bool removeConnection) {
NodeLink connecting = null;
NodeLink[] conns = a.GetComponents<NodeLink>();
for (int i = 0; i < conns.Length; i++) {
if (conns[i].end == b) {
connecting = conns[i];
break;
}
}
conns = b.GetComponents<NodeLink>();
for (int i = 0; i < conns.Length; i++) {
if (conns[i].end == a) {
connecting = conns[i];
break;
}
}
if (removeConnection) {
if (connecting != null) DestroyImmediate(connecting);
} else {
if (connecting == null) {
connecting = a.gameObject.AddComponent<NodeLink>();
connecting.end = b;
} else {
connecting.deleteConnection = !connecting.deleteConnection;
}
}
}
#endif
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ca327ce4e754a4597a70fb963758f8bd
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,308 @@
using UnityEngine;
using System.Collections.Generic;
namespace Pathfinding {
using Pathfinding.Util;
/// <summary>
/// Connects two nodes via two intermediate point nodes.
/// In contrast to the NodeLink component, this link type will not connect the nodes directly
/// instead it will create two point nodes at the start and end position of this link and connect
/// through those nodes.
///
/// If the closest node to this object is called A and the closest node to the end transform is called
/// D, then it will create one point node at this object's position (call it B) and one point node at
/// the position of the end transform (call it C), it will then connect A to B, B to C and C to D.
///
/// This link type is possible to detect while following since it has these special point nodes in the middle.
/// The link corresponding to one of those intermediate nodes can be retrieved using the <see cref="GetNodeLink"/> method
/// which can be of great use if you want to, for example, play a link specific animation when reaching the link.
///
/// See: The example scene RecastExample2 contains a few links which you can take a look at to see how they are used.
/// </summary>
[AddComponentMenu("Pathfinding/Link2")]
[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_node_link2.php")]
public class NodeLink2 : GraphModifier {
protected static Dictionary<GraphNode, NodeLink2> reference = new Dictionary<GraphNode, NodeLink2>();
public static NodeLink2 GetNodeLink (GraphNode node) {
NodeLink2 v;
reference.TryGetValue(node, out v);
return v;
}
/// <summary>End position of the link</summary>
public Transform end;
/// <summary>
/// The connection will be this times harder/slower to traverse.
/// Note that values lower than 1 will not always make the pathfinder choose this path instead of another path even though this one should
/// lead to a lower total cost unless you also adjust the Heuristic Scale in A* Inspector -> Settings -> Pathfinding or disable the heuristic altogether.
/// </summary>
public float costFactor = 1.0f;
/// <summary>Make a one-way connection</summary>
public bool oneWay = false;
public Transform StartTransform {
get { return transform; }
}
public Transform EndTransform {
get { return end; }
}
public PointNode startNode { get; private set; }
public PointNode endNode { get; private set; }
GraphNode connectedNode1, connectedNode2;
Vector3 clamped1, clamped2;
bool postScanCalled = false;
[System.Obsolete("Use startNode instead (lowercase s)")]
public GraphNode StartNode {
get { return startNode; }
}
[System.Obsolete("Use endNode instead (lowercase e)")]
public GraphNode EndNode {
get { return endNode; }
}
public override void OnPostScan () {
InternalOnPostScan();
}
public void InternalOnPostScan () {
if (EndTransform == null || StartTransform == null) return;
#if ASTAR_NO_POINT_GRAPH
throw new System.Exception("Point graph is not included. Check your A* optimization settings.");
#else
if (AstarPath.active.data.pointGraph == null) {
var graph = AstarPath.active.data.AddGraph(typeof(PointGraph)) as PointGraph;
graph.name = "PointGraph (used for node links)";
}
if (startNode != null && startNode.Destroyed) {
reference.Remove(startNode);
startNode = null;
}
if (endNode != null && endNode.Destroyed) {
reference.Remove(endNode);
endNode = null;
}
// Create new nodes on the point graph
if (startNode == null) startNode = AstarPath.active.data.pointGraph.AddNode((Int3)StartTransform.position);
if (endNode == null) endNode = AstarPath.active.data.pointGraph.AddNode((Int3)EndTransform.position);
connectedNode1 = null;
connectedNode2 = null;
if (startNode == null || endNode == null) {
startNode = null;
endNode = null;
return;
}
postScanCalled = true;
reference[startNode] = this;
reference[endNode] = this;
Apply(true);
#endif
}
public override void OnGraphsPostUpdate () {
// Don't bother running it now since OnPostScan will be called later anyway
if (AstarPath.active.isScanning)
return;
if (connectedNode1 != null && connectedNode1.Destroyed) {
connectedNode1 = null;
}
if (connectedNode2 != null && connectedNode2.Destroyed) {
connectedNode2 = null;
}
if (!postScanCalled) {
OnPostScan();
} else {
Apply(false);
}
}
protected override void OnEnable () {
base.OnEnable();
#if !ASTAR_NO_POINT_GRAPH
if (Application.isPlaying && AstarPath.active != null && AstarPath.active.data != null && AstarPath.active.data.pointGraph != null && !AstarPath.active.isScanning) {
// Call OnGraphsPostUpdate as soon as possible when it is safe to update the graphs
AstarPath.active.AddWorkItem(OnGraphsPostUpdate);
}
#endif
}
protected override void OnDisable () {
base.OnDisable();
postScanCalled = false;
if (startNode != null) reference.Remove(startNode);
if (endNode != null) reference.Remove(endNode);
if (startNode != null && endNode != null) {
startNode.RemoveConnection(endNode);
endNode.RemoveConnection(startNode);
if (connectedNode1 != null && connectedNode2 != null) {
startNode.RemoveConnection(connectedNode1);
connectedNode1.RemoveConnection(startNode);
endNode.RemoveConnection(connectedNode2);
connectedNode2.RemoveConnection(endNode);
}
}
}
void RemoveConnections (GraphNode node) {
//TODO, might be better to replace connection
node.ClearConnections(true);
}
[ContextMenu("Recalculate neighbours")]
void ContextApplyForce () {
if (Application.isPlaying) {
Apply(true);
}
}
public void Apply (bool forceNewCheck) {
//TODO
//This function assumes that connections from the n1,n2 nodes never need to be removed in the future (e.g because the nodes move or something)
NNConstraint nn = NNConstraint.None;
int graph = (int)startNode.GraphIndex;
//Search all graphs but the one which start and end nodes are on
nn.graphMask = ~(1 << graph);
startNode.SetPosition((Int3)StartTransform.position);
endNode.SetPosition((Int3)EndTransform.position);
RemoveConnections(startNode);
RemoveConnections(endNode);
uint cost = (uint)Mathf.RoundToInt(((Int3)(StartTransform.position-EndTransform.position)).costMagnitude*costFactor);
startNode.AddConnection(endNode, cost);
endNode.AddConnection(startNode, cost);
if (connectedNode1 == null || forceNewCheck) {
var info = AstarPath.active.GetNearest(StartTransform.position, nn);
connectedNode1 = info.node;
clamped1 = info.position;
}
if (connectedNode2 == null || forceNewCheck) {
var info = AstarPath.active.GetNearest(EndTransform.position, nn);
connectedNode2 = info.node;
clamped2 = info.position;
}
if (connectedNode2 == null || connectedNode1 == null) return;
//Add connections between nodes, or replace old connections if existing
connectedNode1.AddConnection(startNode, (uint)Mathf.RoundToInt(((Int3)(clamped1 - StartTransform.position)).costMagnitude*costFactor));
if (!oneWay) connectedNode2.AddConnection(endNode, (uint)Mathf.RoundToInt(((Int3)(clamped2 - EndTransform.position)).costMagnitude*costFactor));
if (!oneWay) startNode.AddConnection(connectedNode1, (uint)Mathf.RoundToInt(((Int3)(clamped1 - StartTransform.position)).costMagnitude*costFactor));
endNode.AddConnection(connectedNode2, (uint)Mathf.RoundToInt(((Int3)(clamped2 - EndTransform.position)).costMagnitude*costFactor));
}
private readonly static Color GizmosColor = new Color(206.0f/255.0f, 136.0f/255.0f, 48.0f/255.0f, 0.5f);
private readonly static Color GizmosColorSelected = new Color(235.0f/255.0f, 123.0f/255.0f, 32.0f/255.0f, 1.0f);
public virtual void OnDrawGizmosSelected () {
OnDrawGizmos(true);
}
public void OnDrawGizmos () {
OnDrawGizmos(false);
}
public void OnDrawGizmos (bool selected) {
Color color = selected ? GizmosColorSelected : GizmosColor;
if (StartTransform != null) {
Draw.Gizmos.CircleXZ(StartTransform.position, 0.4f, color);
}
if (EndTransform != null) {
Draw.Gizmos.CircleXZ(EndTransform.position, 0.4f, color);
}
if (StartTransform != null && EndTransform != null) {
Draw.Gizmos.Bezier(StartTransform.position, EndTransform.position, color);
if (selected) {
Vector3 cross = Vector3.Cross(Vector3.up, (EndTransform.position-StartTransform.position)).normalized;
Draw.Gizmos.Bezier(StartTransform.position+cross*0.1f, EndTransform.position+cross*0.1f, color);
Draw.Gizmos.Bezier(StartTransform.position-cross*0.1f, EndTransform.position-cross*0.1f, color);
}
}
}
internal static void SerializeReferences (Pathfinding.Serialization.GraphSerializationContext ctx) {
var links = GetModifiersOfType<NodeLink2>();
ctx.writer.Write(links.Count);
foreach (var link in links) {
ctx.writer.Write(link.uniqueID);
ctx.SerializeNodeReference(link.startNode);
ctx.SerializeNodeReference(link.endNode);
ctx.SerializeNodeReference(link.connectedNode1);
ctx.SerializeNodeReference(link.connectedNode2);
ctx.SerializeVector3(link.clamped1);
ctx.SerializeVector3(link.clamped2);
ctx.writer.Write(link.postScanCalled);
}
}
internal static void DeserializeReferences (Pathfinding.Serialization.GraphSerializationContext ctx) {
int count = ctx.reader.ReadInt32();
for (int i = 0; i < count; i++) {
var linkID = ctx.reader.ReadUInt64();
var startNode = ctx.DeserializeNodeReference();
var endNode = ctx.DeserializeNodeReference();
var connectedNode1 = ctx.DeserializeNodeReference();
var connectedNode2 = ctx.DeserializeNodeReference();
var clamped1 = ctx.DeserializeVector3();
var clamped2 = ctx.DeserializeVector3();
var postScanCalled = ctx.reader.ReadBoolean();
GraphModifier link;
if (usedIDs.TryGetValue(linkID, out link)) {
var link2 = link as NodeLink2;
if (link2 != null) {
if (startNode != null) reference[startNode] = link2;
if (endNode != null) reference[endNode] = link2;
// If any nodes happened to be registered right now
if (link2.startNode != null) reference.Remove(link2.startNode);
if (link2.endNode != null) reference.Remove(link2.endNode);
link2.startNode = startNode as PointNode;
link2.endNode = endNode as PointNode;
link2.connectedNode1 = connectedNode1;
link2.connectedNode2 = connectedNode2;
link2.postScanCalled = postScanCalled;
link2.clamped1 = clamped1;
link2.clamped2 = clamped2;
} else {
throw new System.Exception("Tried to deserialize a NodeLink2 reference, but the link was not of the correct type or it has been destroyed.\nIf a NodeLink2 is included in serialized graph data, the same NodeLink2 component must be present in the scene when loading the graph data.");
}
} else {
throw new System.Exception("Tried to deserialize a NodeLink2 reference, but the link could not be found in the scene.\nIf a NodeLink2 is included in serialized graph data, the same NodeLink2 component must be present in the scene when loading the graph data.");
}
}
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bd2cfff5dfa8c4244aa00fea9675adb2
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,313 @@
using UnityEngine;
using System.Collections.Generic;
namespace Pathfinding {
using Pathfinding.Util;
public class NodeLink3Node : PointNode {
public NodeLink3 link;
public Vector3 portalA;
public Vector3 portalB;
public NodeLink3Node (AstarPath active) : base(active) {}
public override bool GetPortal (GraphNode other, List<Vector3> left, List<Vector3> right, bool backwards) {
if (this.connections.Length < 2) return false;
if (this.connections.Length != 2) throw new System.Exception("Invalid NodeLink3Node. Expected 2 connections, found " + this.connections.Length);
if (left != null) {
left.Add(portalA);
right.Add(portalB);
}
return true;
}
public GraphNode GetOther (GraphNode a) {
if (this.connections.Length < 2) return null;
if (this.connections.Length != 2) throw new System.Exception("Invalid NodeLink3Node. Expected 2 connections, found " + this.connections.Length);
return a == connections[0].node ? (connections[1].node as NodeLink3Node).GetOtherInternal(this) : (connections[0].node as NodeLink3Node).GetOtherInternal(this);
}
GraphNode GetOtherInternal (GraphNode a) {
if (this.connections.Length < 2) return null;
return a == connections[0].node ? connections[1].node : connections[0].node;
}
}
/// <summary>
/// Connects two TriangleMeshNodes (recast/navmesh graphs) as if they had shared an edge.
/// Note: Usually you do not want to use this type of link, you want to use NodeLink2 or NodeLink (sorry for the not so descriptive names).
/// </summary>
[AddComponentMenu("Pathfinding/Link3")]
[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_node_link3.php")]
public class NodeLink3 : GraphModifier {
protected static Dictionary<GraphNode, NodeLink3> reference = new Dictionary<GraphNode, NodeLink3>();
public static NodeLink3 GetNodeLink (GraphNode node) {
NodeLink3 v;
reference.TryGetValue(node, out v);
return v;
}
/// <summary>End position of the link</summary>
public Transform end;
/// <summary>
/// The connection will be this times harder/slower to traverse.
/// Note that values lower than one will not always make the pathfinder choose this path instead of another path even though this one should
/// lead to a lower total cost unless you also adjust the Heuristic Scale in A* Inspector -> Settings -> Pathfinding or disable the heuristic altogether.
/// </summary>
public float costFactor = 1.0f;
/// <summary>Make a one-way connection</summary>
public bool oneWay = false;
public Transform StartTransform {
get { return transform; }
}
public Transform EndTransform {
get { return end; }
}
NodeLink3Node startNode;
NodeLink3Node endNode;
MeshNode connectedNode1, connectedNode2;
Vector3 clamped1, clamped2;
bool postScanCalled = false;
public GraphNode StartNode {
get { return startNode; }
}
public GraphNode EndNode {
get { return endNode; }
}
public override void OnPostScan () {
if (AstarPath.active.isScanning) {
InternalOnPostScan();
} else {
AstarPath.active.AddWorkItem(new AstarWorkItem(force => {
InternalOnPostScan();
return true;
}));
}
}
public void InternalOnPostScan () {
#if !ASTAR_NO_POINT_GRAPH
if (AstarPath.active.data.pointGraph == null) {
AstarPath.active.data.AddGraph(typeof(PointGraph));
}
//Get nearest nodes from the first point graph, assuming both start and end transforms are nodes
startNode = AstarPath.active.data.pointGraph.AddNode(new NodeLink3Node(AstarPath.active), (Int3)StartTransform.position);
startNode.link = this;
endNode = AstarPath.active.data.pointGraph.AddNode(new NodeLink3Node(AstarPath.active), (Int3)EndTransform.position);
endNode.link = this;
#else
throw new System.Exception("Point graphs are not included. Check your A* Optimization settings.");
#endif
connectedNode1 = null;
connectedNode2 = null;
if (startNode == null || endNode == null) {
startNode = null;
endNode = null;
return;
}
postScanCalled = true;
reference[startNode] = this;
reference[endNode] = this;
Apply(true);
}
public override void OnGraphsPostUpdate () {
if (!AstarPath.active.isScanning) {
if (connectedNode1 != null && connectedNode1.Destroyed) {
connectedNode1 = null;
}
if (connectedNode2 != null && connectedNode2.Destroyed) {
connectedNode2 = null;
}
if (!postScanCalled) {
OnPostScan();
} else {
//OnPostScan will also call this method
Apply(false);
}
}
}
protected override void OnEnable () {
base.OnEnable();
#if !ASTAR_NO_POINT_GRAPH
if (Application.isPlaying && AstarPath.active != null && AstarPath.active.data != null && AstarPath.active.data.pointGraph != null) {
OnGraphsPostUpdate();
}
#endif
}
protected override void OnDisable () {
base.OnDisable();
postScanCalled = false;
if (startNode != null) reference.Remove(startNode);
if (endNode != null) reference.Remove(endNode);
if (startNode != null && endNode != null) {
startNode.RemoveConnection(endNode);
endNode.RemoveConnection(startNode);
if (connectedNode1 != null && connectedNode2 != null) {
startNode.RemoveConnection(connectedNode1);
connectedNode1.RemoveConnection(startNode);
endNode.RemoveConnection(connectedNode2);
connectedNode2.RemoveConnection(endNode);
}
}
}
void RemoveConnections (GraphNode node) {
//TODO, might be better to replace connection
node.ClearConnections(true);
}
[ContextMenu("Recalculate neighbours")]
void ContextApplyForce () {
if (Application.isPlaying) {
Apply(true);
}
}
public void Apply (bool forceNewCheck) {
//TODO
//This function assumes that connections from the n1,n2 nodes never need to be removed in the future (e.g because the nodes move or something)
NNConstraint nn = NNConstraint.None;
nn.distanceXZ = true;
int graph = (int)startNode.GraphIndex;
//Search all graphs but the one which start and end nodes are on
nn.graphMask = ~(1 << graph);
bool same = true;
{
var info = AstarPath.active.GetNearest(StartTransform.position, nn);
same &= info.node == connectedNode1 && info.node != null;
connectedNode1 = info.node as MeshNode;
clamped1 = info.position;
if (connectedNode1 != null) Debug.DrawRay((Vector3)connectedNode1.position, Vector3.up*5, Color.red);
}
{
var info = AstarPath.active.GetNearest(EndTransform.position, nn);
same &= info.node == connectedNode2 && info.node != null;
connectedNode2 = info.node as MeshNode;
clamped2 = info.position;
if (connectedNode2 != null) Debug.DrawRay((Vector3)connectedNode2.position, Vector3.up*5, Color.cyan);
}
if (connectedNode2 == null || connectedNode1 == null) return;
startNode.SetPosition((Int3)StartTransform.position);
endNode.SetPosition((Int3)EndTransform.position);
if (same && !forceNewCheck) return;
RemoveConnections(startNode);
RemoveConnections(endNode);
uint cost = (uint)Mathf.RoundToInt(((Int3)(StartTransform.position-EndTransform.position)).costMagnitude*costFactor);
startNode.AddConnection(endNode, cost);
endNode.AddConnection(startNode, cost);
Int3 dir = connectedNode2.position - connectedNode1.position;
for (int a = 0; a < connectedNode1.GetVertexCount(); a++) {
Int3 va1 = connectedNode1.GetVertex(a);
Int3 va2 = connectedNode1.GetVertex((a+1) % connectedNode1.GetVertexCount());
if (Int3.DotLong((va2-va1).Normal2D(), dir) > 0) continue;
for (int b = 0; b < connectedNode2.GetVertexCount(); b++) {
Int3 vb1 = connectedNode2.GetVertex(b);
Int3 vb2 = connectedNode2.GetVertex((b+1) % connectedNode2.GetVertexCount());
if (Int3.DotLong((vb2-vb1).Normal2D(), dir) < 0) continue;
if (Int3.Angle((vb2-vb1), (va2-va1)) > (170.0/360.0f)*Mathf.PI*2) {
float t1 = 0;
float t2 = 1;
t2 = System.Math.Min(t2, VectorMath.ClosestPointOnLineFactor(va1, va2, vb1));
t1 = System.Math.Max(t1, VectorMath.ClosestPointOnLineFactor(va1, va2, vb2));
if (t2 < t1) {
Debug.LogError("Something went wrong! " + t1 + " " + t2 + " " + va1 + " " + va2 + " " + vb1 + " " + vb2+"\nTODO, how can this happen?");
} else {
Vector3 pa = (Vector3)(va2-va1)*t1 + (Vector3)va1;
Vector3 pb = (Vector3)(va2-va1)*t2 + (Vector3)va1;
startNode.portalA = pa;
startNode.portalB = pb;
endNode.portalA = pb;
endNode.portalB = pa;
//Add connections between nodes, or replace old connections if existing
connectedNode1.AddConnection(startNode, (uint)Mathf.RoundToInt(((Int3)(clamped1 - StartTransform.position)).costMagnitude*costFactor));
connectedNode2.AddConnection(endNode, (uint)Mathf.RoundToInt(((Int3)(clamped2 - EndTransform.position)).costMagnitude*costFactor));
startNode.AddConnection(connectedNode1, (uint)Mathf.RoundToInt(((Int3)(clamped1 - StartTransform.position)).costMagnitude*costFactor));
endNode.AddConnection(connectedNode2, (uint)Mathf.RoundToInt(((Int3)(clamped2 - EndTransform.position)).costMagnitude*costFactor));
return;
}
}
}
}
}
private readonly static Color GizmosColor = new Color(206.0f/255.0f, 136.0f/255.0f, 48.0f/255.0f, 0.5f);
private readonly static Color GizmosColorSelected = new Color(235.0f/255.0f, 123.0f/255.0f, 32.0f/255.0f, 1.0f);
public virtual void OnDrawGizmosSelected () {
OnDrawGizmos(true);
}
public void OnDrawGizmos () {
OnDrawGizmos(false);
}
public void OnDrawGizmos (bool selected) {
Color col = selected ? GizmosColorSelected : GizmosColor;
if (StartTransform != null) {
Draw.Gizmos.CircleXZ(StartTransform.position, 0.4f, col);
}
if (EndTransform != null) {
Draw.Gizmos.CircleXZ(EndTransform.position, 0.4f, col);
}
if (StartTransform != null && EndTransform != null) {
Draw.Gizmos.Bezier(StartTransform.position, EndTransform.position, col);
if (selected) {
Vector3 cross = Vector3.Cross(Vector3.up, (EndTransform.position-StartTransform.position)).normalized;
Draw.Gizmos.Bezier(StartTransform.position+cross*0.1f, EndTransform.position+cross*0.1f, col);
Draw.Gizmos.Bezier(StartTransform.position-cross*0.1f, EndTransform.position-cross*0.1f, col);
}
}
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3850d0dc2bead45568e6b5bbcc011606
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,131 @@
#if !UNITY_EDITOR
// Extra optimizations when not running in the editor, but less error checking
#define ASTAR_OPTIMIZE_POOLING
#endif
using System;
using System.Collections.Generic;
namespace Pathfinding.Util {
public interface IAstarPooledObject {
void OnEnterPool();
}
/// <summary>
/// Lightweight object Pool for IAstarPooledObject.
/// Handy class for pooling objects of type T which implements the IAstarPooledObject interface.
///
/// Usage:
/// - Claim a new object using <code> SomeClass foo = ObjectPool<SomeClass>.Claim (); </code>
/// - Use it and do stuff with it
/// - Release it with <code> ObjectPool<SomeClass>.Release (foo); </code>
///
/// After you have released a object, you should never use it again.
///
/// \since Version 3.2
/// Version: Since 3.7.6 this class is thread safe
/// See: Pathfinding.Util.ListPool
/// See: ObjectPoolSimple
/// </summary>
public static class ObjectPool<T> where T : class, IAstarPooledObject, new(){
public static T Claim () {
return ObjectPoolSimple<T>.Claim();
}
public static void Release (ref T obj) {
obj.OnEnterPool();
ObjectPoolSimple<T>.Release(ref obj);
}
}
/// <summary>
/// Lightweight object Pool.
/// Handy class for pooling objects of type T.
///
/// Usage:
/// - Claim a new object using <code> SomeClass foo = ObjectPool<SomeClass>.Claim (); </code>
/// - Use it and do stuff with it
/// - Release it with <code> ObjectPool<SomeClass>.Release (foo); </code>
///
/// After you have released a object, you should never use it again.
///
/// \since Version 3.2
/// Version: Since 3.7.6 this class is thread safe
/// See: Pathfinding.Util.ListPool
/// See: ObjectPool
/// </summary>
public static class ObjectPoolSimple<T> where T : class, new(){
/// <summary>Internal pool</summary>
static List<T> pool = new List<T>();
#if !ASTAR_NO_POOLING
static readonly HashSet<T> inPool = new HashSet<T>();
#endif
/// <summary>
/// Claim a object.
/// Returns a pooled object if any are in the pool.
/// Otherwise it creates a new one.
/// After usage, this object should be released using the Release function (though not strictly necessary).
/// </summary>
public static T Claim () {
#if ASTAR_NO_POOLING
return new T();
#else
lock (pool) {
if (pool.Count > 0) {
T ls = pool[pool.Count-1];
pool.RemoveAt(pool.Count-1);
inPool.Remove(ls);
return ls;
} else {
return new T();
}
}
#endif
}
/// <summary>
/// Releases an object.
/// After the object has been released it should not be used anymore.
/// The variable will be set to null to prevent silly mistakes.
///
/// \throws System.InvalidOperationException
/// Releasing an object when it has already been released will cause an exception to be thrown.
/// However enabling ASTAR_OPTIMIZE_POOLING will prevent this check.
///
/// See: Claim
/// </summary>
public static void Release (ref T obj) {
#if !ASTAR_NO_POOLING
lock (pool) {
#if !ASTAR_OPTIMIZE_POOLING
if (!inPool.Add(obj)) {
throw new InvalidOperationException("You are trying to pool an object twice. Please make sure that you only pool it once.");
}
#endif
pool.Add(obj);
}
#endif
obj = null;
}
/// <summary>
/// Clears the pool for objects of this type.
/// This is an O(n) operation, where n is the number of pooled objects.
/// </summary>
public static void Clear () {
lock (pool) {
#if !ASTAR_OPTIMIZE_POOLING && !ASTAR_NO_POOLING
inPool.Clear();
#endif
pool.Clear();
}
}
/// <summary>Number of objects of this type in the pool</summary>
public static int GetSize () {
return pool.Count;
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 10837c5b030bd47a2a0e6e213fea0868
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,206 @@
using UnityEngine;
using System.Collections.Generic;
namespace Pathfinding.Util {
/// <summary>Interpolates along a sequence of points</summary>
public class PathInterpolator {
List<Vector3> path;
float distanceToSegmentStart;
float currentDistance;
float currentSegmentLength = float.PositiveInfinity;
float totalDistance = float.PositiveInfinity;
/// <summary>Current position</summary>
public virtual Vector3 position {
get {
float t = currentSegmentLength > 0.0001f ? (currentDistance - distanceToSegmentStart) / currentSegmentLength : 0f;
return Vector3.Lerp(path[segmentIndex], path[segmentIndex+1], t);
}
}
/// <summary>Last point in the path</summary>
public Vector3 endPoint {
get {
return path[path.Count-1];
}
}
/// <summary>Tangent of the curve at the current position</summary>
public Vector3 tangent {
get {
return path[segmentIndex+1] - path[segmentIndex];
}
}
/// <summary>Remaining distance until the end of the path</summary>
public float remainingDistance {
get {
return totalDistance - distance;
}
set {
distance = totalDistance - value;
}
}
/// <summary>Traversed distance from the start of the path</summary>
public float distance {
get {
return currentDistance;
}
set {
currentDistance = value;
while (currentDistance < distanceToSegmentStart && segmentIndex > 0) PrevSegment();
while (currentDistance > distanceToSegmentStart + currentSegmentLength && segmentIndex < path.Count - 2) NextSegment();
}
}
/// <summary>
/// Current segment.
/// The start and end points of the segment are path[value] and path[value+1].
/// </summary>
public int segmentIndex { get; private set; }
/// <summary>
/// True if this instance has a path set.
/// See: SetPath
/// </summary>
public bool valid {
get {
return path != null;
}
}
/// <summary>Appends the remaining path between <see cref="position"/> and <see cref="endPoint"/> to buffer</summary>
public void GetRemainingPath (List<Vector3> buffer) {
if (!valid) throw new System.Exception("PathInterpolator is not valid");
buffer.Add(position);
for (int i = segmentIndex+1; i < path.Count; i++) {
buffer.Add(path[i]);
}
}
/// <summary>
/// Set the path to interpolate along.
/// This will reset all interpolation variables.
/// </summary>
public void SetPath (List<Vector3> path) {
this.path = path;
currentDistance = 0;
segmentIndex = 0;
distanceToSegmentStart = 0;
if (path == null) {
totalDistance = float.PositiveInfinity;
currentSegmentLength = float.PositiveInfinity;
return;
}
if (path.Count < 2) throw new System.ArgumentException("Path must have a length of at least 2");
currentSegmentLength = (path[1] - path[0]).magnitude;
totalDistance = 0f;
var prev = path[0];
for (int i = 1; i < path.Count; i++) {
var current = path[i];
totalDistance += (current - prev).magnitude;
prev = current;
}
}
/// <summary>Move to the specified segment and move a fraction of the way to the next segment</summary>
public void MoveToSegment (int index, float fractionAlongSegment) {
if (path == null) return;
if (index < 0 || index >= path.Count - 1) throw new System.ArgumentOutOfRangeException("index");
while (segmentIndex > index) PrevSegment();
while (segmentIndex < index) NextSegment();
distance = distanceToSegmentStart + Mathf.Clamp01(fractionAlongSegment) * currentSegmentLength;
}
/// <summary>Move as close as possible to the specified point</summary>
public void MoveToClosestPoint (Vector3 point) {
if (path == null) return;
float bestDist = float.PositiveInfinity;
float bestFactor = 0f;
int bestIndex = 0;
for (int i = 0; i < path.Count-1; i++) {
float factor = VectorMath.ClosestPointOnLineFactor(path[i], path[i+1], point);
Vector3 closest = Vector3.Lerp(path[i], path[i+1], factor);
float dist = (point - closest).sqrMagnitude;
if (dist < bestDist) {
bestDist = dist;
bestFactor = factor;
bestIndex = i;
}
}
MoveToSegment(bestIndex, bestFactor);
}
public void MoveToLocallyClosestPoint (Vector3 point, bool allowForwards = true, bool allowBackwards = true) {
if (path == null) return;
while (allowForwards && segmentIndex < path.Count - 2 && (path[segmentIndex+1] - point).sqrMagnitude <= (path[segmentIndex] - point).sqrMagnitude) {
NextSegment();
}
while (allowBackwards && segmentIndex > 0 && (path[segmentIndex-1] - point).sqrMagnitude <= (path[segmentIndex] - point).sqrMagnitude) {
PrevSegment();
}
// Check the distances to the two segments extending from the vertex path[segmentIndex]
// and pick the position on those segments that is closest to the #point parameter.
float factor1 = 0, factor2 = 0, d1 = float.PositiveInfinity, d2 = float.PositiveInfinity;
if (segmentIndex > 0) {
factor1 = VectorMath.ClosestPointOnLineFactor(path[segmentIndex-1], path[segmentIndex], point);
d1 = (Vector3.Lerp(path[segmentIndex-1], path[segmentIndex], factor1) - point).sqrMagnitude;
}
if (segmentIndex < path.Count - 1) {
factor2 = VectorMath.ClosestPointOnLineFactor(path[segmentIndex], path[segmentIndex+1], point);
d2 = (Vector3.Lerp(path[segmentIndex], path[segmentIndex+1], factor2) - point).sqrMagnitude;
}
if (d1 < d2) MoveToSegment(segmentIndex - 1, factor1);
else MoveToSegment(segmentIndex, factor2);
}
public void MoveToCircleIntersection2D (Vector3 circleCenter3D, float radius, IMovementPlane transform) {
if (path == null) return;
// Move forwards as long as we are getting closer to circleCenter3D
while (segmentIndex < path.Count - 2 && VectorMath.ClosestPointOnLineFactor(path[segmentIndex], path[segmentIndex+1], circleCenter3D) > 1) {
NextSegment();
}
var circleCenter = transform.ToPlane(circleCenter3D);
// Move forwards as long as the current segment endpoint is within the circle
while (segmentIndex < path.Count - 2 && (transform.ToPlane(path[segmentIndex+1]) - circleCenter).sqrMagnitude <= radius*radius) {
NextSegment();
}
// Calculate the intersection with the circle. This involves some math.
var factor = VectorMath.LineCircleIntersectionFactor(circleCenter, transform.ToPlane(path[segmentIndex]), transform.ToPlane(path[segmentIndex+1]), radius);
// Move to the intersection point
MoveToSegment(segmentIndex, factor);
}
protected virtual void PrevSegment () {
segmentIndex--;
currentSegmentLength = (path[segmentIndex+1] - path[segmentIndex]).magnitude;
distanceToSegmentStart -= currentSegmentLength;
}
protected virtual void NextSegment () {
segmentIndex++;
distanceToSegmentStart += currentSegmentLength;
currentSegmentLength = (path[segmentIndex+1] - path[segmentIndex]).magnitude;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9c0392dbc5e744ee28e7b9ee81aea1e2
timeCreated: 1490125383
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,88 @@
//#define ASTAR_NO_POOLING // Disable pooling for some reason. Maybe for debugging or just for measuring the difference.
using System;
using System.Collections.Generic;
namespace Pathfinding {
/// <summary>Pools path objects to reduce load on the garbage collector</summary>
public static class PathPool {
static readonly Dictionary<Type, Stack<Path> > pool = new Dictionary<Type, Stack<Path> >();
static readonly Dictionary<Type, int> totalCreated = new Dictionary<Type, int>();
/// <summary>
/// Adds a path to the pool.
/// This function should not be used directly. Instead use the Path.Claim and Path.Release functions.
/// </summary>
public static void Pool (Path path) {
#if !ASTAR_NO_POOLING
lock (pool) {
if (((IPathInternals)path).Pooled) {
throw new System.ArgumentException("The path is already pooled.");
}
Stack<Path> poolStack;
if (!pool.TryGetValue(path.GetType(), out poolStack)) {
poolStack = new Stack<Path>();
pool[path.GetType()] = poolStack;
}
((IPathInternals)path).Pooled = true;
((IPathInternals)path).OnEnterPool();
poolStack.Push(path);
}
#endif
}
/// <summary>Total created instances of paths of the specified type</summary>
public static int GetTotalCreated (Type type) {
int created;
if (totalCreated.TryGetValue(type, out created)) {
return created;
} else {
return 0;
}
}
/// <summary>Number of pooled instances of a path of the specified type</summary>
public static int GetSize (Type type) {
Stack<Path> poolStack;
if (pool.TryGetValue(type, out poolStack)) {
return poolStack.Count;
} else {
return 0;
}
}
/// <summary>Get a path from the pool or create a new one if the pool is empty</summary>
public static T GetPath<T>() where T : Path, new() {
#if ASTAR_NO_POOLING
T result = new T();
((IPathInternals)result).Reset();
return result;
#else
lock (pool) {
T result;
Stack<Path> poolStack;
if (pool.TryGetValue(typeof(T), out poolStack) && poolStack.Count > 0) {
// Guaranteed to have the correct type
result = poolStack.Pop() as T;
} else {
result = new T();
// Make sure an entry for the path type exists
if (!totalCreated.ContainsKey(typeof(T))) {
totalCreated[typeof(T)] = 0;
}
totalCreated[typeof(T)]++;
}
((IPathInternals)result).Pooled = false;
((IPathInternals)result).Reset();
return result;
}
#endif
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: cefe1014ab62848a89016fb97b1f8f7b
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,587 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
#if UNITY_5_5_OR_NEWER
using UnityEngine.Profiling;
#endif
namespace Pathfinding {
#if NETFX_CORE
using Thread = Pathfinding.WindowsStore.Thread;
#else
using Thread = System.Threading.Thread;
#endif
public class PathProcessor {
public event System.Action<Path> OnPathPreSearch;
public event System.Action<Path> OnPathPostSearch;
public event System.Action OnQueueUnblocked;
internal readonly ThreadControlQueue queue;
readonly AstarPath astar;
readonly PathReturnQueue returnQueue;
readonly PathHandler[] pathHandlers;
/// <summary>References to each of the pathfinding threads</summary>
readonly Thread[] threads;
/// <summary>
/// When no multithreading is used, the IEnumerator is stored here.
/// When no multithreading is used, a coroutine is used instead. It is not directly called with StartCoroutine
/// but a separate function has just a while loop which increments the main IEnumerator.
/// This is done so other functions can step the thread forward at any time, without having to wait for Unity to update it.
/// See: CalculatePaths
/// See: CalculatePathsHandler
/// </summary>
IEnumerator threadCoroutine;
/// <summary>
/// Holds the next node index which has not been used by any previous node.
/// See: nodeIndexPool
/// </summary>
int nextNodeIndex = 1;
/// <summary>
/// Holds indices for nodes that have been destroyed.
/// To avoid trashing a lot of memory structures when nodes are
/// frequently deleted and created, node indices are reused.
/// </summary>
readonly Stack<int> nodeIndexPool = new Stack<int>();
readonly List<int> locks = new List<int>();
int nextLockID = 0;
#if UNITY_2017_3_OR_NEWER
CustomSampler profilingSampler;
#endif
/// <summary>
/// Number of parallel pathfinders.
/// Returns the number of concurrent processes which can calculate paths at once.
/// When using multithreading, this will be the number of threads, if not using multithreading it is always 1 (since only 1 coroutine is used).
/// See: threadInfos
/// See: IsUsingMultithreading
/// </summary>
public int NumThreads {
get {
return pathHandlers.Length;
}
}
/// <summary>Returns whether or not multithreading is used</summary>
public bool IsUsingMultithreading {
get {
return threads != null;
}
}
internal PathProcessor (AstarPath astar, PathReturnQueue returnQueue, int processors, bool multithreaded) {
this.astar = astar;
this.returnQueue = returnQueue;
if (processors < 0) {
throw new System.ArgumentOutOfRangeException("processors");
}
if (!multithreaded && processors != 1) {
throw new System.Exception("Only a single non-multithreaded processor is allowed");
}
// Set up path queue with the specified number of receivers
queue = new ThreadControlQueue(processors);
pathHandlers = new PathHandler[processors];
for (int i = 0; i < processors; i++) {
pathHandlers[i] = new PathHandler(i, processors);
}
if (multithreaded) {
#if UNITY_2017_3_OR_NEWER
profilingSampler = CustomSampler.Create("Calculating Path");
#endif
threads = new Thread[processors];
// Start lots of threads
for (int i = 0; i < processors; i++) {
var pathHandler = pathHandlers[i];
threads[i] = new Thread(() => CalculatePathsThreaded(pathHandler));
#if !UNITY_SWITCH || UNITY_EDITOR
// Note: Setting the thread name seems to crash when deploying for Switch: https://forum.arongranberg.com/t/path-processor-crashing-nintendo-switch-build/6584
threads[i].Name = "Pathfinding Thread " + i;
#endif
threads[i].IsBackground = true;
threads[i].Start();
}
} else {
// Start coroutine if not using multithreading
threadCoroutine = CalculatePaths(pathHandlers[0]);
}
}
/// <summary>Prevents pathfinding from running while held</summary>
public struct GraphUpdateLock {
PathProcessor pathProcessor;
int id;
public GraphUpdateLock (PathProcessor pathProcessor, bool block) {
this.pathProcessor = pathProcessor;
id = pathProcessor.Lock(block);
}
/// <summary>
/// True while this lock is preventing the pathfinding threads from processing more paths.
/// Note that the pathfinding threads may not be paused yet (if this lock was obtained using PausePathfinding(false)).
/// </summary>
public bool Held {
get {
return pathProcessor != null && pathProcessor.locks.Contains(id);
}
}
/// <summary>Allow pathfinding to start running again if no other locks are still held</summary>
public void Release () {
pathProcessor.Unlock(id);
}
}
int Lock (bool block) {
queue.Block();
if (block) {
while (!queue.AllReceiversBlocked) {
if (IsUsingMultithreading) {
Thread.Sleep(1);
} else {
TickNonMultithreaded();
}
}
}
nextLockID++;
locks.Add(nextLockID);
return nextLockID;
}
void Unlock (int id) {
if (!locks.Remove(id)) {
throw new System.ArgumentException("This lock has already been released");
}
// Check if there are no remaining active locks
if (locks.Count == 0) {
if (OnQueueUnblocked != null) OnQueueUnblocked();
queue.Unblock();
}
}
/// <summary>
/// Prevents pathfinding threads from starting to calculate any new paths.
///
/// Returns: A lock object. You need to call Unlock on that object to allow pathfinding to resume.
///
/// Note: In most cases this should not be called from user code.
/// </summary>
/// <param name="block">If true, this call will block until all pathfinding threads are paused.
/// otherwise the threads will be paused as soon as they are done with what they are currently doing.</param>
public GraphUpdateLock PausePathfinding (bool block) {
return new GraphUpdateLock(this, block);
}
public void TickNonMultithreaded () {
// Process paths
if (threadCoroutine != null) {
try {
threadCoroutine.MoveNext();
} catch (System.Exception e) {
//This will kill pathfinding
threadCoroutine = null;
// Queue termination exceptions should be ignored, they are supposed to kill the thread
if (!(e is ThreadControlQueue.QueueTerminationException)) {
Debug.LogException(e);
Debug.LogError("Unhandled exception during pathfinding. Terminating.");
queue.TerminateReceivers();
// This will throw an exception supposed to kill the thread
try {
queue.PopNoBlock(false);
} catch {}
}
}
}
}
/// <summary>Calls 'Join' on each of the threads to block until they have completed</summary>
public void JoinThreads () {
if (threads != null) {
for (int i = 0; i < threads.Length; i++) {
if (!threads[i].Join(200)) {
Debug.LogError("Could not terminate pathfinding thread["+i+"] in 200ms, trying Thread.Abort");
threads[i].Abort();
}
}
}
}
/// <summary>Calls 'Abort' on each of the threads</summary>
public void AbortThreads () {
if (threads == null) return;
for (int i = 0; i < threads.Length; i++) {
if (threads[i] != null && threads[i].IsAlive) threads[i].Abort();
}
}
/// <summary>
/// Returns a new global node index.
/// Warning: This method should not be called directly. It is used by the GraphNode constructor.
/// </summary>
public int GetNewNodeIndex () {
return nodeIndexPool.Count > 0 ? nodeIndexPool.Pop() : nextNodeIndex++;
}
/// <summary>
/// Initializes temporary path data for a node.
/// Warning: This method should not be called directly. It is used by the GraphNode constructor.
/// </summary>
public void InitializeNode (GraphNode node) {
if (!queue.AllReceiversBlocked) {
throw new System.Exception("Trying to initialize a node when it is not safe to initialize any nodes. Must be done during a graph update. See http://arongranberg.com/astar/docs/graph-updates.php#direct");
}
for (int i = 0; i < pathHandlers.Length; i++) {
pathHandlers[i].InitializeNode(node);
}
astar.hierarchicalGraph.OnCreatedNode(node);
}
/// <summary>
/// Destroyes the given node.
/// This is to be called after the node has been disconnected from the graph so that it cannot be reached from any other nodes.
/// It should only be called during graph updates, that is when the pathfinding threads are either not running or paused.
///
/// Warning: This method should not be called by user code. It is used internally by the system.
/// </summary>
public void DestroyNode (GraphNode node) {
if (node.NodeIndex == -1) return;
nodeIndexPool.Push(node.NodeIndex);
for (int i = 0; i < pathHandlers.Length; i++) {
pathHandlers[i].DestroyNode(node);
}
astar.hierarchicalGraph.AddDirtyNode(node);
}
/// <summary>
/// Main pathfinding method (multithreaded).
/// This method will calculate the paths in the pathfinding queue when multithreading is enabled.
///
/// See: CalculatePaths
/// See: StartPath
/// </summary>
void CalculatePathsThreaded (PathHandler pathHandler) {
#if UNITY_2017_3_OR_NEWER
UnityEngine.Profiling.Profiler.BeginThreadProfiling("Pathfinding", "Pathfinding thread #" + (pathHandler.threadID+1));
#endif
#if !ASTAR_FAST_BUT_NO_EXCEPTIONS
try {
#endif
// Max number of ticks we are allowed to continue working in one run.
// One tick is 1/10000 of a millisecond.
// We need to check once in a while if the thread should be stopped.
long maxTicks = (long)(10*10000);
long targetTick = System.DateTime.UtcNow.Ticks + maxTicks;
while (true) {
// The path we are currently calculating
Path path = queue.Pop();
#if UNITY_2017_3_OR_NEWER
profilingSampler.Begin();
#endif
// Access the internal implementation methods
IPathInternals ipath = (IPathInternals)path;
// Trying to prevent simple modding to allow more than one thread
if (pathHandler.threadID > 0) {
throw new System.Exception("Thread Error");
}
AstarProfiler.StartFastProfile(0);
ipath.PrepareBase(pathHandler);
// Now processing the path
// Will advance to Processing
ipath.AdvanceState(PathState.Processing);
// Call some callbacks
if (OnPathPreSearch != null) {
OnPathPreSearch(path);
}
// Tick for when the path started, used for calculating how long time the calculation took
long startTicks = System.DateTime.UtcNow.Ticks;
// Prepare the path
ipath.Prepare();
AstarProfiler.EndFastProfile(0);
if (path.CompleteState == PathCompleteState.NotCalculated) {
// For visualization purposes, we set the last computed path to p, so we can view debug info on it in the editor (scene view).
astar.debugPathData = ipath.PathHandler;
astar.debugPathID = path.pathID;
AstarProfiler.StartFastProfile(1);
// Initialize the path, now ready to begin search
ipath.Initialize();
AstarProfiler.EndFastProfile(1);
// Loop while the path has not been fully calculated
while (path.CompleteState == PathCompleteState.NotCalculated) {
// Do some work on the path calculation.
// The function will return when it has taken too much time
// or when it has finished calculation
AstarProfiler.StartFastProfile(2);
ipath.CalculateStep(targetTick);
AstarProfiler.EndFastProfile(2);
targetTick = System.DateTime.UtcNow.Ticks + maxTicks;
// Cancel function (and thus the thread) if no more paths should be accepted.
// This is done when the A* object is about to be destroyed
// The path is returned and then this function will be terminated
if (queue.IsTerminating) {
path.FailWithError("AstarPath object destroyed");
}
}
path.duration = (System.DateTime.UtcNow.Ticks - startTicks)*0.0001F;
#if ProfileAstar
System.Threading.Interlocked.Increment(ref AstarPath.PathsCompleted);
System.Threading.Interlocked.Add(ref AstarPath.TotalSearchTime, System.DateTime.UtcNow.Ticks - startTicks);
#endif
}
// Cleans up node tagging and other things
ipath.Cleanup();
AstarProfiler.StartFastProfile(9);
if (path.immediateCallback != null) path.immediateCallback(path);
if (OnPathPostSearch != null) {
OnPathPostSearch(path);
}
// Push the path onto the return stack
// It will be detected by the main Unity thread and returned as fast as possible (the next late update hopefully)
returnQueue.Enqueue(path);
// Will advance to ReturnQueue
ipath.AdvanceState(PathState.ReturnQueue);
AstarProfiler.EndFastProfile(9);
#if UNITY_2017_3_OR_NEWER
profilingSampler.End();
#endif
}
#if !ASTAR_FAST_BUT_NO_EXCEPTIONS
}
catch (System.Exception e) {
#if !NETFX_CORE
if (e is ThreadAbortException || e is ThreadControlQueue.QueueTerminationException)
#else
if (e is ThreadControlQueue.QueueTerminationException)
#endif
{
if (astar.logPathResults == PathLog.Heavy)
Debug.LogWarning("Shutting down pathfinding thread #" + pathHandler.threadID);
return;
}
Debug.LogException(e);
Debug.LogError("Unhandled exception during pathfinding. Terminating.");
// Unhandled exception, kill pathfinding
queue.TerminateReceivers();
} finally {
#if UNITY_2017_3_OR_NEWER
UnityEngine.Profiling.Profiler.EndThreadProfiling();
#endif
}
#endif
Debug.LogError("Error : This part should never be reached.");
queue.ReceiverTerminated();
}
/// <summary>
/// Main pathfinding method.
/// This method will calculate the paths in the pathfinding queue.
///
/// See: CalculatePathsThreaded
/// See: StartPath
/// </summary>
IEnumerator CalculatePaths (PathHandler pathHandler) {
// Max number of ticks before yielding/sleeping
long maxTicks = (long)(astar.maxFrameTime*10000);
long targetTick = System.DateTime.UtcNow.Ticks + maxTicks;
while (true) {
// The path we are currently calculating
Path p = null;
AstarProfiler.StartProfile("Path Queue");
// Try to get the next path to be calculated
bool blockedBefore = false;
while (p == null) {
try {
p = queue.PopNoBlock(blockedBefore);
blockedBefore |= p == null;
} catch (ThreadControlQueue.QueueTerminationException) {
yield break;
}
if (p == null) {
AstarProfiler.EndProfile();
yield return null;
AstarProfiler.StartProfile("Path Queue");
}
}
AstarProfiler.EndProfile();
AstarProfiler.StartProfile("Path Calc");
IPathInternals ip = (IPathInternals)p;
// Max number of ticks we are allowed to continue working in one run
// One tick is 1/10000 of a millisecond
maxTicks = (long)(astar.maxFrameTime*10000);
ip.PrepareBase(pathHandler);
// Now processing the path
// Will advance to Processing
ip.AdvanceState(PathState.Processing);
// Call some callbacks
// It needs to be stored in a local variable to avoid race conditions
var tmpOnPathPreSearch = OnPathPreSearch;
if (tmpOnPathPreSearch != null) tmpOnPathPreSearch(p);
// Tick for when the path started, used for calculating how long time the calculation took
long startTicks = System.DateTime.UtcNow.Ticks;
long totalTicks = 0;
AstarProfiler.StartFastProfile(8);
AstarProfiler.StartFastProfile(0);
//Prepare the path
AstarProfiler.StartProfile("Path Prepare");
ip.Prepare();
AstarProfiler.EndProfile("Path Prepare");
AstarProfiler.EndFastProfile(0);
// Check if the Prepare call caused the path to complete
// If this happens the path usually failed
if (p.CompleteState == PathCompleteState.NotCalculated) {
// For debug uses, we set the last computed path to p, so we can view debug info on it in the editor (scene view).
astar.debugPathData = ip.PathHandler;
astar.debugPathID = p.pathID;
// Initialize the path, now ready to begin search
AstarProfiler.StartProfile("Path Initialize");
ip.Initialize();
AstarProfiler.EndProfile();
// The error can turn up in the Init function
while (p.CompleteState == PathCompleteState.NotCalculated) {
// Do some work on the path calculation.
// The function will return when it has taken too much time
// or when it has finished calculation
AstarProfiler.StartFastProfile(2);
AstarProfiler.StartProfile("Path Calc Step");
ip.CalculateStep(targetTick);
AstarProfiler.EndFastProfile(2);
AstarProfiler.EndProfile();
// If the path has finished calculation, we can break here directly instead of sleeping
// Improves latency
if (p.CompleteState != PathCompleteState.NotCalculated) break;
AstarProfiler.EndFastProfile(8);
totalTicks += System.DateTime.UtcNow.Ticks-startTicks;
// Yield/sleep so other threads can work
AstarProfiler.EndProfile();
yield return null;
AstarProfiler.StartProfile("Path Calc");
startTicks = System.DateTime.UtcNow.Ticks;
AstarProfiler.StartFastProfile(8);
// Cancel function (and thus the thread) if no more paths should be accepted.
// This is done when the A* object is about to be destroyed
// The path is returned and then this function will be terminated (see similar IF statement higher up in the function)
if (queue.IsTerminating) {
p.FailWithError("AstarPath object destroyed");
}
targetTick = System.DateTime.UtcNow.Ticks + maxTicks;
}
totalTicks += System.DateTime.UtcNow.Ticks-startTicks;
p.duration = totalTicks*0.0001F;
#if ProfileAstar
System.Threading.Interlocked.Increment(ref AstarPath.PathsCompleted);
#endif
}
// Cleans up node tagging and other things
ip.Cleanup();
AstarProfiler.EndFastProfile(8);
// Call the immediate callback
// It needs to be stored in a local variable to avoid race conditions
var tmpImmediateCallback = p.immediateCallback;
if (tmpImmediateCallback != null) tmpImmediateCallback(p);
AstarProfiler.StartFastProfile(13);
// It needs to be stored in a local variable to avoid race conditions
var tmpOnPathPostSearch = OnPathPostSearch;
if (tmpOnPathPostSearch != null) tmpOnPathPostSearch(p);
AstarProfiler.EndFastProfile(13);
// Push the path onto the return stack
// It will be detected by the main Unity thread and returned as fast as possible (the next late update)
returnQueue.Enqueue(p);
ip.AdvanceState(PathState.ReturnQueue);
AstarProfiler.EndProfile();
// Wait a bit if we have calculated a lot of paths
if (System.DateTime.UtcNow.Ticks > targetTick) {
yield return null;
targetTick = System.DateTime.UtcNow.Ticks + maxTicks;
}
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f1b519f8b6e2c450aaae5cb2df6c73be
timeCreated: 1443114816
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,73 @@
using UnityEngine;
using System.Collections.Generic;
#if UNITY_5_5_OR_NEWER
using UnityEngine.Profiling;
#endif
namespace Pathfinding {
class PathReturnQueue {
/// <summary>
/// Holds all paths which are waiting to be flagged as completed.
/// See: <see cref="ReturnPaths"/>
/// </summary>
Queue<Path> pathReturnQueue = new Queue<Path>();
/// <summary>
/// Paths are claimed silently by some object to prevent them from being recycled while still in use.
/// This will be set to the AstarPath object.
/// </summary>
System.Object pathsClaimedSilentlyBy;
public PathReturnQueue (System.Object pathsClaimedSilentlyBy) {
this.pathsClaimedSilentlyBy = pathsClaimedSilentlyBy;
}
public void Enqueue (Path path) {
lock (pathReturnQueue) {
pathReturnQueue.Enqueue(path);
}
}
/// <summary>
/// Returns all paths in the return stack.
/// Paths which have been processed are put in the return stack.
/// This function will pop all items from the stack and return them to e.g the Seeker requesting them.
/// </summary>
/// <param name="timeSlice">Do not return all paths at once if it takes a long time, instead return some and wait until the next call.</param>
public void ReturnPaths (bool timeSlice) {
Profiler.BeginSample("Calling Path Callbacks");
// Hard coded limit on 1.0 ms
long targetTick = timeSlice ? System.DateTime.UtcNow.Ticks + 1 * 10000 : 0;
int counter = 0;
// Loop through the linked list and return all paths
while (true) {
// Move to the next path
Path path;
lock (pathReturnQueue) {
if (pathReturnQueue.Count == 0) break;
path = pathReturnQueue.Dequeue();
}
// Return the path
((IPathInternals)path).ReturnPath();
// Will increment path state to Returned
((IPathInternals)path).AdvanceState(PathState.Returned);
path.Release(pathsClaimedSilentlyBy, true);
counter++;
// At least 5 paths will be returned, even if timeSlice is enabled
if (counter > 5 && timeSlice) {
counter = 0;
if (System.DateTime.UtcNow.Ticks >= targetTick) {
break;
}
}
}
Profiler.EndSample();
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 70ca370ae38794366be2fa32880f362e
timeCreated: 1443114816
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,98 @@
//#define ASTAR_NO_POOLING //@SHOWINEDITOR Disable pooling for some reason. Could be debugging or just for measuring the difference.
using System.Collections.Generic;
namespace Pathfinding.Util {
/// <summary>
/// Lightweight Stack Pool.
/// Handy class for pooling stacks of type T.
///
/// Usage:
/// - Claim a new stack using <code> Stack<SomeClass> foo = StackPool<SomeClass>.Claim (); </code>
/// - Use it and do stuff with it
/// - Release it with <code> StackPool<SomeClass>.Release (foo); </code>
///
/// You do not need to clear the stack before releasing it.
/// After you have released a stack, you should never use it again.
///
/// Warning: This class is not thread safe
///
/// \since Version 3.2
/// See: Pathfinding.Util.ListPool
/// </summary>
public static class StackPool<T> {
/// <summary>Internal pool</summary>
static readonly List<Stack<T> > pool;
/// <summary>Static constructor</summary>
static StackPool () {
pool = new List<Stack<T> >();
}
/// <summary>
/// Claim a stack.
/// Returns a pooled stack if any are in the pool.
/// Otherwise it creates a new one.
/// After usage, this stack should be released using the Release function (though not strictly necessary).
/// </summary>
public static Stack<T> Claim () {
#if ASTAR_NO_POOLING
return new Stack<T>();
#else
lock (pool) {
if (pool.Count > 0) {
Stack<T> ls = pool[pool.Count-1];
pool.RemoveAt(pool.Count-1);
return ls;
}
}
return new Stack<T>();
#endif
}
/// <summary>
/// Makes sure the pool contains at least count pooled items.
/// This is good if you want to do all allocations at start.
/// </summary>
public static void Warmup (int count) {
var tmp = new Stack<T>[count];
for (int i = 0; i < count; i++) tmp[i] = Claim();
for (int i = 0; i < count; i++) Release(tmp[i]);
}
/// <summary>
/// Releases a stack.
/// After the stack has been released it should not be used anymore.
/// Releasing a stack twice will cause an error.
/// </summary>
public static void Release (Stack<T> stack) {
#if !ASTAR_NO_POOLING
stack.Clear();
lock (pool) {
for (int i = 0; i < pool.Count; i++)
if (pool[i] == stack) UnityEngine.Debug.LogError("The Stack is released even though it is inside the pool");
pool.Add(stack);
}
#endif
}
/// <summary>
/// Clears all pooled stacks of this type.
/// This is an O(n) operation, where n is the number of pooled stacks
/// </summary>
public static void Clear () {
lock (pool) {
pool.Clear();
}
}
/// <summary>Number of stacks of this type in the pool</summary>
public static int GetSize () {
return pool.Count;
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: de467bbbb1ff84668ae8262caad00941
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,291 @@
using System.Threading;
namespace Pathfinding {
/// <summary>Queue of paths to be processed by the system</summary>
class ThreadControlQueue {
public class QueueTerminationException : System.Exception {
}
Path head;
Path tail;
readonly System.Object lockObj = new System.Object();
readonly int numReceivers;
bool blocked;
/// <summary>
/// Number of receiver threads that are currently blocked.
/// This is only modified while a thread has a lock on lockObj
/// </summary>
int blockedReceivers;
/// <summary>
/// True while head == null.
/// This is only modified while a thread has a lock on lockObj
/// </summary>
bool starving;
/// <summary>
/// True after TerminateReceivers has been called.
/// All receivers will be terminated when they next call Pop.
/// </summary>
bool terminate;
ManualResetEvent block = new ManualResetEvent(true);
/// <summary>
/// Create a new queue with the specified number of receivers.
/// It is important that the number of receivers is fixed.
/// Properties like AllReceiversBlocked rely on knowing the exact number of receivers using the Pop (or PopNoBlock) methods.
/// </summary>
public ThreadControlQueue (int numReceivers) {
this.numReceivers = numReceivers;
}
/// <summary>True if the queue is empty</summary>
public bool IsEmpty {
get {
return head == null;
}
}
/// <summary>True if TerminateReceivers has been called</summary>
public bool IsTerminating {
get {
return terminate;
}
}
/// <summary>Block queue, all calls to Pop will block until Unblock is called</summary>
public void Block () {
lock (lockObj) {
blocked = true;
block.Reset();
}
}
/// <summary>
/// Unblock queue.
/// Calls to Pop will not block anymore.
/// See: Block
/// </summary>
public void Unblock () {
lock (lockObj) {
blocked = false;
block.Set();
}
}
/// <summary>
/// Aquires a lock on this queue.
/// Must be paired with a call to <see cref="Unlock"/>
/// </summary>
public void Lock () {
Monitor.Enter(lockObj);
}
/// <summary>Releases the lock on this queue</summary>
public void Unlock () {
Monitor.Exit(lockObj);
}
/// <summary>True if blocking and all receivers are waiting for unblocking</summary>
public bool AllReceiversBlocked {
get {
lock (lockObj) {
return blocked && blockedReceivers == numReceivers;
}
}
}
/// <summary>Push a path to the front of the queue</summary>
public void PushFront (Path path) {
lock (lockObj) {
// If termination is due, why add stuff to a queue which will not be read from anyway
if (terminate) return;
if (tail == null) {// (tail == null) ==> (head == null)
head = path;
tail = path;
if (starving && !blocked) {
starving = false;
block.Set();
} else {
starving = false;
}
} else {
path.next = head;
head = path;
}
}
}
/// <summary>Push a path to the end of the queue</summary>
public void Push (Path path) {
lock (lockObj) {
// If termination is due, why add stuff to a queue which will not be read from anyway
if (terminate) return;
if (tail == null) {// (tail == null) ==> (head == null)
head = path;
tail = path;
if (starving && !blocked) {
starving = false;
block.Set();
} else {
starving = false;
}
} else {
tail.next = path;
tail = path;
}
}
}
void Starving () {
starving = true;
block.Reset();
}
/// <summary>All calls to Pop and PopNoBlock will now generate exceptions</summary>
public void TerminateReceivers () {
lock (lockObj) {
terminate = true;
block.Set();
}
}
/// <summary>
/// Pops the next item off the queue.
/// This call will block if there are no items in the queue or if the queue is currently blocked.
///
/// Returns: A Path object, guaranteed to be not null.
/// \throws QueueTerminationException if <see cref="TerminateReceivers"/> has been called.
/// \throws System.InvalidOperationException if more receivers get blocked than the fixed count sent to the constructor
/// </summary>
public Path Pop () {
Monitor.Enter(lockObj);
try {
if (terminate) {
blockedReceivers++;
throw new QueueTerminationException();
}
if (head == null) {
Starving();
}
while (blocked || starving) {
blockedReceivers++;
if (blockedReceivers > numReceivers) {
throw new System.InvalidOperationException("More receivers are blocked than specified in constructor ("+blockedReceivers + " > " + numReceivers+")");
}
Monitor.Exit(lockObj);
block.WaitOne();
Monitor.Enter(lockObj);
if (terminate) {
throw new QueueTerminationException();
}
blockedReceivers--;
if (head == null) {
Starving();
}
}
Path p = head;
var newHead = head.next;
if (newHead == null) {
tail = null;
}
head.next = null;
head = newHead;
return p;
} finally {
// Normally this only exits via a QueueTerminationException and will always be entered in that case.
// However the thread may also be aborted using a ThreadAbortException which can happen at any time.
// In particular if the Unity Editor recompiles scripts and is configured to exit play mode on recompilation
// then it will apparently abort all threads before the AstarPath.OnDestroy method is called (which would have
// cleaned up the threads gracefully). So we need to check if we actually hold the lock before releaseing it.
if (Monitor.IsEntered(lockObj)) {
Monitor.Exit(lockObj);
}
}
}
/// <summary>
/// Call when a receiver was terminated in other ways than by a QueueTerminationException.
///
/// After this call, the receiver should be dead and not call anything else in this class.
/// </summary>
public void ReceiverTerminated () {
Monitor.Enter(lockObj);
blockedReceivers++;
Monitor.Exit(lockObj);
}
/// <summary>
/// Pops the next item off the queue, this call will not block.
/// To ensure stability, the caller must follow this pattern.
/// 1. Call PopNoBlock(false), if a null value is returned, wait for a bit (e.g yield return null in a Unity coroutine)
/// 2. try again with PopNoBlock(true), if still null, wait for a bit
/// 3. Repeat from step 2.
///
/// \throws QueueTerminationException if <see cref="TerminateReceivers"/> has been called.
/// \throws System.InvalidOperationException if more receivers get blocked than the fixed count sent to the constructor
/// </summary>
public Path PopNoBlock (bool blockedBefore) {
Monitor.Enter(lockObj);
try {
if (terminate) {
blockedReceivers++;
throw new QueueTerminationException();
}
if (head == null) {
Starving();
}
if (blocked || starving) {
if (!blockedBefore) {
blockedReceivers++;
if (terminate) throw new QueueTerminationException();
if (blockedReceivers == numReceivers) {
//Last alive
} else if (blockedReceivers > numReceivers) {
throw new System.InvalidOperationException("More receivers are blocked than specified in constructor ("+blockedReceivers + " > " + numReceivers+")");
}
}
return null;
}
if (blockedBefore) {
blockedReceivers--;
}
Path p = head;
var newHead = head.next;
if (newHead == null) {
tail = null;
}
head.next = null;
head = newHead;
return p;
} finally {
Monitor.Exit(lockObj);
}
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e6a8344fd0d1c453cbaf5c5eb8f55ca5
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,148 @@
#if NETFX_CORE
using System.Threading;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;
using TP = System.Reflection.TypeInfo;
#else
using TP = System.Type;
#endif
namespace Pathfinding.WindowsStore {
public static class WindowsStoreCompatibility {
public static System.Type GetTypeFromInfo (TP type) {
#if NETFX_CORE
return type.AsType();
#else
return type;
#endif
}
public static TP GetTypeInfo (System.Type type) {
#if NETFX_CORE
return type.GetTypeInfo();
#else
return type;
#endif
}
#if NETFX_CORE
public static void Close (this BinaryWriter stream) {
stream.Dispose();
}
public static void Close (this BinaryReader stream) {
stream.Dispose();
}
public static void Close (this StreamWriter stream) {
stream.Dispose();
}
#endif
}
#if NETFX_CORE
public delegate void ParameterizedThreadStart(System.Object ob);
public delegate void ThreadStart();
public class Thread {
//
// Fields
//
private Pathfinding.WindowsStore.ParameterizedThreadStart _paramThreadStart;
private CancellationTokenSource _taskCancellationTokenSource;
private Task _task = null;
private Pathfinding.WindowsStore.ThreadStart _threadStart;
private static ManualResetEvent SleepEvent = new ManualResetEvent(false);
//
// Properties
//
public bool IsAlive {
get {
return this._task != null && !this._task.IsCompleted;
}
set {
throw new System.NotImplementedException();
}
}
public bool IsBackground {
get {
return false;
}
set {
}
}
public string Name {
get;
set;
}
//
// Constructors
//
public Thread (Pathfinding.WindowsStore.ParameterizedThreadStart start) {
this._taskCancellationTokenSource = new CancellationTokenSource();
this._paramThreadStart = start;
}
public Thread (Pathfinding.WindowsStore.ThreadStart start) {
this._taskCancellationTokenSource = new CancellationTokenSource();
this._threadStart = start;
}
//
// Static Methods
//
public static void Sleep (int ms) {
SleepEvent.WaitOne(ms);
}
//
// Methods
//
public void Abort () {
if (this._taskCancellationTokenSource != null) {
this._taskCancellationTokenSource.Cancel();
}
}
private void EnsureTask (object paramThreadStartParam = null) {
if (this._task == null) {
if (this._paramThreadStart != null) {
this._task = new Task(delegate {
this._paramThreadStart(paramThreadStartParam);
}, this._taskCancellationTokenSource.Token);
} else {
if (this._threadStart != null) {
this._task = new Task(delegate {
this._threadStart();
}, this._taskCancellationTokenSource.Token);
}
}
}
}
public bool Join (int ms) {
this.EnsureTask();
return this._task.Wait(ms, this._taskCancellationTokenSource.Token);
}
public void Start () {
this.EnsureTask();
this._task.Start(TaskScheduler.Default);
}
public void Start (object param) {
this.EnsureTask(param);
this._task.Start(TaskScheduler.Default);
}
}
#endif
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 810a4e97f5ccb4b5184c4c3206492974
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,338 @@
using UnityEngine;
#if UNITY_5_5_OR_NEWER
using UnityEngine.Profiling;
#endif
namespace Pathfinding {
using UnityEngine;
/// <summary>
/// An item of work that can be executed when graphs are safe to update.
/// See: <see cref="AstarPath.UpdateGraphs"/>
/// See: <see cref="AstarPath.AddWorkItem"/>
/// </summary>
public struct AstarWorkItem {
/// <summary>
/// Init function.
/// May be null if no initialization is needed.
/// Will be called once, right before the first call to <see cref="update"/>.
/// </summary>
public System.Action init;
/// <summary>
/// Init function.
/// May be null if no initialization is needed.
/// Will be called once, right before the first call to <see cref="update"/>.
///
/// A context object is sent as a parameter. This can be used
/// to for example queue a flood fill that will be executed either
/// when a work item calls EnsureValidFloodFill or all work items have
/// been completed. If multiple work items are updating nodes
/// so that they need a flood fill afterwards, using the QueueFloodFill
/// method is preferred since then only a single flood fill needs
/// to be performed for all of the work items instead of one
/// per work item.
/// </summary>
public System.Action<IWorkItemContext> initWithContext;
/// <summary>
/// Update function, called once per frame when the work item executes.
/// Takes a param force. If that is true, the work item should try to complete the whole item in one go instead
/// of spreading it out over multiple frames.
/// Returns: True when the work item is completed.
/// </summary>
public System.Func<bool, bool> update;
/// <summary>
/// Update function, called once per frame when the work item executes.
/// Takes a param force. If that is true, the work item should try to complete the whole item in one go instead
/// of spreading it out over multiple frames.
/// Returns: True when the work item is completed.
///
/// A context object is sent as a parameter. This can be used
/// to for example queue a flood fill that will be executed either
/// when a work item calls EnsureValidFloodFill or all work items have
/// been completed. If multiple work items are updating nodes
/// so that they need a flood fill afterwards, using the QueueFloodFill
/// method is preferred since then only a single flood fill needs
/// to be performed for all of the work items instead of one
/// per work item.
/// </summary>
public System.Func<IWorkItemContext, bool, bool> updateWithContext;
public AstarWorkItem (System.Func<bool, bool> update) {
this.init = null;
this.initWithContext = null;
this.updateWithContext = null;
this.update = update;
}
public AstarWorkItem (System.Func<IWorkItemContext, bool, bool> update) {
this.init = null;
this.initWithContext = null;
this.updateWithContext = update;
this.update = null;
}
public AstarWorkItem (System.Action init, System.Func<bool, bool> update = null) {
this.init = init;
this.initWithContext = null;
this.update = update;
this.updateWithContext = null;
}
public AstarWorkItem (System.Action<IWorkItemContext> init, System.Func<IWorkItemContext, bool, bool> update = null) {
this.init = null;
this.initWithContext = init;
this.update = null;
this.updateWithContext = update;
}
}
/// <summary>Interface to expose a subset of the WorkItemProcessor functionality</summary>
public interface IWorkItemContext {
/// <summary>
/// Call during work items to queue a flood fill.
/// An instant flood fill can be done via FloodFill()
/// but this method can be used to batch several updates into one
/// to increase performance.
/// WorkItems which require a valid Flood Fill in their execution can call EnsureValidFloodFill
/// to ensure that a flood fill is done if any earlier work items queued one.
///
/// Once a flood fill is queued it will be done after all WorkItems have been executed.
///
/// Deprecated: Avoid using. This will force a full recalculation of the connected components. In most cases the HierarchicalGraph class takes care of things automatically behind the scenes now. In pretty much all cases you should be able to remove the call to this function.
/// </summary>
[System.Obsolete("Avoid using. This will force a full recalculation of the connected components. In most cases the HierarchicalGraph class takes care of things automatically behind the scenes now. In pretty much all cases you should be able to remove the call to this function.")]
void QueueFloodFill();
/// <summary>
/// If a WorkItem needs to have a valid area information during execution, call this method to ensure there are no pending flood fills.
/// If you are using the <see cref="Pathfinding.GraphNode.Area"/> property or the <see cref="Pathfinding.PathUtilities.IsPathPossible"/> method in your work items, then you might want to call this method before you use them
/// to ensure that the data is up to date.
///
/// See: <see cref="Pathfinding.HierarchicalGraph"/>
///
/// <code>
/// AstarPath.active.AddWorkItem(new AstarWorkItem((IWorkItemContext ctx) => {
/// ctx.EnsureValidFloodFill();
///
/// // The above call guarantees that this method has up to date information about the graph
/// if (PathUtilities.IsPathPossible(someNode, someOtherNode)) {
/// // Do something
/// }
/// }));
/// </code>
/// </summary>
void EnsureValidFloodFill();
/// <summary>
/// Trigger a graph modification event.
/// This will cause a <see cref="Pathfinding.GraphModifier.PostUpdate"/> event to be issued after all graph updates have finished.
/// Some scripts listen for this event. For example off-mesh links listen to it and will recalculate which nodes they are connected to when it it sent.
/// If a graph is dirtied multiple times, or even if multiple graphs are dirtied, the event will only be sent once.
/// </summary>
void SetGraphDirty(NavGraph graph);
}
class WorkItemProcessor : IWorkItemContext {
/// <summary>Used to prevent waiting for work items to complete inside other work items as that will cause the program to hang</summary>
public bool workItemsInProgressRightNow { get; private set; }
readonly AstarPath astar;
readonly IndexedQueue<AstarWorkItem> workItems = new IndexedQueue<AstarWorkItem>();
/// <summary>True if any work items are queued right now</summary>
public bool anyQueued {
get { return workItems.Count > 0; }
}
/// <summary>
/// True if any work items have queued a flood fill.
/// See: QueueWorkItemFloodFill
/// </summary>
bool queuedWorkItemFloodFill = false;
bool anyGraphsDirty = true;
/// <summary>
/// True while a batch of work items are being processed.
/// Set to true when a work item is started to be processed, reset to false when all work items are complete.
///
/// Work item updates are often spread out over several frames, this flag will be true during the whole time the
/// updates are in progress.
/// </summary>
public bool workItemsInProgress { get; private set; }
/// <summary>Similar to Queue<T> but allows random access</summary>
class IndexedQueue<T> {
T[] buffer = new T[4];
int start;
public T this[int index] {
get {
if (index < 0 || index >= Count) throw new System.IndexOutOfRangeException();
return buffer[(start + index) % buffer.Length];
}
set {
if (index < 0 || index >= Count) throw new System.IndexOutOfRangeException();
buffer[(start + index) % buffer.Length] = value;
}
}
public int Count { get; private set; }
public void Enqueue (T item) {
if (Count == buffer.Length) {
var newBuffer = new T[buffer.Length*2];
for (int i = 0; i < Count; i++) {
newBuffer[i] = this[i];
}
buffer = newBuffer;
start = 0;
}
buffer[(start + Count) % buffer.Length] = item;
Count++;
}
public T Dequeue () {
if (Count == 0) throw new System.InvalidOperationException();
var item = buffer[start];
start = (start + 1) % buffer.Length;
Count--;
return item;
}
}
/// <summary>
/// Call during work items to queue a flood fill.
/// An instant flood fill can be done via FloodFill()
/// but this method can be used to batch several updates into one
/// to increase performance.
/// WorkItems which require a valid Flood Fill in their execution can call EnsureValidFloodFill
/// to ensure that a flood fill is done if any earlier work items queued one.
///
/// Once a flood fill is queued it will be done after all WorkItems have been executed.
/// </summary>
void IWorkItemContext.QueueFloodFill () {
queuedWorkItemFloodFill = true;
}
void IWorkItemContext.SetGraphDirty (NavGraph graph) {
anyGraphsDirty = true;
}
/// <summary>If a WorkItem needs to have a valid area information during execution, call this method to ensure there are no pending flood fills</summary>
public void EnsureValidFloodFill () {
if (queuedWorkItemFloodFill) {
astar.hierarchicalGraph.RecalculateAll();
} else {
astar.hierarchicalGraph.RecalculateIfNecessary();
}
}
public WorkItemProcessor (AstarPath astar) {
this.astar = astar;
}
public void OnFloodFill () {
queuedWorkItemFloodFill = false;
}
/// <summary>
/// Add a work item to be processed when pathfinding is paused.
///
/// See: ProcessWorkItems
/// </summary>
public void AddWorkItem (AstarWorkItem item) {
workItems.Enqueue(item);
}
/// <summary>
/// Process graph updating work items.
/// Process all queued work items, e.g graph updates and the likes.
///
/// Returns:
/// - false if there are still items to be processed.
/// - true if the last work items was processed and pathfinding threads are ready to be resumed.
///
/// See: AddWorkItem
/// See: threadSafeUpdateState
/// See: Update
/// </summary>
public bool ProcessWorkItems (bool force) {
if (workItemsInProgressRightNow) throw new System.Exception("Processing work items recursively. Please do not wait for other work items to be completed inside work items. " +
"If you think this is not caused by any of your scripts, this might be a bug.");
UnityEngine.Physics2D.SyncTransforms();
workItemsInProgressRightNow = true;
astar.data.LockGraphStructure(true);
while (workItems.Count > 0) {
// Working on a new batch
if (!workItemsInProgress) {
workItemsInProgress = true;
queuedWorkItemFloodFill = false;
}
// Peek at first item in the queue
AstarWorkItem itm = workItems[0];
bool status;
try {
// Call init the first time the item is seen
if (itm.init != null) {
itm.init();
itm.init = null;
}
if (itm.initWithContext != null) {
itm.initWithContext(this);
itm.initWithContext = null;
}
// Make sure the item in the queue is up to date
workItems[0] = itm;
if (itm.update != null) {
status = itm.update(force);
} else if (itm.updateWithContext != null) {
status = itm.updateWithContext(this, force);
} else {
status = true;
}
} catch {
workItems.Dequeue();
workItemsInProgressRightNow = false;
astar.data.UnlockGraphStructure();
throw;
}
if (!status) {
if (force) {
Debug.LogError("Misbehaving WorkItem. 'force'=true but the work item did not complete.\nIf force=true is passed to a WorkItem it should always return true.");
}
// Still work items to process
workItemsInProgressRightNow = false;
astar.data.UnlockGraphStructure();
return false;
} else {
workItems.Dequeue();
}
}
EnsureValidFloodFill();
Profiler.BeginSample("PostUpdate");
if (anyGraphsDirty) GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate);
Profiler.EndSample();
anyGraphsDirty = false;
workItemsInProgressRightNow = false;
workItemsInProgress = false;
astar.data.UnlockGraphStructure();
return true;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4236764d0fc1041abaac13b858be5118
timeCreated: 1443114816
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: