#region License
/* FNA - XNA4 Reimplementation for Desktop Platforms
* Copyright 2009-2016 Ethan Lee and the MonoGame Team
*
* Released under the Microsoft Public License.
* See LICENSE for details.
*/
/* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.Xna.Framework.Design;
#endregion
namespace Microsoft.Xna.Framework
{
///
/// Describes a sphere in 3D-space for bounding operations.
///
[Serializable]
[TypeConverter(typeof(BoundingSphereConverter))]
[DebuggerDisplay("{DebugDisplayString,nq}")]
public struct BoundingSphere : IEquatable
{
#region Internal Properties
internal string DebugDisplayString
{
get
{
return string.Concat(
"Center( ", Center.DebugDisplayString, " ) \r\n",
"Radius( ", Radius.ToString(), " ) "
);
}
}
#endregion
#region Public Fields
///
/// The sphere center.
///
public Vector3 Center;
///
/// The sphere radius.
///
public float Radius;
#endregion
#region Public Constructors
///
/// Constructs a bounding sphere with the specified center and radius.
///
/// The sphere center.
/// The sphere radius.
public BoundingSphere(Vector3 center, float radius)
{
this.Center = center;
this.Radius = radius;
}
#endregion
#region Public Methods
///
/// Creates a new that contains a transformation of translation and scale from this sphere by the specified .
///
/// The transformation .
/// Transformed .
public BoundingSphere Transform(Matrix matrix)
{
BoundingSphere sphere = new BoundingSphere();
sphere.Center = Vector3.Transform(this.Center, matrix);
sphere.Radius = this.Radius *
(
(float) Math.Sqrt((double) Math.Max(
((matrix.M11 * matrix.M11) + (matrix.M12 * matrix.M12)) + (matrix.M13 * matrix.M13),
Math.Max(
((matrix.M21 * matrix.M21) + (matrix.M22 * matrix.M22)) + (matrix.M23 * matrix.M23),
((matrix.M31 * matrix.M31) + (matrix.M32 * matrix.M32)) + (matrix.M33 * matrix.M33))
)
)
);
return sphere;
}
///
/// Creates a new that contains a transformation of translation and scale from this sphere by the specified .
///
/// The transformation .
/// Transformed as an output parameter.
public void Transform(ref Matrix matrix, out BoundingSphere result)
{
result.Center = Vector3.Transform(this.Center, matrix);
result.Radius = this.Radius *
(
(float) Math.Sqrt((double) Math.Max(
((matrix.M11 * matrix.M11) + (matrix.M12 * matrix.M12)) + (matrix.M13 * matrix.M13),
Math.Max(
((matrix.M21 * matrix.M21) + (matrix.M22 * matrix.M22)) + (matrix.M23 * matrix.M23),
((matrix.M31 * matrix.M31) + (matrix.M32 * matrix.M32)) + (matrix.M33 * matrix.M33))
)
)
);
}
///
/// Test if a bounding box is fully inside, outside, or just intersecting the sphere.
///
/// The box for testing.
/// The containment type as an output parameter.
public void Contains(ref BoundingBox box, out ContainmentType result)
{
result = this.Contains(box);
}
///
/// Test if a sphere is fully inside, outside, or just intersecting the sphere.
///
/// The other sphere for testing.
/// The containment type as an output parameter.
public void Contains(ref BoundingSphere sphere, out ContainmentType result)
{
result = Contains(sphere);
}
///
/// Test if a point is fully inside, outside, or just intersecting the sphere.
///
/// The vector in 3D-space for testing.
/// The containment type as an output parameter.
public void Contains(ref Vector3 point, out ContainmentType result)
{
result = Contains(point);
}
///
/// Test if a bounding box is fully inside, outside, or just intersecting the sphere.
///
/// The box for testing.
/// The containment type.
public ContainmentType Contains(BoundingBox box)
{
// Check if all corners are in sphere.
bool inside = true;
foreach (Vector3 corner in box.GetCorners())
{
if (this.Contains(corner) == ContainmentType.Disjoint)
{
inside = false;
break;
}
}
if (inside)
{
return ContainmentType.Contains;
}
// Check if the distance from sphere center to cube face is less than radius.
double dmin = 0;
if (Center.X < box.Min.X)
{
dmin += (Center.X - box.Min.X) * (Center.X - box.Min.X);
}
else if (Center.X > box.Max.X)
{
dmin += (Center.X - box.Max.X) * (Center.X - box.Max.X);
}
if (Center.Y < box.Min.Y)
{
dmin += (Center.Y - box.Min.Y) * (Center.Y - box.Min.Y);
}
else if (Center.Y > box.Max.Y)
{
dmin += (Center.Y - box.Max.Y) * (Center.Y - box.Max.Y);
}
if (Center.Z < box.Min.Z)
{
dmin += (Center.Z - box.Min.Z) * (Center.Z - box.Min.Z);
}
else if (Center.Z > box.Max.Z)
{
dmin += (Center.Z - box.Max.Z) * (Center.Z - box.Max.Z);
}
if (dmin <= Radius * Radius)
{
return ContainmentType.Intersects;
}
// Else disjoint
return ContainmentType.Disjoint;
}
///
/// Test if a frustum is fully inside, outside, or just intersecting the sphere.
///
/// The box for testing.
/// The containment type as an output parameter.
public ContainmentType Contains(BoundingFrustum frustum)
{
// Check if all corners are in sphere.
bool inside = true;
Vector3[] corners = frustum.GetCorners();
foreach (Vector3 corner in corners)
{
if (this.Contains(corner) == ContainmentType.Disjoint)
{
inside = false;
break;
}
}
if (inside)
{
return ContainmentType.Contains;
}
// Check if the distance from sphere center to frustrum face is less than radius.
double dmin = 0;
// TODO : calcul dmin
if (dmin <= Radius * Radius)
{
return ContainmentType.Intersects;
}
// Else disjoint
return ContainmentType.Disjoint;
}
///
/// Test if a sphere is fully inside, outside, or just intersecting the sphere.
///
/// The other sphere for testing.
/// The containment type.
public ContainmentType Contains(BoundingSphere sphere)
{
float sqDistance;
Vector3.DistanceSquared(ref sphere.Center, ref Center, out sqDistance);
if (sqDistance > (sphere.Radius + Radius) * (sphere.Radius + Radius))
{
return ContainmentType.Disjoint;
}
else if (sqDistance <= (Radius * sphere.Radius) * (Radius - sphere.Radius))
{
return ContainmentType.Contains;
}
return ContainmentType.Intersects;
}
///
/// Test if a point is fully inside, outside, or just intersecting the sphere.
///
/// The vector in 3D-space for testing.
/// The containment type.
public ContainmentType Contains(Vector3 point)
{
float sqRadius = Radius * Radius;
float sqDistance;
Vector3.DistanceSquared(ref point, ref Center, out sqDistance);
if (sqDistance > sqRadius)
{
return ContainmentType.Disjoint;
}
else if (sqDistance < sqRadius)
{
return ContainmentType.Contains;
}
return ContainmentType.Intersects;
}
///
/// Compares whether current instance is equal to specified .
///
/// The to compare.
/// true if the instances are equal; false otherwise.
public bool Equals(BoundingSphere other)
{
return ( this.Center == other.Center &&
MathHelper.WithinEpsilon(this.Radius, other.Radius) );
}
#endregion
#region Public Static Methods
///
/// Creates the smallest that can contain a specified .
///
/// The box to create the sphere from.
/// The new .
public static BoundingSphere CreateFromBoundingBox(BoundingBox box)
{
BoundingSphere result;
CreateFromBoundingBox(ref box, out result);
return result;
}
///
/// Creates the smallest that can contain a specified .
///
/// The box to create the sphere from.
/// The new as an output parameter.
public static void CreateFromBoundingBox(ref BoundingBox box, out BoundingSphere result)
{
// Find the center of the box.
Vector3 center = new Vector3(
(box.Min.X + box.Max.X) / 2.0f,
(box.Min.Y + box.Max.Y) / 2.0f,
(box.Min.Z + box.Max.Z) / 2.0f
);
// Find the distance between the center and one of the corners of the box.
float radius = Vector3.Distance(center, box.Max);
result = new BoundingSphere(center, radius);
}
///
/// Creates the smallest that can contain a specified .
///
/// The frustum to create the sphere from.
/// The new .
public static BoundingSphere CreateFromFrustum(BoundingFrustum frustum)
{
return CreateFromPoints(frustum.GetCorners());
}
///
/// Creates the smallest that can contain a specified list of points in 3D-space.
///
/// List of point to create the sphere from.
/// The new .
public static BoundingSphere CreateFromPoints(IEnumerable points)
{
if (points == null)
{
throw new ArgumentNullException("points");
}
// From "Real-Time Collision Detection" (Page 89)
Vector3 minx = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
Vector3 maxx = -minx;
Vector3 miny = minx;
Vector3 maxy = -minx;
Vector3 minz = minx;
Vector3 maxz = -minx;
// Find the most extreme points along the principle axis.
int numPoints = 0;
foreach (Vector3 pt in points)
{
numPoints += 1;
if (pt.X < minx.X)
{
minx = pt;
}
if (pt.X > maxx.X)
{
maxx = pt;
}
if (pt.Y < miny.Y)
{
miny = pt;
}
if (pt.Y > maxy.Y)
{
maxy = pt;
}
if (pt.Z < minz.Z)
{
minz = pt;
}
if (pt.Z > maxz.Z)
{
maxz = pt;
}
}
if (numPoints == 0)
{
throw new ArgumentException(
"You should have at least one point in points."
);
}
float sqDistX = Vector3.DistanceSquared(maxx, minx);
float sqDistY = Vector3.DistanceSquared(maxy, miny);
float sqDistZ = Vector3.DistanceSquared(maxz, minz);
// Pick the pair of most distant points.
Vector3 min = minx;
Vector3 max = maxx;
if (sqDistY > sqDistX && sqDistY > sqDistZ)
{
max = maxy;
min = miny;
}
if (sqDistZ > sqDistX && sqDistZ > sqDistY)
{
max = maxz;
min = minz;
}
Vector3 center = (min + max) * 0.5f;
float radius = Vector3.Distance(max, center);
// Test every point and expand the sphere.
// The current bounding sphere is just a good approximation and may not enclose all points.
// From: Mathematics for 3D Game Programming and Computer Graphics, Eric Lengyel, Third Edition.
// Page 218
float sqRadius = radius * radius;
foreach (Vector3 pt in points)
{
Vector3 diff = (pt - center);
float sqDist = diff.LengthSquared();
if (sqDist > sqRadius)
{
float distance = (float) Math.Sqrt(sqDist); // equal to diff.Length();
Vector3 direction = diff / distance;
Vector3 G = center - radius * direction;
center = (G + pt) / 2;
radius = Vector3.Distance(pt, center);
sqRadius = radius * radius;
}
}
return new BoundingSphere(center, radius);
}
///
/// Creates the smallest that can contain two spheres.
///
/// First sphere.
/// Second sphere.
/// The new .
public static BoundingSphere CreateMerged(BoundingSphere original, BoundingSphere additional)
{
BoundingSphere result;
CreateMerged(ref original, ref additional, out result);
return result;
}
///
/// Creates the smallest that can contain two spheres.
///
/// First sphere.
/// Second sphere.
/// The new as an output parameter.
public static void CreateMerged(
ref BoundingSphere original,
ref BoundingSphere additional,
out BoundingSphere result
) {
Vector3 ocenterToaCenter = Vector3.Subtract(additional.Center, original.Center);
float distance = ocenterToaCenter.Length();
// Intersect
if (distance <= original.Radius + additional.Radius)
{
// Original contains additional.
if (distance <= original.Radius - additional.Radius)
{
result = original;
return;
}
// Additional contains original.
if (distance <= additional.Radius - original.Radius)
{
result = additional;
return;
}
}
// Else find center of new sphere and radius
float leftRadius = Math.Max(original.Radius - distance, additional.Radius);
float Rightradius = Math.Max(original.Radius + distance, additional.Radius);
// oCenterToResultCenter
ocenterToaCenter = ocenterToaCenter +
(
((leftRadius - Rightradius) / (2 * ocenterToaCenter.Length()))
* ocenterToaCenter
);
result = new BoundingSphere();
result.Center = original.Center + ocenterToaCenter;
result.Radius = (leftRadius + Rightradius) / 2;
}
///
/// Gets whether or not a specified intersects with this sphere.
///
/// The box for testing.
/// true if intersects with this sphere; false otherwise.
public bool Intersects(BoundingBox box)
{
return box.Intersects(this);
}
///
/// Gets whether or not a specified intersects with this sphere.
///
/// The box for testing.
/// true if intersects with this sphere; false otherwise. As an output parameter.
public void Intersects(ref BoundingBox box, out bool result)
{
box.Intersects(ref this, out result);
}
public bool Intersects(BoundingFrustum frustum)
{
return frustum.Intersects(this);
}
///
/// Gets whether or not the other intersects with this sphere.
///
/// The other sphere for testing.
/// true if other intersects with this sphere; false otherwise.
public bool Intersects(BoundingSphere sphere)
{
bool result;
Intersects(ref sphere, out result);
return result;
}
///
/// Gets whether or not the other intersects with this sphere.
///
/// The other sphere for testing.
/// true if other intersects with this sphere; false otherwise. As an output parameter.
public void Intersects(ref BoundingSphere sphere, out bool result)
{
float sqDistance;
Vector3.DistanceSquared(ref sphere.Center, ref Center, out sqDistance);
result = !(sqDistance > (sphere.Radius + Radius) * (sphere.Radius + Radius));
}
///
/// Gets whether or not a specified intersects with this sphere.
///
/// The ray for testing.
/// Distance of ray intersection or null if there is no intersection.
public float? Intersects(Ray ray)
{
return ray.Intersects(this);
}
///
/// Gets whether or not a specified intersects with this sphere.
///
/// The ray for testing.
/// Distance of ray intersection or null if there is no intersection as an output parameter.
public void Intersects(ref Ray ray, out float? result)
{
ray.Intersects(ref this, out result);
}
///
/// Gets whether or not a specified intersects with this sphere.
///
/// The plane for testing.
/// Type of intersection.
public PlaneIntersectionType Intersects(Plane plane)
{
PlaneIntersectionType result = default(PlaneIntersectionType);
// TODO: We might want to inline this for performance reasons.
this.Intersects(ref plane, out result);
return result;
}
///
/// Gets whether or not a specified intersects with this sphere.
///
/// The plane for testing.
/// Type of intersection as an output parameter.
public void Intersects(ref Plane plane, out PlaneIntersectionType result)
{
float distance = default(float);
// TODO: We might want to inline this for performance reasons.
Vector3.Dot(ref plane.Normal, ref this.Center, out distance);
distance += plane.D;
if (distance > this.Radius)
{
result = PlaneIntersectionType.Front;
}
else if (distance < -this.Radius)
{
result = PlaneIntersectionType.Back;
}
else
{
result = PlaneIntersectionType.Intersecting;
}
}
#endregion
#region Public Static Operators and Override Methods
///
/// Compares whether current instance is equal to specified .
///
/// The to compare.
/// true if the instances are equal; false otherwise.
public override bool Equals(object obj)
{
if (obj is BoundingSphere)
{
return this.Equals((BoundingSphere)obj);
}
return false;
}
///
/// Gets the hash code of this .
///
/// Hash code of this .
public override int GetHashCode()
{
return this.Center.GetHashCode() + this.Radius.GetHashCode();
}
///
/// Returns a representation of this in the format:
/// {Center:[] Radius:[]}
///
/// A representation of this .
public override string ToString()
{
return (
"{Center:" + Center.ToString() +
" Radius:" + Radius.ToString() +
"}"
);
}
///
/// Compares whether two instances are equal.
///
/// instance on the left of the equal sign.
/// instance on the right of the equal sign.
/// true if the instances are equal; false otherwise.
public static bool operator ==(BoundingSphere a, BoundingSphere b)
{
return a.Equals(b);
}
///
/// Compares whether two instances are not equal.
///
/// instance on the left of the not equal sign.
/// instance on the right of the not equal sign.
/// true if the instances are not equal; false otherwise.
public static bool operator !=(BoundingSphere a, BoundingSphere b)
{
return !a.Equals(b);
}
#endregion
}
}