/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 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/Referenced>

namespace osg {

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

    public:
    
    
        CullingSet();

        CullingSet(const CullingSet& cs):
            Referenced(),
            _mask(cs._mask),
            _frustum(cs._frustum),
            _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),
            _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;
            _occluderList = cs._occluderList;
            _pixelSizeVector = cs._pixelSizeVector;
            _smallFeatureCullingPixelSize = cs._smallFeatureCullingPixelSize;
            
            return *this;
        }


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

            _frustum.transformProvidingInverse(matrix);
            
            for(OccluderList::iterator itr=_occluderList.begin();
                itr!=_occluderList.end();
                ++itr)
            {
                itr->transformProvidingInverse(matrix);
            }
        
        }
        
        typedef std::vector<ShadowVolumeOccluder>   OccluderList;

        typedef unsigned 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; }

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

        Polytope& getFrustum() { return _frustum; }
        const Polytope& getFrustum() const { return _frustum; }    
        
        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; }

        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 an bounding sphere.*/
        float pixelSize(const BoundingSphere& bs) const { return bs.radius()/(bs.center()*_pixelSizeVector); }


        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;
            }

            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;
                    }
                }
            }
            
            return false;
        }
        
        inline void pushCurrentMask()
        {
            _frustum.pushCurrentMask();
            
            if (!_occluderList.empty())
            {
                for(OccluderList::iterator itr=_occluderList.begin();
                    itr!=_occluderList.end();
                    ++itr)
                {
                    itr->pushCurrentMask();
                }
            }
        }
        
        inline void popCurrentMask()
        {
            _frustum.popCurrentMask();

            if (!_occluderList.empty())
            {
                for(OccluderList::iterator itr=_occluderList.begin();
                    itr!=_occluderList.end();
                    ++itr)
                {
                    itr->popCurrentMask();
                }
            }
        }
        
        void disableAndPushOccludersCurrentMask(NodePath& nodePath);

        void popOccludersCurrentMask(NodePath& nodePath);
        
        virtual ~CullingSet();

    protected:
    

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

};

}	// end of namespace

#endif 
