RuntimeUtility.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. #if !UNITY_2019_3_OR_NEWER
  2. #define CINEMACHINE_PHYSICS
  3. #define CINEMACHINE_PHYSICS_2D
  4. #endif
  5. using UnityEngine;
  6. namespace Cinemachine
  7. {
  8. /// <summary>An ad-hoc collection of helpers, used by Cinemachine
  9. /// or its editor tools in various places</summary>
  10. [DocumentationSorting(DocumentationSortingAttribute.Level.Undoc)]
  11. public static class RuntimeUtility
  12. {
  13. /// <summary>Convenience to destroy an object, using the appropriate method depending
  14. /// on whether the game is playing</summary>
  15. /// <param name="obj">The object to destroy</param>
  16. public static void DestroyObject(UnityEngine.Object obj)
  17. {
  18. if (obj != null)
  19. {
  20. #if UNITY_EDITOR
  21. if (Application.isPlaying)
  22. UnityEngine.Object.Destroy(obj);
  23. else
  24. UnityEngine.Object.DestroyImmediate(obj);
  25. #else
  26. UnityEngine.Object.Destroy(obj);
  27. #endif
  28. }
  29. }
  30. /// <summary>
  31. /// Check whether a GameObject is a prefab.
  32. /// For editor only - some things are disallowed if prefab. In runtime, will always return false.
  33. /// </summary>
  34. /// <param name="gameObject">the object to check</param>
  35. /// <returns>If editor, checks if object is a prefab or prefab instance.
  36. /// In runtime, returns false always</returns>
  37. public static bool IsPrefab(GameObject gameObject)
  38. {
  39. #if UNITY_EDITOR
  40. return UnityEditor.PrefabUtility.GetPrefabInstanceStatus(gameObject)
  41. != UnityEditor.PrefabInstanceStatus.NotAPrefab;
  42. #else
  43. return false;
  44. #endif
  45. }
  46. #if CINEMACHINE_PHYSICS
  47. private static RaycastHit[] s_HitBuffer = new RaycastHit[16];
  48. private static int[] s_PenetrationIndexBuffer = new int[16];
  49. /// <summary>
  50. /// Perform a raycast, but pass through any objects that have a given tag
  51. /// </summary>
  52. /// <param name="ray">The ray to cast</param>
  53. /// <param name="hitInfo">The returned results</param>
  54. /// <param name="rayLength">Length of the raycast</param>
  55. /// <param name="layerMask">Layers to include</param>
  56. /// <param name="ignoreTag">Tag to ignore</param>
  57. /// <returns>True if something was hit. Results in hitInfo</returns>
  58. public static bool RaycastIgnoreTag(
  59. Ray ray, out RaycastHit hitInfo, float rayLength, int layerMask, in string ignoreTag)
  60. {
  61. if (ignoreTag.Length == 0)
  62. {
  63. if (Physics.Raycast(
  64. ray, out hitInfo, rayLength, layerMask,
  65. QueryTriggerInteraction.Ignore))
  66. {
  67. return true;
  68. }
  69. }
  70. else
  71. {
  72. int closestHit = -1;
  73. int numHits = Physics.RaycastNonAlloc(
  74. ray, s_HitBuffer, rayLength, layerMask, QueryTriggerInteraction.Ignore);
  75. for (int i = 0; i < numHits; ++i)
  76. {
  77. if (s_HitBuffer[i].collider.CompareTag(ignoreTag))
  78. continue;
  79. if (closestHit < 0 || s_HitBuffer[i].distance < s_HitBuffer[closestHit].distance)
  80. closestHit = i;
  81. }
  82. if (closestHit >= 0)
  83. {
  84. hitInfo = s_HitBuffer[closestHit];
  85. if (numHits == s_HitBuffer.Length)
  86. s_HitBuffer = new RaycastHit[s_HitBuffer.Length * 2]; // full! grow for next time
  87. return true;
  88. }
  89. }
  90. hitInfo = new RaycastHit();
  91. return false;
  92. }
  93. /// <summary>
  94. /// Perform a sphere cast, but pass through objects with a given tag
  95. /// </summary>
  96. /// <param name="rayStart">Start of the ray</param>
  97. /// <param name="radius">Radius of the sphere cast</param>
  98. /// <param name="dir">Normalized direction of the ray</param>
  99. /// <param name="hitInfo">Results go here</param>
  100. /// <param name="rayLength">Length of the ray</param>
  101. /// <param name="layerMask">Layers to include</param>
  102. /// <param name="ignoreTag">Tag to ignore</param>
  103. /// <returns>True if something is hit. Results in hitInfo.</returns>
  104. public static bool SphereCastIgnoreTag(
  105. Vector3 rayStart, float radius, Vector3 dir,
  106. out RaycastHit hitInfo, float rayLength,
  107. int layerMask, in string ignoreTag)
  108. {
  109. int closestHit = -1;
  110. int numPenetrations = 0;
  111. float penetrationDistanceSum = 0;
  112. int numHits = Physics.SphereCastNonAlloc(
  113. rayStart, radius, dir, s_HitBuffer, rayLength, layerMask,
  114. QueryTriggerInteraction.Ignore);
  115. for (int i = 0; i < numHits; ++i)
  116. {
  117. var h = s_HitBuffer[i];
  118. if (ignoreTag.Length > 0 && h.collider.CompareTag(ignoreTag))
  119. continue;
  120. // Collect overlapping items
  121. if (h.distance == 0 && h.normal == -dir)
  122. {
  123. // hitInfo for overlapping colliders will have special
  124. // values that are not helpful to the caller. Fix that here.
  125. var scratchCollider = GetScratchCollider();
  126. scratchCollider.radius = radius;
  127. var c = h.collider;
  128. if (Physics.ComputePenetration(
  129. scratchCollider, rayStart, Quaternion.identity,
  130. c, c.transform.position, c.transform.rotation,
  131. out var offsetDir, out var offsetDistance))
  132. {
  133. h.point = rayStart + offsetDir * (offsetDistance - radius);
  134. h.distance = offsetDistance - radius; // will be -ve
  135. h.normal = offsetDir;
  136. s_HitBuffer[i] = h;
  137. if (h.distance < -0.0001f)
  138. {
  139. penetrationDistanceSum += h.distance;
  140. if (s_PenetrationIndexBuffer.Length > numPenetrations + 1)
  141. s_PenetrationIndexBuffer[numPenetrations++] = i;
  142. }
  143. }
  144. else
  145. {
  146. continue; // don't know what's going on, just forget about it
  147. }
  148. }
  149. if (closestHit < 0 || h.distance < s_HitBuffer[closestHit].distance)
  150. {
  151. closestHit = i;
  152. }
  153. }
  154. // Naively combine penetrating items
  155. if (numPenetrations > 1)
  156. {
  157. hitInfo = new RaycastHit();
  158. for (int i = 0; i < numPenetrations; ++i)
  159. {
  160. var h = s_HitBuffer[s_PenetrationIndexBuffer[i]];
  161. var t = h.distance / penetrationDistanceSum;
  162. hitInfo.point += h.point * t;
  163. hitInfo.distance += h.distance * t;
  164. hitInfo.normal += h.normal * t;
  165. }
  166. hitInfo.normal = hitInfo.normal.normalized;
  167. return true;
  168. }
  169. if (closestHit >= 0)
  170. {
  171. hitInfo = s_HitBuffer[closestHit];
  172. if (numHits == s_HitBuffer.Length)
  173. s_HitBuffer = new RaycastHit[s_HitBuffer.Length * 2]; // full! grow for next time
  174. return true;
  175. }
  176. hitInfo = new RaycastHit();
  177. return false;
  178. }
  179. private static SphereCollider s_ScratchCollider;
  180. private static GameObject s_ScratchColliderGameObject;
  181. internal static SphereCollider GetScratchCollider()
  182. {
  183. if (s_ScratchColliderGameObject == null)
  184. {
  185. s_ScratchColliderGameObject = new GameObject("Cinemachine Scratch Collider");
  186. s_ScratchColliderGameObject.hideFlags = HideFlags.HideAndDontSave;
  187. s_ScratchColliderGameObject.transform.position = Vector3.zero;
  188. s_ScratchColliderGameObject.SetActive(true);
  189. s_ScratchCollider = s_ScratchColliderGameObject.AddComponent<SphereCollider>();
  190. s_ScratchCollider.isTrigger = true;
  191. var rb = s_ScratchColliderGameObject.AddComponent<Rigidbody>();
  192. rb.detectCollisions = false;
  193. rb.isKinematic = true;
  194. }
  195. return s_ScratchCollider;
  196. }
  197. internal static void DestroyScratchCollider()
  198. {
  199. if (s_ScratchColliderGameObject != null)
  200. {
  201. s_ScratchColliderGameObject.SetActive(false);
  202. DestroyObject(s_ScratchColliderGameObject.GetComponent<Rigidbody>());
  203. }
  204. DestroyObject(s_ScratchCollider);
  205. DestroyObject(s_ScratchColliderGameObject);
  206. s_ScratchColliderGameObject = null;
  207. s_ScratchCollider = null;
  208. }
  209. #endif
  210. /// <summary>
  211. /// Normalize a curve so that its X and Y axes range from 0 to 1
  212. /// </summary>
  213. /// <param name="curve">Curve to normalize</param>
  214. /// <param name="normalizeX">If true, normalize the X axis</param>
  215. /// <param name="normalizeY">If true, normalize the Y axis</param>
  216. /// <returns>The normalized curve</returns>
  217. public static AnimationCurve NormalizeCurve(
  218. AnimationCurve curve, bool normalizeX, bool normalizeY)
  219. {
  220. if (!normalizeX && !normalizeY)
  221. return curve;
  222. Keyframe[] keys = curve.keys;
  223. if (keys.Length > 0)
  224. {
  225. float minTime = keys[0].time;
  226. float maxTime = minTime;
  227. float minVal = keys[0].value;
  228. float maxVal = minVal;
  229. for (int i = 0; i < keys.Length; ++i)
  230. {
  231. minTime = Mathf.Min(minTime, keys[i].time);
  232. maxTime = Mathf.Max(maxTime, keys[i].time);
  233. minVal = Mathf.Min(minVal, keys[i].value);
  234. maxVal = Mathf.Max(maxVal, keys[i].value);
  235. }
  236. float range = maxTime - minTime;
  237. float timeScale = range < 0.0001f ? 1 : 1 / range;
  238. range = maxVal - minVal;
  239. float valScale = range < 1 ? 1 : 1 / range;
  240. float valOffset = 0;
  241. if (range < 1)
  242. {
  243. if (minVal > 0 && minVal + range <= 1)
  244. valOffset = minVal;
  245. else
  246. valOffset = 1 - range;
  247. }
  248. for (int i = 0; i < keys.Length; ++i)
  249. {
  250. if (normalizeX)
  251. keys[i].time = (keys[i].time - minTime) * timeScale;
  252. if (normalizeY)
  253. keys[i].value = ((keys[i].value - minVal) * valScale) + valOffset;
  254. }
  255. curve.keys = keys;
  256. }
  257. return curve;
  258. }
  259. }
  260. }