/*****************************************************************************
 * $LastChangedDate: 2010-04-04 19:32:06 -0400 (Sun, 04 Apr 2010) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Graph containing nodes that cast/receive shadows.
 * @remarks - Shadows are disabled per-node by SceneGraph::AttachNode(),
 *            or entirely disabled by if SceneGraph internally substitutes
 *            a BaseSceneGraph object for a ShadowSceneGraph object.
 *          - IMPORTANT: ShadowSceneGraph must not be attached at all
 *            if shadows are disabled (because of an obsolete OpenGL implementation),
 *            else OSG 2.9 will fail to render anything (white window).
 *          - Problem of distorted shadows:
 *            If the configuration sets ObjectScale != 1.0 then distorted shadows
 *            will appear when the chase-view is nearly at same altitude as its target.
 *            What causes this problem is unknown.
 *//*
 * 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 GRAPH_SHADOW_SCENE_GRAPH_CC 1
#include "base/module.hh"
using namespace base;
#include "gfx/module.hh"
#include "gfx/osg.hh"
using namespace gfx;
#include "view/module.hh"
#include "view/node_masks.hh"
using namespace view;
#include "shader/module.hh"
#include "shader/shader_funcs.hh"
using namespace shader;
#include "fx/module.hh"
#include "fx/texture.hh"
using namespace fx;
#include "graph/module.hh"
#include "graph/osg.hh"
#include "graph/conf.hh"
#include "graph/base_scene_graph.hh"
#include "graph/shadow_scene_graph.hh"

#include <osg/Light>
#include <osg/LightSource>
#include <osgShadow/ShadowedScene>
#include <osgShadow/MinimalShadowMap>

namespace graph {

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////  ShadowSceneGraph  ///////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor.
 * osgShadow::ShadowedScene is a group node that is the root of the shadowed sub-graph.
 * osgShadow::ShadowMap is an algorithm ("shadow technique").
 *****************************************************************************/
ShadowSceneGraph::ShadowSceneGraph( void )
:   BaseSceneGraph(),
    mShadowedScene(new osgShadow::ShadowedScene),
    mShadowRootNode(new GroupNode),
    mShadowMap(new osgShadow::MinimalShadowMap)
#if LIMIT_SHADOW_CASTERS
   ,mShadowCasterNode(NULL)
#endif
{
    //--------------------------------------------------------------------------
    // osgShadow::ShadowedScene -> ShadowSceneGraph::mShadowRootNode -> shadowCasters
    //                                                                  shadowReceivers
    // The reason for this arrangement is to override the stock OSG shaders
    // attached to the osgShadow::ShadowedScene node.
    // mShadowRootNode will be the effective root node.
    // mShadowRootNode is operated on by the BaseSceneGraph methods.
    //--------------------------------------------------------------------------
    BaseSceneGraph::SetRootNode( mShadowRootNode.get() );
    mShadowedScene->addChild( mShadowRootNode.get() );

    // Initialize ShadowedScene (group node).
    mShadowedScene->setShadowTechnique( mShadowMap.get() );
    mShadowedScene->setReceivesShadowTraversalMask( view::defs::NODE_MASK_SHADOW_RECEIVER );
    mShadowedScene->setCastsShadowTraversalMask( view::defs::NODE_MASK_SHADOW_CASTER );

    // Initialize ShadowMap (algorithm/technique).
    // This program is using its own shaders.
    mShadowMap->setShadowTextureUnit( fx::defs::TEXTURE_UNIT_SHADOW );
    mShadowMap->setShadowVertexShader( NULL );
    mShadowMap->setShadowFragmentShader( NULL );
    mShadowMap->setMainVertexShader( NULL );
    mShadowMap->setMainFragmentShader( NULL );

    // Initialize an OpenGL hw light.
    // Though rendering is done by shaders,
    // the shaders use standard light uniforms as input.
    RefPtr<osg::LightSource> lightSource = new osg::LightSource;
    RefPtr<osg::Light> light = new osg::Light;
    light->setLightNum(  GRAPH_CONF.mShadowLightNum );
    light->setPosition(  GRAPH_CONF.mShadowLightPos );
    light->setDirection( GRAPH_CONF.mShadowLightDir );
    light->setAmbient(   GRAPH_CONF.mShadowLightColor );
    light->setDiffuse(   GRAPH_CONF.mShadowLightColor );
    light->setSpecular(  GRAPH_CONF.mShadowLightColor );
    lightSource->setReferenceFrame( osg::LightSource::RELATIVE_RF );
    lightSource->setLight( light.get() );
    mShadowedScene->addChild( lightSource.get() );
}

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

/*****************************************************************************
 * @return The absolute root node of ShadowSceneGraph.
 *****************************************************************************/
RefPtr<GroupNode>
ShadowSceneGraph::GetRootNode( void )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    return mShadowedScene.get();
}

/*****************************************************************************
 * Attach node that will either cast or receive a shadow.
 *****************************************************************************/
void
ShadowSceneGraph::AttachNode( RefPtr<Node> node, const NodeSort& nodeSort )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);
CHECK_TYPESIG(&nodeSort,TYPESIG_NODE_SORT);

    const bool shadowCaster = (nodeSort.GetAttribs().mVal) & (NodeSort::Attribs::SHADOW_CASTER);

    // Compute node mask that determines whether to cast or receive a shadow.
    // Assign uniform for shadow mode appropriately.
    uint mask = node->getNodeMask();
#if LIMIT_SHADOW_CASTERS
    // Limit to having one shadow caster else the shadow-map algorithm will malfunction or be slow.
    if ( shadowCaster )
    {
        // Unless passing back same caster.
        if ( (mShadowCasterNode != NULL) and (mShadowCasterNode.get() != node.get()) )
            DisableShadows( mShadowCasterNode );    // clear shadow node-mask in predecessor
        mShadowCasterNode = node;                   // replace with successor
    }
#endif
    mask = mask & ~(view::defs::NODE_MASK_SHADOW_CASTER | NodeSort::Attribs::SHADOW_RECEIVER);
    if ( shadowCaster )
    {
        mask |= view::defs::NODE_MASK_SHADOW_CASTER;
        SetShaderUniform<int>( SHADER_UNIFORM_NAME_SHADOW_MODE, graph::defs::SHADOW_MODE_DISABLED, *node );
    }
    else
    {
        mask |= view::defs::NODE_MASK_SHADOW_RECEIVER;
        SetShaderUniform<int>( SHADER_UNIFORM_NAME_SHADOW_MODE, GRAPH_CONF.mShadowMode, *node );
    }
    node->setNodeMask( mask );

    // Node is now prepared for shadows.
    // Attach node to scene graph.
    BaseSceneGraph::AttachNode( node.get(), nodeSort );
}

/*****************************************************************************
 * Detach node.
 *****************************************************************************/
void
ShadowSceneGraph::DetachNode( RefPtr<Node> node )
{
CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    BaseSceneGraph::DetachNode( node );

#if LIMIT_SHADOW_CASTERS
    // Let the sole shadow caster be replaced.
    if ( node.get() == mShadowCasterNode.get() )
        mShadowCasterNode = NULL;
#endif
}

/*****************************************************************************
 * Disable shadowing on a node (presumably a part of a model that is shadowed).
 *****************************************************************************/
void // CLASS_METHOD
ShadowSceneGraph::DisableShadows( RefPtr<Node> node )
{
//CHECK_TYPESIG(this,TYPESIG_SCENE_GRAPH);

    // Change node mask.
    uint mask = node->getNodeMask();
    mask = mask & ~(view::defs::NODE_MASK_SHADOW_CASTER | NodeSort::Attribs::SHADOW_RECEIVER);
    node->setNodeMask( mask );

    // Change uniform.
    SetShaderUniform<int>( SHADER_UNIFORM_NAME_SHADOW_MODE, graph::defs::SHADOW_MODE_DISABLED, *node );
}

} // namespace graph
