/* -*-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_CullingSet
#define OSG_CullingSet 1

#include <osg/Polytope>
#include <osg/ShadowVolumeOccluder>
#include <osg/Viewport>

#include <math.h>


namespace osg {

#define COMPILE_WITH_SHADOW_OCCLUSION_CULLING

/** A CullingSet class which contains a frustum and a list of occluders. */
class OSG_EXPORT CullingSet : public Referenced
{

    public:

        typedef std::pair< osg::ref_ptr<osg::StateSet>, osg::Polytope > StateFrustumPair;
        typedef std::vector< StateFrustumPair > StateFrustumList;

        CullingSet();

        CullingSet(const CullingSet& cs):
            Referenced(),
            _mask(cs._mask),
            _frustum(cs._frustum),
            _stateFrustumList(cs._stateFrustumList),
            _occluderList(cs._occluderList),
            _pixelSizeVector(cs._pixelSizeVector),
            _smallFeatureCullingPixelSize(cs._smallFeatureCullingPixelSize)
        {
        }

        CullingSet(const CullingSet& cs,const Matrix& matrix, const Vec4& pixelSizeVector):
            _mask(cs._mask),
            _frustum(cs._frustum),
            _stateFrustumList(cs._stateFrustumList),
            _occluderList(cs._occluderList),
            _pixelSizeVector(pixelSizeVector),
            _smallFeatureCullingPixelSize(cs._smallFeatureCullingPixelSize)
        {
            _frustum.transformProvidingInverse(matrix);
            for(OccluderList::iterator itr=_occluderList.begin();
                itr!=_occluderList.end();
                ++itr)
            {
                itr->transformProvidingInverse(matrix);
            }
        }

        CullingSet& operator = (const CullingSet& cs)
        {
            if (this==&cs) return *this;

            _mask = cs._mask;
            _frustum = cs._frustum;
            _stateFrustumList = cs._stateFrustumList;
            _occluderList = cs._occluderList;
            _pixelSizeVector = cs._pixelSizeVector;
            _smallFeatureCullingPixelSize = cs._smallFeatureCullingPixelSize;

            return *this;
        }


        inline void set(const CullingSet& cs)
        {
            _mask = cs._mask;
            _frustum = cs._frustum;
            _stateFrustumList = cs._stateFrustumList;
            _occluderList = cs._occluderList;
            _pixelSizeVector = cs._pixelSizeVector;
            _smallFeatureCullingPixelSize = cs._smallFeatureCullingPixelSize;
        }

        inline void set(const CullingSet& cs,const Matrix& matrix, const Vec4& pixelSizeVector)
        {
            _mask = cs._mask;
            _stateFrustumList = cs._stateFrustumList;
            _occluderList = cs._occluderList;
            _pixelSizeVector = pixelSizeVector;
            _smallFeatureCullingPixelSize = cs._smallFeatureCullingPixelSize;

            //_frustum = cs._frustum;
            //_frustum.transformProvidingInverse(matrix);

            _frustum.setAndTransformProvidingInverse(cs._frustum,matrix);

            for(StateFrustumList::iterator sitr=_stateFrustumList.begin();
                sitr!=_stateFrustumList.end();
                ++sitr)
            {
                sitr->second.transformProvidingInverse(matrix);
            }

            for(OccluderList::iterator oitr=_occluderList.begin();
                oitr!=_occluderList.end();
                ++oitr)
            {
                oitr->transformProvidingInverse(matrix);
            }

        }

        typedef std::vector<ShadowVolumeOccluder>   OccluderList;

        typedef int Mask;

        enum MaskValues
        {
            NO_CULLING                  = 0x0,
            VIEW_FRUSTUM_SIDES_CULLING  = 0x1,
            NEAR_PLANE_CULLING          = 0x2,
            FAR_PLANE_CULLING           = 0x4,
            VIEW_FRUSTUM_CULLING        = VIEW_FRUSTUM_SIDES_CULLING|
                                          NEAR_PLANE_CULLING|
                                          FAR_PLANE_CULLING,
            SMALL_FEATURE_CULLING       = 0x8,
            SHADOW_OCCLUSION_CULLING    = 0x10,
            DEFAULT_CULLING             = VIEW_FRUSTUM_SIDES_CULLING|
                                          SMALL_FEATURE_CULLING|
                                          SHADOW_OCCLUSION_CULLING,
            ENABLE_ALL_CULLING          = VIEW_FRUSTUM_CULLING|
                                          SMALL_FEATURE_CULLING|
                                          SHADOW_OCCLUSION_CULLING
        };

        void setCullingMask(Mask mask) { _mask = mask; }
        Mask getCullingMask() const { return _mask; }

        void setFrustum(Polytope& cv) { _frustum = cv; }

        Polytope& getFrustum() { return _frustum; }
        const Polytope& getFrustum() const { return _frustum; }

        void addStateFrustum(StateSet* stateset, Polytope& polytope) { _stateFrustumList.push_back(StateFrustumPair(stateset,polytope)); }

        void getStateFrustumList(StateFrustumList& sfl) { _stateFrustumList = sfl; }
        StateFrustumList& getStateFrustumList() { return _stateFrustumList; }

        void addOccluder(ShadowVolumeOccluder& cv) { _occluderList.push_back(cv); }

        void setPixelSizeVector(const Vec4& v) { _pixelSizeVector = v; }

        Vec4& getPixelSizeVector() { return _pixelSizeVector; }
        const Vec4& getPixelSizeVector() const { return _pixelSizeVector; }

        /** Threshold at which small features are culled.
        \param value Boulding volume size in screen space. Default is 2.0. */
        void setSmallFeatureCullingPixelSize(float value) { _smallFeatureCullingPixelSize=value; }

        float& getSmallFeatureCullingPixelSize() { return _smallFeatureCullingPixelSize; }

        float getSmallFeatureCullingPixelSize() const { return _smallFeatureCullingPixelSize; }


        /** Compute the pixel of an object at position v, with specified radius.*/
        float pixelSize(const Vec3& v,float radius) const { return radius/(v*_pixelSizeVector); }

        /** Compute the pixel of a bounding sphere.*/
        float pixelSize(const BoundingSphere& bs) const { return bs.radius()/(bs.center()*_pixelSizeVector); }

        /** Compute the pixel of an object at position v, with specified radius. fabs()ed to always be positive. */
        float clampedPixelSize(const Vec3& v,float radius) const { return fabs(pixelSize(v, radius)); }

        /** Compute the pixel of a bounding sphere. fabs()ed to always be positive. */
        float clampedPixelSize(const BoundingSphere& bs) const { return fabs(pixelSize(bs)); }


        inline bool isCulled(const std::vector<Vec3>& vertices)
        {
            if (_mask&VIEW_FRUSTUM_CULLING)
            {
                // is it outside the view frustum...
                if (!_frustum.contains(vertices)) return true;
            }

            if (_mask&SMALL_FEATURE_CULLING)
            {
            }

            if (_mask&SHADOW_OCCLUSION_CULLING)
            {
                // is it in one of the shadow occluder volumes.
                if (!_occluderList.empty())
                {
                    for(OccluderList::iterator itr=_occluderList.begin();
                        itr!=_occluderList.end();
                        ++itr)
                    {
                        if (itr->contains(vertices)) return true;
                    }
                }
            }

            return false;
        }

        inline bool isCulled(const BoundingBox& bb)
        {
            if (_mask&VIEW_FRUSTUM_CULLING)
            {
                // is it outside the view frustum...
                if (!_frustum.contains(bb)) return true;
            }

            if (_mask&SMALL_FEATURE_CULLING)
            {
            }

            if (_mask&SHADOW_OCCLUSION_CULLING)
            {
                // is it in one of the shadow occluder volumes.
                if (!_occluderList.empty())
                {
                    for(OccluderList::iterator itr=_occluderList.begin();
                        itr!=_occluderList.end();
                        ++itr)
                    {
                        if (itr->contains(bb)) return true;
                    }
                }
            }

            return false;
        }

        inline bool isCulled(const BoundingSphere& bs)
        {
            if (_mask&VIEW_FRUSTUM_CULLING)
            {
                // is it outside the view frustum...
                if (!_frustum.contains(bs)) return true;
            }

            if (_mask&SMALL_FEATURE_CULLING)
            {
                if (((bs.center()*_pixelSizeVector)*_smallFeatureCullingPixelSize)>bs.radius()) return true;
            }
#ifdef COMPILE_WITH_SHADOW_OCCLUSION_CULLING
            if (_mask&SHADOW_OCCLUSION_CULLING)
            {
                // is it in one of the shadow occluder volumes.
                if (!_occluderList.empty())
                {
                    for(OccluderList::iterator itr=_occluderList.begin();
                        itr!=_occluderList.end();
                        ++itr)
                    {
                        if (itr->contains(bs)) return true;
                    }
                }
            }
#endif
            return false;
        }

        inline void pushCurrentMask()
        {
            _frustum.pushCurrentMask();

            if (!_stateFrustumList.empty())
            {
                for(StateFrustumList::iterator itr=_stateFrustumList.begin();
                    itr!=_stateFrustumList.end();
                    ++itr)
                {
                    itr->second.pushCurrentMask();
                }
            }


#ifdef COMPILE_WITH_SHADOW_OCCLUSION_CULLING
            if (!_occluderList.empty())
            {
                for(OccluderList::iterator itr=_occluderList.begin();
                    itr!=_occluderList.end();
                    ++itr)
                {
                    itr->pushCurrentMask();
                }
            }
#endif
        }

        inline void popCurrentMask()
        {
            _frustum.popCurrentMask();

            if (!_stateFrustumList.empty())
            {
                for(StateFrustumList::iterator itr=_stateFrustumList.begin();
                    itr!=_stateFrustumList.end();
                    ++itr)
                {
                    itr->second.popCurrentMask();
                }
            }

#ifdef COMPILE_WITH_SHADOW_OCCLUSION_CULLING
            if (!_occluderList.empty())
            {
                for(OccluderList::iterator itr=_occluderList.begin();
                    itr!=_occluderList.end();
                    ++itr)
                {
                    itr->popCurrentMask();
                }
            }
#endif
        }

        inline void resetCullingMask()
        {
            _frustum.setResultMask(_frustum.getCurrentMask());
        }

        void disableAndPushOccludersCurrentMask(NodePath& nodePath);

        void popOccludersCurrentMask(NodePath& nodePath);

        static osg::Vec4 computePixelSizeVector(const Viewport& W, const Matrix& P, const Matrix& M);

        virtual ~CullingSet();


    protected:


        Mask                _mask;
        Polytope            _frustum;
        StateFrustumList    _stateFrustumList;
        OccluderList        _occluderList;
        Vec4                _pixelSizeVector;
        float               _smallFeatureCullingPixelSize;

};

}    // end of namespace

#endif
