diff --git a/CODEOWNERS b/CODEOWNERS index d5405dcca99..88fd0998e85 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -18,33 +18,32 @@ # first in this file, followed by more specific rules. # Build / CI Pipeline -/Pipelines/ @keveleigh +/Pipelines/ @marlenaklein-msft @AMollis @shaynie @srinjoym # Checked-in external dependencies -/ExternalDependencies/ @keveleigh @davidkline-ms +/ExternalDependencies/ @marlenaklein-msft @AMollis @shaynie @srinjoym # Tooling -/Tooling/ @keveleigh @Zee2 +/Tooling/ @marlenaklein-msft @AMollis @shaynie @srinjoym # Feature packages -/com.microsoft.mrtk.accessibility/ @davidkline-ms -/com.microsoft.mrtk.audio/ @davidkline-ms -/com.microsoft.mrtk.core/ @davidkline-ms @keveleigh @Zee2 -/com.microsoft.mrtk.data/ @maluoi @Zee2 -/com.microsoft.mrtk.diagnostics/ @davidkline-ms -/com.microsoft.mrtk.input/ @Zee2 @RogPodge @MaxWang-MS -/com.microsoft.mrtk.spatialmanipulation/ @RogPodge @Zee2 -/com.microsoft.mrtk.tools/ @davidkline-ms @keveleigh -/com.microsoft.mrtk.uxcomponents/ @Zee2 @RogPodge -/com.microsoft.mrtk.uxcore/ @Zee2 @RogPodge -/com.microsoft.mrtk.windowsspeech/ @MaxWang-MS +/com.microsoft.mrtk.accessibility/ @marlenaklein-msft @AMollis @shaynie @srinjoym +/com.microsoft.mrtk.audio/ @marlenaklein-msft @AMollis @shaynie @srinjoym +/com.microsoft.mrtk.core/ @marlenaklein-msft @AMollis @shaynie @srinjoym +/com.microsoft.mrtk.data/ @marlenaklein-msft @AMollis @shaynie @srinjoym +/com.microsoft.mrtk.diagnostics/ @marlenaklein-msft @AMollis @shaynie @srinjoym +/com.microsoft.mrtk.input/ @marlenaklein-msft @AMollis @shaynie @srinjoym +/com.microsoft.mrtk.spatialmanipulation/ @marlenaklein-msft @AMollis @shaynie @srinjoym +/com.microsoft.mrtk.tools/ @marlenaklein-msft @AMollis @shaynie @srinjoym +/com.microsoft.mrtk.uxcomponents/ @marlenaklein-msft @AMollis @shaynie @srinjoym +/com.microsoft.mrtk.uxcore/ @marlenaklein-msft @AMollis @shaynie @srinjoym +/com.microsoft.mrtk.windowsspeech/ @marlenaklein-msft @AMollis @shaynie @srinjoym # Input Simulation -/com.microsoft.mrtk.input/Simulation/ @davidkline-ms @Zee2 @RogPodge +/com.microsoft.mrtk.input/Simulation/ @marlenaklein-msft @AMollis @shaynie @srinjoym # Examples and templates -/UnityProjects/ @davidkline-ms @keveleigh @maluoi @MaxWang-MS @RogPodge @Zee2 - +/UnityProjects/ @marlenaklein-msft @AMollis @shaynie @srinjoym # Packaging -*.asmdef @keveleigh @davidkline-ms -package.json @davidkline-ms @keveleigh \ No newline at end of file +*.asmdef @marlenaklein-msft @AMollis @shaynie @srinjoym +package.json @marlenaklein-msft @AMollis @shaynie @srinjoym \ No newline at end of file diff --git a/com.microsoft.mrtk.core/Tests/Runtime/MRTK.Core.RuntimeTests.asmdef b/com.microsoft.mrtk.core/Tests/Runtime/MRTK.Core.RuntimeTests.asmdef index 0a774b0a01a..6d26714c86e 100644 --- a/com.microsoft.mrtk.core/Tests/Runtime/MRTK.Core.RuntimeTests.asmdef +++ b/com.microsoft.mrtk.core/Tests/Runtime/MRTK.Core.RuntimeTests.asmdef @@ -4,7 +4,8 @@ "references": [ "Microsoft.MixedReality.Toolkit.Core", "UnityEditor.TestRunner", - "UnityEngine.TestRunner" + "UnityEngine.TestRunner", + "Microsoft.MixedReality.Toolkit.Core.TestUtilities" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/com.microsoft.mrtk.core/Tests/Runtime/TransformExtensionsTests.cs b/com.microsoft.mrtk.core/Tests/Runtime/TransformExtensionsTests.cs new file mode 100644 index 00000000000..783a6136dba --- /dev/null +++ b/com.microsoft.mrtk.core/Tests/Runtime/TransformExtensionsTests.cs @@ -0,0 +1,248 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using NUnit.Framework; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Microsoft.MixedReality.Toolkit.Core.Tests +{ + public class TransformExtensionsTests : BaseRuntimeTests + { + [UnityTest] + public IEnumerator GetColliderBoundsTest() + { + // Create a root object with colliders attached to it + GameObject rootObject = new GameObject("Root"); + BoxCollider rootCollider = rootObject.AddComponent(); + rootCollider.center = new Vector3(1f, 2f, 3f); + rootCollider.size = new Vector3(2f, 3f, 4f); + + // Create two child objects with colliders attached to them + GameObject childObject1 = new GameObject("Child1"); + childObject1.transform.parent = rootObject.transform; + SphereCollider childCollider1 = childObject1.AddComponent(); + childCollider1.center = new Vector3(2f, 3f, 4f); + childCollider1.radius = 1f; + + GameObject childObject2 = new GameObject("Child2"); + childObject2.transform.parent = rootObject.transform; + CapsuleCollider childCollider2 = childObject2.AddComponent(); + childCollider2.center = new Vector3(1f, 1f, 1f); + childCollider2.radius = 0.5f; + childCollider2.height = 2f; + + // Get encapsulated bounds + Bounds bounds = rootObject.transform.GetColliderBounds(); + + Assert.AreEqual(new Vector3(1.5f, 2f, 2.75f), bounds.center); + Assert.AreEqual(new Vector3(3f, 4f, 4.50f), bounds.size); + yield return null; + } + + + + [UnityTest] + public IEnumerator GetFullPathTest() + { + var parent = new GameObject("Parent").transform; + var child1 = new GameObject("Child1").transform; + var child2 = new GameObject("Child2").transform; + var grandchild = new GameObject("Grandchild").transform; + child1.SetParent(parent); + child2.SetParent(parent); + grandchild.SetParent(child1); + + var fullPath = child1.GetFullPath(prefix: "/", delimiter: "."); + + Assert.AreEqual("/Parent.Child1", fullPath); + + fullPath = grandchild.GetFullPath(prefix: "/", delimiter: "/"); + Assert.AreEqual("/Parent/Child1/Grandchild", fullPath); + + fullPath = child2.GetFullPath(prefix: "", delimiter: "-"); + Assert.AreEqual("Parent-Child2", fullPath); + + yield return null; + } + + [UnityTest] + public IEnumerator EnumerateHierarchyTest() + { + var root = new GameObject("Root").transform; + var child1 = new GameObject("Child1").transform; + var child2 = new GameObject("Child2").transform; + child1.SetParent(root); + child2.SetParent(root); + + var children = root.EnumerateHierarchy().ToList(); + + // 3 because root is also included + Assert.AreEqual(3, children.Count()); + Assert.Contains(root, children); + Assert.Contains(child1, children); + Assert.Contains(child2, children); + yield return null; + } + + [UnityTest] + public IEnumerator EnumerateHierarchyIgnoresTransformTest() + { + var root = new GameObject("Root").transform; + var child1 = new GameObject("Child1").transform; + var child2 = new GameObject("Child2").transform; + var ignore = new List { child2 }; + child1.SetParent(root); + child2.SetParent(root); + + var children = root.EnumerateHierarchy(ignore).ToList(); + + // 2 because root is also included, but not child2 + Assert.AreEqual(2, children.Count()); + Assert.Contains(root, children); + Assert.Contains(child1, children); + Assert.IsFalse(children.Contains(child2)); + yield return null; + } + + + [UnityTest] + public IEnumerator IsParentOrChildOfTest() + { + var parent = new GameObject("Parent").transform; + var child = new GameObject("Child").transform; + child.SetParent(parent); + + var result = child.IsParentOrChildOf(parent); + Assert.IsTrue(result); + + result = parent.IsParentOrChildOf(child); + Assert.IsTrue(result); + + yield return null; + } + + [UnityTest] + public IEnumerator FindAncestorComponentNullTest() + { + var gameObject = new GameObject(); + var transform = gameObject.transform; + + var component = transform.FindAncestorComponent(); + + Assert.IsNull(component); + yield return null; + } + + [UnityTest] + public IEnumerator FindAncestorComponentTest() + { + var parentGameObject = new GameObject(); + var childGameObject = new GameObject(); + var component = childGameObject.AddComponent(); + childGameObject.transform.SetParent(parentGameObject.transform); + + var foundComponent = childGameObject.transform.FindAncestorComponent(); + + Assert.AreEqual(component, foundComponent); + yield return null; + } + + [UnityTest] + public IEnumerator EnumerateAncestorsTest() + { + var grandparentGameObject = new GameObject(); + var parentGameObject = new GameObject(); + var childGameObject = new GameObject(); + childGameObject.transform.SetParent(parentGameObject.transform); + parentGameObject.transform.SetParent(grandparentGameObject.transform); + + var ancestors = childGameObject.transform.EnumerateAncestors(false).ToList(); + + Assert.AreEqual(2, ancestors.Count); + Assert.AreEqual(parentGameObject.transform, ancestors[0]); + Assert.AreEqual(grandparentGameObject.transform, ancestors[1]); + yield return null; + } + + [UnityTest] + public IEnumerator TransformSizeTest() + { + var parentGameObject = new GameObject(); + parentGameObject.transform.localScale = new Vector3(2, 2, 2); + var childGameObject = new GameObject(); + childGameObject.transform.SetParent(parentGameObject.transform); + childGameObject.transform.localScale = new Vector3(2, 2, 2); + + var worldSize = childGameObject.transform.TransformSize(new Vector3(2, 3, 4)); + + Assert.AreEqual(new Vector3(8, 12, 16), worldSize); + yield return null; + } + + [UnityTest] + public IEnumerator InverseTransformSizeTest() + { + var parentGameObject = new GameObject(); + var childGameObject = new GameObject(); + childGameObject.transform.SetParent(parentGameObject.transform); + parentGameObject.transform.localScale = new Vector3(2, 2, 2); + + var localSize = parentGameObject.transform.InverseTransformSize(new Vector3(2, 2, 2)); + + Assert.AreEqual(new Vector3(1, 1, 1), localSize); + yield return null; + } + + + [UnityTest] + public IEnumerator GetDepthTest() + { + var grandparentGameObject = new GameObject("Grandparent"); + var parentGameObject = new GameObject("Parent"); + var childGameObject = new GameObject("Child"); + childGameObject.transform.SetParent(parentGameObject.transform); + parentGameObject.transform.SetParent(grandparentGameObject.transform); + + var depth = childGameObject.transform.GetDepth(); + + Assert.AreEqual(1, depth); + + depth = grandparentGameObject.transform.GetDepth(); + Assert.AreEqual(-1, depth); + + depth = parentGameObject.transform.GetDepth(); + Assert.AreEqual(0, depth); + + yield return null; + } + + [UnityTest] + public IEnumerator GetChildRecursiveTest() + { + var greatGrandparentGameObject = new GameObject("Greatgrandparent"); + var grandparentGameObject = new GameObject("Grandparent"); + var parentGameObject = new GameObject("Parent"); + var uncleGameObject = new GameObject("Uncle"); + var childGameObject = new GameObject("Child"); + var cousinGameObject = new GameObject("Cousin"); + childGameObject.transform.SetParent(parentGameObject.transform); + cousinGameObject.transform.SetParent(uncleGameObject.transform); + parentGameObject.transform.SetParent(grandparentGameObject.transform); + uncleGameObject.transform.SetParent(grandparentGameObject.transform); + grandparentGameObject.transform.SetParent(greatGrandparentGameObject.transform); + + var childResult = TransformExtensions.GetChildRecursive(greatGrandparentGameObject.transform, "Child"); + Assert.AreSame(childGameObject.transform, childResult); + var cousinResult = TransformExtensions.GetChildRecursive(greatGrandparentGameObject.transform, "Cousin"); + Assert.AreSame(cousinGameObject.transform, cousinResult); + var nullResult = TransformExtensions.GetChildRecursive(greatGrandparentGameObject.transform, "Sister"); + Assert.IsNull(nullResult); + + yield return null; + } + } +} diff --git a/com.microsoft.mrtk.core/Tests/Runtime/TransformExtensionsTests.cs.meta b/com.microsoft.mrtk.core/Tests/Runtime/TransformExtensionsTests.cs.meta new file mode 100644 index 00000000000..808b044d005 --- /dev/null +++ b/com.microsoft.mrtk.core/Tests/Runtime/TransformExtensionsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0d308c64dc9eff47b8944bd24766ff1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.core/Utilities/Extensions/TransformExtensions.cs b/com.microsoft.mrtk.core/Utilities/Extensions/TransformExtensions.cs new file mode 100644 index 00000000000..933f3840cbd --- /dev/null +++ b/com.microsoft.mrtk.core/Utilities/Extensions/TransformExtensions.cs @@ -0,0 +1,274 @@ +// 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; + } + } +} diff --git a/com.microsoft.mrtk.core/Utilities/Extensions/TransformExtensions.cs.meta b/com.microsoft.mrtk.core/Utilities/Extensions/TransformExtensions.cs.meta new file mode 100644 index 00000000000..01e1a424b07 --- /dev/null +++ b/com.microsoft.mrtk.core/Utilities/Extensions/TransformExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e4977b904a7de1d419a748c761cabbcf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.uxcore/StateVisualizer/Effects/AnimationEffect.cs b/com.microsoft.mrtk.uxcore/StateVisualizer/Effects/AnimationEffect.cs index 103b1125550..676362945ae 100644 --- a/com.microsoft.mrtk.uxcore/StateVisualizer/Effects/AnimationEffect.cs +++ b/com.microsoft.mrtk.uxcore/StateVisualizer/Effects/AnimationEffect.cs @@ -49,7 +49,6 @@ internal class AnimationEffect : PlayableEffect, IAnimationMixableEffect public WeightType WeightMode => weightMode; [SerializeField] - [DrawIf("weightMode", WeightType.Transition)] [Tooltip("How long should it take to transition to a weight of 1.0 when the state becomes active?")] private float transitionDuration;