SimpleSmoothModifier.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using Pathfinding.Util;
  4. namespace Pathfinding {
  5. [AddComponentMenu("Pathfinding/Modifiers/Simple Smooth")]
  6. [System.Serializable]
  7. [RequireComponent(typeof(Seeker))]
  8. /// <summary>
  9. /// Modifier which smooths the path. This modifier can smooth a path by either moving the points closer together (Simple) or using Bezier curves (Bezier).
  10. ///
  11. /// Attach this component to the same GameObject as a Seeker component.
  12. ///
  13. /// This component will hook in to the Seeker's path post-processing system and will post process any paths it searches for.
  14. /// Take a look at the Modifier Priorities settings on the Seeker component to determine where in the process this modifier should process the path.
  15. ///
  16. /// Several smoothing types are available, here follows a list of them and a short description of what they do, and how they work.
  17. /// But the best way is really to experiment with them yourself.
  18. ///
  19. /// - <b>Simple</b> Smooths the path by drawing all points close to each other. This results in paths that might cut corners if you are not careful.
  20. /// It will also subdivide the path to create more more points to smooth as otherwise it would still be quite rough.
  21. /// [Open online documentation to see images]
  22. /// - <b>Bezier</b> Smooths the path using Bezier curves. This results a smooth path which will always pass through all points in the path, but make sure it doesn't turn too quickly.
  23. /// [Open online documentation to see images]
  24. /// - <b>OffsetSimple</b> An alternative to Simple smooth which will offset the path outwards in each step to minimize the corner-cutting.
  25. /// But be careful, if too high values are used, it will turn into loops and look really ugly.
  26. /// - <b>Curved Non Uniform</b> [Open online documentation to see images]
  27. ///
  28. /// Note: Modifies vectorPath array
  29. /// TODO: Make the smooth modifier take the world geometry into account when smoothing
  30. /// </summary>
  31. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_simple_smooth_modifier.php")]
  32. public class SimpleSmoothModifier : MonoModifier {
  33. #if UNITY_EDITOR
  34. [UnityEditor.MenuItem("CONTEXT/Seeker/Add Simple Smooth Modifier")]
  35. public static void AddComp (UnityEditor.MenuCommand command) {
  36. (command.context as Component).gameObject.AddComponent(typeof(SimpleSmoothModifier));
  37. }
  38. #endif
  39. public override int Order { get { return 50; } }
  40. /// <summary>Type of smoothing to use</summary>
  41. public SmoothType smoothType = SmoothType.Simple;
  42. /// <summary>Number of times to subdivide when not using a uniform length</summary>
  43. [Tooltip("The number of times to subdivide (divide in half) the path segments. [0...inf] (recommended [1...10])")]
  44. public int subdivisions = 2;
  45. /// <summary>Number of times to apply smoothing</summary>
  46. [Tooltip("Number of times to apply smoothing")]
  47. public int iterations = 2;
  48. /// <summary>Determines how much smoothing to apply in each smooth iteration. 0.5 usually produces the nicest looking curves.</summary>
  49. [Tooltip("Determines how much smoothing to apply in each smooth iteration. 0.5 usually produces the nicest looking curves")]
  50. [Range(0, 1)]
  51. public float strength = 0.5F;
  52. /// <summary>
  53. /// Toggle to divide all lines in equal length segments.
  54. /// See: <see cref="maxSegmentLength"/>
  55. /// </summary>
  56. [Tooltip("Toggle to divide all lines in equal length segments")]
  57. public bool uniformLength = true;
  58. /// <summary>
  59. /// The length of the segments in the smoothed path when using <see cref="uniformLength"/>.
  60. /// A high value yields rough paths and low value yields very smooth paths, but is slower
  61. /// </summary>
  62. [Tooltip("The length of each segment in the smoothed path. A high value yields rough paths and low value yields very smooth paths, but is slower")]
  63. public float maxSegmentLength = 2F;
  64. /// <summary>Length factor of the bezier curves' tangents'</summary>
  65. [Tooltip("Length factor of the bezier curves' tangents")]
  66. public float bezierTangentLength = 0.4F;
  67. /// <summary>Offset to apply in each smoothing iteration when using Offset Simple. See: <see cref="smoothType"/></summary>
  68. [Tooltip("Offset to apply in each smoothing iteration when using Offset Simple")]
  69. public float offset = 0.2F;
  70. /// <summary>Roundness factor used for CurvedNonuniform</summary>
  71. [Tooltip("How much to smooth the path. A higher value will give a smoother path, but might take the character far off the optimal path.")]
  72. public float factor = 0.1F;
  73. public enum SmoothType {
  74. Simple,
  75. Bezier,
  76. OffsetSimple,
  77. CurvedNonuniform
  78. }
  79. public override void Apply (Path p) {
  80. // This should never trigger unless some other modifier has messed stuff up
  81. if (p.vectorPath == null) {
  82. Debug.LogWarning("Can't process NULL path (has another modifier logged an error?)");
  83. return;
  84. }
  85. List<Vector3> path = null;
  86. switch (smoothType) {
  87. case SmoothType.Simple:
  88. path = SmoothSimple(p.vectorPath); break;
  89. case SmoothType.Bezier:
  90. path = SmoothBezier(p.vectorPath); break;
  91. case SmoothType.OffsetSimple:
  92. path = SmoothOffsetSimple(p.vectorPath); break;
  93. case SmoothType.CurvedNonuniform:
  94. path = CurvedNonuniform(p.vectorPath); break;
  95. }
  96. if (path != p.vectorPath) {
  97. ListPool<Vector3>.Release(ref p.vectorPath);
  98. p.vectorPath = path;
  99. }
  100. }
  101. public List<Vector3> CurvedNonuniform (List<Vector3> path) {
  102. if (maxSegmentLength <= 0) {
  103. Debug.LogWarning("Max Segment Length is <= 0 which would cause DivByZero-exception or other nasty errors (avoid this)");
  104. return path;
  105. }
  106. int pointCounter = 0;
  107. for (int i = 0; i < path.Count-1; i++) {
  108. //pointCounter += Mathf.FloorToInt ((path[i]-path[i+1]).magnitude / maxSegmentLength)+1;
  109. float dist = (path[i]-path[i+1]).magnitude;
  110. //In order to avoid floating point errors as much as possible, and in lack of a better solution
  111. //loop through it EXACTLY as the other code further down will
  112. for (float t = 0; t <= dist; t += maxSegmentLength) {
  113. pointCounter++;
  114. }
  115. }
  116. List<Vector3> subdivided = ListPool<Vector3>.Claim(pointCounter);
  117. // Set first velocity
  118. Vector3 preEndVel = (path[1]-path[0]).normalized;
  119. for (int i = 0; i < path.Count-1; i++) {
  120. float dist = (path[i]-path[i+1]).magnitude;
  121. Vector3 startVel1 = preEndVel;
  122. Vector3 endVel1 = i < path.Count-2 ? ((path[i+2]-path[i+1]).normalized - (path[i]-path[i+1]).normalized).normalized : (path[i+1]-path[i]).normalized;
  123. Vector3 startVel = startVel1 * dist * factor;
  124. Vector3 endVel = endVel1 * dist * factor;
  125. Vector3 start = path[i];
  126. Vector3 end = path[i+1];
  127. float onedivdist = 1F / dist;
  128. for (float t = 0; t <= dist; t += maxSegmentLength) {
  129. float t2 = t * onedivdist;
  130. subdivided.Add(GetPointOnCubic(start, end, startVel, endVel, t2));
  131. }
  132. preEndVel = endVel1;
  133. }
  134. subdivided[subdivided.Count-1] = path[path.Count-1];
  135. return subdivided;
  136. }
  137. public static Vector3 GetPointOnCubic (Vector3 a, Vector3 b, Vector3 tan1, Vector3 tan2, float t) {
  138. float t2 = t*t, t3 = t2*t;
  139. float h1 = 2*t3 - 3*t2 + 1; // calculate basis function 1
  140. float h2 = -2*t3 + 3*t2; // calculate basis function 2
  141. float h3 = t3 - 2*t2 + t; // calculate basis function 3
  142. float h4 = t3 - t2; // calculate basis function 4
  143. return h1*a + // multiply and sum all funtions
  144. h2*b + // together to build the interpolated
  145. h3*tan1 + // point along the curve.
  146. h4*tan2;
  147. }
  148. public List<Vector3> SmoothOffsetSimple (List<Vector3> path) {
  149. if (path.Count <= 2 || iterations <= 0) {
  150. return path;
  151. }
  152. if (iterations > 12) {
  153. Debug.LogWarning("A very high iteration count was passed, won't let this one through");
  154. return path;
  155. }
  156. int maxLength = (path.Count-2)*(int)Mathf.Pow(2, iterations)+2;
  157. List<Vector3> subdivided = ListPool<Vector3>.Claim(maxLength);
  158. List<Vector3> subdivided2 = ListPool<Vector3>.Claim(maxLength);
  159. for (int i = 0; i < maxLength; i++) { subdivided.Add(Vector3.zero); subdivided2.Add(Vector3.zero); }
  160. for (int i = 0; i < path.Count; i++) {
  161. subdivided[i] = path[i];
  162. }
  163. for (int iteration = 0; iteration < iterations; iteration++) {
  164. int currentPathLength = (path.Count-2)*(int)Mathf.Pow(2, iteration)+2;
  165. //Switch the arrays
  166. List<Vector3> tmp = subdivided;
  167. subdivided = subdivided2;
  168. subdivided2 = tmp;
  169. const float nextMultiplier = 1F;
  170. for (int i = 0; i < currentPathLength-1; i++) {
  171. Vector3 current = subdivided2[i];
  172. Vector3 next = subdivided2[i+1];
  173. Vector3 normal = Vector3.Cross(next-current, Vector3.up);
  174. normal = normal.normalized;
  175. bool firstRight = false;
  176. bool secondRight = false;
  177. bool setFirst = false;
  178. bool setSecond = false;
  179. if (i != 0 && !VectorMath.IsColinearXZ(current, next, subdivided2[i-1])) {
  180. setFirst = true;
  181. firstRight = VectorMath.RightOrColinearXZ(current, next, subdivided2[i-1]);
  182. }
  183. if (i < currentPathLength-1 && !VectorMath.IsColinearXZ(current, next, subdivided2[i+2])) {
  184. setSecond = true;
  185. secondRight = VectorMath.RightOrColinearXZ(current, next, subdivided2[i+2]);
  186. }
  187. if (setFirst) {
  188. subdivided[i*2] = current + (firstRight ? normal*offset*nextMultiplier : -normal*offset*nextMultiplier);
  189. } else {
  190. subdivided[i*2] = current;
  191. }
  192. if (setSecond) {
  193. subdivided[i*2+1] = next + (secondRight ? normal*offset*nextMultiplier : -normal*offset*nextMultiplier);
  194. } else {
  195. subdivided[i*2+1] = next;
  196. }
  197. }
  198. subdivided[(path.Count-2)*(int)Mathf.Pow(2, iteration+1)+2-1] = subdivided2[currentPathLength-1];
  199. }
  200. ListPool<Vector3>.Release(ref subdivided2);
  201. return subdivided;
  202. }
  203. public List<Vector3> SmoothSimple (List<Vector3> path) {
  204. if (path.Count < 2) return path;
  205. List<Vector3> subdivided;
  206. if (uniformLength) {
  207. // Clamp to a small value to avoid the path being divided into a huge number of segments
  208. maxSegmentLength = Mathf.Max(maxSegmentLength, 0.005f);
  209. float pathLength = 0;
  210. for (int i = 0; i < path.Count-1; i++) {
  211. pathLength += Vector3.Distance(path[i], path[i+1]);
  212. }
  213. int estimatedNumberOfSegments = Mathf.FloorToInt(pathLength / maxSegmentLength);
  214. // Get a list with an initial capacity high enough so that we can add all points
  215. subdivided = ListPool<Vector3>.Claim(estimatedNumberOfSegments+2);
  216. float distanceAlong = 0;
  217. // Sample points every [maxSegmentLength] world units along the path
  218. for (int i = 0; i < path.Count-1; i++) {
  219. var start = path[i];
  220. var end = path[i+1];
  221. float length = Vector3.Distance(start, end);
  222. while (distanceAlong < length) {
  223. subdivided.Add(Vector3.Lerp(start, end, distanceAlong / length));
  224. distanceAlong += maxSegmentLength;
  225. }
  226. distanceAlong -= length;
  227. }
  228. // Make sure we get the exact position of the last point
  229. subdivided.Add(path[path.Count-1]);
  230. } else {
  231. subdivisions = Mathf.Max(subdivisions, 0);
  232. if (subdivisions > 10) {
  233. Debug.LogWarning("Very large number of subdivisions. Cowardly refusing to subdivide every segment into more than " + (1 << subdivisions) + " subsegments");
  234. subdivisions = 10;
  235. }
  236. int steps = 1 << subdivisions;
  237. subdivided = ListPool<Vector3>.Claim((path.Count-1)*steps + 1);
  238. Polygon.Subdivide(path, subdivided, steps);
  239. }
  240. if (strength > 0) {
  241. for (int it = 0; it < iterations; it++) {
  242. Vector3 prev = subdivided[0];
  243. for (int i = 1; i < subdivided.Count-1; i++) {
  244. Vector3 tmp = subdivided[i];
  245. // prev is at this point set to the value that subdivided[i-1] had before this loop started
  246. // Move the point closer to the average of the adjacent points
  247. subdivided[i] = Vector3.Lerp(tmp, (prev+subdivided[i+1])/2F, strength);
  248. prev = tmp;
  249. }
  250. }
  251. }
  252. return subdivided;
  253. }
  254. public List<Vector3> SmoothBezier (List<Vector3> path) {
  255. if (subdivisions < 0) subdivisions = 0;
  256. int subMult = 1 << subdivisions;
  257. List<Vector3> subdivided = ListPool<Vector3>.Claim();
  258. for (int i = 0; i < path.Count-1; i++) {
  259. Vector3 tangent1;
  260. Vector3 tangent2;
  261. if (i == 0) {
  262. tangent1 = path[i+1]-path[i];
  263. } else {
  264. tangent1 = path[i+1]-path[i-1];
  265. }
  266. if (i == path.Count-2) {
  267. tangent2 = path[i]-path[i+1];
  268. } else {
  269. tangent2 = path[i]-path[i+2];
  270. }
  271. tangent1 *= bezierTangentLength;
  272. tangent2 *= bezierTangentLength;
  273. Vector3 v1 = path[i];
  274. Vector3 v2 = v1+tangent1;
  275. Vector3 v4 = path[i+1];
  276. Vector3 v3 = v4+tangent2;
  277. for (int j = 0; j < subMult; j++) {
  278. subdivided.Add(AstarSplines.CubicBezier(v1, v2, v3, v4, (float)j/subMult));
  279. }
  280. }
  281. // Assign the last point
  282. subdivided.Add(path[path.Count-1]);
  283. return subdivided;
  284. }
  285. }
  286. }