//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#ifndef _MBOX_H_
#define _MBOX_H_

#ifndef _MPOINT_H_
#include "math/mPoint.h"
#endif

//------------------------------------------------------------------------------
/// Bounding Box
///
/// A helper class for working with boxes. It runs at F32 precision.
///
/// @see Box3D
class Box3F
{
  public:
   Point3F mMin; ///< Minimum extents of box
   Point3F mMax; ///< Maximum extents of box

  public:
   Box3F() { }

   /// Create a box from two points.
   ///
   /// Normally, this function will compensate for mismatched
   /// min/max values. If you know your values are valid, you
   /// can set in_overrideCheck to true and skip this.
   ///
   /// @param   in_rMin          Minimum extents of box.
   /// @param   in_rMax          Maximum extents of box.
   /// @param   in_overrideCheck  Pass true to skip check of extents.
   Box3F(const Point3F& in_rMin, const Point3F& in_rMax, const bool in_overrideCheck = false);

   /// Create a box from six extent values.
   ///
   /// No checking is performed as to the validity of these
   /// extents, unlike the other constructor.
   Box3F(F32 xmin, F32 ymin, F32 zmin, F32 max, F32 ymax, F32 zmax);

   /// Check to see if a point is contained in this box.
   bool isContained(const Point3F& in_rContained) const;

   /// Check to see if another box overlaps this box.
   bool isOverlapped(const Box3F&  in_rOverlap) const;

   /// Check to see if another box is contained in this box.
   bool isContained(const Box3F& in_rContained) const;

   F32 len_x() const;
   F32 len_y() const;
   F32 len_z() const;

   /// Perform an intersection operation with another box
   /// and store the results in this box.
   void intersect(const Box3F& in_rIntersect);
   void intersect(const Point3F& in_rIntersect);

   /// Get the center of this box.
   ///
   /// This is the average of min and mMax.
   void getCenter(Point3F* center) const;

   /// Collide a line against the box.
   ///
   /// @param   start   Start of line.
   /// @param   end     End of line.
   /// @param   t       Value from 0.0-1.0, indicating position
   ///                  along line of collision.
   /// @param   n       Normal of collision.
   bool collideLine(const Point3F& start, const Point3F& end, F32*t, Point3F*n) const;

   /// Collide a line against the box.
   ///
   /// Returns true on collision.
   bool collideLine(const Point3F& start, const Point3F& end) const;

   /// Collide an oriented box against the box.
   ///
   /// Returns true if "oriented" box collides with us.
   /// Assumes incoming box is centered at origin of source space.
   ///
   /// @param   radii   The dimension of incoming box (half x,y,z length).
   /// @param   toUs    A transform that takes incoming box into our space.
   bool collideOrientedBox(const Point3F & radii, const MatrixF & toUs) const;

   /// Check that the box is valid.
   ///
   /// Currently, this just means that min < mMax.
   bool isValidBox() const { return (mMin.x <= mMax.x) &&
                                    (mMin.y <= mMax.y) &&
                                    (mMin.z <= mMax.z); }

   /// Return the closest point of the box, relative to the passed point.
   Point3F getClosestPoint(const Point3F& refPt) const;
};

inline Box3F::Box3F(const Point3F& in_rMin, const Point3F& in_rMax, const bool in_overrideCheck)
 : mMin(in_rMin),
   mMax(in_rMax)
{
   if (in_overrideCheck == false) {
      mMin.setMin(in_rMax);
      mMax.setMax(in_rMin);
   }
}

inline Box3F::Box3F(F32 xMin, F32 yMin, F32 zMin, F32 xMax, F32 yMax, F32 zMax)
   : mMin(xMin,yMin,zMin),
     mMax(xMax,yMax,zMax)
{
}

inline bool Box3F::isContained(const Point3F& in_rContained) const
{
   return (in_rContained.x >= mMin.x && in_rContained.x < mMax.x) &&
          (in_rContained.y >= mMin.y && in_rContained.y < mMax.y) &&
          (in_rContained.z >= mMin.z && in_rContained.z < mMax.z);
}

inline bool Box3F::isOverlapped(const Box3F& in_rOverlap) const
{
   if (in_rOverlap.mMin.x > mMax.x ||
       in_rOverlap.mMin.y > mMax.y ||
       in_rOverlap.mMin.z > mMax.z)
      return false;
   if (in_rOverlap.mMax.x < mMin.x ||
       in_rOverlap.mMax.y < mMin.y ||
       in_rOverlap.mMax.z < mMin.z)
      return false;
   return true;
}

inline bool Box3F::isContained(const Box3F& in_rContained) const
{
   return (mMin.x <= in_rContained.mMin.x) &&
          (mMin.y <= in_rContained.mMin.y) &&
          (mMin.z <= in_rContained.mMin.z) &&
          (mMax.x >= in_rContained.mMax.x) &&
          (mMax.y >= in_rContained.mMax.y) &&
          (mMax.z >= in_rContained.mMax.z);
}

inline F32 Box3F::len_x() const
{
   return mMax.x - mMin.x;
}

inline F32 Box3F::len_y() const
{
   return mMax.y - mMin.y;
}

inline F32 Box3F::len_z() const
{
   return mMax.z - mMin.z;
}

inline void Box3F::intersect(const Box3F& in_rIntersect)
{
   mMin.setMin(in_rIntersect.mMin);
   mMax.setMax(in_rIntersect.mMax);
}

inline void Box3F::intersect(const Point3F& in_rIntersect)
{
   mMin.setMin(in_rIntersect);
   mMax.setMax(in_rIntersect);
}

inline void Box3F::getCenter(Point3F* center) const
{
   center->x = F32((mMin.x + mMax.x) * 0.5);
   center->y = F32((mMin.y + mMax.y) * 0.5);
   center->z = F32((mMin.z + mMax.z) * 0.5);
}

inline Point3F Box3F::getClosestPoint(const Point3F& refPt) const
{
   Point3F closest;
   if      (refPt.x <= mMin.x) closest.x = mMin.x;
   else if (refPt.x >  mMax.x) closest.x = mMax.x;
   else                       closest.x = refPt.x;

   if      (refPt.y <= mMin.y) closest.y = mMin.y;
   else if (refPt.y >  mMax.y) closest.y = mMax.y;
   else                       closest.y = refPt.y;

   if      (refPt.z <= mMin.z) closest.z = mMin.z;
   else if (refPt.z >  mMax.z) closest.z = mMax.z;
   else                       closest.z = refPt.z;

   return closest;
}


//------------------------------------------------------------------------------
/// Clone of Box3F, using 3D types.
///
/// 3D types use F64.
///
/// @see Box3F
class Box3D
{
  public:
   Point3D mMin;
   Point3D mMax;

  public:
   Box3D() { }
   Box3D(const Point3D& in_rMin, const Point3D& in_rMax, const bool in_overrideCheck = false);

   bool isContained(const Point3D& in_rContained) const;
   bool isOverlapped(const Box3D&  in_rOverlap) const;

   F64 len_x() const;
   F64 len_y() const;
   F64 len_z() const;

   void intersect(const Box3D& in_rIntersect);
   void getCenter(Point3D* center) const;
};

inline Box3D::Box3D(const Point3D& in_rMin, const Point3D& in_rMax, const bool in_overrideCheck)
 : mMin(in_rMin),
   mMax(in_rMax)
{
   if (in_overrideCheck == false) {
      mMin.setMin(in_rMax);
      mMax.setMax(in_rMin);
   }
}

inline bool Box3D::isContained(const Point3D& in_rContained) const
{
   return (in_rContained.x >= mMin.x && in_rContained.x < mMax.x) &&
          (in_rContained.y >= mMin.y && in_rContained.y < mMax.y) &&
          (in_rContained.z >= mMin.z && in_rContained.z < mMax.z);
}

inline bool Box3D::isOverlapped(const Box3D& in_rOverlap) const
{
   if (in_rOverlap.mMin.x > mMax.x ||
       in_rOverlap.mMin.y > mMax.y ||
       in_rOverlap.mMin.z > mMax.z)
      return false;
   if (in_rOverlap.mMax.x < mMin.x ||
       in_rOverlap.mMax.y < mMin.y ||
       in_rOverlap.mMax.z < mMin.z)
      return false;
   return true;
}

inline F64 Box3D::len_x() const
{
   return mMax.x - mMin.x;
}

inline F64 Box3D::len_y() const
{
   return mMax.y - mMin.y;
}

inline F64 Box3D::len_z() const
{
   return mMax.z - mMin.z;
}

inline void Box3D::intersect(const Box3D& in_rIntersect)
{
   mMin.setMin(in_rIntersect.mMin);
   mMax.setMax(in_rIntersect.mMax);
}

inline void Box3D::getCenter(Point3D* center) const
{
   center->x = (mMin.x + mMax.x) * 0.5;
   center->y = (mMin.y + mMax.y) * 0.5;
   center->z = (mMin.z + mMax.z) * 0.5;
}


#endif // _DBOX_H_
