// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using UltimateXR.Core; using UltimateXR.Core.Math; using UltimateXR.Extensions.System; using UltimateXR.Extensions.System.Collections; using UltimateXR.Extensions.Unity.Math; using UltimateXR.Extensions.Unity.Render; using UnityEngine; using Object = UnityEngine.Object; namespace UltimateXR.Extensions.Unity { using UnityObject = Object; /// /// extensions. /// public static class TransformExt { #region Public Types & Data /// /// Mirroring options for . /// public enum MirrorType { /// /// Mirror X and Y vectors. Z Vector will be computed the using cross product. /// MirrorXY, /// /// Mirror X and Z vectors. Y Vector will be computed the using cross product. /// MirrorXZ, /// /// Mirror Y and Z Vectors. X Vector will be computed using the cross product. /// MirrorYZ } #endregion #region Public Methods /// /// Destroys all children using Destroy(). /// /// Transform to destroy all children of public static void DestroyAllChildren(this Transform transform) { for (int i = 0; i < transform.childCount; ++i) { UnityObject.Destroy(transform.GetChild(i).gameObject); } } /// /// Destroys all children GameObjects using Destroy() that have a given component type. /// /// Transform to destroy all children of /// Also delete children with inactive components? public static void DestroyAllChildren(this Transform transform, bool includeInactive = true) where T : Component { T[] children = transform.GetComponentsInChildren(includeInactive); foreach (T t in children) { if (t != null) { UnityObject.Destroy(t.gameObject); } } } /// /// Destroys all children using DestroyImmediate(). /// /// Transform to destroy all children of public static void DestroyImmediateAllChildren(this Transform transform) { for (int i = 0; i < transform.childCount; ++i) { UnityObject.DestroyImmediate(transform.GetChild(i).gameObject); } } /// /// Destroys all children GameObjects using DestroyImmediate() that have a given component type. /// /// Transform to destroy all children of /// Also delete children with inactive components? public static void DestroyImmediateAllChildren(this Transform transform, bool includeInactive = true) where T : Component { T[] children = transform.GetComponentsInChildren(includeInactive); foreach (T t in children) { if (t != null) { UnityObject.DestroyImmediate(t.gameObject); } } } /// /// Assigns a transform the same position and rotation from another. /// /// Transform to change /// Target to set the position and rotation of public static void SetPositionAndRotation(this Transform self, Transform target) { self.SetPositionAndRotation(target.position, target.rotation); } #if !(UNITY_2021_3_11 || UNITY_2022_2 || UNITY_2023_1) /// /// Sets the local position and local rotation in one go. /// /// Transform to change /// New local position /// New local rotation public static void SetLocalPositionAndRotation(this Transform self, Vector3 localPosition, Quaternion localRotation) { self.localPosition = localPosition; self.localRotation = localRotation; } #endif /// /// Sets the localPosition.x value of a given . /// /// Transform to set the localPosition.x of /// New x value public static void SetLocalPositionX(this Transform self, float x) { Vector3 localPosition = self.localPosition; localPosition.x = x; self.localPosition = localPosition; } /// /// Sets the localPosition.y value of a given . /// /// Transform to set the localPosition.y of /// New y value public static void SetLocalPositionY(this Transform self, float y) { Vector3 localPosition = self.localPosition; localPosition.y = y; self.localPosition = localPosition; } /// /// Sets the localPosition.z value of a given . /// /// Transform to set the localPosition.z of /// New z value public static void SetLocalPositionZ(this Transform self, float z) { Vector3 localPosition = self.localPosition; localPosition.z = z; self.localPosition = localPosition; } /// /// Adds x, y and z values to the localPosition of a given . /// /// Transform whose localPosition to add the values to /// Value to add to localPosition.x /// Value to add to localPosition.y /// Value to add to localPosition.z public static void IncreaseLocalPosition(this Transform self, float x, float y, float z) { Vector3 localPosition = self.localPosition; localPosition.x += x; localPosition.y += y; localPosition.z += z; self.localPosition = localPosition; } /// /// Sets the position.x value of a . /// /// Transform whose position.x value to set /// New x value public static void SetPositionX(this Transform self, float x) { Vector3 position = self.position; position.x = x; self.position = position; } /// /// Sets the position.y value of a . /// /// Transform whose position.y value to set /// New y value public static void SetPositionY(this Transform self, float y) { Vector3 position = self.position; position.y = y; self.position = position; } /// /// Sets the position.z value of a . /// /// Transform whose position.z value to set /// New z value public static void SetPositionZ(this Transform self, float z) { Vector3 position = self.position; position.z = z; self.position = position; } /// /// Adds x, y and z values to the position of a given . /// /// Transform whose position to add the values to /// Value to add to position.x /// Value to add to position.y /// Value to add to position.z public static void IncreasePosition(this Transform self, float x, float y, float z) { Vector3 position = self.position; position.x += x; position.y += y; position.z += z; self.position = position; } /// /// Gets a vector result of adding a Transform's right, up and forward vectors scaled by the x, y and z values of a /// vector. /// /// Transform whose axes will be used /// Vector with the x, y and z scale factors /// Vector result of applying the scale factors on the Transform axes and adding them together public static Vector3 GetScaledVector(this Transform self, Vector3 v) { return GetScaledVector(self, v.x, v.y, v.z); } /// /// Gets a vector result of adding a Transform's right, up and forward vectors scaled by x, y and z values /// respectively. /// /// Transform whose axes will be used /// Scale factor applied to the Transform's right vector /// Scale factor applied to the Transform's up vector /// Scale factor applied to the Transform's forward vector /// Vector result of applying the scale values on the Transform axes and adding them together public static Vector3 GetScaledVector(this Transform self, float x, float y, float z) { return self.right * x + self.up * y + self.forward * z; } /// /// Gets a vector representing the axis from a that has the smallest angle to a given /// world-space vector. /// The returned vector may be any of the three positive or negative axes in local space of the /// . /// /// Transform whose axes to compare to the given vector /// World-space vector to compare /// A local-space vector representing the positive or negative axis with the smallest angle to the vector public static Vector3 GetClosestLocalAxis(this Transform self, Vector3 vector) { return self.InverseTransformDirection(vector).normalized.GetClosestAxis(); } /// /// Gets a vector representing the axis from a that has the smallest angle to a given /// world-space vector. /// The returned vector may be any of the three positive or negative axes in world space of the /// . /// /// Transform whose axes to compare to the given vector /// World-space vector to compare /// A world-space vector representing the positive or negative axis with the smallest angle to the vector public static Vector3 GetClosestAxis(this Transform self, Vector3 vector) { return self.TransformDirection(self.InverseTransformDirection(vector).normalized.GetClosestAxis()); } /// /// Constraints the given transform position to the volume specified by a box collider. /// /// The transform to constrain /// The volume that the position will be constrained to public static void ClampPositionToBox(this Transform self, BoxCollider box) { self.position = self.position.ClampToBox(box); } /// /// Aligns all children of a given component on an axis. /// /// Transform whose children to align /// Pivot separation between the different children /// Axis where the children will be aligned /// Space to use for the alignment axis /// Transform is null public static void AlignChildrenOnAxis(this Transform transform, float padding, Vector3 axis, Space space = Space.World) { if (transform == null) { throw new ArgumentNullException(nameof(transform)); } Vector3 paddingVector = padding * axis; Vector3 centerPos = space == Space.Self ? Vector3.zero : transform.position; Vector3 firstPos = centerPos - 0.5f * (transform.childCount - 1) * paddingVector; for (int i = 0; i < transform.childCount; ++i) { Vector3 pos = firstPos + i * paddingVector; if (space == Space.Self) { transform.GetChild(i).localPosition = pos; } else { transform.GetChild(i).position = pos; } } } /// /// Aligns a set of transforms on an axis. /// /// Transform whose children to align /// Pivot separation between the different children /// Axis where the children will be aligned /// Space to use for the alignment axis /// Transform is null public static void AlignOnAxis(this IEnumerable transforms, float padding, Vector3 axis, Space space = Space.World) { if (transforms == null) { throw new ArgumentNullException(nameof(transforms)); } Transform[] tArray = transforms.ToArray(); Vector3 paddingVector = padding * axis; Vector3 pivotPos = space == Space.Self ? tArray[0].localPosition : tArray[0].position; Vector3 firstPos = pivotPos - 0.5f * (tArray.Length - 1) * paddingVector; for (int i = 0; i < tArray.Length; ++i) { Vector3 pos = firstPos + i * paddingVector; if (space == Space.Self) { tArray[i].localPosition = pos; } else { tArray[i].position = pos; } } } /// /// Applies a mirroring to a transform. /// /// Transform to apply mirroring to /// Mirror /// Mirror axis /// Which vectors to mirror. See /// Whether to rotate the object /// Whether to translate the object public static void ApplyMirroring(this Transform self, Transform mirror, UxrAxis mirrorAxis, MirrorType mirrorType, bool rotate = true, bool reposition = true) { ApplyMirroring(self, mirror.position, mirror.TransformDirection(mirrorAxis), mirrorType, rotate, reposition); } /// /// Applies a mirroring to a transform. /// /// Transform to apply mirroring to /// Mirror position /// Mirror normal /// Which vectors to mirror. See /// Whether to rotate the object /// Whether to translate the object public static void ApplyMirroring(this Transform self, Vector3 mirrorPosition, Vector3 mirrorNormal, MirrorType mirrorType, bool rotate = true, bool reposition = true) { if (rotate) { Vector3 right = Vector3.Reflect(self.right, mirrorNormal); Vector3 up = Vector3.Reflect(self.up, mirrorNormal); Vector3 forward = Vector3.Reflect(self.forward, mirrorNormal); if (mirrorType == MirrorType.MirrorXY) { self.rotation = Quaternion.LookRotation(Vector3.Cross(right, up), up); } else if (mirrorType == MirrorType.MirrorXZ) { self.rotation = Quaternion.LookRotation(forward, Vector3.Cross(forward, right)); } else if (mirrorType == MirrorType.MirrorYZ) { self.rotation = Quaternion.LookRotation(forward, up); } } if (reposition) { Vector3 projection = new Plane(mirrorNormal, mirrorPosition).ClosestPointOnPlane(self.position); self.position += (projection - self.position) * 2; } } /// /// Applies to , the transformation required to make align with /// . /// /// Transform to apply the alignment to /// Source reference that will try to match /// Target reference /// /// Whether to apply rotation or translation transformations. By default it will rotate and /// translate. /// /// Optional interpolation value public static void ApplyAlignment(this Transform self, Transform sourceAlign, Transform targetAlign, UxrTransformations transformations = UxrTransformations.All, float t = 1.0f) { if (transformations.HasFlag(UxrTransformations.Rotate)) { ApplyAlignment(self, sourceAlign.rotation, targetAlign.rotation, t); } if (transformations.HasFlag(UxrTransformations.Translate)) { self.position += (targetAlign.position - sourceAlign.position) * t; } } /// /// Applies to the transformation to make a transform defined by /// and move and rotate to /// and . /// /// Transform to apply the alignment to /// Source position that will try to match /// Source rotation that will try to match /// Target position /// Target rotation /// /// Whether to apply rotation or translation transformations. By default it will rotate and /// translate. /// /// Optional interpolation value [0.0, 1.0] public static void ApplyAlignment(this Transform self, Vector3 sourcePosition, Quaternion sourceRotation, Vector3 targetPosition, Quaternion targetRotation, UxrTransformations transformations = UxrTransformations.All, float t = 1.0f) { if (transformations.HasFlag(UxrTransformations.Rotate)) { Vector3 sourceLocalPos = self.InverseTransformPoint(sourcePosition); ApplyAlignment(self, sourceRotation, targetRotation, t); sourcePosition = self.TransformPoint(sourceLocalPos); } if (transformations.HasFlag(UxrTransformations.Translate)) { self.position += (targetPosition - sourcePosition) * t; } } /// /// Applies to the transformation to make a rotation defined by /// rotate towards . /// /// Transform to apply the alignment to /// Source rotation that will try to match /// Target rotation to match /// Optional interpolation value [0.0, 1.0] public static void ApplyAlignment(this Transform self, Quaternion sourceRotation, Quaternion targetRotation, float t = 1.0f) { Quaternion selfRotation = self.rotation; Quaternion rotation = Quaternion.RotateTowards(sourceRotation, targetRotation, 180.0f); Quaternion relative = Quaternion.Inverse(sourceRotation) * selfRotation; self.rotation = Quaternion.Slerp(selfRotation, rotation * relative, t); } /// /// Moves and rotates so that a transform defined by /// and gets aligned to /// and . /// /// Position to align /// Rotation to align /// /// Reference position to align to /// /// /// Reference rotation to align to /// /// Target position /// Target rotation /// /// Whether to apply rotation or translation transformations. By default it will rotate and /// translate. /// /// Optional interpolation value [0.0, 1.0] public static void ApplyAlignment(ref Vector3 position, ref Quaternion rotation, Vector3 sourcePosition, Quaternion sourceRotation, Vector3 targetPosition, Quaternion targetRotation, UxrTransformations transformations = UxrTransformations.All, float t = 1.0f) { Matrix4x4 matrix = Matrix4x4.TRS(position, rotation, Vector3.one); Vector3 relativePos = matrix.inverse.MultiplyPoint(sourcePosition); if (transformations.HasFlag(UxrTransformations.Rotate)) { Quaternion rotationTowards = Quaternion.RotateTowards(sourceRotation, targetRotation, 180.0f); Quaternion relative = Quaternion.Inverse(sourceRotation) * rotation; rotation = Quaternion.Slerp(rotation, rotationTowards * relative, t); Matrix4x4 newMatrix = Matrix4x4.TRS(position, rotation, Vector3.one); sourcePosition = newMatrix.MultiplyPoint(relativePos); } if (transformations.HasFlag(UxrTransformations.Translate)) { position += (targetPosition - sourcePosition) * t; } } /// /// Applies an interpolation between two other transforms. /// /// Transform to apply the interpolation to /// Start transform /// Finish transform /// Interpolation value public static void ApplyInterpolation(Transform source, Transform a, Transform b, float t) { source.SetPositionAndRotation(Vector3.Lerp(a.position, b.position, t), Quaternion.Slerp(a.rotation, b.rotation, t)); } /// /// Checks if a given transform has a given parent in its upwards hierarchy, or if it is the transform itself. /// /// Caller /// Transform to check if it is present /// True if present, false if not public static bool HasParent(this Transform self, Transform parent) { if (self == null) { return false; } if (self == parent) { return true; } if (self.parent != null) { return HasParent(self.parent, parent); } return false; } /// /// Checks if a given transform has a given child in its hierarchy or if the transform is the child itself. /// /// Caller /// Transform to check if it is present /// True if present, false if not public static bool HasChild(this Transform current, Transform child) { if (current == null) { return false; } if (current == child) { return true; } for (int i = 0; i < current.transform.childCount; ++i) { if (current.GetChild(i) == child) { return true; } if (HasChild(current.GetChild(i), child)) { return true; } } return false; } /// /// Gets all the children recursively under a given . /// The list will not contain the source itself. /// /// Transform to get all children of /// A list with all the transforms found below the hierarchy public static void GetAllChildren(this Transform transform, ref List transforms) { for (int i = 0; i < transform.childCount; ++i) { Transform child = transform.GetChild(i); transforms.Add(child); child.GetAllChildren(ref transforms); } } /// /// From a set of transforms, returns which one of them is a common root of all if any. /// The transform must be in the list itself. /// /// Variable number of transforms to check /// /// Returns which transform from all the ones passed as parameters is a common root from all. If no one is a /// common root it will return null. /// public static Transform GetCommonRootTransformFromSet(params Transform[] transforms) { Transform commonRoot = null; for (int i = 0; i < transforms.Length; i++) { if (i == 0) { commonRoot = transforms[i]; } else { if (commonRoot == null || (transforms[i] != commonRoot && HasParent(transforms[i], commonRoot) == false)) { bool found = true; for (int j = 0; j < i - 1; j++) { if (transforms[i] != transforms[j] && HasParent(transforms[j], transforms[i]) == false) { found = false; } } commonRoot = found ? transforms[i] : null; } } } return commonRoot; } /// /// Gets all the transforms that don't have any children. /// /// /// Transform where to start looking for. This method will only traverse the transform itself and its sub-hierarchy /// /// A list where all the found transforms without children will be appended public static void GetTransformsWithoutChildren(this Transform transform, ref List transforms) { if (transform.childCount == 0) { transforms.Add(transform); } else { for (int i = 0; i < transform.childCount; i++) { GetTransformsWithoutChildren(transform.GetChild(i), ref transforms); } } } /// /// Gets the first non-null transform from a set of transforms. /// /// Variable number of transforms to check /// Returns the first non-null transform or null if none was found public static Transform GetFirstNonNullTransformFromSet(params Transform[] transforms) { foreach (Transform transform in transforms) { if (transform != null) { return transform; } } return null; } /// /// Gets the n-th non-null transform from a set of transforms. /// /// How many non-null elements to skip until the next non-null is returned /// Variable number of transforms to check /// Returns the i-th non-null transform or null if none was found public static Transform GetNthNonNullTransformFromSet(int n, params Transform[] transforms) { int found = 0; foreach (Transform transform in transforms) { if (transform != null) { if (found == n) { return transform; } found++; } } return null; } /// /// Tries to find an object by its name in the given or any of its children recursively /// /// Transform whose hierarchy to search /// Name of the transform to find /// Comparison rules to use /// First transform found with the given name, or null if not found public static Transform FindRecursive(this Transform self, string name, StringComparison stringComparison = StringComparison.Ordinal) { if (self == null) { return null; } if (string.Compare(self.name, name, stringComparison) == 0) { return self; } for (int i = 0; i < self.childCount; ++i) { Transform transform = self.GetChild(i).FindRecursive(name, stringComparison); if (transform != null) { return transform; } } return null; } /// /// Gets the sibling index taking scene root GameObjects into account. /// Fixes https://issuetracker.unity3d.com/issues/getsiblingindex-always-returns-zero-in-standalone-builds /// /// Transform to get sibling index of /// Sibling index, including those of root GameObjects public static int GetCorrectSiblingIndex(this Transform self) { if (self.parent == null) { if (self.gameObject.scene.name != null) { #if UNITY_EDITOR UnityEditor.SceneManagement.PrefabStage prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage(); if (prefabStage != null) { return 0; } #endif int index = self.gameObject.scene.GetRootGameObjects().IndexOf(self.gameObject); return index != -1 ? index : 0; } return 0; } return self.GetSiblingIndex(); } /// /// Gets the full GameObject path of a Transform in the hierarchy. /// /// Transform to get the full path of /// /// Optional Transform to get the path relative to. If it's not the same Transform or a Transform up in the hierarchy /// it will return the full path /// /// Full path of the GameObject public static string GetPathUnderScene(this Transform self, Transform relativeTo = null) { self.ThrowIfNull(nameof(self)); string path = self.name; while (self.parent != null && self != relativeTo) { self = self.parent; path = $"{self.name}/{path}"; } return path; } /// /// Gets a unique path name of Transform in the hierarchy. Adds a sibling index to the path to assure that siblings /// with the same name create different path names. /// /// Transform to get a unique path of /// /// Optional Transform to get the path relative to. If it's not the same Transform or a Transform up in the hierarchy /// it will return the full path /// /// Unique full path that includes sibling index information to make it different from others public static string GetUniqueScenePath(this Transform self, Transform relativeTo = null) { self.ThrowIfNull(nameof(self)); string prePath = null; if (relativeTo != null) { if (relativeTo != self) { // Recurse up prePath = self.parent.GetUniqueScenePath(relativeTo); } else { // Start relative string prePath = string.Empty; } } else { string sceneString = !string.IsNullOrEmpty(self.gameObject.scene.name) ? self.gameObject.scene.name : string.Empty; prePath = self.parent != null ? self.parent.GetUniqueScenePath() : sceneString; } return $"{prePath}/{self.GetCorrectSiblingIndex():000}-{self.name}"; } /// /// Gets the parent local to world transform matrix. /// /// Transform to get the parent matrix of /// Parent local-to-world matrix or Identity if it has no parent public static Matrix4x4 GetParentWorldMatrix(this Transform self) { return self.parent != null ? self.parent.localToWorldMatrix : Matrix4x4.identity; } /// /// Gets the parent rotation or the identity Quaternion if it doesn't exist. /// /// Transform to get the parent rotation of /// Parent rotation or Identity if it has no parent public static Quaternion GetParentRotation(this Transform self) { return self.parent != null ? self.parent.rotation : Quaternion.identity; } /// /// Gets the given position in local coordinates. If is /// null, will be returned. /// /// Transform to get the local coordinates in /// Position /// Coordinates in local space or if is null public static Vector3 GetLocalPosition(Transform transform, Vector3 position) { return transform != null ? transform.InverseTransformPoint(position) : position; } /// /// Transforms a position to world space coordinates. If is null, /// will be returned. /// /// Transform with the space of the local coordinates /// Position in local coordinates /// Coordinates in world space or if is null public static Vector3 GetWorldPosition(Transform transform, Vector3 localPosition) { return transform != null ? transform.TransformPoint(localPosition) : localPosition; } /// /// Gets the given rotation in local coordinates. If is /// null, will be returned. /// /// Transform to get the local coordinates in /// Rotation /// Rotation in local space or if is null public static Quaternion GetLocalRotation(Transform transform, Quaternion rotation) { return transform != null ? Quaternion.Inverse(transform.rotation) * rotation : rotation; } /// /// Transforms a rotation to world space. If is null, /// will be returned. /// /// Transform with the space of the local rotation /// Local rotation /// Rotation in world space or if is null public static Quaternion GetWorldRotation(Transform transform, Quaternion localRotation) { return transform != null ? transform.rotation * localRotation : localRotation; } /// /// Gets the given direction in local coordinates. If is /// null, will be returned. /// /// Transform to get the local coordinates in /// Direction /// Direction in local space or if is null public static Vector3 GetLocalDirection(Transform transform, Vector3 direction) { return transform != null ? Quaternion.Inverse(transform.rotation) * direction : direction; } /// /// Transforms a direction to world space. If is null, /// will be returned. /// /// Transform with the space of the local direction /// Direction /// Rotation in world space or if is null public static Vector3 GetWorldDirection(Transform transform, Vector3 localDirection) { return transform != null ? transform.rotation * localDirection : localDirection; } /// /// Calculates the position and rotation a would have if it were rotated around a pivot and /// axis. /// /// Transform to calculate the rotation for /// Rotation pivot /// Rotation axis /// Rotation angle in degrees /// Returns the new position /// Returns the new rotation public static void GetRotationAround(this Transform transform, Vector3 pivot, Vector3 axis, float angle, out Vector3 position, out Quaternion rotation) { rotation = Quaternion.AngleAxis(angle, axis); position = pivot + rotation * (transform.position - pivot); rotation *= transform.rotation; } /// /// Calculates the position and rotation a Transform would have if it were rotated around a pivot and /// axis. /// /// Transform position /// Transform rotation /// Rotation pivot /// Rotation axis /// Rotation angle in degrees /// Returns the new position /// Returns the new rotation public static void GetRotationAround(Vector3 position, Quaternion rotation, Vector3 pivot, Vector3 axis, float angle, out Vector3 resultPosition, out Quaternion resultRotation) { Quaternion q = Quaternion.AngleAxis(angle, axis); resultPosition = pivot + q * (position - pivot); resultRotation = q * rotation; } /// /// Computes the bounds of all MeshRenderers that hang from a parent transform. /// /// Parent Transform to get all the MeshRenderers of /// Space in which to retrieve the bounds /// Whether to include inactive MeshRenderers /// Bounds containing all MeshRenderers /// Transform is null public static Bounds CalculateBounds(this Transform self, Space space = Space.World, bool includeInactive = false) { self.ThrowIfNull(nameof(self)); Bounds result = self.GetComponentsInChildren(includeInactive).CalculateBounds(); if (result != default && space == Space.Self) { result = self.InverseTransformBounds(result); } foreach (RectTransform r in self.GetComponentsInChildren(includeInactive)) { if (result == default) { result = r.CalculateRectBounds(space); } else { result.Encapsulate(r.CalculateRectBounds(space)); } } return result; } /// /// Computes the bounds containing a 's . /// /// RectTransform to process /// Space in which to retrieve the bounds /// Bounds of the RectTransform /// Transform is null public static Bounds CalculateRectBounds(this RectTransform transform, Space space = Space.World) { if (transform == null) { throw new ArgumentNullException(nameof(transform)); } Rect rect = transform.rect; Bounds localBounds = new Bounds(rect.center, rect.size); return space == Space.Self ? localBounds : transform.TransformBounds(localBounds); } /// /// Gets the bounds of a BoxCollider in a given space. /// /// BoxCollider to get the bounds of /// Space in which to retrieve the bounds /// BoxCollider bounds /// BoxCollider is null public static Bounds CalculateBounds(this BoxCollider boxCollider, Space space = Space.World) { if (boxCollider == null) { throw new ArgumentNullException(nameof(boxCollider)); } if (space == Space.Self) { return new Bounds(boxCollider.center, boxCollider.size); } if (boxCollider.enabled) { return boxCollider.bounds; } Bounds localBounds = new Bounds(boxCollider.center, boxCollider.size); return boxCollider.transform.TransformBounds(localBounds); } /// /// Gets the bounds in a given space of all MeshRenderers in a set of Transforms. /// /// Transforms whose MeshRenderers to get the bounds of /// Space in which to retrieve the bounds /// Whether to include inactive MeshRenderers /// Bounds containing all MeshRenderers /// transforms is null public static Bounds CalculateBounds(this IEnumerable transforms, Space space = Space.World, bool includeInactive = false) { if (transforms == null) { throw new ArgumentNullException(nameof(transforms)); } Bounds bounds = default; Transform firstTransform = null; int nBounds = 0; foreach (Transform t in transforms) { Bounds b = t.CalculateBounds(Space.World, includeInactive); if (nBounds++ == 0) { firstTransform = t; bounds = b; } else { bounds.Encapsulate(b); } } if (bounds == default) { return default; } return space == Space.World ? bounds : firstTransform!.InverseTransformBounds(bounds); } /// /// Gets the bounds in the given transform's children with the largest squared length of the bounds /// vector. /// /// Transform to process all children of /// Whether to include inactive MeshRenderers /// Bounds with the largest squared length size vector /// transform is null public static Bounds GetWiderBoundsInChildren(this Transform transform, bool includeInactive = false) { if (transform == null) { throw new ArgumentNullException(nameof(transform)); } Bounds result = default; float sqrSizeMax = 0f; for (int i = 0; i < transform.childCount; ++i) { Bounds b = transform.GetChild(i).CalculateBounds(Space.World, includeInactive); float sqrSize = b.size.sqrMagnitude; if (b.size.sqrMagnitude > sqrSizeMax) { result = b; sqrSizeMax = sqrSize; } } return result; } /// /// Gets the bounds in the given transforms children with the largest squared length of the bounds /// vector. /// /// Transforms to process all children of /// Whether to include inactive MeshRenderers /// Bounds with the largest squared length size vector /// transforms is null public static Bounds GetWiderBounds(this IEnumerable transforms, bool includeInactive = false) { if (transforms == null) { throw new ArgumentNullException(nameof(transforms)); } Bounds result = default; float sqrSizeMax = 0f; foreach (Transform t in transforms) { Bounds b = t.CalculateBounds(Space.World, includeInactive); float sqrSize = b.size.sqrMagnitude; if (b.size.sqrMagnitude > sqrSizeMax) { result = b; sqrSizeMax = sqrSize; } } return result; } /// /// Transforms a given object in local space using a component. /// /// Transform applied to the bounds /// Bounds in local space /// Transformed bounds /// transform is null public static Bounds TransformBounds(this Transform transform, Bounds localBounds) { if (transform == null) { throw new ArgumentNullException(nameof(transform)); } var center = transform.TransformPoint(localBounds.center); // self the local extents' axes var extents = localBounds.extents; var axisX = transform.TransformVector(extents.x, 0, 0); var axisY = transform.TransformVector(0, extents.y, 0); var axisZ = transform.TransformVector(0, 0, extents.z); // sum their absolute value to get the world extents extents.x = Mathf.Abs(axisX.x) + Mathf.Abs(axisY.x) + Mathf.Abs(axisZ.x); extents.y = Mathf.Abs(axisX.y) + Mathf.Abs(axisY.y) + Mathf.Abs(axisZ.y); extents.z = Mathf.Abs(axisX.z) + Mathf.Abs(axisY.z) + Mathf.Abs(axisZ.z); return new Bounds { center = center, extents = extents }; } /// /// Transforms a given object in world space to the local space of a . /// /// Transform defining the local space where to move the bounds to /// Bounds in world space /// Transformed bounds /// transform is null public static Bounds InverseTransformBounds(this Transform transform, Bounds worldBounds) { if (transform == null) { throw new ArgumentNullException(nameof(transform)); } var center = transform.InverseTransformPoint(worldBounds.center); // self the local extents' axes var extents = worldBounds.extents; var axisX = transform.InverseTransformVector(extents.x, 0, 0); var axisY = transform.InverseTransformVector(0, extents.y, 0); var axisZ = transform.InverseTransformVector(0, 0, extents.z); // sum their absolute value to get the world extents extents.x = Mathf.Abs(axisX.x) + Mathf.Abs(axisY.x) + Mathf.Abs(axisZ.x); extents.y = Mathf.Abs(axisX.y) + Mathf.Abs(axisY.y) + Mathf.Abs(axisZ.y); extents.z = Mathf.Abs(axisX.z) + Mathf.Abs(axisY.z) + Mathf.Abs(axisZ.z); return new Bounds { center = center, extents = extents }; } #endregion } }