/Synergy88Project/Assets/ThirdParty/SplineTrailRenderer/Scripts/CatmullRomSpline.cs

https://bitbucket.org/agnavarroSyn88/assessment · C# · 421 lines · 309 code · 97 blank · 15 comment · 39 complexity · 911f8a76667ffb92847140219abeb34d MD5 · raw file

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. [System.Serializable]
  5. public class Knot
  6. {
  7. public float distanceFromStart = -1f;
  8. public CatmullRomSpline.SubKnot[] subKnots = new CatmullRomSpline.SubKnot[CatmullRomSpline.NbSubSegmentPerSegment+1]; //[0, 1]
  9. public Vector3 position;
  10. public Knot(Vector3 position)
  11. {
  12. this.position = position;
  13. }
  14. public void Invalidate()
  15. {
  16. distanceFromStart = -1f;
  17. }
  18. }
  19. public class CatmullRomSpline
  20. {
  21. public struct SubKnot
  22. {
  23. public float distanceFromStart;
  24. public Vector3 position;
  25. public Vector3 tangent;
  26. }
  27. public class Marker
  28. {
  29. public int segmentIndex;
  30. public int subKnotAIndex;
  31. public int subKnotBIndex;
  32. public float lerpRatio;
  33. }
  34. public List<Knot> knots = new List<Knot>();
  35. public const int NbSubSegmentPerSegment = 10;
  36. private const int MinimumKnotNb = 4;
  37. private const int FirstSegmentKnotIndex = 2;
  38. public int NbSegments { get { return System.Math.Max(0, knots.Count - 3); } }
  39. public Vector3 FindPositionFromDistance(float distance)
  40. {
  41. Vector3 tangent = Vector3.zero;
  42. Marker result = new Marker();
  43. bool foundSegment = PlaceMarker(result, distance);
  44. if(foundSegment)
  45. {
  46. tangent = GetPosition(result);
  47. }
  48. return tangent;
  49. }
  50. public Vector3 FindTangentFromDistance(float distance)
  51. {
  52. Vector3 tangent = Vector3.zero;
  53. Marker result = new Marker();
  54. bool foundSegment = PlaceMarker(result, distance);
  55. if(foundSegment)
  56. {
  57. tangent = GetTangent(result);
  58. }
  59. return tangent;
  60. }
  61. public static Vector3 ComputeBinormal(Vector3 tangent, Vector3 normal)
  62. {
  63. return Vector3.Cross(tangent, normal).normalized;
  64. }
  65. public float Length()
  66. {
  67. if(NbSegments == 0) return 0f;
  68. //Parametrize();
  69. return System.Math.Max(0, GetSegmentDistanceFromStart(NbSegments-1));
  70. }
  71. public void Clear()
  72. {
  73. knots.Clear();
  74. }
  75. public void MoveMarker(Marker marker, float distance) //in Unity units
  76. {
  77. PlaceMarker(marker, distance, marker);
  78. }
  79. public Vector3 GetPosition(Marker marker)
  80. {
  81. Vector3 pos = Vector3.zero;
  82. if(NbSegments == 0) return pos;
  83. SubKnot[] subKnots = GetSegmentSubKnots(marker.segmentIndex);
  84. pos = Vector3.Lerp(subKnots[marker.subKnotAIndex].position,
  85. subKnots[marker.subKnotBIndex].position, marker.lerpRatio);
  86. return pos;
  87. }
  88. public Vector3 GetTangent(Marker marker)
  89. {
  90. Vector3 tangent = Vector3.zero;
  91. if(NbSegments == 0) return tangent;
  92. SubKnot[] subKnots = GetSegmentSubKnots(marker.segmentIndex);
  93. tangent = Vector3.Lerp(subKnots[marker.subKnotAIndex].tangent,
  94. subKnots[marker.subKnotBIndex].tangent, marker.lerpRatio);
  95. return tangent;
  96. }
  97. private float Epsilon { get { return 1f / NbSubSegmentPerSegment; } }
  98. private SubKnot[] GetSegmentSubKnots(int i)
  99. {
  100. return knots[FirstSegmentKnotIndex+i].subKnots;
  101. }
  102. public float GetSegmentDistanceFromStart(int i)
  103. {
  104. return knots[FirstSegmentKnotIndex+i].distanceFromStart;
  105. }
  106. private bool IsSegmentValid(int i)
  107. {
  108. return knots[i].distanceFromStart != -1f && knots[i+1].distanceFromStart != -1f &&
  109. knots[i+2].distanceFromStart != -1f && knots[i+3].distanceFromStart != -1f;
  110. }
  111. private bool OutOfBoundSegmentIndex(int i)
  112. {
  113. return i < 0 || i >= NbSegments;
  114. }
  115. public void Parametrize()
  116. {
  117. Parametrize(0, NbSegments-1);
  118. }
  119. public void Parametrize(int fromSegmentIndex, int toSegmentIndex)
  120. {
  121. if(knots.Count < MinimumKnotNb) return;
  122. int nbSegments = System.Math.Min(toSegmentIndex+1, NbSegments);
  123. fromSegmentIndex = System.Math.Max(0, fromSegmentIndex);
  124. float totalDistance = 0;
  125. if(fromSegmentIndex > 0)
  126. {
  127. totalDistance = GetSegmentDistanceFromStart(fromSegmentIndex-1);
  128. }
  129. for(int i=fromSegmentIndex; i<nbSegments; i++)
  130. {
  131. /*if(IsSegmentValid(i) && !force)
  132. {
  133. totalDistance = GetSegmentDistanceFromStart(i);
  134. continue;
  135. }*/
  136. SubKnot[] subKnots = GetSegmentSubKnots(i);
  137. for(int j=0; j<subKnots.Length; j++)
  138. {
  139. SubKnot sk = new SubKnot();
  140. sk.distanceFromStart = totalDistance += ComputeLengthOfSegment(i, (j-1)*Epsilon, j*Epsilon);
  141. sk.position = GetPositionOnSegment(i, j*Epsilon);
  142. sk.tangent = GetTangentOnSegment(i, j*Epsilon);
  143. subKnots[j] = sk;
  144. }
  145. knots[FirstSegmentKnotIndex+i].distanceFromStart = totalDistance;
  146. }
  147. }
  148. public bool PlaceMarker(Marker result, float distance, Marker from = null)
  149. {
  150. //result = new Marker();
  151. SubKnot[] subKnots;
  152. int nbSegments = NbSegments;
  153. if(nbSegments == 0) return false;
  154. //Parametrize();
  155. if(distance <= 0)
  156. {
  157. result.segmentIndex = 0;
  158. result.subKnotAIndex = 0;
  159. result.subKnotBIndex = 1;
  160. result.lerpRatio = 0f;
  161. return true;
  162. }
  163. else if(distance >= Length())
  164. {
  165. subKnots = GetSegmentSubKnots(nbSegments-1);
  166. result.segmentIndex = nbSegments-1;
  167. result.subKnotAIndex = subKnots.Length-2;
  168. result.subKnotBIndex = subKnots.Length-1;
  169. result.lerpRatio = 1f;
  170. return true;
  171. }
  172. int fromSegmentIndex = 0;
  173. int fromSubKnotIndex = 1;
  174. if(from != null)
  175. {
  176. fromSegmentIndex = from.segmentIndex;
  177. //fromSubKnotIndex = from.subKnotAIndex;
  178. }
  179. for(int i=fromSegmentIndex; i<nbSegments; i++)
  180. {
  181. if(distance > GetSegmentDistanceFromStart(i)) continue;
  182. subKnots = GetSegmentSubKnots(i);
  183. for(int j=fromSubKnotIndex; j<subKnots.Length; j++)
  184. {
  185. SubKnot sk = subKnots[j];
  186. if(distance > sk.distanceFromStart) continue;
  187. result.segmentIndex = i;
  188. result.subKnotAIndex = j-1;
  189. result.subKnotBIndex = j;
  190. result.lerpRatio = 1f - ((sk.distanceFromStart - distance) /
  191. (sk.distanceFromStart - subKnots[j-1].distanceFromStart));
  192. break;
  193. }
  194. break;
  195. }
  196. return true;
  197. }
  198. private float ComputeLength()
  199. {
  200. if(knots.Count < 4) return 0;
  201. float length = 0;
  202. int nbSegments = NbSegments;
  203. for(int i=0; i<nbSegments; i++)
  204. {
  205. length += ComputeLengthOfSegment(i, 0f, 1f);
  206. }
  207. return length;
  208. }
  209. private float ComputeLengthOfSegment(int segmentIndex, float from, float to)
  210. {
  211. float length = 0;
  212. from = Mathf.Clamp01(from);
  213. to = Mathf.Clamp01(to);
  214. Vector3 lastPoint = GetPositionOnSegment(segmentIndex, from);
  215. for(float j=from+Epsilon; j<to+Epsilon/2f; j+=Epsilon)
  216. {
  217. Vector3 point = GetPositionOnSegment(segmentIndex, j);
  218. length += Vector3.Distance(point, lastPoint);
  219. lastPoint = point;
  220. }
  221. return length;
  222. }
  223. public void DebugDrawEquallySpacedDots()
  224. {
  225. Gizmos.color = Color.red;
  226. int nbPoints = NbSubSegmentPerSegment*NbSegments;
  227. float length = Length();
  228. Marker marker = new Marker();
  229. PlaceMarker(marker, 0f);
  230. for(int i=0; i<=nbPoints; i++)
  231. {
  232. MoveMarker(marker, i*(length/nbPoints));
  233. Vector3 position = GetPosition(marker);
  234. //Vector3 tangent = GetTangent(marker);
  235. //Vector3 position = FindPositionFromDistance(i*(length/nbPoints));
  236. //Vector3 tangent = FindTangentFromDistance(i*(length/nbPoints));
  237. //Vector3 binormal = ComputeBinormal(tangent, new Vector3(0, 0, 1));
  238. Gizmos.DrawWireSphere(position, 0.025f);
  239. //Debug.DrawRay(position, binormal * 0.2f, Color.green);
  240. }
  241. }
  242. public void DebugDrawSubKnots()
  243. {
  244. Gizmos.color = Color.yellow;
  245. int nbSegments = NbSegments;
  246. for(int i=0; i<nbSegments; i++)
  247. {
  248. SubKnot[] subKnots = GetSegmentSubKnots(i);
  249. for(int j=0; j<subKnots.Length; j++)
  250. {
  251. Gizmos.DrawWireSphere(subKnots[j].position, 0.025f);
  252. //Gizmos.DrawWireSphere(new Vector3(segments[i].subSegments[j].length, 0, 0), 0.025f);
  253. }
  254. }
  255. }
  256. public void DebugDrawSpline()
  257. {
  258. if(knots.Count >= 4)
  259. {
  260. Gizmos.color = Color.green;
  261. Gizmos.DrawWireSphere(knots[0].position, 0.2f);
  262. Gizmos.color = Color.red;
  263. Gizmos.DrawWireSphere(knots[knots.Count-1].position, 0.2f);
  264. Gizmos.color = Color.blue;
  265. Gizmos.DrawWireSphere(knots[knots.Count-2].position, 0.2f);
  266. int nbSegments = NbSegments;
  267. for(int i=0; i<nbSegments; i++)
  268. {
  269. Vector3 lastPoint = GetPositionOnSegment(i, 0f);
  270. Gizmos.DrawWireSphere(lastPoint, 0.2f);
  271. for(float j=Epsilon; j<1f+Epsilon/2f; j+=Epsilon)
  272. {
  273. Vector3 point = GetPositionOnSegment(i, j);
  274. Debug.DrawLine(lastPoint, point, Color.white);
  275. lastPoint = point;
  276. }
  277. }
  278. }
  279. }
  280. private Vector3 GetPositionOnSegment(int segmentIndex, float t)
  281. {
  282. return FindSplinePoint(knots[segmentIndex].position, knots[segmentIndex+1].position,
  283. knots[segmentIndex+2].position, knots[segmentIndex+3].position, t);
  284. }
  285. private Vector3 GetTangentOnSegment(int segmentIndex, float t)
  286. {
  287. return FindSplineTangent(knots[segmentIndex].position, knots[segmentIndex+1].position,
  288. knots[segmentIndex+2].position, knots[segmentIndex+3].position, t).normalized;
  289. }
  290. private static Vector3 FindSplinePoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
  291. {
  292. Vector3 ret = new Vector3();
  293. float t2 = t * t;
  294. float t3 = t2 * t;
  295. ret.x = 0.5f * ((2.0f * p1.x) +
  296. (-p0.x + p2.x) * t +
  297. (2.0f * p0.x - 5.0f * p1.x + 4 * p2.x - p3.x) * t2 +
  298. (-p0.x + 3.0f * p1.x - 3.0f * p2.x + p3.x) * t3);
  299. ret.y = 0.5f * ((2.0f * p1.y) +
  300. (-p0.y + p2.y) * t +
  301. (2.0f * p0.y - 5.0f * p1.y + 4 * p2.y - p3.y) * t2 +
  302. (-p0.y + 3.0f * p1.y - 3.0f * p2.y + p3.y) * t3);
  303. ret.z = 0.5f * ((2.0f * p1.z) +
  304. (-p0.z + p2.z) * t +
  305. (2.0f * p0.z - 5.0f * p1.z + 4 * p2.z - p3.z) * t2 +
  306. (-p0.z + 3.0f * p1.z - 3.0f * p2.z + p3.z) * t3);
  307. return ret;
  308. }
  309. private static Vector3 FindSplineTangent(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
  310. {
  311. Vector3 ret = new Vector3();
  312. float t2 = t * t;
  313. ret.x = 0.5f * (-p0.x + p2.x) +
  314. (2.0f * p0.x - 5.0f * p1.x + 4 * p2.x - p3.x) * t +
  315. (-p0.x + 3.0f * p1.x - 3.0f * p2.x + p3.x) * t2 * 1.5f;
  316. ret.y = 0.5f * (-p0.y + p2.y) +
  317. (2.0f * p0.y - 5.0f * p1.y + 4 * p2.y - p3.y) * t +
  318. (-p0.y + 3.0f * p1.y - 3.0f * p2.y + p3.y) * t2 * 1.5f;
  319. ret.z = 0.5f * (-p0.z + p2.z) +
  320. (2.0f * p0.z - 5.0f * p1.z + 4 * p2.z - p3.z) * t +
  321. (-p0.z + 3.0f * p1.z - 3.0f * p2.z + p3.z) * t2 * 1.5f;
  322. return ret;
  323. }
  324. }