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