#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