#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 Microsoft.Xna.Framework;
namespace Nuclex.Geometry.Volumes {
  /// Three-dimensional box with arbitrary orientation
#if !NO_SERIALIZATION
  [Serializable]
#endif
  public class Box3 : IVolume3 {
    /// Initializes a new instance of the oriented box
    /// 
    ///   Transformation matrix that defines the box' rotation and translation
    /// 
    /// The extents (half the dimensions) of the box
    [System.Diagnostics.DebuggerStepThrough]
    public Box3(Matrix transform, Vector3 extents) {
      this.Transform = transform;
      this.Extents = extents;
    }
    /// Initializes a new oriented box as copy of an existing box
    /// Existing box that will be copied
    [System.Diagnostics.DebuggerStepThrough]
    public Box3(Box3 other)
      : this(other.Transform, other.Extents) { }
    /// 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.
    ///   
    ///   
    ///     This method was actually thought up by myself when all googling did not
    ///     reveal a clever way to avoid the expensive matrix-vector multiplications.
    ///     Feel free to use it in any way you see fit.
    ///   
    /// 
    public AxisAlignedBox3 BoundingBox {
      get {
        // We just calculate one half of the oriented box and obtain the other
        // by mirroring these points
        Vector3[] corners = new Vector3[] {
          new Vector3( this.Extents.X,  this.Extents.Y,  this.Extents.Z),
          new Vector3(-this.Extents.X,  this.Extents.Y,  this.Extents.Z),
          new Vector3( this.Extents.X, -this.Extents.Y,  this.Extents.Z),
          new Vector3( this.Extents.X,  this.Extents.Y, -this.Extents.Z)
        };
        // Transform all points and calculate the maximum distance on each
        // axis in positive direction by using its absolute value
        Vector3 aabbExtents = new Vector3();
        foreach(Vector3 corner in corners)
          aabbExtents = Vector3.Max(
            aabbExtents, VectorHelper.Abs(Vector3.TransformNormal(corner, this.Transform))
          );
        // Now we can just mirror the other direction
        return new AxisAlignedBox3(
          this.Center - aabbExtents,
          this.Center + aabbExtents
        );
      }
    }
    /// 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(this.Center, this.Extents.Length()); }
    }
    /// Amount of mass that the volume contains
    public float Mass {
      get { return Dimensions.X * Dimensions.Y * Dimensions.Z; }
    }
    /// The volume's total surface area
    public float SurfaceArea {
      get {
        // x 4  because the extents are half as wide as the dimensions
        //      if you make your screen half as wide and half as high you've got one
        //      quarter of what it had been before, so we multiply by 4
        // x 2  because we've got two faces
        return 4.0f * 2.0f * (
          (Extents.X * Extents.Y) +
          (Extents.X * Extents.Z) +
          (Extents.Y * Extents.Z)
        );
      }
    }
    /// Center of the volume's mass
    public Vector3 CenterOfMass {
      get { return Center; }
    }
    /// The inetria tensor matrix of the volume
    public Matrix InertiaTensor {
      get {
        float width = this.Extents.X;
        float height = this.Extents.Y;
        float depth = this.Extents.Z;
        return new Matrix(
          (height * height + depth * depth) / 3.0f, 0.0f, 0.0f, 0.0f,
          0.0f, (width * width + depth * depth) / 3.0f, 0.0f, 0.0f,
          0.0f, 0.0f, (width * width + height * height) / 3.0f, 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) {
      // We transform the point to the coordinate frame of the oriented box
      Vector3 difference = location - this.Center;
      Vector3 offset = new Vector3(
        Vector3.Dot(difference, this.Transform.Right),
        Vector3.Dot(difference, this.Transform.Up),
        Vector3.Dot(difference, this.Transform.Forward)
      );
      Vector3 local = new Vector3(
        Math.Min(Math.Max(offset.X, -this.Extents.X), this.Extents.X),
        Math.Min(Math.Max(offset.Y, -this.Extents.Y), this.Extents.Y),
        Math.Min(Math.Max(offset.Z, -this.Extents.Z), this.Extents.Z)
      );
      return Vector3.Transform(local, this.Transform) + this.Center;
    }
    /// 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.ObbSphereCollider.CheckContact(
        this.Transform, this.Extents, 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.AabbObbCollider.CheckContact(
        box.Extents, this.Transform, this.Extents
      );
    }
    /// 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.ObbObbCollider.CheckContact(
        this.Transform, this.Extents, box.Transform, box.Extents
      );
    }
    /// 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.ObbPointGenerator.GenerateRandomPointOnSurface(
        randomNumberGenerator, this.Transform, this.Extents
      ) + 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.ObbPointGenerator.GenerateRandomPointWithin(
        randomNumberGenerator, this.Transform, this.Extents
      ) + this.Center;
    }
    /// The dimensions of this box
    public Vector3 Dimensions {
      get { return Extents * 2.0f; }
    }
    /// Determines if two oriented boxes are equal
    /// First oriented box to be compared
    /// Second oriented box to be compared
    /// True if both axis oriented are equal
    [System.Diagnostics.DebuggerStepThrough]
    public static bool operator ==(Box3 first, Box3 second) {
      return
        (first.Transform == second.Transform) &&
        (first.Center == second.Center) &&
        (first.Extents == second.Extents);
    }
    /// Determines if two oriented boxes are unequal
    /// First oriented box to be compared
    /// Second oriented box to be compared
    /// True if both oriented boxes are unequal
    [System.Diagnostics.DebuggerStepThrough]
    public static bool operator !=(Box3 first, Box3 second) {
      return
        (first.Transform != second.Transform) ||
        (first.Center != second.Center) ||
        (first.Extents != second.Extents);
    }
    /// Determines if an object is identical to the oriented box
    /// Object to compare to
    /// True if the object is identical to the oriented box
    public override bool Equals(object obj) {
      if(obj is Box3)
        return this == (obj as Box3);
      else
        return false;
    }
    /// Builds a hashing code for the instance
    /// The instance's hashing code
    public override int GetHashCode() {
      return Transform.GetHashCode() ^ Center.GetHashCode() ^ Extents.GetHashCode();
    }
    /// Converts the oriented box to a readable string representation
    /// The axis oriented as a string
    public override string ToString() {
      return
        "{ " +
          Transform.ToString() + " C:" + Center.ToString() + " E:" + Extents.ToString() +
        " }";
    }
    /// Location of the box' center
    public Vector3 Center {
      get { return this.Transform.Translation; }
      set { this.Transform.Translation = value; }
    }
    /// Orientation of the box in 3D space
    public Matrix Transform;
    /// Box dimensions in the box' local coordinate system
    /// 
    ///   These are the extents, not the dimensions. The dimensions are the
    ///   total length of the box on each of its three local coordinate axes while
    ///   the extents refer to the distance of each side from the center of the
    ///   box, much like the radius and the diameter of a sphere.
    /// 
    public Vector3 Extents;
  }
} // namespace Nuclex.Geometry.Volumes