/* -*-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_PLANE
#define OSG_PLANE 1

#include <osg/Config>
#include <osg/Export>
#include <osg/Vec3>
#include <osg/Vec4>
#include <osg/Matrix>
#include <osg/BoundingSphere>
#include <osg/BoundingBox>

#include <vector>

namespace osg {

/** A plane class. It can be used to represent an infinite plane.*/
class OSG_EXPORT Plane
{

    public:

#ifdef OSG_USE_FLOAT_PLANE
        /** Type of Plane class.*/
        typedef float value_type;
        typedef Vec3f Vec3_type;
        typedef Vec4f Vec4_type;
#else
        /** Type of Plane class.*/
        typedef double value_type;
        typedef Vec3d Vec3_type;
        typedef Vec4d Vec4_type;
#endif

        /** Number of vector components. */
        enum { num_components = 3 };


        inline Plane() { _fv[0]=0.0; _fv[1]=0.0; _fv[2]=0.0; _fv[3]=0.0; _lowerBBCorner = 0; _upperBBCorner = 0; }
        inline Plane(const Plane& pl) { set(pl); }
        inline Plane(value_type a,value_type b,value_type c,value_type d) { set(a,b,c,d); }

        inline Plane(const Vec4f& vec) { set(vec); }
        inline Plane(const Vec4d& vec) { set(vec); }

        inline Plane(const Vec3_type& norm,value_type d) { set(norm,d); }

        inline Plane(const Vec3_type& v1, const Vec3_type& v2, const Vec3_type& v3) { set(v1,v2,v3); }

        inline Plane(const Vec3_type& norm, const Vec3_type& point) { set(norm,point); }

        inline Plane& operator = (const Plane& pl)
        {
            if (&pl==this) return *this;
            set(pl);
            return *this;
        }

        inline void set(const Plane& pl) { _fv[0]=pl._fv[0]; _fv[1]=pl._fv[1]; _fv[2]=pl._fv[2]; _fv[3]=pl._fv[3]; calculateUpperLowerBBCorners(); }
        inline void set(value_type a, value_type b, value_type c, value_type d) { _fv[0]=a; _fv[1]=b; _fv[2]=c; _fv[3]=d; calculateUpperLowerBBCorners(); }

        inline void set(const Vec4f& vec) { set(vec[0],vec[1],vec[2],vec[3]); }
        inline void set(const Vec4d& vec) { set(vec[0],vec[1],vec[2],vec[3]); }

        inline void set(const Vec3_type& norm, double d) { set(norm[0],norm[1],norm[2],d); }

        inline void set(const Vec3_type& v1, const Vec3_type& v2, const Vec3_type& v3)
        {
            Vec3_type norm = (v2-v1)^(v3-v2);
            value_type length = norm.length();
            if (length>1e-6) norm/= length;
            else norm.set(0.0,0.0,0.0);
            set(norm[0],norm[1],norm[2],-(v1*norm));
        }

        inline void set(const Vec3_type& norm, const Vec3_type& point)
        {
            value_type d = -norm[0]*point[0] - norm[1]*point[1] - norm[2]*point[2];
            set(norm[0],norm[1],norm[2],d);
        }

        /** flip/reverse the orientation of the plane.*/
        inline void flip()
        {
            _fv[0] = -_fv[0];
            _fv[1] = -_fv[1];
            _fv[2] = -_fv[2];
            _fv[3] = -_fv[3];
            calculateUpperLowerBBCorners();
        }


        inline void makeUnitLength()
        {
            value_type inv_length = 1.0 / sqrt(_fv[0]*_fv[0] + _fv[1]*_fv[1]+ _fv[2]*_fv[2]);
            _fv[0] *= inv_length;
            _fv[1] *= inv_length;
            _fv[2] *= inv_length;
            _fv[3] *= inv_length;
        }

        /** calculate the upper and lower bounding box corners to be used
          * in the intersect(BoundingBox&) method for speeding calculations.*/
        inline void calculateUpperLowerBBCorners()
        {
            _upperBBCorner = (_fv[0]>=0.0?1:0) |
                             (_fv[1]>=0.0?2:0) |
                             (_fv[2]>=0.0?4:0);

            _lowerBBCorner = (~_upperBBCorner)&7;

        }

        inline bool valid() const { return !isNaN(); }
        inline bool isNaN() const { return osg::isNaN(_fv[0]) || osg::isNaN(_fv[1]) || osg::isNaN(_fv[2]) || osg::isNaN(_fv[3]); }

        inline bool operator == (const Plane& plane) const { return _fv[0]==plane._fv[0] && _fv[1]==plane._fv[1] && _fv[2]==plane._fv[2] && _fv[3]==plane._fv[3]; }

        inline bool operator != (const Plane& plane) const { return _fv[0]!=plane._fv[0] || _fv[1]!=plane._fv[1] || _fv[2]!=plane._fv[2] || _fv[3]!=plane._fv[3]; }

        inline bool operator <  (const Plane& plane) const
        {
            if (_fv[0]<plane._fv[0]) return true;
            else if (_fv[0]>plane._fv[0]) return false;
            else if (_fv[1]<plane._fv[1]) return true;
            else if (_fv[1]>plane._fv[1]) return false;
            else if (_fv[2]<plane._fv[2]) return true;
            else if (_fv[2]>plane._fv[2]) return false;
            else return (_fv[3]<plane._fv[3]);
        }


        inline value_type* ptr() { return _fv; }
        inline const value_type* ptr() const { return _fv; }

        inline Vec4_type asVec4() const { return Vec4(_fv[0],_fv[1],_fv[2],_fv[3]); }

        inline value_type& operator [] (unsigned int i) { return _fv[i]; }
        inline value_type operator [] (unsigned int i) const { return _fv[i]; }


        inline Vec3_type getNormal() const { return Vec3_type(_fv[0],_fv[1],_fv[2]); }
      
        /** calculate the distance between a point and the plane.*/
        inline float distance(const osg::Vec3f& v) const
        {
            return _fv[0]*v.x()+
                   _fv[1]*v.y()+
                   _fv[2]*v.z()+
                   _fv[3];
        }

        inline double distance(const osg::Vec3d& v) const
        {
            return _fv[0]*v.x()+
                   _fv[1]*v.y()+
                   _fv[2]*v.z()+
                   _fv[3];
        }

        /** calculate the dot product of the plane normal and a point.*/
        inline float dotProductNormal(const osg::Vec3f& v) const
        {
            return _fv[0]*v.x()+
                   _fv[1]*v.y()+
                   _fv[2]*v.z();
        }

        /** calculate the dot product of the plane normal and a point.*/
        inline double dotProductNormal(const osg::Vec3d& v) const
        {
            return _fv[0]*v.x()+
                   _fv[1]*v.y()+
                   _fv[2]*v.z();
        }

        /** intersection test between plane and vertex list
            return 1 if the bs is completely above plane,
            return 0 if the bs intersects the plane,
            return -1 if the bs is completely below the plane.*/
        inline int intersect(const std::vector<Vec3f>& vertices) const
        {
            if (vertices.empty()) return -1;

            int noAbove = 0;
            int noBelow = 0;
            int noOn = 0;
            for(std::vector<Vec3f>::const_iterator itr=vertices.begin();
                itr != vertices.end();
                ++itr)
            {
                float d = distance(*itr);
                if (d>0.0f) ++noAbove;
                else if (d<0.0f) ++noBelow;
                else ++noOn;
            }

            if (noAbove>0)
            {
                if (noBelow>0) return 0;
                else return 1;
            }
            return -1; // treat points on line as outside...
        }

        /** intersection test between plane and vertex list
            return 1 if the bs is completely above plane,
            return 0 if the bs intersects the plane,
            return -1 if the bs is completely below the plane.*/
        inline int intersect(const std::vector<Vec3d>& vertices) const
        {
            if (vertices.empty()) return -1;

            int noAbove = 0;
            int noBelow = 0;
            int noOn = 0;
            for(std::vector<Vec3d>::const_iterator itr=vertices.begin();
                itr != vertices.end();
                ++itr)
            {
                double d = distance(*itr);
                if (d>0.0) ++noAbove;
                else if (d<0.0) ++noBelow;
                else ++noOn;
            }

            if (noAbove>0)
            {
                if (noBelow>0) return 0;
                else return 1;
            }
            return -1; // treat points on line as outside...
        }

        /** intersection test between plane and bounding sphere.
            return 1 if the bs is completely above plane,
            return 0 if the bs intersects the plane,
            return -1 if the bs is completely below the plane.*/
        inline int intersect(const BoundingSphere& bs) const
        {
            float d = distance(bs.center());

            if (d>bs.radius()) return 1;
            else if (d<-bs.radius()) return -1;
            else return 0;
        }


        /** intersection test between plane and bounding sphere.
            return 1 if the bs is completely above plane,
            return 0 if the bs intersects the plane,
            return -1 if the bs is completely below the plane.*/
        inline int intersect(const BoundingBox& bb) const
        {
            // if lowest point above plane than all above.
            if (distance(bb.corner(_lowerBBCorner))>0.0f) return 1;

            // if highest point is below plane then all below.
            if (distance(bb.corner(_upperBBCorner))<0.0f) return -1;

            // d_lower<=0.0f && d_upper>=0.0f
            // therefore must be crossing plane.
            return 0;

        }

        /** Transform the plane by matrix.  Note, this operation carries out
          * the calculation of the inverse of the matrix since a plane 
          * must be multiplied by the inverse transposed to transform it. This
          * make this operation expensive.  If the inverse has been already
          * calculated elsewhere then use transformProvidingInverse() instead.
          * See http://www.worldserver.com/turk/computergraphics/NormalTransformations.pdf*/
        inline void transform(const osg::Matrix& matrix)
        {
            osg::Matrix inverse;
            inverse.invert(matrix);
            transformProvidingInverse(inverse);
        }

        /** Transform the plane by providing a pre inverted matrix.
          * see transform for details. */
        inline void transformProvidingInverse(const osg::Matrix& matrix)
        {
            // note pre multiplications, which effectively transposes matrix.
            Vec4_type vec(_fv[0],_fv[1],_fv[2],_fv[3]);
            vec = matrix * vec;
            set(vec);
            makeUnitLength();
        }

    protected:

        /** Vec member variable. */
        value_type _fv[4];

        // variables cached to optimize calcs against bounding boxes.
        unsigned int        _upperBBCorner;
        unsigned int        _lowerBBCorner;


};

} // end of namespace

#endif 
