/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Light implemented by shaders.
 *//*
 * 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_LIGHT_SHADER_CC 1
#include "base/module.hh"
#include "base/stream.hh"
using namespace base;
#include "graph/module.hh"
#include "graph/scene_graph.hh"
using namespace graph;
#include "view/module.hh"
#include "view/view.hh"
#include "view/events.hh"
using namespace view;
#include "shader/module.hh"
#include "shader/conf.hh"
#include "shader/shader_funcs.hh"
#include "shader/light_shader.hh"
#if COMPILE_HW_LIGHT
#include "shader/light_hw.hh"
#endif

namespace shader {

const fp BASE_AMBIENT_FACTOR = 0.35f;  // reduce in order to limit its affect on brightness

// Currently, the shaders only support one light.
// But this could be extended in the future.
#define CHECK_LIGHT_NUM(NUM) ASSERT( (NUM) == 0 )

////////////////////////////////////////////////////////////////////////////////
/// @class shader::ShaderLight_PreDrawViewListener
/// @brief Transforms the light's position from world to eye space.
///
/// Before drawing every View, this callback is invoked to transform
/// the light position from world space to eye space.
/// Transformation must be done using the root-level modelview matrix
/// (obtained from the current View object being rendered).
///
CLASS_EVENT_LISTENER( ShaderLight_PreDrawViewListener, shptr<View>, ShaderLight, mShaderLight )

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////  ShaderLight  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

ShaderLight* ShaderLight::msInstance;

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
ShaderLight::ShaderLight( void )
:   mPreDrawViewListener(new ShaderLight_PreDrawViewListener(*this)),
    mBrightness(1.0f),
    mLights()
{
    // Register functors to be called before rendering this View.
    EVENT_VIEW_PRE_DRAW_ONE_VIEW.Listen( mPreDrawViewListener.PTR() );

    // Create only light (shaders don't yet support multiple lights).
    mLights.push_back( Light() );
}

ShaderLight::~ShaderLight()
{
    // Unregister.
    EVENT_VIEW_PRE_DRAW_ONE_VIEW.Unlisten( mPreDrawViewListener.PTR() );
}

/*****************************************************************************
 * Set type of light (positional, directional).
 *****************************************************************************/
void
ShaderLight::SetLightType( const uint lightNum, const shader::defs::eLightType type )
{
CHECK_LIGHT_NUM(lightNum);
ASSERT( type == shader::defs::LIGHT_DIRECTIONAL );  // shaders are hardwired to be directional
}

/*****************************************************************************
 * Set position of light (in world space).
 *****************************************************************************/
void
ShaderLight::SetLightPosition( const uint lightNum, const WorldVertex& pos )
{
CHECK_LIGHT_NUM(lightNum);

    // Effect delayed to listener of PreDrawView event
    // which transforms light position from world space to eye space.
    mLights[lightNum].mPos = pos;
}

/*****************************************************************************
 * @return Position of light (in world space).
 *****************************************************************************/
WorldVertex
ShaderLight::GetLightPosition( const uint lightNum )
{
CHECK_LIGHT_NUM(lightNum);

    return mLights[lightNum].mPos;
}

/*****************************************************************************
 * Set the color of a light source.
 * Implementation:
 * The shaders don't compute light color in terms of ambient/diffuse/specular.
 *****************************************************************************/
void
ShaderLight::SetLightColor( const uint lightNum, const RGBA color )
{
CHECK_LIGHT_NUM(lightNum);

#if DEBUG
    // Sue me for false advertisement.
    CERROR << "ShaderLight::SetLightColor() unimplemented.  SetBaseAmbient() is an alternative." << std::endl;
    ASSERT_UNIMPLEMENTED();
#endif
}

/*****************************************************************************
 * Set brightness of all lights.
 * @param   brightness
 * @param   setting
 *          BRIGHTNESS_ABSOLUTE sets value directly.
 *          BRIGHTNESS_RELATIVE accounts for -brightness arg.
 *****************************************************************************/
void
ShaderLight::SetLightBrightness( const fp brightness, const shader::defs::eBrightness setting )
{
    if ( setting == shader::defs::BRIGHTNESS_ABSOLUTE )
        mBrightness = brightness;
    else  // BRIGHTNESS_RELATIVE
        mBrightness = brightness * SHADER_CONF.mBrightness;

    SetShaderUniform<fp>( SHADER_UNIFORM_NAME_BRIGHTNESS, brightness, *GET_SCENE_GRAPH().GetRootNode() );
}

/*****************************************************************************
 * @return Brightness of all lights.
 *****************************************************************************/
fp
ShaderLight::GetLightBrightness( void )
{
    return mBrightness;
}

/*****************************************************************************
 * Set base ambient of all lights.
 * Implementation:
 * The shaders don't compute light color in terms of ambient/diffuse/specular.
 * Instead, uni_baseAmbient is added to the fragment in a late stage
 * (before brightness is applied).
 *****************************************************************************/
void
ShaderLight::SetLightBaseAmbient( const RGBA color )
{
    // Shader:
    osg::Vec4f uni_baseAmbient = RGBA2VEC4F(color);
    const fp f = BASE_AMBIENT_FACTOR;  // reduce in order to limit its affect on brightness
    uni_baseAmbient[RR] *= f;
    uni_baseAmbient[GG] *= f;
    uni_baseAmbient[BB] *= f;
    uni_baseAmbient[AA] = 0.0f;
    SetShaderUniform<osg::Vec4f>( SHADER_UNIFORM_NAME_BASE_AMBIENT, uni_baseAmbient, *GET_SCENE_GRAPH().GetRootNode() );
}

/*****************************************************************************
 * Set fog density.
 *****************************************************************************/
void
ShaderLight::SetFogDensity( const fpx fogDensity_, const osg::Fog::Mode fogMode )
{
    const fpx fogDensity = SHADER_CONF.mEnableFog ? fogDensity_ : 0.0;
    SetShaderUniform<fp>( SHADER_UNIFORM_NAME_FOG_DENSITY, fogDensity, *GET_SCENE_GRAPH().GetRootNode() );
}

/*****************************************************************************
 * Set fog color.
 *****************************************************************************/
void
ShaderLight::SetFogColor( const RGBA fogColor )
{
    SetShaderUniform<osg::Vec4f>( SHADER_UNIFORM_NAME_FOG_COLOR, RGBA2VEC4F(fogColor), *GET_SCENE_GRAPH().GetRootNode() );  
}

/*****************************************************************************
 * Set minimum fog (unit range).
 * This ensures that near objects have some fog.
 *****************************************************************************/
void
ShaderLight::SetFogMin( const fp fogMin )
{
    SetShaderUniform<fp>( SHADER_UNIFORM_NAME_FOG_MIN,
                          SHADER_CONF.mEnableFog ? fogMin : 0.0f,
                          *GET_SCENE_GRAPH().GetRootNode() );
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////  ShaderLight_PreDrawViewListener  ///////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Transforms the light's position from world to eye space.
 *****************************************************************************/
void
ShaderLight_PreDrawViewListener::operator()( shptr<View> view )
{
    // Reflect.
    mShaderLight.PreDraw( view );
}

void
ShaderLight::PreDraw( shptr<View> view )
{
CHECK_TYPESIG(view,TYPESIG_VIEW);

    const uint lightNum = 0;

    // Transform light position: world --> eye
    const EyeVertex ev = view->TransformWorld2Eye( mLights[lightNum].mPos );

    SetShaderUniform<osg::Vec4f>( SHADER_UNIFORM_NAME_LIGHT0_EC_POSITION,
                                  // w==0 directional light
                                  osg::Vec4( ev[XX], ev[YY], ev[ZZ], 0.0f ),
                                  *GET_SCENE_GRAPH().GetRootNode() );
}

} // namespace shader
