#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