UnityVectorExtensions.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. using UnityEngine;
  2. namespace Cinemachine.Utility
  3. {
  4. /// <summary>Extensions to the Vector3 class, used by Cinemachine</summary>
  5. public static class UnityVectorExtensions
  6. {
  7. /// <summary>A useful Epsilon</summary>
  8. public const float Epsilon = 0.0001f;
  9. /// <summary>
  10. /// Get the closest point on a line segment.
  11. /// </summary>
  12. /// <param name="p">A point in space</param>
  13. /// <param name="s0">Start of line segment</param>
  14. /// <param name="s1">End of line segment</param>
  15. /// <returns>The interpolation parameter representing the point on the segment, with 0==s0, and 1==s1</returns>
  16. public static float ClosestPointOnSegment(this Vector3 p, Vector3 s0, Vector3 s1)
  17. {
  18. Vector3 s = s1 - s0;
  19. float len2 = Vector3.SqrMagnitude(s);
  20. if (len2 < Epsilon)
  21. return 0; // degenrate segment
  22. return Mathf.Clamp01(Vector3.Dot(p - s0, s) / len2);
  23. }
  24. /// <summary>
  25. /// Get the closest point on a line segment.
  26. /// </summary>
  27. /// <param name="p">A point in space</param>
  28. /// <param name="s0">Start of line segment</param>
  29. /// <param name="s1">End of line segment</param>
  30. /// <returns>The interpolation parameter representing the point on the segment, with 0==s0, and 1==s1</returns>
  31. public static float ClosestPointOnSegment(this Vector2 p, Vector2 s0, Vector2 s1)
  32. {
  33. Vector2 s = s1 - s0;
  34. float len2 = Vector2.SqrMagnitude(s);
  35. if (len2 < Epsilon)
  36. return 0; // degenrate segment
  37. return Mathf.Clamp01(Vector2.Dot(p - s0, s) / len2);
  38. }
  39. /// <summary>
  40. /// Returns a non-normalized projection of the supplied vector onto a plane
  41. /// as described by its normal
  42. /// </summary>
  43. /// <param name="vector"></param>
  44. /// <param name="planeNormal">The normal that defines the plane. Must have a length of 1.</param>
  45. /// <returns>The component of the vector that lies in the plane</returns>
  46. public static Vector3 ProjectOntoPlane(this Vector3 vector, Vector3 planeNormal)
  47. {
  48. return (vector - Vector3.Dot(vector, planeNormal) * planeNormal);
  49. }
  50. /// <summary>
  51. /// Component-wise absolute value
  52. /// </summary>
  53. /// <param name="v"></param>
  54. /// <returns></returns>
  55. public static Vector3 Abs(this Vector3 v)
  56. {
  57. return new Vector3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z));
  58. }
  59. /// <summary>Is the vector within Epsilon of zero length?</summary>
  60. /// <param name="v"></param>
  61. /// <returns>True if the square magnitude of the vector is within Epsilon of zero</returns>
  62. public static bool AlmostZero(this Vector3 v)
  63. {
  64. return v.sqrMagnitude < (Epsilon * Epsilon);
  65. }
  66. /// <summary>Much more stable for small angles than Unity's native implementation</summary>
  67. public static float Angle(Vector3 v1, Vector3 v2)
  68. {
  69. #if false // Maybe this version is better? to test....
  70. float a = v1.magnitude;
  71. v1 *= v2.magnitude;
  72. v2 *= a;
  73. return Mathf.Atan2((v1 - v2).magnitude, (v1 + v2).magnitude) * Mathf.Rad2Deg * 2;
  74. #else
  75. v1.Normalize();
  76. v2.Normalize();
  77. return Mathf.Atan2((v1 - v2).magnitude, (v1 + v2).magnitude) * Mathf.Rad2Deg * 2;
  78. #endif
  79. }
  80. /// <summary>Much more stable for small angles than Unity's native implementation</summary>
  81. public static float SignedAngle(Vector3 v1, Vector3 v2, Vector3 up)
  82. {
  83. float angle = Angle(v1, v2);
  84. if (Mathf.Sign(Vector3.Dot(up, Vector3.Cross(v1, v2))) < 0)
  85. return -angle;
  86. return angle;
  87. }
  88. /// <summary>Much more stable for small angles than Unity's native implementation</summary>
  89. public static Quaternion SafeFromToRotation(Vector3 v1, Vector3 v2, Vector3 up)
  90. {
  91. var axis = Vector3.Cross(v1, v2);
  92. if (axis.AlmostZero())
  93. axis = up; // in case they are pointing in opposite directions
  94. return Quaternion.AngleAxis(Angle(v1, v2), axis);
  95. }
  96. /// <summary>This is a slerp that mimics a camera operator's movement in that
  97. /// it chooses a path that avoids the lower hemisphere, as defined by
  98. /// the up param</summary>
  99. /// <param name="vA">First direction</param>
  100. /// <param name="vB">Second direction</param>
  101. /// <param name="t">Interpolation amoun t</param>
  102. /// <param name="up">Defines the up direction</param>
  103. public static Vector3 SlerpWithReferenceUp(
  104. Vector3 vA, Vector3 vB, float t, Vector3 up)
  105. {
  106. float dA = vA.magnitude;
  107. float dB = vB.magnitude;
  108. if (dA < Epsilon || dB < Epsilon)
  109. return Vector3.Lerp(vA, vB, t);
  110. Vector3 dirA = vA / dA;
  111. Vector3 dirB = vB / dB;
  112. Quaternion qA = Quaternion.LookRotation(dirA, up);
  113. Quaternion qB = Quaternion.LookRotation(dirB, up);
  114. Quaternion q = UnityQuaternionExtensions.SlerpWithReferenceUp(qA, qB, t, up);
  115. Vector3 dir = q * Vector3.forward;
  116. return dir * Mathf.Lerp(dA, dB, t);
  117. }
  118. }
  119. /// <summary>Extensions to the Quaternion class, usen in various places by Cinemachine</summary>
  120. public static class UnityQuaternionExtensions
  121. {
  122. /// <summary>This is a slerp that mimics a camera operator's movement in that
  123. /// it chooses a path that avoids the lower hemisphere, as defined by
  124. /// the up param</summary>
  125. /// <param name="qA">First direction</param>
  126. /// <param name="qB">Second direction</param>
  127. /// <param name="t">Interpolation amount</param>
  128. /// <param name="up">Defines the up direction. Must have a length of 1.</param>
  129. public static Quaternion SlerpWithReferenceUp(
  130. Quaternion qA, Quaternion qB, float t, Vector3 up)
  131. {
  132. var dirA = (qA * Vector3.forward).ProjectOntoPlane(up);
  133. var dirB = (qB * Vector3.forward).ProjectOntoPlane(up);
  134. if (dirA.AlmostZero() || dirB.AlmostZero())
  135. return Quaternion.Slerp(qA, qB, t);
  136. // Work on the plane, in eulers
  137. var qBase = Quaternion.LookRotation(dirA, up);
  138. var qBaseInv = Quaternion.Inverse(qBase);
  139. Quaternion qA1 = qBaseInv * qA;
  140. Quaternion qB1 = qBaseInv * qB;
  141. var eA = qA1.eulerAngles;
  142. var eB = qB1.eulerAngles;
  143. return qBase * Quaternion.Euler(
  144. Mathf.LerpAngle(eA.x, eB.x, t),
  145. Mathf.LerpAngle(eA.y, eB.y, t),
  146. Mathf.LerpAngle(eA.z, eB.z, t));
  147. }
  148. /// <summary>Normalize a quaternion</summary>
  149. /// <param name="q"></param>
  150. /// <returns>The normalized quaternion. Unit length is 1.</returns>
  151. public static Quaternion Normalized(this Quaternion q)
  152. {
  153. Vector4 v = new Vector4(q.x, q.y, q.z, q.w).normalized;
  154. return new Quaternion(v.x, v.y, v.z, v.w);
  155. }
  156. /// <summary>
  157. /// Get the rotations, first about world up, then about (travelling) local right,
  158. /// necessary to align the quaternion's forward with the target direction.
  159. /// This represents the tripod head movement needed to look at the target.
  160. /// This formulation makes it easy to interpolate without introducing spurious roll.
  161. /// </summary>
  162. /// <param name="orient"></param>
  163. /// <param name="lookAtDir">The worldspace target direction in which we want to look</param>
  164. /// <param name="worldUp">Which way is up. Must have a length of 1.</param>
  165. /// <returns>Vector2.y is rotation about worldUp, and Vector2.x is second rotation,
  166. /// about local right.</returns>
  167. public static Vector2 GetCameraRotationToTarget(
  168. this Quaternion orient, Vector3 lookAtDir, Vector3 worldUp)
  169. {
  170. if (lookAtDir.AlmostZero())
  171. return Vector2.zero; // degenerate
  172. // Work in local space
  173. Quaternion toLocal = Quaternion.Inverse(orient);
  174. Vector3 up = toLocal * worldUp;
  175. lookAtDir = toLocal * lookAtDir;
  176. // Align yaw based on world up
  177. float angleH = 0;
  178. {
  179. Vector3 targetDirH = lookAtDir.ProjectOntoPlane(up);
  180. if (!targetDirH.AlmostZero())
  181. {
  182. Vector3 currentDirH = Vector3.forward.ProjectOntoPlane(up);
  183. if (currentDirH.AlmostZero())
  184. {
  185. // We're looking at the north or south pole
  186. if (Vector3.Dot(currentDirH, up) > 0)
  187. currentDirH = Vector3.down.ProjectOntoPlane(up);
  188. else
  189. currentDirH = Vector3.up.ProjectOntoPlane(up);
  190. }
  191. angleH = UnityVectorExtensions.SignedAngle(currentDirH, targetDirH, up);
  192. }
  193. }
  194. Quaternion q = Quaternion.AngleAxis(angleH, up);
  195. // Get local vertical angle
  196. float angleV = UnityVectorExtensions.SignedAngle(
  197. q * Vector3.forward, lookAtDir, q * Vector3.right);
  198. return new Vector2(angleV, angleH);
  199. }
  200. /// <summary>
  201. /// Apply rotations, first about world up, then about (travelling) local right.
  202. /// rot.y is rotation about worldUp, and rot.x is second rotation, about local right.
  203. /// </summary>
  204. /// <param name="orient"></param>
  205. /// <param name="rot">Vector2.y is rotation about worldUp, and Vector2.x is second rotation,
  206. /// about local right.</param>
  207. /// <param name="worldUp">Which way is up</param>
  208. public static Quaternion ApplyCameraRotation(
  209. this Quaternion orient, Vector2 rot, Vector3 worldUp)
  210. {
  211. Quaternion q = Quaternion.AngleAxis(rot.x, Vector3.right);
  212. return (Quaternion.AngleAxis(rot.y, worldUp) * orient) * q;
  213. }
  214. }
  215. /// <summary>Ad-hoc xxtentions to the Rect structure, used by Cinemachine</summary>
  216. public static class UnityRectExtensions
  217. {
  218. /// <summary>Inflate a rect</summary>
  219. /// <param name="r"></param>
  220. /// <param name="delta">x and y are added/subtracted fto/from the edges of
  221. /// the rect, inflating it in all directions</param>
  222. /// <returns>The inflated rect</returns>
  223. public static Rect Inflated(this Rect r, Vector2 delta)
  224. {
  225. return new Rect(
  226. r.xMin - delta.x, r.yMin - delta.y,
  227. r.width + delta.x * 2, r.height + delta.y * 2);
  228. }
  229. }
  230. }