/* -*-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.
*/
#include <osg/Transform>
#include <osg/Projection>
#include <osg/Geode>
#include <osg/LOD>
#include <osg/Billboard>
#include <osg/LightSource>
#include <osg/ClipNode>
#include <osg/TexGenNode>
#include <osg/OccluderNode>
#include <osg/OcclusionQueryNode>
#include <osg/Notify>
#include <osg/TexEnv>
#include <osg/AlphaFunc>
#include <osg/LineSegment>
#include <osg/TemplatePrimitiveFunctor>
#include <osg/Geometry>
#include <osg/io_utils>

#include <osgUtil/CullVisitor>

#include <float.h>
#include <algorithm>

#include <osg/Timer>

using namespace osg;
using namespace osgUtil;

inline float MAX_F(float a, float b)
    { return a>b?a:b; }
inline int EQUAL_F(float a, float b)
    { return a == b || fabsf(a-b) <= MAX_F(fabsf(a),fabsf(b))*1e-3f; }


CullVisitor::CullVisitor():
    NodeVisitor(CULL_VISITOR,TRAVERSE_ACTIVE_CHILDREN),
    _currentStateGraph(NULL),
    _currentRenderBin(NULL),
    _traversalNumber(0),
    _computed_znear(FLT_MAX),
    _computed_zfar(-FLT_MAX),
    _currentReuseRenderLeafIndex(0),
    _numberOfEncloseOverrideRenderBinDetails(0)
{
    _identifier = new Identifier;
}

CullVisitor::CullVisitor(const CullVisitor& rhs):
    NodeVisitor(rhs),
    CullStack(rhs),
    _currentStateGraph(NULL),
    _currentRenderBin(NULL),
    _traversalNumber(0),
    _computed_znear(FLT_MAX),
    _computed_zfar(-FLT_MAX),
    _currentReuseRenderLeafIndex(0),
    _numberOfEncloseOverrideRenderBinDetails(0),
    _identifier(rhs._identifier)
{
}

CullVisitor::~CullVisitor()
{
    reset();
}

osg::ref_ptr<CullVisitor>& CullVisitor::prototype()
{
    static osg::ref_ptr<CullVisitor> s_CullVisitor = new CullVisitor;
    return s_CullVisitor;
}

CullVisitor* CullVisitor::create()
{
    return CullVisitor::prototype().valid() ?
           CullVisitor::prototype()->clone() :
           new CullVisitor;
}


void CullVisitor::reset()
{
    //
    // first unref all referenced objects and then empty the containers.
    //

    CullStack::reset();

    _renderBinStack.clear();

    _numberOfEncloseOverrideRenderBinDetails = 0;

    // reset the traversal number
    _traversalNumber = 0;

    // reset the calculated near far planes.
    _computed_znear = FLT_MAX;
    _computed_zfar = -FLT_MAX;


    osg::Vec3 lookVector(0.0,0.0,-1.0);

    _bbCornerFar = (lookVector.x()>=0?1:0) |
                   (lookVector.y()>=0?2:0) |
                   (lookVector.z()>=0?4:0);

    _bbCornerNear = (~_bbCornerFar)&7;

    // Only reset the RenderLeaf objects used last frame.
    for(RenderLeafList::iterator itr=_reuseRenderLeafList.begin(),
        iter_end=_reuseRenderLeafList.begin()+_currentReuseRenderLeafIndex;
        itr!=iter_end;
        ++itr)
    {
        (*itr)->reset();
    }

    // reset the resuse lists.
    _currentReuseRenderLeafIndex = 0;

    _nearPlaneCandidateMap.clear();
    _farPlaneCandidateMap.clear();
}

float CullVisitor::getDistanceToEyePoint(const Vec3& pos, bool withLODScale) const
{
    if (withLODScale) return (pos-getEyeLocal()).length()*getLODScale();
    else return (pos-getEyeLocal()).length();
}

float CullVisitor::getDistanceToViewPoint(const Vec3& pos, bool withLODScale) const
{
    if (withLODScale) return (pos-getViewPointLocal()).length()*getLODScale();
    else return (pos-getViewPointLocal()).length();
}

inline CullVisitor::value_type distance(const osg::Vec3& coord,const osg::Matrix& matrix)
{
    return -((CullVisitor::value_type)coord[0]*(CullVisitor::value_type)matrix(0,2)+(CullVisitor::value_type)coord[1]*(CullVisitor::value_type)matrix(1,2)+(CullVisitor::value_type)coord[2]*(CullVisitor::value_type)matrix(2,2)+matrix(3,2));
}

float CullVisitor::getDistanceFromEyePoint(const osg::Vec3& pos, bool withLODScale) const
{
    const Matrix& matrix = *_modelviewStack.back();
    float dist = distance(pos,matrix);

    if (withLODScale) return dist*getLODScale();
    else return dist;
}

void CullVisitor::computeNearPlane()
{
    //OSG_NOTICE<<"CullVisitor::computeNearPlane() _computed_znear="<<_computed_znear<<", _computed_zfar="<<_computed_zfar<<std::endl;
    if (!_nearPlaneCandidateMap.empty())
    {
#if 0
        osg::Timer_t start_t = osg::Timer::instance()->tick();
#endif
        // update near from defferred list of drawables
        unsigned int numTests = 0;
        for(DistanceMatrixDrawableMap::iterator itr=_nearPlaneCandidateMap.begin();
            itr!=_nearPlaneCandidateMap.end() && itr->first<_computed_znear;
            ++itr)
        {
            ++numTests;
            value_type d_near = computeNearestPointInFrustum(itr->second._matrix, itr->second._planes,*(itr->second._drawable));
            //OSG_NOTICE<<"  testing computeNearestPointInFrustum with, drawable"<<itr->second._drawable<<", "<<itr->first<<", d_near = "<<d_near<<std::endl;
            if (d_near<_computed_znear)
            {
                _computed_znear = d_near;
#if 0
                OSG_NOTICE<<"   updating znear to "<<d_near<<std::endl;
#endif
            }
        }

#if 0
        osg::Timer_t end_t = osg::Timer::instance()->tick();
        OSG_NOTICE<<"Took "<<osg::Timer::instance()->delta_m(start_t,end_t)<<"ms to test "<<numTests<<" out of "<<_nearPlaneCandidateMap.size()<<std::endl;
#endif
        _nearPlaneCandidateMap.clear();
    }

    if (!_farPlaneCandidateMap.empty())
    {

        //osg::Timer_t start_t = osg::Timer::instance()->tick();

        // update near from defferred list of drawables
        unsigned int numTests = 0;
        for(DistanceMatrixDrawableMap::reverse_iterator itr=_farPlaneCandidateMap.rbegin();
            itr!=_farPlaneCandidateMap.rend() && itr->first>_computed_zfar;
            ++itr)
        {
            ++numTests;
            // OSG_WARN<<"testing computeFurthestPointInFrustum with d_far = "<<itr->first<<std::endl;
            value_type d_far = computeFurthestPointInFrustum(itr->second._matrix, itr->second._planes,*(itr->second._drawable));
            if (d_far>_computed_zfar)
            {
                _computed_zfar = d_far;
                // OSG_WARN<<"updating zfar to "<<_computed_zfar<<std::endl;
            }
        }

        //osg::Timer_t end_t = osg::Timer::instance()->tick();
        //OSG_NOTICE<<"Took "<<osg::Timer::instance()->delta_m(start_t,end_t)<<"ms to test "<<numTests<<" out of "<<_farPlaneCandidateMap.size()<<std::endl;

        _farPlaneCandidateMap.clear();
    }
#if 0
    OSG_NOTICE<<"  result _computed_znear="<<_computed_znear<<", _computed_zfar="<<_computed_zfar<<std::endl;
#endif
}

void CullVisitor::popProjectionMatrix()
{
    computeNearPlane();

    if (_computeNearFar && _computed_zfar>=_computed_znear)
    {

        //OSG_INFO<<"clamping "<< "znear="<<_computed_znear << " zfar="<<_computed_zfar<<std::endl;


        // adjust the projection matrix so that it encompases the local coords.
        // so it doesn't cull them out.
        osg::Matrix& projection = *_projectionStack.back();

        value_type tmp_znear = _computed_znear;
        value_type tmp_zfar = _computed_zfar;

        clampProjectionMatrix(projection, tmp_znear, tmp_zfar);
    }
    else
    {
        //OSG_INFO<<"Not clamping "<< "znear="<<_computed_znear << " zfar="<<_computed_zfar<<std::endl;
    }

    CullStack::popProjectionMatrix();
}

template<class matrix_type, class value_type>
bool _clampProjectionMatrix(matrix_type& projection, double& znear, double& zfar, value_type nearFarRatio)
{
    double epsilon = 1e-6;
    if (zfar<znear-epsilon)
    {
        if (zfar != -FLT_MAX || znear != FLT_MAX)
        {
            OSG_INFO<<"_clampProjectionMatrix not applied, invalid depth range, znear = "<<znear<<"  zfar = "<<zfar<<std::endl;
        }
        return false;
    }

    if (zfar<znear+epsilon)
    {
        // znear and zfar are too close together and could cause divide by zero problems
        // late on in the clamping code, so move the znear and zfar apart.
        double average = (znear+zfar)*0.5;
        znear = average-epsilon;
        zfar = average+epsilon;
        // OSG_INFO << "_clampProjectionMatrix widening znear and zfar to "<<znear<<" "<<zfar<<std::endl;
    }

    if (fabs(projection(0,3))<epsilon  && fabs(projection(1,3))<epsilon  && fabs(projection(2,3))<epsilon )
    {
        // OSG_INFO << "Orthographic matrix before clamping"<<projection<<std::endl;

        value_type delta_span = (zfar-znear)*0.02;
        if (delta_span<1.0) delta_span = 1.0;
        value_type desired_znear = znear - delta_span;
        value_type desired_zfar = zfar + delta_span;

        // assign the clamped values back to the computed values.
        znear = desired_znear;
        zfar = desired_zfar;

        projection(2,2)=-2.0f/(desired_zfar-desired_znear);
        projection(3,2)=-(desired_zfar+desired_znear)/(desired_zfar-desired_znear);

        // OSG_INFO << "Orthographic matrix after clamping "<<projection<<std::endl;
    }
    else
    {

        // OSG_INFO << "Persepective matrix before clamping"<<projection<<std::endl;

        //std::cout << "_computed_znear"<<_computed_znear<<std::endl;
        //std::cout << "_computed_zfar"<<_computed_zfar<<std::endl;

        value_type zfarPushRatio = 1.02;
        value_type znearPullRatio = 0.98;

        //znearPullRatio = 0.99;

        value_type desired_znear = znear * znearPullRatio;
        value_type desired_zfar = zfar * zfarPushRatio;

        // near plane clamping.
        double min_near_plane = zfar*nearFarRatio;
        if (desired_znear<min_near_plane) desired_znear=min_near_plane;

        // assign the clamped values back to the computed values.
        znear = desired_znear;
        zfar = desired_zfar;

        value_type trans_near_plane = (-desired_znear*projection(2,2)+projection(3,2))/(-desired_znear*projection(2,3)+projection(3,3));
        value_type trans_far_plane = (-desired_zfar*projection(2,2)+projection(3,2))/(-desired_zfar*projection(2,3)+projection(3,3));

        value_type ratio = fabs(2.0/(trans_near_plane-trans_far_plane));
        value_type center = -(trans_near_plane+trans_far_plane)/2.0;

        projection.postMult(osg::Matrix(1.0f,0.0f,0.0f,0.0f,
                                        0.0f,1.0f,0.0f,0.0f,
                                        0.0f,0.0f,ratio,0.0f,
                                        0.0f,0.0f,center*ratio,1.0f));

        // OSG_INFO << "Persepective matrix after clamping"<<projection<<std::endl;
    }
    return true;
}


bool CullVisitor::clampProjectionMatrixImplementation(osg::Matrixf& projection, double& znear, double& zfar) const
{
    return _clampProjectionMatrix( projection, znear, zfar, _nearFarRatio );
}

bool CullVisitor::clampProjectionMatrixImplementation(osg::Matrixd& projection, double& znear, double& zfar) const
{
    return _clampProjectionMatrix( projection, znear, zfar, _nearFarRatio );
}

template<typename Comparator>
struct ComputeNearFarFunctor
{

    ComputeNearFarFunctor():
        _planes(0) {}

    void set(CullVisitor::value_type znear, const osg::Matrix& matrix, const osg::Polytope::PlaneList* planes)
    {
        _znear = znear;
        _matrix = matrix;
        _planes = planes;
    }

    typedef std::pair<float, osg::Vec3>  DistancePoint;
    typedef std::vector<DistancePoint>  Polygon;

    Comparator                      _comparator;

    CullVisitor::value_type         _znear;
    osg::Matrix                     _matrix;
    const osg::Polytope::PlaneList* _planes;
    Polygon                         _polygonOriginal;
    Polygon                         _polygonNew;

    Polygon                         _pointCache;

    // Handle Points
    inline void operator() ( const osg::Vec3 &v1, bool)
    {
        CullVisitor::value_type n1 = distance(v1,_matrix);

        // check if point is behind znear, if so discard
        if (_comparator.greaterEqual(n1,_znear))
        {
            //OSG_NOTICE<<"Point beyond znear"<<std::endl;
            return;
        }

        if (n1 < 0.0)
        {
            // OSG_NOTICE<<"Point behind eye point"<<std::endl;
            return;
        }

        // If point is outside any of frustum planes, discard.
        osg::Polytope::PlaneList::const_iterator pitr;
        for(pitr = _planes->begin();
            pitr != _planes->end();
            ++pitr)
        {
            const osg::Plane& plane = *pitr;
            float d1 = plane.distance(v1);

            if (d1<0.0)
            {
                //OSG_NOTICE<<"Point outside frustum "<<d1<<std::endl;
                return;
            }
            //OSG_NOTICE<<"Point ok w.r.t plane "<<d1<<std::endl;
        }

        _znear = n1;
        //OSG_NOTICE<<"Near plane updated "<<_znear<<std::endl;
    }

    // Handle Lines
    inline void operator() ( const osg::Vec3 &v1, const osg::Vec3 &v2, bool)
    {
        CullVisitor::value_type n1 = distance(v1,_matrix);
        CullVisitor::value_type n2 = distance(v2,_matrix);

        // check if line is totally behind znear, if so discard
        if (_comparator.greaterEqual(n1,_znear) &&
            _comparator.greaterEqual(n2,_znear))
        {
            //OSG_NOTICE<<"Line totally beyond znear"<<std::endl;
            return;
        }

        if (n1 < 0.0 &&
            n2 < 0.0)
        {
            // OSG_NOTICE<<"Line totally behind eye point"<<std::endl;
            return;
        }

        // Check each vertex to each frustum plane.
        osg::Polytope::ClippingMask selector_mask = 0x1;
        osg::Polytope::ClippingMask active_mask = 0x0;

        osg::Polytope::PlaneList::const_iterator pitr;
        for(pitr = _planes->begin();
            pitr != _planes->end();
            ++pitr)
        {
            const osg::Plane& plane = *pitr;
            float d1 = plane.distance(v1);
            float d2 = plane.distance(v2);

            unsigned int numOutside = ((d1<0.0)?1:0) + ((d2<0.0)?1:0);
            if (numOutside==2)
            {
                //OSG_NOTICE<<"Line totally outside frustum "<<d1<<"\t"<<d2<<std::endl;
                return;
            }
            unsigned int numInside = ((d1>=0.0)?1:0) + ((d2>=0.0)?1:0);
            if (numInside<2)
            {
                active_mask = active_mask | selector_mask;
            }

            //OSG_NOTICE<<"Line ok w.r.t plane "<<d1<<"\t"<<d2<<std::endl;

            selector_mask <<= 1;
        }

        if (active_mask==0)
        {
            _znear = minimum(_znear,n1);
            _znear = minimum(_znear,n2);
            // OSG_NOTICE<<"Line all inside frustum "<<n1<<"\t"<<n2<<" number of plane="<<_planes->size()<<std::endl;
            return;
        }

        //OSG_NOTICE<<"Using brute force method of line cutting frustum walls"<<std::endl;
        DistancePoint p1(0, v1);
        DistancePoint p2(0, v2);

        selector_mask = 0x1;

        for(pitr = _planes->begin();
            pitr != _planes->end();
            ++pitr)
        {
            if (active_mask & selector_mask)
            {
                // clip line to plane
                const osg::Plane& plane = *pitr;

                // assign the distance from the current plane.
                p1.first = plane.distance(p1.second);
                p2.first = plane.distance(p2.second);

                if (p1.first >= 0.0f)
                {
                    // p1 is in.
                    if (p2.first < 0.0)
                    {
                        // p2 is out.
                        // replace p2 with intersection
                        float r = p1.first/(p1.first-p2.first);
                        p2 = DistancePoint(0.0f, p1.second*(1.0f-r) + p2.second*r);

                    }
                }
                else if (p2.first >= 0.0f)
                {
                    // p1 is out and p2 is in.
                    // replace p1 with intersection
                    float r = p1.first/(p1.first-p2.first);
                    p1 = DistancePoint(0.0f, p1.second*(1.0f-r) + p2.second*r);
                }
                // The case where both are out was handled above.
            }
            selector_mask <<= 1;
        }

        n1 = distance(p1.second,_matrix);
        n2 = distance(p2.second,_matrix);
        _znear = _comparator.minimum(n1, n2);
        //OSG_NOTICE<<"Near plane updated "<<_znear<<std::endl;
    }

    // Handle Triangles
    inline void operator() ( const osg::Vec3 &v1, const osg::Vec3 &v2, const osg::Vec3 &v3, bool)
    {
        CullVisitor::value_type n1 = distance(v1,_matrix);
        CullVisitor::value_type n2 = distance(v2,_matrix);
        CullVisitor::value_type n3 = distance(v3,_matrix);

        // check if triangle is total behind znear, if so discard
        if (_comparator.greaterEqual(n1,_znear) &&
            _comparator.greaterEqual(n2,_znear) &&
            _comparator.greaterEqual(n3,_znear))
        {
            //OSG_NOTICE<<"Triangle totally beyond znear"<<std::endl;
            return;
        }


        if (n1 < 0.0 &&
            n2 < 0.0 &&
            n3 < 0.0)
        {
            // OSG_NOTICE<<"Triangle totally behind eye point"<<std::endl;
            return;
        }

        // Check each vertex to each frustum plane.
        osg::Polytope::ClippingMask selector_mask = 0x1;
        osg::Polytope::ClippingMask active_mask = 0x0;

        osg::Polytope::PlaneList::const_iterator pitr;
        for(pitr = _planes->begin();
            pitr != _planes->end();
            ++pitr)
        {
            const osg::Plane& plane = *pitr;
            float d1 = plane.distance(v1);
            float d2 = plane.distance(v2);
            float d3 = plane.distance(v3);

            unsigned int numOutside = ((d1<0.0)?1:0) + ((d2<0.0)?1:0) + ((d3<0.0)?1:0);
            if (numOutside==3)
            {
                //OSG_NOTICE<<"Triangle totally outside frustum "<<d1<<"\t"<<d2<<"\t"<<d3<<std::endl;
                return;
            }
            unsigned int numInside = ((d1>=0.0)?1:0) + ((d2>=0.0)?1:0) + ((d3>=0.0)?1:0);
            if (numInside<3)
            {
                active_mask = active_mask | selector_mask;
            }

            //OSG_NOTICE<<"Triangle ok w.r.t plane "<<d1<<"\t"<<d2<<"\t"<<d3<<std::endl;

            selector_mask <<= 1;
        }

        if (active_mask==0)
        {
            _znear = _comparator.minimum(_znear,n1);
            _znear = _comparator.minimum(_znear,n2);
            _znear = _comparator.minimum(_znear,n3);
            // OSG_NOTICE<<"Triangle all inside frustum "<<n1<<"\t"<<n2<<"\t"<<n3<<" number of plane="<<_planes->size()<<std::endl;
            return;
        }

        //return;

        // numPartiallyInside>0) so we have a triangle cutting an frustum wall,
        // this means that use brute force methods for dividing up triangle.

        //OSG_NOTICE<<"Using brute force method of triangle cutting frustum walls"<<std::endl;
        _polygonOriginal.clear();
        _polygonOriginal.push_back(DistancePoint(0,v1));
        _polygonOriginal.push_back(DistancePoint(0,v2));
        _polygonOriginal.push_back(DistancePoint(0,v3));

        selector_mask = 0x1;

        for(pitr = _planes->begin();
            pitr != _planes->end() && !_polygonOriginal.empty();
            ++pitr)
        {
            if (active_mask & selector_mask)
            {
                // polygon bisects plane so need to divide it up.
                const osg::Plane& plane = *pitr;
                _polygonNew.clear();

                // assign the distance from the current plane.
                for(Polygon::iterator polyItr = _polygonOriginal.begin();
                    polyItr != _polygonOriginal.end();
                    ++polyItr)
                {
                    polyItr->first = plane.distance(polyItr->second);
                }

                // create the new polygon by clamping against the
                unsigned int psize = _polygonOriginal.size();

                for(unsigned int ci = 0; ci < psize; ++ci)
                {
                    unsigned int ni = (ci+1)%psize;
                    bool computeIntersection = false;
                    if (_polygonOriginal[ci].first>=0.0f)
                    {
                        _polygonNew.push_back(_polygonOriginal[ci]);

                        if (_polygonOriginal[ni].first<0.0f) computeIntersection = true;
                    }
                    else if (_polygonOriginal[ni].first>0.0f) computeIntersection = true;


                    if (computeIntersection)
                    {
                        // segment intersects with the plane, compute new position.
                        float r = _polygonOriginal[ci].first/(_polygonOriginal[ci].first-_polygonOriginal[ni].first);
                        _polygonNew.push_back(DistancePoint(0.0f,_polygonOriginal[ci].second*(1.0f-r) + _polygonOriginal[ni].second*r));
                    }
                }
                _polygonOriginal.swap(_polygonNew);
            }
            selector_mask <<= 1;
        }

        // now take the nearst points to the eye point.
        for(Polygon::iterator polyItr = _polygonOriginal.begin();
            polyItr != _polygonOriginal.end();
            ++polyItr)
        {
            CullVisitor::value_type dist = distance(polyItr->second,_matrix);
            if (_comparator.less(dist,_znear))
            {
                _znear = dist;
                //OSG_NOTICE<<"Near plane updated "<<_znear<<std::endl;
            }
        }
    }

    // Handle Quadrilaterals
    inline void operator() ( const osg::Vec3 &v1, const osg::Vec3 &v2, const osg::Vec3 &v3, const osg::Vec3 &v4, bool treatVertexDataAsTemporary)
    {
        this->operator()(v1, v2, v3, treatVertexDataAsTemporary);
        this->operator()(v1, v3, v4, treatVertexDataAsTemporary);
    }
};


struct LessComparator
{
    inline bool less(CullVisitor::value_type lhs, CullVisitor::value_type rhs) const { return lhs<rhs; }
    inline bool lessEqual(CullVisitor::value_type lhs, CullVisitor::value_type rhs) const { return lhs<=rhs; }
    inline bool greaterEqual(CullVisitor::value_type lhs, CullVisitor::value_type rhs) const { return lhs>=rhs; }
    inline CullVisitor::value_type minimum(CullVisitor::value_type lhs, CullVisitor::value_type rhs) const { return lhs<rhs?lhs:rhs; }
};
typedef ComputeNearFarFunctor<LessComparator> ComputeNearestPointFunctor;

struct GreaterComparator
{
    inline bool less(CullVisitor::value_type lhs, CullVisitor::value_type rhs) const { return lhs>rhs; }
    inline bool lessEqual(CullVisitor::value_type lhs, CullVisitor::value_type rhs) const { return lhs>=rhs; }
    inline bool greaterEqual(CullVisitor::value_type lhs, CullVisitor::value_type rhs) const { return lhs<=rhs; }
    inline CullVisitor::value_type minimum(CullVisitor::value_type lhs, CullVisitor::value_type rhs) const { return lhs>rhs?lhs:rhs; }
};
typedef ComputeNearFarFunctor<GreaterComparator> ComputeFurthestPointFunctor;


CullVisitor::value_type CullVisitor::computeNearestPointInFrustum(const osg::Matrix& matrix, const osg::Polytope::PlaneList& planes,const osg::Drawable& drawable)
{
    // OSG_NOTICE<<"CullVisitor::computeNearestPointInFrustum("<<getTraversalNumber()<<"\t"<<planes.size()<<std::endl;

    osg::TemplatePrimitiveFunctor<ComputeNearestPointFunctor> cnpf;
    cnpf.set(FLT_MAX, matrix, &planes);

    drawable.accept(cnpf);

    return cnpf._znear;
}

CullVisitor::value_type CullVisitor::computeFurthestPointInFrustum(const osg::Matrix& matrix, const osg::Polytope::PlaneList& planes,const osg::Drawable& drawable)
{
    //OSG_NOTICE<<"CullVisitor::computeFurthestPointInFrustum("<<getTraversalNumber()<<"\t"<<planes.size()<<")"<<std::endl;

    osg::TemplatePrimitiveFunctor<ComputeFurthestPointFunctor> cnpf;
    cnpf.set(-FLT_MAX, matrix, &planes);

    drawable.accept(cnpf);

    return cnpf._znear;
}

bool CullVisitor::updateCalculatedNearFar(const osg::Matrix& matrix,const osg::BoundingBox& bb)
{
    // efficient computation of near and far, only taking into account the nearest and furthest
    // corners of the bounding box.
    value_type d_near = distance(bb.corner(_bbCornerNear),matrix);
    value_type d_far = distance(bb.corner(_bbCornerFar),matrix);

    if (d_near>d_far)
    {
        std::swap(d_near,d_far);
        if ( !EQUAL_F(d_near, d_far) )
        {
            OSG_WARN<<"Warning: CullVisitor::updateCalculatedNearFar(.) near>far in range calculation,"<< std::endl;
            OSG_WARN<<"         correcting by swapping values d_near="<<d_near<<" dfar="<<d_far<< std::endl;
        }
    }

    if (d_far<0.0)
    {
        // whole object behind the eye point so discard
        return false;
    }

    if (d_near<_computed_znear) _computed_znear = d_near;
    if (d_far>_computed_zfar) _computed_zfar = d_far;

    return true;
}

bool CullVisitor::updateCalculatedNearFar(const osg::Matrix& matrix,const osg::Drawable& drawable, bool isBillboard)
{
    const osg::BoundingBox& bb = drawable.getBoundingBox();

    value_type d_near, d_far;

    if (isBillboard)
    {

#ifdef TIME_BILLBOARD_NEAR_FAR_CALCULATION
        static unsigned int lastFrameNumber = getTraversalNumber();
        static unsigned int numBillboards = 0;
        static double elapsed_time = 0.0;
        if (lastFrameNumber != getTraversalNumber())
        {
            OSG_NOTICE<<"Took "<<elapsed_time<<"ms to test "<<numBillboards<<" billboards"<<std::endl;
            numBillboards = 0;
            elapsed_time = 0.0;
            lastFrameNumber = getTraversalNumber();
        }
        osg::Timer_t start_t = osg::Timer::instance()->tick();
#endif

        osg::Vec3 lookVector(-matrix(0,2),-matrix(1,2),-matrix(2,2));

        unsigned int bbCornerFar = (lookVector.x()>=0?1:0) +
                       (lookVector.y()>=0?2:0) +
                       (lookVector.z()>=0?4:0);

        unsigned int bbCornerNear = (~bbCornerFar)&7;

        d_near = distance(bb.corner(bbCornerNear),matrix);
        d_far = distance(bb.corner(bbCornerFar),matrix);

        OSG_NOTICE.precision(15);

        if (false)
        {

            OSG_NOTICE<<"TESTING Billboard near/far computation"<<std::endl;

             // OSG_WARN<<"Checking corners of billboard "<<std::endl;
            // deprecated brute force way, use all corners of the bounding box.
            value_type nd_near, nd_far;
            nd_near = nd_far = distance(bb.corner(0),matrix);
            for(unsigned int i=0;i<8;++i)
            {
                value_type d = distance(bb.corner(i),matrix);
                if (d<nd_near) nd_near = d;
                if (d>nd_far) nd_far = d;
                OSG_NOTICE<<"\ti="<<i<<"\td="<<d<<std::endl;
            }

            if (nd_near==d_near && nd_far==d_far)
            {
                OSG_NOTICE<<"\tBillboard near/far computation correct "<<std::endl;
            }
            else
            {
                OSG_NOTICE<<"\tBillboard near/far computation ERROR\n\t\t"<<d_near<<"\t"<<nd_near
                                        <<"\n\t\t"<<d_far<<"\t"<<nd_far<<std::endl;
            }

        }

#ifdef TIME_BILLBOARD_NEAR_FAR_CALCULATION
        osg::Timer_t end_t = osg::Timer::instance()->tick();

        elapsed_time += osg::Timer::instance()->delta_m(start_t,end_t);
        ++numBillboards;
#endif
    }
    else
    {
        // efficient computation of near and far, only taking into account the nearest and furthest
        // corners of the bounding box.
        d_near = distance(bb.corner(_bbCornerNear),matrix);
        d_far = distance(bb.corner(_bbCornerFar),matrix);
    }

    if (d_near>d_far)
    {
        std::swap(d_near,d_far);
        if ( !EQUAL_F(d_near, d_far) )
        {
            OSG_WARN<<"Warning: CullVisitor::updateCalculatedNearFar(.) near>far in range calculation,"<< std::endl;
            OSG_WARN<<"         correcting by swapping values d_near="<<d_near<<" dfar="<<d_far<< std::endl;
        }
    }

    if (d_far<0.0)
    {
        // whole object behind the eye point so discard
        return false;
    }

    if (_computeNearFar==COMPUTE_NEAR_FAR_USING_PRIMITIVES || _computeNearFar==COMPUTE_NEAR_USING_PRIMITIVES)
    {
        if (d_near<_computed_znear || d_far>_computed_zfar)
        {
            osg::Polytope& frustum = getCurrentCullingSet().getFrustum();
            if (frustum.getResultMask())
            {
                MatrixPlanesDrawables mpd;
                if (isBillboard)
                {
                    // OSG_WARN<<"Adding billboard into deffered list"<<std::endl;
                    osg::Polytope transformed_frustum;
                    transformed_frustum.setAndTransformProvidingInverse(getProjectionCullingStack().back().getFrustum(),matrix);
                    mpd.set(matrix,&drawable,transformed_frustum);
                }
                else
                {
                    mpd.set(matrix,&drawable,frustum);
                }

                if (d_near<_computed_znear)
                {
                    _nearPlaneCandidateMap.insert(DistanceMatrixDrawableMap::value_type(d_near,mpd) );
                }

                if (_computeNearFar==COMPUTE_NEAR_FAR_USING_PRIMITIVES)
                {
                    if (d_far>_computed_zfar)
                    {
                        _farPlaneCandidateMap.insert(DistanceMatrixDrawableMap::value_type(d_far,mpd) );
                    }
                }

                // use the far point if its nearer than current znear as this is a conservative estimate of the znear
                // while the final computation for this drawable is deferred.
                if (d_far>=0.0 && d_far<_computed_znear)
                {
                    //_computed_znear = d_far;
                }

                if (_computeNearFar==COMPUTE_NEAR_FAR_USING_PRIMITIVES)
                {
                    // use the near point if its further than current zfar as this is a conservative estimate of the zfar
                    // while the final computation for this drawable is deferred.
                    if (d_near>=0.0 && d_near>_computed_zfar)
                    {
                        // _computed_zfar = d_near;
                    }
                }
                else // computing zfar using bounding sphere
                {
                    if (d_far>_computed_zfar) _computed_zfar = d_far;
                }
            }
            else
            {
                if (d_near<_computed_znear) _computed_znear = d_near;
                if (d_far>_computed_zfar) _computed_zfar = d_far;
            }
        }
    }
    else
    {
        if (d_near<_computed_znear) _computed_znear = d_near;
        if (d_far>_computed_zfar) _computed_zfar = d_far;
    }


/*
    // deprecated brute force way, use all corners of the bounding box.
    updateCalculatedNearFar(bb.corner(0));
    updateCalculatedNearFar(bb.corner(1));
    updateCalculatedNearFar(bb.corner(2));
    updateCalculatedNearFar(bb.corner(3));
    updateCalculatedNearFar(bb.corner(4));
    updateCalculatedNearFar(bb.corner(5));
    updateCalculatedNearFar(bb.corner(6));
    updateCalculatedNearFar(bb.corner(7));
*/

    return true;

}
void CullVisitor::updateCalculatedNearFar(const osg::Vec3& pos)
{
    float d;
    if (!_modelviewStack.empty())
    {
        const osg::Matrix& matrix = *(_modelviewStack.back());
        d = distance(pos,matrix);
    }
    else
    {
        d = -pos.z();
    }

    if (d<_computed_znear)
    {
       _computed_znear = d;
       if (d<0.0) OSG_WARN<<"Alerting billboard ="<<d<< std::endl;
    }
    if (d>_computed_zfar) _computed_zfar = d;
}

void CullVisitor::apply(Node& node)
{
    if (isCulled(node)) return;

    // push the culling mode.
    pushCurrentMask();

    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);

    handle_cull_callbacks_and_traverse(node);

    // pop the node's state off the geostate stack.
    if (node_state) popStateSet();

    // pop the culling mode.
    popCurrentMask();
}


void CullVisitor::apply(Geode& node)
{
    if (isCulled(node)) return;

    // push the culling mode.
    pushCurrentMask();

    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);

    handle_cull_callbacks_and_traverse(node);

    // pop the node's state off the geostate stack.
    if (node_state) popStateSet();

    // pop the culling mode.
    popCurrentMask();
}

void CullVisitor::apply(osg::Drawable& drawable)
{
    RefMatrix& matrix = *getModelViewMatrix();

    const BoundingBox &bb =drawable.getBoundingBox();

    if( drawable.getCullCallback() )
    {
        osg::Drawable::CullCallback* dcb = dynamic_cast<osg::Drawable::CullCallback*>(drawable.getCullCallback());
        if (dcb)
        {
            if( dcb->cull( this, &drawable, &_renderInfo ) == true ) return;
        }
    }

    if (!getNodePath().empty() && getNodePath().back()->isCullingActive() && isCulled(bb)) return;


    if (_computeNearFar && bb.valid())
    {
        if (!updateCalculatedNearFar(matrix,drawable,false)) return;
    }

    // need to track how push/pops there are, so we can unravel the stack correctly.
    unsigned int numPopStateSetRequired = 0;

    // push the geoset's state on the geostate stack.
    StateSet* stateset = drawable.getStateSet();
    if (stateset)
    {
        ++numPopStateSetRequired;
        pushStateSet(stateset);
    }

    CullingSet& cs = getCurrentCullingSet();
    if (!cs.getStateFrustumList().empty())
    {
        osg::CullingSet::StateFrustumList& sfl = cs.getStateFrustumList();
        for(osg::CullingSet::StateFrustumList::iterator itr = sfl.begin();
            itr != sfl.end();
            ++itr)
        {
            if (itr->second.contains(bb))
            {
                ++numPopStateSetRequired;
                pushStateSet(itr->first.get());
            }
        }
    }

    float depth = bb.valid() ? distance(bb.center(),matrix) : 0.0f;

    if (osg::isNaN(depth))
    {
        OSG_NOTICE<<"CullVisitor::apply(Geode&) detected NaN,"<<std::endl
                                <<"    depth="<<depth<<", center=("<<bb.center()<<"),"<<std::endl
                                <<"    matrix="<<matrix<<std::endl;
        OSG_DEBUG << "    NodePath:" << std::endl;
        for (NodePath::const_iterator i = getNodePath().begin(); i != getNodePath().end(); ++i)
        {
            OSG_DEBUG << "        \"" << (*i)->getName() << "\"" << std::endl;
        }
    }
    else
    {
        addDrawableAndDepth(&drawable,&matrix,depth);
    }

    for(unsigned int i=0;i< numPopStateSetRequired; ++i)
    {
        popStateSet();
    }
}


void CullVisitor::apply(Billboard& node)
{
    if (isCulled(node)) return;

    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);

    // Don't traverse billboard, since drawables are handled manually below
    //handle_cull_callbacks_and_traverse(node);

    const Vec3& eye_local = getEyeLocal();
    const RefMatrix& modelview = *getModelViewMatrix();

    for(unsigned int i=0;i<node.getNumDrawables();++i)
    {
        const Vec3& pos = node.getPosition(i);

        Drawable* drawable = node.getDrawable(i);
        // need to modify isCulled to handle the billboard offset.
        // if (isCulled(drawable->getBound())) continue;

        if( drawable->getCullCallback() )
        {
            osg::Drawable::CullCallback* dcb = dynamic_cast<osg::Drawable::CullCallback*>(drawable->getCullCallback());
            if (dcb && dcb->cull( this, drawable, &_renderInfo ) == true )
                continue;
        }

        RefMatrix* billboard_matrix = createOrReuseMatrix(modelview);

        node.computeMatrix(*billboard_matrix,eye_local,pos);


        if (_computeNearFar && drawable->getBoundingBox().valid()) updateCalculatedNearFar(*billboard_matrix,*drawable,true);
        float depth = distance(pos,modelview);
/*
        if (_computeNearFar)
        {
            if (d<_computed_znear)
            {
                if (d<0.0) OSG_WARN<<"Alerting billboard handling ="<<d<< std::endl;
                _computed_znear = d;
            }
            if (d>_computed_zfar) _computed_zfar = d;
        }
*/
        StateSet* stateset = drawable->getStateSet();
        if (stateset) pushStateSet(stateset);

        if (osg::isNaN(depth))
        {
            OSG_NOTICE<<"CullVisitor::apply(Billboard&) detected NaN,"<<std::endl
                                    <<"    depth="<<depth<<", pos=("<<pos<<"),"<<std::endl
                                    <<"    *billboard_matrix="<<*billboard_matrix<<std::endl;
            OSG_DEBUG << "    NodePath:" << std::endl;
            for (NodePath::const_iterator i = getNodePath().begin(); i != getNodePath().end(); ++i)
            {
                OSG_DEBUG << "        \"" << (*i)->getName() << "\"" << std::endl;
            }
        }
        else
        {
            addDrawableAndDepth(drawable,billboard_matrix,depth);
        }

        if (stateset) popStateSet();

    }

    // pop the node's state off the geostate stack.
    if (node_state) popStateSet();

}


void CullVisitor::apply(LightSource& node)
{
    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);

    StateAttribute* light = node.getLight();
    if (light)
    {
        if (node.getReferenceFrame()==osg::LightSource::RELATIVE_RF)
        {
            RefMatrix& matrix = *getModelViewMatrix();
            addPositionedAttribute(&matrix,light);
        }
        else
        {
            // relative to absolute.
            addPositionedAttribute(0,light);
        }
    }

    handle_cull_callbacks_and_traverse(node);

    // pop the node's state off the geostate stack.
    if (node_state) popStateSet();
}

void CullVisitor::apply(ClipNode& node)
{
    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);

    RefMatrix& matrix = *getModelViewMatrix();

    const ClipNode::ClipPlaneList& planes = node.getClipPlaneList();
    for(ClipNode::ClipPlaneList::const_iterator itr=planes.begin();
        itr!=planes.end();
        ++itr)
    {
        if (node.getReferenceFrame()==osg::ClipNode::RELATIVE_RF)
        {
            addPositionedAttribute(&matrix,itr->get());
        }
        else
        {
            addPositionedAttribute(0,itr->get());
        }
    }

    handle_cull_callbacks_and_traverse(node);

    // pop the node's state off the geostate stack.
    if (node_state) popStateSet();
}

void CullVisitor::apply(TexGenNode& node)
{
    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);


    if (node.getReferenceFrame()==osg::TexGenNode::RELATIVE_RF)
    {
        RefMatrix& matrix = *getModelViewMatrix();
        addPositionedTextureAttribute(node.getTextureUnit(), &matrix ,node.getTexGen());
    }
    else
    {
        addPositionedTextureAttribute(node.getTextureUnit(), 0 ,node.getTexGen());
    }

    handle_cull_callbacks_and_traverse(node);

    // pop the node's state off the geostate stack.
    if (node_state) popStateSet();
}

void CullVisitor::apply(Group& node)
{
    if (isCulled(node)) return;

    // push the culling mode.
    pushCurrentMask();

    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);

    handle_cull_callbacks_and_traverse(node);

    // pop the node's state off the render graph stack.
    if (node_state) popStateSet();

    // pop the culling mode.
    popCurrentMask();
}

void CullVisitor::apply(Transform& node)
{
    if (isCulled(node)) return;

    // push the culling mode.
    pushCurrentMask();

    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);

    ref_ptr<RefMatrix> matrix = createOrReuseMatrix(*getModelViewMatrix());
    node.computeLocalToWorldMatrix(*matrix,this);
    pushModelViewMatrix(matrix.get(), node.getReferenceFrame());

    handle_cull_callbacks_and_traverse(node);

    popModelViewMatrix();

    // pop the node's state off the render graph stack.
    if (node_state) popStateSet();

    // pop the culling mode.
    popCurrentMask();
}

void CullVisitor::apply(Projection& node)
{

    // push the culling mode.
    pushCurrentMask();

    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);


    // record previous near and far values.
    float previous_znear = _computed_znear;
    float previous_zfar = _computed_zfar;

    // take a copy of the current near plane candidates
    DistanceMatrixDrawableMap  previousNearPlaneCandidateMap;
    previousNearPlaneCandidateMap.swap(_nearPlaneCandidateMap);

    DistanceMatrixDrawableMap  previousFarPlaneCandidateMap;
    previousFarPlaneCandidateMap.swap(_farPlaneCandidateMap);

    _computed_znear = FLT_MAX;
    _computed_zfar = -FLT_MAX;


    ref_ptr<RefMatrix> matrix = createOrReuseMatrix(node.getMatrix());
    pushProjectionMatrix(matrix.get());

    //OSG_INFO<<"Push projection "<<*matrix<<std::endl;

    // note do culling check after the frustum has been updated to ensure
    // that the node is not culled prematurely.
    if (!isCulled(node))
    {
        handle_cull_callbacks_and_traverse(node);
    }

    popProjectionMatrix();

    //OSG_INFO<<"Pop projection "<<*matrix<<std::endl;

    _computed_znear = previous_znear;
    _computed_zfar = previous_zfar;

    // swap back the near plane candidates
    previousNearPlaneCandidateMap.swap(_nearPlaneCandidateMap);
    previousFarPlaneCandidateMap.swap(_farPlaneCandidateMap);

    // pop the node's state off the render graph stack.
    if (node_state) popStateSet();

    // pop the culling mode.
    popCurrentMask();
}

void CullVisitor::apply(Switch& node)
{
    apply((Group&)node);
}


void CullVisitor::apply(LOD& node)
{
    if (isCulled(node)) return;

    // push the culling mode.
    pushCurrentMask();

    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);

    handle_cull_callbacks_and_traverse(node);

    // pop the node's state off the render graph stack.
    if (node_state) popStateSet();

    // pop the culling mode.
    popCurrentMask();
}

void CullVisitor::apply(osg::ClearNode& node)
{
    // simply override the current earth sky.
    if (node.getRequiresClear())
    {
      getCurrentRenderBin()->getStage()->setClearColor(node.getClearColor());
      getCurrentRenderBin()->getStage()->setClearMask(node.getClearMask());
    }
    else
    {
      // we have an earth sky implementation to do the work for us
      // so we don't need to clear.
      getCurrentRenderBin()->getStage()->setClearMask(0);
    }

    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);

    handle_cull_callbacks_and_traverse(node);

    // pop the node's state off the render graph stack.
    if (node_state) popStateSet();

}

namespace osgUtil
{

class RenderStageCache : public osg::Object
{
    public:

        RenderStageCache() {}
        RenderStageCache(const RenderStageCache&, const osg::CopyOp&) {}

        META_Object(osgUtil, RenderStageCache);

        void setRenderStage(CullVisitor* cv, RenderStage* rs)
        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
            _renderStageMap[cv] = rs;
        }

        RenderStage* getRenderStage(osgUtil::CullVisitor* cv)
        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
            return _renderStageMap[cv].get();
        }

        typedef std::map<CullVisitor*, osg::ref_ptr<RenderStage> > RenderStageMap;

        /** Resize any per context GLObject buffers to specified size. */
        virtual void resizeGLObjectBuffers(unsigned int maxSize)
        {
            for(RenderStageMap::const_iterator itr = _renderStageMap.begin();
                itr != _renderStageMap.end();
                ++itr)
            {
                itr->second->resizeGLObjectBuffers(maxSize);
            }
        }

        /** If State is non-zero, this function releases any associated OpenGL objects for
           * the specified graphics context. Otherwise, releases OpenGL objexts
           * for all graphics contexts. */
        virtual void releaseGLObjects(osg::State* state= 0) const
        {
            for(RenderStageMap::const_iterator itr = _renderStageMap.begin();
                itr != _renderStageMap.end();
                ++itr)
            {
                itr->second->releaseGLObjects(state);
            }
        }

        OpenThreads::Mutex  _mutex;
        RenderStageMap      _renderStageMap;
};

}

void CullVisitor::apply(osg::Camera& camera)
{
    // push the node's state.
    StateSet* node_state = camera.getStateSet();
    if (node_state) pushStateSet(node_state);

//#define DEBUG_CULLSETTINGS

#ifdef DEBUG_CULLSETTINGS
    if (osg::isNotifyEnabled(osg::NOTICE))
    {
        OSG_NOTICE<<std::endl<<std::endl<<"CullVisitor, before : ";
        write(osg::notify(osg::NOTICE));
    }
#endif

    // Save current cull settings
    CullSettings saved_cull_settings(*this);

#ifdef DEBUG_CULLSETTINGS
    if (osg::isNotifyEnabled(osg::NOTICE))
    {
        OSG_NOTICE<<"CullVisitor, saved_cull_settings : ";
        saved_cull_settings.write(osg::notify(osg::NOTICE));
    }
#endif

#if 1
    // set cull settings from this Camera
    setCullSettings(camera);

#ifdef DEBUG_CULLSETTINGS
    OSG_NOTICE<<"CullVisitor, after setCullSettings(camera) : ";
    write(osg::notify(osg::NOTICE));
#endif
    // inherit the settings from above
    inheritCullSettings(saved_cull_settings, camera.getInheritanceMask());

#ifdef DEBUG_CULLSETTINGS
    OSG_NOTICE<<"CullVisitor, after inheritCullSettings(saved_cull_settings,"<<camera.getInheritanceMask()<<") : ";
    write(osg::notify(osg::NOTICE));
#endif

#else
    // activate all active cull settings from this Camera
    inheritCullSettings(camera);
#endif

    // set the cull mask.
    unsigned int savedTraversalMask = getTraversalMask();
    bool mustSetCullMask = (camera.getInheritanceMask() & osg::CullSettings::CULL_MASK) == 0;
    if (mustSetCullMask) setTraversalMask(camera.getCullMask());

    RefMatrix& originalModelView = *getModelViewMatrix();

    osg::RefMatrix* projection = 0;
    osg::RefMatrix* modelview = 0;

    if (camera.getReferenceFrame()==osg::Transform::RELATIVE_RF)
    {
        if (camera.getTransformOrder()==osg::Camera::POST_MULTIPLY)
        {
            projection = createOrReuseMatrix(*getProjectionMatrix()*camera.getProjectionMatrix());
            modelview = createOrReuseMatrix(*getModelViewMatrix()*camera.getViewMatrix());
        }
        else // pre multiply
        {
            projection = createOrReuseMatrix(camera.getProjectionMatrix()*(*getProjectionMatrix()));
            modelview = createOrReuseMatrix(camera.getViewMatrix()*(*getModelViewMatrix()));
        }
    }
    else
    {
        // an absolute reference frame
        projection = createOrReuseMatrix(camera.getProjectionMatrix());
        modelview = createOrReuseMatrix(camera.getViewMatrix());
    }


    if (camera.getViewport()) pushViewport(camera.getViewport());

    // record previous near and far values.
    value_type previous_znear = _computed_znear;
    value_type previous_zfar = _computed_zfar;

    // take a copy of the current near plane candidates
    DistanceMatrixDrawableMap  previousNearPlaneCandidateMap;
    previousNearPlaneCandidateMap.swap(_nearPlaneCandidateMap);

    DistanceMatrixDrawableMap  previousFarPlaneCandidateMap;
    previousFarPlaneCandidateMap.swap(_farPlaneCandidateMap);

    _computed_znear = FLT_MAX;
    _computed_zfar = -FLT_MAX;

    pushProjectionMatrix(projection);
    pushModelViewMatrix(modelview, camera.getReferenceFrame());


    if (camera.getRenderOrder()==osg::Camera::NESTED_RENDER)
    {
        handle_cull_callbacks_and_traverse(camera);
    }
    else
    {
        // set up lighting.
        // currently ignore lights in the scene graph itself..
        // will do later.
        osgUtil::RenderStage* previous_stage = getCurrentRenderBin()->getStage();

//        unsigned int contextID = getState() ? getState()->getContextID() : 0;

        // use render to texture stage.
        // create the render to texture stage.
        osg::ref_ptr<osgUtil::RenderStageCache> rsCache = dynamic_cast<osgUtil::RenderStageCache*>(camera.getRenderingCache());
        if (!rsCache)
        {
            rsCache = new osgUtil::RenderStageCache;
            camera.setRenderingCache(rsCache.get());
        }

        osg::ref_ptr<osgUtil::RenderStage> rtts = rsCache->getRenderStage(this);
        if (!rtts)
        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*(camera.getDataChangeMutex()));

            rtts = new osgUtil::RenderStage;
            rsCache->setRenderStage(this,rtts.get());

            rtts->setCamera(&camera);

            if ( camera.getInheritanceMask() & DRAW_BUFFER )
            {
                // inherit draw buffer from above.
                rtts->setDrawBuffer(previous_stage->getDrawBuffer(),previous_stage->getDrawBufferApplyMask());
            }
            else
            {
                rtts->setDrawBuffer(camera.getDrawBuffer());
            }

            if ( camera.getInheritanceMask() & READ_BUFFER )
            {
                // inherit read buffer from above.
                rtts->setReadBuffer(previous_stage->getReadBuffer(), previous_stage->getReadBufferApplyMask());
            }
            else
            {
                rtts->setReadBuffer(camera.getReadBuffer());
            }
        }
        else
        {
            // reusing render to texture stage, so need to reset it to empty it from previous frames contents.
            rtts->reset();
        }

        // set up clera masks/values
        rtts->setClearDepth(camera.getClearDepth());
        rtts->setClearAccum(camera.getClearAccum());
        rtts->setClearStencil(camera.getClearStencil());
        rtts->setClearMask(camera.getClearMask());


        // set up the background color and clear mask.
        if (camera.getInheritanceMask() & CLEAR_COLOR)
        {
            rtts->setClearColor(previous_stage->getClearColor());
        }
        else
        {
            rtts->setClearColor(camera.getClearColor());
        }
        if (camera.getInheritanceMask() & CLEAR_MASK)
        {
            rtts->setClearMask(previous_stage->getClearMask());
        }
        else
        {
            rtts->setClearMask(camera.getClearMask());
        }


        // set the color mask.
        osg::ColorMask* colorMask = camera.getColorMask()!=0 ? camera.getColorMask() : previous_stage->getColorMask();
        rtts->setColorMask(colorMask);

        // set up the viewport.
        osg::Viewport* viewport = camera.getViewport()!=0 ? camera.getViewport() : previous_stage->getViewport();
        rtts->setViewport( viewport );

        // set initial view matrix
        rtts->setInitialViewMatrix(modelview);

        // set up to charge the same PositionalStateContainer is the parent previous stage.
        osg::Matrix inheritedMVtolocalMV;
        inheritedMVtolocalMV.invert(originalModelView);
        inheritedMVtolocalMV.postMult(*getModelViewMatrix());
        rtts->setInheritedPositionalStateContainerMatrix(inheritedMVtolocalMV);
        rtts->setInheritedPositionalStateContainer(previous_stage->getPositionalStateContainer());

        // record the render bin, to be restored after creation
        // of the render to text
        osgUtil::RenderBin* previousRenderBin = getCurrentRenderBin();

        // set the current renderbin to be the newly created stage.
        setCurrentRenderBin(rtts.get());

        // traverse the subgraph
        {
            handle_cull_callbacks_and_traverse(camera);
        }

        // restore the previous renderbin.
        setCurrentRenderBin(previousRenderBin);


        if (rtts->getStateGraphList().size()==0 && rtts->getRenderBinList().size()==0)
        {
            // getting to this point means that all the subgraph has been
            // culled by small feature culling or is beyond LOD ranges.
        }


        // and the render to texture stage to the current stages
        // dependancy list.
        switch(camera.getRenderOrder())
        {
            case osg::Camera::PRE_RENDER:
                getCurrentRenderBin()->getStage()->addPreRenderStage(rtts.get(),camera.getRenderOrderNum());
                break;
            default:
                getCurrentRenderBin()->getStage()->addPostRenderStage(rtts.get(),camera.getRenderOrderNum());
                break;
        }

    }

    // restore the previous model view matrix.
    popModelViewMatrix();

    // restore the previous model view matrix.
    popProjectionMatrix();


    // restore the original near and far values
    _computed_znear = previous_znear;
    _computed_zfar = previous_zfar;

    // swap back the near plane candidates
    previousNearPlaneCandidateMap.swap(_nearPlaneCandidateMap);
    previousFarPlaneCandidateMap.swap(_farPlaneCandidateMap);


    if (camera.getViewport()) popViewport();

    // restore the previous traversal mask settings
    if (mustSetCullMask) setTraversalMask(savedTraversalMask);

    // restore the previous cull settings
    setCullSettings(saved_cull_settings);

    // pop the node's state off the render graph stack.
    if (node_state) popStateSet();

}

void CullVisitor::apply(osg::OccluderNode& node)
{
    // need to check if occlusion node is in the occluder
    // list, if so disable the appropriate ShadowOccluderVolume
    disableAndPushOccludersCurrentMask(_nodePath);


    if (isCulled(node))
    {
        popOccludersCurrentMask(_nodePath);
        return;
    }

    // push the culling mode.
    pushCurrentMask();

    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);



    handle_cull_callbacks_and_traverse(node);

    // pop the node's state off the render graph stack.
    if (node_state) popStateSet();

    // pop the culling mode.
    popCurrentMask();

    // pop the current mask for the disabled occluder
    popOccludersCurrentMask(_nodePath);
}

void CullVisitor::apply(osg::OcclusionQueryNode& node)
{
    if (isCulled(node)) return;

    // push the culling mode.
    pushCurrentMask();

    // push the node's state.
    StateSet* node_state = node.getStateSet();
    if (node_state) pushStateSet(node_state);


    osg::Camera* camera = getCurrentCamera();

    // If previous query indicates visible, then traverse as usual.
    if (node.getPassed( camera, *this ))
        handle_cull_callbacks_and_traverse(node);

    // Traverse the query subtree if OcclusionQueryNode needs to issue another query.
    node.traverseQuery( camera, *this );

    // Traverse the debug bounding geometry, if enabled.
    node.traverseDebug( *this );


    // pop the node's state off the render graph stack.
    if (node_state) popStateSet();

    // pop the culling mode.
    popCurrentMask();
}

