mixedreality/com.microsoft.mixedreality..../Core/Extensions/BoundsExtensions.cs

777 lines
36 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
namespace Microsoft.MixedReality.Toolkit
{
/// <summary>
/// Extension methods for Unity's Bounds struct
/// </summary>
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 capsules lengthwise orientation in the objects 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
/// <summary>
/// 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.
/// </summary>
public static Bounds GetInvalidBoundsInstance()
{
return new Bounds(Vector3.zero, GetInvalidBoundsSize());
}
/// <summary>
/// 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'.
/// </summary>
public static bool IsValid(this Bounds bounds)
{
return bounds.size != GetInvalidBoundsSize();
}
/// <summary>
/// Gets all the corner points of the bounds in world space by transforming input bounds using the given transform
/// </summary>
/// <param name="transform">Local to world transform</param>
/// <param name="positions">Output corner positions</param>
/// <param name="bounds">Input bounds, in local space</param>
/// <remarks>
/// <para>Use BoxColliderExtensions.{Left|Right}{Bottom|Top}{Front|Back} consts to index into the output
/// corners array.</para>
/// </remarks>
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);
}
/// <summary>
/// Gets all the corner points of the bounds
/// </summary>
/// <remarks>
/// <para>Use BoxColliderExtensions.{Left|Right}{Bottom|Top}{Front|Back} consts to index into the output
/// corners array.</para>
/// </remarks>
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);
}
/// <summary>
/// Gets all the corner points from Renderer's Bounds
/// </summary>
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);
}
/// <summary>
/// Gets all the corner points and mid points from Renderer's Bounds
/// </summary>
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);
}
/// <summary>
/// Gets all the corner points and mid points from Renderer's Bounds, ignoring the z axis
/// </summary>
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);
}
/// <summary>
/// Method to get bounds from a collection of points.
/// </summary>
/// <param name="points">The points to construct a bounds around.</param>
/// <param name="bounds">An AABB in world space around all the points.</param>
/// <returns>True if bounds were calculated, if zero points are present bounds will not be calculated.</returns>
public static bool GetPointsBounds(List<Vector3> 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;
}
/// <summary>
/// Method to get bounds using collider method.
/// </summary>
/// <param name="target">GameObject to generate the bounds around.</param>
/// <param name="bounds">An AABB in world space around all the colliders in a gameObject hierarchy.</param>
/// <param name="ignoreLayers">A LayerMask to restrict the colliders selected.</param>
/// <returns>True if bounds were calculated, if zero colliders are present bounds will not be calculated.</returns>
public static bool GetColliderBounds(GameObject target, out Bounds bounds, LayerMask ignoreLayers)
{
var boundsPoints = new List<Vector3>();
GetColliderBoundsPoints(target, boundsPoints, ignoreLayers);
return GetPointsBounds(boundsPoints, out bounds);
}
/// <summary>
/// Calculates how much scale is required for this Bounds to match another Bounds.
/// </summary>
/// <param name="otherBounds">Object representation to be scaled to</param>
/// <param name="padding">padding multiplied into another bounds</param>
/// <returns>Scale represented as a Vector3 </returns>
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);
}
/// <summary>
/// Calculates how much scale is required for this Bounds to fit inside another bounds without stretching.
/// </summary>
/// <param name="containerBounds">The bounds of the container we're trying to fit this object.</param>
/// <returns>A single scale factor that can be applied to this object to fit inside the container.</returns>
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);
}
/// <summary>
/// Method to get bounding box points using Collider method.
/// </summary>
/// <param name="target">gameObject that boundingBox bounds.</param>
/// <param name="boundsPoints">array reference that gets filled with points</param>
/// <param name="ignoreLayers">layerMask to simplify search</param>
/// <param name="relativeTo">compute bounds relative to this transform</param>
public static void GetColliderBoundsPoints(GameObject target, List<Vector3> boundsPoints, LayerMask ignoreLayers, Transform relativeTo = null)
{
Collider[] colliders = target.GetComponentsInChildren<Collider>();
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]);
}
}
}
/// <summary>
/// Method to get bounds from a single Collider
/// </summary>
/// <param name="collider">Target collider</param>
/// <param name="boundsPoints">array reference that gets filled with points</param>
/// <param name="ignoreLayers">layerMask to simplify search</param>
public static void GetColliderBoundsPoints(Collider collider, List<Vector3> 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);
}
}
/// <summary>
/// Method to get bounds using renderer method.
/// </summary>
/// <param name="target">GameObject to generate the bounds around.</param>
/// <param name="bounds">An AABB in world space around all the renderers in a gameObject hierarchy.</param>
/// <param name="ignoreLayers">A LayerMask to restrict the colliders selected.</param>
/// <returns>True if bounds were calculated, if zero renderers are present bounds will not be calculated.</returns>
public static bool GetRenderBounds(GameObject target, out Bounds bounds, LayerMask ignoreLayers)
{
var boundsPoints = new List<Vector3>();
GetRenderBoundsPoints(target, boundsPoints, ignoreLayers);
return GetPointsBounds(boundsPoints, out bounds);
}
/// <summary>
/// GetRenderBoundsPoints gets bounding box points using Render method.
/// </summary>
/// <param name="target">gameObject that boundingbox bounds</param>
/// <param name="boundsPoints">array reference that gets filled with points</param>
/// <param name="ignoreLayers">layerMask to simplify search</param>
public static void GetRenderBoundsPoints(GameObject target, List<Vector3> boundsPoints, LayerMask ignoreLayers)
{
Renderer[] renderers = target.GetComponentsInChildren<Renderer>();
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);
}
}
/// <summary>
/// Method to get bounds using mesh filters method.
/// </summary>
/// <param name="target">GameObject to generate the bounds around.</param>
/// <param name="bounds">An AABB in world space around all the mesh filters in a GameObject hierarchy.</param>
/// <param name="ignoreLayers">A LayerMask to restrict the colliders selected.</param>
/// <returns>True if bounds were calculated, if zero mesh filters are present bounds will not be calculated.</returns>
public static bool GetMeshFilterBounds(GameObject target, out Bounds bounds, LayerMask ignoreLayers)
{
var boundsPoints = new List<Vector3>();
GetMeshFilterBoundsPoints(target, boundsPoints, ignoreLayers);
return GetPointsBounds(boundsPoints, out bounds);
}
/// <summary>
/// GetMeshFilterBoundsPoints - gets bounding box points using MeshFilter method.
/// </summary>
/// <param name="target">gameObject that boundingbox bounds</param>
/// <param name="boundsPoints">array reference that gets filled with points</param>
/// <param name="ignoreLayers">layerMask to simplify search</param>
public static void GetMeshFilterBoundsPoints(GameObject target, List<Vector3> boundsPoints, LayerMask ignoreLayers)
{
MeshFilter[] meshFilters = target.GetComponentsInChildren<MeshFilter>();
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<RectTransform>();
for (int i = 0; i < rectTransforms.Length; i++)
{
rectTransforms[i].GetWorldCorners(rectTransformCorners);
boundsPoints.AddRange(rectTransformCorners);
}
}
/// <summary>
/// Transforms 'bounds' using the specified transform matrix.
/// </summary>
/// <remarks>
/// <para>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'.</para>
/// </remarks>
/// <param name="bounds">
/// The 'Bounds' instance which must be transformed.
/// </param>
/// <param name="transformMatrix">
/// 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.
/// </param>
/// <returns>
/// The transformed 'Bounds' instance.
/// </returns>
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;
}
/// <summary>
/// Returns the screen space corner points of the specified 'Bounds' instance.
/// </summary>
/// <param name="camera">
/// The camera used for rendering to the screen. This is needed to perform the
/// transformation to screen space.
/// </param>
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))
};
}
/// <summary>
/// Returns the rectangle which encloses the specifies 'Bounds' instance in screen space.
/// </summary>
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);
}
/// <summary>
/// Returns the volume of the bounds.
/// </summary>
public static float Volume(this Bounds bounds)
{
return bounds.size.x * bounds.size.y * bounds.size.z;
}
/// <summary>
/// Returns bounds that contain both this bounds and the bounds passed in.
/// </summary>
public static Bounds ExpandToContain(this Bounds originalBounds, Bounds otherBounds)
{
Bounds tmpBounds = originalBounds;
tmpBounds.Encapsulate(otherBounds);
return tmpBounds;
}
/// <summary>
/// Checks to see if bounds contains the other bounds completely.
/// </summary>
public static bool ContainsBounds(this Bounds bounds, Bounds otherBounds)
{
return bounds.Contains(otherBounds.min) && bounds.Contains(otherBounds.max);
}
/// <summary>
/// Checks to see whether point is closer to bounds or otherBounds
/// </summary>
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);
}
/// <summary>
/// Draws a wire frame <see href="https://docs.unity3d.com/ScriptReference/Bounds.html">Bounds</see> object using <see href="https://docs.unity3d.com/ScriptReference/Debug.DrawLine.html">Debug.DrawLine</see>.
/// </summary>
/// <param name="bounds">The <see href="https://docs.unity3d.com/ScriptReference/Bounds.html">Bounds</see> to draw.</param>
/// <param name="color">Color of the line.</param>
/// <param name="duration">How long the line should be visible for in seconds.</param>
/// <param name="depthTest">Should the line be obscured by objects closer to the camera?</param>
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
/// <summary>
/// Returns the vector which is used to represent and invalid bounds size.
/// </summary>
private static Vector3 GetInvalidBoundsSize()
{
return new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
}
#endregion
}
}