UnityVectorExtensions.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. using System;
  2. using UnityEngine;
  3. namespace Cinemachine.Utility
  4. {
  5. /// <summary>Extensions to the Vector3 class, used by Cinemachine</summary>
  6. public static class UnityVectorExtensions
  7. {
  8. /// <summary>A useful Epsilon</summary>
  9. public const float Epsilon = 0.0001f;
  10. /// <summary>
  11. /// Checks if the Vector2 contains NaN for x or y.
  12. /// </summary>
  13. /// <param name="v">Vector2 to check for NaN</param>
  14. /// <returns>True, if any components of the vector are NaN</returns>
  15. public static bool IsNaN(this Vector2 v)
  16. {
  17. return float.IsNaN(v.x) || float.IsNaN(v.y);
  18. }
  19. /// <summary>
  20. /// Checks if the Vector2 contains NaN for x or y.
  21. /// </summary>
  22. /// <param name="v">Vector2 to check for NaN</param>
  23. /// <returns>True, if any components of the vector are NaN</returns>
  24. public static bool IsNaN(this Vector3 v)
  25. {
  26. return float.IsNaN(v.x) || float.IsNaN(v.y) || float.IsNaN(v.z);
  27. }
  28. /// <summary>
  29. /// Get the closest point on a line segment.
  30. /// </summary>
  31. /// <param name="p">A point in space</param>
  32. /// <param name="s0">Start of line segment</param>
  33. /// <param name="s1">End of line segment</param>
  34. /// <returns>The interpolation parameter representing the point on the segment, with 0==s0, and 1==s1</returns>
  35. public static float ClosestPointOnSegment(this Vector3 p, Vector3 s0, Vector3 s1)
  36. {
  37. Vector3 s = s1 - s0;
  38. float len2 = Vector3.SqrMagnitude(s);
  39. if (len2 < Epsilon)
  40. return 0; // degenrate segment
  41. return Mathf.Clamp01(Vector3.Dot(p - s0, s) / len2);
  42. }
  43. /// <summary>
  44. /// Get the closest point on a line segment.
  45. /// </summary>
  46. /// <param name="p">A point in space</param>
  47. /// <param name="s0">Start of line segment</param>
  48. /// <param name="s1">End of line segment</param>
  49. /// <returns>The interpolation parameter representing the point on the segment, with 0==s0, and 1==s1</returns>
  50. public static float ClosestPointOnSegment(this Vector2 p, Vector2 s0, Vector2 s1)
  51. {
  52. Vector2 s = s1 - s0;
  53. float len2 = Vector2.SqrMagnitude(s);
  54. if (len2 < Epsilon)
  55. return 0; // degenrate segment
  56. return Mathf.Clamp01(Vector2.Dot(p - s0, s) / len2);
  57. }
  58. /// <summary>
  59. /// Returns a non-normalized projection of the supplied vector onto a plane
  60. /// as described by its normal
  61. /// </summary>
  62. /// <param name="vector"></param>
  63. /// <param name="planeNormal">The normal that defines the plane. Must have a length of 1.</param>
  64. /// <returns>The component of the vector that lies in the plane</returns>
  65. public static Vector3 ProjectOntoPlane(this Vector3 vector, Vector3 planeNormal)
  66. {
  67. return (vector - Vector3.Dot(vector, planeNormal) * planeNormal);
  68. }
  69. /// <summary>
  70. /// Normalized the vector onto the unit square instead of the unit circle
  71. /// </summary>
  72. /// <param name="v">The vector to normalize</param>
  73. /// <returns>The normalized vector, or the zero vector if its magnitude
  74. /// was too small to normalize</returns>
  75. public static Vector2 SquareNormalize(this Vector2 v)
  76. {
  77. var d = Mathf.Max(Mathf.Abs(v.x), Mathf.Abs(v.y));
  78. return d < Epsilon ? Vector2.zero : v / d;
  79. }
  80. /// <summary>
  81. /// Calculates the intersection point defined by line_1 [p1, p2], and line_2 [q1, q2].
  82. /// </summary>
  83. /// <param name="p1">line_1 is defined by (p1, p2)</param>
  84. /// <param name="p2">line_1 is defined by (p1, p2)</param>
  85. /// <param name="q1">line_2 is defined by (q1, q2)</param>
  86. /// <param name="q2">line_2 is defined by (q1, q2)</param>
  87. /// <param name="intersection">If lines intersect at a single point,
  88. /// then this will hold the intersection point.
  89. /// Otherwise, it will be Vector2.positiveInfinity.</param>
  90. /// <returns>
  91. /// 0 = no intersection,
  92. /// 1 = lines intersect,
  93. /// 2 = segments intersect,
  94. /// 3 = lines are colinear, segments do not touch,
  95. /// 4 = lines are colinear, segments touch (at one or at multiple points)
  96. /// </returns>
  97. public static int FindIntersection(
  98. in Vector2 p1, in Vector2 p2, in Vector2 q1, in Vector2 q2,
  99. out Vector2 intersection)
  100. {
  101. var p = p2 - p1;
  102. var q = q2 - q1;
  103. var pq = q1 - p1;
  104. var pXq = p.Cross(q);
  105. if (Mathf.Abs(pXq) < 0.00001f)
  106. {
  107. // The lines are parallel (or close enough to it)
  108. intersection = Vector2.positiveInfinity;
  109. if (Mathf.Abs(pq.Cross(p)) < 0.00001f)
  110. {
  111. // The lines are colinear. Do the segments touch?
  112. var dotPQ = Vector2.Dot(q, p);
  113. if (dotPQ > 0 && (p1 - q2).sqrMagnitude < 0.001f)
  114. {
  115. // q points to start of p
  116. intersection = q2;
  117. return 4;
  118. }
  119. if (dotPQ < 0 && (p2 - q2).sqrMagnitude < 0.001f)
  120. {
  121. // p and q point at the same point
  122. intersection = p2;
  123. return 4;
  124. }
  125. var dot = Vector2.Dot(pq, p);
  126. if (0 <= dot && dot <= Vector2.Dot(p, p))
  127. {
  128. if (dot < 0.0001f)
  129. {
  130. if (dotPQ <= 0 && (p1 - q1).sqrMagnitude < 0.001f)
  131. intersection = p1; // p and q start at the same point and point away
  132. }
  133. else if (dotPQ > 0 && (p2 - q1).sqrMagnitude < 0.001f)
  134. intersection = p2; // p points at start of q
  135. return 4; // colinear segments touch
  136. }
  137. dot = Vector2.Dot(p1 - q1, q);
  138. if (0 <= dot && dot <= Vector2.Dot(q, q))
  139. return 4; // colinear segments overlap
  140. return 3; // colinear segments don't touch
  141. }
  142. return 0; // the lines are parallel and not colinear
  143. }
  144. var t = pq.Cross(q) / pXq;
  145. intersection = p1 + t * p;
  146. var u = pq.Cross(p) / pXq;
  147. if (0 <= t && t <= 1 && 0 <= u && u <= 1)
  148. return 2; // segments touch
  149. return 1; // segments don't touch but lines intersect
  150. }
  151. private static float Cross(this Vector2 v1, Vector2 v2) { return (v1.x * v2.y) - (v1.y * v2.x); }
  152. /// <summary>
  153. /// Component-wise absolute value
  154. /// </summary>
  155. /// <param name="v">Input vector</param>
  156. /// <returns>Component-wise absolute value of the input vector</returns>
  157. public static Vector2 Abs(this Vector2 v)
  158. {
  159. return new Vector2(Mathf.Abs(v.x), Mathf.Abs(v.y));
  160. }
  161. /// <summary>
  162. /// Component-wise absolute value
  163. /// </summary>
  164. /// <param name="v">Input vector</param>
  165. /// <returns>Component-wise absolute value of the input vector</returns>
  166. public static Vector3 Abs(this Vector3 v)
  167. {
  168. return new Vector3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z));
  169. }
  170. /// <summary>
  171. /// Checks whether the vector components are the same value.
  172. /// </summary>
  173. /// <param name="v">Vector to check</param>
  174. /// <returns>True, if the vector elements are the same. False, otherwise.</returns>
  175. public static bool IsUniform(this Vector2 v)
  176. {
  177. return Math.Abs(v.x - v.y) < Epsilon;
  178. }
  179. /// <summary>
  180. /// Checks whether the vector components are the same value.
  181. /// </summary>
  182. /// <param name="v">Vector to check</param>
  183. /// <returns>True, if the vector elements are the same. False, otherwise.</returns>
  184. public static bool IsUniform(this Vector3 v)
  185. {
  186. return Math.Abs(v.x - v.y) < Epsilon && Math.Abs(v.x - v.z) < Epsilon;
  187. }
  188. /// <summary>Is the vector within Epsilon of zero length?</summary>
  189. /// <param name="v"></param>
  190. /// <returns>True if the square magnitude of the vector is within Epsilon of zero</returns>
  191. public static bool AlmostZero(this Vector3 v)
  192. {
  193. return v.sqrMagnitude < (Epsilon * Epsilon);
  194. }
  195. internal static void ConservativeSetPositionAndRotation(this Transform t, Vector3 pos, Quaternion rot)
  196. {
  197. #if UNITY_EDITOR
  198. // Avoid dirtying the scene with insignificant diffs
  199. if (Application.isPlaying)
  200. t.SetPositionAndRotation(pos, rot);
  201. else
  202. {
  203. // Work in local space to reduce precision mismatches
  204. if (t.parent != null)
  205. {
  206. pos = t.parent.InverseTransformPoint(pos);
  207. rot = Quaternion.Inverse(t.parent.rotation) * rot;
  208. }
  209. const float tolerance = 0.0001f;
  210. var p = t.localPosition;
  211. if (Mathf.Abs(p.x - pos.x) < tolerance
  212. && Mathf.Abs(p.y - pos.y) < tolerance
  213. && Mathf.Abs(p.z - pos.z) < tolerance)
  214. pos = p;
  215. var r = t.localRotation;
  216. if (Mathf.Abs(r.x - rot.x) < tolerance
  217. && Mathf.Abs(r.y - rot.y) < tolerance
  218. && Mathf.Abs(r.z - rot.z) < tolerance
  219. && Mathf.Abs(r.w - rot.w) < tolerance)
  220. rot = r;
  221. t.localPosition = pos;
  222. t.localRotation = rot;
  223. }
  224. #else
  225. t.SetPositionAndRotation(pos, rot);
  226. #endif
  227. }
  228. /// <summary>Much more stable for small angles than Unity's native implementation</summary>
  229. /// <param name="v1">The first vector</param>
  230. /// <param name="v2">The second vector</param>
  231. /// <returns>Angle between the vectors, in degrees</returns>
  232. public static float Angle(Vector3 v1, Vector3 v2)
  233. {
  234. #if false // Maybe this version is better? to test....
  235. float a = v1.magnitude;
  236. v1 *= v2.magnitude;
  237. v2 *= a;
  238. return Mathf.Atan2((v1 - v2).magnitude, (v1 + v2).magnitude) * Mathf.Rad2Deg * 2;
  239. #else
  240. v1.Normalize();
  241. v2.Normalize();
  242. return Mathf.Atan2((v1 - v2).magnitude, (v1 + v2).magnitude) * Mathf.Rad2Deg * 2;
  243. #endif
  244. }
  245. /// <summary>Much more stable for small angles than Unity's native implementation</summary>
  246. /// <param name="v1">The first vector</param>
  247. /// <param name="v2">The second vector</param>
  248. /// <param name="up">Definition of up (used to determine the sign)</param>
  249. /// <returns>Signed angle between the vectors, in degrees</returns>
  250. public static float SignedAngle(Vector3 v1, Vector3 v2, Vector3 up)
  251. {
  252. float angle = Angle(v1, v2);
  253. if (Mathf.Sign(Vector3.Dot(up, Vector3.Cross(v1, v2))) < 0)
  254. return -angle;
  255. return angle;
  256. }
  257. /// <summary>Much more stable for small angles than Unity's native implementation</summary>
  258. /// <param name="v1">The first vector</param>
  259. /// <param name="v2">The second vector</param>
  260. /// <param name="up">Definition of up (used to determine the sign)</param>
  261. /// <returns>Rotation between the vectors</returns>
  262. public static Quaternion SafeFromToRotation(Vector3 v1, Vector3 v2, Vector3 up)
  263. {
  264. var axis = Vector3.Cross(v1, v2);
  265. if (axis.AlmostZero())
  266. axis = up; // in case they are pointing in opposite directions
  267. return Quaternion.AngleAxis(Angle(v1, v2), axis);
  268. }
  269. /// <summary>This is a slerp that mimics a camera operator's movement in that
  270. /// it chooses a path that avoids the lower hemisphere, as defined by
  271. /// the up param</summary>
  272. /// <param name="vA">First direction</param>
  273. /// <param name="vB">Second direction</param>
  274. /// <param name="t">Interpolation amoun t</param>
  275. /// <param name="up">Defines the up direction</param>
  276. /// <returns>Interpolated vector</returns>
  277. public static Vector3 SlerpWithReferenceUp(
  278. Vector3 vA, Vector3 vB, float t, Vector3 up)
  279. {
  280. float dA = vA.magnitude;
  281. float dB = vB.magnitude;
  282. if (dA < Epsilon || dB < Epsilon)
  283. return Vector3.Lerp(vA, vB, t);
  284. Vector3 dirA = vA / dA;
  285. Vector3 dirB = vB / dB;
  286. Quaternion qA = Quaternion.LookRotation(dirA, up);
  287. Quaternion qB = Quaternion.LookRotation(dirB, up);
  288. Quaternion q = UnityQuaternionExtensions.SlerpWithReferenceUp(qA, qB, t, up);
  289. Vector3 dir = q * Vector3.forward;
  290. return dir * Mathf.Lerp(dA, dB, t);
  291. }
  292. }
  293. /// <summary>Extensions to the Quaternion class, usen in various places by Cinemachine</summary>
  294. public static class UnityQuaternionExtensions
  295. {
  296. /// <summary>This is a slerp that mimics a camera operator's movement in that
  297. /// it chooses a path that avoids the lower hemisphere, as defined by
  298. /// the up param</summary>
  299. /// <param name="qA">First direction</param>
  300. /// <param name="qB">Second direction</param>
  301. /// <param name="t">Interpolation amount</param>
  302. /// <param name="up">Defines the up direction. Must have a length of 1.</param>
  303. /// <returns>Interpolated quaternion</returns>
  304. public static Quaternion SlerpWithReferenceUp(
  305. Quaternion qA, Quaternion qB, float t, Vector3 up)
  306. {
  307. var dirA = (qA * Vector3.forward).ProjectOntoPlane(up);
  308. var dirB = (qB * Vector3.forward).ProjectOntoPlane(up);
  309. if (dirA.AlmostZero() || dirB.AlmostZero())
  310. return Quaternion.Slerp(qA, qB, t);
  311. // Work on the plane, in eulers
  312. var qBase = Quaternion.LookRotation(dirA, up);
  313. var qBaseInv = Quaternion.Inverse(qBase);
  314. Quaternion qA1 = qBaseInv * qA;
  315. Quaternion qB1 = qBaseInv * qB;
  316. var eA = qA1.eulerAngles;
  317. var eB = qB1.eulerAngles;
  318. return qBase * Quaternion.Euler(
  319. Mathf.LerpAngle(eA.x, eB.x, t),
  320. Mathf.LerpAngle(eA.y, eB.y, t),
  321. Mathf.LerpAngle(eA.z, eB.z, t));
  322. }
  323. /// <summary>Normalize a quaternion</summary>
  324. /// <param name="q"></param>
  325. /// <returns>The normalized quaternion. Unit length is 1.</returns>
  326. public static Quaternion Normalized(this Quaternion q)
  327. {
  328. Vector4 v = new Vector4(q.x, q.y, q.z, q.w).normalized;
  329. return new Quaternion(v.x, v.y, v.z, v.w);
  330. }
  331. /// <summary>
  332. /// Get the rotations, first about world up, then about (travelling) local right,
  333. /// necessary to align the quaternion's forward with the target direction.
  334. /// This represents the tripod head movement needed to look at the target.
  335. /// This formulation makes it easy to interpolate without introducing spurious roll.
  336. /// </summary>
  337. /// <param name="orient"></param>
  338. /// <param name="lookAtDir">The worldspace target direction in which we want to look</param>
  339. /// <param name="worldUp">Which way is up. Must have a length of 1.</param>
  340. /// <returns>Vector2.y is rotation about worldUp, and Vector2.x is second rotation,
  341. /// about local right.</returns>
  342. public static Vector2 GetCameraRotationToTarget(
  343. this Quaternion orient, Vector3 lookAtDir, Vector3 worldUp)
  344. {
  345. if (lookAtDir.AlmostZero())
  346. return Vector2.zero; // degenerate
  347. // Work in local space
  348. Quaternion toLocal = Quaternion.Inverse(orient);
  349. Vector3 up = toLocal * worldUp;
  350. lookAtDir = toLocal * lookAtDir;
  351. // Align yaw based on world up
  352. float angleH = 0;
  353. {
  354. Vector3 targetDirH = lookAtDir.ProjectOntoPlane(up);
  355. if (!targetDirH.AlmostZero())
  356. {
  357. Vector3 currentDirH = Vector3.forward.ProjectOntoPlane(up);
  358. if (currentDirH.AlmostZero())
  359. {
  360. // We're looking at the north or south pole
  361. if (Vector3.Dot(currentDirH, up) > 0)
  362. currentDirH = Vector3.down.ProjectOntoPlane(up);
  363. else
  364. currentDirH = Vector3.up.ProjectOntoPlane(up);
  365. }
  366. angleH = UnityVectorExtensions.SignedAngle(currentDirH, targetDirH, up);
  367. }
  368. }
  369. Quaternion q = Quaternion.AngleAxis(angleH, up);
  370. // Get local vertical angle
  371. float angleV = UnityVectorExtensions.SignedAngle(
  372. q * Vector3.forward, lookAtDir, q * Vector3.right);
  373. return new Vector2(angleV, angleH);
  374. }
  375. /// <summary>
  376. /// Apply rotations, first about world up, then about (travelling) local right.
  377. /// rot.y is rotation about worldUp, and rot.x is second rotation, about local right.
  378. /// </summary>
  379. /// <param name="orient"></param>
  380. /// <param name="rot">Vector2.y is rotation about worldUp, and Vector2.x is second rotation,
  381. /// about local right.</param>
  382. /// <param name="worldUp">Which way is up</param>
  383. /// <returns>Result rotation after the input is applied to the input quaternion</returns>
  384. public static Quaternion ApplyCameraRotation(
  385. this Quaternion orient, Vector2 rot, Vector3 worldUp)
  386. {
  387. if (rot.sqrMagnitude < 0.0001f)
  388. return orient;
  389. Quaternion q = Quaternion.AngleAxis(rot.x, Vector3.right);
  390. return (Quaternion.AngleAxis(rot.y, worldUp) * orient) * q;
  391. }
  392. }
  393. /// <summary>Ad-hoc xxtentions to the Rect structure, used by Cinemachine</summary>
  394. public static class UnityRectExtensions
  395. {
  396. /// <summary>Inflate a rect</summary>
  397. /// <param name="r"></param>
  398. /// <param name="delta">x and y are added/subtracted fto/from the edges of
  399. /// the rect, inflating it in all directions</param>
  400. /// <returns>The inflated rect</returns>
  401. public static Rect Inflated(this Rect r, Vector2 delta)
  402. {
  403. return new Rect(
  404. r.xMin - delta.x, r.yMin - delta.y,
  405. r.width + delta.x * 2, r.height + delta.y * 2);
  406. }
  407. }
  408. }