#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Diagnostics;
using Microsoft.Xna.Framework;
namespace Nuclex.Geometry.Volumes {
/// A two-dimensional circle
#if !NO_SERIALIZATION
[Serializable]
#endif
public class Sphere3 : IVolume3 {
/// Initializes a new sphere
/// The center of the circle
/// Radius the circle will have
[DebuggerStepThrough]
public Sphere3(Vector3 center, float radius) {
Center = center;
Radius = radius;
}
/// Initializes a new sphere as copy of an existing sphere
/// Existing sphere that will be copied
[DebuggerStepThrough]
public Sphere3(Sphere3 other)
: this(other.Center, other.Radius) { }
/// Accepts a visitor to access the concrete volume implementation
/// Visitor to be accepted
public void Accept(VolumeVisitor visitor) {
visitor.Visit(this);
}
/// Smallest box that encloses the volume in its entirety
///
/// This always produces an optimal box which means a tight-fitting box is generated
/// that will touch the volume on each of its six sides. As a side effect, it is very
/// likely that this box needs to be recalculated whenever the volume changes its
/// orientation.
///
public AxisAlignedBox3 BoundingBox {
get {
return new AxisAlignedBox3(
new Vector3(Center.X - Radius, Center.Y - Radius, Center.Z - Radius),
new Vector3(Center.X + Radius, Center.Y + Radius, Center.Z + Radius)
);
}
}
/// Smallest sphere that encloses the volume in its entirety
///
/// Bounding spheres have the advantage to not change even when the volume is
/// rotated. That makes them ideal for dynamic objects that are not keeping their
/// original orientation.
///
public Sphere3 BoundingSphere {
get { return new Sphere3(Center, Radius); } // Create a copy to be on the safe side...
}
/// Amount of mass that the volume contains
public float Mass {
get { return 4.0f / 3.0f * MathHelper.Pi * (this.Radius * this.Radius * this.Radius); }
}
/// The volume's total surface area
public float SurfaceArea {
get { return 4.0f * MathHelper.Pi * (this.Radius * this.Radius); }
}
/// Center of the volume's mass
public Vector3 CenterOfMass {
get { return Center; }
}
/// The inertia tensor matrix of the volume
public Matrix InertiaTensor {
get {
// 2 * (r ^ 2) / 5
float r = 0.4f * this.Radius * this.Radius;
return new Matrix(
r, 0.0f, 0.0f, 0.0f,
0.0f, r, 0.0f, 0.0f,
0.0f, 0.0f, r, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
}
}
/// Locates the nearest point in the volume to some arbitrary location
/// Location to which the closest point is determined
/// The closest point in the volume to the specified location
public Vector3 ClosestPointTo(Vector3 location) {
Vector3 offset = location - Center;
float distance = offset.Length();
if(distance < Radius)
return location;
else
return this.Center + (offset * (this.Radius / distance));
}
/// Determines whether a point is inside the sphere
/// Point to be checked
/// True if the point lies within the sphere
public bool Contains(Vector3 point) {
float distance = (point - this.Center).Length();
return (distance * distance) < this.Radius;
}
/// Determines if the volume clips the circle
/// Circle that will be checked for intersection
/// True if the objects overlap
public bool Intersects(Sphere3 sphere) {
return Collisions.SphereSphereCollider.CheckContact(
this.Center, this.Radius, sphere.Center, sphere.Radius
);
}
/// Determines if the volume clips the axis aligned box
/// Box that will be checked for intersection
/// True if the objects overlap
public bool Intersects(AxisAlignedBox3 box) {
return Collisions.AabbSphereCollider.CheckContact(
box.Min, box.Max, this.Center, this.Radius
);
}
/// Determines if the volume clips the box
/// Box that will be checked for intersection
/// True if the objects overlap
public bool Intersects(Box3 box) {
return Collisions.ObbSphereCollider.CheckContact(
box.Transform, box.Extents, this.Center, this.Radius
);
}
/// Returns a random point on the volume's surface
/// Random number generator that will be used
/// A random point on the volume's surface
public Vector3 RandomPointOnSurface(IRandom randomNumberGenerator) {
return PointGenerators.SpherePointGenerator.GenerateRandomPointOnSurface(
randomNumberGenerator, this.Radius
) + this.Center;
}
/// Returns a random point within the volume
/// Random number generator that will be used
/// A random point within the volume
public Vector3 RandomPointWithin(IRandom randomNumberGenerator) {
return PointGenerators.SpherePointGenerator.GenerateRandomPointWithin(
randomNumberGenerator, this.Radius
) + this.Center;
}
/// Determines if the volume will impact on a sphere
/// Velocity with which this volume is moving
/// Sphere that will be checked for intersection
/// The point of first contact, if any
///
///
/// Conventional tests that resort to stepping often fail to detect collisions
/// between fast-moving objects. This impact determination test will always
/// detect a collision if it occurs, giving the exact time of the impact.
///
///
/// This is a simplified test that assumes a linear trajectory and does
/// not take off-center object rotation into account. It is well suited to use
/// on two bounding spheres in order to determine if a collision between the
/// shape contained is possible at all.
///
///
/// Ideas taken from the "Simple Intersection Tests for Games" article
/// on gamasutra by Gomez.
///
///
public float[] LocateImpact(Vector3 thisVelocity, Sphere3 sphere) {
Vector3 distance = Center - sphere.Center;
float radii = Radius + sphere.Radius;
float radii2 = radii * radii;
// Already inside the other circle
if(distance.LengthSquared() < radii2)
return new float[] { 0.0f };
float a = thisVelocity.LengthSquared();
float b = Vector3.Dot(thisVelocity, distance) * 2.0f;
float c = distance.LengthSquared() - radii2;
float q = b * b - 4.0f * a * c;
// If the other sphere is not crossing our location, then no impact will happen
if(q < 0.0)
return null;
float sq = (float)Math.Sqrt(q);
float d = 1.0f / (2.0f * a);
float r1 = (-b + sq) * d;
float r2 = (-b - sq) * d;
if(r1 < r2)
return new float[] { r1 };
else
return new float[] { r2 };
}
/// Determines if two spheres are equal
/// First sphere to be compared
/// Second sphere to be compared
/// True if both spheres are equal
[System.Diagnostics.DebuggerStepThrough]
public static bool operator ==(Sphere3 first, Sphere3 second) {
return (first.Center == second.Center) && (first.Radius == second.Radius);
}
/// Determines if two spheres are unequal
/// First sphere to be compared
/// Second sphere to be compared
/// True if both spheres are unequal
[System.Diagnostics.DebuggerStepThrough]
public static bool operator !=(Sphere3 first, Sphere3 second) {
return (first.Center != second.Center) || (first.Radius != second.Radius);
}
/// Determines if an object is identical to the sphere
/// Object to compare to
/// True if the object is identical to the sphere
public override bool Equals(object obj) {
if(obj is Sphere3)
return this == (obj as Sphere3);
else
return false;
}
/// Builds a hashing code for the instance
/// The instance's hashing code
public override int GetHashCode() {
return Center.GetHashCode() ^ Radius.GetHashCode();
}
/// Converts the sphere to a readable string representation
/// The sphere as a string
public override string ToString() {
return "{ " + Center.ToString() + " R:" + Radius + " }";
}
/// The center of the circle
public Vector3 Center;
/// Radius of the circle
public float Radius;
}
} // namespace Nuclex.Geometry.Volumes