UnityUtil.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using UnityEngine;
  5. using UnityEngine.Tilemaps;
  6. namespace DunGen
  7. {
  8. public static class UnityUtil
  9. {
  10. #region Reflection
  11. public static Type ProBuilderMeshType { get; private set; }
  12. public static PropertyInfo ProBuilderPositionsProperty { get; private set; }
  13. static UnityUtil()
  14. {
  15. FindProBuilderObjectType();
  16. }
  17. public static void FindProBuilderObjectType()
  18. {
  19. if (ProBuilderMeshType != null)
  20. return;
  21. // Look through each of the loaded assemblies in our current AppDomain, looking for ProBuilder's pb_Object type
  22. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
  23. {
  24. if (assembly.FullName.Contains("ProBuilder"))
  25. {
  26. ProBuilderMeshType = assembly.GetType("UnityEngine.ProBuilder.ProBuilderMesh");
  27. if (ProBuilderMeshType != null)
  28. {
  29. ProBuilderPositionsProperty = ProBuilderMeshType.GetProperty("positions");
  30. if (ProBuilderPositionsProperty != null)
  31. break;
  32. }
  33. }
  34. }
  35. }
  36. #endregion
  37. public static T FindObjectByType<T>() where T : UnityEngine.Object
  38. {
  39. #if UNITY_2022_2_OR_NEWER
  40. return UnityEngine.Object.FindFirstObjectByType<T>();
  41. #else
  42. return UnityEngine.Object.FindObjectOfType<T>();
  43. #endif
  44. }
  45. public static T[] FindObjectsByType<T>() where T : UnityEngine.Object
  46. {
  47. #if UNITY_2022_2_OR_NEWER
  48. return UnityEngine.Object.FindObjectsByType<T>(FindObjectsSortMode.None);
  49. #else
  50. return UnityEngine.Object.FindObjectsOfType<T>();
  51. #endif
  52. }
  53. public static void Restart(this System.Diagnostics.Stopwatch stopwatch)
  54. {
  55. if (stopwatch == null)
  56. stopwatch = System.Diagnostics.Stopwatch.StartNew();
  57. else
  58. {
  59. stopwatch.Reset();
  60. stopwatch.Start();
  61. }
  62. }
  63. public static bool Contains(this Bounds bounds, Bounds other)
  64. {
  65. if (other.min.x < bounds.min.x || other.min.y < bounds.min.y || other.min.z < bounds.min.z ||
  66. other.max.x > bounds.max.x || other.max.y > bounds.max.y || other.max.z > bounds.max.z)
  67. return false;
  68. return true;
  69. }
  70. public static Bounds TransformBounds(this Transform transform, Bounds localBounds)
  71. {
  72. Vector3 transformedCenter = transform.TransformPoint(localBounds.center);
  73. Vector3 transformedSize = transform.rotation * localBounds.size;
  74. transformedSize.x = Mathf.Abs(transformedSize.x);
  75. transformedSize.y = Mathf.Abs(transformedSize.y);
  76. transformedSize.z = Mathf.Abs(transformedSize.z);
  77. return new Bounds(transformedCenter, transformedSize);
  78. }
  79. public static Bounds InverseTransformBounds(this Transform transform, Bounds worldBounds)
  80. {
  81. Vector3 transformedCenter = transform.InverseTransformPoint(worldBounds.center);
  82. Vector3 transformedSize = Quaternion.Inverse(transform.rotation) * worldBounds.size;
  83. transformedSize.x = Mathf.Abs(transformedSize.x);
  84. transformedSize.y = Mathf.Abs(transformedSize.y);
  85. transformedSize.z = Mathf.Abs(transformedSize.z);
  86. return new Bounds(transformedCenter, transformedSize);
  87. }
  88. public static void SetLayerRecursive(GameObject gameObject, int layer)
  89. {
  90. gameObject.layer = layer;
  91. for (int i = 0; i < gameObject.transform.childCount; i++)
  92. SetLayerRecursive(gameObject.transform.GetChild(i).gameObject, layer);
  93. }
  94. public static void Destroy(UnityEngine.Object obj)
  95. {
  96. //if (Application.isPlaying)
  97. //{
  98. // // Work-Around
  99. // // If we're destroying a GameObject, disable it first to avoid tile colliders from contributing to the NavMesh when generating synchronously
  100. // // since Destroy() only destroys the GameObject at the end of the frame. Are there any down-sides to using DestroyImmediate() here instead?
  101. // GameObject go = obj as GameObject;
  102. // if (go != null)
  103. // go.SetActive(false);
  104. // UnityEngine.Object.Destroy(obj);
  105. //}
  106. //else
  107. // UnityEngine.Object.DestroyImmediate(obj);
  108. UnityEngine.Object.DestroyImmediate(obj);
  109. }
  110. public static string GetUniqueName(string name, IEnumerable<string> usedNames)
  111. {
  112. if(string.IsNullOrEmpty(name))
  113. return GetUniqueName("New", usedNames);
  114. string baseName = name;
  115. int number = 0;
  116. bool hasNumber = false;
  117. int indexOfLastSeperator = name.LastIndexOf(' ');
  118. if(indexOfLastSeperator > -1)
  119. {
  120. baseName = name.Substring(0, indexOfLastSeperator);
  121. hasNumber = int.TryParse(name.Substring(indexOfLastSeperator + 1), out number);
  122. number++;
  123. }
  124. foreach(var n in usedNames)
  125. {
  126. if(n == name)
  127. {
  128. if(hasNumber)
  129. return GetUniqueName(baseName + " " + number.ToString(), usedNames);
  130. else
  131. return GetUniqueName(name + " 2", usedNames);
  132. }
  133. }
  134. return name;
  135. }
  136. public static Bounds CombineBounds(params Bounds[] bounds)
  137. {
  138. if (bounds.Length == 0)
  139. return new Bounds();
  140. else if (bounds.Length == 1)
  141. return bounds[0];
  142. Bounds combinedBounds = bounds[0];
  143. for (int i = 1; i < bounds.Length; i++)
  144. combinedBounds.Encapsulate(bounds[i]);
  145. return combinedBounds;
  146. }
  147. public static Bounds CalculateProxyBounds(GameObject prefab, Vector3 upVector)
  148. {
  149. var bounds = CalculateObjectBounds(prefab, true, DunGenSettings.Instance.BoundsCalculationsIgnoreSprites);
  150. // Since ProBuilder objects don't have a mesh until they're instantiated, we have to calculate the bounds manually
  151. if (ProBuilderMeshType != null && ProBuilderPositionsProperty != null)
  152. {
  153. foreach (var pbMesh in prefab.GetComponentsInChildren(ProBuilderMeshType))
  154. {
  155. Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
  156. Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
  157. var vertices = (IList<Vector3>)ProBuilderPositionsProperty.GetValue(pbMesh, null);
  158. foreach (var vert in vertices)
  159. {
  160. min = Vector3.Min(min, vert);
  161. max = Vector3.Max(max, vert);
  162. }
  163. Vector3 size = prefab.transform.TransformDirection(max - min);
  164. Vector3 center = prefab.transform.TransformPoint(min) + size / 2;
  165. bounds.Encapsulate(new Bounds(center, size));
  166. }
  167. }
  168. return bounds;
  169. }
  170. public static Bounds CalculateObjectBounds(GameObject obj, bool includeInactive, bool ignoreSpriteRenderers, bool ignoreTriggerColliders = true)
  171. {
  172. Bounds bounds = new Bounds();
  173. bool hasBounds = false;
  174. // We need to compress the bounds of a tilemap first or the renderer will return bounds that are too big
  175. foreach (var tilemap in obj.GetComponentsInChildren<Tilemap>(includeInactive))
  176. tilemap.CompressBounds();
  177. // Renderers
  178. foreach (var renderer in obj.GetComponentsInChildren<Renderer>(includeInactive))
  179. {
  180. if (ignoreSpriteRenderers && renderer is SpriteRenderer)
  181. continue;
  182. if (renderer is ParticleSystemRenderer)
  183. continue;
  184. if (hasBounds)
  185. bounds.Encapsulate(renderer.bounds);
  186. else
  187. bounds = renderer.bounds;
  188. hasBounds = true;
  189. }
  190. // Colliders
  191. foreach (var collider in obj.GetComponentsInChildren<Collider>(includeInactive))
  192. {
  193. // Terrain colliders report incorrect bounds when not placed in the scene
  194. if (collider is TerrainCollider)
  195. continue;
  196. if (ignoreTriggerColliders && collider.isTrigger)
  197. continue;
  198. if (hasBounds)
  199. bounds.Encapsulate(collider.bounds);
  200. else
  201. bounds = collider.bounds;
  202. hasBounds = true;
  203. }
  204. // Terrain
  205. foreach (var terrain in obj.GetComponentsInChildren<Terrain>(includeInactive))
  206. {
  207. var terrainBounds = terrain.terrainData.bounds;
  208. terrainBounds.center += terrain.gameObject.transform.position;
  209. if (hasBounds)
  210. bounds.Encapsulate(terrainBounds);
  211. else
  212. bounds = terrainBounds;
  213. hasBounds = true;
  214. }
  215. // Fix any zero or negative extents
  216. const float minExtents = 0.01f;
  217. Vector3 extents = bounds.extents;
  218. if (extents.x == 0f)
  219. extents.x = minExtents;
  220. else if (extents.x < 0f)
  221. extents.x *= -1f;
  222. if (extents.y == 0f)
  223. extents.y = minExtents;
  224. else if (extents.y < 0f)
  225. extents.y *= -1f;
  226. if (extents.z == 0f)
  227. extents.z = minExtents;
  228. else if (extents.z < 0f)
  229. extents.z *= -1f;
  230. bounds.extents = extents;
  231. return bounds;
  232. }
  233. /// <summary>
  234. /// Positions an object by aligning one of it's own sockets to the socket of another object
  235. /// </summary>
  236. /// <param name="objectA">The object to move</param>
  237. /// <param name="socketA">A socket for the object that we want to move (must be a child somewhere in the object's hierarchy)</param>
  238. /// <param name="socketB">The socket we want to attach the object to (must not be a child in the object's hierarchy)</param>
  239. public static void PositionObjectBySocket(GameObject objectA, GameObject socketA, GameObject socketB)
  240. {
  241. PositionObjectBySocket(objectA.transform, socketA.transform, socketB.transform);
  242. }
  243. /// <summary>
  244. /// Positions an object by aligning one of it's own sockets to the socket of another object
  245. /// </summary>
  246. /// <param name="objectA">The object to move</param>
  247. /// <param name="socketA">A socket for the object that we want to move (must be a child somewhere in the object's hierarchy)</param>
  248. /// <param name="socketB">The socket we want to attach the object to (must not be a child in the object's hierarchy)</param>
  249. public static void PositionObjectBySocket(Transform objectA, Transform socketA, Transform socketB)
  250. {
  251. Quaternion targetRotation = Quaternion.LookRotation(-socketB.forward, socketB.up);
  252. objectA.rotation = targetRotation * Quaternion.Inverse(Quaternion.Inverse(objectA.rotation) * socketA.rotation);
  253. Vector3 targetPosition = socketB.position;
  254. objectA.position = targetPosition - (socketA.position - objectA.position);
  255. }
  256. public static bool IsVectorAxisAligned(Vector3 direction)
  257. {
  258. float dotX = Mathf.Abs(Vector3.Dot(direction, new Vector3(1, 0, 0)));
  259. float dotY = Mathf.Abs(Vector3.Dot(direction, new Vector3(0, 1, 0)));
  260. float dotZ = Mathf.Abs(Vector3.Dot(direction, new Vector3(0, 0, 1)));
  261. const float epsilon = 0.01f;
  262. if (dotX > 1 - epsilon && dotY < epsilon && dotZ < epsilon)
  263. return true;
  264. if (dotY > 1 - epsilon && dotX < epsilon && dotZ < epsilon)
  265. return true;
  266. if (dotZ > 1 - epsilon && dotX < epsilon && dotY < epsilon)
  267. return true;
  268. return false;
  269. }
  270. public static bool AreBoundsOverlapping(Bounds boundsA, Bounds boundsB, float maxOverlap)
  271. {
  272. Vector3 overlap = CalculateBoundsOverlap(boundsA, boundsB);
  273. return Mathf.Min(overlap.x, overlap.y, overlap.z) > maxOverlap;
  274. }
  275. public static bool AreBoundsOverlappingOrOverhanging(Bounds boundsA, Bounds boundsB, AxisDirection upDirection, float maxOverlap)
  276. {
  277. Vector3 overlaps = CalculatePerAxisOverlap(boundsB, boundsA);
  278. float overlap;
  279. // Check for overlaps only along the ground plane, disregarding the up-axis
  280. // E.g. For +Y up, check for overlaps along X & Z axes
  281. switch (upDirection)
  282. {
  283. case AxisDirection.PosX:
  284. case AxisDirection.NegX:
  285. overlap = Mathf.Min(overlaps.y, overlaps.z);
  286. break;
  287. case AxisDirection.PosY:
  288. case AxisDirection.NegY:
  289. overlap = Mathf.Min(overlaps.x, overlaps.z);
  290. break;
  291. case AxisDirection.PosZ:
  292. case AxisDirection.NegZ:
  293. overlap = Mathf.Min(overlaps.x, overlaps.y);
  294. break;
  295. default:
  296. throw new NotImplementedException("AxisDirection '" + upDirection + "' is not implemented");
  297. }
  298. return overlap > maxOverlap;
  299. }
  300. public static Vector3 CalculateBoundsOverlap(Bounds boundsA, Bounds boundsB)
  301. {
  302. float overlapPx = boundsA.max.x - boundsB.min.x;
  303. float overlapNx = boundsB.max.x - boundsA.min.x;
  304. float overlapPy = boundsA.max.y - boundsB.min.y;
  305. float overlapNy = boundsB.max.y - boundsA.min.y;
  306. float overlapPz = boundsA.max.z - boundsB.min.z;
  307. float overlapNz = boundsB.max.z - boundsA.min.z;
  308. return new Vector3(Mathf.Min(overlapPx, overlapNx),
  309. Mathf.Min(overlapPy, overlapNy),
  310. Mathf.Min(overlapPz, overlapNz));
  311. }
  312. public static Vector3 GetCardinalDirection(Vector3 direction, out float magnitude)
  313. {
  314. float absX = Math.Abs(direction.x);
  315. float absY = Math.Abs(direction.y);
  316. float absZ = Math.Abs(direction.z);
  317. float dirX = direction.x / absX;
  318. float dirY = direction.y / absY;
  319. float dirZ = direction.z / absZ;
  320. if (absX > absY && absX > absZ)
  321. {
  322. magnitude = dirX;
  323. return new Vector3(dirX, 0, 0);
  324. }
  325. else if (absY > absX && absY > absZ)
  326. {
  327. magnitude = dirY;
  328. return new Vector3(0, dirY, 0);
  329. }
  330. else if (absZ > absX && absZ > absY)
  331. {
  332. magnitude = dirZ;
  333. return new Vector3(0, 0, dirZ);
  334. }
  335. else
  336. {
  337. magnitude = dirX;
  338. return new Vector3(dirX, 0, 0);
  339. }
  340. }
  341. public static Vector3 VectorAbs(Vector3 vector)
  342. {
  343. return new Vector3(Math.Abs(vector.x), Math.Abs(vector.y), Math.Abs(vector.z));
  344. }
  345. public static void SetVector3Masked(ref Vector3 input, Vector3 value, Vector3 mask)
  346. {
  347. if (mask.x != 0)
  348. input.x = value.x;
  349. if (mask.y != 0)
  350. input.y = value.y;
  351. if (mask.z != 0)
  352. input.z = value.z;
  353. }
  354. public static Vector3 ClampVector(Vector3 input, Vector3 min, Vector3 max)
  355. {
  356. return new Vector3(
  357. input.x < min.x ? min.x : input.x > max.x ? max.x : input.x,
  358. input.y < min.y ? min.y : input.y > max.y ? max.y : input.y,
  359. input.z < min.z ? min.z : input.z > max.z ? max.z : input.z
  360. );
  361. }
  362. public static Bounds CondenseBounds(Bounds bounds, IEnumerable<Doorway> doorways)
  363. {
  364. Vector3 min = bounds.center - bounds.extents;
  365. Vector3 max = bounds.center + bounds.extents;
  366. foreach(var doorway in doorways)
  367. {
  368. float magnitude;
  369. Vector3 dir = UnityUtil.GetCardinalDirection(doorway.transform.forward, out magnitude);
  370. if (magnitude < 0)
  371. SetVector3Masked(ref min, doorway.transform.position, dir);
  372. else
  373. SetVector3Masked(ref max, doorway.transform.position, dir);
  374. }
  375. Vector3 size = max - min;
  376. Vector3 center = min + (size / 2);
  377. return new Bounds(center, size);
  378. }
  379. public static IEnumerable<T> GetComponentsInParents<T>(GameObject obj, bool includeInactive = false) where T : Component
  380. {
  381. if (obj.activeSelf || includeInactive)
  382. {
  383. foreach (var comp in obj.GetComponents<T>())
  384. yield return comp;
  385. }
  386. if (obj.transform.parent != null)
  387. foreach (var comp in GetComponentsInParents<T>(obj.transform.parent.gameObject, includeInactive))
  388. yield return comp;
  389. }
  390. public static T GetComponentInParents<T>(GameObject obj, bool includeInactive = false) where T : Component
  391. {
  392. if (obj.activeSelf || includeInactive)
  393. {
  394. foreach (var comp in obj.GetComponents<T>())
  395. return comp;
  396. }
  397. if (obj.transform.parent != null)
  398. return GetComponentInParents<T>(obj.transform.parent.gameObject, includeInactive);
  399. else
  400. return null;
  401. }
  402. public static float CalculateOverlap(Bounds boundsA, Bounds boundsB)
  403. {
  404. float overlapPx = boundsA.max.x - boundsB.min.x;
  405. float overlapNx = boundsB.max.x - boundsA.min.x;
  406. float overlapPy = boundsA.max.y - boundsB.min.y;
  407. float overlapNy = boundsB.max.y - boundsA.min.y;
  408. float overlapPz = boundsA.max.z - boundsB.min.z;
  409. float overlapNz = boundsB.max.z - boundsA.min.z;
  410. return Mathf.Min(overlapPx, overlapNx, overlapPy, overlapNy, overlapPz, overlapNz);
  411. }
  412. public static Vector3 CalculatePerAxisOverlap(Bounds boundsA, Bounds boundsB)
  413. {
  414. float overlapPx = boundsA.max.x - boundsB.min.x;
  415. float overlapNx = boundsB.max.x - boundsA.min.x;
  416. float overlapPy = boundsA.max.y - boundsB.min.y;
  417. float overlapNy = boundsB.max.y - boundsA.min.y;
  418. float overlapPz = boundsA.max.z - boundsB.min.z;
  419. float overlapNz = boundsB.max.z - boundsA.min.z;
  420. return new Vector3(Mathf.Min(overlapPx, overlapNx), Mathf.Min(overlapPy, overlapNy), Mathf.Min(overlapPz, overlapNz));
  421. }
  422. }
  423. }