/Scripts/SplineInterpolator.cs

http://acid-and-base.googlecode.com/ · C# · 248 lines · 186 code · 56 blank · 6 comment · 37 complexity · 3df55e6d3d6370843be58578a21328d4 MD5 · raw file

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. public enum eEndPointsMode { AUTO, AUTOCLOSED, EXPLICIT }
  5. public enum eWrapMode { ONCE, LOOP }
  6. public delegate void OnEndCallback();
  7. public class SplineInterpolator : MonoBehaviour
  8. {
  9. eEndPointsMode mEndPointsMode = eEndPointsMode.AUTO;
  10. internal class SplineNode
  11. {
  12. internal Vector3 Point;
  13. internal Quaternion Rot;
  14. internal float Time;
  15. internal Vector2 EaseIO;
  16. internal SplineNode(Vector3 p, Quaternion q, float t, Vector2 io) { Point = p; Rot = q; Time = t; EaseIO = io; }
  17. internal SplineNode(SplineNode o) { Point = o.Point; Rot = o.Rot; Time = o.Time; EaseIO = o.EaseIO; }
  18. }
  19. List<SplineNode> mNodes = new List<SplineNode>();
  20. string mState = "";
  21. bool mRotations;
  22. OnEndCallback mOnEndCallback;
  23. void Awake()
  24. {
  25. Reset();
  26. }
  27. public void StartInterpolation(OnEndCallback endCallback, bool bRotations, eWrapMode mode)
  28. {
  29. if (mState != "Reset")
  30. throw new System.Exception("First reset, add points and then call here");
  31. mState = mode == eWrapMode.ONCE ? "Once" : "Loop";
  32. mRotations = bRotations;
  33. mOnEndCallback = endCallback;
  34. SetInput();
  35. }
  36. public void Reset()
  37. {
  38. mNodes.Clear();
  39. mState = "Reset";
  40. mCurrentIdx = 1;
  41. mCurrentTime = 0;
  42. mRotations = false;
  43. mEndPointsMode = eEndPointsMode.AUTO;
  44. }
  45. public void AddPoint(Vector3 pos, Quaternion quat, float timeInSeconds, Vector2 easeInOut)
  46. {
  47. if (mState != "Reset")
  48. throw new System.Exception("Cannot add points after start");
  49. mNodes.Add(new SplineNode(pos, quat, timeInSeconds, easeInOut));
  50. }
  51. void SetInput()
  52. {
  53. if (mNodes.Count < 2)
  54. throw new System.Exception("Invalid number of points");
  55. if (mRotations)
  56. {
  57. for (int c = 1; c < mNodes.Count; c++)
  58. {
  59. SplineNode node = mNodes[c];
  60. SplineNode prevNode = mNodes[c - 1];
  61. // Always interpolate using the shortest path -> Selective negation
  62. if (Quaternion.Dot(node.Rot, prevNode.Rot) < 0)
  63. {
  64. node.Rot.x = -node.Rot.x;
  65. node.Rot.y = -node.Rot.y;
  66. node.Rot.z = -node.Rot.z;
  67. node.Rot.w = -node.Rot.w;
  68. }
  69. }
  70. }
  71. if (mEndPointsMode == eEndPointsMode.AUTO)
  72. {
  73. mNodes.Insert(0, mNodes[0]);
  74. mNodes.Add(mNodes[mNodes.Count - 1]);
  75. }
  76. else if (mEndPointsMode == eEndPointsMode.EXPLICIT && (mNodes.Count < 4))
  77. throw new System.Exception("Invalid number of points");
  78. }
  79. void SetExplicitMode()
  80. {
  81. if (mState != "Reset")
  82. throw new System.Exception("Cannot change mode after start");
  83. mEndPointsMode = eEndPointsMode.EXPLICIT;
  84. }
  85. public void SetAutoCloseMode(float joiningPointTime)
  86. {
  87. if (mState != "Reset")
  88. throw new System.Exception("Cannot change mode after start");
  89. mEndPointsMode = eEndPointsMode.AUTOCLOSED;
  90. mNodes.Add(new SplineNode(mNodes[0] as SplineNode));
  91. mNodes[mNodes.Count - 1].Time = joiningPointTime;
  92. Vector3 vInitDir = (mNodes[1].Point - mNodes[0].Point).normalized;
  93. Vector3 vEndDir = (mNodes[mNodes.Count - 2].Point - mNodes[mNodes.Count - 1].Point).normalized;
  94. float firstLength = (mNodes[1].Point - mNodes[0].Point).magnitude;
  95. float lastLength = (mNodes[mNodes.Count - 2].Point - mNodes[mNodes.Count - 1].Point).magnitude;
  96. SplineNode firstNode = new SplineNode(mNodes[0] as SplineNode);
  97. firstNode.Point = mNodes[0].Point + vEndDir * firstLength;
  98. SplineNode lastNode = new SplineNode(mNodes[mNodes.Count - 1] as SplineNode);
  99. lastNode.Point = mNodes[0].Point + vInitDir * lastLength;
  100. mNodes.Insert(0, firstNode);
  101. mNodes.Add(lastNode);
  102. }
  103. float mCurrentTime;
  104. int mCurrentIdx = 1;
  105. void Update()
  106. {
  107. if (mState == "Reset" || mState == "Stopped" || mNodes.Count < 4)
  108. return;
  109. mCurrentTime += Time.deltaTime;
  110. // We advance to next point in the path
  111. if (mCurrentTime >= mNodes[mCurrentIdx + 1].Time)
  112. {
  113. if (mCurrentIdx < mNodes.Count - 3)
  114. {
  115. mCurrentIdx++;
  116. }
  117. else
  118. {
  119. if (mState != "Loop")
  120. {
  121. mState = "Stopped";
  122. // We stop right in the end point
  123. transform.position = mNodes[mNodes.Count - 2].Point;
  124. if (mRotations)
  125. transform.rotation = mNodes[mNodes.Count - 2].Rot;
  126. // We call back to inform that we are ended
  127. if (mOnEndCallback != null)
  128. mOnEndCallback();
  129. }
  130. else
  131. {
  132. mCurrentIdx = 1;
  133. mCurrentTime = 0;
  134. }
  135. }
  136. }
  137. if (mState != "Stopped")
  138. {
  139. // Calculates the t param between 0 and 1
  140. float param = (mCurrentTime - mNodes[mCurrentIdx].Time) / (mNodes[mCurrentIdx + 1].Time - mNodes[mCurrentIdx].Time);
  141. // Smooth the param
  142. param = MathUtils.Ease(param, mNodes[mCurrentIdx].EaseIO.x, mNodes[mCurrentIdx].EaseIO.y);
  143. transform.position = GetHermiteInternal(mCurrentIdx, param);
  144. if (mRotations)
  145. {
  146. transform.rotation = GetSquad(mCurrentIdx, param);
  147. }
  148. }
  149. }
  150. Quaternion GetSquad(int idxFirstPoint, float t)
  151. {
  152. Quaternion Q0 = mNodes[idxFirstPoint - 1].Rot;
  153. Quaternion Q1 = mNodes[idxFirstPoint].Rot;
  154. Quaternion Q2 = mNodes[idxFirstPoint + 1].Rot;
  155. Quaternion Q3 = mNodes[idxFirstPoint + 2].Rot;
  156. Quaternion T1 = MathUtils.GetSquadIntermediate(Q0, Q1, Q2);
  157. Quaternion T2 = MathUtils.GetSquadIntermediate(Q1, Q2, Q3);
  158. return MathUtils.GetQuatSquad(t, Q1, Q2, T1, T2);
  159. }
  160. public Vector3 GetHermiteInternal(int idxFirstPoint, float t)
  161. {
  162. float t2 = t * t;
  163. float t3 = t2 * t;
  164. Vector3 P0 = mNodes[idxFirstPoint - 1].Point;
  165. Vector3 P1 = mNodes[idxFirstPoint].Point;
  166. Vector3 P2 = mNodes[idxFirstPoint + 1].Point;
  167. Vector3 P3 = mNodes[idxFirstPoint + 2].Point;
  168. float tension = 0.5f; // 0.5 equivale a catmull-rom
  169. Vector3 T1 = tension * (P2 - P0);
  170. Vector3 T2 = tension * (P3 - P1);
  171. float Blend1 = 2 * t3 - 3 * t2 + 1;
  172. float Blend2 = -2 * t3 + 3 * t2;
  173. float Blend3 = t3 - 2 * t2 + t;
  174. float Blend4 = t3 - t2;
  175. return Blend1 * P1 + Blend2 * P2 + Blend3 * T1 + Blend4 * T2;
  176. }
  177. public Vector3 GetHermiteAtTime(float timeParam)
  178. {
  179. if (timeParam >= mNodes[mNodes.Count - 2].Time)
  180. return mNodes[mNodes.Count - 2].Point;
  181. int c;
  182. for (c = 1; c < mNodes.Count - 2; c++)
  183. {
  184. if (mNodes[c].Time > timeParam)
  185. break;
  186. }
  187. int idx = c - 1;
  188. float param = (timeParam - mNodes[idx].Time) / (mNodes[idx + 1].Time - mNodes[idx].Time);
  189. param = MathUtils.Ease(param, mNodes[idx].EaseIO.x, mNodes[idx].EaseIO.y);
  190. return GetHermiteInternal(idx, param);
  191. }
  192. }