// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; using UnityEngine; namespace Microsoft.MixedReality.Toolkit.UI { /// /// The BoundingBoxHelper class contains functions for getting geometric info from the non-axis-aligned /// bounding box of a GameObject. These functions can be used to align another object to the center of /// a certain face or the center of an edge of a face... etc. /// The BoundingBoxHelper static function can be used for a one time calculation. /// The dynamic functions can be used to obtain boundingcube info on an object's Update loop. Operations /// are minimized in the dynamic use scenario. /// public class BoundingBoxHelper { readonly int[] face0 = { 0, 1, 3, 2 }; readonly int[] face1 = { 1, 5, 7, 3 }; readonly int[] face2 = { 5, 4, 6, 7 }; readonly int[] face3 = { 4, 0, 2, 6 }; readonly int[] face4 = { 6, 2, 3, 7 }; readonly int[] face5 = { 1, 0, 4, 5 }; readonly int[] noFaceIndices = { }; readonly Vector3[] noFaceVertices = { }; private Vector3[] face = new Vector3[4]; private Vector3[] midpoints = new Vector3[4]; private List rawBoundingCorners = new List(); private List worldBoundingCorners = new List(); private BoxCollider targetBounds; private bool rawBoundingCornersObtained = false; /// /// Objects that align to an target's bounding box can call this function in the object's UpdateLoop /// to get current bound points; /// [Obsolete("Use UpdateNonAABoundsCornerPositions and pass in TargetBounds")] public void UpdateNonAABoundingBoxCornerPositions(BoundingBox boundingBox, List boundsPoints) { UpdateNonAABoundsCornerPositions(boundingBox.TargetBounds, boundsPoints); } /// /// Returns the corner points of the given collider bounds /// /// The collider bounds the corner points are calculated from /// The corner points calculated from the collider points public void UpdateNonAABoundsCornerPositions(BoxCollider colliderBounds, List boundsPoints) { if (colliderBounds != targetBounds || rawBoundingCornersObtained == false) { GetRawBoundsCorners(colliderBounds); } if (colliderBounds == targetBounds && rawBoundingCornersObtained) { boundsPoints.Clear(); for (int i = 0; i < rawBoundingCorners.Count; ++i) { boundsPoints.Add(colliderBounds.transform.localToWorldMatrix.MultiplyPoint(rawBoundingCorners[i])); } worldBoundingCorners.Clear(); worldBoundingCorners.AddRange(boundsPoints); } } /// /// This function calculates the untransformed bounding box corner points of a GameObject. /// [Obsolete("Use GetRawBoundsCorners and pass in boundingBox.TargetBounds")] public void GetRawBBCorners(BoundingBox boundingBox) { GetRawBoundsCorners(boundingBox.TargetBounds); } /// /// Calculates the untransformed corner points of the given collider bounds /// /// The collider bounds the corner points are calculated from. public void GetRawBoundsCorners(BoxCollider colliderBounds) { targetBounds = colliderBounds; rawBoundingCorners.Clear(); rawBoundingCornersObtained = false; GetUntransformedCornersFromObject(colliderBounds, rawBoundingCorners); if (rawBoundingCorners != null && rawBoundingCorners.Count >= 4) { rawBoundingCornersObtained = true; } } /// /// this function gets the indices of the bounding cube corners that make up a face. /// /// the face index of the bounding cube 0-5 /// an array of four integer indices public int[] GetFaceIndices(int index) { switch (index) { case 0: return face0; case 1: return face1; case 2: return face2; case 3: return face3; case 4: return face4; case 5: return face5; } return noFaceIndices; } /// /// This function returns the midpoints of each of the edges of the face of the bounding box /// /// the index of the face of the bounding cube- 0-5 /// four Vector3 points public Vector3[] GetFaceEdgeMidpoints(int index) { Vector3[] corners = GetFaceCorners(index); midpoints[0] = (corners[0] + corners[1]) * 0.5f; midpoints[1] = (corners[1] + corners[2]) * 0.5f; midpoints[2] = (corners[2] + corners[3]) * 0.5f; midpoints[3] = (corners[3] + corners[0]) * 0.5f; return midpoints; } /// /// Get the normal of the face of the bounding cube specified by index /// /// the index of the face of the bounding cube 0-5 /// a vector3 representing the face normal public Vector3 GetFaceNormal(int index) { int[] face = GetFaceIndices(index); if (face.Length == 4) { Vector3 ab = (worldBoundingCorners[face[1]] - worldBoundingCorners[face[0]]).normalized; Vector3 ac = (worldBoundingCorners[face[2]] - worldBoundingCorners[face[0]]).normalized; return Vector3.Cross(ab, ac).normalized; } return Vector3.zero; } /// /// This function returns the centroid of a face of the bounding cube of an object specified /// by the index parameter; /// /// an index into the list of faces of a boundingcube. 0-5 public Vector3 GetFaceCentroid(int index) { int[] faceIndices = GetFaceIndices(index); if (faceIndices.Length == 4) { return (worldBoundingCorners[faceIndices[0]] + worldBoundingCorners[faceIndices[1]] + worldBoundingCorners[faceIndices[2]] + worldBoundingCorners[faceIndices[3]]) * 0.25f; } return Vector3.zero; } /// /// Get the center of the bottom edge of a face of the bounding box determined by index /// /// parameter indicating which face is used. 0-5 /// a vector representing the bottom most edge center of the face public Vector3 GetFaceBottomCentroid(int index) { Vector3[] edgeCentroids = GetFaceEdgeMidpoints(index); Vector3 leastYPoint = edgeCentroids[0]; for (int i = 1; i < 4; ++i) { leastYPoint = edgeCentroids[i].y < leastYPoint.y ? edgeCentroids[i] : leastYPoint; } return leastYPoint; } /// /// This function returns the four corners of a face of a bounding cube specified by index. /// /// the index of the face of the bounding cube. 0-5 /// an array of 4 vectors public Vector3[] GetFaceCorners(int index) { int[] faceIndices = GetFaceIndices(index); if (faceIndices.Length == 4) { face[0] = worldBoundingCorners[faceIndices[0]]; face[1] = worldBoundingCorners[faceIndices[1]]; face[2] = worldBoundingCorners[faceIndices[2]]; face[3] = worldBoundingCorners[faceIndices[3]]; return face; } return noFaceVertices; } /// /// This function gets the index of the face of the bounding cube that is most facing the lookAtPoint. /// This could be the headPosition or camera position if the face that was facing the view is desired. /// /// the world coordinate to test which face is desired /// an integer representing the index of the bounding box faces public int GetIndexOfForwardFace(Vector3 lookAtPoint) { int highestDotIndex = -1; float hightestDotValue = float.MinValue; for (int i = 0; i < 6; ++i) { Vector3 a = (lookAtPoint - GetFaceCentroid(i)).normalized; Vector3 b = GetFaceNormal(i); float dot = Vector3.Dot(a, b); if (hightestDotValue < dot) { hightestDotValue = dot; highestDotIndex = i; } } return highestDotIndex; } /// /// static function that performs one-time non-persistent calculation of corner points of given bounds /// without taking world transform into account. /// /// the bounds the corner points are to be calculated from /// the array of 8 corner points that will be filled public static void GetUntransformedCornersFromObject(BoxCollider targetBounds, List boundsPoints) { Bounds cloneBounds = new Bounds(targetBounds.center, targetBounds.size); Vector3[] corners = null; cloneBounds.GetCornerPositions(ref corners); boundsPoints.AddRange(corners); } } }