Files
dungeons/Assets/UltimateXR/Runtime/Scripts/Extensions/Unity/TransformExt.cs
2024-08-06 21:58:35 +02:00

1265 lines
55 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TransformExt.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
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;
/// <summary>
/// <see cref="Transform" /> extensions.
/// </summary>
public static class TransformExt
{
#region Public Types & Data
/// <summary>
/// Mirroring options for <see cref="TransformExt.ApplyMirroring" />.
/// </summary>
public enum MirrorType
{
/// <summary>
/// Mirror X and Y vectors. Z Vector will be computed the using cross product.
/// </summary>
MirrorXY,
/// <summary>
/// Mirror X and Z vectors. Y Vector will be computed the using cross product.
/// </summary>
MirrorXZ,
/// <summary>
/// Mirror Y and Z Vectors. X Vector will be computed using the cross product.
/// </summary>
MirrorYZ
}
#endregion
#region Public Methods
/// <summary>
/// Destroys all children using Destroy().
/// </summary>
/// <param name="transform">Transform to destroy all children of</param>
public static void DestroyAllChildren(this Transform transform)
{
for (int i = 0; i < transform.childCount; ++i)
{
UnityObject.Destroy(transform.GetChild(i).gameObject);
}
}
/// <summary>
/// Destroys all children GameObjects using Destroy() that have a given component type.
/// </summary>
/// <param name="transform">Transform to destroy all children of</param>
/// <param name="includeInactive">Also delete children with inactive components?</param>
public static void DestroyAllChildren<T>(this Transform transform, bool includeInactive = true)
where T : Component
{
T[] children = transform.GetComponentsInChildren<T>(includeInactive);
foreach (T t in children)
{
if (t != null)
{
UnityObject.Destroy(t.gameObject);
}
}
}
/// <summary>
/// Destroys all children using DestroyImmediate().
/// </summary>
/// <param name="transform">Transform to destroy all children of</param>
public static void DestroyImmediateAllChildren(this Transform transform)
{
for (int i = 0; i < transform.childCount; ++i)
{
UnityObject.DestroyImmediate(transform.GetChild(i).gameObject);
}
}
/// <summary>
/// Destroys all children GameObjects using DestroyImmediate() that have a given component type.
/// </summary>
/// <param name="transform">Transform to destroy all children of</param>
/// <param name="includeInactive">Also delete children with inactive components?</param>
public static void DestroyImmediateAllChildren<T>(this Transform transform, bool includeInactive = true)
where T : Component
{
T[] children = transform.GetComponentsInChildren<T>(includeInactive);
foreach (T t in children)
{
if (t != null)
{
UnityObject.DestroyImmediate(t.gameObject);
}
}
}
/// <summary>
/// Assigns a transform the same position and rotation from another.
/// </summary>
/// <param name="self">Transform to change</param>
/// <param name="target">Target to set the position and rotation of</param>
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)
/// <summary>
/// Sets the local position and local rotation in one go.
/// </summary>
/// <param name="self">Transform to change</param>
/// <param name="localPosition">New local position</param>
/// <param name="localRotation">New local rotation</param>
public static void SetLocalPositionAndRotation(this Transform self, Vector3 localPosition, Quaternion localRotation)
{
self.localPosition = localPosition;
self.localRotation = localRotation;
}
#endif
/// <summary>
/// Sets the localPosition.x value of a given <see cref="Transform" />.
/// </summary>
/// <param name="self">Transform to set the localPosition.x of</param>
/// <param name="x">New x value</param>
public static void SetLocalPositionX(this Transform self, float x)
{
Vector3 localPosition = self.localPosition;
localPosition.x = x;
self.localPosition = localPosition;
}
/// <summary>
/// Sets the localPosition.y value of a given <see cref="Transform" />.
/// </summary>
/// <param name="self">Transform to set the localPosition.y of</param>
/// <param name="y">New y value</param>
public static void SetLocalPositionY(this Transform self, float y)
{
Vector3 localPosition = self.localPosition;
localPosition.y = y;
self.localPosition = localPosition;
}
/// <summary>
/// Sets the localPosition.z value of a given <see cref="Transform" />.
/// </summary>
/// <param name="self">Transform to set the localPosition.z of</param>
/// <param name="x">New z value</param>
public static void SetLocalPositionZ(this Transform self, float z)
{
Vector3 localPosition = self.localPosition;
localPosition.z = z;
self.localPosition = localPosition;
}
/// <summary>
/// Adds x, y and z values to the localPosition of a given <see cref="Transform" />.
/// </summary>
/// <param name="self">Transform whose localPosition to add the values to</param>
/// <param name="x">Value to add to localPosition.x</param>
/// <param name="y">Value to add to localPosition.y</param>
/// <param name="z">Value to add to localPosition.z</param>
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;
}
/// <summary>
/// Sets the position.x value of a <see cref="Transform" />.
/// </summary>
/// <param name="self">Transform whose position.x value to set</param>
/// <param name="x">New x value</param>
public static void SetPositionX(this Transform self, float x)
{
Vector3 position = self.position;
position.x = x;
self.position = position;
}
/// <summary>
/// Sets the position.y value of a <see cref="Transform" />.
/// </summary>
/// <param name="self">Transform whose position.y value to set</param>
/// <param name="x">New y value</param>
public static void SetPositionY(this Transform self, float y)
{
Vector3 position = self.position;
position.y = y;
self.position = position;
}
/// <summary>
/// Sets the position.z value of a <see cref="Transform" />.
/// </summary>
/// <param name="self">Transform whose position.z value to set</param>
/// <param name="x">New z value</param>
public static void SetPositionZ(this Transform self, float z)
{
Vector3 position = self.position;
position.z = z;
self.position = position;
}
/// <summary>
/// Adds x, y and z values to the position of a given <see cref="Transform" />.
/// </summary>
/// <param name="self">Transform whose position to add the values to</param>
/// <param name="x">Value to add to position.x</param>
/// <param name="y">Value to add to position.y</param>
/// <param name="z">Value to add to position.z</param>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="self">Transform whose axes will be used</param>
/// <param name="v">Vector with the x, y and z scale factors</param>
/// <returns>Vector result of applying the scale factors on the Transform axes and adding them together</returns>
public static Vector3 GetScaledVector(this Transform self, Vector3 v)
{
return GetScaledVector(self, v.x, v.y, v.z);
}
/// <summary>
/// Gets a vector result of adding a Transform's right, up and forward vectors scaled by x, y and z values
/// respectively.
/// </summary>
/// <param name="self">Transform whose axes will be used</param>
/// <param name="x">Scale factor applied to the Transform's right vector</param>
/// <param name="y">Scale factor applied to the Transform's up vector</param>
/// <param name="z">Scale factor applied to the Transform's forward vector</param>
/// <returns>Vector result of applying the scale values on the Transform axes and adding them together</returns>
public static Vector3 GetScaledVector(this Transform self, float x, float y, float z)
{
return self.right * x + self.up * y + self.forward * z;
}
/// <summary>
/// Gets a vector representing the axis from a <see cref="Transform" /> 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
/// <see cref="Transform" />.
/// </summary>
/// <param name="self">Transform whose axes to compare to the given vector</param>
/// <param name="vector">World-space vector to compare</param>
/// <returns>A local-space vector representing the positive or negative axis with the smallest angle to the vector</returns>
public static Vector3 GetClosestLocalAxis(this Transform self, Vector3 vector)
{
return self.InverseTransformDirection(vector).normalized.GetClosestAxis();
}
/// <summary>
/// Gets a vector representing the axis from a <see cref="Transform" /> 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
/// <see cref="Transform" />.
/// </summary>
/// <param name="self">Transform whose axes to compare to the given vector</param>
/// <param name="vector">World-space vector to compare</param>
/// <returns>A world-space vector representing the positive or negative axis with the smallest angle to the vector</returns>
public static Vector3 GetClosestAxis(this Transform self, Vector3 vector)
{
return self.TransformDirection(self.InverseTransformDirection(vector).normalized.GetClosestAxis());
}
/// <summary>
/// Constraints the given transform position to the volume specified by a box collider.
/// </summary>
/// <param name="self">The transform to constrain</param>
/// <param name="box">The volume that the position will be constrained to</param>
public static void ClampPositionToBox(this Transform self, BoxCollider box)
{
self.position = self.position.ClampToBox(box);
}
/// <summary>
/// Aligns all children of a given component on an axis.
/// </summary>
/// <param name="transform">Transform whose children to align</param>
/// <param name="padding">Pivot separation between the different children</param>
/// <param name="axis">Axis where the children will be aligned</param>
/// <param name="space">Space to use for the alignment axis</param>
/// <exception cref="System.ArgumentNullException">Transform is null</exception>
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;
}
}
}
/// <summary>
/// Aligns a set of transforms on an axis.
/// </summary>
/// <param name="transforms">Transform whose children to align</param>
/// <param name="padding">Pivot separation between the different children</param>
/// <param name="axis">Axis where the children will be aligned</param>
/// <param name="space">Space to use for the alignment axis</param>
/// <exception cref="System.ArgumentNullException">Transform is null</exception>
public static void AlignOnAxis(this IEnumerable<Transform> 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;
}
}
}
/// <summary>
/// Applies a mirroring to a transform.
/// </summary>
/// <param name="self">Transform to apply mirroring to</param>
/// <param name="mirror">Mirror</param>
/// <param name="mirrorAxis">Mirror axis</param>
/// <param name="mirrorType">Which vectors to mirror. See <see cref="MirrorType" /></param>
/// <param name="rotate">Whether to rotate the object</param>
/// <param name="reposition">Whether to translate the object</param>
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);
}
/// <summary>
/// Applies a mirroring to a transform.
/// </summary>
/// <param name="self">Transform to apply mirroring to</param>
/// <param name="mirrorPosition">Mirror position</param>
/// <param name="mirrorNormal">Mirror normal</param>
/// <param name="mirrorType">Which vectors to mirror. See <see cref="MirrorType" /></param>
/// <param name="rotate">Whether to rotate the object</param>
/// <param name="reposition">Whether to translate the object</param>
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;
}
}
/// <summary>
/// Applies to <paramref name="self" />, the transformation required to make <paramref name="sourceAlign" /> align with
/// <paramref name="targetAlign" />.
/// </summary>
/// <param name="self">Transform to apply the alignment to</param>
/// <param name="sourceAlign">Source reference that will try to match <paramref name="targetAlign" /></param>
/// <param name="targetAlign">Target reference</param>
/// <param name="transformations">
/// Whether to apply rotation or translation transformations. By default it will rotate and
/// translate.
/// </param>
/// <param name="t">Optional interpolation value</param>
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;
}
}
/// <summary>
/// Applies to <paramref name="self" /> the transformation to make a transform defined by
/// <paramref name="sourcePosition" /> and <paramref name="sourceRotation" /> move and rotate to
/// <paramref name="targetPosition" /> and <paramref name="targetRotation" />.
/// </summary>
/// <param name="self">Transform to apply the alignment to</param>
/// <param name="sourcePosition">Source position that will try to match <paramref name="targetPosition" /></param>
/// <param name="sourceRotation">Source rotation that will try to match <paramref name="targetRotation" /></param>
/// <param name="targetPosition">Target position</param>
/// <param name="targetRotation">Target rotation</param>
/// <param name="transformations">
/// Whether to apply rotation or translation transformations. By default it will rotate and
/// translate.
/// </param>
/// <param name="t">Optional interpolation value [0.0, 1.0]</param>
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;
}
}
/// <summary>
/// Applies to <paramref name="self" /> the transformation to make a rotation defined by
/// <paramref name="sourceRotation" /> rotate towards <paramref name="targetRotation" />.
/// </summary>
/// <param name="self">Transform to apply the alignment to</param>
/// <param name="sourceRotation">Source rotation that will try to match <paramref name="targetRotation" /></param>
/// <param name="targetRotation">Target rotation to match</param>
/// <param name="t">Optional interpolation value [0.0, 1.0]</param>
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);
}
/// <summary>
/// Moves <paramref name="position" /> and rotates <paramref name="rotation" /> so that a transform defined by
/// <paramref name="sourcePosition" /> and <paramref name="sourceRotation" /> gets aligned to
/// <paramref name="targetPosition" /> and <paramref name="targetRotation" />.
/// </summary>
/// <param name="position">Position to align</param>
/// <param name="rotation">Rotation to align</param>
/// <param name="sourcePosition">
/// Reference position to align to <paramref name="targetPosition" />
/// </param>
/// <param name="sourceRotation">
/// Reference rotation to align to <paramref name="targetRotation" />
/// </param>
/// <param name="targetPosition">Target position</param>
/// <param name="targetRotation">Target rotation</param>
/// <param name="transformations">
/// Whether to apply rotation or translation transformations. By default it will rotate and
/// translate.
/// </param>
/// <param name="t">Optional interpolation value [0.0, 1.0]</param>
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;
}
}
/// <summary>
/// Applies an interpolation between two other transforms.
/// </summary>
/// <param name="source">Transform to apply the interpolation to</param>
/// <param name="a">Start transform</param>
/// <param name="b">Finish transform</param>
/// <param name="t">Interpolation value</param>
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));
}
/// <summary>
/// Checks if a given transform has a given parent in its upwards hierarchy, or if it is the transform itself.
/// </summary>
/// <param name="self">Caller</param>
/// <param name="parent">Transform to check if it is present</param>
/// <returns>True if present, false if not</returns>
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;
}
/// <summary>
/// Checks if a given transform has a given child in its hierarchy or if the transform is the child itself.
/// </summary>
/// <param name="current">Caller</param>
/// <param name="child">Transform to check if it is present</param>
/// <returns>True if present, false if not</returns>
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;
}
/// <summary>
/// Gets all the children recursively under a given <see cref="Transform" />.
/// The list will not contain the source <see cref="transform" /> itself.
/// </summary>
/// <param name="transform">Transform to get all children of</param>
/// <param name="transforms">A list with all the transforms found below the hierarchy</param>
public static void GetAllChildren(this Transform transform, ref List<Transform> transforms)
{
for (int i = 0; i < transform.childCount; ++i)
{
Transform child = transform.GetChild(i);
transforms.Add(child);
child.GetAllChildren(ref transforms);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="transforms">Variable number of transforms to check</param>
/// <returns>
/// 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.
/// </returns>
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;
}
/// <summary>
/// Gets all the transforms that don't have any children.
/// </summary>
/// <param name="transform">
/// Transform where to start looking for. This method will only traverse the transform itself and its sub-hierarchy
/// </param>
/// <param name="transforms">A list where all the found transforms without children will be appended</param>
public static void GetTransformsWithoutChildren(this Transform transform, ref List<Transform> transforms)
{
if (transform.childCount == 0)
{
transforms.Add(transform);
}
else
{
for (int i = 0; i < transform.childCount; i++)
{
GetTransformsWithoutChildren(transform.GetChild(i), ref transforms);
}
}
}
/// <summary>
/// Gets the first non-null transform from a set of transforms.
/// </summary>
/// <param name="transforms">Variable number of transforms to check</param>
/// <returns>Returns the first non-null transform or null if none was found</returns>
public static Transform GetFirstNonNullTransformFromSet(params Transform[] transforms)
{
foreach (Transform transform in transforms)
{
if (transform != null)
{
return transform;
}
}
return null;
}
/// <summary>
/// Gets the n-th non-null transform from a set of transforms.
/// </summary>
/// <param name="n">How many non-null elements to skip until the next non-null is returned</param>
/// <param name="transforms">Variable number of transforms to check</param>
/// <returns>Returns the i-th non-null transform or null if none was found</returns>
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;
}
/// <summary>
/// Tries to find an object by its name in the given <see cref="Transform" /> or any of its children recursively
/// </summary>
/// <param name="self">Transform whose hierarchy to search</param>
/// <param name="name">Name of the transform to find</param>
/// <param name="stringComparison">Comparison rules to use</param>
/// <returns>First transform found with the given name, or null if not found</returns>
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;
}
/// <summary>
/// Gets the sibling index taking scene root GameObjects into account.
/// Fixes https://issuetracker.unity3d.com/issues/getsiblingindex-always-returns-zero-in-standalone-builds
/// </summary>
/// <param name="self">Transform to get sibling index of</param>
/// <returns>Sibling index, including those of root GameObjects</returns>
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();
}
/// <summary>
/// Gets the full GameObject path of a Transform in the hierarchy.
/// </summary>
/// <param name="self">Transform to get the full path of</param>
/// <param name="relativeTo">
/// 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
/// </param>
/// <returns>Full path of the GameObject</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="self">Transform to get a unique path of</param>
/// <param name="relativeTo">
/// 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
/// </param>
/// <returns>Unique full path that includes sibling index information to make it different from others</returns>
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}";
}
/// <summary>
/// Gets the parent local to world transform matrix.
/// </summary>
/// <param name="self">Transform to get the parent matrix of</param>
/// <returns>Parent local-to-world matrix or Identity if it has no parent</returns>
public static Matrix4x4 GetParentWorldMatrix(this Transform self)
{
return self.parent != null ? self.parent.localToWorldMatrix : Matrix4x4.identity;
}
/// <summary>
/// Gets the parent rotation or the identity Quaternion if it doesn't exist.
/// </summary>
/// <param name="self">Transform to get the parent rotation of</param>
/// <returns>Parent rotation or Identity if it has no parent</returns>
public static Quaternion GetParentRotation(this Transform self)
{
return self.parent != null ? self.parent.rotation : Quaternion.identity;
}
/// <summary>
/// Gets the given position in <paramref name="transform" /> local coordinates. If <paramref name="transform" /> is
/// null, <paramref name="position" /> will be returned.
/// </summary>
/// <param name="transform">Transform to get the local coordinates in</param>
/// <param name="position">Position</param>
/// <returns>Coordinates in local space or <paramref name="position" /> if <paramref name="transform" /> is null</returns>
public static Vector3 GetLocalPosition(Transform transform, Vector3 position)
{
return transform != null ? transform.InverseTransformPoint(position) : position;
}
/// <summary>
/// Transforms a position to world space coordinates. If <paramref name="transform" /> is null,
/// <paramref name="localPosition" /> will be returned.
/// </summary>
/// <param name="transform">Transform with the space of the local coordinates</param>
/// <param name="localPosition">Position in local coordinates</param>
/// <returns>Coordinates in world space or <paramref name="localPosition" /> if <paramref name="transform" /> is null</returns>
public static Vector3 GetWorldPosition(Transform transform, Vector3 localPosition)
{
return transform != null ? transform.TransformPoint(localPosition) : localPosition;
}
/// <summary>
/// Gets the given rotation in <paramref name="transform" /> local coordinates. If <paramref name="transform" /> is
/// null, <paramref name="rotation" /> will be returned.
/// </summary>
/// <param name="transform">Transform to get the local coordinates in</param>
/// <param name="rotation">Rotation</param>
/// <returns>Rotation in local space or <paramref name="rotation" /> if <paramref name="transform" /> is null</returns>
public static Quaternion GetLocalRotation(Transform transform, Quaternion rotation)
{
return transform != null ? Quaternion.Inverse(transform.rotation) * rotation : rotation;
}
/// <summary>
/// Transforms a rotation to world space. If <paramref name="transform" /> is null, <paramref name="localRotation" />
/// will be returned.
/// </summary>
/// <param name="transform">Transform with the space of the local rotation</param>
/// <param name="localRotation">Local rotation</param>
/// <returns>Rotation in world space or <paramref name="localRotation" /> if <paramref name="transform" /> is null</returns>
public static Quaternion GetWorldRotation(Transform transform, Quaternion localRotation)
{
return transform != null ? transform.rotation * localRotation : localRotation;
}
/// <summary>
/// Gets the given direction in <paramref name="transform" /> local coordinates. If <paramref name="transform" /> is
/// null, <paramref name="direction" /> will be returned.
/// </summary>
/// <param name="transform">Transform to get the local coordinates in</param>
/// <param name="direction">Direction</param>
/// <returns>Direction in local space or <paramref name="direction" /> if <paramref name="transform" /> is null</returns>
public static Vector3 GetLocalDirection(Transform transform, Vector3 direction)
{
return transform != null ? Quaternion.Inverse(transform.rotation) * direction : direction;
}
/// <summary>
/// Transforms a direction to world space. If <paramref name="transform" /> is null, <paramref name="localDirection" />
/// will be returned.
/// </summary>
/// <param name="transform">Transform with the space of the local direction</param>
/// <param name="localDirection">Direction</param>
/// <returns>Rotation in world space or <paramref name="localDirection" /> if <paramref name="transform" /> is null</returns>
public static Vector3 GetWorldDirection(Transform transform, Vector3 localDirection)
{
return transform != null ? transform.rotation * localDirection : localDirection;
}
/// <summary>
/// Calculates the position and rotation a <see cref="Transform" /> would have if it were rotated around a pivot and
/// axis.
/// </summary>
/// <param name="transform">Transform to calculate the rotation for</param>
/// <param name="pivot">Rotation pivot</param>
/// <param name="axis">Rotation axis</param>
/// <param name="angle">Rotation angle in degrees</param>
/// <param name="position">Returns the new position</param>
/// <param name="rotation">Returns the new rotation</param>
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;
}
/// <summary>
/// Calculates the position and rotation a Transform would have if it were rotated around a pivot and
/// axis.
/// </summary>
/// <param name="position">Transform position</param>
/// <param name="rotation">Transform rotation</param>
/// <param name="pivot">Rotation pivot</param>
/// <param name="axis">Rotation axis</param>
/// <param name="angle">Rotation angle in degrees</param>
/// <param name="position">Returns the new position</param>
/// <param name="rotation">Returns the new rotation</param>
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;
}
/// <summary>
/// Computes the bounds of all MeshRenderers that hang from a parent transform.
/// </summary>
/// <param name="self">Parent Transform to get all the MeshRenderers of</param>
/// <param name="space">Space in which to retrieve the bounds</param>
/// <param name="includeInactive">Whether to include inactive MeshRenderers</param>
/// <returns>Bounds containing all MeshRenderers</returns>
/// <exception cref="ArgumentNullException">Transform is null</exception>
public static Bounds CalculateBounds(this Transform self, Space space = Space.World, bool includeInactive = false)
{
self.ThrowIfNull(nameof(self));
Bounds result = self.GetComponentsInChildren<MeshRenderer>(includeInactive).CalculateBounds();
if (result != default && space == Space.Self)
{
result = self.InverseTransformBounds(result);
}
foreach (RectTransform r in self.GetComponentsInChildren<RectTransform>(includeInactive))
{
if (result == default)
{
result = r.CalculateRectBounds(space);
}
else
{
result.Encapsulate(r.CalculateRectBounds(space));
}
}
return result;
}
/// <summary>
/// Computes the bounds containing a <see cref="RectTransform" />'s <see cref="RectTransform.rect" />.
/// </summary>
/// <param name="transform">RectTransform to process</param>
/// <param name="space">Space in which to retrieve the bounds</param>
/// <returns>Bounds of the RectTransform</returns>
/// <exception cref="ArgumentNullException">Transform is null</exception>
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);
}
/// <summary>
/// Gets the bounds of a BoxCollider in a given space.
/// </summary>
/// <param name="boxCollider">BoxCollider to get the bounds of</param>
/// <param name="space">Space in which to retrieve the bounds</param>
/// <returns>BoxCollider bounds</returns>
/// <exception cref="ArgumentNullException">BoxCollider is null</exception>
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);
}
/// <summary>
/// Gets the bounds in a given space of all MeshRenderers in a set of Transforms.
/// </summary>
/// <param name="transforms">Transforms whose MeshRenderers to get the bounds of</param>
/// <param name="space">Space in which to retrieve the bounds</param>
/// <param name="includeInactive">Whether to include inactive MeshRenderers</param>
/// <returns>Bounds containing all MeshRenderers</returns>
/// <exception cref="ArgumentNullException">transforms is null</exception>
public static Bounds CalculateBounds(this IEnumerable<Transform> 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);
}
/// <summary>
/// Gets the bounds in the given transform's children with the largest squared length of the bounds
/// <see cref="Bounds.size" /> vector.
/// </summary>
/// <param name="transform">Transform to process all children of</param>
/// <param name="includeInactive">Whether to include inactive MeshRenderers</param>
/// <returns>Bounds with the largest squared length size vector</returns>
/// <exception cref="ArgumentNullException">transform is null</exception>
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;
}
/// <summary>
/// Gets the bounds in the given transforms children with the largest squared length of the bounds
/// <see cref="Bounds.size" /> vector.
/// </summary>
/// <param name="transforms">Transforms to process all children of</param>
/// <param name="includeInactive">Whether to include inactive MeshRenderers</param>
/// <returns>Bounds with the largest squared length size vector</returns>
/// <exception cref="ArgumentNullException">transforms is null</exception>
public static Bounds GetWiderBounds(this IEnumerable<Transform> 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;
}
/// <summary>
/// Transforms a given <see cref="Bounds" /> object in local space using a <see cref="Transform" /> component.
/// </summary>
/// <param name="transform">Transform applied to the bounds</param>
/// <param name="localBounds">Bounds in local space</param>
/// <returns>Transformed bounds</returns>
/// <exception cref="ArgumentNullException">transform is null</exception>
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 };
}
/// <summary>
/// Transforms a given <see cref="Bounds" /> object in world space to the local space of a <see cref="Transform" />.
/// </summary>
/// <param name="transform">Transform defining the local space where to move the bounds to</param>
/// <param name="worldBounds">Bounds in world space</param>
/// <returns>Transformed bounds</returns>
/// <exception cref="ArgumentNullException">transform is null</exception>
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
}
}