// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit
{
///
/// Extension methods for Unity's Transform class
///
public static class TransformExtensions
{
///
/// An extension method that will get you the full path to an object.
///
/// The transform you wish a full path to.
/// The delimiter with which each object is delimited in the string.
/// Prefix with which the full path to the object should start.
/// A delimited string that is the full path to the game object in the hierarchy.
public static string GetFullPath(this Transform transform, string delimiter = ".", string prefix = "/")
{
var stringBuilder = new StringBuilder();
GetFullPath(stringBuilder, transform, delimiter, prefix);
return stringBuilder.ToString();
}
private static void GetFullPath(StringBuilder stringBuilder, Transform transform, string delimiter, string prefix)
{
if (transform.parent == null)
{
stringBuilder.Append(prefix);
}
else
{
GetFullPath(stringBuilder, transform.parent, delimiter, prefix);
stringBuilder.Append(delimiter);
}
stringBuilder.Append(transform.name);
}
///
/// Enumerates all children in the hierarchy starting at the root object.
///
/// Start point of the traversion set
public static IEnumerable EnumerateHierarchy(this Transform root)
{
if (root == null) { throw new ArgumentNullException("root"); }
return root.EnumerateHierarchyCore(new List(0));
}
///
/// Enumerates all children in the hierarchy starting at the root object except for the branches in ignore.
///
/// Start point of the traversion set
/// Transforms and all its children to be ignored
public static IEnumerable EnumerateHierarchy(this Transform root, ICollection ignore)
{
if (root == null) { throw new ArgumentNullException("root"); }
if (ignore == null)
{
throw new ArgumentNullException("ignore", "Ignore collection can't be null, use EnumerateHierarchy(root) instead.");
}
return root.EnumerateHierarchyCore(ignore);
}
///
/// Enumerates all children in the hierarchy starting at the root object except for the branches in ignore.
///
/// Start point of the traversion set
/// Transforms and all its children to be ignored
private static IEnumerable EnumerateHierarchyCore(this Transform root, ICollection ignore)
{
var transformQueue = new Queue();
transformQueue.Enqueue(root);
while (transformQueue.Count > 0)
{
var parentTransform = transformQueue.Dequeue();
if (!parentTransform || ignore.Contains(parentTransform)) { continue; }
for (var i = 0; i < parentTransform.childCount; i++)
{
transformQueue.Enqueue(parentTransform.GetChild(i));
}
yield return parentTransform;
}
}
///
/// Calculates the bounds of all the colliders attached to this GameObject and all its children
///
/// Transform of root GameObject the colliders are attached to
/// The total bounds of all colliders attached to this GameObject.
/// If no colliders attached, returns a bounds of center and extents 0
public static Bounds GetColliderBounds(this Transform transform)
{
Collider[] colliders = transform.GetComponentsInChildren();
if (colliders.Length == 0) { return new Bounds(); }
Bounds bounds = colliders[0].bounds;
for (int i = 1; i < colliders.Length; i++)
{
bounds.Encapsulate(colliders[i].bounds);
}
return bounds;
}
///
/// Checks if the provided transforms are child/parent related.
///
/// True if either transform is the parent of the other or if they are the same
public static bool IsParentOrChildOf(this Transform transform1, Transform transform2)
{
return transform1.IsChildOf(transform2) || transform2.IsChildOf(transform1);
}
///
/// Find the first component of type in the ancestors of the specified transform.
///
/// Type of component to find.
/// Transform for which ancestors must be considered.
/// Indicates whether the specified transform should be included.
/// The component of type . Null if it none was found.
public static T FindAncestorComponent(this Transform startTransform, bool includeSelf = true) where T : Component
{
foreach (Transform transform in startTransform.EnumerateAncestors(includeSelf))
{
T component = transform.GetComponent();
if (component != null)
{
return component;
}
}
return null;
}
///
/// Enumerates the ancestors of the specified transform.
///
/// Transform for which ancestors must be returned.
/// Indicates whether the specified transform should be included.
/// An enumeration of all ancestor transforms of the specified start transform.
public static IEnumerable EnumerateAncestors(this Transform startTransform, bool includeSelf)
{
if (!includeSelf)
{
startTransform = startTransform.parent;
}
for (Transform transform = startTransform; transform != null; transform = transform.parent)
{
yield return transform;
}
}
///
/// Transforms the size from local to world.
///
/// The transform.
/// The local size.
/// World size.
public static Vector3 TransformSize(this Transform transform, Vector3 localSize)
{
Vector3 transformedSize = new Vector3(localSize.x, localSize.y, localSize.z);
Transform t = transform;
do
{
transformedSize.x *= t.localScale.x;
transformedSize.y *= t.localScale.y;
transformedSize.z *= t.localScale.z;
t = t.parent;
}
while (t != null);
return transformedSize;
}
///
/// Transforms the size from world to local.
///
/// The transform.
/// The world size
/// World size.
public static Vector3 InverseTransformSize(this Transform transform, Vector3 worldSize)
{
Vector3 transformedSize = new Vector3(worldSize.x, worldSize.y, worldSize.z);
Transform t = transform;
do
{
transformedSize.x /= t.localScale.x;
transformedSize.y /= t.localScale.y;
transformedSize.z /= t.localScale.z;
t = t.parent;
}
while (t != null);
return transformedSize;
}
///
/// Gets the hierarchical depth of the Transform from its root. Returns -1 if the transform is the root.
///
/// The transform to get the depth for.
public static int GetDepth(this Transform t)
{
int depth = -1;
Transform root = t.transform.root;
if (root == t.transform)
{
return depth;
}
TryGetDepth(t, root, ref depth);
return depth;
}
///
/// Tries to get the hierarchical depth of the Transform from the specified parent. This method is recursive.
///
/// The transform to get the depth for
/// The starting transform to look for the target transform in
/// The depth of the target transform
/// 'true' if the depth could be retrieved, or 'false' because the transform is a root transform.
public static bool TryGetDepth(Transform target, Transform parent, ref int depth)
{
foreach (Transform child in parent)
{
depth++;
if (child == target.transform || TryGetDepth(target, child, ref depth))
{
return true;
}
}
return false;
}
///
/// Walk hierarchy looking for named transform
///
/// root transform to start searching from
/// name to look for
/// returns found transform or null if none found
public static Transform GetChildRecursive(Transform t, string name)
{
int numChildren = t.childCount;
for (int ii = 0; ii < numChildren; ++ii)
{
Transform child = t.GetChild(ii);
if (child.name == name)
{
return child;
}
Transform foundIt = GetChildRecursive(child, name);
if (foundIt != null)
{
return foundIt;
}
}
return null;
}
}
}