/*****************************************************************************
 * $LastChangedDate: 2010-02-03 19:48:51 -0500 (Wed, 03 Feb 2010) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Modify a 3D model for rendering by shaders.
 * @remarks A visitor is run to attach shader uniforms
 *          on appropriate osg::Drawable nodes to tell
 *          the shaders how to draw them properly.
 *          This step is necessary for legacy 3D models
 *          that weren't originally designed for shaders.
 *          Loosely based on ShaderGen (OSG 2.9) by Maciej Krol.
 *
 *          ShaderModel::ModifyModelForShaders() is the interface.
 *          ShaderVisitor is the private implementation.
 *
 *          Optimization faux-pas:
 *          This visitor traversal does seem to set uniforms redundantly,
 *          but the CULL/DRAW TRAVERSAL will follow a different path than this one.
 *
 *          GLSL does not have bitwise operators (so individual uniform bools).
 *//*
 * LEGAL:   COPYRIGHT (C) 2009 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define SHADER_SHADER_MODEL_CC 1
#include "base/module.hh"
#include "base/stack.hh"
using namespace base;
#include "gfx/module.hh"
#include "gfx/osg.hh"
using namespace gfx;
#include "shader/module.hh"
#include "shader/conf.hh"
#include "shader/shader_model.hh"
#include "shader/shader_funcs.hh"
#include "shader/shader_name.hh"

#include <osgUtil/Export>
#include <osg/NodeVisitor>
#include <osg/State>
#include <osg/Uniform>
#include <osg/Program>

namespace shader {

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////  ShaderVisitor  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief ShaderVisitor (private implementation of ShaderModel).
///
class ShaderVisitor : public osg::NodeVisitor
{
public:
                ShaderVisitor( osg::Node& node );
                ~ShaderVisitor();
    void        apply( osg::Node& node );
    void        apply( osg::Geode& geode );
    void        update( osg::Drawable* drawable );

private:
    Stack<RefPtr<osg::StateSet> >   mStateStack;    ///< stack of graphical states
};

/*****************************************************************************
 * ctor.
 *****************************************************************************/
ShaderVisitor::ShaderVisitor( osg::Node& node )
:   NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
    mStateStack()
{
    ResetShaderUniformsForModel( node );
}

ShaderVisitor::~ShaderVisitor()
{
    // NOP
}

/*****************************************************************************
 * Apply method (Node).
 *****************************************************************************/
void
ShaderVisitor::apply( osg::Node& node )
{
    // Track state while traversing.
    RefPtr<osg::StateSet> stateSet = node.getStateSet();
    if ( stateSet != NULL )
    {
        mStateStack.Push( stateSet );
        traverse( node );
        mStateStack.Pop();
    }
    else
    {
        traverse( node );
    }
}

/*****************************************************************************
 * Apply method (Geode).
 *****************************************************************************/
void
ShaderVisitor::apply( osg::Geode& geode )
{
    RefPtr<osg::StateSet> stateSet = geode.getStateSet();

    if ( stateSet != NULL )
        mStateStack.Push( stateSet );

    const uint count = geode.getNumDrawables();
    for ( uint i = 0; i < count; ++i )
    {
        RefPtr<osg::Drawable> drawable = geode.getDrawable( i );
        RefPtr<osg::StateSet> drawableStateSet = drawable->getStateSet();
        if ( drawableStateSet != NULL )
        {
            mStateStack.Push( drawableStateSet );
            update( drawable.get() );
            mStateStack.Pop();
        }
        else
        {
            update( drawable.get() );
        }
    }

    if ( stateSet != NULL )
        mStateStack.Pop();
}

/*****************************************************************************
 * Update method (Drawable).
 *****************************************************************************/
void
ShaderVisitor::update( osg::Drawable* drawable )
{
    // Skip nodes without statesets.
    // Update only geometry due to compatibility issues with user defined drawables.
    if ( (not mStateStack.IfEmpty()) and (drawable->asGeometry() != NULL) )
    {
        RefPtr<osg::StateSet> stateSet = mStateStack.Top();

        SetShaderUniform( SHADER_UNIFORM_NAME_SHADER_MODE_TEXTURE,  // 0 : texture unit 0
                          ((stateSet->getTextureMode(0,GL_TEXTURE_2D) & osg::StateAttribute::ON) != 0),
                          stateSet );
    }
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////////  interface function  ///////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Interface.
 * Modify 3D model for rendering by shaders.
 *****************************************************************************/
void
ShaderModel::ModifyModelForShaders( RefPtr<osg::Node> rootNode )
{
    if ( not SHADER_CONF.mEnableShaders )
        return;

    ShaderVisitor visitor( *rootNode );
    visitor.apply( *rootNode );
}

} // namespace shader
