/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * 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 
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSG_BOUNDINGBOX
#define OSG_BOUNDINGBOX 1

#include <osg/Export>
#include <osg/Vec3>
#include <float.h>

namespace osg {

class BoundingSphere;

/** General purpose axis-aligned bounding box class for enclosing objects/vertices.
  * Bounds leaf objects in a scene such as osg::Drawable objects. Used for frustum
  * culling etc.
*/
class OSG_EXPORT BoundingBox
{
    public:
    
        /** Minimum extent. (Smallest X, Y, and Z values of all coordinates.) */
        Vec3 _min;
        /** Maximum extent. (Greatest X, Y, and Z values of all coordinates.) */
        Vec3 _max;

        /** Creates an uninitialized bounding box. */
        inline BoundingBox() : _min(FLT_MAX,FLT_MAX,FLT_MAX),
                        _max(-FLT_MAX,-FLT_MAX,-FLT_MAX) {}
    
        /** Creates a bounding box initialized to the given extents. */
        inline BoundingBox(float xmin,float ymin,float zmin,
                    float xmax,float ymax,float zmax) :
                    _min(xmin,ymin,zmin),
                    _max(xmax,ymax,zmax) {}

        /** Creates a bounding box initialized to the given extents. */
        inline BoundingBox(const Vec3& min,const Vec3& max) : 
                    _min(min),
                    _max(max) {}

        /** Clear the bounding box. Erases existing minimum and maximum extents. */
        inline void init()
        {
            _min.set(FLT_MAX,FLT_MAX,FLT_MAX);
            _max.set(-FLT_MAX,-FLT_MAX,-FLT_MAX);
        }
        
        /** Returns true if the bounding box extents are valid, false otherwise. */              
        inline bool valid() const
        {
            return _max.x()>=_min.x() &&  _max.y()>=_min.y() &&  _max.z()>=_min.z();
        }

        /** Sets the bounding box extents. */
        inline void set (float xmin,float ymin,float zmin,
                         float xmax,float ymax,float zmax)
        {
            _min.set(xmin,ymin,zmin);
            _max.set(xmax,ymax,zmax);
        }

        /** Sets the bounding box extents. */
        inline void set(const Vec3& min,const Vec3& max)
        {
            _min = min;
            _max = max;
        }


        inline float& xMin() { return _min.x(); }
        inline float xMin() const { return _min.x(); }
 
        inline float& yMin() { return _min.y(); }
        inline float yMin() const { return _min.y(); }
 
        inline float& zMin() { return _min.z(); }
        inline float zMin() const { return _min.z(); }

        inline float& xMax() { return _max.x(); }
        inline float xMax() const { return _max.x(); }
 
        inline float& yMax() { return _max.y(); }
        inline float yMax() const { return _max.y(); }
 
        inline float& zMax() { return _max.z(); }
        inline float zMax() const { return _max.z(); }

        /** Calculates and returns the bounding box center. */
        inline const Vec3 center() const
        {
            return (_min+_max)*0.5f;
        }

        /** Calculates and returns the bounding box radius. */
        inline float radius() const
        {
            return sqrtf(radius2());
        }

        /** Calculates and returns the squared length of the bounding box radius.
          * Note, radius2() is faster to calculate than radius(). */
        inline float radius2() const
        {
            return 0.25f*((_max-_min).length2());
        }

        /** Returns a specific corner of the bounding box.
          * pos specifies the corner as a number between 0 and 7.
          * Each bit selects an axis, X, Y, or Z from least- to
          * most-significant. Unset bits select the minimum value
          * for that axis, and set bits select the maximum. */
        inline const Vec3 corner(unsigned int pos) const
        {
            return Vec3(pos&1?_max.x():_min.x(),pos&2?_max.y():_min.y(),pos&4?_max.z():_min.z());
        }

        /** Expands the bounding box to include the given coordinate.
          * If the box is uninitialized, set its min and max extents to v. */
        inline void expandBy(const Vec3& v)
        {
            if(v.x()<_min.x()) _min.x() = v.x();
            if(v.x()>_max.x()) _max.x() = v.x();

            if(v.y()<_min.y()) _min.y() = v.y();
            if(v.y()>_max.y()) _max.y() = v.y();

            if(v.z()<_min.z()) _min.z() = v.z();
            if(v.z()>_max.z()) _max.z() = v.z();
        }

        /** Expands the bounding box to include the given coordinate.
          * If the box is uninitialized, set its min and max extents to
          * Vec3(x,y,z). */
        inline void expandBy(float x,float y,float z)
        {
            if(x<_min.x()) _min.x() = x;
            if(x>_max.x()) _max.x() = x;

            if(y<_min.y()) _min.y() = y;
            if(y>_max.y()) _max.y() = y;

            if(z<_min.z()) _min.z() = z;
            if(z>_max.z()) _max.z() = z;
        }

        /** Expands this bounding box to include the given bounding box.
          * If this box is uninitialized, set it equal to bb. */
        void expandBy(const BoundingBox& bb);

        /** Expands this bounding box to include the given sphere.
          * If this box is uninitialized, set it to include sh. */
        void expandBy(const BoundingSphere& sh);
        

        /** Returns the intesection of this bounding box and the specified bounding box. */
        BoundingBox intersect(const BoundingBox& bb) const
        {    return osg::BoundingBox(osg::maximum(xMin(),bb.xMin()),osg::maximum(yMin(),bb.yMin()),osg::maximum(zMin(),bb.zMin()),
                                     osg::minimum(xMax(),bb.xMax()),osg::minimum(yMax(),bb.yMax()),osg::minimum(zMax(),bb.zMax()));

        }

        /** Return true if this bounding box intersects the specified bounding box. */
        bool intersects(const BoundingBox& bb) const
        {    return osg::maximum(xMin(),bb.xMin()) <= osg::minimum(xMax(),bb.xMax()) &&
                    osg::maximum(yMin(),bb.yMin()) <= osg::minimum(yMax(),bb.yMax()) &&
                    osg::maximum(zMin(),bb.zMin()) <= osg::minimum(zMax(),bb.zMax());

        }

        /** Returns true if this bounding box contains the specified coordinate. */
        inline bool contains(const Vec3& v) const
        {
            return valid() && 
                   (v.x()>=_min.x() && v.x()<=_max.x()) &&
                   (v.y()>=_min.y() && v.y()<=_max.y()) &&
                   (v.z()>=_min.z() && v.z()<=_max.z());
        }
};

}

#endif
