RadiusModifier.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. using UnityEngine;
  2. using System;
  3. using System.Collections.Generic;
  4. namespace Pathfinding {
  5. /// <summary>
  6. /// Radius path modifier for offsetting paths.
  7. ///
  8. /// The radius modifier will offset the path to create the effect
  9. /// of adjusting it to the characters radius.
  10. /// It gives good results on navmeshes which have not been offset with the
  11. /// character radius during scan. Especially useful when characters with different
  12. /// radiuses are used on the same navmesh. It is also useful when using
  13. /// rvo local avoidance with the RVONavmesh since the RVONavmesh assumes the
  14. /// navmesh has not been offset with the character radius.
  15. ///
  16. /// This modifier assumes all paths are in the XZ plane (i.e Y axis is up).
  17. ///
  18. /// It is recommended to use the Funnel Modifier on the path as well.
  19. ///
  20. /// [Open online documentation to see images]
  21. ///
  22. /// See: RVONavmesh
  23. /// See: modifiers
  24. ///
  25. /// Also check out the howto page "Using Modifiers".
  26. ///
  27. /// Since: Added in 3.2.6
  28. /// </summary>
  29. [AddComponentMenu("Pathfinding/Modifiers/Radius Offset")]
  30. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_radius_modifier.php")]
  31. public class RadiusModifier : MonoModifier {
  32. #if UNITY_EDITOR
  33. [UnityEditor.MenuItem("CONTEXT/Seeker/Add Radius Modifier")]
  34. public static void AddComp (UnityEditor.MenuCommand command) {
  35. (command.context as Component).gameObject.AddComponent(typeof(RadiusModifier));
  36. }
  37. #endif
  38. public override int Order { get { return 41; } }
  39. /// <summary>
  40. /// Radius of the circle segments generated.
  41. /// Usually similar to the character radius.
  42. /// </summary>
  43. public float radius = 1f;
  44. /// <summary>
  45. /// Detail of generated circle segments.
  46. /// Measured as steps per full circle.
  47. ///
  48. /// It is more performant to use a low value.
  49. /// For movement, using a high value will barely improve path quality.
  50. /// </summary>
  51. public float detail = 10;
  52. /// <summary>
  53. /// Calculates inner tangents for a pair of circles.
  54. ///
  55. /// Add a to sigma to get the first tangent angle, subtract a from sigma to get the second tangent angle.
  56. ///
  57. /// Returns: True on success. False when the circles are overlapping.
  58. /// </summary>
  59. /// <param name="p1">Position of first circle</param>
  60. /// <param name="p2">Position of the second circle</param>
  61. /// <param name="r1">Radius of the first circle</param>
  62. /// <param name="r2">Radius of the second circle</param>
  63. /// <param name="a">Angle from the line joining the centers of the circles to the inner tangents.</param>
  64. /// <param name="sigma">World angle from p1 to p2 (in XZ space)</param>
  65. bool CalculateCircleInner (Vector3 p1, Vector3 p2, float r1, float r2, out float a, out float sigma) {
  66. float dist = (p1-p2).magnitude;
  67. if (r1+r2 > dist) {
  68. a = 0;
  69. sigma = 0;
  70. return false;
  71. }
  72. a = (float)Math.Acos((r1+r2)/dist);
  73. sigma = (float)Math.Atan2(p2.z-p1.z, p2.x-p1.x);
  74. return true;
  75. }
  76. /// <summary>
  77. /// Calculates outer tangents for a pair of circles.
  78. ///
  79. /// Add a to sigma to get the first tangent angle, subtract a from sigma to get the second tangent angle.
  80. ///
  81. /// Returns: True on success. False on failure (more specifically when |r1-r2| > |p1-p2| )
  82. /// </summary>
  83. /// <param name="p1">Position of first circle</param>
  84. /// <param name="p2">Position of the second circle</param>
  85. /// <param name="r1">Radius of the first circle</param>
  86. /// <param name="r2">Radius of the second circle</param>
  87. /// <param name="a">Angle from the line joining the centers of the circles to the inner tangents.</param>
  88. /// <param name="sigma">World angle from p1 to p2 (in XZ space)</param>
  89. bool CalculateCircleOuter (Vector3 p1, Vector3 p2, float r1, float r2, out float a, out float sigma) {
  90. float dist = (p1-p2).magnitude;
  91. if (Math.Abs(r1 - r2) > dist) {
  92. a = 0;
  93. sigma = 0;
  94. return false;
  95. }
  96. a = (float)Math.Acos((r1-r2)/dist);
  97. sigma = (float)Math.Atan2(p2.z-p1.z, p2.x-p1.x);
  98. return true;
  99. }
  100. [System.Flags]
  101. enum TangentType {
  102. OuterRight = 1 << 0,
  103. InnerRightLeft = 1 << 1,
  104. InnerLeftRight = 1 << 2,
  105. OuterLeft = 1 << 3,
  106. Outer = OuterRight | OuterLeft,
  107. Inner = InnerRightLeft | InnerLeftRight
  108. }
  109. TangentType CalculateTangentType (Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4) {
  110. bool l1 = VectorMath.RightOrColinearXZ(p1, p2, p3);
  111. bool l2 = VectorMath.RightOrColinearXZ(p2, p3, p4);
  112. return (TangentType)(1 << ((l1 ? 2 : 0) + (l2 ? 1 : 0)));
  113. }
  114. TangentType CalculateTangentTypeSimple (Vector3 p1, Vector3 p2, Vector3 p3) {
  115. bool l2 = VectorMath.RightOrColinearXZ(p1, p2, p3);
  116. bool l1 = l2;
  117. return (TangentType)(1 << ((l1 ? 2 : 0) + (l2 ? 1 : 0)));
  118. }
  119. public override void Apply (Path p) {
  120. List<Vector3> vs = p.vectorPath;
  121. List<Vector3> res = Apply(vs);
  122. if (res != vs) {
  123. Pathfinding.Util.ListPool<Vector3>.Release(ref p.vectorPath);
  124. p.vectorPath = res;
  125. }
  126. }
  127. float[] radi = new float[10];
  128. float[] a1 = new float[10];
  129. float[] a2 = new float[10];
  130. bool[] dir = new bool[10];
  131. /// <summary>Apply this modifier on a raw Vector3 list</summary>
  132. public List<Vector3> Apply (List<Vector3> vs) {
  133. if (vs == null || vs.Count < 3) return vs;
  134. /// <summary>TODO: Do something about these allocations</summary>
  135. if (radi.Length < vs.Count) {
  136. radi = new float[vs.Count];
  137. a1 = new float[vs.Count];
  138. a2 = new float[vs.Count];
  139. dir = new bool[vs.Count];
  140. }
  141. for (int i = 0; i < vs.Count; i++) {
  142. radi[i] = radius;
  143. }
  144. radi[0] = 0;
  145. radi[vs.Count-1] = 0;
  146. int count = 0;
  147. for (int i = 0; i < vs.Count-1; i++) {
  148. count++;
  149. if (count > 2*vs.Count) {
  150. Debug.LogWarning("Could not resolve radiuses, the path is too complex. Try reducing the base radius");
  151. break;
  152. }
  153. TangentType tt;
  154. if (i == 0) {
  155. tt = CalculateTangentTypeSimple(vs[i], vs[i+1], vs[i+2]);
  156. } else if (i == vs.Count-2) {
  157. tt = CalculateTangentTypeSimple(vs[i-1], vs[i], vs[i+1]);
  158. } else {
  159. tt = CalculateTangentType(vs[i-1], vs[i], vs[i+1], vs[i+2]);
  160. }
  161. //DrawCircle (vs[i], radi[i], Color.yellow);
  162. if ((tt & TangentType.Inner) != 0) {
  163. //Angle to tangent
  164. float a;
  165. //Angle to other circle
  166. float sigma;
  167. //Calculate angles to the next circle and angles for the inner tangents
  168. if (!CalculateCircleInner(vs[i], vs[i+1], radi[i], radi[i+1], out a, out sigma)) {
  169. //Failed, try modifying radiuses
  170. float magn = (vs[i+1]-vs[i]).magnitude;
  171. radi[i] = magn*(radi[i]/(radi[i]+radi[i+1]));
  172. radi[i+1] = magn - radi[i];
  173. radi[i] *= 0.99f;
  174. radi[i+1] *= 0.99f;
  175. i -= 2;
  176. continue;
  177. }
  178. if (tt == TangentType.InnerRightLeft) {
  179. a2[i] = sigma-a;
  180. a1[i+1] = sigma-a + (float)Math.PI;
  181. dir[i] = true;
  182. } else {
  183. a2[i] = sigma+a;
  184. a1[i+1] = sigma+a + (float)Math.PI;
  185. dir[i] = false;
  186. }
  187. } else {
  188. float sigma;
  189. float a;
  190. //Calculate angles to the next circle and angles for the outer tangents
  191. if (!CalculateCircleOuter(vs[i], vs[i+1], radi[i], radi[i+1], out a, out sigma)) {
  192. //Failed, try modifying radiuses
  193. if (i == vs.Count-2) {
  194. //The last circle has a fixed radius at 0, don't modify it
  195. radi[i] = (vs[i+1]-vs[i]).magnitude;
  196. radi[i] *= 0.99f;
  197. i -= 1;
  198. } else {
  199. if (radi[i] > radi[i+1]) {
  200. radi[i+1] = radi[i] - (vs[i+1]-vs[i]).magnitude;
  201. } else {
  202. radi[i+1] = radi[i] + (vs[i+1]-vs[i]).magnitude;
  203. }
  204. radi[i+1] *= 0.99f;
  205. }
  206. i -= 1;
  207. continue;
  208. }
  209. if (tt == TangentType.OuterRight) {
  210. a2[i] = sigma-a;
  211. a1[i+1] = sigma-a;
  212. dir[i] = true;
  213. } else {
  214. a2[i] = sigma+a;
  215. a1[i+1] = sigma+a;
  216. dir[i] = false;
  217. }
  218. }
  219. }
  220. List<Vector3> res = Pathfinding.Util.ListPool<Vector3>.Claim();
  221. res.Add(vs[0]);
  222. if (detail < 1) detail = 1;
  223. float step = (float)(2*Math.PI)/detail;
  224. for (int i = 1; i < vs.Count-1; i++) {
  225. float start = a1[i];
  226. float end = a2[i];
  227. float rad = radi[i];
  228. if (dir[i]) {
  229. if (end < start) end += (float)Math.PI*2;
  230. for (float t = start; t < end; t += step) {
  231. res.Add(new Vector3((float)Math.Cos(t), 0, (float)Math.Sin(t))*rad + vs[i]);
  232. }
  233. } else {
  234. if (start < end) start += (float)Math.PI*2;
  235. for (float t = start; t > end; t -= step) {
  236. res.Add(new Vector3((float)Math.Cos(t), 0, (float)Math.Sin(t))*rad + vs[i]);
  237. }
  238. }
  239. }
  240. res.Add(vs[vs.Count-1]);
  241. return res;
  242. }
  243. }
  244. }