/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSGUTIL_SCENEVIEW
#define OSGUTIL_SCENEVIEW 1

#include <osg/Node>
#include <osg/StateSet>
#include <osg/Light>
#include <osg/FrameStamp>
#include <osg/DisplaySettings>


#include <osgUtil/CullVisitor>

namespace osgUtil {

/**
 * SceneView is literally a view of a scene, encapsulating the
 * camera (modelview+projection matrices), global state, lights and the scene itself.  Provides
 * methods for setting up the view and rendering it.
*/
class OSGUTIL_EXPORT SceneView : public osg::Referenced
{
    public:

        /** Construct a default scene view.*/
        SceneView(osg::DisplaySettings* ds=NULL);

        /** Set scene view to use default global state, light, camera
         *  and render visitor.
        */
        void setDefaults();

        /** Set the data which to view. The data will typically be
         *  an osg::Scene but can be any osg::Node type.
         */
        void setSceneData(osg::Node* node) { _sceneData = node; }
        /** Get the scene data which to view. The data will typically be
         *  an osg::Scene but can be any osg::Node type.
         */
        osg::Node* getSceneData() { return _sceneData.get(); }

        /** Get the const scene data which to view. The data will typically be
         *  an osg::Scene but can be any osg::Node type.
         */
        const osg::Node* getSceneData() const { return _sceneData.get(); }

        /** Set the viewport of the scene view to use specfied osg::Viewport. */
        void setViewport(osg::Viewport* viewport)
        {
            if (viewport) _viewport = viewport;
            else
            {
                // ensure that _viewport is always valid.
                _viewport = new osg::Viewport;
            }
        }

        /** Set the viewport of the scene view to specified dimensions. */
        void setViewport(int x,int y,int width,int height)
        {
            _viewport->setViewport(x,y,width,height);
        }
        

        /** Get the const viewport. */
        const osg::Viewport* getViewport() const { return _viewport.get(); }

        /** Get the viewport. */
        osg::Viewport* getViewport() { return _viewport.get(); }

        /** Get the viewport of the scene view. */
        void getViewport(int& x,int& y,int& width,int& height)
        {
            _viewport->getViewport(x,y,width,height);
        }

        /** Set the DisplaySettings. */
        inline void setDisplaySettings(osg::DisplaySettings* vs) { _displaySettings = vs; }
        
        /** Get the const DisplaySettings */
        inline const osg::DisplaySettings* getDisplaySettings() const { return _displaySettings.get(); }

        /** Get the DisplaySettings */
        inline osg::DisplaySettings* getDisplaySettings() { return _displaySettings.get(); }


        /** Set the background color used in glClearColor().
            Defaults to an off blue color.*/
        void setBackgroundColor(const osg::Vec4& color) { _backgroundColor=color; }
        /** Get the background color.*/
        const osg::Vec4& getBackgroundColor() const { return _backgroundColor; }

        void setGlobalStateSet(osg::StateSet* state) { _globalStateSet = state; }
        osg::StateSet* getGlobalStateSet() { return _globalStateSet.get(); }
        const osg::StateSet* getGlobalStateSet() const { return _globalStateSet.get(); }

        void setLocalStateSet(osg::StateSet* state) { _localStateSet = state; }
        osg::StateSet* getLocalStateSet() { return _localStateSet.get(); }
        const osg::StateSet* getLocalStateSet() const { return _localStateSet.get(); }

        enum LightingMode {
            HEADLIGHT, // default
            SKY_LIGHT,
            NO_SCENEVIEW_LIGHT
        };

        void setLightingMode(LightingMode mode) { _lightingMode=mode; }
        LightingMode getLightingMode() const { return _lightingMode; }

        void setLight(osg::Light* light) { _light = light; }
        osg::Light* getLight() { return _light.get(); }
        const osg::Light* getLight() const { return _light.get(); }
        
        void setState(osg::State* state) { _state = state; }
        osg::State* getState() { return _state.get(); }
        const osg::State* getState() const { return _state.get(); }



        /** Set the projection matrix. Can be thought of as setting the lens of a camera. */
        inline void setProjectionMatrix(const osg::Matrixf& matrix) { _projectionMatrix.set(matrix); }

        /** Set the projection matrix. Can be thought of as setting the lens of a camera. */
        inline void setProjectionMatrix(const osg::Matrixd& matrix) { _projectionMatrix.set(matrix); }

        /** Set to a orthographic projection. See OpenGL glOrtho for documentation further details.*/
        void setProjectionMatrixAsOrtho(double left, double right,
                                        double bottom, double top,
                                        double zNear, double zFar);

        /** Set to a 2D orthographic projection. See OpenGL glOrtho2D documentation for further details.*/
        void setProjectionMatrixAsOrtho2D(double left, double right,
                                          double bottom, double top);

        /** Set to a perspective projection. See OpenGL glFrustum documentation for further details.*/
        void setProjectionMatrixAsFrustum(double left, double right,
                                          double bottom, double top,
                                          double zNear, double zFar);

        /** Create a symmetrical perspective projection, See OpenGL gluPerspective documentation for further details.
          * Aspect ratio is defined as width/height.*/
        void setProjectionMatrixAsPerspective(double fovy,double aspectRatio,
                                              double zNear, double zFar);

        /** Get the projection matrix.*/
        osg::Matrixd& getProjectionMatrix() { return _projectionMatrix; }

        /** Get the const projection matrix.*/
        const osg::Matrixd& getProjectionMatrix() const { return _projectionMatrix; }

        /** Get the othorgraphic settings of the orthographic projection matrix. 
          * Returns false if matrix is not an orthographic matrix, where parameter values are undefined.*/
        bool getProjectionMatrixAsOrtho(double& left, double& right,
                                        double& bottom, double& top,
                                        double& zNear, double& zFar);

        /** Get the frustum setting of a perspective projection matrix.
          * Returns false if matrix is not a perspective matrix, where parameter values are undefined.*/
        bool getProjectionMatrixAsFrustum(double& left, double& right,
                                          double& bottom, double& top,
                                          double& zNear, double& zFar);

        /** Get the frustum setting of a symetric perspective projection matrix.
          * Returns false if matrix is not a perspective matrix, where parameter values are undefined. 
          * Note, if matrix is not a symetric perspective matrix then the shear will be lost.
          * Asymetric metrices occur when stereo, power walls, caves and reality center display are used.
          * In these configuration one should use the AsFrustum method instead.*/
        bool getProjectionMatrixAsPerspective(double& fovy,double& aspectRatio,
                                              double& zNear, double& zFar);


        /** Set the view matrix. Can be thought of as setting the position of the world relative to the camera in camera coordinates. */
        inline void setViewMatrix(const osg::Matrixf& matrix) { _viewMatrix.set(matrix); }
        
        /** Set the view matrix. Can be thought of as setting the position of the world relative to the camera in camera coordinates. */
        inline void setViewMatrix(const osg::Matrixd& matrix) { _viewMatrix.set(matrix); }

        /** Set to the position and orientation of view matrix, using the same convention as gluLookAt. */
        void setViewMatrixAsLookAt(const osg::Vec3& eye,const osg::Vec3& center,const osg::Vec3& up);


        /** Get the view matrix. */
        osg::Matrixd& getViewMatrix() { return _viewMatrix; }

        /** Get the const view matrix. */
        const osg::Matrixd& getViewMatrix() const { return _viewMatrix; }

        /** Get to the position and orientation of a modelview matrix, using the same convention as gluLookAt. */
        void getViewMatrixAsLookAt(osg::Vec3& eye,osg::Vec3& center,osg::Vec3& up,float lookDistance=1.0f);


        
        void setInitVisitor(osg::NodeVisitor* av) { _initVisitor = av; }
        osg::NodeVisitor* getInitVisitor() { return _initVisitor.get(); }
        const osg::NodeVisitor* getInitVisitor() const { return _initVisitor.get(); }


        void setUpdateVisitor(osg::NodeVisitor* av) { _updateVisitor = av; }
        osg::NodeVisitor* getUpdateVisitor() { return _updateVisitor.get(); }
        const osg::NodeVisitor* getUpdateVisitor() const { return _updateVisitor.get(); }


        void setCullVisitor(osgUtil::CullVisitor* cv) { _cullVisitor = cv; }
        osgUtil::CullVisitor* getCullVisitor() { return _cullVisitor.get(); }
        const osgUtil::CullVisitor* getCullVisitor() const { return _cullVisitor.get(); }

        void setCullVisitorLeft(osgUtil::CullVisitor* cv) { _cullVisitorLeft = cv; }
        osgUtil::CullVisitor* getCullVisitorLeft() { return _cullVisitorLeft.get(); }
        const osgUtil::CullVisitor* getCullVisitorLeft() const { return _cullVisitorLeft.get(); }

        void setCullVisitorRight(osgUtil::CullVisitor* cv) { _cullVisitorRight = cv; }
        osgUtil::CullVisitor* getCullVisitorRight() { return _cullVisitorRight.get(); }
        const osgUtil::CullVisitor* getCullVisitorRight() const { return _cullVisitorRight.get(); }


        void setRenderGraph(osgUtil::RenderGraph* rg) { _rendergraph = rg; }
        osgUtil::RenderGraph* getRenderGraph() { return _rendergraph.get(); }
        const osgUtil::RenderGraph* getRenderGraph() const { return _rendergraph.get(); }

        void setRenderGraphLeft(osgUtil::RenderGraph* rg) { _rendergraphLeft = rg; }
        osgUtil::RenderGraph* getRenderGraphLeft() { return _rendergraphLeft.get(); }
        const osgUtil::RenderGraph* getRenderGraphLeft() const { return _rendergraphLeft.get(); }

        void setRenderGraphRight(osgUtil::RenderGraph* rg) { _rendergraphRight = rg; }
        osgUtil::RenderGraph* getRenderGraphRight() { return _rendergraphRight.get(); }
        const osgUtil::RenderGraph* getRenderGraphRight() const { return _rendergraphRight.get(); }


        void setRenderStage(osgUtil::RenderStage* rs) { _renderStage = rs; }
        osgUtil::RenderStage* getRenderStage() { return _renderStage.get(); }
        const osgUtil::RenderStage* getRenderStage() const { return _renderStage.get(); }

        void setRenderStageLeft(osgUtil::RenderStage* rs) { _renderStageLeft = rs; }
        osgUtil::RenderStage* getRenderStageLeft() { return _renderStageLeft.get(); }
        const osgUtil::RenderStage* getRenderStageLeft() const { return _renderStageLeft.get(); }

        void setRenderStageRight(osgUtil::RenderStage* rs) { _renderStageRight = rs; }
        osgUtil::RenderStage* getRenderStageRight() { return _renderStageRight.get(); }
        const osgUtil::RenderStage* getRenderStageRight() const { return _renderStageRight.get(); }


        void setCullMask(const osg::Node::NodeMask nm) { _cullMask = nm; }
        osg::Node::NodeMask getCullMask() const { return _cullMask; }

        void setCullMaskLeft(const osg::Node::NodeMask nm) { _cullMaskLeft = nm; }
        osg::Node::NodeMask getCullMaskLeft() const { return _cullMaskLeft; }

        void setCullMaskRight(const osg::Node::NodeMask nm) { _cullMaskRight = nm; }
        osg::Node::NodeMask getCullMaskRight() const { return _cullMaskRight; }


        /** Set the LOD bias for the CullVisitor to use.*/
        void setLODScale(float bias) { _LODScale = bias; }
        
        /** Get the LOD bias.*/
        float getLODScale() const { return _LODScale; }

        /** Set the Small Feature Culling Pixel Size.*/
        void setSmallFeatureCullingPixelSize(float value) { _smallFeatureCullingPixelSize=value; }

        /** Get the Small Feature Culling Pixel Size.*/
        float getSmallFeatureCullingPixelSize() const { return _smallFeatureCullingPixelSize; }

        /** Set the culling mode for the CullVisitor to use.*/
        void setCullingMode(osg::CullStack::CullingMode mode) { _cullingMode = mode; }
        
        /** Returns the current CullingMode.*/
        osg::CullStack::CullingMode getCullingMode() const { return _cullingMode; }

        /** Set the ComputeNearFarMode for the CullVisitor to use.*/
        void setComputeNearFarMode(CullVisitor::ComputeNearFarMode cnfm) { _computeNearFar=cnfm; } 
        
        /** Get the ComputeNearFarMode.*/
        CullVisitor::ComputeNearFarMode getComputeNearFarMode() const { return _computeNearFar;} 

        /** FusionDistanceMode is used only when working in stereo.*/
        enum FusionDistanceMode
        {
            /** Use fusion distance from the value set on the SceneView.*/
            USE_FUSION_DISTANCE_VALUE,
            /** Compute the fusion distance by multiplying the screen distance by the  fusion distance value.*/
            PROPORTIONAL_TO_SCREEN_DISTANCE
        };

        /** Set the FusionDistanceMode and Value. Note, is used only when working in stereo.*/
        void setFusionDistance(FusionDistanceMode mode,float value=1.0f)
	{
	    _fusionDistanceMode = mode;
	    _fusionDistanceValue = value;
	}

        /** Get the FusionDistanceMode.*/
        FusionDistanceMode getFusionDistanceMode() const { return _fusionDistanceMode; }

        /** Get the FusionDistanceValue. Note, only used for USE_FUSION_DISTANCE_VALUE & PROPORTIONAL_TO_SCREEN_DISTANCE modes.*/
        float getFusionDistanceValue() const { return _fusionDistanceValue; }


        /** set whether the draw method should call renderer->prioritizeTexture.*/
        void setPrioritizeTextures(bool pt) { _prioritizeTextures = pt; }
        
        /** get whether the draw method should call renderer->prioritizeTexture.*/
        bool getPrioritizeTextures() const { return _prioritizeTextures; }


        /** 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.  Consistent with OpenGL
            windows coordinates are calculated relative to the bottom left of the window.
            Returns true on successful projection.
        */
        bool projectWindowIntoObject(const osg::Vec3& window,osg::Vec3& object) const;

        /** 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.  Consistent with OpenGL
            windows coordinates are calculated relative to the bottom left of the window.
            Returns true on successful projection.
        */
        bool projectWindowXYIntoObject(int x,int y,osg::Vec3& near_point,osg::Vec3& far_point) const;

        /** 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.  Consistent with OpenGL
            windows coordinates are calculated relative to the bottom left of the window,
            whereas as window API's normally have the top left as the origin,
            so you may need to pass in (mouseX,window_height-mouseY,...).
            Returns true on successful projection.
        */
        bool projectObjectIntoWindow(const osg::Vec3& object,osg::Vec3& window) const;


        /** Set the frame stamp for the current frame.*/
        inline void setFrameStamp(osg::FrameStamp* fs) { _frameStamp = fs; }

        /** Set the frame stamp for the current frame.*/
        inline const osg::FrameStamp* getFrameStamp() const { return _frameStamp.get(); }



        /** callback for overidding the default method for compute the offset projection and view matrices.*/
        struct ComputeStereoMatricesCallback : public osg::Referenced
        {
            virtual osg::Matrixd computeLeftEyeProjection(const osg::Matrixd& projection) const = 0;
            virtual osg::Matrixd computeLeftEyeView(const osg::Matrixd& view) const = 0;

            virtual osg::Matrixd computeRightEyeProjection(const osg::Matrixd& projection) const = 0;
            virtual osg::Matrixd computeRightEyeView(const osg::Matrixd& view) const = 0;
        };
        
        void setComputeStereoMatricesCallback(ComputeStereoMatricesCallback* callback) { _computeStereoMatricesCallback=callback; }
        ComputeStereoMatricesCallback* getComputeStereoMatricesCallback(ComputeStereoMatricesCallback* callback) { return _computeStereoMatricesCallback.get(); }
        const ComputeStereoMatricesCallback* getComputeStereoMatricesCallback() const { return _computeStereoMatricesCallback.get(); }

        inline osg::Matrixd computeLeftEyeProjection(const osg::Matrixd& projection)  const
        {
            if (_computeStereoMatricesCallback.valid()) return _computeStereoMatricesCallback->computeLeftEyeProjection(projection);
            else return computeLeftEyeProjectionImplementation(projection);
        }

        inline osg::Matrixd computeLeftEyeView(const osg::Matrixd& view) const
        {
            if (_computeStereoMatricesCallback.valid()) return _computeStereoMatricesCallback->computeLeftEyeView(view);
            else return computeLeftEyeViewImplementation(view);
        }
        
        inline osg::Matrixd computeRightEyeProjection(const osg::Matrixd& projection)  const
        {
            if (_computeStereoMatricesCallback.valid()) return _computeStereoMatricesCallback->computeRightEyeProjection(projection);
            else return computeRightEyeProjectionImplementation(projection);
        }

        inline osg::Matrixd computeRightEyeView(const osg::Matrixd& view) const
        {
            if (_computeStereoMatricesCallback.valid()) return _computeStereoMatricesCallback->computeRightEyeView(view);
            else return computeRightEyeViewImplementation(view);
        }

        virtual osg::Matrixd computeLeftEyeProjectionImplementation(const osg::Matrixd& projection) const;
        virtual osg::Matrixd computeLeftEyeViewImplementation(const osg::Matrixd& view) const;

        virtual osg::Matrixd computeRightEyeProjectionImplementation(const osg::Matrixd& projection) const;
        virtual osg::Matrixd computeRightEyeViewImplementation(const osg::Matrixd& view) const;


        /** Do init traversal of attached scene graph using Init NodeVisitor.
          * The init traversal is called once for each SceneView, and should
          * be used to compile display list, texture objects intialize data
          * not otherwise intializaed during scene graph loading. Note, is
          * called automatically by update&cull if it hasn't already been called
          * elsewhere. Also init() should only ever be called within a valid
          * graphics context.*/
        virtual void init();

        /** Do app traversal of attached scene graph using App NodeVisitor.*/
        virtual void update();

        /** Do cull traversal of attached scene graph using Cull NodeVisitor.*/
        virtual void cull();

        /** Do draw traversal of draw bins generated by cull traversal.*/
        virtual void draw();
        
    protected:

        virtual ~SceneView();

        /** Do cull traversal of attached scene graph using Cull NodeVisitor.*/
        virtual void cullStage(const osg::Matrixd& projection,const osg::Matrixd& modelview,osgUtil::CullVisitor* cullVisitor, osgUtil::RenderGraph* rendergraph, osgUtil::RenderStage* renderStage);

        const osg::Matrix computeMVPW() const;

        void clearArea(int x,int y,int width,int height,const osg::Vec4& color);

        osg::ref_ptr<osg::Node>                     _sceneData;
        osg::ref_ptr<osg::StateSet>                 _globalStateSet;
        osg::ref_ptr<osg::StateSet>                 _localStateSet;
        osg::ref_ptr<osg::Light>                    _light;
        osg::Matrixd                                _projectionMatrix;
        osg::Matrixd                                _viewMatrix;
        osg::ref_ptr<osg::DisplaySettings>          _displaySettings;
        osg::ref_ptr<osg::State>                    _state;
        
        bool                                        _initCalled;
        osg::ref_ptr<osg::NodeVisitor>              _initVisitor;
        osg::ref_ptr<osg::NodeVisitor>              _updateVisitor;
        osg::Node::NodeMask                         _cullMask;
        osg::ref_ptr<osgUtil::CullVisitor>          _cullVisitor;
        osg::ref_ptr<osgUtil::RenderGraph>          _rendergraph;
        osg::ref_ptr<osgUtil::RenderStage>          _renderStage;

        osg::ref_ptr<ComputeStereoMatricesCallback> _computeStereoMatricesCallback;

        osg::Node::NodeMask                          _cullMaskLeft;
        osg::ref_ptr<osgUtil::CullVisitor>          _cullVisitorLeft;
        osg::ref_ptr<osgUtil::RenderGraph>          _rendergraphLeft;
        osg::ref_ptr<osgUtil::RenderStage>          _renderStageLeft;

        osg::Node::NodeMask                          _cullMaskRight;
        osg::ref_ptr<osgUtil::CullVisitor>          _cullVisitorRight;
        osg::ref_ptr<osgUtil::RenderGraph>          _rendergraphRight;
        osg::ref_ptr<osgUtil::RenderStage>          _renderStageRight;

        osg::ref_ptr<osg::FrameStamp>               _frameStamp;

        osg::Vec4                                   _backgroundColor;

        CullVisitor::ComputeNearFarMode             _computeNearFar;
        osg::CullStack::CullingMode                 _cullingMode;
        float                                       _LODScale;
        float                                       _smallFeatureCullingPixelSize;

        FusionDistanceMode                          _fusionDistanceMode;
        float                                       _fusionDistanceValue;

        osg::ref_ptr<osg::Viewport>                 _viewport;

        LightingMode                                _lightingMode;
        
        bool                                        _prioritizeTextures;
        
};

}

#endif

