/* -*-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.
 *
 * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski
 * Thanks to to my company http://www.ai.com.pl for allowing me free this work.
*/

#ifndef OSGSHADOW_MINIMALSHADOWMAP
#define OSGSHADOW_MINIMALSHADOWMAP 1

#include <osgShadow/StandardShadowMap>

namespace osgShadow {

class OSGSHADOW_EXPORT MinimalShadowMap : public StandardShadowMap
{
    public :
        /** Convenient typedef used in definition of ViewData struct and methods */
        typedef MinimalShadowMap  ThisClass;
        /** Convenient typedef used in definition of ViewData struct and methods */
        typedef StandardShadowMap BaseClass;

        /** Classic OSG constructor */
        MinimalShadowMap();

        /** Classic OSG cloning constructor */
        MinimalShadowMap(
            const MinimalShadowMap& msm,
            const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        /** Declaration of standard OSG object methods */
        META_Object( osgShadow, MinimalShadowMap );

        void setModellingSpaceToWorldTransform( const osg::Matrix & modellingSpaceToWorld )
            { _modellingSpaceToWorld = modellingSpaceToWorld; }

        const osg::Matrix & getModellingSpaceToWorldTransform( void ) const
            { return _modellingSpaceToWorld; }

        float getMaxFarPlane( ) const
            { return _maxFarPlane; }

        void setMaxFarPlane( float maxFarPlane )
            { _maxFarPlane = maxFarPlane; }

        float getMinLightMargin( ) const
            { return _minLightMargin; }

        void setMinLightMargin( float minLightMargin )
            { _minLightMargin = minLightMargin; }

        enum ShadowReceivingCoarseBoundAccuracy {
            EMPTY_BOX,
            BOUNDING_SPHERE,
            BOUNDING_BOX,
            DEFAULT_ACCURACY = BOUNDING_BOX
        };

        void  setShadowReceivingCoarseBoundAccuracy
                ( ShadowReceivingCoarseBoundAccuracy accuracy )
                    { _shadowReceivingCoarseBoundAccuracy = accuracy; }

        ShadowReceivingCoarseBoundAccuracy
                getShadowReceivingCoarseBoundAccuracy() const
                    { return _shadowReceivingCoarseBoundAccuracy; }

    protected:
        /** Classic protected OSG destructor */
        virtual ~MinimalShadowMap(void);

    protected:
        // Matrix modellingSpaceToWorld and its inverse
        // are used to define Modelling Space where shadowed scene drawables
        // have minimal (smallest possible extent) bounding boxes.

        // Computing visible shadow range in this space
        // allows for optimal use of ShadowMap resolution.

        // By default it is set to identity ie computations are in world space.
        // But it should be set to ElipsoidModel::localToWorld
        // when scene objects are put on earth ellipsoid surface.

        // Other scenarios are also possible for example when models are
        // built in XZY space which would require identity matrix with swapped columns

        osg::Matrix                        _modellingSpaceToWorld;
        float                              _maxFarPlane;
        float                              _minLightMargin;
        ShadowReceivingCoarseBoundAccuracy _shadowReceivingCoarseBoundAccuracy;

        struct OSGSHADOW_EXPORT ViewData: public BaseClass::ViewData
        {
            osg::Matrix                      *_modellingSpaceToWorldPtr;
            float                            *_maxFarPlanePtr;
            float                            *_minLightMarginPtr;
            int                               _frameShadowCastingCameraPasses;

            ConvexPolyhedron                 _sceneReceivingShadowPolytope;
            std::vector< osg::Vec3d >        _sceneReceivingShadowPolytopePoints;

            osg::Matrixd                     _clampedProjection;

            virtual void init( ThisClass * st, osgUtil::CullVisitor * cv );

            virtual osg::BoundingBox computeShadowReceivingCoarseBounds( );

            virtual void cullShadowReceivingScene( );

            virtual void aimShadowCastingCamera(
                const osg::BoundingSphere &bounds,
                const osg::Light *light,
                const osg::Vec4 &worldLightPos,
                const osg::Vec3 &worldLightDir,
                const osg::Vec3 &worldLightUp = osg::Vec3(0,1,0) );

            virtual void aimShadowCastingCamera( const osg::Light *light,
                const osg::Vec4 &worldLightPos,
                const osg::Vec3 &worldLightDir,
                const osg::Vec3 &worldLightUp
                = osg::Vec3(0,1,0) );

            virtual void frameShadowCastingCamera
                ( const osg::Camera* cameraMain, osg::Camera* cameraShadow, int pass = 1 );

            void cutScenePolytope( const osg::Matrix & matrix,
                const osg::Matrix & inverse,
                const osg::BoundingBox &bb =
                osg::BoundingBox(-1,-1,-1,1,1,1) );

            osg::BoundingBox computeScenePolytopeBounds();
            osg::BoundingBox computeScenePolytopeBounds(const osg::Matrix& m);

            // Utility methods for adjusting projection matrices

            // Modify projection matrix so that some output subrange
            // is remapped to whole clip space (-1..1,-1..1,-1..1).
            // Bit mask can be used to limit remaping to selected bounds only.
            static void trimProjection
                ( osg::Matrixd & projection, osg::BoundingBox subrange,
                unsigned int trimMask = (1|2|4|8|16|32)
                /*1=left|2=right|4=bottom|8=top|16=near|32=far*/);

            static void clampProjection
                ( osg::Matrixd & projection, float n = 0, float f = FLT_MAX );

            static void extendProjection
                ( osg::Matrixd & projection, osg::Viewport * viewport, const osg::Vec2& margin );
        };

        META_ViewDependentShadowTechniqueData( ThisClass, ThisClass::ViewData )
};

} // namespace osgShadow

#endif
