// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Input; using Microsoft.MixedReality.Toolkit.UI.BoundsControlTypes; using System.Collections.Generic; using UnityEngine; namespace Microsoft.MixedReality.Toolkit.UI.BoundsControl { /// /// Helper class providing some static utility functions for handles /// internal class VisualUtils { internal static void HandleIgnoreCollider(Collider handlesIgnoreCollider, List handles, bool ignore = true) { if (handlesIgnoreCollider != null) { foreach (Transform handle in handles) { Collider[] colliders = handle.gameObject.GetComponents(); foreach (Collider collider in colliders) { UnityEngine.Physics.IgnoreCollision(collider, handlesIgnoreCollider, ignore); } } } } internal static float GetMaxComponent(Vector3 vec) { return Mathf.Max(Mathf.Max(vec.x, vec.y), vec.z); } internal static Bounds GetMaxBounds(GameObject g) { var b = new Bounds(); Mesh currentMesh; foreach (MeshFilter r in g.GetComponentsInChildren()) { if ((currentMesh = r.sharedMesh) == null) { continue; } if (b.size == Vector3.zero) { b = currentMesh.bounds; } else { b.Encapsulate(currentMesh.bounds); } } foreach (SkinnedMeshRenderer r in g.GetComponentsInChildren()) { if ((currentMesh = r.sharedMesh) == null) { continue; } if (b.size == Vector3.zero) { b = currentMesh.bounds; } else { b.Encapsulate(currentMesh.bounds); } } return b; } internal static void ApplyMaterialToAllRenderers(GameObject root, Material material) { if (material != null) { Renderer[] renderers = root.GetComponentsInChildren(); for (int i = 0; i < renderers.Length; ++i) { renderers[i].material = material; } } } /// /// Add all common components to a corner or rotate affordance. /// internal static void AddComponentsToAffordance(GameObject afford, Bounds bounds, HandlePrefabCollider colliderType, CursorContextInfo.CursorAction cursorType, Vector3 colliderPadding, Transform parent, bool drawTetherWhenManipulating) { if (colliderType == HandlePrefabCollider.Box) { BoxCollider collider = afford.EnsureComponent(); collider.size = bounds.size; collider.center = bounds.center; collider.size += colliderPadding; } else { SphereCollider sphere = afford.EnsureComponent(); sphere.center = bounds.center; sphere.radius = bounds.extents.x; sphere.radius += GetMaxComponent(colliderPadding); } // In order for the affordance to be grabbed using near interaction we need // to add NearInteractionGrabbable; var g = afford.EnsureComponent(); g.ShowTetherWhenManipulating = drawTetherWhenManipulating; g.IsBoundsHandles = true; var contextInfo = afford.EnsureComponent(); contextInfo.CurrentCursorAction = cursorType; contextInfo.ObjectCenter = parent; } /// /// Creates the default material for bounds control handles. /// internal static Material CreateDefaultMaterial() { return Resources.Load("BoundsControlHandleDefault"); } /// /// Calculates an array of corner points out of the given bounds. /// /// Bounds of the box. /// Calculated corner points. static internal void GetCornerPositionsFromBounds(Bounds bounds, ref Vector3[] positions) { const int numCorners = 1 << 3; if (positions == null || positions.Length != numCorners) { positions = new Vector3[numCorners]; } // Permutate all axes using minCorner and maxCorner. Vector3 minCorner = bounds.center - bounds.extents; Vector3 maxCorner = bounds.center + bounds.extents; for (int c = 0; c < numCorners; c++) { positions[c] = new Vector3( (c & (1 << 0)) == 0 ? minCorner[0] : maxCorner[0], (c & (1 << 1)) == 0 ? minCorner[1] : maxCorner[1], (c & (1 << 2)) == 0 ? minCorner[2] : maxCorner[2]); } } /// /// Flattens the given extents according to the passed flattenAxis. The flattenAxis value will be replaced by flattenValue. /// /// The original extents (unflattened). /// The axis to flatten. /// The value to flatten the flattenAxis to. /// New extents with flattened axis. static internal Vector3 FlattenBounds(Vector3 extents, FlattenModeType flattenAxis, float flattenValue = 0.0f) { Vector3 boundsExtents = extents; if (boundsExtents != Vector3.zero) { if (flattenAxis == FlattenModeType.FlattenAuto) { flattenAxis = DetermineAxisToFlatten(boundsExtents); } boundsExtents.x = (flattenAxis == FlattenModeType.FlattenX) ? flattenValue : boundsExtents.x; boundsExtents.y = (flattenAxis == FlattenModeType.FlattenY) ? flattenValue : boundsExtents.y; boundsExtents.z = (flattenAxis == FlattenModeType.FlattenZ) ? flattenValue : boundsExtents.z; } return boundsExtents; } /// /// Determine the axis to flatten based on the bound extents. Useful when is set to instead of an explicit axis. /// /// The current bound extents. /// Determined axis to be flattened. internal static FlattenModeType DetermineAxisToFlatten(Vector3 boundsExtents) { FlattenModeType axisToFlatten; if (boundsExtents.x < boundsExtents.y && boundsExtents.x < boundsExtents.z) { axisToFlatten = FlattenModeType.FlattenX; } else if (boundsExtents.y < boundsExtents.z) { axisToFlatten = FlattenModeType.FlattenY; } else { axisToFlatten = FlattenModeType.FlattenZ; } return axisToFlatten; } /// /// Util function for retrieving a position for the given edge index of a box. /// This method makes sure all visual components are having the same definition of edges / corners. /// /// Index of the edge the position is queried for. /// Corner points array of the box. /// Center position of link. static internal Vector3 GetLinkPosition(int linkIndex, ref Vector3[] cornerPoints) { Debug.Assert(cornerPoints != null && cornerPoints.Length == 8, "Invalid corner points array passed"); if (cornerPoints != null && cornerPoints.Length == 8) { switch (linkIndex) { case (int)Edges.FrontBottom: return (cornerPoints[0] + cornerPoints[1]) * 0.5f; case (int)Edges.FrontLeft: return (cornerPoints[0] + cornerPoints[2]) * 0.5f; case (int)Edges.FrontTop: return (cornerPoints[3] + cornerPoints[2]) * 0.5f; case (int)Edges.FrontRight: return (cornerPoints[3] + cornerPoints[1]) * 0.5f; case (int)Edges.BackBottom: return (cornerPoints[4] + cornerPoints[5]) * 0.5f; case (int)Edges.BackLeft: return (cornerPoints[4] + cornerPoints[6]) * 0.5f; case (int)Edges.BackTop: return (cornerPoints[7] + cornerPoints[6]) * 0.5f; case (int)Edges.BackRight: return (cornerPoints[7] + cornerPoints[5]) * 0.5f; case (int)Edges.BottomLeft: return (cornerPoints[0] + cornerPoints[4]) * 0.5f; case (int)Edges.BottomRight: return (cornerPoints[1] + cornerPoints[5]) * 0.5f; case (int)Edges.TopLeft: return (cornerPoints[2] + cornerPoints[6]) * 0.5f; case (int)Edges.TopRight: return (cornerPoints[3] + cornerPoints[7]) * 0.5f; } } return Vector3.zero; } /// /// Util function for retrieving a position for the given face index of a box. /// This method makes sure all visual components are having the same definition of face centers. /// /// Index of the face the position is queried for. /// Corner points array of the box. /// Center position of face. static internal Vector3 GetFaceCenterPosition(int faceIndex, ref Vector3[] cornerPoints) { Debug.Assert(cornerPoints != null && cornerPoints.Length == 8, "Invalid corner points array passed"); if (cornerPoints != null && cornerPoints.Length == 8) { switch (faceIndex) { case (int)Face.ForwardX: return (cornerPoints[0] + cornerPoints[2] + cornerPoints[4] + cornerPoints[6]) * 0.25f; case (int)Face.BackwardX: return (cornerPoints[1] + cornerPoints[3] + cornerPoints[5] + cornerPoints[7]) * 0.25f; case (int)Face.ForwardY: return (cornerPoints[0] + cornerPoints[1] + cornerPoints[2] + cornerPoints[3]) * 0.25f; case (int)Face.BackwardY: return (cornerPoints[4] + cornerPoints[5] + cornerPoints[6] + cornerPoints[7]) * 0.25f; case (int)Face.ForwardZ: return (cornerPoints[0] + cornerPoints[1] + cornerPoints[4] + cornerPoints[5]) * 0.25f; case (int)Face.BackwardZ: return (cornerPoints[2] + cornerPoints[3] + cornerPoints[6] + cornerPoints[7]) * 0.25f; } } return Vector3.zero; } /// /// Returns the flatten indices to the corresponding flattenAxis mode. /// /// Flatten axis mode that should be converted to indices. /// Flattened indices. internal static List GetFlattenedIndices(FlattenModeType flattenAxis, CardinalAxisType[] axisArray) { Debug.Assert(flattenAxis != FlattenModeType.FlattenAuto, "FlattenAuto passed to GetFlattenedIndices. Resolve FlattenAuto into an actual axis before calling."); List flattenedIndices = new List(); for (int i = 0; i < axisArray.Length; ++i) { if ((flattenAxis == FlattenModeType.FlattenX && axisArray[i] == CardinalAxisType.X) || (flattenAxis == FlattenModeType.FlattenY && axisArray[i] == CardinalAxisType.Y) || (flattenAxis == FlattenModeType.FlattenZ && axisArray[i] == CardinalAxisType.Z)) { flattenedIndices.Add(i); } } return flattenedIndices; } internal static readonly CardinalAxisType[] EdgeAxisType = new CardinalAxisType[] { CardinalAxisType.X, CardinalAxisType.Y, CardinalAxisType.X, CardinalAxisType.Y, CardinalAxisType.X, CardinalAxisType.Y, CardinalAxisType.X, CardinalAxisType.Y, CardinalAxisType.Z, CardinalAxisType.Z, CardinalAxisType.Z, CardinalAxisType.Z }; internal static readonly CardinalAxisType[] FaceAxisType = new CardinalAxisType[] { CardinalAxisType.X, CardinalAxisType.X, CardinalAxisType.Z, CardinalAxisType.Z, CardinalAxisType.Y, CardinalAxisType.Y }; } }