AdvancedSmooth.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace Pathfinding {
  5. [AddComponentMenu("Pathfinding/Modifiers/Advanced Smooth")]
  6. [System.Serializable]
  7. /// <summary>Smoothing by dividing path into turns and straight segments.</summary>
  8. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_advanced_smooth.php")]
  9. public class AdvancedSmooth : MonoModifier {
  10. public override int Order { get { return 40; } }
  11. public float turningRadius = 1.0F;
  12. public MaxTurn turnConstruct1 = new MaxTurn();
  13. public ConstantTurn turnConstruct2 = new ConstantTurn();
  14. public override void Apply (Path p) {
  15. Vector3[] vectorPath = p.vectorPath.ToArray();
  16. if (vectorPath == null || vectorPath.Length <= 2) {
  17. return;
  18. }
  19. List<Vector3> newPath = new List<Vector3>();
  20. newPath.Add(vectorPath[0]);
  21. TurnConstructor.turningRadius = turningRadius;
  22. for (int i = 1; i < vectorPath.Length-1; i++) {
  23. List<Turn> turnList = new List<Turn>();
  24. TurnConstructor.Setup(i, vectorPath);
  25. turnConstruct1.Prepare(i, vectorPath);
  26. turnConstruct2.Prepare(i, vectorPath);
  27. TurnConstructor.PostPrepare();
  28. if (i == 1) {
  29. turnConstruct1.PointToTangent(turnList);
  30. turnConstruct2.PointToTangent(turnList);
  31. } else {
  32. turnConstruct1.TangentToTangent(turnList);
  33. turnConstruct2.TangentToTangent(turnList);
  34. }
  35. EvaluatePaths(turnList, newPath);
  36. //Last point
  37. if (i == vectorPath.Length-2) {
  38. turnConstruct1.TangentToPoint(turnList);
  39. turnConstruct2.TangentToPoint(turnList);
  40. }
  41. EvaluatePaths(turnList, newPath);
  42. }
  43. newPath.Add(vectorPath[vectorPath.Length-1]);
  44. p.vectorPath = newPath;
  45. }
  46. void EvaluatePaths (List<Turn> turnList, List<Vector3> output) {
  47. turnList.Sort();
  48. for (int j = 0; j < turnList.Count; j++) {
  49. if (j == 0) {
  50. turnList[j].GetPath(output);
  51. }
  52. }
  53. turnList.Clear();
  54. if (TurnConstructor.changedPreviousTangent) {
  55. turnConstruct1.OnTangentUpdate();
  56. turnConstruct2.OnTangentUpdate();
  57. }
  58. }
  59. [System.Serializable]
  60. /// <summary>Type of turn.</summary>
  61. public class MaxTurn : TurnConstructor {
  62. Vector3 preRightCircleCenter = Vector3.zero;
  63. Vector3 preLeftCircleCenter = Vector3.zero;
  64. Vector3 rightCircleCenter;
  65. Vector3 leftCircleCenter;
  66. double vaRight, vaLeft, preVaLeft, preVaRight;
  67. double gammaLeft, gammaRight;
  68. double betaRightRight, betaRightLeft, betaLeftRight, betaLeftLeft;
  69. double deltaRightLeft, deltaLeftRight;
  70. double alfaRightRight, alfaLeftLeft, alfaRightLeft, alfaLeftRight;
  71. public override void OnTangentUpdate () {
  72. rightCircleCenter = current + normal * turningRadius;
  73. leftCircleCenter = current - normal * turningRadius;
  74. vaRight = Atan2(current-rightCircleCenter);
  75. vaLeft = vaRight + Math.PI;
  76. }
  77. public override void Prepare (int i, Vector3[] vectorPath) {
  78. preRightCircleCenter = rightCircleCenter;
  79. preLeftCircleCenter = leftCircleCenter;
  80. rightCircleCenter = current + normal * turningRadius;
  81. leftCircleCenter = current - normal * turningRadius;
  82. preVaRight = vaRight;
  83. preVaLeft = vaLeft;
  84. vaRight = Atan2(current-rightCircleCenter);
  85. vaLeft = vaRight + Math.PI;
  86. }
  87. public override void TangentToTangent (List<Turn> turnList) {
  88. alfaRightRight = Atan2(rightCircleCenter - preRightCircleCenter); // + Math.PI*0.5; //Angle tangent to the angle the previous circle (from the current circle)
  89. alfaLeftLeft = Atan2(leftCircleCenter - preLeftCircleCenter); // + Math.PI*0.5;
  90. alfaRightLeft = Atan2(leftCircleCenter - preRightCircleCenter); // + Math.PI*0.5; //RightLeft means: from the previous right circle to the current left circle
  91. alfaLeftRight = Atan2(rightCircleCenter - preLeftCircleCenter); // + Math.PI*0.5; //LeftRight means: from the previous left circle to the current right circle
  92. double magnRightLeft = (leftCircleCenter - preRightCircleCenter).magnitude;
  93. double magnLeftRight = (rightCircleCenter - preLeftCircleCenter).magnitude;
  94. bool noRightLeft = false;
  95. bool noLeftRight = false;
  96. //Discard RightLeft and LeftRight paths if the circles lie to close to each other
  97. if (magnRightLeft < turningRadius*2) {
  98. magnRightLeft = turningRadius*2;
  99. noRightLeft = true;
  100. }
  101. if (magnLeftRight < turningRadius*2) {
  102. magnLeftRight = turningRadius*2;
  103. noLeftRight = true;
  104. }
  105. deltaRightLeft = noRightLeft ? 0 : (ThreeSixtyRadians * 0.25) - Math.Asin(turningRadius*2 / magnRightLeft); //turn*2 should be r1 + r2 for circles with different radiis
  106. deltaLeftRight = noLeftRight ? 0 : (ThreeSixtyRadians * 0.25) - Math.Asin(turningRadius*2 / magnLeftRight); //turn*2 should be r1 + r2 for circles with different radiis
  107. //Length for the first turn
  108. betaRightRight = ClockwiseAngle(preVaRight, alfaRightRight - ThreeSixtyRadians*0.25); // ThreeSixtyRadians * 0.25 = 90 degrees
  109. betaRightLeft = ClockwiseAngle(preVaRight, alfaRightLeft - deltaRightLeft);
  110. betaLeftRight = CounterClockwiseAngle(preVaLeft, alfaLeftRight + deltaLeftRight);
  111. betaLeftLeft = CounterClockwiseAngle(preVaLeft, alfaLeftLeft + ThreeSixtyRadians*0.25);
  112. //Add length for the second turn
  113. betaRightRight += ClockwiseAngle(alfaRightRight - ThreeSixtyRadians*0.25, vaRight);
  114. betaRightLeft += CounterClockwiseAngle(alfaRightLeft + deltaRightLeft, vaLeft);
  115. betaLeftRight += ClockwiseAngle(alfaLeftRight - deltaLeftRight, vaRight);
  116. betaLeftLeft += CounterClockwiseAngle(alfaLeftLeft + ThreeSixtyRadians*0.25, vaLeft);
  117. betaRightRight = GetLengthFromAngle(betaRightRight, turningRadius);
  118. betaRightLeft = GetLengthFromAngle(betaRightLeft, turningRadius);
  119. betaLeftRight = GetLengthFromAngle(betaLeftRight, turningRadius);
  120. betaLeftLeft = GetLengthFromAngle(betaLeftLeft, turningRadius);
  121. Vector3
  122. pRightRight1, pRightRight2,
  123. pRightLeft1, pRightLeft2,
  124. pLeftRight1, pLeftRight2,
  125. pLeftLeft1, pLeftLeft2;
  126. //Debug.Log ("=== DELTA VALUES===\nRightLeft "+ToDegrees (deltaRightLeft)+" - LeftRight "+ToDegrees (deltaLeftRight));
  127. //Set up points where the straigh segments starts and ends (between the turns)
  128. pRightRight1 = AngleToVector(alfaRightRight - ThreeSixtyRadians*0.25)*turningRadius + preRightCircleCenter;
  129. pRightLeft1 = AngleToVector(alfaRightLeft - deltaRightLeft)*turningRadius + preRightCircleCenter;
  130. pLeftRight1 = AngleToVector(alfaLeftRight + deltaLeftRight)*turningRadius + preLeftCircleCenter;
  131. pLeftLeft1 = AngleToVector(alfaLeftLeft + ThreeSixtyRadians*0.25)*turningRadius + preLeftCircleCenter;
  132. pRightRight2 = AngleToVector(alfaRightRight - ThreeSixtyRadians*0.25)*turningRadius + rightCircleCenter;
  133. pRightLeft2 = AngleToVector(alfaRightLeft - deltaRightLeft + Math.PI)*turningRadius + leftCircleCenter;
  134. pLeftRight2 = AngleToVector(alfaLeftRight + deltaLeftRight + Math.PI)*turningRadius + rightCircleCenter;
  135. pLeftLeft2 = AngleToVector(alfaLeftLeft + ThreeSixtyRadians*0.25)*turningRadius + leftCircleCenter;
  136. betaRightRight += (pRightRight1 - pRightRight2).magnitude;
  137. betaRightLeft += (pRightLeft1 - pRightLeft2).magnitude;
  138. betaLeftRight += (pLeftRight1 - pLeftRight2).magnitude;
  139. betaLeftLeft += (pLeftLeft1 - pLeftLeft2).magnitude;
  140. if (noRightLeft) {
  141. betaRightLeft += 10000000;
  142. }
  143. if (noLeftRight) {
  144. betaLeftRight += 10000000;
  145. }
  146. turnList.Add(new Turn((float)betaRightRight, this, 2));
  147. turnList.Add(new Turn((float)betaRightLeft, this, 3));
  148. turnList.Add(new Turn((float)betaLeftRight, this, 4));
  149. turnList.Add(new Turn((float)betaLeftLeft, this, 5));
  150. }
  151. public override void PointToTangent (List<Turn> turnList) {
  152. bool noRight = false, noLeft = false;
  153. float rightMagn = (prev-rightCircleCenter).magnitude;
  154. float leftMagn = (prev-leftCircleCenter).magnitude;
  155. if (rightMagn < turningRadius)
  156. noRight = true;
  157. if (leftMagn < turningRadius)
  158. noLeft = true;
  159. double alfa = noRight ? 0 : Atan2(prev-rightCircleCenter);
  160. double delta = noRight ? 0 : (ThreeSixtyRadians * 0.25) - Math.Asin(turningRadius / (prev-rightCircleCenter).magnitude);
  161. //Angle to the point where turning ends on the right circle
  162. gammaRight = alfa + delta;
  163. double betaRight = noRight ? 0 : ClockwiseAngle(gammaRight, vaRight);
  164. double alfaLeft = noLeft ? 0 : Atan2(prev-leftCircleCenter);
  165. double deltaLeft = noLeft ? 0 : (ThreeSixtyRadians * 0.25) - Math.Asin(turningRadius / (prev-leftCircleCenter).magnitude);
  166. //Angle to the point where turning ends
  167. gammaLeft = alfaLeft - deltaLeft;
  168. double betaLeft = noLeft ? 0 : CounterClockwiseAngle(gammaLeft, vaLeft);
  169. if (!noRight)
  170. turnList.Add(new Turn((float)betaRight, this, 0));
  171. if (!noLeft)
  172. turnList.Add(new Turn((float)betaLeft, this, 1));
  173. }
  174. public override void TangentToPoint (List<Turn> turnList) {
  175. bool noRight = false, noLeft = false;
  176. float rightMagn = (next-rightCircleCenter).magnitude;
  177. float leftMagn = (next-leftCircleCenter).magnitude;
  178. if (rightMagn < turningRadius)
  179. noRight = true;
  180. if (leftMagn < turningRadius)
  181. noLeft = true;
  182. if (!noRight) {
  183. double alfa = Atan2(next-rightCircleCenter);
  184. double delta = (ThreeSixtyRadians * 0.25) - Math.Asin(turningRadius / rightMagn);
  185. //Angle to the point where turning ends on the right circle
  186. gammaRight = alfa - delta;
  187. double betaRight = ClockwiseAngle(vaRight, gammaRight);
  188. turnList.Add(new Turn((float)betaRight, this, 6));
  189. }
  190. if (!noLeft) {
  191. double alfaLeft = Atan2(next-leftCircleCenter);
  192. double deltaLeft = (ThreeSixtyRadians * 0.25) - Math.Asin(turningRadius / leftMagn);
  193. //Angle to the point where turning ends
  194. gammaLeft = alfaLeft + deltaLeft;
  195. double betaLeft = CounterClockwiseAngle(vaLeft, gammaLeft);
  196. turnList.Add(new Turn((float)betaLeft, this, 7));
  197. }
  198. }
  199. public override void GetPath (Turn turn, List<Vector3> output) {
  200. switch (turn.id) {
  201. case 0:
  202. //Right - Point to tangent
  203. AddCircleSegment(gammaRight, vaRight, true, rightCircleCenter, output, turningRadius);
  204. break;
  205. case 1:
  206. //Left - Point to tangent
  207. AddCircleSegment(gammaLeft, vaLeft, false, leftCircleCenter, output, turningRadius);
  208. break;
  209. case 2:
  210. //Right Right - Tangent to tangent
  211. AddCircleSegment(preVaRight, alfaRightRight - ThreeSixtyRadians*0.25, true, preRightCircleCenter, output, turningRadius);
  212. AddCircleSegment(alfaRightRight - ThreeSixtyRadians*0.25, vaRight, true, rightCircleCenter, output, turningRadius);
  213. break;
  214. case 3:
  215. //Right Left - Tangent to tangent
  216. AddCircleSegment(preVaRight, alfaRightLeft - deltaRightLeft, true, preRightCircleCenter, output, turningRadius);
  217. AddCircleSegment(alfaRightLeft - deltaRightLeft + Math.PI, vaLeft, false, leftCircleCenter, output, turningRadius);
  218. break;
  219. case 4:
  220. //Left Right - Tangent to tangent
  221. AddCircleSegment(preVaLeft, alfaLeftRight + deltaLeftRight, false, preLeftCircleCenter, output, turningRadius);
  222. AddCircleSegment(alfaLeftRight + deltaLeftRight + Math.PI, vaRight, true, rightCircleCenter, output, turningRadius);
  223. break;
  224. case 5:
  225. //Left Left - Tangent to tangent
  226. AddCircleSegment(preVaLeft, alfaLeftLeft + ThreeSixtyRadians*0.25, false, preLeftCircleCenter, output, turningRadius);
  227. AddCircleSegment(alfaLeftLeft + ThreeSixtyRadians*0.25, vaLeft, false, leftCircleCenter, output, turningRadius);
  228. break;
  229. case 6:
  230. //Right - Tangent to point
  231. AddCircleSegment(vaRight, gammaRight, true, rightCircleCenter, output, turningRadius);
  232. break;
  233. case 7:
  234. //Left - Tangent to point
  235. AddCircleSegment(vaLeft, gammaLeft, false, leftCircleCenter, output, turningRadius);
  236. break;
  237. }
  238. }
  239. }
  240. [System.Serializable]
  241. /// <summary>Constant turning speed.</summary>
  242. public class ConstantTurn : TurnConstructor {
  243. Vector3 circleCenter;
  244. double gamma1;
  245. double gamma2;
  246. bool clockwise;
  247. public override void Prepare (int i, Vector3[] vectorPath) {}
  248. public override void TangentToTangent (List<Turn> turnList) {
  249. Vector3 preNormal = Vector3.Cross(t1, Vector3.up);
  250. Vector3 dir = (current-prev);
  251. Vector3 pos = dir*0.5F + prev;
  252. dir = Vector3.Cross(dir, Vector3.up);
  253. bool didIntersect;
  254. circleCenter = VectorMath.LineDirIntersectionPointXZ(prev, preNormal, pos, dir, out didIntersect);
  255. if (!didIntersect) {
  256. return;
  257. }
  258. gamma1 = Atan2(prev-circleCenter);
  259. gamma2 = Atan2(current-circleCenter);
  260. clockwise = !VectorMath.RightOrColinearXZ(circleCenter, prev, prev+t1);
  261. double beta = clockwise ? ClockwiseAngle(gamma1, gamma2) : CounterClockwiseAngle(gamma1, gamma2);
  262. beta = GetLengthFromAngle(beta, (circleCenter - current).magnitude);
  263. turnList.Add(new Turn((float)beta, this, 0));
  264. }
  265. public override void GetPath (Turn turn, List<Vector3> output) {
  266. AddCircleSegment(gamma1, gamma2, clockwise, circleCenter, output, (circleCenter - current).magnitude);
  267. normal = (current - circleCenter).normalized;
  268. t2 = Vector3.Cross(normal, Vector3.up).normalized;
  269. normal = -normal;
  270. if (!clockwise) {
  271. t2 = -t2;
  272. normal = -normal;
  273. }
  274. changedPreviousTangent = true;
  275. }
  276. }
  277. /// <summary>Abstract turn constructor.</summary>
  278. public abstract class TurnConstructor {
  279. /// <summary>
  280. /// Constant bias to add to the path lengths.
  281. /// This can be used to favor certain turn types before others.
  282. /// By for example setting this to -5, paths from this path constructor will be chosen
  283. /// if there are no other paths more than 5 world units shorter than this one (as opposed to just any shorter path)
  284. /// </summary>
  285. public float constantBias = 0;
  286. /// <summary>
  287. /// Bias to multiply the path lengths with. This can be used to favor certain turn types before others.
  288. /// See: <see cref="constantBias"/>
  289. /// </summary>
  290. public float factorBias = 1;
  291. public static float turningRadius = 1.0F;
  292. public const double ThreeSixtyRadians = Math.PI * 2;
  293. public static Vector3 prev, current, next; //The current points
  294. public static Vector3 t1, t2; //The current tangents - t2 is at 'current', t1 is at 'prev'
  295. public static Vector3 normal, prevNormal; //Normal at 'current'
  296. public static bool changedPreviousTangent = false;
  297. public abstract void Prepare(int i, Vector3[] vectorPath);
  298. public virtual void OnTangentUpdate () {}
  299. public virtual void PointToTangent (List<Turn> turnList) {}
  300. public virtual void TangentToPoint (List<Turn> turnList) {}
  301. public virtual void TangentToTangent (List<Turn> turnList) {}
  302. public abstract void GetPath(Turn turn, List<Vector3> output);
  303. //abstract void Evaluate (Turn turn);
  304. public static void Setup (int i, Vector3[] vectorPath) {
  305. current = vectorPath[i];
  306. prev = vectorPath[i-1];
  307. next = vectorPath[i+1];
  308. prev.y = current.y;
  309. next.y = current.y;
  310. t1 = t2;
  311. t2 = (next-current).normalized - (prev-current).normalized;
  312. t2 = t2.normalized;
  313. prevNormal = normal;
  314. normal = Vector3.Cross(t2, Vector3.up);
  315. normal = normal.normalized;
  316. }
  317. public static void PostPrepare () {
  318. changedPreviousTangent = false;
  319. }
  320. //Utilities
  321. public void AddCircleSegment (double startAngle, double endAngle, bool clockwise, Vector3 center, List<Vector3> output, float radius) {
  322. double step = ThreeSixtyRadians / 100;
  323. if (clockwise) {
  324. while (endAngle > startAngle+ThreeSixtyRadians) {
  325. endAngle -= ThreeSixtyRadians;
  326. }
  327. while (endAngle < startAngle) {
  328. endAngle += ThreeSixtyRadians;
  329. }
  330. } else {
  331. while (endAngle < startAngle-ThreeSixtyRadians) {
  332. endAngle += ThreeSixtyRadians;
  333. }
  334. while (endAngle > startAngle) {
  335. endAngle -= ThreeSixtyRadians;
  336. }
  337. }
  338. //Add curve
  339. if (clockwise) {
  340. for (double i = startAngle; i < endAngle; i += step) {
  341. output.Add(AngleToVector(i)*radius+center);
  342. }
  343. } else {
  344. for (double i = startAngle; i > endAngle; i -= step) {
  345. output.Add(AngleToVector(i)*radius+center);
  346. }
  347. }
  348. //Add last point
  349. output.Add(AngleToVector(endAngle)*radius+center);
  350. }
  351. public void DebugCircleSegment (Vector3 center, double startAngle, double endAngle, double radius, Color color) {
  352. double step = ThreeSixtyRadians / 100;
  353. while (endAngle < startAngle) {
  354. endAngle += ThreeSixtyRadians;
  355. }
  356. Vector3 prev = AngleToVector(startAngle)*(float)radius+center;
  357. for (double i = startAngle+step; i < endAngle; i += step) {
  358. Debug.DrawLine(prev, AngleToVector(i)*(float)radius+center);
  359. }
  360. Debug.DrawLine(prev, AngleToVector(endAngle)*(float)radius+center);
  361. }
  362. public void DebugCircle (Vector3 center, double radius, Color color) {
  363. double step = ThreeSixtyRadians / 100;
  364. Vector3 prePos = AngleToVector(-step)*(float)radius+center;
  365. for (double i = 0; i < ThreeSixtyRadians; i += step) {
  366. Vector3 pos = AngleToVector(i)*(float)radius+center;
  367. Debug.DrawLine(prePos, pos, color);
  368. prePos = pos;
  369. }
  370. }
  371. /// <summary>Returns the length of an circular arc with a radius and angle. Angle is specified in radians</summary>
  372. public double GetLengthFromAngle (double angle, double radius) {
  373. return radius * angle;
  374. }
  375. /// <summary>Returns the angle between from and to in a clockwise direction</summary>
  376. public double ClockwiseAngle (double from, double to) {
  377. return ClampAngle(to - from);
  378. }
  379. /// <summary>Returns the angle between from and to in a counter-clockwise direction</summary>
  380. public double CounterClockwiseAngle (double from, double to) {
  381. return ClampAngle(from - to);
  382. }
  383. public Vector3 AngleToVector (double a) {
  384. return new Vector3((float)Math.Cos(a), 0, (float)Math.Sin(a));
  385. }
  386. public double ToDegrees (double rad) {
  387. return rad * Mathf.Rad2Deg;
  388. }
  389. public double ClampAngle (double a) {
  390. while (a < 0) { a += ThreeSixtyRadians; }
  391. while (a > ThreeSixtyRadians) { a -= ThreeSixtyRadians; }
  392. return a;
  393. }
  394. public double Atan2 (Vector3 v) {
  395. return Math.Atan2(v.z, v.x);
  396. }
  397. }
  398. //Turn class
  399. /// <summary>Represents a turn in a path.</summary>
  400. public struct Turn : IComparable<Turn> {
  401. public float length;
  402. public int id;
  403. public TurnConstructor constructor;
  404. public float score {
  405. get {
  406. return length*constructor.factorBias+constructor.constantBias;
  407. }
  408. }
  409. public Turn (float length, TurnConstructor constructor, int id = 0) {
  410. this.length = length;
  411. this.id = id;
  412. this.constructor = constructor;
  413. }
  414. public void GetPath (List<Vector3> output) {
  415. constructor.GetPath(this, output);
  416. }
  417. public int CompareTo (Turn t) {
  418. return t.score > score ? -1 : (t.score < score ? 1 : 0);
  419. }
  420. public static bool operator < (Turn lhs, Turn rhs) {
  421. return lhs.score < rhs.score;
  422. }
  423. public static bool operator > (Turn lhs, Turn rhs) {
  424. return lhs.score > rhs.score;
  425. }
  426. }
  427. }
  428. }