Adding A* Algorythm and carpet roads
This commit is contained in:
21
AR/Assets/AstarPathfindingProject/Utilities/AstarChecksum.cs
Normal file
21
AR/Assets/AstarPathfindingProject/Utilities/AstarChecksum.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
namespace Pathfinding.Util {
|
||||
/// <summary>Calculates checksums of byte arrays</summary>
|
||||
public class Checksum {
|
||||
/// <summary>
|
||||
/// Calculate checksum for the byte array starting from a previous values.
|
||||
/// Useful if data is split up between several byte arrays
|
||||
/// </summary>
|
||||
public static uint GetChecksum (byte[] arr, uint hash) {
|
||||
// Sort of implements the Fowler–Noll–Vo hash function
|
||||
const int prime = 16777619;
|
||||
|
||||
hash ^= 2166136261U;
|
||||
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
hash = (hash ^ arr[i]) * prime;
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
7
AR/Assets/AstarPathfindingProject/Utilities/AstarChecksum.cs.meta
generated
Normal file
7
AR/Assets/AstarPathfindingProject/Utilities/AstarChecksum.cs.meta
generated
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8959628b7b47f4ccca91bcfa87d9f77e
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
109
AR/Assets/AstarPathfindingProject/Utilities/AstarMemory.cs
Normal file
109
AR/Assets/AstarPathfindingProject/Utilities/AstarMemory.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
|
||||
namespace Pathfinding.Util {
|
||||
/// <summary>Various utilities for handling arrays and memory</summary>
|
||||
public static class Memory {
|
||||
/// <summary>
|
||||
/// Sets all values in an array to a specific value faster than a loop.
|
||||
/// Only faster for large arrays. Slower for small ones.
|
||||
/// Tests indicate it becomes faster somewhere when the length of the array grows above around 100.
|
||||
/// For large arrays this can be magnitudes faster. Up to 40 times faster has been measured.
|
||||
///
|
||||
/// Note: Only works on primitive value types such as int, long, float, etc.
|
||||
///
|
||||
/// <code>
|
||||
/// //Set all values to 8 in the array
|
||||
/// int[] arr = new int[20000];
|
||||
/// Pathfinding.Util.Memory.MemSet<int> (arr, 8, sizeof(int));
|
||||
/// </code>
|
||||
/// See: System.Buffer.BlockCopy
|
||||
/// </summary>
|
||||
/// <param name="array">the array to fill</param>
|
||||
/// <param name="value">the value to fill the array with</param>
|
||||
/// <param name="byteSize">size in bytes of every element in the array. e.g 4 bytes for an int, or 8 bytes for a long.
|
||||
/// It can be efficiently got using the sizeof built-in function.</param>
|
||||
public static void MemSet<T>(T[] array, T value, int byteSize) where T : struct {
|
||||
if (array == null) {
|
||||
throw new ArgumentNullException("array");
|
||||
}
|
||||
|
||||
int block = 32, index = 0;
|
||||
int length = Math.Min(block, array.Length);
|
||||
|
||||
//Fill the initial array
|
||||
while (index < length) {
|
||||
array[index] = value;
|
||||
index++;
|
||||
}
|
||||
|
||||
length = array.Length;
|
||||
while (index < length) {
|
||||
Buffer.BlockCopy(array, 0, array, index*byteSize, Math.Min(block, length-index)*byteSize);
|
||||
index += block;
|
||||
block *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all values in an array to a specific value faster than a loop.
|
||||
/// Only faster for large arrays. Slower for small ones.
|
||||
/// Tests indicate it becomes faster somewhere when the length of the array grows above around 100.
|
||||
/// For large arrays this can be magnitudes faster. Up to 40 times faster has been measured.
|
||||
///
|
||||
/// Note: Only works on primitive value types such as int, long, float, etc.
|
||||
///
|
||||
/// It can be efficiently got using the sizeof built-in function.
|
||||
///
|
||||
/// <code>
|
||||
/// //Set all values to 8 in the array
|
||||
/// int[] arr = new int[20000];
|
||||
/// Pathfinding.Util.Memory.MemSet<int> (arr, 8, sizeof(int));
|
||||
/// </code>
|
||||
/// See: System.Buffer.BlockCopy
|
||||
/// </summary>
|
||||
/// <param name="array">the array to fill</param>
|
||||
/// <param name="value">the value to fill the array with</param>
|
||||
/// <param name="byteSize">size in bytes of every element in the array. e.g 4 bytes for an int, or 8 bytes for a long.</param>
|
||||
/// <param name="totalSize">all indices in the range [0, totalSize-1] will be set</param>
|
||||
public static void MemSet<T>(T[] array, T value, int totalSize, int byteSize) where T : struct {
|
||||
if (array == null) {
|
||||
throw new ArgumentNullException("array");
|
||||
}
|
||||
|
||||
int block = 32, index = 0;
|
||||
int length = Math.Min(block, totalSize);
|
||||
|
||||
//Fill the initial array
|
||||
while (index < length) {
|
||||
array[index] = value;
|
||||
index++;
|
||||
}
|
||||
|
||||
length = totalSize;
|
||||
while (index < length) {
|
||||
Buffer.BlockCopy(array, 0, array, index*byteSize, Math.Min(block, totalSize-index)*byteSize);
|
||||
index += block;
|
||||
block *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new array with at most length newLength.
|
||||
/// The array will contain a copy of all elements of arr up to but excluding the index newLength.
|
||||
/// </summary>
|
||||
public static T[] ShrinkArray<T>(T[] arr, int newLength) {
|
||||
newLength = Math.Min(newLength, arr.Length);
|
||||
var shrunkArr = new T[newLength];
|
||||
Array.Copy(arr, shrunkArr, newLength);
|
||||
return shrunkArr;
|
||||
}
|
||||
|
||||
/// <summary>Swaps the variables a and b</summary>
|
||||
public static void Swap<T>(ref T a, ref T b) {
|
||||
T tmp = a;
|
||||
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
}
|
||||
}
|
7
AR/Assets/AstarPathfindingProject/Utilities/AstarMemory.cs.meta
generated
Normal file
7
AR/Assets/AstarPathfindingProject/Utilities/AstarMemory.cs.meta
generated
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9bdecddfdfec947eb8ed96282e4b1fe1
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
107
AR/Assets/AstarPathfindingProject/Utilities/AstarParallel.cs
Normal file
107
AR/Assets/AstarPathfindingProject/Utilities/AstarParallel.cs
Normal file
@ -0,0 +1,107 @@
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
#define SINGLE_THREAD
|
||||
#endif
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Pathfinding.Util {
|
||||
/// <summary>
|
||||
/// Helper for parallelizing tasks.
|
||||
/// More specifically this class is useful if the tasks need some large and slow to initialize 'scratch pad'.
|
||||
/// Using this class you can initialize a scratch pad per thread and then use the appropriate one in the task
|
||||
/// callback (which includes a thread index).
|
||||
///
|
||||
/// Any exception that is thrown in the worker threads will be propagated out to the caller of the <see cref="Run"/> method.
|
||||
/// </summary>
|
||||
public class ParallelWorkQueue<T> {
|
||||
/// <summary>
|
||||
/// Callback to run for each item in the queue.
|
||||
/// The callback takes the item as the first parameter and the thread index as the second parameter.
|
||||
/// </summary>
|
||||
public System.Action<T, int> action;
|
||||
|
||||
/// <summary>Number of threads to use</summary>
|
||||
public readonly int threadCount;
|
||||
|
||||
/// <summary>Queue of items</summary>
|
||||
readonly Queue<T> queue;
|
||||
readonly int initialCount;
|
||||
#if !SINGLE_THREAD
|
||||
ManualResetEvent[] waitEvents;
|
||||
System.Exception innerException;
|
||||
#endif
|
||||
|
||||
public ParallelWorkQueue (Queue<T> queue) {
|
||||
this.queue = queue;
|
||||
initialCount = queue.Count;
|
||||
#if SINGLE_THREAD
|
||||
threadCount = 1;
|
||||
#else
|
||||
threadCount = System.Math.Min(initialCount, System.Math.Max(1, AstarPath.CalculateThreadCount(ThreadCount.AutomaticHighLoad)));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Execute the tasks.</summary>
|
||||
/// <param name="progressTimeoutMillis">This iterator will yield approximately every progressTimeoutMillis milliseconds.
|
||||
/// This can be used to e.g show a progress bar.</param>
|
||||
public IEnumerable<int> Run (int progressTimeoutMillis) {
|
||||
if (initialCount != queue.Count) throw new System.InvalidOperationException("Queue has been modified since the constructor");
|
||||
|
||||
// Return early if there are no items in the queue.
|
||||
// This is important because WaitHandle.WaitAll with an array of length zero
|
||||
// results in weird behaviour (Microsoft's .Net returns false, Mono returns true
|
||||
// and the documentation says it should throw an exception).
|
||||
if (initialCount == 0) yield break;
|
||||
|
||||
#if SINGLE_THREAD
|
||||
// WebGL does not support multithreading so we will do everything on the main thread instead
|
||||
for (int i = 0; i < initialCount; i++) {
|
||||
action(queue.Dequeue(), 0);
|
||||
yield return i + 1;
|
||||
}
|
||||
#else
|
||||
// Fire up a bunch of threads to scan the graph in parallel
|
||||
waitEvents = new ManualResetEvent[threadCount];
|
||||
for (int i = 0; i < waitEvents.Length; i++) {
|
||||
waitEvents[i] = new ManualResetEvent(false);
|
||||
#if NETFX_CORE
|
||||
// Need to make a copy here, otherwise it may refer to some other index when the task actually runs.
|
||||
int threadIndex = i;
|
||||
System.Threading.Tasks.Task.Run(() => RunTask(threadIndex));
|
||||
#else
|
||||
ThreadPool.QueueUserWorkItem(threadIndex => RunTask((int)threadIndex), i);
|
||||
#endif
|
||||
}
|
||||
|
||||
while (!WaitHandle.WaitAll(waitEvents, progressTimeoutMillis)) {
|
||||
int count;
|
||||
lock (queue) count = queue.Count;
|
||||
yield return initialCount - count;
|
||||
}
|
||||
|
||||
if (innerException != null) throw innerException;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !SINGLE_THREAD
|
||||
void RunTask (int threadIndex) {
|
||||
try {
|
||||
while (true) {
|
||||
T tile;
|
||||
lock (queue) {
|
||||
if (queue.Count == 0) return;
|
||||
tile = queue.Dequeue();
|
||||
}
|
||||
action(tile, threadIndex);
|
||||
}
|
||||
} catch (System.Exception e) {
|
||||
innerException = e;
|
||||
// Stop the remaining threads
|
||||
lock (queue) queue.Clear();
|
||||
} finally {
|
||||
waitEvents[threadIndex].Set();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
12
AR/Assets/AstarPathfindingProject/Utilities/AstarParallel.cs.meta
generated
Normal file
12
AR/Assets/AstarPathfindingProject/Utilities/AstarParallel.cs.meta
generated
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa95ccba91f5c4ab8922aa7859f0a300
|
||||
timeCreated: 1500049783
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
225
AR/Assets/AstarPathfindingProject/Utilities/AstarProfiler.cs
Normal file
225
AR/Assets/AstarPathfindingProject/Utilities/AstarProfiler.cs
Normal file
@ -0,0 +1,225 @@
|
||||
//Uncomment the next line to enable debugging (also uncomment it in AstarPath.cs)
|
||||
//#define ProfileAstar //@SHOWINEDITOR
|
||||
//#define ASTAR_UNITY_PRO_PROFILER //@SHOWINEDITOR Requires ProfileAstar, profiles section of astar code which will show up in the Unity Pro Profiler.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
#if UNITY_5_5_OR_NEWER
|
||||
using UnityEngine.Profiling;
|
||||
#endif
|
||||
|
||||
namespace Pathfinding {
|
||||
public class AstarProfiler {
|
||||
public class ProfilePoint {
|
||||
//public DateTime lastRecorded;
|
||||
//public TimeSpan totalTime;
|
||||
public System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
|
||||
public int totalCalls;
|
||||
public long tmpBytes;
|
||||
public long totalBytes;
|
||||
}
|
||||
|
||||
static readonly Dictionary<string, ProfilePoint> profiles = new Dictionary<string, ProfilePoint>();
|
||||
static DateTime startTime = DateTime.UtcNow;
|
||||
|
||||
public static ProfilePoint[] fastProfiles;
|
||||
public static string[] fastProfileNames;
|
||||
|
||||
private AstarProfiler() {
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("ProfileAstar")]
|
||||
public static void InitializeFastProfile (string[] profileNames) {
|
||||
fastProfileNames = new string[profileNames.Length+2];
|
||||
Array.Copy(profileNames, fastProfileNames, profileNames.Length);
|
||||
fastProfileNames[fastProfileNames.Length-2] = "__Control1__";
|
||||
fastProfileNames[fastProfileNames.Length-1] = "__Control2__";
|
||||
fastProfiles = new ProfilePoint[fastProfileNames.Length];
|
||||
for (int i = 0; i < fastProfiles.Length; i++) fastProfiles[i] = new ProfilePoint();
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("ProfileAstar")]
|
||||
public static void StartFastProfile (int tag) {
|
||||
//profiles.TryGetValue(tag, out point);
|
||||
fastProfiles[tag].watch.Start();//lastRecorded = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("ProfileAstar")]
|
||||
public static void EndFastProfile (int tag) {
|
||||
/*if (!profiles.ContainsKey(tag))
|
||||
* {
|
||||
* Debug.LogError("Can only end profiling for a tag which has already been started (tag was " + tag + ")");
|
||||
* return;
|
||||
* }*/
|
||||
ProfilePoint point = fastProfiles[tag];
|
||||
|
||||
point.totalCalls++;
|
||||
point.watch.Stop();
|
||||
//DateTime now = DateTime.UtcNow;
|
||||
//point.totalTime += now - point.lastRecorded;
|
||||
//fastProfiles[tag] = point;
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("ASTAR_UNITY_PRO_PROFILER")]
|
||||
public static void EndProfile () {
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("ProfileAstar")]
|
||||
public static void StartProfile (string tag) {
|
||||
#if ASTAR_UNITY_PRO_PROFILER
|
||||
Profiler.BeginSample(tag);
|
||||
#else
|
||||
//Console.WriteLine ("Profile Start - " + tag);
|
||||
ProfilePoint point;
|
||||
|
||||
profiles.TryGetValue(tag, out point);
|
||||
if (point == null) {
|
||||
point = new ProfilePoint();
|
||||
profiles[tag] = point;
|
||||
}
|
||||
point.tmpBytes = GC.GetTotalMemory(false);
|
||||
point.watch.Start();
|
||||
//point.lastRecorded = DateTime.UtcNow;
|
||||
//Debug.Log ("Starting " + tag);
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("ProfileAstar")]
|
||||
public static void EndProfile (string tag) {
|
||||
#if !ASTAR_UNITY_PRO_PROFILER
|
||||
if (!profiles.ContainsKey(tag)) {
|
||||
Debug.LogError("Can only end profiling for a tag which has already been started (tag was " + tag + ")");
|
||||
return;
|
||||
}
|
||||
//Console.WriteLine ("Profile End - " + tag);
|
||||
//DateTime now = DateTime.UtcNow;
|
||||
ProfilePoint point = profiles[tag];
|
||||
//point.totalTime += now - point.lastRecorded;
|
||||
++point.totalCalls;
|
||||
point.watch.Stop();
|
||||
point.totalBytes += GC.GetTotalMemory(false) - point.tmpBytes;
|
||||
//profiles[tag] = point;
|
||||
//Debug.Log ("Ending " + tag);
|
||||
#else
|
||||
EndProfile();
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("ProfileAstar")]
|
||||
public static void Reset () {
|
||||
profiles.Clear();
|
||||
startTime = DateTime.UtcNow;
|
||||
|
||||
if (fastProfiles != null) {
|
||||
for (int i = 0; i < fastProfiles.Length; i++) {
|
||||
fastProfiles[i] = new ProfilePoint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("ProfileAstar")]
|
||||
public static void PrintFastResults () {
|
||||
if (fastProfiles == null)
|
||||
return;
|
||||
|
||||
StartFastProfile(fastProfiles.Length-2);
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
StartFastProfile(fastProfiles.Length-1);
|
||||
EndFastProfile(fastProfiles.Length-1);
|
||||
}
|
||||
EndFastProfile(fastProfiles.Length-2);
|
||||
|
||||
double avgOverhead = fastProfiles[fastProfiles.Length-2].watch.Elapsed.TotalMilliseconds / 1000.0;
|
||||
|
||||
TimeSpan endTime = DateTime.UtcNow - startTime;
|
||||
var output = new System.Text.StringBuilder();
|
||||
output.Append("============================\n\t\t\t\tProfile results:\n============================\n");
|
||||
output.Append("Name | Total Time | Total Calls | Avg/Call | Bytes");
|
||||
//foreach(KeyValuePair<string, ProfilePoint> pair in profiles)
|
||||
for (int i = 0; i < fastProfiles.Length; i++) {
|
||||
string name = fastProfileNames[i];
|
||||
ProfilePoint value = fastProfiles[i];
|
||||
|
||||
int totalCalls = value.totalCalls;
|
||||
double totalTime = value.watch.Elapsed.TotalMilliseconds - avgOverhead*totalCalls;
|
||||
|
||||
if (totalCalls < 1) continue;
|
||||
|
||||
|
||||
output.Append("\n").Append(name.PadLeft(10)).Append("| ");
|
||||
output.Append(totalTime.ToString("0.0 ").PadLeft(10)).Append(value.watch.Elapsed.TotalMilliseconds.ToString("(0.0)").PadLeft(10)).Append("| ");
|
||||
output.Append(totalCalls.ToString().PadLeft(10)).Append("| ");
|
||||
output.Append((totalTime / totalCalls).ToString("0.000").PadLeft(10));
|
||||
|
||||
|
||||
/* output.Append("\nProfile");
|
||||
* output.Append(name);
|
||||
* output.Append(" took \t");
|
||||
* output.Append(totalTime.ToString("0.0"));
|
||||
* output.Append(" ms to complete over ");
|
||||
* output.Append(totalCalls);
|
||||
* output.Append(" iteration");
|
||||
* if (totalCalls != 1) output.Append("s");
|
||||
* output.Append(", averaging \t");
|
||||
* output.Append((totalTime / totalCalls).ToString("0.000"));
|
||||
* output.Append(" ms per call"); */
|
||||
}
|
||||
output.Append("\n\n============================\n\t\tTotal runtime: ");
|
||||
output.Append(endTime.TotalSeconds.ToString("F3"));
|
||||
output.Append(" seconds\n============================");
|
||||
Debug.Log(output.ToString());
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("ProfileAstar")]
|
||||
public static void PrintResults () {
|
||||
TimeSpan endTime = DateTime.UtcNow - startTime;
|
||||
var output = new System.Text.StringBuilder();
|
||||
|
||||
output.Append("============================\n\t\t\t\tProfile results:\n============================\n");
|
||||
|
||||
int maxLength = 5;
|
||||
foreach (KeyValuePair<string, ProfilePoint> pair in profiles) {
|
||||
maxLength = Math.Max(pair.Key.Length, maxLength);
|
||||
}
|
||||
|
||||
output.Append(" Name ".PadRight(maxLength)).
|
||||
Append("|").Append(" Total Time ".PadRight(20)).
|
||||
Append("|").Append(" Total Calls ".PadRight(20)).
|
||||
Append("|").Append(" Avg/Call ".PadRight(20));
|
||||
|
||||
|
||||
|
||||
foreach (var pair in profiles) {
|
||||
double totalTime = pair.Value.watch.Elapsed.TotalMilliseconds;
|
||||
int totalCalls = pair.Value.totalCalls;
|
||||
if (totalCalls < 1) continue;
|
||||
|
||||
string name = pair.Key;
|
||||
|
||||
output.Append("\n").Append(name.PadRight(maxLength)).Append("| ");
|
||||
output.Append(totalTime.ToString("0.0").PadRight(20)).Append("| ");
|
||||
output.Append(totalCalls.ToString().PadRight(20)).Append("| ");
|
||||
output.Append((totalTime / totalCalls).ToString("0.000").PadRight(20));
|
||||
output.Append(AstarMath.FormatBytesBinary((int)pair.Value.totalBytes).PadLeft(10));
|
||||
|
||||
/*output.Append("\nProfile ");
|
||||
* output.Append(pair.Key);
|
||||
* output.Append(" took ");
|
||||
* output.Append(totalTime.ToString("0"));
|
||||
* output.Append(" ms to complete over ");
|
||||
* output.Append(totalCalls);
|
||||
* output.Append(" iteration");
|
||||
* if (totalCalls != 1) output.Append("s");
|
||||
* output.Append(", averaging ");
|
||||
* output.Append((totalTime / totalCalls).ToString("0.0"));
|
||||
* output.Append(" ms per call");*/
|
||||
}
|
||||
output.Append("\n\n============================\n\t\tTotal runtime: ");
|
||||
output.Append(endTime.TotalSeconds.ToString("F3"));
|
||||
output.Append(" seconds\n============================");
|
||||
Debug.Log(output.ToString());
|
||||
}
|
||||
}
|
||||
}
|
7
AR/Assets/AstarPathfindingProject/Utilities/AstarProfiler.cs.meta
generated
Normal file
7
AR/Assets/AstarPathfindingProject/Utilities/AstarProfiler.cs.meta
generated
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d7c020300da24330bbc70ec30fee5d5
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,163 @@
|
||||
namespace Pathfinding.Util {
|
||||
/// <summary>
|
||||
/// Simple implementation of a GUID.
|
||||
/// Version: Since 3.6.4 this struct works properly on platforms with different endianness such as Wii U.
|
||||
/// </summary>
|
||||
public struct Guid {
|
||||
const string hex = "0123456789ABCDEF";
|
||||
|
||||
public static readonly Guid zero = new Guid(new byte[16]);
|
||||
public static readonly string zeroString = new Guid(new byte[16]).ToString();
|
||||
|
||||
readonly ulong _a, _b;
|
||||
|
||||
public Guid (byte[] bytes) {
|
||||
// Pack 128 bits into 2 longs
|
||||
ulong a = ((ulong)bytes[0] << 8*0) |
|
||||
((ulong)bytes[1] << 8*1) |
|
||||
((ulong)bytes[2] << 8*2) |
|
||||
((ulong)bytes[3] << 8*3) |
|
||||
((ulong)bytes[4] << 8*4) |
|
||||
((ulong)bytes[5] << 8*5) |
|
||||
((ulong)bytes[6] << 8*6) |
|
||||
((ulong)bytes[7] << 8*7);
|
||||
|
||||
ulong b = ((ulong)bytes[8] << 8*0) |
|
||||
((ulong)bytes[9] << 8*1) |
|
||||
((ulong)bytes[10] << 8*2) |
|
||||
((ulong)bytes[11] << 8*3) |
|
||||
((ulong)bytes[12] << 8*4) |
|
||||
((ulong)bytes[13] << 8*5) |
|
||||
((ulong)bytes[14] << 8*6) |
|
||||
((ulong)bytes[15] << 8*7);
|
||||
|
||||
// Need to swap endianness on e.g Wii U
|
||||
_a = System.BitConverter.IsLittleEndian ? a : SwapEndianness(a);
|
||||
_b = System.BitConverter.IsLittleEndian ? b : SwapEndianness(b);
|
||||
}
|
||||
|
||||
public Guid (string str) {
|
||||
_a = 0;
|
||||
_b = 0;
|
||||
|
||||
if (str.Length < 32)
|
||||
throw new System.FormatException("Invalid Guid format");
|
||||
|
||||
int counter = 0;
|
||||
int i = 0;
|
||||
int offset = 15*4;
|
||||
|
||||
for (; counter < 16; i++) {
|
||||
if (i >= str.Length)
|
||||
throw new System.FormatException("Invalid Guid format. String too short");
|
||||
|
||||
char c = str[i];
|
||||
if (c == '-') continue;
|
||||
|
||||
//Neat trick, perhaps a bit slow, but one will probably not use Guid parsing that much
|
||||
int value = hex.IndexOf(char.ToUpperInvariant(c));
|
||||
if (value == -1)
|
||||
throw new System.FormatException("Invalid Guid format : "+c+" is not a hexadecimal character");
|
||||
|
||||
_a |= (ulong)value << offset;
|
||||
//SetByte (counter,(byte)value);
|
||||
offset -= 4;
|
||||
counter++;
|
||||
}
|
||||
|
||||
offset = 15*4;
|
||||
for (; counter < 32; i++) {
|
||||
if (i >= str.Length)
|
||||
throw new System.FormatException("Invalid Guid format. String too short");
|
||||
|
||||
char c = str[i];
|
||||
if (c == '-') continue;
|
||||
|
||||
//Neat trick, perhaps a bit slow, but one will probably not use Guid parsing that much
|
||||
int value = hex.IndexOf(char.ToUpperInvariant(c));
|
||||
if (value == -1)
|
||||
throw new System.FormatException("Invalid Guid format : "+c+" is not a hexadecimal character");
|
||||
|
||||
_b |= (ulong)value << offset;
|
||||
//SetByte (counter,(byte)value);
|
||||
offset -= 4;
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
public static Guid Parse (string input) {
|
||||
return new Guid(input);
|
||||
}
|
||||
|
||||
/// <summary>Swaps between little and big endian</summary>
|
||||
static ulong SwapEndianness (ulong value) {
|
||||
var b1 = (value >> 0) & 0xff;
|
||||
var b2 = (value >> 8) & 0xff;
|
||||
var b3 = (value >> 16) & 0xff;
|
||||
var b4 = (value >> 24) & 0xff;
|
||||
var b5 = (value >> 32) & 0xff;
|
||||
var b6 = (value >> 40) & 0xff;
|
||||
var b7 = (value >> 48) & 0xff;
|
||||
var b8 = (value >> 56) & 0xff;
|
||||
|
||||
return b1 << 56 | b2 << 48 | b3 << 40 | b4 << 32 | b5 << 24 | b6 << 16 | b7 << 8 | b8 << 0;
|
||||
}
|
||||
|
||||
public byte[] ToByteArray () {
|
||||
var bytes = new byte[16];
|
||||
|
||||
byte[] ba = System.BitConverter.GetBytes(!System.BitConverter.IsLittleEndian ? SwapEndianness(_a) : _a);
|
||||
byte[] bb = System.BitConverter.GetBytes(!System.BitConverter.IsLittleEndian ? SwapEndianness(_b) : _b);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bytes[i] = ba[i];
|
||||
bytes[i+8] = bb[i];
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static System.Random random = new System.Random();
|
||||
|
||||
public static Guid NewGuid () {
|
||||
var bytes = new byte[16];
|
||||
|
||||
random.NextBytes(bytes);
|
||||
return new Guid(bytes);
|
||||
}
|
||||
|
||||
public static bool operator == (Guid lhs, Guid rhs) {
|
||||
return lhs._a == rhs._a && lhs._b == rhs._b;
|
||||
}
|
||||
|
||||
public static bool operator != (Guid lhs, Guid rhs) {
|
||||
return lhs._a != rhs._a || lhs._b != rhs._b;
|
||||
}
|
||||
|
||||
public override bool Equals (System.Object _rhs) {
|
||||
if (!(_rhs is Guid)) return false;
|
||||
|
||||
var rhs = (Guid)_rhs;
|
||||
|
||||
return _a == rhs._a && _b == rhs._b;
|
||||
}
|
||||
|
||||
public override int GetHashCode () {
|
||||
ulong ab = _a ^ _b;
|
||||
|
||||
return (int)(ab >> 32) ^ (int)ab;
|
||||
}
|
||||
|
||||
private static System.Text.StringBuilder text;
|
||||
|
||||
public override string ToString () {
|
||||
if (text == null) {
|
||||
text = new System.Text.StringBuilder();
|
||||
}
|
||||
lock (text) {
|
||||
text.Length = 0;
|
||||
text.Append(_a.ToString("x16")).Append('-').Append(_b.ToString("x16"));
|
||||
return text.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
AR/Assets/AstarPathfindingProject/Utilities/DotNetReplacements.cs.meta
generated
Normal file
7
AR/Assets/AstarPathfindingProject/Utilities/DotNetReplacements.cs.meta
generated
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c306b1bfc824f40909c5a91e06e4090f
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,235 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Attach this script to any obstacle with a collider to enable dynamic updates of the graphs around it.
|
||||
/// When the object has moved or rotated at least <see cref="updateError"/> world units
|
||||
/// then it will call AstarPath.UpdateGraphs and update the graph around it.
|
||||
///
|
||||
/// Make sure that any children colliders do not extend beyond the bounds of the collider attached to the
|
||||
/// GameObject that the DynamicGridObstacle component is attached to since this script only updates the graph
|
||||
/// around the bounds of the collider on the same GameObject.
|
||||
///
|
||||
/// An update will be triggered whenever the bounding box of the attached collider has changed (moved/expanded/etc.) by at least <see cref="updateError"/> world units or if
|
||||
/// the GameObject has rotated enough so that the outmost point of the object has moved at least <see cref="updateError"/> world units.
|
||||
///
|
||||
/// This script works with both 2D colliders and normal 3D colliders.
|
||||
///
|
||||
/// Note: This script works best with a GridGraph, PointGraph or LayerGridGraph
|
||||
/// You can use this with recast graphs as well. However since recast graph updates are much slower it is recommended to use the <see cref="Pathfinding.NavmeshCut"/> component if at all possible.
|
||||
///
|
||||
/// See: AstarPath.UpdateGraphs
|
||||
/// See: graph-updates (view in online documentation for working links)
|
||||
/// See: navmeshcutting (view in online documentation for working links)
|
||||
/// </summary>
|
||||
[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_dynamic_grid_obstacle.php")]
|
||||
public class DynamicGridObstacle : GraphModifier {
|
||||
/// <summary>Collider to get bounds information from</summary>
|
||||
Collider coll;
|
||||
|
||||
/// <summary>2D Collider to get bounds information from</summary>
|
||||
Collider2D coll2D;
|
||||
|
||||
/// <summary>Cached transform component</summary>
|
||||
Transform tr;
|
||||
|
||||
/// <summary>The minimum change in world units along one of the axis of the bounding box of the collider to trigger a graph update</summary>
|
||||
public float updateError = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Time in seconds between bounding box checks.
|
||||
/// If AstarPath.batchGraphUpdates is enabled, it is not beneficial to have a checkTime much lower
|
||||
/// than AstarPath.graphUpdateBatchingInterval because that will just add extra unnecessary graph updates.
|
||||
///
|
||||
/// In real time seconds (based on Time.realtimeSinceStartup).
|
||||
/// </summary>
|
||||
public float checkTime = 0.2F;
|
||||
|
||||
/// <summary>Bounds of the collider the last time the graphs were updated</summary>
|
||||
Bounds prevBounds;
|
||||
|
||||
/// <summary>Rotation of the collider the last time the graphs were updated</summary>
|
||||
Quaternion prevRotation;
|
||||
|
||||
/// <summary>True if the collider was enabled last time the graphs were updated</summary>
|
||||
bool prevEnabled;
|
||||
|
||||
float lastCheckTime = -9999;
|
||||
Queue<GraphUpdateObject> pendingGraphUpdates = new Queue<GraphUpdateObject>();
|
||||
|
||||
Bounds bounds {
|
||||
get {
|
||||
if (coll != null) {
|
||||
return coll.bounds;
|
||||
} else {
|
||||
var b = coll2D.bounds;
|
||||
// Make sure the bounding box stretches close to infinitely along the Z axis (which is the axis perpendicular to the 2D plane).
|
||||
// We don't want any change along the Z axis to make a difference.
|
||||
b.extents += new Vector3(0, 0, 10000);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool colliderEnabled {
|
||||
get {
|
||||
return coll != null ? coll.enabled : coll2D.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Awake () {
|
||||
base.Awake();
|
||||
|
||||
coll = GetComponent<Collider>();
|
||||
coll2D = GetComponent<Collider2D>();
|
||||
tr = transform;
|
||||
if (coll == null && coll2D == null && Application.isPlaying) {
|
||||
throw new System.Exception("A collider or 2D collider must be attached to the GameObject(" + gameObject.name + ") for the DynamicGridObstacle to work");
|
||||
}
|
||||
|
||||
prevBounds = bounds;
|
||||
prevRotation = tr.rotation;
|
||||
// Make sure we update the graph as soon as we find that the collider is enabled
|
||||
prevEnabled = false;
|
||||
}
|
||||
|
||||
public override void OnPostScan () {
|
||||
// Make sure we find the collider
|
||||
// AstarPath.Awake may run before Awake on this component
|
||||
if (coll == null) Awake();
|
||||
|
||||
// In case the object was in the scene from the start and the graphs
|
||||
// were scanned then we ignore the first update since it is unnecessary.
|
||||
if (coll != null) prevEnabled = colliderEnabled;
|
||||
}
|
||||
|
||||
void Update () {
|
||||
if (!Application.isPlaying) return;
|
||||
|
||||
if (coll == null && coll2D == null) {
|
||||
Debug.LogError("Removed collider from DynamicGridObstacle", this);
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the previous graph updates have been completed yet.
|
||||
// We don't want to update the graph again until the last graph updates are done.
|
||||
// This is particularly important for recast graphs for which graph updates can take a long time.
|
||||
while (pendingGraphUpdates.Count > 0 && pendingGraphUpdates.Peek().stage != GraphUpdateStage.Pending) {
|
||||
pendingGraphUpdates.Dequeue();
|
||||
}
|
||||
|
||||
if (AstarPath.active == null || AstarPath.active.isScanning || Time.realtimeSinceStartup - lastCheckTime < checkTime || !Application.isPlaying || pendingGraphUpdates.Count > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastCheckTime = Time.realtimeSinceStartup;
|
||||
if (colliderEnabled) {
|
||||
// The current bounds of the collider
|
||||
Bounds newBounds = bounds;
|
||||
var newRotation = tr.rotation;
|
||||
|
||||
Vector3 minDiff = prevBounds.min - newBounds.min;
|
||||
Vector3 maxDiff = prevBounds.max - newBounds.max;
|
||||
|
||||
var extents = newBounds.extents.magnitude;
|
||||
// This is the distance that a point furthest out on the bounding box
|
||||
// would have moved due to the changed rotation of the object
|
||||
var errorFromRotation = extents*Quaternion.Angle(prevRotation, newRotation)*Mathf.Deg2Rad;
|
||||
|
||||
// If the difference between the previous bounds and the new bounds is greater than some value, update the graphs
|
||||
if (minDiff.sqrMagnitude > updateError*updateError || maxDiff.sqrMagnitude > updateError*updateError ||
|
||||
errorFromRotation > updateError || !prevEnabled) {
|
||||
// Update the graphs as soon as possible
|
||||
DoUpdateGraphs();
|
||||
}
|
||||
} else {
|
||||
// Collider has just been disabled
|
||||
if (prevEnabled) {
|
||||
DoUpdateGraphs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revert graphs when disabled.
|
||||
/// When the DynamicObstacle is disabled or destroyed, a last graph update should be done to revert nodes to their original state
|
||||
/// </summary>
|
||||
protected override void OnDisable () {
|
||||
base.OnDisable();
|
||||
if (AstarPath.active != null && Application.isPlaying) {
|
||||
var guo = new GraphUpdateObject(prevBounds);
|
||||
pendingGraphUpdates.Enqueue(guo);
|
||||
AstarPath.active.UpdateGraphs(guo);
|
||||
prevEnabled = false;
|
||||
}
|
||||
|
||||
// Stop caring about pending graph updates if this object is disabled.
|
||||
// This avoids a memory leak since `Update` will never be called again to remove pending updates
|
||||
// that have been completed.
|
||||
pendingGraphUpdates.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the graphs around this object.
|
||||
/// Note: The graphs will not be updated immediately since the pathfinding threads need to be paused first.
|
||||
/// If you want to guarantee that the graphs have been updated then call AstarPath.active.FlushGraphUpdates()
|
||||
/// after the call to this method.
|
||||
/// </summary>
|
||||
public void DoUpdateGraphs () {
|
||||
if (coll == null && coll2D == null) return;
|
||||
|
||||
// Required to ensure we get the most up to date bounding box from the physics engine
|
||||
UnityEngine.Physics.SyncTransforms();
|
||||
UnityEngine.Physics2D.SyncTransforms();
|
||||
|
||||
if (!colliderEnabled) {
|
||||
// If the collider is not enabled, then col.bounds will empty
|
||||
// so just update prevBounds
|
||||
var guo = new GraphUpdateObject(prevBounds);
|
||||
pendingGraphUpdates.Enqueue(guo);
|
||||
AstarPath.active.UpdateGraphs(guo);
|
||||
} else {
|
||||
Bounds newBounds = bounds;
|
||||
|
||||
Bounds merged = newBounds;
|
||||
merged.Encapsulate(prevBounds);
|
||||
|
||||
// Check what seems to be fastest, to update the union of prevBounds and newBounds in a single request
|
||||
// or to update them separately, the smallest volume is usually the fastest
|
||||
if (BoundsVolume(merged) < BoundsVolume(newBounds) + BoundsVolume(prevBounds)) {
|
||||
// Send an update request to update the nodes inside the 'merged' volume
|
||||
var guo = new GraphUpdateObject(merged);
|
||||
pendingGraphUpdates.Enqueue(guo);
|
||||
AstarPath.active.UpdateGraphs(guo);
|
||||
} else {
|
||||
// Send two update request to update the nodes inside the 'prevBounds' and 'newBounds' volumes
|
||||
var guo1 = new GraphUpdateObject(prevBounds);
|
||||
var guo2 = new GraphUpdateObject(newBounds);
|
||||
pendingGraphUpdates.Enqueue(guo1);
|
||||
pendingGraphUpdates.Enqueue(guo2);
|
||||
AstarPath.active.UpdateGraphs(guo1);
|
||||
AstarPath.active.UpdateGraphs(guo2);
|
||||
}
|
||||
|
||||
#if ASTARDEBUG
|
||||
Debug.DrawLine(prevBounds.min, prevBounds.max, Color.yellow);
|
||||
Debug.DrawLine(newBounds.min, newBounds.max, Color.red);
|
||||
#endif
|
||||
prevBounds = newBounds;
|
||||
}
|
||||
|
||||
prevEnabled = colliderEnabled;
|
||||
prevRotation = tr.rotation;
|
||||
|
||||
// Set this here as well since the DoUpdateGraphs method can be called from other scripts
|
||||
lastCheckTime = Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
/// <summary>Volume of a Bounds object. X*Y*Z</summary>
|
||||
static float BoundsVolume (Bounds b) {
|
||||
return System.Math.Abs(b.size.x * b.size.y * b.size.z);
|
||||
}
|
||||
}
|
||||
}
|
12
AR/Assets/AstarPathfindingProject/Utilities/DynamicGridObstacle.cs.meta
generated
Normal file
12
AR/Assets/AstarPathfindingProject/Utilities/DynamicGridObstacle.cs.meta
generated
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 183748dd497aa473a98ff0cd8cb67fc5
|
||||
timeCreated: 1490044676
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -220
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
432
AR/Assets/AstarPathfindingProject/Utilities/Funnel.cs
Normal file
432
AR/Assets/AstarPathfindingProject/Utilities/Funnel.cs
Normal file
@ -0,0 +1,432 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding {
|
||||
using Pathfinding.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the funnel algorithm as well as various related methods.
|
||||
/// See: http://digestingduck.blogspot.se/2010/03/simple-stupid-funnel-algorithm.html
|
||||
/// See: FunnelModifier for the component that you can attach to objects to use the funnel algorithm.
|
||||
/// </summary>
|
||||
public class Funnel {
|
||||
/// <summary>Funnel in which the path to the target will be</summary>
|
||||
public struct FunnelPortals {
|
||||
public List<Vector3> left;
|
||||
public List<Vector3> right;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Part of a path.
|
||||
/// This is either a sequence of adjacent triangles
|
||||
/// or a link.
|
||||
/// See: NodeLink2
|
||||
/// </summary>
|
||||
public struct PathPart {
|
||||
/// <summary>Index of the first node in this part</summary>
|
||||
public int startIndex;
|
||||
/// <summary>Index of the last node in this part</summary>
|
||||
public int endIndex;
|
||||
public Vector3 startPoint, endPoint;
|
||||
public bool isLink;
|
||||
}
|
||||
|
||||
public static List<PathPart> SplitIntoParts (Path path) {
|
||||
var nodes = path.path;
|
||||
|
||||
var result = ListPool<PathPart>.Claim();
|
||||
|
||||
if (nodes == null || nodes.Count == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Loop through the path and split it into
|
||||
// parts joined by links
|
||||
for (int i = 0; i < nodes.Count; i++) {
|
||||
if (nodes[i] is TriangleMeshNode || nodes[i] is GridNodeBase) {
|
||||
var part = new PathPart();
|
||||
part.startIndex = i;
|
||||
uint currentGraphIndex = nodes[i].GraphIndex;
|
||||
|
||||
// Loop up until we find a node in another graph
|
||||
// Ignore NodeLink3 nodes
|
||||
for (; i < nodes.Count; i++) {
|
||||
if (nodes[i].GraphIndex != currentGraphIndex && !(nodes[i] is NodeLink3Node)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i--;
|
||||
part.endIndex = i;
|
||||
|
||||
// If this is the first part in the path, use the exact start point
|
||||
// otherwise use the position of the node right before the start of this
|
||||
// part which is likely the end of the link to this part
|
||||
if (part.startIndex == 0) {
|
||||
part.startPoint = path.vectorPath[0];
|
||||
} else {
|
||||
part.startPoint = (Vector3)nodes[part.startIndex-1].position;
|
||||
}
|
||||
|
||||
if (part.endIndex == nodes.Count-1) {
|
||||
part.endPoint = path.vectorPath[path.vectorPath.Count-1];
|
||||
} else {
|
||||
part.endPoint = (Vector3)nodes[part.endIndex+1].position;
|
||||
}
|
||||
|
||||
result.Add(part);
|
||||
} else if (NodeLink2.GetNodeLink(nodes[i]) != null) {
|
||||
var part = new PathPart();
|
||||
part.startIndex = i;
|
||||
var currentGraphIndex = nodes[i].GraphIndex;
|
||||
|
||||
for (i++; i < nodes.Count; i++) {
|
||||
if (nodes[i].GraphIndex != currentGraphIndex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
i--;
|
||||
|
||||
if (i - part.startIndex == 0) {
|
||||
// Just ignore it, it might be the case that a NodeLink was the closest node
|
||||
continue;
|
||||
} else if (i - part.startIndex != 1) {
|
||||
throw new System.Exception("NodeLink2 link length greater than two (2) nodes. " + (i - part.startIndex + 1));
|
||||
}
|
||||
|
||||
part.endIndex = i;
|
||||
part.isLink = true;
|
||||
part.startPoint = (Vector3)nodes[part.startIndex].position;
|
||||
part.endPoint = (Vector3)nodes[part.endIndex].position;
|
||||
result.Add(part);
|
||||
} else {
|
||||
throw new System.Exception("Unsupported node type or null node");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static FunnelPortals ConstructFunnelPortals (List<GraphNode> nodes, PathPart part) {
|
||||
if (nodes == null || nodes.Count == 0) {
|
||||
return new FunnelPortals { left = ListPool<Vector3>.Claim(0), right = ListPool<Vector3>.Claim(0) };
|
||||
}
|
||||
|
||||
if (part.endIndex < part.startIndex || part.startIndex < 0 || part.endIndex > nodes.Count) throw new System.ArgumentOutOfRangeException();
|
||||
|
||||
// Claim temporary lists and try to find lists with a high capacity
|
||||
var left = ListPool<Vector3>.Claim(nodes.Count+1);
|
||||
var right = ListPool<Vector3>.Claim(nodes.Count+1);
|
||||
|
||||
// Add start point
|
||||
left.Add(part.startPoint);
|
||||
right.Add(part.startPoint);
|
||||
|
||||
// Loop through all nodes in the path (except the last one)
|
||||
for (int i = part.startIndex; i < part.endIndex; i++) {
|
||||
// Get the portal between path[i] and path[i+1] and add it to the left and right lists
|
||||
bool portalWasAdded = nodes[i].GetPortal(nodes[i+1], left, right, false);
|
||||
|
||||
if (!portalWasAdded) {
|
||||
// Fallback, just use the positions of the nodes
|
||||
left.Add((Vector3)nodes[i].position);
|
||||
right.Add((Vector3)nodes[i].position);
|
||||
|
||||
left.Add((Vector3)nodes[i+1].position);
|
||||
right.Add((Vector3)nodes[i+1].position);
|
||||
}
|
||||
}
|
||||
|
||||
// Add end point
|
||||
left.Add(part.endPoint);
|
||||
right.Add(part.endPoint);
|
||||
|
||||
return new FunnelPortals { left = left, right = right };
|
||||
}
|
||||
|
||||
public static void ShrinkPortals (FunnelPortals portals, float shrink) {
|
||||
if (shrink <= 0.00001f) return;
|
||||
|
||||
for (int i = 0; i < portals.left.Count; i++) {
|
||||
var left = portals.left[i];
|
||||
var right = portals.right[i];
|
||||
|
||||
var length = (left - right).magnitude;
|
||||
if (length > 0) {
|
||||
float s = Mathf.Min(shrink / length, 0.4f);
|
||||
portals.left[i] = Vector3.Lerp(left, right, s);
|
||||
portals.right[i] = Vector3.Lerp(left, right, 1 - s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool UnwrapHelper (Vector3 portalStart, Vector3 portalEnd, Vector3 prevPoint, Vector3 nextPoint, ref Quaternion mRot, ref Vector3 mOffset) {
|
||||
// Skip the point if it was on the rotation axis
|
||||
if (VectorMath.IsColinear(portalStart, portalEnd, nextPoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var axis = portalEnd - portalStart;
|
||||
var sqrMagn = axis.sqrMagnitude;
|
||||
prevPoint -= Vector3.Dot(prevPoint - portalStart, axis)/sqrMagn * axis;
|
||||
nextPoint -= Vector3.Dot(nextPoint - portalStart, axis)/sqrMagn * axis;
|
||||
var rot = Quaternion.FromToRotation(nextPoint - portalStart, portalStart - prevPoint);
|
||||
|
||||
// The code below is equivalent to these matrix operations (but a lot faster)
|
||||
// This represents a rotation around a line in 3D space
|
||||
//mat = mat * Matrix4x4.TRS(portalStart, rot, Vector3.one) * Matrix4x4.TRS(-portalStart, Quaternion.identity, Vector3.one);
|
||||
mOffset += mRot * (portalStart - rot * portalStart);
|
||||
mRot *= rot;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unwraps the funnel portals from 3D space to 2D space.
|
||||
/// The result is stored in the left and right arrays which must be at least as large as the funnel.left and funnel.right lists.
|
||||
///
|
||||
/// The input is a funnel like in the image below. It may be rotated and twisted.
|
||||
/// [Open online documentation to see images]
|
||||
/// The output will be a funnel in 2D space like in the image below. All twists and bends will have been straightened out.
|
||||
/// [Open online documentation to see images]
|
||||
///
|
||||
/// See: <see cref="Calculate(FunnelPortals,bool,bool)"/>
|
||||
/// </summary>
|
||||
public static void Unwrap (FunnelPortals funnel, Vector2[] left, Vector2[] right) {
|
||||
int startingIndex = 1;
|
||||
var normal = Vector3.Cross(funnel.right[1] - funnel.left[0], funnel.left[1] - funnel.left[0]);
|
||||
|
||||
// This handles the case when the starting point is colinear with the first portal.
|
||||
// Note that left.Length is only guaranteed to be at least as large as funnel.left.Count, it may be larger.
|
||||
while (normal.sqrMagnitude <= 0.00000001f && startingIndex + 1 < funnel.left.Count) {
|
||||
startingIndex++;
|
||||
normal = Vector3.Cross(funnel.right[startingIndex] - funnel.left[0], funnel.left[startingIndex] - funnel.left[0]);
|
||||
}
|
||||
|
||||
left[0] = right[0] = Vector2.zero;
|
||||
|
||||
var portalLeft = funnel.left[1];
|
||||
var portalRight = funnel.right[1];
|
||||
var prevPoint = funnel.left[0];
|
||||
|
||||
// The code below is equivalent to this matrix (but a lot faster)
|
||||
// This represents a rotation around a line in 3D space
|
||||
// Matrix4x4 m = Matrix4x4.TRS(Vector3.zero, Quaternion.FromToRotation(normal, Vector3.forward), Vector3.one) * Matrix4x4.TRS(-funnel.right[0], Quaternion.identity, Vector3.one);
|
||||
Quaternion mRot = Quaternion.FromToRotation(normal, Vector3.forward);
|
||||
Vector3 mOffset = mRot * (-funnel.right[0]);
|
||||
|
||||
for (int i = 1; i < funnel.left.Count; i++) {
|
||||
if (UnwrapHelper(portalLeft, portalRight, prevPoint, funnel.left[i], ref mRot, ref mOffset)) {
|
||||
prevPoint = portalLeft;
|
||||
portalLeft = funnel.left[i];
|
||||
}
|
||||
|
||||
left[i] = mRot * funnel.left[i] + mOffset;
|
||||
|
||||
if (UnwrapHelper(portalLeft, portalRight, prevPoint, funnel.right[i], ref mRot, ref mOffset)) {
|
||||
prevPoint = portalRight;
|
||||
portalRight = funnel.right[i];
|
||||
}
|
||||
|
||||
right[i] = mRot * funnel.right[i] + mOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to fix degenerate or invalid funnels.
|
||||
/// Returns: The number of vertices at the start of both arrays that should be ignored or -1 if the algorithm failed.
|
||||
/// </summary>
|
||||
static int FixFunnel (Vector2[] left, Vector2[] right, int numPortals) {
|
||||
if (numPortals > left.Length || numPortals > right.Length) throw new System.ArgumentException("Arrays do not have as many elements as specified");
|
||||
|
||||
if (numPortals < 3) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Remove duplicate vertices
|
||||
int startIndex = 0;
|
||||
while (left[startIndex + 1] == left[startIndex + 2] && right[startIndex + 1] == right[startIndex + 2]) {
|
||||
// Equivalent to RemoveAt(1) if they would have been lists
|
||||
left[startIndex + 1] = left[startIndex + 0];
|
||||
right[startIndex + 1] = right[startIndex + 0];
|
||||
startIndex++;
|
||||
|
||||
if (numPortals - startIndex < 3) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
protected static Vector2 ToXZ (Vector3 p) {
|
||||
return new Vector2(p.x, p.z);
|
||||
}
|
||||
|
||||
protected static Vector3 FromXZ (Vector2 p) {
|
||||
return new Vector3(p.x, 0, p.y);
|
||||
}
|
||||
|
||||
/// <summary>True if b is to the right of or on the line from (0,0) to a</summary>
|
||||
protected static bool RightOrColinear (Vector2 a, Vector2 b) {
|
||||
return (a.x*b.y - b.x*a.y) <= 0;
|
||||
}
|
||||
|
||||
/// <summary>True if b is to the left of or on the line from (0,0) to a</summary>
|
||||
protected static bool LeftOrColinear (Vector2 a, Vector2 b) {
|
||||
return (a.x*b.y - b.x*a.y) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the shortest path through the funnel.
|
||||
///
|
||||
/// If the unwrap option is disabled the funnel will simply be projected onto the XZ plane.
|
||||
/// If the unwrap option is enabled then the funnel may be oriented arbitrarily and may have twists and bends.
|
||||
/// This makes it possible to support the funnel algorithm in XY space as well as in more complicated cases, such
|
||||
/// as on curved worlds.
|
||||
/// [Open online documentation to see images]
|
||||
///
|
||||
/// [Open online documentation to see images]
|
||||
///
|
||||
/// See: Unwrap
|
||||
/// </summary>
|
||||
/// <param name="funnel">The portals of the funnel. The first and last vertices portals must be single points (so for example left[0] == right[0]).</param>
|
||||
/// <param name="unwrap">Determines if twists and bends should be straightened out before running the funnel algorithm.</param>
|
||||
/// <param name="splitAtEveryPortal">If true, then a vertex will be inserted every time the path crosses a portal
|
||||
/// instead of only at the corners of the path. The result will have exactly one vertex per portal if this is enabled.
|
||||
/// This may introduce vertices with the same position in the output (esp. in corners where many portals meet).</param>
|
||||
public static List<Vector3> Calculate (FunnelPortals funnel, bool unwrap, bool splitAtEveryPortal) {
|
||||
if (funnel.left.Count != funnel.right.Count) throw new System.ArgumentException("funnel.left.Count != funnel.right.Count");
|
||||
|
||||
// Get arrays at least as large as the number of portals
|
||||
var leftArr = ArrayPool<Vector2>.Claim(funnel.left.Count);
|
||||
var rightArr = ArrayPool<Vector2>.Claim(funnel.left.Count);
|
||||
|
||||
if (unwrap) {
|
||||
Unwrap(funnel, leftArr, rightArr);
|
||||
} else {
|
||||
// Copy to arrays
|
||||
for (int i = 0; i < funnel.left.Count; i++) {
|
||||
leftArr[i] = ToXZ(funnel.left[i]);
|
||||
rightArr[i] = ToXZ(funnel.right[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int startIndex = FixFunnel(leftArr, rightArr, funnel.left.Count);
|
||||
var intermediateResult = ListPool<int>.Claim();
|
||||
if (startIndex == -1) {
|
||||
// If funnel algorithm failed, fall back to a simple line
|
||||
intermediateResult.Add(0);
|
||||
intermediateResult.Add(funnel.left.Count - 1);
|
||||
} else {
|
||||
bool lastCorner;
|
||||
Calculate(leftArr, rightArr, funnel.left.Count, startIndex, intermediateResult, int.MaxValue, out lastCorner);
|
||||
}
|
||||
|
||||
// Get list for the final result
|
||||
var result = ListPool<Vector3>.Claim(intermediateResult.Count);
|
||||
|
||||
Vector2 prev2D = leftArr[0];
|
||||
var prevIdx = 0;
|
||||
for (int i = 0; i < intermediateResult.Count; i++) {
|
||||
var idx = intermediateResult[i];
|
||||
|
||||
if (splitAtEveryPortal) {
|
||||
// Check intersections with every portal segment
|
||||
var next2D = idx >= 0 ? leftArr[idx] : rightArr[-idx];
|
||||
for (int j = prevIdx + 1; j < System.Math.Abs(idx); j++) {
|
||||
var factor = VectorMath.LineIntersectionFactorXZ(FromXZ(leftArr[j]), FromXZ(rightArr[j]), FromXZ(prev2D), FromXZ(next2D));
|
||||
result.Add(Vector3.Lerp(funnel.left[j], funnel.right[j], factor));
|
||||
}
|
||||
|
||||
prevIdx = Mathf.Abs(idx);
|
||||
prev2D = next2D;
|
||||
}
|
||||
|
||||
if (idx >= 0) {
|
||||
result.Add(funnel.left[idx]);
|
||||
} else {
|
||||
result.Add(funnel.right[-idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Release lists back to the pool
|
||||
ListPool<int>.Release(ref intermediateResult);
|
||||
ArrayPool<Vector2>.Release(ref leftArr);
|
||||
ArrayPool<Vector2>.Release(ref rightArr);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Funnel algorithm.
|
||||
/// funnelPath will be filled with the result.
|
||||
/// The result is the indices of the vertices that were picked, a non-negative value refers to the corresponding index in the
|
||||
/// left array, a negative value refers to the corresponding index in the right array.
|
||||
/// So e.g 5 corresponds to left[5] and -2 corresponds to right[2]
|
||||
///
|
||||
/// See: http://digestingduck.blogspot.se/2010/03/simple-stupid-funnel-algorithm.html
|
||||
/// </summary>
|
||||
static void Calculate (Vector2[] left, Vector2[] right, int numPortals, int startIndex, List<int> funnelPath, int maxCorners, out bool lastCorner) {
|
||||
if (left.Length != right.Length) throw new System.ArgumentException();
|
||||
|
||||
lastCorner = false;
|
||||
|
||||
int apexIndex = startIndex + 0;
|
||||
int rightIndex = startIndex + 1;
|
||||
int leftIndex = startIndex + 1;
|
||||
|
||||
Vector2 portalApex = left[apexIndex];
|
||||
Vector2 portalLeft = left[leftIndex];
|
||||
Vector2 portalRight = right[rightIndex];
|
||||
|
||||
funnelPath.Add(apexIndex);
|
||||
|
||||
for (int i = startIndex + 2; i < numPortals; i++) {
|
||||
if (funnelPath.Count >= maxCorners) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (funnelPath.Count > 2000) {
|
||||
Debug.LogWarning("Avoiding infinite loop. Remove this check if you have this long paths.");
|
||||
break;
|
||||
}
|
||||
|
||||
Vector2 pLeft = left[i];
|
||||
Vector2 pRight = right[i];
|
||||
|
||||
if (LeftOrColinear(portalRight - portalApex, pRight - portalApex)) {
|
||||
if (portalApex == portalRight || RightOrColinear(portalLeft - portalApex, pRight - portalApex)) {
|
||||
portalRight = pRight;
|
||||
rightIndex = i;
|
||||
} else {
|
||||
portalApex = portalRight = portalLeft;
|
||||
i = apexIndex = rightIndex = leftIndex;
|
||||
|
||||
funnelPath.Add(apexIndex);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (RightOrColinear(portalLeft - portalApex, pLeft - portalApex)) {
|
||||
if (portalApex == portalLeft || LeftOrColinear(portalRight - portalApex, pLeft - portalApex)) {
|
||||
portalLeft = pLeft;
|
||||
leftIndex = i;
|
||||
} else {
|
||||
portalApex = portalLeft = portalRight;
|
||||
i = apexIndex = leftIndex = rightIndex;
|
||||
|
||||
// Negative value because we are referring
|
||||
// to the right side
|
||||
funnelPath.Add(-apexIndex);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastCorner = true;
|
||||
funnelPath.Add(numPortals-1);
|
||||
}
|
||||
}
|
||||
}
|
12
AR/Assets/AstarPathfindingProject/Utilities/Funnel.cs.meta
generated
Normal file
12
AR/Assets/AstarPathfindingProject/Utilities/Funnel.cs.meta
generated
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 332338376eb42424caa0e3d6bf984a8b
|
||||
timeCreated: 1488237337
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
164
AR/Assets/AstarPathfindingProject/Utilities/GraphGizmoHelper.cs
Normal file
164
AR/Assets/AstarPathfindingProject/Utilities/GraphGizmoHelper.cs
Normal file
@ -0,0 +1,164 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding.Util {
|
||||
public class GraphGizmoHelper : IAstarPooledObject, System.IDisposable {
|
||||
public RetainedGizmos.Hasher hasher { get; private set; }
|
||||
Pathfinding.Util.RetainedGizmos gizmos;
|
||||
PathHandler debugData;
|
||||
ushort debugPathID;
|
||||
GraphDebugMode debugMode;
|
||||
bool showSearchTree;
|
||||
float debugFloor;
|
||||
float debugRoof;
|
||||
public RetainedGizmos.Builder builder { get; private set; }
|
||||
Vector3 drawConnectionStart;
|
||||
Color drawConnectionColor;
|
||||
readonly System.Action<GraphNode> drawConnection;
|
||||
|
||||
public GraphGizmoHelper () {
|
||||
// Cache a delegate to avoid allocating memory for it every time
|
||||
drawConnection = DrawConnection;
|
||||
}
|
||||
|
||||
public void Init (AstarPath active, RetainedGizmos.Hasher hasher, RetainedGizmos gizmos) {
|
||||
if (active != null) {
|
||||
debugData = active.debugPathData;
|
||||
debugPathID = active.debugPathID;
|
||||
debugMode = active.debugMode;
|
||||
debugFloor = active.debugFloor;
|
||||
debugRoof = active.debugRoof;
|
||||
showSearchTree = active.showSearchTree && debugData != null;
|
||||
}
|
||||
this.gizmos = gizmos;
|
||||
this.hasher = hasher;
|
||||
builder = ObjectPool<RetainedGizmos.Builder>.Claim();
|
||||
}
|
||||
|
||||
public void OnEnterPool () {
|
||||
// Will cause pretty much all calls to throw null ref exceptions until Init is called
|
||||
var bld = builder;
|
||||
|
||||
ObjectPool<RetainedGizmos.Builder>.Release(ref bld);
|
||||
builder = null;
|
||||
debugData = null;
|
||||
}
|
||||
|
||||
public void DrawConnections (GraphNode node) {
|
||||
if (showSearchTree) {
|
||||
if (InSearchTree(node, debugData, debugPathID)) {
|
||||
var pnode = debugData.GetPathNode(node);
|
||||
if (pnode.parent != null) {
|
||||
builder.DrawLine((Vector3)node.position, (Vector3)debugData.GetPathNode(node).parent.node.position, NodeColor(node));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Calculate which color to use for drawing the node
|
||||
// based on the settings specified in the editor
|
||||
drawConnectionColor = NodeColor(node);
|
||||
// Get the node position
|
||||
// Cast it here to avoid doing it for every neighbour
|
||||
drawConnectionStart = (Vector3)node.position;
|
||||
node.GetConnections(drawConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawConnection (GraphNode other) {
|
||||
builder.DrawLine(drawConnectionStart, Vector3.Lerp((Vector3)other.position, drawConnectionStart, 0.5f), drawConnectionColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Color to use for gizmos.
|
||||
/// Returns a color to be used for the specified node with the current debug settings (editor only).
|
||||
///
|
||||
/// Version: Since 3.6.1 this method will not handle null nodes
|
||||
/// </summary>
|
||||
public Color NodeColor (GraphNode node) {
|
||||
if (showSearchTree && !InSearchTree(node, debugData, debugPathID)) return Color.clear;
|
||||
|
||||
Color color;
|
||||
|
||||
if (node.Walkable) {
|
||||
switch (debugMode) {
|
||||
case GraphDebugMode.Areas:
|
||||
color = AstarColor.GetAreaColor(node.Area);
|
||||
break;
|
||||
case GraphDebugMode.HierarchicalNode:
|
||||
color = AstarColor.GetTagColor((uint)node.HierarchicalNodeIndex);
|
||||
break;
|
||||
case GraphDebugMode.Penalty:
|
||||
color = Color.Lerp(AstarColor.ConnectionLowLerp, AstarColor.ConnectionHighLerp, ((float)node.Penalty-debugFloor) / (debugRoof-debugFloor));
|
||||
break;
|
||||
case GraphDebugMode.Tags:
|
||||
color = AstarColor.GetTagColor(node.Tag);
|
||||
break;
|
||||
case GraphDebugMode.SolidColor:
|
||||
color = AstarColor.SolidColor;
|
||||
break;
|
||||
default:
|
||||
if (debugData == null) {
|
||||
color = AstarColor.SolidColor;
|
||||
break;
|
||||
}
|
||||
|
||||
PathNode pathNode = debugData.GetPathNode(node);
|
||||
float value;
|
||||
if (debugMode == GraphDebugMode.G) {
|
||||
value = pathNode.G;
|
||||
} else if (debugMode == GraphDebugMode.H) {
|
||||
value = pathNode.H;
|
||||
} else {
|
||||
// mode == F
|
||||
value = pathNode.F;
|
||||
}
|
||||
|
||||
color = Color.Lerp(AstarColor.ConnectionLowLerp, AstarColor.ConnectionHighLerp, (value-debugFloor) / (debugRoof-debugFloor));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
color = AstarColor.UnwalkableNode;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the node is in the search tree of the path.
|
||||
/// Only guaranteed to be correct if path is the latest path calculated.
|
||||
/// Use for gizmo drawing only.
|
||||
/// </summary>
|
||||
public static bool InSearchTree (GraphNode node, PathHandler handler, ushort pathID) {
|
||||
return handler.GetPathNode(node).pathID == pathID;
|
||||
}
|
||||
|
||||
public void DrawWireTriangle (Vector3 a, Vector3 b, Vector3 c, Color color) {
|
||||
builder.DrawLine(a, b, color);
|
||||
builder.DrawLine(b, c, color);
|
||||
builder.DrawLine(c, a, color);
|
||||
}
|
||||
|
||||
public void DrawTriangles (Vector3[] vertices, Color[] colors, int numTriangles) {
|
||||
var triangles = ListPool<int>.Claim(numTriangles);
|
||||
|
||||
for (int i = 0; i < numTriangles*3; i++) triangles.Add(i);
|
||||
builder.DrawMesh(gizmos, vertices, triangles, colors);
|
||||
ListPool<int>.Release(ref triangles);
|
||||
}
|
||||
|
||||
public void DrawWireTriangles (Vector3[] vertices, Color[] colors, int numTriangles) {
|
||||
for (int i = 0; i < numTriangles; i++) {
|
||||
DrawWireTriangle(vertices[i*3+0], vertices[i*3+1], vertices[i*3+2], colors[i*3+0]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Submit () {
|
||||
builder.Submit(gizmos, hasher);
|
||||
}
|
||||
|
||||
void System.IDisposable.Dispose () {
|
||||
var tmp = this;
|
||||
|
||||
Submit();
|
||||
ObjectPool<GraphGizmoHelper>.Release(ref tmp);
|
||||
}
|
||||
}
|
||||
}
|
12
AR/Assets/AstarPathfindingProject/Utilities/GraphGizmoHelper.cs.meta
generated
Normal file
12
AR/Assets/AstarPathfindingProject/Utilities/GraphGizmoHelper.cs.meta
generated
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20ed0bf48b2a14d78b8aa2ebe33f0069
|
||||
timeCreated: 1473875958
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,109 @@
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding.Util;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Contains useful functions for updating graphs.
|
||||
/// This class works a lot with the GraphNode class, a useful function to get nodes is <see cref="AstarPath.GetNearest"/>.
|
||||
///
|
||||
/// See: <see cref="AstarPath.GetNearest"/>
|
||||
/// See: <see cref="Pathfinding.PathUtilities"/>
|
||||
///
|
||||
/// \since Added in 3.1
|
||||
///
|
||||
/// \ingroup utils
|
||||
/// </summary>
|
||||
public static class GraphUpdateUtilities {
|
||||
/// <summary>
|
||||
/// Updates graphs and checks if all nodes are still reachable from each other.
|
||||
/// Graphs are updated, then a check is made to see if the nodes are still reachable from each other.
|
||||
/// If they are not, the graphs are reverted to before the update and false is returned.\n
|
||||
/// This is slower than a normal graph update.
|
||||
/// All queued graph updates and thread safe callbacks will be flushed during this function.
|
||||
///
|
||||
/// Returns: True if the given nodes are still reachable from each other after the guo has been applied. False otherwise.
|
||||
///
|
||||
/// <code>
|
||||
/// var guo = new GraphUpdateObject(tower.GetComponent<Collider>().bounds);
|
||||
/// var spawnPointNode = AstarPath.active.GetNearest(spawnPoint.position).node;
|
||||
/// var goalNode = AstarPath.active.GetNearest(goalPoint.position).node;
|
||||
///
|
||||
/// if (GraphUpdateUtilities.UpdateGraphsNoBlock(guo, spawnPointNode, goalNode, false)) {
|
||||
/// // Valid tower position
|
||||
/// // Since the last parameter (which is called "alwaysRevert") in the method call was false
|
||||
/// // The graph is now updated and the game can just continue
|
||||
/// } else {
|
||||
/// // Invalid tower position. It blocks the path between the spawn point and the goal
|
||||
/// // The effect on the graph has been reverted
|
||||
/// Destroy(tower);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="guo">The GraphUpdateObject to update the graphs with</param>
|
||||
/// <param name="node1">Node which should have a valid path to node2. All nodes should be walkable or false will be returned.</param>
|
||||
/// <param name="node2">Node which should have a valid path to node1. All nodes should be walkable or false will be returned.</param>
|
||||
/// <param name="alwaysRevert">If true, reverts the graphs to the old state even if no blocking occurred</param>
|
||||
public static bool UpdateGraphsNoBlock (GraphUpdateObject guo, GraphNode node1, GraphNode node2, bool alwaysRevert = false) {
|
||||
List<GraphNode> buffer = ListPool<GraphNode>.Claim();
|
||||
|
||||
buffer.Add(node1);
|
||||
buffer.Add(node2);
|
||||
|
||||
bool worked = UpdateGraphsNoBlock(guo, buffer, alwaysRevert);
|
||||
ListPool<GraphNode>.Release(ref buffer);
|
||||
return worked;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates graphs and checks if all nodes are still reachable from each other.
|
||||
/// Graphs are updated, then a check is made to see if the nodes are still reachable from each other.
|
||||
/// If they are not, the graphs are reverted to before the update and false is returned.
|
||||
/// This is slower than a normal graph update.
|
||||
/// All queued graph updates will be flushed during this function.
|
||||
///
|
||||
/// Returns: True if the given nodes are still reachable from each other after the guo has been applied. False otherwise.
|
||||
/// </summary>
|
||||
/// <param name="guo">The GraphUpdateObject to update the graphs with</param>
|
||||
/// <param name="nodes">Nodes which should have valid paths between them. All nodes should be walkable or false will be returned.</param>
|
||||
/// <param name="alwaysRevert">If true, reverts the graphs to the old state even if no blocking occurred</param>
|
||||
public static bool UpdateGraphsNoBlock (GraphUpdateObject guo, List<GraphNode> nodes, bool alwaysRevert = false) {
|
||||
bool worked;
|
||||
|
||||
// Pause pathfinding while modifying the graphs
|
||||
var graphLock = AstarPath.active.PausePathfinding();
|
||||
|
||||
try {
|
||||
// Make sure any pending graph updates have been done before we start
|
||||
AstarPath.active.FlushGraphUpdates();
|
||||
|
||||
// Make sure all nodes are walkable
|
||||
for (int i = 0; i < nodes.Count; i++) if (!nodes[i].Walkable) return false;
|
||||
|
||||
// Track changed nodes to enable reversion of the guo
|
||||
guo.trackChangedNodes = true;
|
||||
|
||||
AstarPath.active.UpdateGraphs(guo);
|
||||
|
||||
// Update the graphs immediately
|
||||
AstarPath.active.FlushGraphUpdates();
|
||||
|
||||
// Check if all nodes are in the same area and that they are walkable, i.e that there are paths between all of them
|
||||
worked = PathUtilities.IsPathPossible(nodes);
|
||||
|
||||
// If it did not work, revert the GUO
|
||||
if (!worked || alwaysRevert) {
|
||||
guo.RevertFromBackup();
|
||||
// Recalculate connected components
|
||||
AstarPath.active.hierarchicalGraph.RecalculateIfNecessary();
|
||||
}
|
||||
} finally {
|
||||
graphLock.Release();
|
||||
}
|
||||
|
||||
// Disable tracking nodes, not strictly necessary, but will slightly reduce the cance that some user causes errors
|
||||
guo.trackChangedNodes = false;
|
||||
|
||||
return worked;
|
||||
}
|
||||
}
|
||||
}
|
7
AR/Assets/AstarPathfindingProject/Utilities/GraphUpdateUtilities.cs.meta
generated
Normal file
7
AR/Assets/AstarPathfindingProject/Utilities/GraphUpdateUtilities.cs.meta
generated
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5818f65b5e1449c1ae6f20de9f75ff5
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
536
AR/Assets/AstarPathfindingProject/Utilities/PathUtilities.cs
Normal file
536
AR/Assets/AstarPathfindingProject/Utilities/PathUtilities.cs
Normal file
@ -0,0 +1,536 @@
|
||||
using Pathfinding.Util;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Contains useful functions for working with paths and nodes.
|
||||
/// This class works a lot with the <see cref="Pathfinding.GraphNode"/> class, a useful function to get nodes is AstarPath.GetNearest.
|
||||
/// See: <see cref="AstarPath.GetNearest"/>
|
||||
/// See: <see cref="Pathfinding.GraphUpdateUtilities"/>
|
||||
/// See: <see cref="Pathfinding.GraphUtilities"/>
|
||||
/// \ingroup utils
|
||||
/// </summary>
|
||||
public static class PathUtilities {
|
||||
/// <summary>
|
||||
/// Returns if there is a walkable path from node1 to node2.
|
||||
/// This method is extremely fast because it only uses precalculated information.
|
||||
///
|
||||
/// <code>
|
||||
/// GraphNode node1 = AstarPath.active.GetNearest(point1, NNConstraint.Default).node;
|
||||
/// GraphNode node2 = AstarPath.active.GetNearest(point2, NNConstraint.Default).node;
|
||||
///
|
||||
/// if (PathUtilities.IsPathPossible(node1, node2)) {
|
||||
/// // Yay, there is a path between those two nodes
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// See: graph-updates (view in online documentation for working links)
|
||||
/// See: <see cref="AstarPath.GetNearest"/>
|
||||
/// </summary>
|
||||
public static bool IsPathPossible (GraphNode node1, GraphNode node2) {
|
||||
return node1.Walkable && node2.Walkable && node1.Area == node2.Area;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if there are walkable paths between all nodes.
|
||||
///
|
||||
/// See: graph-updates (view in online documentation for working links)
|
||||
///
|
||||
/// Returns true for empty lists.
|
||||
///
|
||||
/// See: <see cref="AstarPath.GetNearest"/>
|
||||
/// </summary>
|
||||
public static bool IsPathPossible (List<GraphNode> nodes) {
|
||||
if (nodes.Count == 0) return true;
|
||||
|
||||
uint area = nodes[0].Area;
|
||||
for (int i = 0; i < nodes.Count; i++) if (!nodes[i].Walkable || nodes[i].Area != area) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if there are walkable paths between all nodes.
|
||||
/// See: graph-updates (view in online documentation for working links)
|
||||
///
|
||||
/// This method will actually only check if the first node can reach all other nodes. However this is
|
||||
/// equivalent in 99% of the cases since almost always the graph connections are bidirectional.
|
||||
/// If you are not aware of any cases where you explicitly create unidirectional connections
|
||||
/// this method can be used without worries.
|
||||
///
|
||||
/// Returns true for empty lists
|
||||
///
|
||||
/// Warning: This method is significantly slower than the IsPathPossible method which does not take a tagMask
|
||||
///
|
||||
/// See: <see cref="AstarPath.GetNearest"/>
|
||||
/// </summary>
|
||||
public static bool IsPathPossible (List<GraphNode> nodes, int tagMask) {
|
||||
if (nodes.Count == 0) return true;
|
||||
|
||||
// Make sure that the first node has a valid tag
|
||||
if (((tagMask >> (int)nodes[0].Tag) & 1) == 0) return false;
|
||||
|
||||
// Fast check first
|
||||
if (!IsPathPossible(nodes)) return false;
|
||||
|
||||
// Make sure that the first node can reach all other nodes
|
||||
var reachable = GetReachableNodes(nodes[0], tagMask);
|
||||
bool result = true;
|
||||
|
||||
// Make sure that the first node can reach all other nodes
|
||||
for (int i = 1; i < nodes.Count; i++) {
|
||||
if (!reachable.Contains(nodes[i])) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Pool the temporary list
|
||||
ListPool<GraphNode>.Release(ref reachable);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all nodes reachable from the seed node.
|
||||
/// This function performs a DFS (depth-first-search) or flood fill of the graph and returns all nodes which can be reached from
|
||||
/// the seed node. In almost all cases this will be identical to returning all nodes which have the same area as the seed node.
|
||||
/// In the editor areas are displayed as different colors of the nodes.
|
||||
/// The only case where it will not be so is when there is a one way path from some part of the area to the seed node
|
||||
/// but no path from the seed node to that part of the graph.
|
||||
///
|
||||
/// The returned list is not sorted in any particular way.
|
||||
///
|
||||
/// Depending on the number of reachable nodes, this function can take quite some time to calculate
|
||||
/// so don't use it too often or it might affect the framerate of your game.
|
||||
///
|
||||
/// See: bitmasks (view in online documentation for working links).
|
||||
///
|
||||
/// Returns: A List<Node> containing all nodes reachable from the seed node.
|
||||
/// For better memory management the returned list should be pooled, see Pathfinding.Util.ListPool.
|
||||
/// </summary>
|
||||
/// <param name="seed">The node to start the search from.</param>
|
||||
/// <param name="tagMask">Optional mask for tags. This is a bitmask.</param>
|
||||
/// <param name="filter">Optional filter for which nodes to search. You can combine this with tagMask = -1 to make the filter determine everything.
|
||||
/// Only walkable nodes are searched regardless of the filter. If the filter function returns false the node will be treated as unwalkable.</param>
|
||||
public static List<GraphNode> GetReachableNodes (GraphNode seed, int tagMask = -1, System.Func<GraphNode, bool> filter = null) {
|
||||
Stack<GraphNode> dfsStack = StackPool<GraphNode>.Claim();
|
||||
List<GraphNode> reachable = ListPool<GraphNode>.Claim();
|
||||
|
||||
/// <summary>TODO: Pool</summary>
|
||||
var map = new HashSet<GraphNode>();
|
||||
|
||||
System.Action<GraphNode> callback;
|
||||
// Check if we can use the fast path
|
||||
if (tagMask == -1 && filter == null) {
|
||||
callback = (GraphNode node) => {
|
||||
if (node.Walkable && map.Add(node)) {
|
||||
reachable.Add(node);
|
||||
dfsStack.Push(node);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
callback = (GraphNode node) => {
|
||||
if (node.Walkable && ((tagMask >> (int)node.Tag) & 0x1) != 0 && map.Add(node)) {
|
||||
if (filter != null && !filter(node)) return;
|
||||
|
||||
reachable.Add(node);
|
||||
dfsStack.Push(node);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
callback(seed);
|
||||
|
||||
while (dfsStack.Count > 0) {
|
||||
dfsStack.Pop().GetConnections(callback);
|
||||
}
|
||||
|
||||
StackPool<GraphNode>.Release(dfsStack);
|
||||
return reachable;
|
||||
}
|
||||
|
||||
static Queue<GraphNode> BFSQueue;
|
||||
static Dictionary<GraphNode, int> BFSMap;
|
||||
|
||||
/// <summary>
|
||||
/// Returns all nodes up to a given node-distance from the seed node.
|
||||
/// This function performs a BFS (<a href="https://en.wikipedia.org/wiki/Breadth-first_search">breadth-first search</a>) or flood fill of the graph and returns all nodes within a specified node distance which can be reached from
|
||||
/// the seed node. In almost all cases when depth is large enough this will be identical to returning all nodes which have the same area as the seed node.
|
||||
/// In the editor areas are displayed as different colors of the nodes.
|
||||
/// The only case where it will not be so is when there is a one way path from some part of the area to the seed node
|
||||
/// but no path from the seed node to that part of the graph.
|
||||
///
|
||||
/// The returned list is sorted by node distance from the seed node
|
||||
/// i.e distance is measured in the number of nodes the shortest path from seed to that node would pass through.
|
||||
/// Note that the distance measurement does not take heuristics, penalties or tag penalties.
|
||||
///
|
||||
/// Depending on the number of nodes, this function can take quite some time to calculate
|
||||
/// so don't use it too often or it might affect the framerate of your game.
|
||||
///
|
||||
/// Returns: A List<GraphNode> containing all nodes reachable up to a specified node distance from the seed node.
|
||||
/// For better memory management the returned list should be pooled, see Pathfinding.Util.ListPool
|
||||
///
|
||||
/// Warning: This method is not thread safe. Only use it from the Unity thread (i.e normal game code).
|
||||
///
|
||||
/// The video below shows the BFS result with varying values of depth. Points are sampled on the nodes using <see cref="GetPointsOnNodes"/>.
|
||||
/// [Open online documentation to see videos]
|
||||
/// </summary>
|
||||
/// <param name="seed">The node to start the search from.</param>
|
||||
/// <param name="depth">The maximum node-distance from the seed node.</param>
|
||||
/// <param name="tagMask">Optional mask for tags. This is a bitmask.</param>
|
||||
/// <param name="filter">Optional filter for which nodes to search. You can combine this with depth = int.MaxValue and tagMask = -1 to make the filter determine everything.
|
||||
/// Only walkable nodes are searched regardless of the filter. If the filter function returns false the node will be treated as unwalkable.</param>
|
||||
public static List<GraphNode> BFS (GraphNode seed, int depth, int tagMask = -1, System.Func<GraphNode, bool> filter = null) {
|
||||
#if ASTAR_PROFILE
|
||||
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
|
||||
watch.Start();
|
||||
#endif
|
||||
|
||||
BFSQueue = BFSQueue ?? new Queue<GraphNode>();
|
||||
var que = BFSQueue;
|
||||
|
||||
BFSMap = BFSMap ?? new Dictionary<GraphNode, int>();
|
||||
var map = BFSMap;
|
||||
|
||||
// Even though we clear at the end of this function, it is good to
|
||||
// do it here as well in case the previous invocation of the method
|
||||
// threw an exception for some reason
|
||||
// and didn't clear the que and map
|
||||
que.Clear();
|
||||
map.Clear();
|
||||
|
||||
List<GraphNode> result = ListPool<GraphNode>.Claim();
|
||||
|
||||
int currentDist = -1;
|
||||
System.Action<GraphNode> callback;
|
||||
if (tagMask == -1) {
|
||||
callback = node => {
|
||||
if (node.Walkable && !map.ContainsKey(node)) {
|
||||
if (filter != null && !filter(node)) return;
|
||||
|
||||
map.Add(node, currentDist+1);
|
||||
result.Add(node);
|
||||
que.Enqueue(node);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
callback = node => {
|
||||
if (node.Walkable && ((tagMask >> (int)node.Tag) & 0x1) != 0 && !map.ContainsKey(node)) {
|
||||
if (filter != null && !filter(node)) return;
|
||||
|
||||
map.Add(node, currentDist+1);
|
||||
result.Add(node);
|
||||
que.Enqueue(node);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
callback(seed);
|
||||
|
||||
while (que.Count > 0) {
|
||||
GraphNode n = que.Dequeue();
|
||||
currentDist = map[n];
|
||||
|
||||
if (currentDist >= depth) break;
|
||||
|
||||
n.GetConnections(callback);
|
||||
}
|
||||
|
||||
que.Clear();
|
||||
map.Clear();
|
||||
|
||||
#if ASTAR_PROFILE
|
||||
watch.Stop();
|
||||
Debug.Log((1000*watch.Elapsed.TotalSeconds).ToString("0.0 ms"));
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns points in a spiral centered around the origin with a minimum clearance from other points.
|
||||
/// The points are laid out on the involute of a circle
|
||||
/// See: http://en.wikipedia.org/wiki/Involute
|
||||
/// Which has some nice properties.
|
||||
/// All points are separated by clearance world units.
|
||||
/// This method is O(n), yes if you read the code you will see a binary search, but that binary search
|
||||
/// has an upper bound on the number of steps, so it does not yield a log factor.
|
||||
///
|
||||
/// Note: Consider recycling the list after usage to reduce allocations.
|
||||
/// See: Pathfinding.Util.ListPool
|
||||
/// </summary>
|
||||
public static List<Vector3> GetSpiralPoints (int count, float clearance) {
|
||||
List<Vector3> pts = ListPool<Vector3>.Claim(count);
|
||||
|
||||
// The radius of the smaller circle used for generating the involute of a circle
|
||||
// Calculated from the separation distance between the turns
|
||||
float a = clearance/(2*Mathf.PI);
|
||||
float t = 0;
|
||||
|
||||
|
||||
pts.Add(InvoluteOfCircle(a, t));
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
Vector3 prev = pts[pts.Count-1];
|
||||
|
||||
// d = -t0/2 + sqrt( t0^2/4 + 2d/a )
|
||||
// Minimum angle (radians) which would create an arc distance greater than clearance
|
||||
float d = -t/2 + Mathf.Sqrt(t*t/4 + 2*clearance/a);
|
||||
|
||||
// Binary search for separating this point and the previous one
|
||||
float mn = t + d;
|
||||
float mx = t + 2*d;
|
||||
while (mx - mn > 0.01f) {
|
||||
float mid = (mn + mx)/2;
|
||||
Vector3 p = InvoluteOfCircle(a, mid);
|
||||
if ((p - prev).sqrMagnitude < clearance*clearance) {
|
||||
mn = mid;
|
||||
} else {
|
||||
mx = mid;
|
||||
}
|
||||
}
|
||||
|
||||
pts.Add(InvoluteOfCircle(a, mx));
|
||||
t = mx;
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the XZ coordinate of the involute of circle.
|
||||
/// See: http://en.wikipedia.org/wiki/Involute
|
||||
/// </summary>
|
||||
private static Vector3 InvoluteOfCircle (float a, float t) {
|
||||
return new Vector3(a*(Mathf.Cos(t) + t*Mathf.Sin(t)), 0, a*(Mathf.Sin(t) - t*Mathf.Cos(t)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will calculate a number of points around p which are on the graph and are separated by clearance from each other.
|
||||
/// This is like GetPointsAroundPoint except that previousPoints are treated as being in world space.
|
||||
/// The average of the points will be found and then that will be treated as the group center.
|
||||
/// </summary>
|
||||
/// <param name="p">The point to generate points around</param>
|
||||
/// <param name="g">The graph to use for linecasting. If you are only using one graph, you can get this by AstarPath.active.graphs[0] as IRaycastableGraph.
|
||||
/// Note that not all graphs are raycastable, recast, navmesh and grid graphs are raycastable. On recast and navmesh it works the best.</param>
|
||||
/// <param name="previousPoints">The points to use for reference. Note that these are in world space.
|
||||
/// The new points will overwrite the existing points in the list. The result will be in world space.</param>
|
||||
/// <param name="radius">The final points will be at most this distance from p.</param>
|
||||
/// <param name="clearanceRadius">The points will if possible be at least this distance from each other.</param>
|
||||
public static void GetPointsAroundPointWorld (Vector3 p, IRaycastableGraph g, List<Vector3> previousPoints, float radius, float clearanceRadius) {
|
||||
if (previousPoints.Count == 0) return;
|
||||
|
||||
Vector3 avg = Vector3.zero;
|
||||
for (int i = 0; i < previousPoints.Count; i++) avg += previousPoints[i];
|
||||
avg /= previousPoints.Count;
|
||||
|
||||
for (int i = 0; i < previousPoints.Count; i++) previousPoints[i] -= avg;
|
||||
|
||||
GetPointsAroundPoint(p, g, previousPoints, radius, clearanceRadius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will calculate a number of points around center which are on the graph and are separated by clearance from each other.
|
||||
/// The maximum distance from center to any point will be radius.
|
||||
/// Points will first be tried to be laid out as previousPoints and if that fails, random points will be selected.
|
||||
/// This is great if you want to pick a number of target points for group movement. If you pass all current agent points from e.g the group's average position
|
||||
/// this method will return target points so that the units move very little within the group, this is often aesthetically pleasing and reduces jitter if using
|
||||
/// some kind of local avoidance.
|
||||
///
|
||||
/// TODO: Write unit tests
|
||||
/// </summary>
|
||||
/// <param name="center">The point to generate points around</param>
|
||||
/// <param name="g">The graph to use for linecasting. If you are only using one graph, you can get this by AstarPath.active.graphs[0] as IRaycastableGraph.
|
||||
/// Note that not all graphs are raycastable, recast, navmesh and grid graphs are raycastable. On recast and navmesh it works the best.</param>
|
||||
/// <param name="previousPoints">The points to use for reference. Note that these should not be in world space. They are treated as relative to center.
|
||||
/// The new points will overwrite the existing points in the list. The result will be in world space, not relative to center.</param>
|
||||
/// <param name="radius">The final points will be at most this distance from center.</param>
|
||||
/// <param name="clearanceRadius">The points will if possible be at least this distance from each other.</param>
|
||||
public static void GetPointsAroundPoint (Vector3 center, IRaycastableGraph g, List<Vector3> previousPoints, float radius, float clearanceRadius) {
|
||||
if (g == null) throw new System.ArgumentNullException("g");
|
||||
|
||||
var graph = g as NavGraph;
|
||||
|
||||
if (graph == null) throw new System.ArgumentException("g is not a NavGraph");
|
||||
|
||||
NNInfoInternal nn = graph.GetNearestForce(center, NNConstraint.Default);
|
||||
center = nn.clampedPosition;
|
||||
|
||||
if (nn.node == null) {
|
||||
// No valid point to start from
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Make sure the enclosing circle has a radius which can pack circles with packing density 0.5
|
||||
radius = Mathf.Max(radius, 1.4142f*clearanceRadius*Mathf.Sqrt(previousPoints.Count)); //Mathf.Sqrt(previousPoints.Count*clearanceRadius*2));
|
||||
clearanceRadius *= clearanceRadius;
|
||||
|
||||
for (int i = 0; i < previousPoints.Count; i++) {
|
||||
Vector3 dir = previousPoints[i];
|
||||
float magn = dir.magnitude;
|
||||
|
||||
if (magn > 0) dir /= magn;
|
||||
|
||||
float newMagn = radius;//magn > radius ? radius : magn;
|
||||
dir *= newMagn;
|
||||
|
||||
GraphHitInfo hit;
|
||||
|
||||
int tests = 0;
|
||||
while (true) {
|
||||
Vector3 pt = center + dir;
|
||||
|
||||
if (g.Linecast(center, pt, nn.node, out hit)) {
|
||||
if (hit.point == Vector3.zero) {
|
||||
// Oops, linecast actually failed completely
|
||||
// try again unless we have tried lots of times
|
||||
// then we just continue anyway
|
||||
tests++;
|
||||
if (tests > 8) {
|
||||
previousPoints[i] = pt;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
pt = hit.point;
|
||||
}
|
||||
}
|
||||
|
||||
bool worked = false;
|
||||
|
||||
for (float q = 0.1f; q <= 1.0f; q += 0.05f) {
|
||||
Vector3 qt = Vector3.Lerp(center, pt, q);
|
||||
worked = true;
|
||||
for (int j = 0; j < i; j++) {
|
||||
if ((previousPoints[j] - qt).sqrMagnitude < clearanceRadius) {
|
||||
worked = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Abort after 8 tests or when we have found a valid point
|
||||
if (worked || tests > 8) {
|
||||
worked = true;
|
||||
previousPoints[i] = qt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Break out of nested loop
|
||||
if (worked) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If we could not find a valid point, reduce the clearance radius slightly to improve
|
||||
// the chances next time
|
||||
clearanceRadius *= 0.9f;
|
||||
// This will pick points in 2D closer to the edge of the circle with a higher probability
|
||||
dir = Random.onUnitSphere * Mathf.Lerp(newMagn, radius, tests / 5);
|
||||
dir.y = 0;
|
||||
tests++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns randomly selected points on the specified nodes with each point being separated by clearanceRadius from each other.
|
||||
/// Selecting points ON the nodes only works for TriangleMeshNode (used by Recast Graph and Navmesh Graph) and GridNode (used by GridGraph).
|
||||
/// For other node types, only the positions of the nodes will be used.
|
||||
///
|
||||
/// clearanceRadius will be reduced if no valid points can be found.
|
||||
///
|
||||
/// Note: This method assumes that the nodes in the list have the same type for some special cases.
|
||||
/// More specifically if the first node is not a TriangleMeshNode or a GridNode, it will use a fast path
|
||||
/// which assumes that all nodes in the list have the same surface area (which usually is a surface area of zero and the
|
||||
/// nodes are all PointNodes).
|
||||
/// </summary>
|
||||
public static List<Vector3> GetPointsOnNodes (List<GraphNode> nodes, int count, float clearanceRadius = 0) {
|
||||
if (nodes == null) throw new System.ArgumentNullException("nodes");
|
||||
if (nodes.Count == 0) throw new System.ArgumentException("no nodes passed");
|
||||
|
||||
List<Vector3> pts = ListPool<Vector3>.Claim(count);
|
||||
|
||||
// Square
|
||||
clearanceRadius *= clearanceRadius;
|
||||
|
||||
if (clearanceRadius > 0 || nodes[0] is TriangleMeshNode
|
||||
#if !ASTAR_NO_GRID_GRAPH
|
||||
|| nodes[0] is GridNode
|
||||
#endif
|
||||
) {
|
||||
// Accumulated area of all nodes
|
||||
List<float> accs = ListPool<float>.Claim(nodes.Count);
|
||||
|
||||
// Total area of all nodes so far
|
||||
float tot = 0;
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++) {
|
||||
var surfaceArea = nodes[i].SurfaceArea();
|
||||
// Ensures that even if the nodes have a surface area of 0, a random one will still be picked
|
||||
// instead of e.g always picking the first or the last one.
|
||||
surfaceArea += 0.001f;
|
||||
tot += surfaceArea;
|
||||
accs.Add(tot);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
//Pick point
|
||||
int testCount = 0;
|
||||
int testLimit = 10;
|
||||
bool worked = false;
|
||||
|
||||
while (!worked) {
|
||||
worked = true;
|
||||
|
||||
// If no valid points could be found, progressively lower the clearance radius until such a point is found
|
||||
if (testCount >= testLimit) {
|
||||
// Note that clearanceRadius is a squared radius
|
||||
clearanceRadius *= 0.9f*0.9f;
|
||||
testLimit += 10;
|
||||
if (testLimit > 100) clearanceRadius = 0;
|
||||
}
|
||||
|
||||
// Pick a random node among the ones in the list weighted by their area
|
||||
float tg = Random.value*tot;
|
||||
int v = accs.BinarySearch(tg);
|
||||
if (v < 0) v = ~v;
|
||||
|
||||
if (v >= nodes.Count) {
|
||||
// Cover edge cases
|
||||
worked = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var node = nodes[v];
|
||||
var p = node.RandomPointOnSurface();
|
||||
|
||||
// Test if it is some distance away from the other points
|
||||
if (clearanceRadius > 0) {
|
||||
for (int j = 0; j < pts.Count; j++) {
|
||||
if ((pts[j]-p).sqrMagnitude < clearanceRadius) {
|
||||
worked = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (worked) {
|
||||
pts.Add(p);
|
||||
break;
|
||||
}
|
||||
testCount++;
|
||||
}
|
||||
}
|
||||
|
||||
ListPool<float>.Release(ref accs);
|
||||
} else {
|
||||
// Fast path, assumes all nodes have the same area (usually zero)
|
||||
for (int i = 0; i < count; i++) {
|
||||
pts.Add((Vector3)nodes[Random.Range(0, nodes.Count)].RandomPointOnSurface());
|
||||
}
|
||||
}
|
||||
|
||||
return pts;
|
||||
}
|
||||
}
|
||||
}
|
7
AR/Assets/AstarPathfindingProject/Utilities/PathUtilities.cs.meta
generated
Normal file
7
AR/Assets/AstarPathfindingProject/Utilities/PathUtilities.cs.meta
generated
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d93ae7d64ab84e23819ac5754065f34
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,376 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Moves a grid graph to follow a target.
|
||||
///
|
||||
/// Attach this to some object in the scene and assign the target to e.g the player.
|
||||
/// Then the graph will follow that object around as it moves.
|
||||
///
|
||||
/// This is useful if pathfinding is only necessary in a small region around an object (for example the player).
|
||||
/// It makes it possible to have vast open worlds (maybe procedurally generated) and still be able to use pathfinding on them.
|
||||
///
|
||||
/// When the graph is moved you may notice an fps drop.
|
||||
/// If this grows too large you can try a few things:
|
||||
/// - Reduce the <see cref="updateDistance"/>. This will make the updates smaller but more frequent.
|
||||
/// This only works to some degree however since an update has an inherent overhead.
|
||||
/// - Reduce the grid size.
|
||||
/// - Turn on multithreading (A* Inspector -> Settings)
|
||||
/// - Disable Height Testing or Collision Testing in the grid graph. This can give a performance boost
|
||||
/// since fewer calls to the physics engine need to be done.
|
||||
/// - Avoid using any erosion in the grid graph settings. This is relatively slow.
|
||||
///
|
||||
/// This script has a built-in constant called <see cref="MaxMillisPerFrame"/> and it tries to not use any more
|
||||
/// cpu time than that per frame.
|
||||
///
|
||||
/// Make sure you have 'Show Graphs' disabled in the A* inspector since gizmos in the scene view can take some
|
||||
/// time to update when the graph moves and thus make it seem like this script is slower than it actually is.
|
||||
///
|
||||
/// See: Take a look at the example scene called "Procedural" for an example of how to use this script
|
||||
///
|
||||
/// Note: Using erosion on grid graphs can significantly lower the performance when updating graphs.
|
||||
/// Each erosion iteration requires expanding the region that is updated by 1 node.
|
||||
/// </summary>
|
||||
[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_procedural_grid_mover.php")]
|
||||
public class ProceduralGridMover : VersionedMonoBehaviour {
|
||||
/// <summary>
|
||||
/// Graph will be updated if the target is more than this number of nodes from the graph center.
|
||||
/// Note that this is in nodes, not world units.
|
||||
///
|
||||
/// Version: The unit was changed to nodes instead of world units in 3.6.8.
|
||||
/// </summary>
|
||||
public float updateDistance = 10;
|
||||
|
||||
/// <summary>Graph will be moved to follow this target</summary>
|
||||
public Transform target;
|
||||
|
||||
/// <summary>Temporary buffer</summary>
|
||||
GridNodeBase[] buffer;
|
||||
|
||||
/// <summary>True while the graph is being updated by this script</summary>
|
||||
public bool updatingGraph { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Grid graph to update.
|
||||
/// This will be set at Start based on <see cref="graphIndex"/>.
|
||||
/// During runtime you may set this to any graph or to null to disable updates.
|
||||
/// </summary>
|
||||
public GridGraph graph;
|
||||
|
||||
/// <summary>
|
||||
/// Index for the graph to update.
|
||||
/// This will be used at Start to set <see cref="graph"/>.
|
||||
///
|
||||
/// This is an index into the AstarPath.active.data.graphs array.
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
public int graphIndex;
|
||||
|
||||
void Start () {
|
||||
if (AstarPath.active == null) throw new System.Exception("There is no AstarPath object in the scene");
|
||||
|
||||
// If one creates this component via a script then they may have already set the graph field.
|
||||
// In that case don't replace it.
|
||||
if (graph == null) {
|
||||
if (graphIndex < 0) throw new System.Exception("Graph index should not be negative");
|
||||
if (graphIndex >= AstarPath.active.data.graphs.Length) throw new System.Exception("The ProceduralGridMover was configured to use graph index " + graphIndex + ", but only " + AstarPath.active.data.graphs.Length + " graphs exist");
|
||||
|
||||
graph = AstarPath.active.data.graphs[graphIndex] as GridGraph;
|
||||
if (graph == null) throw new System.Exception("The ProceduralGridMover was configured to use graph index " + graphIndex + " but that graph either does not exist or is not a GridGraph or LayerGridGraph");
|
||||
}
|
||||
|
||||
UpdateGraph();
|
||||
}
|
||||
|
||||
/// <summary>Update is called once per frame</summary>
|
||||
void Update () {
|
||||
if (graph == null) return;
|
||||
|
||||
// Calculate where the graph center and the target position is in graph space
|
||||
var graphCenterInGraphSpace = PointToGraphSpace(graph.center);
|
||||
var targetPositionInGraphSpace = PointToGraphSpace(target.position);
|
||||
|
||||
// Check the distance in graph space
|
||||
// We only care about the X and Z axes since the Y axis is the "height" coordinate of the nodes (in graph space)
|
||||
// We only care about the plane that the nodes are placed in
|
||||
if (VectorMath.SqrDistanceXZ(graphCenterInGraphSpace, targetPositionInGraphSpace) > updateDistance*updateDistance) {
|
||||
UpdateGraph();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a point from world space to graph space.
|
||||
/// In graph space, (0,0,0) is bottom left corner of the graph
|
||||
/// and one unit along the X and Z axes equals distance between two nodes
|
||||
/// the Y axis still uses world units
|
||||
/// </summary>
|
||||
Vector3 PointToGraphSpace (Vector3 p) {
|
||||
// Multiply with the inverse matrix of the graph
|
||||
// to get the point in graph space
|
||||
return graph.transform.InverseTransform(p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the graph asynchronously.
|
||||
/// This will move the graph so that the target's position is the center of the graph.
|
||||
/// If the graph is already being updated, the call will be ignored.
|
||||
///
|
||||
/// The image below shows which nodes will be updated when the graph moves.
|
||||
/// The whole graph is not recalculated each time it is moved, but only those
|
||||
/// nodes that have to be updated, the rest will keep their old values.
|
||||
/// The image is a bit simplified but it shows the main idea.
|
||||
/// [Open online documentation to see images]
|
||||
///
|
||||
/// If you want to move the graph synchronously then call
|
||||
/// <code> AstarPath.active.FlushWorkItems(); </code>
|
||||
/// Immediately after you have called this method.
|
||||
/// </summary>
|
||||
public void UpdateGraph () {
|
||||
if (updatingGraph) {
|
||||
// We are already updating the graph
|
||||
// so ignore this call
|
||||
return;
|
||||
}
|
||||
|
||||
updatingGraph = true;
|
||||
|
||||
// Start a work item for updating the graph
|
||||
// This will pause the pathfinding threads
|
||||
// so that it is safe to update the graph
|
||||
// and then do it over several frames
|
||||
// (hence the IEnumerator coroutine)
|
||||
// to avoid too large FPS drops
|
||||
IEnumerator ie = UpdateGraphCoroutine();
|
||||
AstarPath.active.AddWorkItem(new AstarWorkItem(
|
||||
(context, force) => {
|
||||
// If force is true we need to calculate all steps at once
|
||||
if (force) while (ie.MoveNext()) {}
|
||||
|
||||
// Calculate one step. False will be returned when there are no more steps
|
||||
bool done;
|
||||
try {
|
||||
done = !ie.MoveNext();
|
||||
} catch (System.Exception e) {
|
||||
// The code MAY throw an exception in rare circumstances if for example the user
|
||||
// changes the width of the graph in the inspector while an update is being performed
|
||||
// at the same time. So lets just fail in that case and retry later.
|
||||
Debug.LogException(e, this);
|
||||
done = true;
|
||||
}
|
||||
|
||||
if (done) {
|
||||
updatingGraph = false;
|
||||
}
|
||||
return done;
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>Async method for moving the graph</summary>
|
||||
IEnumerator UpdateGraphCoroutine () {
|
||||
// Find the direction that we want to move the graph in.
|
||||
// Calcuculate this in graph space (where a distance of one is the size of one node)
|
||||
Vector3 dir = PointToGraphSpace(target.position) - PointToGraphSpace(graph.center);
|
||||
|
||||
// Snap to a whole number of nodes
|
||||
dir.x = Mathf.Round(dir.x);
|
||||
dir.z = Mathf.Round(dir.z);
|
||||
dir.y = 0;
|
||||
|
||||
// Nothing do to
|
||||
if (dir == Vector3.zero) yield break;
|
||||
|
||||
// Number of nodes to offset in each direction
|
||||
Int2 offset = new Int2(-Mathf.RoundToInt(dir.x), -Mathf.RoundToInt(dir.z));
|
||||
|
||||
// Move the center (this is in world units, so we need to convert it back from graph space)
|
||||
graph.center += graph.transform.TransformVector(dir);
|
||||
graph.UpdateTransform();
|
||||
|
||||
// Cache some variables for easier access
|
||||
int width = graph.width;
|
||||
int depth = graph.depth;
|
||||
GridNodeBase[] nodes;
|
||||
// Layers are required when handling LayeredGridGraphs
|
||||
int layers = graph.LayerCount;
|
||||
nodes = graph.nodes;
|
||||
|
||||
// Create a temporary buffer required for the calculations
|
||||
if (buffer == null || buffer.Length != width*depth) {
|
||||
buffer = new GridNodeBase[width*depth];
|
||||
}
|
||||
|
||||
// Check if we have moved less than a whole graph width all directions
|
||||
// If we have moved more than this we can just as well recalculate the whole graph
|
||||
if (Mathf.Abs(offset.x) <= width && Mathf.Abs(offset.y) <= depth) {
|
||||
IntRect recalculateRect = new IntRect(0, 0, offset.x, offset.y);
|
||||
|
||||
// If offset.x < 0, adjust the rect
|
||||
if (recalculateRect.xmin > recalculateRect.xmax) {
|
||||
int tmp2 = recalculateRect.xmax;
|
||||
recalculateRect.xmax = width + recalculateRect.xmin;
|
||||
recalculateRect.xmin = width + tmp2;
|
||||
}
|
||||
|
||||
// If offset.y < 0, adjust the rect
|
||||
if (recalculateRect.ymin > recalculateRect.ymax) {
|
||||
int tmp2 = recalculateRect.ymax;
|
||||
recalculateRect.ymax = depth + recalculateRect.ymin;
|
||||
recalculateRect.ymin = depth + tmp2;
|
||||
}
|
||||
|
||||
// Connections need to be recalculated for the neighbours as well, so we need to expand the rect by 1
|
||||
var connectionRect = recalculateRect.Expand(1);
|
||||
|
||||
// Makes sure the rect stays inside the grid
|
||||
connectionRect = IntRect.Intersection(connectionRect, new IntRect(0, 0, width, depth));
|
||||
|
||||
// Offset each node by the #offset variable
|
||||
// nodes which would end up outside the graph
|
||||
// will wrap around to the other side of it
|
||||
for (int l = 0; l < layers; l++) {
|
||||
int layerOffset = l*width*depth;
|
||||
for (int z = 0; z < depth; z++) {
|
||||
int pz = z*width;
|
||||
int tz = ((z+offset.y + depth)%depth)*width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
buffer[tz + ((x+offset.x + width) % width)] = nodes[layerOffset + pz + x];
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
|
||||
// Copy the nodes back to the graph
|
||||
// and set the correct indices
|
||||
for (int z = 0; z < depth; z++) {
|
||||
int pz = z*width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
int newIndex = pz + x;
|
||||
var node = buffer[newIndex];
|
||||
if (node != null) node.NodeInGridIndex = newIndex;
|
||||
nodes[layerOffset + newIndex] = node;
|
||||
}
|
||||
|
||||
// Calculate the limits for the region that has been wrapped
|
||||
// to the other side of the graph
|
||||
int xmin, xmax;
|
||||
if (z >= recalculateRect.ymin && z < recalculateRect.ymax) {
|
||||
xmin = 0;
|
||||
xmax = depth;
|
||||
} else {
|
||||
xmin = recalculateRect.xmin;
|
||||
xmax = recalculateRect.xmax;
|
||||
}
|
||||
|
||||
for (int x = xmin; x < xmax; x++) {
|
||||
var node = buffer[pz + x];
|
||||
if (node != null) {
|
||||
// Clear connections on all nodes that are wrapped and placed on the other side of the graph.
|
||||
// This is both to clear any custom connections (which do not really make sense after moving the node)
|
||||
// and to prevent possible exceptions when the node will later (possibly) be destroyed because it was
|
||||
// not needed anymore (only for layered grid graphs).
|
||||
node.ClearConnections(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// The calculation will only update approximately this number of
|
||||
// nodes per frame. This is used to keep CPU load per frame low
|
||||
int yieldEvery = 1000;
|
||||
// To avoid the update taking too long, make yieldEvery somewhat proportional to the number of nodes that we are going to update
|
||||
int approxNumNodesToUpdate = Mathf.Max(Mathf.Abs(offset.x), Mathf.Abs(offset.y)) * Mathf.Max(width, depth);
|
||||
yieldEvery = Mathf.Max(yieldEvery, approxNumNodesToUpdate/10);
|
||||
int counter = 0;
|
||||
|
||||
// Recalculate the nodes
|
||||
// Take a look at the image in the docs for the UpdateGraph method
|
||||
// to see which nodes are being recalculated.
|
||||
for (int z = 0; z < depth; z++) {
|
||||
int xmin, xmax;
|
||||
if (z >= recalculateRect.ymin && z < recalculateRect.ymax) {
|
||||
xmin = 0;
|
||||
xmax = width;
|
||||
} else {
|
||||
xmin = recalculateRect.xmin;
|
||||
xmax = recalculateRect.xmax;
|
||||
}
|
||||
|
||||
for (int x = xmin; x < xmax; x++) {
|
||||
graph.RecalculateCell(x, z, false, false);
|
||||
}
|
||||
|
||||
counter += (xmax - xmin);
|
||||
|
||||
if (counter > yieldEvery) {
|
||||
counter = 0;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
for (int z = 0; z < depth; z++) {
|
||||
int xmin, xmax;
|
||||
if (z >= connectionRect.ymin && z < connectionRect.ymax) {
|
||||
xmin = 0;
|
||||
xmax = width;
|
||||
} else {
|
||||
xmin = connectionRect.xmin;
|
||||
xmax = connectionRect.xmax;
|
||||
}
|
||||
|
||||
for (int x = xmin; x < xmax; x++) {
|
||||
graph.CalculateConnections(x, z);
|
||||
}
|
||||
|
||||
counter += (xmax - xmin);
|
||||
|
||||
if (counter > yieldEvery) {
|
||||
counter = 0;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
|
||||
// Calculate all connections for the nodes along the boundary
|
||||
// of the graph, these always need to be updated
|
||||
/// <summary>TODO: Optimize to not traverse all nodes in the graph, only those at the edges</summary>
|
||||
for (int z = 0; z < depth; z++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (x == 0 || z == 0 || x == width-1 || z == depth-1) graph.CalculateConnections(x, z);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The calculation will only update approximately this number of
|
||||
// nodes per frame. This is used to keep CPU load per frame low
|
||||
int yieldEvery = Mathf.Max(depth*width / 20, 1000);
|
||||
int counter = 0;
|
||||
// Just update all nodes
|
||||
for (int z = 0; z < depth; z++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
graph.RecalculateCell(x, z);
|
||||
}
|
||||
counter += width;
|
||||
if (counter > yieldEvery) {
|
||||
counter = 0;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Recalculate the connections of all nodes
|
||||
for (int z = 0; z < depth; z++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
graph.CalculateConnections(x, z);
|
||||
}
|
||||
counter += width;
|
||||
if (counter > yieldEvery) {
|
||||
counter = 0;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
AR/Assets/AstarPathfindingProject/Utilities/ProceduralGridMover.cs.meta
generated
Normal file
8
AR/Assets/AstarPathfindingProject/Utilities/ProceduralGridMover.cs.meta
generated
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a634230476dc478b88eceac73b1c8a4
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
123
AR/Assets/AstarPathfindingProject/Utilities/ProfileHelper.cs
Normal file
123
AR/Assets/AstarPathfindingProject/Utilities/ProfileHelper.cs
Normal file
@ -0,0 +1,123 @@
|
||||
// Disable some warnings since this class compiles out large parts of the code depending on compiler directives
|
||||
#pragma warning disable 0162
|
||||
#pragma warning disable 0414
|
||||
#pragma warning disable 0429
|
||||
//#define PROFILE // Uncomment to enable profiling
|
||||
//#define KEEP_SAMPLES
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Pathfinding {
|
||||
public class Profile {
|
||||
const bool PROFILE_MEM = false;
|
||||
|
||||
public readonly string name;
|
||||
readonly System.Diagnostics.Stopwatch watch;
|
||||
int counter;
|
||||
long mem;
|
||||
long smem;
|
||||
|
||||
#if KEEP_SAMPLES
|
||||
List<float> samples = new List<float>();
|
||||
#endif
|
||||
|
||||
int control = 1 << 30;
|
||||
const bool dontCountFirst = false;
|
||||
|
||||
public int ControlValue () {
|
||||
return control;
|
||||
}
|
||||
|
||||
public Profile (string name) {
|
||||
this.name = name;
|
||||
watch = new System.Diagnostics.Stopwatch();
|
||||
}
|
||||
|
||||
public static void WriteCSV (string path, params Profile[] profiles) {
|
||||
#if KEEP_SAMPLES
|
||||
var s = new System.Text.StringBuilder();
|
||||
s.AppendLine("x, y");
|
||||
foreach (var profile in profiles) {
|
||||
for (int i = 0; i < profile.samples.Count; i++) {
|
||||
s.AppendLine(profile.name + ", " + profile.samples[i].ToString("R"));
|
||||
}
|
||||
}
|
||||
System.IO.File.WriteAllText(path, s.ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Run (System.Action action) {
|
||||
Start();
|
||||
action();
|
||||
Stop();
|
||||
}
|
||||
|
||||
[System.Diagnostics.ConditionalAttribute("PROFILE")]
|
||||
public void Start () {
|
||||
if (PROFILE_MEM) {
|
||||
smem = GC.GetTotalMemory(false);
|
||||
}
|
||||
if (dontCountFirst && counter == 1) return;
|
||||
watch.Start();
|
||||
}
|
||||
|
||||
[System.Diagnostics.ConditionalAttribute("PROFILE")]
|
||||
public void Stop () {
|
||||
counter++;
|
||||
if (dontCountFirst && counter == 1) return;
|
||||
|
||||
watch.Stop();
|
||||
if (PROFILE_MEM) {
|
||||
mem += GC.GetTotalMemory(false)-smem;
|
||||
}
|
||||
#if KEEP_SAMPLES
|
||||
samples.Add((float)watch.Elapsed.TotalMilliseconds);
|
||||
watch.Reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Diagnostics.ConditionalAttribute("PROFILE")]
|
||||
/// <summary>Log using Debug.Log</summary>
|
||||
public void Log () {
|
||||
UnityEngine.Debug.Log(ToString());
|
||||
}
|
||||
|
||||
[System.Diagnostics.ConditionalAttribute("PROFILE")]
|
||||
/// <summary>Log using System.Console</summary>
|
||||
public void ConsoleLog () {
|
||||
#if !NETFX_CORE || UNITY_EDITOR
|
||||
System.Console.WriteLine(ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Diagnostics.ConditionalAttribute("PROFILE")]
|
||||
public void Stop (int control) {
|
||||
counter++;
|
||||
if (dontCountFirst && counter == 1) return;
|
||||
|
||||
watch.Stop();
|
||||
if (PROFILE_MEM) {
|
||||
mem += GC.GetTotalMemory(false)-smem;
|
||||
}
|
||||
|
||||
if (this.control == 1 << 30) this.control = control;
|
||||
else if (this.control != control) throw new Exception("Control numbers do not match " + this.control + " != " + control);
|
||||
}
|
||||
|
||||
[System.Diagnostics.ConditionalAttribute("PROFILE")]
|
||||
public void Control (Profile other) {
|
||||
if (ControlValue() != other.ControlValue()) {
|
||||
throw new Exception("Control numbers do not match ("+name + " " + other.name + ") " + ControlValue() + " != " + other.ControlValue());
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString () {
|
||||
string s = name + " #" + counter + " " + watch.Elapsed.TotalMilliseconds.ToString("0.0 ms") + " avg: " + (watch.Elapsed.TotalMilliseconds/counter).ToString("0.00 ms");
|
||||
|
||||
if (PROFILE_MEM) {
|
||||
s += " avg mem: " + (mem/(1.0*counter)).ToString("0 bytes");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
8
AR/Assets/AstarPathfindingProject/Utilities/ProfileHelper.cs.meta
generated
Normal file
8
AR/Assets/AstarPathfindingProject/Utilities/ProfileHelper.cs.meta
generated
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ec9029c797ec471fa8b5640e1829fc0
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
376
AR/Assets/AstarPathfindingProject/Utilities/RetainedGizmos.cs
Normal file
376
AR/Assets/AstarPathfindingProject/Utilities/RetainedGizmos.cs
Normal file
@ -0,0 +1,376 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
#if UNITY_5_5_OR_NEWER
|
||||
using UnityEngine.Profiling;
|
||||
#endif
|
||||
|
||||
namespace Pathfinding.Util {
|
||||
/// <summary>
|
||||
/// Helper for drawing Gizmos in a performant way.
|
||||
/// This is a replacement for the Unity Gizmos class as that is not very performant
|
||||
/// when drawing very large amounts of geometry (for example a large grid graph).
|
||||
/// These gizmos can be persistent, so if the data does not change, the gizmos
|
||||
/// do not need to be updated.
|
||||
///
|
||||
/// How to use
|
||||
/// - Create a Hasher object and hash whatever data you will be using to draw the gizmos
|
||||
/// Could be for example the positions of the vertices or something. Just as long as
|
||||
/// if the gizmos should change, then the hash changes as well.
|
||||
/// - Check if a cached mesh exists for that hash
|
||||
/// - If not, then create a Builder object and call the drawing methods until you are done
|
||||
/// and then call Finalize with a reference to a gizmos class and the hash you calculated before.
|
||||
/// - Call gizmos.Draw with the hash.
|
||||
/// - When you are done with drawing gizmos for this frame, call gizmos.FinalizeDraw
|
||||
///
|
||||
/// <code>
|
||||
/// var a = Vector3.zero;
|
||||
/// var b = Vector3.one;
|
||||
/// var color = Color.red;
|
||||
/// var hasher = new RetainedGizmos.Hasher();
|
||||
/// hasher.AddHash(a.GetHashCode());
|
||||
/// hasher.AddHash(b.GetHashCode());
|
||||
/// hasher.AddHash(color.GetHashCode());
|
||||
/// if (!gizmos.Draw(hasher)) {
|
||||
/// using (var helper = gizmos.GetGizmoHelper(active, hasher)) {
|
||||
/// builder.DrawLine(a, b, color);
|
||||
/// builder.Finalize(gizmos, hasher);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public class RetainedGizmos {
|
||||
/// <summary>Combines hashes into a single hash value</summary>
|
||||
public struct Hasher {
|
||||
ulong hash;
|
||||
bool includePathSearchInfo;
|
||||
bool includeAreaInfo;
|
||||
PathHandler debugData;
|
||||
|
||||
public Hasher (AstarPath active) {
|
||||
hash = 0;
|
||||
this.debugData = active.debugPathData;
|
||||
includePathSearchInfo = debugData != null && (active.debugMode == GraphDebugMode.F || active.debugMode == GraphDebugMode.G || active.debugMode == GraphDebugMode.H || active.showSearchTree);
|
||||
includeAreaInfo = active.debugMode == GraphDebugMode.Areas;
|
||||
AddHash((int)active.debugMode);
|
||||
AddHash(active.debugFloor.GetHashCode());
|
||||
AddHash(active.debugRoof.GetHashCode());
|
||||
AddHash(AstarColor.ColorHash());
|
||||
}
|
||||
|
||||
public void AddHash (int hash) {
|
||||
this.hash = (1572869UL * this.hash) ^ (ulong)hash;
|
||||
}
|
||||
|
||||
public void HashNode (GraphNode node) {
|
||||
AddHash(node.GetGizmoHashCode());
|
||||
if (includeAreaInfo) AddHash((int)node.Area);
|
||||
|
||||
if (includePathSearchInfo) {
|
||||
var pathNode = debugData.GetPathNode(node.NodeIndex);
|
||||
AddHash((int)pathNode.pathID);
|
||||
AddHash(pathNode.pathID == debugData.PathID ? 1 : 0);
|
||||
AddHash((int)pathNode.F);
|
||||
}
|
||||
}
|
||||
|
||||
public ulong Hash {
|
||||
get {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Helper for drawing gizmos</summary>
|
||||
public class Builder : IAstarPooledObject {
|
||||
List<Vector3> lines = new List<Vector3>();
|
||||
List<Color32> lineColors = new List<Color32>();
|
||||
List<Mesh> meshes = new List<Mesh>();
|
||||
|
||||
public void DrawMesh (RetainedGizmos gizmos, Vector3[] vertices, List<int> triangles, Color[] colors) {
|
||||
var mesh = gizmos.GetMesh();
|
||||
|
||||
// Set all data on the mesh
|
||||
mesh.vertices = vertices;
|
||||
mesh.SetTriangles(triangles, 0);
|
||||
mesh.colors = colors;
|
||||
|
||||
// Upload all data
|
||||
mesh.UploadMeshData(false);
|
||||
meshes.Add(mesh);
|
||||
}
|
||||
|
||||
/// <summary>Draws a wire cube after being transformed the specified transformation</summary>
|
||||
public void DrawWireCube (GraphTransform tr, Bounds bounds, Color color) {
|
||||
var min = bounds.min;
|
||||
var max = bounds.max;
|
||||
|
||||
DrawLine(tr.Transform(new Vector3(min.x, min.y, min.z)), tr.Transform(new Vector3(max.x, min.y, min.z)), color);
|
||||
DrawLine(tr.Transform(new Vector3(max.x, min.y, min.z)), tr.Transform(new Vector3(max.x, min.y, max.z)), color);
|
||||
DrawLine(tr.Transform(new Vector3(max.x, min.y, max.z)), tr.Transform(new Vector3(min.x, min.y, max.z)), color);
|
||||
DrawLine(tr.Transform(new Vector3(min.x, min.y, max.z)), tr.Transform(new Vector3(min.x, min.y, min.z)), color);
|
||||
|
||||
DrawLine(tr.Transform(new Vector3(min.x, max.y, min.z)), tr.Transform(new Vector3(max.x, max.y, min.z)), color);
|
||||
DrawLine(tr.Transform(new Vector3(max.x, max.y, min.z)), tr.Transform(new Vector3(max.x, max.y, max.z)), color);
|
||||
DrawLine(tr.Transform(new Vector3(max.x, max.y, max.z)), tr.Transform(new Vector3(min.x, max.y, max.z)), color);
|
||||
DrawLine(tr.Transform(new Vector3(min.x, max.y, max.z)), tr.Transform(new Vector3(min.x, max.y, min.z)), color);
|
||||
|
||||
DrawLine(tr.Transform(new Vector3(min.x, min.y, min.z)), tr.Transform(new Vector3(min.x, max.y, min.z)), color);
|
||||
DrawLine(tr.Transform(new Vector3(max.x, min.y, min.z)), tr.Transform(new Vector3(max.x, max.y, min.z)), color);
|
||||
DrawLine(tr.Transform(new Vector3(max.x, min.y, max.z)), tr.Transform(new Vector3(max.x, max.y, max.z)), color);
|
||||
DrawLine(tr.Transform(new Vector3(min.x, min.y, max.z)), tr.Transform(new Vector3(min.x, max.y, max.z)), color);
|
||||
}
|
||||
|
||||
public void DrawLine (Vector3 start, Vector3 end, Color color) {
|
||||
lines.Add(start);
|
||||
lines.Add(end);
|
||||
var col32 = (Color32)color;
|
||||
lineColors.Add(col32);
|
||||
lineColors.Add(col32);
|
||||
}
|
||||
|
||||
public void Submit (RetainedGizmos gizmos, Hasher hasher) {
|
||||
SubmitLines(gizmos, hasher.Hash);
|
||||
SubmitMeshes(gizmos, hasher.Hash);
|
||||
}
|
||||
|
||||
void SubmitMeshes (RetainedGizmos gizmos, ulong hash) {
|
||||
for (int i = 0; i < meshes.Count; i++) {
|
||||
gizmos.meshes.Add(new MeshWithHash { hash = hash, mesh = meshes[i], lines = false });
|
||||
gizmos.existingHashes.Add(hash);
|
||||
}
|
||||
}
|
||||
|
||||
void SubmitLines (RetainedGizmos gizmos, ulong hash) {
|
||||
// Unity only supports 65535 vertices per mesh. 65532 used because MaxLineEndPointsPerBatch needs to be even.
|
||||
const int MaxLineEndPointsPerBatch = 65532/2;
|
||||
int batches = (lines.Count + MaxLineEndPointsPerBatch - 1)/MaxLineEndPointsPerBatch;
|
||||
|
||||
for (int batch = 0; batch < batches; batch++) {
|
||||
int startIndex = MaxLineEndPointsPerBatch * batch;
|
||||
int endIndex = Mathf.Min(startIndex + MaxLineEndPointsPerBatch, lines.Count);
|
||||
int lineEndPointCount = endIndex - startIndex;
|
||||
UnityEngine.Assertions.Assert.IsTrue(lineEndPointCount % 2 == 0);
|
||||
|
||||
// Use pooled lists to avoid excessive allocations
|
||||
var vertices = ListPool<Vector3>.Claim(lineEndPointCount*2);
|
||||
var colors = ListPool<Color32>.Claim(lineEndPointCount*2);
|
||||
var normals = ListPool<Vector3>.Claim(lineEndPointCount*2);
|
||||
var uv = ListPool<Vector2>.Claim(lineEndPointCount*2);
|
||||
var tris = ListPool<int>.Claim(lineEndPointCount*3);
|
||||
// Loop through each endpoint of the lines
|
||||
// and add 2 vertices for each
|
||||
for (int j = startIndex; j < endIndex; j++) {
|
||||
var vertex = (Vector3)lines[j];
|
||||
vertices.Add(vertex);
|
||||
vertices.Add(vertex);
|
||||
|
||||
var color = (Color32)lineColors[j];
|
||||
colors.Add(color);
|
||||
colors.Add(color);
|
||||
uv.Add(new Vector2(0, 0));
|
||||
uv.Add(new Vector2(1, 0));
|
||||
}
|
||||
|
||||
// Loop through each line and add
|
||||
// one normal for each vertex
|
||||
for (int j = startIndex; j < endIndex; j += 2) {
|
||||
var lineDir = (Vector3)(lines[j+1] - lines[j]);
|
||||
// Store the line direction in the normals.
|
||||
// A line consists of 4 vertices. The line direction will be used to
|
||||
// offset the vertices to create a line with a fixed pixel thickness
|
||||
normals.Add(lineDir);
|
||||
normals.Add(lineDir);
|
||||
normals.Add(lineDir);
|
||||
normals.Add(lineDir);
|
||||
}
|
||||
|
||||
// Setup triangle indices
|
||||
// A triangle consists of 3 indices
|
||||
// A line (4 vertices) consists of 2 triangles, so 6 triangle indices
|
||||
for (int j = 0, v = 0; j < lineEndPointCount*3; j += 6, v += 4) {
|
||||
// First triangle
|
||||
tris.Add(v+0);
|
||||
tris.Add(v+1);
|
||||
tris.Add(v+2);
|
||||
|
||||
// Second triangle
|
||||
tris.Add(v+1);
|
||||
tris.Add(v+3);
|
||||
tris.Add(v+2);
|
||||
}
|
||||
|
||||
var mesh = gizmos.GetMesh();
|
||||
|
||||
// Set all data on the mesh
|
||||
mesh.SetVertices(vertices);
|
||||
mesh.SetTriangles(tris, 0);
|
||||
mesh.SetColors(colors);
|
||||
mesh.SetNormals(normals);
|
||||
mesh.SetUVs(0, uv);
|
||||
|
||||
// Upload all data
|
||||
mesh.UploadMeshData(false);
|
||||
|
||||
// Release the lists back to the pool
|
||||
ListPool<Vector3>.Release(ref vertices);
|
||||
ListPool<Color32>.Release(ref colors);
|
||||
ListPool<Vector3>.Release(ref normals);
|
||||
ListPool<Vector2>.Release(ref uv);
|
||||
ListPool<int>.Release(ref tris);
|
||||
|
||||
gizmos.meshes.Add(new MeshWithHash { hash = hash, mesh = mesh, lines = true });
|
||||
gizmos.existingHashes.Add(hash);
|
||||
}
|
||||
}
|
||||
|
||||
void IAstarPooledObject.OnEnterPool () {
|
||||
lines.Clear();
|
||||
lineColors.Clear();
|
||||
meshes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
struct MeshWithHash {
|
||||
public ulong hash;
|
||||
public Mesh mesh;
|
||||
public bool lines;
|
||||
}
|
||||
|
||||
List<MeshWithHash> meshes = new List<MeshWithHash>();
|
||||
HashSet<ulong> usedHashes = new HashSet<ulong>();
|
||||
HashSet<ulong> existingHashes = new HashSet<ulong>();
|
||||
Stack<Mesh> cachedMeshes = new Stack<Mesh>();
|
||||
|
||||
public GraphGizmoHelper GetSingleFrameGizmoHelper (AstarPath active) {
|
||||
var uniqHash = new RetainedGizmos.Hasher();
|
||||
|
||||
uniqHash.AddHash(Time.realtimeSinceStartup.GetHashCode());
|
||||
Draw(uniqHash);
|
||||
return GetGizmoHelper(active, uniqHash);
|
||||
}
|
||||
|
||||
public GraphGizmoHelper GetGizmoHelper (AstarPath active, Hasher hasher) {
|
||||
var helper = ObjectPool<GraphGizmoHelper>.Claim();
|
||||
|
||||
helper.Init(active, hasher, this);
|
||||
return helper;
|
||||
}
|
||||
|
||||
void PoolMesh (Mesh mesh) {
|
||||
mesh.Clear();
|
||||
cachedMeshes.Push(mesh);
|
||||
}
|
||||
|
||||
Mesh GetMesh () {
|
||||
if (cachedMeshes.Count > 0) {
|
||||
return cachedMeshes.Pop();
|
||||
} else {
|
||||
return new Mesh {
|
||||
hideFlags = HideFlags.DontSave
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Material to use for the navmesh in the editor</summary>
|
||||
public Material surfaceMaterial;
|
||||
|
||||
/// <summary>Material to use for the navmesh outline in the editor</summary>
|
||||
public Material lineMaterial;
|
||||
|
||||
/// <summary>True if there already is a mesh with the specified hash</summary>
|
||||
public bool HasCachedMesh (Hasher hasher) {
|
||||
return existingHashes.Contains(hasher.Hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules the meshes for the specified hash to be drawn.
|
||||
/// Returns: False if there is no cached mesh for this hash, you may want to
|
||||
/// submit one in that case. The draw command will be issued regardless of the return value.
|
||||
/// </summary>
|
||||
public bool Draw (Hasher hasher) {
|
||||
usedHashes.Add(hasher.Hash);
|
||||
return HasCachedMesh(hasher);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules all meshes that were drawn the last frame (last time FinalizeDraw was called) to be drawn again.
|
||||
/// Also draws any new meshes that have been added since FinalizeDraw was last called.
|
||||
/// </summary>
|
||||
public void DrawExisting () {
|
||||
for (int i = 0; i < meshes.Count; i++) {
|
||||
usedHashes.Add(meshes[i].hash);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Call after all <see cref="Draw"/> commands for the frame have been done to draw everything</summary>
|
||||
public void FinalizeDraw () {
|
||||
RemoveUnusedMeshes(meshes);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Make sure the material references are correct
|
||||
if (surfaceMaterial == null) surfaceMaterial = UnityEditor.AssetDatabase.LoadAssetAtPath(EditorResourceHelper.editorAssets + "/Materials/Navmesh.mat", typeof(Material)) as Material;
|
||||
if (lineMaterial == null) lineMaterial = UnityEditor.AssetDatabase.LoadAssetAtPath(EditorResourceHelper.editorAssets + "/Materials/NavmeshOutline.mat", typeof(Material)) as Material;
|
||||
#endif
|
||||
|
||||
var cam = Camera.current;
|
||||
var planes = GeometryUtility.CalculateFrustumPlanes(cam);
|
||||
|
||||
// Silently do nothing if the materials are not set
|
||||
if (surfaceMaterial == null || lineMaterial == null) return;
|
||||
|
||||
Profiler.BeginSample("Draw Retained Gizmos");
|
||||
// First surfaces, then lines
|
||||
for (int matIndex = 0; matIndex <= 1; matIndex++) {
|
||||
var mat = matIndex == 0 ? surfaceMaterial : lineMaterial;
|
||||
for (int pass = 0; pass < mat.passCount; pass++) {
|
||||
mat.SetPass(pass);
|
||||
for (int i = 0; i < meshes.Count; i++) {
|
||||
if (meshes[i].lines == (mat == lineMaterial) && GeometryUtility.TestPlanesAABB(planes, meshes[i].mesh.bounds)) {
|
||||
Graphics.DrawMeshNow(meshes[i].mesh, Matrix4x4.identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usedHashes.Clear();
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys all cached meshes.
|
||||
/// Used to make sure that no memory leaks happen in the Unity Editor.
|
||||
/// </summary>
|
||||
public void ClearCache () {
|
||||
usedHashes.Clear();
|
||||
RemoveUnusedMeshes(meshes);
|
||||
|
||||
while (cachedMeshes.Count > 0) {
|
||||
Mesh.DestroyImmediate(cachedMeshes.Pop());
|
||||
}
|
||||
|
||||
UnityEngine.Assertions.Assert.IsTrue(meshes.Count == 0);
|
||||
}
|
||||
|
||||
void RemoveUnusedMeshes (List<MeshWithHash> meshList) {
|
||||
// Walk the array with two pointers
|
||||
// i pointing to the entry that should be filled with something
|
||||
// and j pointing to the entry that is a potential candidate for
|
||||
// filling the entry at i.
|
||||
// When j reaches the end of the list it will be reduced in size
|
||||
for (int i = 0, j = 0; i < meshList.Count;) {
|
||||
if (j == meshList.Count) {
|
||||
j--;
|
||||
meshList.RemoveAt(j);
|
||||
} else if (usedHashes.Contains(meshList[j].hash)) {
|
||||
meshList[i] = meshList[j];
|
||||
i++;
|
||||
j++;
|
||||
} else {
|
||||
PoolMesh(meshList[j].mesh);
|
||||
existingHashes.Remove(meshList[j].hash);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
AR/Assets/AstarPathfindingProject/Utilities/RetainedGizmos.cs.meta
generated
Normal file
12
AR/Assets/AstarPathfindingProject/Utilities/RetainedGizmos.cs.meta
generated
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a3cd109c93704c7ba499b8653f402ac
|
||||
timeCreated: 1472980867
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding {
|
||||
[ExecuteInEditMode]
|
||||
/// <summary>
|
||||
/// Helper class to keep track of references to GameObjects.
|
||||
/// Does nothing more than to hold a GUID value.
|
||||
/// </summary>
|
||||
[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_unity_reference_helper.php")]
|
||||
public class UnityReferenceHelper : MonoBehaviour {
|
||||
[HideInInspector]
|
||||
[SerializeField]
|
||||
private string guid;
|
||||
|
||||
public string GetGUID () {
|
||||
return guid;
|
||||
}
|
||||
|
||||
public void Awake () {
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset () {
|
||||
if (string.IsNullOrEmpty(guid)) {
|
||||
guid = Pathfinding.Util.Guid.NewGuid().ToString();
|
||||
Debug.Log("Created new GUID - " + guid, this);
|
||||
} else if (gameObject.scene.name != null) {
|
||||
// Create a new GUID if there are duplicates in the scene.
|
||||
// Don't do this if this is a prefab (scene.name == null)
|
||||
foreach (UnityReferenceHelper urh in FindObjectsOfType(typeof(UnityReferenceHelper)) as UnityReferenceHelper[]) {
|
||||
if (urh != this && guid == urh.guid) {
|
||||
guid = Pathfinding.Util.Guid.NewGuid().ToString();
|
||||
Debug.Log("Created new GUID - " + guid, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
AR/Assets/AstarPathfindingProject/Utilities/UnityReferenceHelper.cs.meta
generated
Normal file
7
AR/Assets/AstarPathfindingProject/Utilities/UnityReferenceHelper.cs.meta
generated
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 632ae7eb1d4ce49d38a1e38ee7efe7c5
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
Reference in New Issue
Block a user