// --------------------------------------------------------------------------------------------------------------------
//
// 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
}
}