/*****************************************************************************
 * $LastChangedDate: 2010-03-26 15:04:03 -0400 (Fri, 26 Mar 2010) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Hardware light for 3D models incompatible with 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).
 *****************************************************************************/

#if COMPILE_HW_LIGHT

#define SHADER_LIGHT_HW_CC 1
#include "base/module.hh"
using namespace base;
#include "math/module.hh"
#include "math/vertex.hh"
using namespace math;
#include "gfx/module.hh"
#include "gfx/rgba.hh"
using namespace base;
#include "view/module.hh"
#include "view/view.hh"
#include "view/events.hh"
using namespace view;
#include "shader/module.hh"
#include "shader/defs.hh"
#include "shader/conf.hh"
#include "shader/light_hw.hh"

#include <osg/Light>
#include <osg/LightSource>
#include <osg/LightModel>
#include <osg/Fog>

namespace shader {

// The hw light has a limited-use.
// Silent NOP if light num isn't 0 (by convention, main light is #0)
// in case a light number of shader light reaches HwLight.
// Also, HwLight has only one osg::LightGroup which
// is limited to having one osg::Light.
#define CHECK_LIGHT_NUM(NUM) {{ if ( (NUM) != 0 ) return; }}

////////////////////////////////////////////////////////////////////////////////
/// @class shader::HwLight_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( HwLight_PreDrawViewListener, shptr<View>, HwLight, mHwLight )

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////  HwLight  ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

HwLight* HwLight::msInstance;

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
HwLight::HwLight( void )
:   mPreDrawViewListener(new HwLight_PreDrawViewListener(*this)),
    mHwLight(new osg::Light),
    mHwFog(new osg::Fog),
    mLightType(shader::defs::LIGHT_DIRECTIONAL),
    mLightPosition(0,0,0),
    mLightAmbient(mHwLight->getAmbient()),  // -- ORDER-DEPENDENT --
    mLightDiffuse(mHwLight->getDiffuse()),
    mLightSpecular(mHwLight->getSpecular()),
    mLightBrightness(1.0f),
    mFogColor(0,0,0,0)
{
    // Register functors to be called before rendering this View.
    EVENT_VIEW_PRE_DRAW_ONE_VIEW.Listen( mPreDrawViewListener.PTR() );

    // Light:
    mHwLight->setLightNum( HW_LIGHT_NUM );

    // Fog:
    SetFogDensity( SHADER_CONF.mFogDensity, SHADER_CONF.mFogMode );
    SetFogColor( SHADER_CONF.mFogColor );
    SetFogMin( SHADER_CONF.mFogMin );
}

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

/*****************************************************************************
 * Interface.
 * Create a new group node with hw light attached to it.
 * Every osg::LightSource (group node) shares the same osg::Light and osg::Fog.
 *****************************************************************************/
RefPtr<osg::LightSource>
HwLight::MakeHwLightGroup( void )
{
    // Every osg::LightSource (group node) shares the same osg::Light (mHwLight) and osg::Fog.
    RefPtr<osg::LightSource> lightGroup = new osg::LightSource;
    RefPtr<osg::StateSet> stateSet = lightGroup->getOrCreateStateSet();
    lightGroup->setLight( mHwLight.get() );

    // This class itself will transform light position.
    // Set OpenGL absolute reference-frame.
    lightGroup->setReferenceFrame( osg::LightSource::ABSOLUTE_RF );

    // Override OpenGL's base ambient light.
    RefPtr<osg::LightModel> lightModel = new osg::LightModel;
    lightModel->setAmbientIntensity( osg::Vec4(0,0,0,0) );
    lightGroup->getOrCreateStateSet()->setAttribute( lightModel.get() );

    // Attach fog.
    if ( SHADER_CONF.mEnableFog )
    {
        stateSet->setAttributeAndModes( mHwFog.get() );
        stateSet->setMode( GL_FOG, osg::StateAttribute::ON );
    }

    return lightGroup.get();
}

/*****************************************************************************
 * Set type of light (positional, directional).
 *****************************************************************************/
void
HwLight::SetLightType( const uint lightNum, const shader::defs::eLightType type )
{
CHECK_LIGHT_NUM(lightNum);

    mLightType = type;

    switch ( type )
    {
        case shader::defs::LIGHT_POSITIONAL:
        case shader::defs::LIGHT_DIRECTIONAL:
        {
            // To update w coord of light position.
            // w coord determines whether directional or positional.
            SetLightPosition( lightNum, GetLightPosition(lightNum) );
        }
        break;

        default:
        case shader::defs::LIGHT_DISABLED:
        case shader::defs::LIGHT_SPOTLIGHT:
        {
            ASSERT_UNIMPLEMENTED();
        }
        break;
    }
}

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

    mLightPosition = position;
    const fp w = (mLightType == shader::defs::LIGHT_DIRECTIONAL) ? 0.0f : 1.0f;  // subtle OpenGL arg
    mHwLight->setPosition( osg::Vec4( position[XX], position[YY], position[ZZ], w ) );
}

/*****************************************************************************
 * @return Position of light (in world space).
 *****************************************************************************/
WorldVertex
HwLight::GetLightPosition( const uint lightNum )
{
    if ( EX( lightNum == 0 ) )
        return mLightPosition;
    else
        return WorldVertex(0,0,0);  // N/A and not a serious error
}

/*****************************************************************************
 * Set colors of a light.
 *****************************************************************************/
void
HwLight::SetLightColor( const uint lightNum, const RGBA color )
{
CHECK_LIGHT_NUM(lightNum);

    const osg::Vec4f color4f = RGBA2VEC4F( color );

    mLightAmbient = mLightDiffuse = mLightSpecular = color4f;  // save

    mHwLight->setAmbient( color4f );
    mHwLight->setDiffuse( color4f );
    mHwLight->setSpecular( color4f );
}

/*****************************************************************************
 * Set brightness of all lights.
 * Brightness is changed by changing the color of hw light and hw fog.
 *****************************************************************************/
void
HwLight::SetLightBrightness( const fp brightness, const shader::defs::eBrightness )
{
#define BRIGHTNESS_MUL_RGB(V4) osg::Vec4( (V4)[RR]*brightness, \
                                          (V4)[GG]*brightness, \
                                          (V4)[BB]*brightness, \
                                          (V4)[AA] )

    mLightBrightness = brightness;  // save

    // Directly (bypassing saving to vars).
    mHwLight->setAmbient(  BRIGHTNESS_MUL_RGB(mLightAmbient) );
    mHwLight->setDiffuse(  BRIGHTNESS_MUL_RGB(mLightDiffuse) );
    mHwLight->setSpecular( BRIGHTNESS_MUL_RGB(mLightSpecular) );
    mHwFog->setColor(      BRIGHTNESS_MUL_RGB(mFogColor) );
}

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

/*****************************************************************************
 * Set base ambient of all lights.
 *****************************************************************************/
void
HwLight::SetLightBaseAmbient( const RGBA color )
{
    // NOP (intentionally leave OpenGL base ambient unchanged at 0,0,0,0).
}

/*****************************************************************************
 * Set fog density.
 *****************************************************************************/
void
HwLight::SetFogDensity( const fpx fogDensity, const osg::Fog::Mode fogMode )
{
    mHwFog->setDensity( fogDensity );
    mHwFog->setMode( fogMode );
}

/*****************************************************************************
 * Set fog color.
 *****************************************************************************/
void
HwLight::SetFogColor( const RGBA fogColor )
{
    mFogColor = RGBA2VEC4F( fogColor );  // save
    mHwFog->setColor( RGBA2VEC4F( mFogColor ) );
}

/*****************************************************************************
 * Set minimum fog (unit range).
 * This ensures that near objects have some fog.
 *****************************************************************************/
void
HwLight::SetFogMin( const fp fogMin )
{
    // NOP (shader-specific).
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////  HwLight_PreDrawViewListener  ////////////////////////
////////////////////////////////////////////////////////////////////////////////

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

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

    // mLightPosition stored light position as WorldVertex arg.
    // Transform light position: world --> eye
    const EyeVertex ev = view->TransformWorld2Eye( mLightPosition );

    // Directly set light position (in eye space) thru OSG object.
    const fp w = (mLightType == shader::defs::LIGHT_DIRECTIONAL) ? 0.0f : 1.0f;  // subtle OpenGL arg
    mHwLight->setPosition( osg::Vec4( ev[XX], ev[YY], ev[ZZ], w ) );
}

} // namespace shader

#endif // COMPILE_HW_LIGHT
