// Copyright (c) Craftwork Games. All rights reserved.
// Licensed under the MIT license.
// See LICENSE file in the project root for full license information.
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
using Microsoft.Xna.Framework;
namespace MonoGame.Extended
{
///
/// Represents an axis-aligned bounding box in 2D space, defined by minimum and maximum corner points.
///
[DataContract]
[DebuggerDisplay("{DebugDisplayString,nq}")]
public struct BoundingBox2D : IEquatable
{
///
/// The number of corners in this bounding box.
///
public const int CornerCount = 4;
#region Public Fields
///
/// The minimum corner position with the smallest X and Y coordinates.
///
[DataMember]
public Vector2 Min;
///
/// The maximum corner position with the largest X and Y coordinates.
///
[DataMember]
public Vector2 Max;
#endregion
#region Public Properties
///
/// Gets the center position of this bounding box in 2D space.
///
public readonly Vector2 Center => (Min + Max) * 0.5f;
///
/// Gets the size of this bounding box as width and height.
///
public readonly Vector2 Size => Max - Min;
///
/// Gets the half extents of this bounding box, representing the distance from the center to each edge.
///
public readonly Vector2 HalfExtents => (Max - Min) * 0.5f;
///
/// Gets the width of this bounding box.
///
public readonly float Width => Max.X - Min.X;
///
/// Gets the height of this bounding box.
///
public readonly float Height => Max.Y - Min.Y;
///
/// Gets the area enclosed by this bounding box.
///
public readonly float Area => Width * Height;
#endregion
#region Internal Properties
internal string DebugDisplayString
{
get
{
return string.Concat(
"Min( ", Min.ToString(), " ) \r\n",
"Max( ", Max.ToString(), " )"
);
}
}
#endregion
#region Public Constructors
///
/// Creates a new with the specified minimum and maximum corners.
///
/// The minimum corner, containing the smallest X and Y coordinate values.
/// The maximum corner, containing the largest X and Y coordinate values.
public BoundingBox2D(Vector2 min, Vector2 max)
{
Min = min;
Max = max;
}
#endregion
#region Public Methods
///
/// Creates a new from the specified minimum and maximum corners.
///
/// The minimum corner, containing the smallest X and Y coordinate values.
/// The maximum corner, containing the largest X and Y coordinate values.
///
/// A new with the specified corners.
///
public static BoundingBox2D CreateFromMinMax(Vector2 min, Vector2 max)
{
return new BoundingBox2D(min, max);
}
///
/// Creates a new from a center position and half extents.
///
/// The center position of the bounding box in 2D space.
///
/// The half extents representing the distance from the center to each edge (half-width and half-height).
///
///
/// A new centered at the specified position with the given extents.
///
public static BoundingBox2D CreateFromCenterAndExtents(Vector2 center, Vector2 halfExtents)
{
return new BoundingBox2D(center - halfExtents, center + halfExtents);
}
///
/// Creates a new from a minimum corner position and size.
///
/// The minimum corner position in 2D space.
/// The size as width and height.
///
/// A new with the specified position and size.
///
public static BoundingBox2D CreateFromPositionAndSize(Vector2 position, Vector2 size)
{
return new BoundingBox2D(position, position + size);
}
///
/// Creates a new that encloses all specified points.
///
/// The array of points to enclose within the bounding box.
///
/// A new with minimum and maximum corners positioned to contain all the specified points.
///
///
/// Thrown when is .
///
///
/// Thrown when is empty.
///
public static BoundingBox2D CreateFromPoints(Vector2[] points)
{
if (points == null)
{
throw new ArgumentNullException(nameof(points));
}
if (points.Length == 0)
{
throw new ArgumentException("Cannot create bounding box from empty point array.", nameof(points));
}
Vector2 min = new Vector2(float.MaxValue, float.MaxValue);
Vector2 max = new Vector2(float.MinValue, float.MinValue);
for (int i = 0; i < points.Length; i++)
{
if (points[i].X < min.X) min.X = points[i].X;
if (points[i].Y < min.Y) min.Y = points[i].Y;
if (points[i].X > max.X) max.X = points[i].X;
if (points[i].Y > max.Y) max.Y = points[i].Y;
}
return new BoundingBox2D(min, max);
}
///
/// Creates a new that encloses two bounding boxes.
///
/// The first bounding box to enclose.
/// The second bounding box to enclose.
///
/// A new that completely contains both input bounding boxes.
///
public static BoundingBox2D CreateMerged(BoundingBox2D original, BoundingBox2D additional)
{
BoundingBox2D result;
result.Min.X = MathF.Min(original.Min.X, additional.Min.X);
result.Min.Y = MathF.Min(original.Min.Y, additional.Min.Y);
result.Max.X = MathF.Max(original.Max.X, additional.Max.X);
result.Max.Y = MathF.Max(original.Max.Y, additional.Max.Y);
return result;
}
///
/// Gets an array containing the four corner positions of this bounding box.
///
///
/// An array of 4 corner positions in counter-clockwise order starting from the minimum corner:
/// bottom-left, bottom-right, top-right, top-left.
///
public readonly Vector2[] GetCorners()
{
return new Vector2[CornerCount]
{
new Vector2(Min.X, Min.Y),
new Vector2(Max.X, Min.Y),
new Vector2(Max.X, Max.Y),
new Vector2(Min.X, Max.Y)
};
}
///
/// Writes the four corner positions of this bounding box into an existing array.
///
///
/// The array to write corner positions into. Must have at least 4 elements.
/// Corners are written in counter-clockwise order starting from the minimum corner.
///
///
/// Thrown when is .
///
///
/// Thrown when has fewer than 4 elements.
///
public readonly void GetCorners(Vector2[] corners)
{
if (corners == null)
throw new ArgumentNullException(nameof(corners));
if (corners.Length < CornerCount)
throw new ArgumentException($"Array must have at least {CornerCount} elements", nameof(corners));
corners[0] = new Vector2(Min.X, Min.Y);
corners[1] = new Vector2(Max.X, Min.Y);
corners[2] = new Vector2(Max.X, Max.Y);
corners[3] = new Vector2(Min.X, Max.Y);
}
///
/// Applies a matrix transformation to this bounding box and creates a new axis-aligned bounding box that encloses the result.
///
/// The transformation matrix to apply.
///
/// A new axis-aligned that completely contains all corners of this bounding box after transformation.
///
///
/// When a transformation includes rotation or skew, the transformed corners of the original box may no longer
/// be axis-aligned. This method computes a new axis-aligned bounding box that encloses all transformed corners,
/// which may be larger than the original if rotation is applied.
///
public readonly BoundingBox2D Transform(Matrix matrix)
{
Vector2 transformedCenter = Vector2.Transform(Center, matrix);
// For each axis, sum the absolute values of the transformed half-extents.
// This gives us the new box that bounds the rotated original
Vector2 newHalfExtents;
newHalfExtents.X = MathF.Abs(matrix.M11) * HalfExtents.X
+ MathF.Abs(matrix.M12) * HalfExtents.Y;
newHalfExtents.Y = MathF.Abs(matrix.M21) * HalfExtents.X
+ MathF.Abs(matrix.M22) * HalfExtents.Y;
return CreateFromCenterAndExtents(transformedCenter, newHalfExtents);
}
///
/// Creates a new by translating this bounding box by the specified offset.
///
/// The offset to translate the bounding box by in 2D space.
///
/// A new at the translated position with the same size.
///
public readonly BoundingBox2D Translate(Vector2 translation)
{
return new BoundingBox2D(Min + translation, Max + translation);
}
///
/// Tests whether a point lies inside this bounding box or on its boundary.
///
/// The point to test in 2D space.
///
/// if the point is inside or on the boundary;
/// otherwise, if the point is outside.
///
public readonly ContainmentType Contains(Vector2 point)
{
return Collision2D.ContainsAabbPoint(point, Min, Max);
}
///
/// Tests whether this bounding box contains, intersects, or is separate from another bounding box.
///
/// The other bounding box to test against.
///
/// if the other bounding box is completely inside this one;
/// if they partially overlap;
/// or if they do not touch.
///
public readonly ContainmentType Contains(BoundingBox2D other)
{
return Collision2D.ContainsAabbAabb(Min, Max, other.Min, other.Max);
}
///
/// Tests whether this bounding box contains, intersects, or is separate from a circle.
///
/// The circle to test against.
///
/// if the circle is completely inside this bounding box;
/// if they partially overlap;
/// or if they do not touch.
///
public readonly ContainmentType Contains(BoundingCircle2D circle)
{
return Collision2D.ContainsAabbCircle(Min, Max, circle.Center, circle.Radius);
}
///
/// Tests whether this bounding box contains, intersects, or is separate from an oriented bounding box.
///
/// The oriented bounding box to test against.
///
/// if the oriented bounding box is completely inside this bounding box;
/// if they partially overlap;
/// or if they do not touch.
///
public readonly ContainmentType Contains(OrientedBoundingBox2D obb)
{
return Collision2D.ContainsAabbObb(Min, Max, obb.Center, obb.AxisX, obb.AxisY, obb.HalfExtents);
}
///
/// Tests whether this bounding box contains, intersects, or is separate from a capsule.
///
/// The capsule to test against.
///
/// if the capsule is completely inside this bounding box;
/// if they partially overlap;
/// or if they do not touch.
///
public readonly ContainmentType Contains(BoundingCapsule2D capsule)
{
return Collision2D.ContainsAabbCapsule(Min, Max, capsule.PointA, capsule.PointB, capsule.Radius);
}
///
/// Tests whether this bounding box contains, intersects, or is separate from a polygon.
///
/// The polygon to test against.
///
/// if the polygon is completely inside this bounding box;
/// if they partially overlap;
/// or if they do not touch.
///
public readonly ContainmentType Contains(BoundingPolygon2D polygon)
{
return Collision2D.ContainsAabbConvexPolygon(Min, Max, polygon.Vertices, polygon.Normals);
}
///
/// Tests whether this bounding box intersects with another bounding box.
///
/// The other bounding box to test against.
///
/// if the bounding boxes overlap or touch; otherwise, .
///
public readonly bool Intersects(BoundingBox2D other)
{
return Collision2D.IntersectsAabbAabb(Min, Max, other.Min, other.Max);
}
///
/// Tests whether this bounding box intersects with a circle.
///
/// The circle to test against.
///
/// if the bounding box and circle overlap or touch; otherwise, .
///
public readonly bool Intersects(BoundingCircle2D circle)
{
return Collision2D.IntersectsCircleAabb(circle.Center, circle.Radius, Min, Max);
}
///
/// Tests whether this bounding box intersects with a capsule.
///
/// The capsule to test against.
///
/// if the bounding box and capsule overlap or touch; otherwise, .
///
public readonly bool Intersects(BoundingCapsule2D capsule)
{
return Collision2D.IntersectsAabbCapsule(Min, Max, capsule.PointA, capsule.PointB, capsule.Radius);
}
///
/// Tests whether this bounding box intersects with an oriented bounding box.
///
/// The oriented bounding box to test against.
///
/// if the boxes overlap or touch; otherwise, .
///
public readonly bool Intersects(OrientedBoundingBox2D obb)
{
return Collision2D.IntersectsAabbObb(Center, HalfExtents, obb.Center, obb.AxisX, obb.AxisY, obb.HalfExtents);
}
///
/// Tests whether this bounding box intersects with a polygon.
///
/// The polygon to test against.
///
/// if the box and polygon overlap or touch; otherwise, .
///
public readonly bool Intersects(BoundingPolygon2D polygon)
{
return Collision2D.IntersectsAabbConvexPolygon(Center, HalfExtents, polygon.Vertices, polygon.Normals);
}
///
/// Deconstructs this bounding box into its component values.
///
///
/// When this method returns, contains the minimum corner, containing the smallest X and Y coordinate values.
///
///
/// When this method returns, contains the maximum corner, containing the largest X and Y coordinate values.
///
public readonly void Deconstruct(out Vector2 min, out Vector2 max)
{
min = Min;
max = Max;
}
///
public readonly bool Equals(BoundingBox2D other)
{
return Min.Equals(other.Min) && Max.Equals(other.Max);
}
///
public override readonly bool Equals(object obj)
{
return obj is BoundingBox2D other && Equals(other);
}
///
public override readonly int GetHashCode()
{
return Min.GetHashCode() ^ Max.GetHashCode();
}
///
public override readonly string ToString()
{
return $"{{Min:{Min} Max:{Max}}}";
}
///
public static bool operator ==(BoundingBox2D left, BoundingBox2D right)
{
return left.Equals(right);
}
///
public static bool operator !=(BoundingBox2D left, BoundingBox2D right)
{
return !left.Equals(right);
}
#endregion
}
}