/* -*-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.
*/
#include <osgUtil/SceneView>
#include <osgUtil/UpdateVisitor>
#include <osgUtil/DisplayListVisitor>

#include <osg/Timer>
#include <osg/Notify>
#include <osg/Texture>
#include <osg/VertexProgram>
#include <osg/FragmentProgram>
#include <osg/AlphaFunc>
#include <osg/TexEnv>
#include <osg/ColorMatrix>
#include <osg/LightModel>
#include <osg/CollectOccludersVisitor>

#include <osg/GLU>

using namespace osg;
using namespace osgUtil;

SceneView::SceneView(DisplaySettings* ds)
{
    _displaySettings = ds;

    _backgroundColor.set(0.2f, 0.2f, 0.4f, 1.0f);

    _computeNearFar = CullVisitor::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES;

    _cullingMode = osg::CullStack::DEFAULT_CULLING;
    _LODScale = 1.0f;
    _smallFeatureCullingPixelSize = 3.0f;

    _fusionDistanceMode = PROPORTIONAL_TO_SCREEN_DISTANCE;
    _fusionDistanceValue = 1.0f;

    _lightingMode=HEADLIGHT;
    
    _prioritizeTextures = false;
    
    _viewport = new Viewport;
    
    _initCalled = false;

    _cullMask = 0xffffffff;
    _cullMaskLeft = 0xffffffff;
    _cullMaskRight = 0xffffffff;

}


SceneView::~SceneView()
{
}


void SceneView::setDefaults()
{
    _projectionMatrix.makePerspective(50.0f,1.4f,1.0f,10000.0f);
    _viewMatrix.makeIdentity();

    _globalStateSet = new osg::StateSet;

    _lightingMode=HEADLIGHT;
    _light = new osg::Light;
    _light->setLightNum(0);
    _light->setAmbient(Vec4(0.00f,0.0f,0.00f,1.0f));
    _light->setDiffuse(Vec4(0.8f,0.8f,0.8f,1.0f));
    _light->setSpecular(Vec4(1.0f,1.0f,1.0f,1.0f));

    _state = new State;
    
    _rendergraph = new RenderGraph;
    _renderStage = new RenderStage;


    DisplayListVisitor::Mode  dlvMode = DisplayListVisitor::COMPILE_DISPLAY_LISTS|DisplayListVisitor::COMPILE_STATE_ATTRIBUTES;

#ifdef __sgi
    dlvMode = DisplayListVisitor::COMPILE_STATE_ATTRIBUTES;
#endif

    // sgi's IR graphics has a problem with lighting and display lists, as it seems to store 
    // lighting state with the display list, and the display list visitor doesn't currently apply
    // state before creating display lists. So will disable the init visitor default, this won't
    // affect functionality since the display lists will be created as and when needed.
    DisplayListVisitor* dlv = new DisplayListVisitor(dlvMode);
    dlv->setState(_state.get());
    dlv->setNodeMaskOverride(0xffffffff);
    _initVisitor = dlv;

    _updateVisitor = new UpdateVisitor;

    _cullVisitor = new CullVisitor;

    _cullVisitor->setRenderGraph(_rendergraph.get());
    _cullVisitor->setRenderStage(_renderStage.get());

    _globalStateSet->setGlobalDefaults();
    
    // enable lighting by default.
    _globalStateSet->setMode(GL_LIGHTING, osg::StateAttribute::ON);
    _globalStateSet->setAssociatedModes(_light.get(),osg::StateAttribute::ON);
    
    // enable depth testing by default.
    _globalStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON);

    // set up an alphafunc by default to speed up blending operations.
    osg::AlphaFunc* alphafunc = new osg::AlphaFunc;
    alphafunc->setFunction(osg::AlphaFunc::GREATER,0.0f);
    _globalStateSet->setAttributeAndModes(alphafunc, osg::StateAttribute::ON);

    // set up an texture environment by default to speed up blending operations.
     osg::TexEnv* texenv = new osg::TexEnv;
     texenv->setMode(osg::TexEnv::MODULATE);
     _globalStateSet->setTextureAttributeAndModes(0,texenv, osg::StateAttribute::ON);

    osg::LightModel* lightmodel = new osg::LightModel;
    lightmodel->setAmbientIntensity(osg::Vec4(0.1f,0.1f,0.1f,1.0f));
    _globalStateSet->setAttributeAndModes(lightmodel, osg::StateAttribute::ON);

    _backgroundColor.set(0.2f, 0.2f, 0.4f, 1.0f);

    _cullMask = 0xffffffff;
    _cullMaskLeft = 0xffffffff;
    _cullMaskRight = 0xffffffff;
}

void SceneView::init()
{

    _initCalled = true;

    if (_sceneData.valid() && _initVisitor.valid())
    {
        _initVisitor->reset();
        _initVisitor->setFrameStamp(_frameStamp.get());
        
        if (_frameStamp.valid())
        {
             _initVisitor->setTraversalNumber(_frameStamp->getFrameNumber());
        }
        
        _sceneData->accept(*_initVisitor.get());
        
    } 
}

void SceneView::update()
{
    if (!_initCalled) init();


    if (_sceneData.valid() && _updateVisitor.valid())
    { 
        _updateVisitor->reset();

        _updateVisitor->setFrameStamp(_frameStamp.get());

        // use the frame number for the traversal number.
        if (_frameStamp.valid())
        {
             _updateVisitor->setTraversalNumber(_frameStamp->getFrameNumber());
        }
        
        _sceneData->accept(*_updateVisitor.get());
        
        // now force a recompute of the bounding volume while we are still in
        // the read/write app phase, this should prevent the need to recompute
        // the bounding volumes from within the cull traversal which may be
        // multi-threaded.
        _sceneData->getBound();
    }
    
    
}

osg::Matrixd SceneView::computeLeftEyeProjectionImplementation(const osg::Matrixd& projection) const
{
    double fusionDistance = _displaySettings->getScreenDistance();
    switch(_fusionDistanceMode)
    {
        case(USE_FUSION_DISTANCE_VALUE):
            fusionDistance = _fusionDistanceValue;
            break;
        case(PROPORTIONAL_TO_SCREEN_DISTANCE):
            fusionDistance *= _fusionDistanceValue;
            break;
    }

    double iod = _displaySettings->getEyeSeparation();
    double sd = _displaySettings->getScreenDistance();
    double scale_x = 1.0;
    double scale_y = 1.0;

    if (_displaySettings->getSplitStereoAutoAjustAspectRatio())
    {
        switch(_displaySettings->getStereoMode())
        {
            case(osg::DisplaySettings::HORIZONTAL_SPLIT):
                scale_x = 2.0;
                break;
            case(osg::DisplaySettings::VERTICAL_SPLIT):
                scale_y = 2.0;
                break;
            default:
                break;
        }
    }

    if (_displaySettings->getDisplayType()==osg::DisplaySettings::HEAD_MOUNTED_DISPLAY)
    {
        // head mounted display has the same projection matrix for left and right eyes.
        return osg::Matrixd::scale(scale_x,scale_y,1.0) *
               projection;
    }
    else
    {
        // all other display types assume working like a projected power wall
        // need to shjear projection matrix to account for asymetric frustum due to eye offset.
        return osg::Matrixd(1.0,0.0,0.0,0.0,
                           0.0,1.0,0.0,0.0,
                           iod/(2.0*sd),0.0,1.0,0.0,
                           0.0,0.0,0.0,1.0) *
               osg::Matrixd::scale(scale_x,scale_y,1.0) *
               projection;
    }
}

osg::Matrixd SceneView::computeLeftEyeViewImplementation(const osg::Matrixd& view) const
{
    double fusionDistance = _displaySettings->getScreenDistance();
    switch(_fusionDistanceMode)
    {
        case(USE_FUSION_DISTANCE_VALUE):
            fusionDistance = _fusionDistanceValue;
            break;
        case(PROPORTIONAL_TO_SCREEN_DISTANCE):
            fusionDistance *= _fusionDistanceValue;
            break;
    }

    double iod = _displaySettings->getEyeSeparation();
    double sd = _displaySettings->getScreenDistance();
    double es = 0.5f*iod*(fusionDistance/sd);

    return view *
           osg::Matrixd(1.0,0.0,0.0,0.0,
                       0.0,1.0,0.0,0.0,
                       0.0,0.0,1.0,0.0,
                       es,0.0,0.0,1.0);
}

osg::Matrixd SceneView::computeRightEyeProjectionImplementation(const osg::Matrixd& projection) const
{
    double fusionDistance = _displaySettings->getScreenDistance();
    switch(_fusionDistanceMode)
    {
        case(USE_FUSION_DISTANCE_VALUE):
            fusionDistance = _fusionDistanceValue;
            break;
        case(PROPORTIONAL_TO_SCREEN_DISTANCE):
            fusionDistance *= _fusionDistanceValue;
            break;
    }

    double iod = _displaySettings->getEyeSeparation();
    double sd = _displaySettings->getScreenDistance();
    double scale_x = 1.0;
    double scale_y = 1.0;

    if (_displaySettings->getSplitStereoAutoAjustAspectRatio())
    {
        switch(_displaySettings->getStereoMode())
        {
            case(osg::DisplaySettings::HORIZONTAL_SPLIT):
                scale_x = 2.0;
                break;
            case(osg::DisplaySettings::VERTICAL_SPLIT):
                scale_y = 2.0;
                break;
            default:
                break;
        }
    }

    if (_displaySettings->getDisplayType()==osg::DisplaySettings::HEAD_MOUNTED_DISPLAY)
    {
        // head mounted display has the same projection matrix for left and right eyes.
        return osg::Matrixd::scale(scale_x,scale_y,1.0) *
               projection;
    }
    else
    {
        // all other display types assume working like a projected power wall
        // need to shjear projection matrix to account for asymetric frustum due to eye offset.
        return osg::Matrixd(1.0,0.0,0.0,0.0,
                           0.0,1.0,0.0,0.0,
                           -iod/(2.0*sd),0.0,1.0,0.0,
                           0.0,0.0,0.0,1.0) *
               osg::Matrixd::scale(scale_x,scale_y,1.0) *
               projection;
    }
}

osg::Matrixd SceneView::computeRightEyeViewImplementation(const osg::Matrixd& view) const
{
    float fusionDistance = _displaySettings->getScreenDistance();
    switch(_fusionDistanceMode)
    {
        case(USE_FUSION_DISTANCE_VALUE):
            fusionDistance = _fusionDistanceValue;
            break;
        case(PROPORTIONAL_TO_SCREEN_DISTANCE):
            fusionDistance *= _fusionDistanceValue;
            break;
    }

    double iod = _displaySettings->getEyeSeparation();
    double sd = _displaySettings->getScreenDistance();
    double es = 0.5*iod*(fusionDistance/sd);

    return view *
           osg::Matrixd(1.0,0.0,0.0,0.0,
                       0.0,1.0,0.0,0.0,
                       0.0,0.0,1.0,0.0,
                       -es,0.0,0.0,1.0);
}

void SceneView::cull()
{

    if (!_state)
    {
        osg::notify(osg::INFO) << "Warning: no valid osgUtil::SceneView::_state attached, creating a default state automatically."<< std::endl;

        // note the constructor for osg::State will set ContextID to 0 which will be fine to single context graphics
        // applications which is ok for most apps, but not multiple context/pipe applications.
        _state = new osg::State;
    }

    if (!_localStateSet)
    {
        _localStateSet = new osg::StateSet;
    }
    
    // we in theory should be able to be able to bypass reset, but we'll call it just incase.
    //_state->reset();
   
    _state->setFrameStamp(_frameStamp.get());
    _state->setDisplaySettings(_displaySettings.get());


    if (!_cullVisitor)
    {
        osg::notify(osg::INFO) << "Warning: no valid osgUtil::SceneView:: attached, creating a default CullVisitor automatically."<< std::endl;
        _cullVisitor = new CullVisitor;
    }
    if (!_rendergraph)
    {
        osg::notify(osg::INFO) << "Warning: no valid osgUtil::SceneView:: attached, creating a global default RenderGraph automatically."<< std::endl;
        _rendergraph = new RenderGraph;
    }
    if (!_renderStage)
    {
        osg::notify(osg::INFO) << "Warning: no valid osgUtil::SceneView::_renderStage attached, creating a default RenderStage automatically."<< std::endl;
        _renderStage = new RenderStage;
    }

    if (_displaySettings.valid() && _displaySettings->getStereo()) 
    {

        if (_displaySettings->getStereoMode()==osg::DisplaySettings::LEFT_EYE)
        {
            // set up the left eye.
            _cullVisitor->setTraversalMask(_cullMaskLeft);
            cullStage(computeLeftEyeProjection(_projectionMatrix),computeLeftEyeView(_viewMatrix),_cullVisitor.get(),_rendergraph.get(),_renderStage.get());

        }
        else if (_displaySettings->getStereoMode()==osg::DisplaySettings::RIGHT_EYE)
        {
            // set up the right eye.
            _cullVisitor->setTraversalMask(_cullMaskRight);
            cullStage(computeRightEyeProjection(_projectionMatrix),computeRightEyeView(_viewMatrix),_cullVisitor.get(),_rendergraph.get(),_renderStage.get());
        }
        else
        {

            if (!_cullVisitorLeft.valid()) _cullVisitorLeft = dynamic_cast<CullVisitor*>(_cullVisitor->cloneType());
            if (!_rendergraphLeft.valid()) _rendergraphLeft = dynamic_cast<RenderGraph*>(_rendergraph->cloneType());
            if (!_renderStageLeft.valid()) _renderStageLeft = dynamic_cast<RenderStage*>(_renderStage->clone(osg::CopyOp::DEEP_COPY_ALL));

            if (!_cullVisitorRight.valid()) _cullVisitorRight = dynamic_cast<CullVisitor*>(_cullVisitor->cloneType());
            if (!_rendergraphRight.valid()) _rendergraphRight = dynamic_cast<RenderGraph*>(_rendergraph->cloneType());
            if (!_renderStageRight.valid()) _renderStageRight = dynamic_cast<RenderStage*>(_renderStage->clone(osg::CopyOp::DEEP_COPY_ALL));


            _cullVisitorLeft->setTraversalMask(_cullMaskLeft);
            cullStage(computeLeftEyeProjection(_projectionMatrix),computeLeftEyeView(_viewMatrix),_cullVisitorLeft.get(),_rendergraphLeft.get(),_renderStageLeft.get());


            // set up the right eye.
            _cullVisitorRight->setTraversalMask(_cullMaskRight);
            cullStage(computeRightEyeProjection(_projectionMatrix),computeRightEyeView(_viewMatrix),_cullVisitorRight.get(),_rendergraphRight.get(),_renderStageRight.get());
           
        }

    }
    else
    {

        _cullVisitor->setTraversalMask(_cullMask);
        cullStage(_projectionMatrix,_viewMatrix,_cullVisitor.get(),_rendergraph.get(),_renderStage.get());

    }
    
    
}

void SceneView::cullStage(const osg::Matrixd& projection,const osg::Matrixd& modelview,osgUtil::CullVisitor* cullVisitor, osgUtil::RenderGraph* rendergraph, osgUtil::RenderStage* renderStage)
{

    if (!_sceneData || !_viewport->valid()) return;

    if (!_initCalled) init();


    osg::ref_ptr<RefMatrix> proj = new osg::RefMatrix(projection);
    osg::ref_ptr<RefMatrix> mv = new osg::RefMatrix(modelview);

    // collect any occluder in the view frustum.
    if (_sceneData->containsOccluderNodes())
    {
        //std::cout << "Scene graph contains occluder nodes, searching for them"<<std::endl;
        
        osg::CollectOccludersVisitor cov;
        
        cov.setFrameStamp(_frameStamp.get());

        // use the frame number for the traversal number.
        if (_frameStamp.valid())
        {
             cov.setTraversalNumber(_frameStamp->getFrameNumber());
        }

        cov.pushViewport(_viewport.get());
        cov.pushProjectionMatrix(proj.get());
        cov.pushModelViewMatrix(mv.get());

        // traverse the scene graph to search for occluder in there new positions.
        _sceneData->accept(cov);

        cov.popModelViewMatrix();
        cov.popProjectionMatrix();
        cov.popViewport();
        
        // sort the occluder from largest occluder volume to smallest.
        cov.removeOccludedOccluders();
        
        
        //std::cout << "finished searching for occluder - found "<<cov.getCollectedOccluderSet().size()<<std::endl;
           
        cullVisitor->getOccluderList().clear();
        std::copy(cov.getCollectedOccluderSet().begin(),cov.getCollectedOccluderSet().end(), std::back_insert_iterator<CullStack::OccluderList>(cullVisitor->getOccluderList()));
    }
    


    cullVisitor->reset();

    cullVisitor->setFrameStamp(_frameStamp.get());

    // use the frame number for the traversal number.
    if (_frameStamp.valid())
    {
         cullVisitor->setTraversalNumber(_frameStamp->getFrameNumber());
    }

    cullVisitor->setCullingMode(_cullingMode);
    cullVisitor->setComputeNearFarMode(_computeNearFar);
    cullVisitor->setLODScale(_LODScale);
    cullVisitor->setSmallFeatureCullingPixelSize(_smallFeatureCullingPixelSize);

    cullVisitor->setClearNode(NULL); // reset earth sky on each frame.
    
    cullVisitor->setRenderGraph(rendergraph);
    cullVisitor->setRenderStage(renderStage);

    cullVisitor->setState( _state.get() );

    renderStage->reset();

    // comment out reset of rendergraph since clean is more efficient.
    //  rendergraph->reset();

    // use clean of the rendergraph rather than reset, as it is able to
    // reuse the structure on the rendergraph in the next frame. This
    // achieves a certain amount of frame cohereancy of memory allocation.
    rendergraph->clean();

    renderStage->setViewport(_viewport.get());
    renderStage->setClearColor(_backgroundColor);


    switch(_lightingMode)
    {
    case(HEADLIGHT):
        if (_light.valid()) renderStage->addPositionedAttribute(NULL,_light.get());
        else osg::notify(osg::WARN)<<"Warning: no osg::Light attached to ogUtil::SceneView to provide head light.*/"<<std::endl;
        break;
    case(SKY_LIGHT):
        if (_light.valid()) renderStage->addPositionedAttribute(mv.get(),_light.get());
        else osg::notify(osg::WARN)<<"Warning: no osg::Light attached to ogUtil::SceneView to provide sky light.*/"<<std::endl;
        break;
    case(NO_SCENEVIEW_LIGHT):
        break;
    }            

    if (_globalStateSet.valid()) cullVisitor->pushStateSet(_globalStateSet.get());
    if (_localStateSet.valid()) cullVisitor->pushStateSet(_localStateSet.get());


    cullVisitor->pushViewport(_viewport.get());
    cullVisitor->pushProjectionMatrix(proj.get());
    cullVisitor->pushModelViewMatrix(mv.get());
    

    // traverse the scene graph to generate the rendergraph.
    _sceneData->accept(*cullVisitor);

    cullVisitor->popModelViewMatrix();
    cullVisitor->popProjectionMatrix();
    cullVisitor->popViewport();

    if (_localStateSet.valid()) cullVisitor->popStateSet();
    if (_globalStateSet.valid()) cullVisitor->popStateSet();
    

    const osg::ClearNode* clearNode = cullVisitor->getClearNode();
    if (clearNode)
    {
        if (clearNode->getRequiresClear())
        {
            renderStage->setClearColor(clearNode->getClearColor());
            renderStage->setClearMask(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
            // really should set clear mask here, but what to? Need
            // to consider the stencil and accumulation buffers..
            // will defer to later.  Robert Osfield. October 2001.
        }
        else
        {
            // we have an earth sky implementation to do the work for use
            // so we don't need to clear.
            renderStage->setClearMask(0);
        }
    }

    renderStage->sort();

    // prune out any empty RenderGraph children.
    // note, this would be not required if the rendergraph had been
    // reset at the start of each frame (see top of this method) but
    // a clean has been used instead to try to minimize the amount of
    // allocation and deleteing of the RenderGraph nodes.
    rendergraph->prune();
}



void SceneView::draw()
{

    // note, to support multi-pipe systems the deletion of OpenGL display list
    // and texture objects is deferred until the OpenGL context is the correct
    // context for when the object were originally created.  Here we know what
    // context we are in so can flush the appropriate caches.
    
    //static osg::Timer timer;
    //osg::Timer_t tstart = timer.tick();
    
    double currentTime = _state->getFrameStamp()?_state->getFrameStamp()->getReferenceTime():0.0;
    double availableTime = 0.005; // 5 ms.
    osg::Drawable::flushDeletedDisplayLists(_state->getContextID(),currentTime,availableTime);
    osg::Drawable::flushDeletedVertexBufferObjects(_state->getContextID(),currentTime,availableTime);
    osg::VertexProgram::flushDeletedVertexProgramObjects(_state->getContextID(),currentTime,availableTime);
    osg::FragmentProgram::flushDeletedFragmentProgramObjects(_state->getContextID(),currentTime,availableTime);
    osg::Texture::flushTextureObjects(_state->getContextID(),currentTime,availableTime);

    //osg::Timer_t tend = timer.tick();
    //std::cout<<"time to flush rendering objects"<<timer.delta_m(tstart,tend)<<std::endl;

    _state->setInitialViewMatrix(new osg::RefMatrix(_viewMatrix));

    RenderLeaf* previous = NULL;
    if (_displaySettings.valid() && _displaySettings->getStereo()) 
    {
    
        switch(_displaySettings->getStereoMode())
        {
        case(osg::DisplaySettings::QUAD_BUFFER):
            {

                _localStateSet->setAttribute(_viewport.get());

                // ensure that all color planes are active.
                osg::ColorMask* cmask = static_cast<osg::ColorMask*>(_localStateSet->getAttribute(osg::StateAttribute::COLORMASK));
                if (cmask)
                {
                    cmask->setMask(true,true,true,true);
                }
                else
                {
                    cmask = new osg::ColorMask(true,true,true,true);
                    _localStateSet->setAttribute(cmask);
                }
                _renderStageLeft->setColorMask(cmask);
                _renderStageRight->setColorMask(cmask);

                _renderStageLeft->drawPreRenderStages(*_state,previous);
                _renderStageRight->drawPreRenderStages(*_state,previous);

                glDrawBuffer(GL_BACK_LEFT);
                _renderStageLeft->draw(*_state,previous);

                glDrawBuffer(GL_BACK_RIGHT);
                _renderStageRight->draw(*_state,previous);

            }
            break;
        case(osg::DisplaySettings::ANAGLYPHIC):
            {
                
                _localStateSet->setAttribute(_viewport.get());

                _renderStageLeft->drawPreRenderStages(*_state,previous);
                _renderStageRight->drawPreRenderStages(*_state,previous);


                // ensure that all color planes are active.
                osg::ColorMask* red = _renderStageLeft->getColorMask();
                if (red)
                {
                    red->setMask(true,false,false,true);
                }
                else
                {
                    red = new osg::ColorMask(true,false,false,true);
                }
                _localStateSet->setAttribute(red);
                _renderStageLeft->setColorMask(red);

               // draw left eye.
                _renderStageLeft->draw(*_state,previous);

                // ensure that all color planes are active.
                osg::ColorMask* cyan = _renderStageLeft->getColorMask();
                if (cyan)
                {
                    cyan->setMask(false,true,true,true);
                }
                else
                {
                    cyan = new osg::ColorMask(false,true,true,true);
                }
                _localStateSet->setAttribute(cyan);
                _renderStageRight->setColorMask(cyan);

                // draw right eye.
                osg::ref_ptr<osg::ColorMask> green = new osg::ColorMask;
                green->setMask(false,true,true,true);
                _localStateSet->setAttribute(green.get());
                _renderStageRight->setColorMask(green.get());
                _renderStageRight->draw(*_state,previous);

            }
            break;
        case(osg::DisplaySettings::HORIZONTAL_SPLIT):
            {
                // ensure that all color planes are active.
                osg::ColorMask* cmask = static_cast<osg::ColorMask*>(_localStateSet->getAttribute(osg::StateAttribute::COLORMASK));
                if (cmask)
                {
                    cmask->setMask(true,true,true,true);
                }
                else
                {
                    cmask = new osg::ColorMask(true,true,true,true);
                    _localStateSet->setAttribute(cmask);
                }
                _renderStageLeft->setColorMask(cmask);
                _renderStageRight->setColorMask(cmask);

                _renderStageLeft->drawPreRenderStages(*_state,previous);
                _renderStageRight->drawPreRenderStages(*_state,previous);

                int separation = _displaySettings->getSplitStereoHorizontalSeparation();

                int left_half_width = (_viewport->width()-separation)/2;
                int right_half_begin = (_viewport->width()+separation)/2;
                int right_half_width = _viewport->width()-right_half_begin;

                osg::ref_ptr<osg::Viewport> viewportLeft = new osg::Viewport;
                viewportLeft->setViewport(_viewport->x(),_viewport->y(),left_half_width,_viewport->height());

                osg::ref_ptr<osg::Viewport> viewportRight = new osg::Viewport;
                viewportRight->setViewport(_viewport->x()+right_half_begin,_viewport->y(),right_half_width,_viewport->height());


                clearArea(_viewport->x()+left_half_width,_viewport->y(),separation,_viewport->height(),_renderStageLeft->getClearColor());

                if (_displaySettings->getSplitStereoHorizontalEyeMapping()==osg::DisplaySettings::LEFT_EYE_LEFT_VIEWPORT)
                {
                    _localStateSet->setAttribute(viewportLeft.get());
                    _renderStageLeft->setViewport(viewportLeft.get());
                    _renderStageLeft->draw(*_state,previous);

                    _localStateSet->setAttribute(viewportRight.get());
                    _renderStageRight->setViewport(viewportRight.get());
                    _renderStageRight->draw(*_state,previous);
                }
                else
                {
                    _localStateSet->setAttribute(viewportRight.get());
                    _renderStageLeft->setViewport(viewportRight.get());
                    _renderStageLeft->draw(*_state,previous);

                    _localStateSet->setAttribute(viewportLeft.get());
                    _renderStageRight->setViewport(viewportLeft.get());
                    _renderStageRight->draw(*_state,previous);
                }

            }
            break;
        case(osg::DisplaySettings::VERTICAL_SPLIT):
            {
                // ensure that all color planes are active.
                osg::ColorMask* cmask = static_cast<osg::ColorMask*>(_localStateSet->getAttribute(osg::StateAttribute::COLORMASK));
                if (cmask)
                {
                    cmask->setMask(true,true,true,true);
                }
                else
                {
                    cmask = new osg::ColorMask(true,true,true,true);
                    _localStateSet->setAttribute(cmask);
                }

                _renderStageLeft->setColorMask(cmask);
                _renderStageRight->setColorMask(cmask);
                _renderStageLeft->drawPreRenderStages(*_state,previous);
                _renderStageRight->drawPreRenderStages(*_state,previous);

                int separation = _displaySettings->getSplitStereoVerticalSeparation();

                int bottom_half_height = (_viewport->height()-separation)/2;
                int top_half_begin = (_viewport->height()+separation)/2;
                int top_half_height = _viewport->height()-top_half_begin;

                osg::ref_ptr<osg::Viewport> viewportTop = new osg::Viewport;
                viewportTop->setViewport(_viewport->x(),_viewport->y()+top_half_begin,_viewport->width(),top_half_height);

                osg::ref_ptr<osg::Viewport> viewportBottom = new osg::Viewport;
                viewportBottom->setViewport(_viewport->x(),_viewport->y(),_viewport->width(),bottom_half_height);

                clearArea(_viewport->x(),_viewport->y()+bottom_half_height,_viewport->width(),separation,_renderStageLeft->getClearColor());

                if (_displaySettings->getSplitStereoVerticalEyeMapping()==osg::DisplaySettings::LEFT_EYE_TOP_VIEWPORT)
                {
                    _localStateSet->setAttribute(viewportTop.get());
                    _renderStageLeft->setViewport(viewportTop.get());
                    _renderStageLeft->draw(*_state,previous);

                    _localStateSet->setAttribute(viewportBottom.get());
                    _renderStageRight->setViewport(viewportBottom.get());
                    _renderStageRight->draw(*_state,previous);
                }
                else
                {
                    _localStateSet->setAttribute(viewportBottom.get());
                    _renderStageLeft->setViewport(viewportBottom.get());
                    _renderStageLeft->draw(*_state,previous);

                    _localStateSet->setAttribute(viewportTop.get());
                    _renderStageRight->setViewport(viewportTop.get());
                    _renderStageRight->draw(*_state,previous);
                }
            }
            break;
        case(osg::DisplaySettings::RIGHT_EYE):
        case(osg::DisplaySettings::LEFT_EYE):
            {
                // ensure that all color planes are active.
                osg::ColorMask* cmask = static_cast<osg::ColorMask*>(_localStateSet->getAttribute(osg::StateAttribute::COLORMASK));
                if (cmask)
                {
                    cmask->setMask(true,true,true,true);
                }
                else
                {
                    cmask = new osg::ColorMask(true,true,true,true);
                    _localStateSet->setAttribute(cmask);
                }
                _renderStage->setColorMask(cmask);
                _renderStage->setColorMask(cmask);

                _localStateSet->setAttribute(_viewport.get());
                _renderStage->drawPreRenderStages(*_state,previous);
                _renderStage->draw(*_state,previous);
            }
            break;
        default:
            {
                osg::notify(osg::NOTICE)<<"Warning: stereo mode not implemented yet."<< std::endl;
            }
            break;
        }
    }
    else
    {

	// Need to restore draw buffer when toggling Stereo off.
	glDrawBuffer(GL_BACK);

        _localStateSet->setAttribute(_viewport.get());


        // ensure that all color planes are active.
        osg::ColorMask* cmask = static_cast<osg::ColorMask*>(_localStateSet->getAttribute(osg::StateAttribute::COLORMASK));
        if (cmask)
        {
            cmask->setMask(true,true,true,true);
        }
        else
        {
            cmask = new osg::ColorMask(true,true,true,true);
            _localStateSet->setAttribute(cmask);
        }
        _renderStage->setColorMask(cmask);

        // bog standard draw.
        _renderStage->drawPreRenderStages(*_state,previous);
        _renderStage->draw(*_state,previous);
    }
    
    // re apply the defalt OGL state.
    _state->popAllStateSets();

    GLenum errorNo = glGetError();
    if (errorNo!=GL_NO_ERROR)
    {
        osg::notify(WARN)<<"Warning: detected OpenGL error '"<<gluErrorString(errorNo)<<"'"<< std::endl;
        // go into debug mode of OGL errors.
        _state->setReportGLErrors(true);
    }
}

/** Calculate, via glUnProject, the object coordinates of a window point.
    Note, current implementation requires that SceneView::draw() has been previously called
    for projectWindowIntoObject to produce valid values.  As per OpenGL
    windows coordinates are calculated relative to the bottom left of the window.*/
bool SceneView::projectWindowIntoObject(const osg::Vec3& window,osg::Vec3& object) const
{
    osg::Matrix inverseMVPW;
    inverseMVPW.invert(computeMVPW());
    
    object = window*inverseMVPW;
    
    return true;
}


/** Calculate, via glUnProject, the object coordinates of a window x,y
    when projected onto the near and far planes.
    Note, current implementation requires that SceneView::draw() has been previously called
    for projectWindowIntoObject to produce valid values.  As per OpenGL
    windows coordinates are calculated relative to the bottom left of the window.*/
bool SceneView::projectWindowXYIntoObject(int x,int y,osg::Vec3& near_point,osg::Vec3& far_point) const
{
    osg::Matrix inverseMVPW;
    inverseMVPW.invert(computeMVPW());
    
    near_point = osg::Vec3(x,y,0.0f)*inverseMVPW;
    far_point = osg::Vec3(x,y,1.0f)*inverseMVPW;
        
    return true;
}


/** Calculate, via glProject, the object coordinates of a window.
    Note, current implementation requires that SceneView::draw() has been previously called
    for projectWindowIntoObject to produce valid values.  As per OpenGL
    windows coordinates are calculated relative to the bottom left of the window.*/
bool SceneView::projectObjectIntoWindow(const osg::Vec3& object,osg::Vec3& window) const
{
    window = object*computeMVPW();
    return true;
}

const osg::Matrix SceneView::computeMVPW() const
{
    osg::Matrix matrix( _viewMatrix * _projectionMatrix);
        
    if (_viewport.valid())
        matrix.postMult(_viewport->computeWindowMatrix());
    else
        osg::notify(osg::WARN)<<"osg::Matrix SceneView::computeMVPW() - error no viewport attached to SceneView, coords will be computed inccorectly."<<std::endl;

    return matrix;
}

void SceneView::clearArea(int x,int y,int width,int height,const osg::Vec4& color)
{
    osg::ref_ptr<osg::Viewport> viewport = new osg::Viewport;
    viewport->setViewport(x,y,width,height);

    _state->applyAttribute(viewport.get());
    
    glScissor( x, y, width, height );
    glEnable( GL_SCISSOR_TEST );
    glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
    glClearColor( color[0], color[1], color[2], color[3]);
    glClear( GL_COLOR_BUFFER_BIT);
    glDisable( GL_SCISSOR_TEST );
}

void SceneView::setProjectionMatrixAsOrtho(double left, double right,
                                           double bottom, double top,
                                           double zNear, double zFar)
{
    setProjectionMatrix(osg::Matrixd::ortho(left, right,
                                           bottom, top,
                                           zNear, zFar));
}                                           

void SceneView::setProjectionMatrixAsOrtho2D(double left, double right,
                                             double bottom, double top)
{
    setProjectionMatrix(osg::Matrixd::ortho2D(left, right,
                                             bottom, top));
}

void SceneView::setProjectionMatrixAsFrustum(double left, double right,
                                             double bottom, double top,
                                             double zNear, double zFar)
{
    setProjectionMatrix(osg::Matrixd::frustum(left, right,
                                             bottom, top,
                                             zNear, zFar));
}

void SceneView::setProjectionMatrixAsPerspective(double fovy,double aspectRatio,
                                                 double zNear, double zFar)
{
    setProjectionMatrix(osg::Matrixd::perspective(fovy,aspectRatio,
                                                 zNear, zFar));
}                                      

bool SceneView::getProjectionMatrixAsOrtho(double& left, double& right,
                                           double& bottom, double& top,
                                           double& zNear, double& zFar)
{
    return _projectionMatrix.getOrtho(left, right,
                                       bottom, top,
                                       zNear, zFar);
}

bool SceneView::getProjectionMatrixAsFrustum(double& left, double& right,
                                             double& bottom, double& top,
                                             double& zNear, double& zFar)
{
    return _projectionMatrix.getFrustum(left, right,
                                         bottom, top,
                                         zNear, zFar);
}                                  

bool SceneView::getProjectionMatrixAsPerspective(double& fovy,double& aspectRatio,
                                                 double& zNear, double& zFar)
{
    return _projectionMatrix.getPerspective(fovy, aspectRatio, zNear, zFar);
}                                                 

void SceneView::setViewMatrixAsLookAt(const Vec3& eye,const Vec3& center,const Vec3& up)
{
    setViewMatrix(osg::Matrixd::lookAt(eye,center,up));
}

void SceneView::getViewMatrixAsLookAt(Vec3& eye,Vec3& center,Vec3& up,float lookDistance)
{
    _viewMatrix.getLookAt(eye,center,up,lookDistance);
}
