PathGenerator.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. //
  2. // (c) 2016 Digital Ruby, LLC
  3. // http://www.digitalruby.com
  4. // Code may not be redistributed in source form!
  5. // Using this code in commercial games and apps is fine.
  6. //
  7. // if not using Unity, comment this out
  8. #define UNITY
  9. // if square root performance or accuracy is a problem, you may try one of the other defines here, and leave the other two commented out
  10. #define USE_MATH_SQUARE_ROOT // most accurate
  11. // #define USE_QUAKE_SQUARE_ROOT // pretty accuracte and faster than Math.Sqrt, but not perfect
  12. // #define USE_FASTER_SQUARE_ROOT // pretty accurate and fastest, although less accurate than quake
  13. // references for curves, splines and math that were invaluable in creating this code:
  14. // http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections
  15. // http://www.technologicalutopia.com/sourcecode/xnageometry/tableofcontents.htm
  16. // http://blog.avangardo.com/2010/10/c-implementation-of-bezier-curvature-calculation/
  17. // https://github.com/mono/sysdrawing-coregraphics/blob/master/System.Drawing.Drawing2D/GraphicsPath.cs
  18. // https://github.com/retuxx/tinyspline
  19. // http://www.codeproject.com/Articles/996281/NURBS-curve-made-easy
  20. // http://catlikecoding.com/unity/tutorials/curves-and-splines/
  21. // https://en.wikipedia.org/wiki/Fast_inverse_square_root
  22. // http://blog.wouldbetheologian.com/2011/11/fast-approximate-sqrt-method-in-c.html
  23. using System;
  24. using System.Collections.Generic;
  25. using System.Linq;
  26. using System.Runtime.InteropServices;
  27. using System.Text;
  28. namespace DigitalRuby.ThunderAndLightning
  29. {
  30. #if UNITY
  31. using UnityEngine;
  32. #endif
  33. /// <summary>
  34. /// Utility class to assist in generating curves, splines, etc.
  35. /// </summary>
  36. public static class PathGenerator
  37. {
  38. #if !UNITY
  39. public struct Vector3
  40. {
  41. public float x;
  42. public float y;
  43. public float z;
  44. public Vector3(float x, float y, float z)
  45. {
  46. this.x = x;
  47. this.y = y;
  48. this.z = z;
  49. }
  50. }
  51. public struct Vector4
  52. {
  53. public float x;
  54. public float y;
  55. public float z;
  56. public float w;
  57. public Vector4(float x, float y, float z, float w)
  58. {
  59. this.x = x;
  60. this.y = y;
  61. this.z = z;
  62. this.w = w;
  63. }
  64. }
  65. #endif
  66. #if !USE_MATH_SQUARE_ROOT
  67. [StructLayout(LayoutKind.Explicit)]
  68. private struct FloatIntUnion
  69. {
  70. [FieldOffset(0)]
  71. public float f;
  72. [FieldOffset(0)]
  73. public int tmp;
  74. }
  75. #endif
  76. /// <summary>
  77. /// Minimum number of points to create a spline
  78. /// </summary>
  79. public const int MinPointsForSpline = 4;
  80. /// <summary>
  81. /// Whether paths are working in 2D or 3D space
  82. /// </summary>
  83. public static bool Is2D;
  84. /// <summary>
  85. /// Adjust how curves are calculated. This value should be positive. Generally leave as is unless you want some gnarly effects.
  86. /// </summary>
  87. private const float curveMultiplier = 3.0f;
  88. /// <summary>
  89. /// Adjusts how the spline curves. This value should be negative. Generally leave as is unless you want some gnarly effects.
  90. /// </summary>
  91. private const float splineMultiplier1 = -3.0f;
  92. /// <summary>
  93. /// Adjusts how the spline curves. This value should be positive. Generally leave as is unless you want some gnarly effects.
  94. /// </summary>
  95. private const float splineMultiplier2 = 3.0f;
  96. /// <summary>
  97. /// Adjusts how the spline curves. This value should be positive. Generally leave as is unless you want some gnarly effects.
  98. /// </summary>
  99. private const float splineMultiplier3 = 2.0f;
  100. /// <summary>
  101. /// In the event that the second distance of a spline is too small, force it to be this value
  102. /// </summary>
  103. private const float splineDistanceClamp = 1.0f;
  104. /// <summary>
  105. /// System.Math doesn't provide epsilon
  106. /// </summary>
  107. private const float splineEpsilon = 0.0001f;
  108. /// <summary>
  109. /// Optimized square root
  110. /// </summary>
  111. /// <param name="x">Value</param>
  112. /// <returns>Square root of value</returns>
  113. public static float SquareRoot(float x)
  114. {
  115. #if USE_MATH_SQUARE_ROOT
  116. return (float)Math.Sqrt(x);
  117. #elif USE_QUAKE_SQUARE_ROOT
  118. // the famous Quake square root hack
  119. FloatIntUnion u;
  120. u.tmp = 0;
  121. float xhalf = 0.5f * x;
  122. u.f = x;
  123. u.tmp = 0x5f375a86 - (u.tmp >> 1);
  124. u.f = u.f * (1.5f - xhalf * u.f * u.f);
  125. return u.f * x;
  126. #else
  127. // a slightly faster but less accurate method
  128. FloatIntUnion u;
  129. u.tmp = 0;
  130. u.f = x;
  131. u.tmp -= 1 << 23; /* Subtract 2^m. */
  132. u.tmp >>= 1; /* Divide by 2. */
  133. u.tmp += 1 << 29; /* Add ((b + 1) / 2) * 2^m. */
  134. return u.f;
  135. #endif
  136. }
  137. private static float Distance2D(ref Vector3 point1, ref Vector3 point2)
  138. {
  139. float deltaX = point2.x - point1.x;
  140. float deltaY = point2.y - point1.y;
  141. return SquareRoot((deltaX * deltaX) + (deltaY * deltaY));
  142. }
  143. private static float Distance3D(ref Vector3 point1, ref Vector3 point2)
  144. {
  145. float deltaX = point2.x - point1.x;
  146. float deltaY = point2.y - point1.y;
  147. float deltaZ = point2.z - point1.z;
  148. return SquareRoot((deltaX * deltaX) + (deltaY * deltaY) + (deltaZ * deltaZ));
  149. }
  150. private static void GetCurvePoint2D(ref Vector3 start, ref Vector3 end, ref Vector3 ctr1, ref Vector3 ctr2, float t, out Vector3 point)
  151. {
  152. float tSquared = t * t;
  153. float tCubed = tSquared * t;
  154. float p1x = curveMultiplier * (ctr1.x - start.x);
  155. float p1y = curveMultiplier * (ctr1.y - start.y);
  156. float p2x = curveMultiplier * (ctr2.x - ctr1.x) - p1x;
  157. float p2y = curveMultiplier * (ctr2.y - ctr1.y) - p1y;
  158. float p3x = end.x - start.x - p1x - p2x;
  159. float p3y = end.y - start.y - p1y - p2y;
  160. float finalX = start.x + (p1x * t) + (p2x * tSquared) + (p3x * tCubed);
  161. float finalY = start.y + (p1y * t) + (p2y * tSquared) + (p3y * tCubed);
  162. point = new Vector3(finalX, finalY, 0.0f);
  163. }
  164. private static void GetCurvePoint3D(ref Vector3 start, ref Vector3 end, ref Vector3 ctr1, ref Vector3 ctr2, float t, out Vector3 point)
  165. {
  166. float tSquared = t * t;
  167. float tCubed = tSquared * t;
  168. float p1x = (ctr1.x - start.x) * curveMultiplier;
  169. float p1y = (ctr1.y - start.y) * curveMultiplier;
  170. float p1z = (ctr1.z - start.z) * curveMultiplier;
  171. float p2x = ((ctr2.x - ctr1.x) * curveMultiplier) - p1x;
  172. float p2y = ((ctr2.y - ctr1.y) * curveMultiplier) - p1y;
  173. float p2z = ((ctr2.z - ctr1.z) * curveMultiplier) - p1z;
  174. float p3x = end.x - start.x - p1x - p2x;
  175. float p3y = end.y - start.y - p1y - p2y;
  176. float p3z = end.z - start.z - p1z - p2z;
  177. float finalX = start.x + (p1x * t) + (p2x * tSquared) + (p3x * tCubed);
  178. float finalY = start.y + (p1y * t) + (p2y * tSquared) + (p3y * tCubed);
  179. float finalZ = start.z + (p1z * t) + (p2z * tSquared) + (p3z * tCubed);
  180. point = new Vector3(finalX, finalY, finalZ);
  181. }
  182. /*
  183. private static Quaternion GetOrientation2D(Vector3[] pts, float t)
  184. {
  185. Vector3 tng = GetTangent(pts, t);
  186. Vector3 nrm = GetNormal2D(pts, t);
  187. return Quaternion.LookRotation(tng, nrm);
  188. }
  189. private static Quaternion GetOrientation3D(Vector3[] pts, float t, Vector3 up)
  190. {
  191. Vector3 tng = GetTangent(pts, t);
  192. Vector3 nrm = GetNormal3D(pts, t, up);
  193. return Quaternion.LookRotation(tng, nrm);
  194. }
  195. private static Vector3 GetNormal2D(Vector3[] pts, float t)
  196. {
  197. Vector3 tng = GetTangent(pts, t);
  198. return new Vector3(-tng.y, tng.x, 0f);
  199. }
  200. private static Vector3 GetNormal3D(Vector3[] pts, float t, Vector3 up)
  201. {
  202. Vector3 tng = GetTangent(pts, t);
  203. Vector3 binormal = Vector3.Cross(up, tng).normalized;
  204. return Vector3.Cross(tng, binormal);
  205. }
  206. private static Vector3 GetTangent(Vector3[] pts, float t)
  207. {
  208. float omt = 1f - t;
  209. float omt2 = omt * omt;
  210. float t2 = t * t;
  211. Vector3 tangent =
  212. pts[0] * (-omt2) +
  213. pts[1] * (3 * omt2 - 2 * omt) +
  214. pts[2] * (-3 * t2 + 2 * t) +
  215. pts[3] * (t2);
  216. return tangent.normalized;
  217. }
  218. private static void CalculateSplinePoint3D(ref Vector3 p0, ref Vector3 p1, ref Vector3 p2, ref Vector3 p3, float t, out Vector3 p)
  219. {
  220. float omt = 1f - t;
  221. float omt2 = omt * omt;
  222. float t2 = t * t;
  223. p = (p0 * omt2 * omt) +
  224. (p1 * 3.0f * omt2 * t) +
  225. (p2 * 3.0f * omt * t2) +
  226. (p3 * t2 * t);
  227. }
  228. */
  229. private static void CalculateNonuniformCatmullRom(float p1, float p2, float p3, float p4, float distance1, float distance2, float distance3, out Vector4 point)
  230. {
  231. // http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections
  232. // compute coefficients for a nonuniform Catmull-Rom spline
  233. float t1 = ((p2 - p1) / distance1 - (p3 - p1) / (distance1 + distance2) + (p3 - p2) / distance2) * distance2;
  234. float t2 = ((p3 - p2) / distance2 - (p4 - p2) / (distance2 + distance3) + (p4 - p3) / distance3) * distance2;
  235. point = new Vector4
  236. (
  237. p2,
  238. t1,
  239. (splineMultiplier1 * p2) + (splineMultiplier2 * p3) - (t2 + (splineMultiplier3 * t1)),
  240. (t1 + t2) + ((splineMultiplier3 * p2) - (splineMultiplier3 * p3))
  241. );
  242. }
  243. private static float CalculatePolynomial(ref Vector4 point, float t)
  244. {
  245. // polynomial is w*t^3+z*t^2+y*t+x
  246. float tSquared = t * t;
  247. float tCubed = tSquared * t;
  248. return (point.w * tCubed) + (point.z * tSquared) + (point.y * t) + point.x;
  249. }
  250. private static void ClampSplineDistances(ref float distance1, ref float distance2, ref float distance3)
  251. {
  252. // safety check for repeated points and 0 distance
  253. if (distance2 < splineEpsilon)
  254. {
  255. distance2 = splineDistanceClamp;
  256. }
  257. if (distance1 < splineEpsilon)
  258. {
  259. distance1 = distance2;
  260. }
  261. if (distance3 < splineEpsilon)
  262. {
  263. distance3 = distance2;
  264. }
  265. }
  266. private static void GetSplinePoint2D(ref Vector3 point1, ref Vector3 point2, ref Vector3 point3, ref Vector3 point4, float t, out Vector3 point)
  267. {
  268. float distance1 = Distance2D(ref point1, ref point2);
  269. float distance2 = Distance2D(ref point2, ref point3);
  270. float distance3 = Distance2D(ref point3, ref point4);
  271. ClampSplineDistances(ref distance1, ref distance2, ref distance3);
  272. // plot the spline on the x and y axis
  273. Vector4 polynomialX;
  274. CalculateNonuniformCatmullRom(point1.x, point2.x, point3.x, point4.x, distance1, distance2, distance3, out polynomialX);
  275. Vector4 polynomialY;
  276. CalculateNonuniformCatmullRom(point1.y, point2.y, point3.y, point4.y, distance1, distance2, distance3, out polynomialY);
  277. point = new Vector3(CalculatePolynomial(ref polynomialX, t), CalculatePolynomial(ref polynomialY, t), 0.0f);
  278. }
  279. private static void GetSplinePoint3D(ref Vector3 point1, ref Vector3 point2, ref Vector3 point3, ref Vector3 point4, float t, out Vector3 point)
  280. {
  281. float distance1 = Distance3D(ref point1, ref point2);
  282. float distance2 = Distance3D(ref point2, ref point3);
  283. float distance3 = Distance3D(ref point3, ref point4);
  284. ClampSplineDistances(ref distance1, ref distance2, ref distance3);
  285. // plot the spline on the x, y and z axis
  286. Vector4 polynomialX;
  287. CalculateNonuniformCatmullRom(point1.x, point2.x, point3.x, point4.x, distance1, distance2, distance3, out polynomialX);
  288. Vector4 polynomialY;
  289. CalculateNonuniformCatmullRom(point1.y, point2.y, point3.y, point4.y, distance1, distance2, distance3, out polynomialY);
  290. Vector4 polynomialZ;
  291. CalculateNonuniformCatmullRom(point1.z, point2.z, point3.z, point4.z, distance1, distance2, distance3, out polynomialZ);
  292. point = new Vector3(CalculatePolynomial(ref polynomialX, t), CalculatePolynomial(ref polynomialY, t), CalculatePolynomial(ref polynomialZ, t));
  293. }
  294. /// <summary>
  295. /// Create a quad/bezier curve that curves using two control points from start to end.
  296. /// </summary>
  297. /// <param name="path">Receives the path points. Collection is not cleared.</param>
  298. /// <param name="start">Start point</param>
  299. /// <param name="end">End point</param>
  300. /// <param name="ctr1">Control point 1</param>
  301. /// <param name="ctr2">Control point 2</param>
  302. /// <param name="numberOfSegments">Number of segments. The higher this number, the more points are sampled.</param>
  303. /// <param name="startT">Start t. This can be the return value of a previous call to maintain a smooth path.</param>
  304. /// <returns>Leftover t. This can be passed back in to startT to continue a path smoothly.</returns>
  305. public static float CreateCurve(ICollection<Vector3> path, Vector3 start, Vector3 end, Vector3 ctr1, Vector3 ctr2, int numberOfSegments, float startT)
  306. {
  307. // make sure number of segments isn't too large or small
  308. numberOfSegments = Math.Min(1024, Math.Max(numberOfSegments, MinPointsForSpline));
  309. float t;
  310. float tIncrement = 1.0f / (float)(numberOfSegments + 1);
  311. Vector3 point;
  312. if (Is2D)
  313. {
  314. for (t = startT; t <= 1.0f; t += tIncrement)
  315. {
  316. GetCurvePoint2D(ref start, ref end, ref ctr1, ref ctr2, t, out point);
  317. path.Add(point);
  318. }
  319. }
  320. else
  321. {
  322. for (t = startT; t <= 1.0f; t += tIncrement)
  323. {
  324. GetCurvePoint3D(ref start, ref end, ref ctr1, ref ctr2, t, out point);
  325. path.Add(point);
  326. }
  327. }
  328. return (t - 1.0f);
  329. }
  330. /// <summary>
  331. /// Creates a spline path that curves around points in a list. Each source segment will have the same number of spline points.
  332. /// </summary>
  333. /// <param name="path">Receives the path points. Collection is not cleared.</param>
  334. /// <param name="points">Points for the spline to follow. Must contain at least 4 points.</param>
  335. /// <param name="numberOfSegments">Total number of line segments for the spline. The higher this number, the more points are sampled.</param>
  336. /// <param name="closePath">True to loop back to the start, false otherwise</param>
  337. /// <returns>True if success, false if points length is too small</returns>
  338. public static bool CreateSpline(ICollection<Vector3> path, IList<Vector3> points, int numberOfSegments, bool closePath)
  339. {
  340. if (points.Count < MinPointsForSpline)
  341. {
  342. return false;
  343. }
  344. // make sure number of segments isn't too large or small
  345. numberOfSegments = Math.Min(1024, Math.Max(numberOfSegments, 4));
  346. int numberOfPoints = (closePath ? points.Count : points.Count - 1);
  347. int previousPointIndex, currentPointIndex, nextPointIndex, nextNextPointIndex;
  348. int closePathInt = (closePath ? 1 : 0);
  349. float t;
  350. float tIncrement = (1.0f / (float)numberOfSegments) * (float)numberOfPoints;
  351. float start = 0.0f;
  352. Vector3 point, previousPoint, currentPoint, nextPoint, nextNextPoint;
  353. for (currentPointIndex = 0; currentPointIndex < numberOfPoints; currentPointIndex++)
  354. {
  355. // we will be using 4 points - the previous point, current point, next point and point after next
  356. // the points can wrap around back to the start of the list
  357. previousPointIndex = (currentPointIndex == 0 ? (numberOfPoints - closePathInt) : (currentPointIndex - 1));
  358. nextPointIndex = currentPointIndex + 1;
  359. nextNextPointIndex = currentPointIndex + 2;
  360. if (closePath && nextPointIndex > numberOfPoints - 1)
  361. {
  362. nextPointIndex -= numberOfPoints;
  363. }
  364. if (nextNextPointIndex > numberOfPoints - 1)
  365. {
  366. nextNextPointIndex = (closePath ? nextNextPointIndex - numberOfPoints : numberOfPoints);
  367. }
  368. // assign the points - these will be passed by ref for performance
  369. previousPoint = points[previousPointIndex];
  370. currentPoint = points[currentPointIndex];
  371. nextPoint = points[nextPointIndex];
  372. nextNextPoint = points[nextNextPointIndex];
  373. if (Is2D)
  374. {
  375. // loop through and spline the points
  376. for (t = start; t <= 1.0f; t += tIncrement)
  377. {
  378. GetSplinePoint2D(ref previousPoint, ref currentPoint, ref nextPoint, ref nextNextPoint, t, out point);
  379. path.Add(point);
  380. }
  381. }
  382. else
  383. {
  384. // loop through and spline the points
  385. for (t = start; t <= 1.0f; t += tIncrement)
  386. {
  387. GetSplinePoint3D(ref previousPoint, ref currentPoint, ref nextPoint, ref nextNextPoint, t, out point);
  388. path.Add(point);
  389. }
  390. }
  391. // reset start, keeping any additional amount over 1.0f to maintain a smooth curve
  392. start = t - 1.0f;
  393. }
  394. return true;
  395. }
  396. /// <summary>
  397. /// Creates a spline path that curves around points in a list. This tries to maintain a similar distance between spline segments, regardless of distance between the initial points.
  398. /// </summary>
  399. /// <param name="path">Receives the path points. Collection is not cleared.</param>
  400. /// <param name="points">Points for the spline to follow. Must contain at least 4 points.</param>
  401. /// <param name="distancePerSegment">The distance that each segment should be in the spline. This is approximate.</param>
  402. /// <param name="closePath">True to loop back to the start, false otherwise</param>
  403. /// <returns>True if success, false if points length is too small</returns>
  404. public static bool CreateSplineWithSegmentDistance(ICollection<Vector3> path, IList<Vector3> points, float distancePerSegment, bool closePath)
  405. {
  406. if (points.Count < MinPointsForSpline || distancePerSegment <= 0.0f)
  407. {
  408. return false;
  409. }
  410. // make sure number of segments isn't too large or small
  411. int numberOfPoints = (closePath ? points.Count : points.Count - 1);
  412. int previousPointIndex, currentPointIndex, nextPointIndex, nextNextPointIndex;
  413. int closePathInt = (closePath ? 1 : 0);
  414. float t;
  415. float tIncrement;
  416. float start = 0.0f;
  417. Vector3 point, previousPoint, currentPoint, nextPoint, nextNextPoint;
  418. for (currentPointIndex = 0; currentPointIndex < numberOfPoints; currentPointIndex++)
  419. {
  420. // we will be using 4 points - the previous point, current point, next point and point after next
  421. // the points can wrap around back to the start of the list
  422. previousPointIndex = (currentPointIndex == 0 ? (numberOfPoints - closePathInt) : (currentPointIndex - 1));
  423. nextPointIndex = currentPointIndex + 1;
  424. nextNextPointIndex = currentPointIndex + 2;
  425. if (closePath && nextPointIndex > numberOfPoints - 1)
  426. {
  427. nextPointIndex -= numberOfPoints;
  428. }
  429. if (nextNextPointIndex > numberOfPoints - 1)
  430. {
  431. nextNextPointIndex = (closePath ? nextNextPointIndex - numberOfPoints : numberOfPoints);
  432. }
  433. // assign the points - these will be passed by ref for performance
  434. previousPoint = points[previousPointIndex];
  435. currentPoint = points[currentPointIndex];
  436. nextPoint = points[nextPointIndex];
  437. nextNextPoint = points[nextNextPointIndex];
  438. if (Is2D)
  439. {
  440. tIncrement = 1.0f / (Distance2D(ref nextPoint, ref currentPoint) / distancePerSegment);
  441. tIncrement = Mathf.Clamp(tIncrement, 0.00390625f, 1.0f);
  442. // loop through and spline the points
  443. for (t = start; t <= 1.0f; t += tIncrement)
  444. {
  445. GetSplinePoint2D(ref previousPoint, ref currentPoint, ref nextPoint, ref nextNextPoint, t, out point);
  446. path.Add(point);
  447. }
  448. }
  449. else
  450. {
  451. tIncrement = 1.0f / (Distance3D(ref nextPoint, ref currentPoint) / distancePerSegment);
  452. tIncrement = Mathf.Clamp(tIncrement, 0.00390625f, 1.0f);
  453. // loop through and spline the points
  454. for (t = start; t <= 1.0f; t += tIncrement)
  455. {
  456. GetSplinePoint3D(ref previousPoint, ref currentPoint, ref nextPoint, ref nextNextPoint, t, out point);
  457. path.Add(point);
  458. }
  459. }
  460. start = 0.0f;
  461. }
  462. return true;
  463. }
  464. }
  465. }