// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; using UnityEngine; using UnityEngine.Assertions; namespace Microsoft.MixedReality.Toolkit { /// /// Extension methods for Unity's Bounds struct /// public static class BoundsExtensions { // Corners public const int LBF = 0; public const int LBB = 1; public const int LTF = 2; public const int LTB = 3; public const int RBF = 4; public const int RBB = 5; public const int RTF = 6; public const int RTB = 7; // X axis public const int LTF_RTF = 8; public const int LBF_RBF = 9; public const int RTB_LTB = 10; public const int RBB_LBB = 11; // Y axis public const int LTF_LBF = 12; public const int RTB_RBB = 13; public const int LTB_LBB = 14; public const int RTF_RBF = 15; // Z axis public const int RBF_RBB = 16; public const int RTF_RTB = 17; public const int LBF_LBB = 18; public const int LTF_LTB = 19; // 2D corners public const int LT = 0; public const int LB = 1; public const int RT = 2; public const int RB = 3; // 2D midpoints public const int LT_RT = 4; public const int RT_RB = 5; public const int RB_LB = 6; public const int LB_LT = 7; // Face points public const int TOP = 0; public const int BOT = 1; public const int LFT = 2; public const int RHT = 3; public const int FWD = 4; public const int BCK = 5; // Axis of the capsule’s lengthwise orientation in the object’s local space private const int CAPSULE_X_AXIS = 0; private const int CAPSULE_Y_AXIS = 1; private const int CAPSULE_Z_AXIS = 2; // Edges used to render the bounds. private static readonly int[] boundsEdges = new int[] { LBF, LBB, LBB, LTB, LTB, LTF, LTF, LBF, LBF, RTB, RTB, RTF, RTF, RBF, RBF, RBB, RBB, RTB, RTF, LBB, RBF, LTB, RBB, LTF }; public enum Axis { X, Y, Z } private static Vector3[] corners = null; private static Vector3[] rectTransformCorners = new Vector3[4]; #region Public Static Functions /// /// Returns an instance of the 'Bounds' class which is invalid. An invalid 'Bounds' instance /// is one which has its size vector set to 'float.MaxValue' for all 3 components. The center /// of an invalid bounds instance is the zero vector. /// public static Bounds GetInvalidBoundsInstance() { return new Bounds(Vector3.zero, GetInvalidBoundsSize()); } /// /// Checks if the specified bounds instance is valid. A valid 'Bounds' instance is /// one whose size vector does not have all 3 components set to 'float.MaxValue'. /// public static bool IsValid(this Bounds bounds) { return bounds.size != GetInvalidBoundsSize(); } /// /// Gets all the corner points of the bounds in world space by transforming input bounds using the given transform /// /// Local to world transform /// Output corner positions /// Input bounds, in local space /// /// Use BoxColliderExtensions.{Left|Right}{Bottom|Top}{Front|Back} consts to index into the output /// corners array. /// public static void GetCornerPositions(this Bounds bounds, Transform transform, ref Vector3[] positions) { // Calculate the local points to transform. Vector3 center = bounds.center; Vector3 extents = bounds.extents; float leftEdge = center.x - extents.x; float rightEdge = center.x + extents.x; float bottomEdge = center.y - extents.y; float topEdge = center.y + extents.y; float frontEdge = center.z - extents.z; float backEdge = center.z + extents.z; // Allocate the array if needed. const int numPoints = 8; if (positions == null || positions.Length != numPoints) { positions = new Vector3[numPoints]; } // Transform all the local points to world space. positions[LBF] = transform.TransformPoint(leftEdge, bottomEdge, frontEdge); positions[LBB] = transform.TransformPoint(leftEdge, bottomEdge, backEdge); positions[LTF] = transform.TransformPoint(leftEdge, topEdge, frontEdge); positions[LTB] = transform.TransformPoint(leftEdge, topEdge, backEdge); positions[RBF] = transform.TransformPoint(rightEdge, bottomEdge, frontEdge); positions[RBB] = transform.TransformPoint(rightEdge, bottomEdge, backEdge); positions[RTF] = transform.TransformPoint(rightEdge, topEdge, frontEdge); positions[RTB] = transform.TransformPoint(rightEdge, topEdge, backEdge); } /// /// Gets all the corner points of the bounds /// /// /// Use BoxColliderExtensions.{Left|Right}{Bottom|Top}{Front|Back} consts to index into the output /// corners array. /// public static void GetCornerPositions(this Bounds bounds, ref Vector3[] positions) { // Calculate the local points to transform. Vector3 center = bounds.center; Vector3 extents = bounds.extents; float leftEdge = center.x - extents.x; float rightEdge = center.x + extents.x; float bottomEdge = center.y - extents.y; float topEdge = center.y + extents.y; float frontEdge = center.z - extents.z; float backEdge = center.z + extents.z; // Allocate the array if needed. const int numPoints = 8; if (positions == null || positions.Length != numPoints) { positions = new Vector3[numPoints]; } // Transform all the local points to world space. positions[LBF] = new Vector3(leftEdge, bottomEdge, frontEdge); positions[LBB] = new Vector3(leftEdge, bottomEdge, backEdge); positions[LTF] = new Vector3(leftEdge, topEdge, frontEdge); positions[LTB] = new Vector3(leftEdge, topEdge, backEdge); positions[RBF] = new Vector3(rightEdge, bottomEdge, frontEdge); positions[RBB] = new Vector3(rightEdge, bottomEdge, backEdge); positions[RTF] = new Vector3(rightEdge, topEdge, frontEdge); positions[RTB] = new Vector3(rightEdge, topEdge, backEdge); } /// /// Gets all the corner points from Renderer's Bounds /// public static void GetCornerPositionsFromRendererBounds(this Bounds bounds, ref Vector3[] positions) { Vector3 center = bounds.center; Vector3 extents = bounds.extents; float leftEdge = center.x - extents.x; float rightEdge = center.x + extents.x; float bottomEdge = center.y - extents.y; float topEdge = center.y + extents.y; float frontEdge = center.z - extents.z; float backEdge = center.z + extents.z; const int numPoints = 8; if (positions == null || positions.Length != numPoints) { positions = new Vector3[numPoints]; } positions[LBF] = new Vector3(leftEdge, bottomEdge, frontEdge); positions[LBB] = new Vector3(leftEdge, bottomEdge, backEdge); positions[LTF] = new Vector3(leftEdge, topEdge, frontEdge); positions[LTB] = new Vector3(leftEdge, topEdge, backEdge); positions[RBF] = new Vector3(rightEdge, bottomEdge, frontEdge); positions[RBB] = new Vector3(rightEdge, bottomEdge, backEdge); positions[RTF] = new Vector3(rightEdge, topEdge, frontEdge); positions[RTB] = new Vector3(rightEdge, topEdge, backEdge); } public static void GetFacePositions(this Bounds bounds, Transform transform, ref Vector3[] positions) { Vector3 center = bounds.center; Vector3 extents = bounds.extents; const int numPoints = 6; if (positions == null || positions.Length != numPoints) { positions = new Vector3[numPoints]; } positions[TOP] = transform.TransformPoint(center + Vector3.up * extents.y); positions[BOT] = transform.TransformPoint(center + Vector3.down * extents.y); positions[LFT] = transform.TransformPoint(center + Vector3.left * extents.x); positions[RHT] = transform.TransformPoint(center + Vector3.right * extents.x); positions[FWD] = transform.TransformPoint(center + Vector3.forward * extents.z); positions[BCK] = transform.TransformPoint(center + Vector3.back * extents.z); } /// /// Gets all the corner points and mid points from Renderer's Bounds /// public static void GetCornerAndMidPointPositions(this Bounds bounds, Transform transform, ref Vector3[] positions) { // Calculate the local points to transform. Vector3 center = bounds.center; Vector3 extents = bounds.extents; float leftEdge = center.x - extents.x; float rightEdge = center.x + extents.x; float bottomEdge = center.y - extents.y; float topEdge = center.y + extents.y; float frontEdge = center.z - extents.z; float backEdge = center.z + extents.z; // Allocate the array if needed. const int numPoints = LTF_LTB + 1; if (positions == null || positions.Length != numPoints) { positions = new Vector3[numPoints]; } // Transform all the local points to world space. positions[LBF] = transform.TransformPoint(leftEdge, bottomEdge, frontEdge); positions[LBB] = transform.TransformPoint(leftEdge, bottomEdge, backEdge); positions[LTF] = transform.TransformPoint(leftEdge, topEdge, frontEdge); positions[LTB] = transform.TransformPoint(leftEdge, topEdge, backEdge); positions[RBF] = transform.TransformPoint(rightEdge, bottomEdge, frontEdge); positions[RBB] = transform.TransformPoint(rightEdge, bottomEdge, backEdge); positions[RTF] = transform.TransformPoint(rightEdge, topEdge, frontEdge); positions[RTB] = transform.TransformPoint(rightEdge, topEdge, backEdge); positions[LTF_RTF] = Vector3.Lerp(positions[LTF], positions[RTF], 0.5f); positions[LBF_RBF] = Vector3.Lerp(positions[LBF], positions[RBF], 0.5f); positions[RTB_LTB] = Vector3.Lerp(positions[RTB], positions[LTB], 0.5f); positions[RBB_LBB] = Vector3.Lerp(positions[RBB], positions[LBB], 0.5f); positions[LTF_LBF] = Vector3.Lerp(positions[LTF], positions[LBF], 0.5f); positions[RTB_RBB] = Vector3.Lerp(positions[RTB], positions[RBB], 0.5f); positions[LTB_LBB] = Vector3.Lerp(positions[LTB], positions[LBB], 0.5f); positions[RTF_RBF] = Vector3.Lerp(positions[RTF], positions[RBF], 0.5f); positions[RBF_RBB] = Vector3.Lerp(positions[RBF], positions[RBB], 0.5f); positions[RTF_RTB] = Vector3.Lerp(positions[RTF], positions[RTB], 0.5f); positions[LBF_LBB] = Vector3.Lerp(positions[LBF], positions[LBB], 0.5f); positions[LTF_LTB] = Vector3.Lerp(positions[LTF], positions[LTB], 0.5f); } /// /// Gets all the corner points and mid points from Renderer's Bounds, ignoring the z axis /// public static void GetCornerAndMidPointPositions2D(this Bounds bounds, Transform transform, ref Vector3[] positions, Axis flattenAxis) { // Calculate the local points to transform. Vector3 center = bounds.center; Vector3 extents = bounds.extents; float leftEdge = 0; float rightEdge = 0; float bottomEdge = 0; float topEdge = 0; // Allocate the array if needed. const int numPoints = LB_LT + 1; if (positions == null || positions.Length != numPoints) { positions = new Vector3[numPoints]; } switch (flattenAxis) { case Axis.X: default: leftEdge = center.z - extents.z; rightEdge = center.z + extents.z; bottomEdge = center.y - extents.y; topEdge = center.y + extents.y; // Transform all the local points to world space. positions[LT] = transform.TransformPoint(0, topEdge, leftEdge); positions[LB] = transform.TransformPoint(0, bottomEdge, leftEdge); positions[RT] = transform.TransformPoint(0, topEdge, rightEdge); positions[RB] = transform.TransformPoint(0, bottomEdge, rightEdge); break; case Axis.Y: leftEdge = center.z - extents.z; rightEdge = center.z + extents.z; bottomEdge = center.x - extents.x; topEdge = center.x + extents.x; // Transform all the local points to world space. positions[LT] = transform.TransformPoint(topEdge, 0, leftEdge); positions[LB] = transform.TransformPoint(bottomEdge, 0, leftEdge); positions[RT] = transform.TransformPoint(topEdge, 0, rightEdge); positions[RB] = transform.TransformPoint(bottomEdge, 0, rightEdge); break; case Axis.Z: leftEdge = center.x - extents.x; rightEdge = center.x + extents.x; bottomEdge = center.y - extents.y; topEdge = center.y + extents.y; // Transform all the local points to world space. positions[LT] = transform.TransformPoint(leftEdge, topEdge, 0); positions[LB] = transform.TransformPoint(leftEdge, bottomEdge, 0); positions[RT] = transform.TransformPoint(rightEdge, topEdge, 0); positions[RB] = transform.TransformPoint(rightEdge, bottomEdge, 0); break; } positions[LT_RT] = Vector3.Lerp(positions[LT], positions[RT], 0.5f); positions[RT_RB] = Vector3.Lerp(positions[RT], positions[RB], 0.5f); positions[RB_LB] = Vector3.Lerp(positions[RB], positions[LB], 0.5f); positions[LB_LT] = Vector3.Lerp(positions[LB], positions[LT], 0.5f); } /// /// Method to get bounds from a collection of points. /// /// The points to construct a bounds around. /// An AABB in world space around all the points. /// True if bounds were calculated, if zero points are present bounds will not be calculated. public static bool GetPointsBounds(List points, out Bounds bounds) { if (points.Count != 0) { bounds = new Bounds(points[0], Vector3.zero); for (var i = 1; i < points.Count; ++i) { bounds.Encapsulate(points[i]); } return true; } bounds = new Bounds(); return false; } /// /// Method to get bounds using collider method. /// /// GameObject to generate the bounds around. /// An AABB in world space around all the colliders in a gameObject hierarchy. /// A LayerMask to restrict the colliders selected. /// True if bounds were calculated, if zero colliders are present bounds will not be calculated. public static bool GetColliderBounds(GameObject target, out Bounds bounds, LayerMask ignoreLayers) { var boundsPoints = new List(); GetColliderBoundsPoints(target, boundsPoints, ignoreLayers); return GetPointsBounds(boundsPoints, out bounds); } /// /// Calculates how much scale is required for this Bounds to match another Bounds. /// /// Object representation to be scaled to /// padding multiplied into another bounds /// Scale represented as a Vector3 public static Vector3 GetScaleToMatchBounds(this Bounds bounds, Bounds otherBounds, Vector3 padding = default(Vector3)) { Vector3 szA = otherBounds.size + new Vector3(otherBounds.size.x * padding.x, otherBounds.size.y * padding.y, otherBounds.size.z * padding.z); Vector3 szB = bounds.size; Assert.IsTrue(szB.x != 0 && szB.y != 0 && szB.z != 0, "The bounds of the object must not be zero."); return new Vector3(szA.x / szB.x, szA.y / szB.y, szA.z / szB.z); } /// /// Calculates how much scale is required for this Bounds to fit inside another bounds without stretching. /// /// The bounds of the container we're trying to fit this object. /// A single scale factor that can be applied to this object to fit inside the container. public static float GetScaleToFitInside(this Bounds bounds, Bounds containerBounds) { var objectSize = bounds.size; var containerSize = containerBounds.size; Assert.IsTrue(objectSize.x != 0 && objectSize.y != 0 && objectSize.z != 0, "The bounds of the container must not be zero."); return Mathf.Min(containerSize.x / objectSize.x, containerSize.y / objectSize.y, containerSize.z / objectSize.z); } /// /// Method to get bounding box points using Collider method. /// /// gameObject that boundingBox bounds. /// array reference that gets filled with points /// layerMask to simplify search /// compute bounds relative to this transform public static void GetColliderBoundsPoints(GameObject target, List boundsPoints, LayerMask ignoreLayers, Transform relativeTo = null) { Collider[] colliders = target.GetComponentsInChildren(); for (int i = 0; i < colliders.Length; i++) { GetColliderBoundsPoints(colliders[i], boundsPoints, ignoreLayers, relativeTo); } } private static void InverseTransformPoints(ref Vector3[] positions, Transform relativeTo) { if (relativeTo) { for (var i = 0; i < positions.Length; ++i) { positions[i] = relativeTo.InverseTransformPoint(positions[i]); } } } /// /// Method to get bounds from a single Collider /// /// Target collider /// array reference that gets filled with points /// layerMask to simplify search public static void GetColliderBoundsPoints(Collider collider, List boundsPoints, LayerMask ignoreLayers, Transform relativeTo = null) { if (ignoreLayers == (1 << collider.gameObject.layer | ignoreLayers)) { return; } if (collider is SphereCollider) { SphereCollider sc = collider as SphereCollider; Bounds sphereBounds = new Bounds(sc.center, Vector3.one * sc.radius * 2); sphereBounds.GetFacePositions(sc.transform, ref corners); InverseTransformPoints(ref corners, relativeTo); boundsPoints.AddRange(corners); } else if (collider is BoxCollider) { BoxCollider bc = collider as BoxCollider; Bounds boxBounds = new Bounds(bc.center, bc.size); boxBounds.GetCornerPositions(bc.transform, ref corners); InverseTransformPoints(ref corners, relativeTo); boundsPoints.AddRange(corners); } else if (collider is MeshCollider) { MeshCollider mc = collider as MeshCollider; Bounds meshBounds = mc.sharedMesh.bounds; meshBounds.GetCornerPositions(mc.transform, ref corners); InverseTransformPoints(ref corners, relativeTo); boundsPoints.AddRange(corners); } else if (collider is CapsuleCollider) { CapsuleCollider cc = collider as CapsuleCollider; Bounds capsuleBounds = new Bounds(cc.center, Vector3.zero); switch (cc.direction) { case CAPSULE_X_AXIS: capsuleBounds.size = new Vector3(cc.height, cc.radius * 2, cc.radius * 2); break; case CAPSULE_Y_AXIS: capsuleBounds.size = new Vector3(cc.radius * 2, cc.height, cc.radius * 2); break; case CAPSULE_Z_AXIS: capsuleBounds.size = new Vector3(cc.radius * 2, cc.radius * 2, cc.height); break; } capsuleBounds.GetFacePositions(cc.transform, ref corners); InverseTransformPoints(ref corners, relativeTo); boundsPoints.AddRange(corners); } } /// /// Method to get bounds using renderer method. /// /// GameObject to generate the bounds around. /// An AABB in world space around all the renderers in a gameObject hierarchy. /// A LayerMask to restrict the colliders selected. /// True if bounds were calculated, if zero renderers are present bounds will not be calculated. public static bool GetRenderBounds(GameObject target, out Bounds bounds, LayerMask ignoreLayers) { var boundsPoints = new List(); GetRenderBoundsPoints(target, boundsPoints, ignoreLayers); return GetPointsBounds(boundsPoints, out bounds); } /// /// GetRenderBoundsPoints gets bounding box points using Render method. /// /// gameObject that boundingbox bounds /// array reference that gets filled with points /// layerMask to simplify search public static void GetRenderBoundsPoints(GameObject target, List boundsPoints, LayerMask ignoreLayers) { Renderer[] renderers = target.GetComponentsInChildren(); for (int i = 0; i < renderers.Length; ++i) { Renderer rendererObj = renderers[i]; if (ignoreLayers == (1 << rendererObj.gameObject.layer | ignoreLayers)) { continue; } rendererObj.bounds.GetCornerPositionsFromRendererBounds(ref corners); boundsPoints.AddRange(corners); } } /// /// Method to get bounds using mesh filters method. /// /// GameObject to generate the bounds around. /// An AABB in world space around all the mesh filters in a GameObject hierarchy. /// A LayerMask to restrict the colliders selected. /// True if bounds were calculated, if zero mesh filters are present bounds will not be calculated. public static bool GetMeshFilterBounds(GameObject target, out Bounds bounds, LayerMask ignoreLayers) { var boundsPoints = new List(); GetMeshFilterBoundsPoints(target, boundsPoints, ignoreLayers); return GetPointsBounds(boundsPoints, out bounds); } /// /// GetMeshFilterBoundsPoints - gets bounding box points using MeshFilter method. /// /// gameObject that boundingbox bounds /// array reference that gets filled with points /// layerMask to simplify search public static void GetMeshFilterBoundsPoints(GameObject target, List boundsPoints, LayerMask ignoreLayers) { MeshFilter[] meshFilters = target.GetComponentsInChildren(); for (int i = 0; i < meshFilters.Length; i++) { MeshFilter meshFilterObj = meshFilters[i]; if (ignoreLayers == (1 << meshFilterObj.gameObject.layer | ignoreLayers)) { continue; } Bounds meshBounds = meshFilterObj.sharedMesh.bounds; meshBounds.GetCornerPositions(meshFilterObj.transform, ref corners); boundsPoints.AddRange(corners); } RectTransform[] rectTransforms = target.GetComponentsInChildren(); for (int i = 0; i < rectTransforms.Length; i++) { rectTransforms[i].GetWorldCorners(rectTransformCorners); boundsPoints.AddRange(rectTransformCorners); } } /// /// Transforms 'bounds' using the specified transform matrix. /// /// /// Transforming a 'Bounds' instance means that the function will construct a new 'Bounds' /// instance which has its center translated using the translation information stored in /// the specified matrix and its size adjusted to account for rotation and scale. The size /// of the new 'Bounds' instance will be calculated in such a way that it will contain the /// old 'Bounds'. /// /// /// The 'Bounds' instance which must be transformed. /// /// /// The specified 'Bounds' instance will be transformed using this transform matrix. The function /// assumes that the matrix doesn't contain any projection or skew transformation. /// /// /// The transformed 'Bounds' instance. /// public static Bounds Transform(this Bounds bounds, Matrix4x4 transformMatrix) { // We will need access to the right, up and look vector which are encoded inside the transform matrix Vector3 rightAxis = transformMatrix.GetColumn(0); Vector3 upAxis = transformMatrix.GetColumn(1); Vector3 lookAxis = transformMatrix.GetColumn(2); // We will 'imagine' that we want to rotate the bounds' extents vector using the rotation information // stored inside the specified transform matrix. We will need these when calculating the new size if // the transformed bounds. Vector3 rotatedExtentsRight = rightAxis * bounds.extents.x; Vector3 rotatedExtentsUp = upAxis * bounds.extents.y; Vector3 rotatedExtentsLook = lookAxis * bounds.extents.z; // Calculate the new bounds size along each axis. The size on each axis is calculated by summing up the // corresponding vector component values of the rotated extents vectors. We multiply by 2 because we want // to get a size and currently we are working with extents which represent half the size. float newSizeX = (Mathf.Abs(rotatedExtentsRight.x) + Mathf.Abs(rotatedExtentsUp.x) + Mathf.Abs(rotatedExtentsLook.x)) * 2.0f; float newSizeY = (Mathf.Abs(rotatedExtentsRight.y) + Mathf.Abs(rotatedExtentsUp.y) + Mathf.Abs(rotatedExtentsLook.y)) * 2.0f; float newSizeZ = (Mathf.Abs(rotatedExtentsRight.z) + Mathf.Abs(rotatedExtentsUp.z) + Mathf.Abs(rotatedExtentsLook.z)) * 2.0f; // Construct the transformed 'Bounds' instance var transformedBounds = new Bounds(); transformedBounds.center = transformMatrix.MultiplyPoint(bounds.center); transformedBounds.size = new Vector3(newSizeX, newSizeY, newSizeZ); // Return the instance to the caller return transformedBounds; } /// /// Returns the screen space corner points of the specified 'Bounds' instance. /// /// /// The camera used for rendering to the screen. This is needed to perform the /// transformation to screen space. /// public static Vector2[] GetScreenSpaceCornerPoints(this Bounds bounds, Camera camera) { Vector3 aabbCenter = bounds.center; Vector3 aabbExtents = bounds.extents; // Return the screen space point array return new Vector2[] { camera.WorldToScreenPoint(new Vector3(aabbCenter.x - aabbExtents.x, aabbCenter.y - aabbExtents.y, aabbCenter.z - aabbExtents.z)), camera.WorldToScreenPoint(new Vector3(aabbCenter.x + aabbExtents.x, aabbCenter.y - aabbExtents.y, aabbCenter.z - aabbExtents.z)), camera.WorldToScreenPoint(new Vector3(aabbCenter.x + aabbExtents.x, aabbCenter.y + aabbExtents.y, aabbCenter.z - aabbExtents.z)), camera.WorldToScreenPoint(new Vector3(aabbCenter.x - aabbExtents.x, aabbCenter.y + aabbExtents.y, aabbCenter.z - aabbExtents.z)), camera.WorldToScreenPoint(new Vector3(aabbCenter.x - aabbExtents.x, aabbCenter.y - aabbExtents.y, aabbCenter.z + aabbExtents.z)), camera.WorldToScreenPoint(new Vector3(aabbCenter.x + aabbExtents.x, aabbCenter.y - aabbExtents.y, aabbCenter.z + aabbExtents.z)), camera.WorldToScreenPoint(new Vector3(aabbCenter.x + aabbExtents.x, aabbCenter.y + aabbExtents.y, aabbCenter.z + aabbExtents.z)), camera.WorldToScreenPoint(new Vector3(aabbCenter.x - aabbExtents.x, aabbCenter.y + aabbExtents.y, aabbCenter.z + aabbExtents.z)) }; } /// /// Returns the rectangle which encloses the specifies 'Bounds' instance in screen space. /// public static Rect GetScreenRectangle(this Bounds bounds, Camera camera) { // Retrieve the bounds' corner points in screen space Vector2[] screenSpaceCornerPoints = bounds.GetScreenSpaceCornerPoints(camera); // Identify the minimum and maximum points in the array Vector3 minScreenPoint = screenSpaceCornerPoints[0], maxScreenPoint = screenSpaceCornerPoints[0]; for (int screenPointIndex = 1; screenPointIndex < screenSpaceCornerPoints.Length; ++screenPointIndex) { minScreenPoint = Vector3.Min(minScreenPoint, screenSpaceCornerPoints[screenPointIndex]); maxScreenPoint = Vector3.Max(maxScreenPoint, screenSpaceCornerPoints[screenPointIndex]); } // Return the screen space rectangle return new Rect(minScreenPoint.x, minScreenPoint.y, maxScreenPoint.x - minScreenPoint.x, maxScreenPoint.y - minScreenPoint.y); } /// /// Returns the volume of the bounds. /// public static float Volume(this Bounds bounds) { return bounds.size.x * bounds.size.y * bounds.size.z; } /// /// Returns bounds that contain both this bounds and the bounds passed in. /// public static Bounds ExpandToContain(this Bounds originalBounds, Bounds otherBounds) { Bounds tmpBounds = originalBounds; tmpBounds.Encapsulate(otherBounds); return tmpBounds; } /// /// Checks to see if bounds contains the other bounds completely. /// public static bool ContainsBounds(this Bounds bounds, Bounds otherBounds) { return bounds.Contains(otherBounds.min) && bounds.Contains(otherBounds.max); } /// /// Checks to see whether point is closer to bounds or otherBounds /// public static bool CloserToPoint(this Bounds bounds, Vector3 point, Bounds otherBounds) { Vector3 distToClosestPoint1 = bounds.ClosestPoint(point) - point; Vector3 distToClosestPoint2 = otherBounds.ClosestPoint(point) - point; if (distToClosestPoint1.magnitude == distToClosestPoint2.magnitude) { Vector3 toCenter1 = point - bounds.center; Vector3 toCenter2 = point - otherBounds.center; return (toCenter1.magnitude <= toCenter2.magnitude); } return (distToClosestPoint1.magnitude <= distToClosestPoint2.magnitude); } /// /// Draws a wire frame Bounds object using Debug.DrawLine. /// /// The Bounds to draw. /// Color of the line. /// How long the line should be visible for in seconds. /// Should the line be obscured by objects closer to the camera? public static void DebugDraw(this Bounds bounds, Color color, float duration = 0.0f, bool depthTest = true) { var center = bounds.center; var x = bounds.extents.x; var y = bounds.extents.y; var z = bounds.extents.z; var a = new Vector3(-x, y, -z); var b = new Vector3(x, -y, -z); var c = new Vector3(x, y, -z); var verticies = new Vector3[] { bounds.min, center + a, center + b, center + c, bounds.max, center - a, center - b, center - c }; for (var i = 0; i < boundsEdges.Length; i += 2) { Debug.DrawLine(verticies[boundsEdges[i]], verticies[boundsEdges[i + 1]], color, duration, depthTest); } } #endregion #region Private Static Functions /// /// Returns the vector which is used to represent and invalid bounds size. /// private static Vector3 GetInvalidBoundsSize() { return new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); } #endregion } }