/*****************************************************************************
 * $LastChangedDate: 2011-08-20 12:07:38 -0400 (Sat, 20 Aug 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Algorithms to paint SkyDome.
 * @verbatim
 *
 * Implementation status:
 * This code is experimental (but stable).
 * Limitations are that its response to time-of-day is discrete instead of continuous,
 * and the sky dome it paints is suited to a FLAT planet only.
 *
 * @endverbatim
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define SKY_SKY_DOME_PAINTER_CC 1
#include <utility>
#include "base/module.hh"
#include "base/random.hh"
using namespace base;
#include "math/module.hh"
#include "math/types_trig.hh"
#include "math/funcs_trig.hh"
using namespace math;
#include "gfx/module.hh"
#include "gfx/rgba.hh"
#include "gfx/rgba_funcs.hh"
#include "gfx/rgb_colors.hh"
#include "gfx/osg.hh"
using namespace gfx;
#include "glue/module.hh"
#include "glue/conf.hh"
#include "glue/sim_time.hh"
using namespace glue;
#include "sky/module.hh"
#include "sky/conf.hh"
#include "sky/defs.hh"
#include "sky/sky_dome.hh"
#include "sky/sky_dome_painter.hh"
#include "sky/sun.hh"

namespace sky {

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  SkyDomePainter  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Main public method.
 * Paint SkyDome according to time-of-day.
 *****************************************************************************/
void  // CLASS_METHOD
SkyDomePainter::Paint( SkyDome& skyDome, const fp hour )
{
    // Begin by painting all patches of sky dome,
    // then paint components of sky.
    PaintDome( skyDome, hour );
    if ( SimTime::IfDayTime( hour ) )
        PaintSunAura( skyDome, hour );
    PaintHorizon( skyDome, hour );
}

/*****************************************************************************
 * Private subroutine.
 * Paint all patches with sky color.
 *****************************************************************************/
void
SkyDomePainter::PaintDome( SkyDome& skyDome, const fp hour )
{
    // Start by repainting each Patch with the general sky color.
    // Uni-color.
    const RGBA skyColor = ComputeSkyColor( hour );
    skyDome.SetColorAll( skyColor );
    if ( not GLUE_CONF.mFastMode )
    {
        // For variation, paint the patches with slightly randomized colors.
        // Don't vary the color near the north pole since
        // it breaks the dome's illusion.
        const osg::Vec4 skyColor4f = RGBA2VEC4F(skyColor);
        const Degree degInc( 1.0f );
        for ( Degree lat = 50.0f; lat <= 95.f;  (lat = lat + degInc) )
        for ( Degree lon =  0.0f; lon < 360.0f; (lon = lon + degInc) )
        {
            const fp r = Random::random_f( 0.025f );
            RGBA color( skyColor4f[RR] + r,
                        skyColor4f[GG] + r,
                        skyColor4f[BB] + r,
                        skyColor4f[AA] );
            skyDome.SetColor( color, lat, lon );
        }
    }
}

/*****************************************************************************
 * Private subroutine.
 * Paint the horizon.
 *****************************************************************************/
void  // CLASS_METHOD
SkyDomePainter::PaintHorizon( SkyDome& skyDome, const fp hour )
{
#if COMPILE_PALOMINO != 4

    const Degree degInc = 360.0f / skyDome.GetSliceCount();
    Degree       lat( 90.0f );
    Degree       latInc( 3.5f );

    if ( SimTime::IfDayTime( hour ) )
    {
        // Day time.
        RGBA color( 0.75f, 0.9f, 1.0f, 1.0f );  // white
        for ( Degree lon = 0.0f; lon < 360.0f; (lon = lon + degInc) )
            skyDome.SetColor( color, lat, lon );
    }
    else if ( SimTime::IfMorningTime( hour ) or SimTime::IfEveningTime( hour ) )
    {
        // Morning or evening.
        RGBA color( 0.8f, 0.8f, 0.3f, 1.0f );
        for ( Degree lon = 0.0f; lon < 360.0f; (lon = lon + degInc) )
            skyDome.SetColor( color, lat, lon );

        lat = lat - latInc;
        color.Set( 0.8f, 0.5f, 0.5f, 1.0f );
        for ( Degree lon = 0.0f; lon < 360.0f; (lon = lon + degInc) )
            skyDome.SetColor( color, lat, lon );

        lat = lat - latInc;
        color.Set( 0.1f, 0.2f, 0.5f, 1.0f );
        for ( Degree lon = 0.0f; lon < 360.0f; (lon = lon + degInc) )
            skyDome.SetColor( color, lat, lon );
    }
    else  // night
    {
        // NOP.
    }

#endif // COMPILE_PALOMINO != 4
}

/*****************************************************************************
 * Private subroutine.
 * Paint the sun's aura.
 *****************************************************************************/
void  // CLASS_METHOD
SkyDomePainter::PaintSunAura( SkyDome& skyDome, const fp hour )
{
    const std::pair<Degree,Degree> sunLatLon = GET_SKY().GetSun().GetLatLon();
    const fp lat0 = sunLatLon.first;
    const fp lon0 = sunLatLon.second;

    const fp rInc = 0.3f;
    const fp rLimit = 32.0f;
    const fp radInc = Deg2Rad( 0.4f );
    RGBA sunColor( 1.0f, 1.0f, 0.75f, 1.0f );
    for ( fp r = 0.01f; r < rLimit; r += rInc )
    {
        for ( fp rad = 0.0f; rad < math::RADIAN_360; rad += radInc )
        {
            const std::pair<fp,fp> sin_cos = SinCos<fp>( rad );
            const fp lat = sin_cos.first  * r + lat0;
            const fp lon = sin_cos.second * r + lon0;
            const fp lat2 = ModuloAbs<fp>( lat, 180.0f );
            if ( lat2 >= 95.0f )
                continue;
            const fp lon2 = ModuloAbs<fp>( lon, 360.0f );
            const fp f = r / rLimit;
            RGBA color = InterpolateRGBA( sunColor,
                                          RGBA(RGB_SKY_BLUE,1.0f),
                                          f );
            skyDome.SetColor( color, lat2, lon2 );
        }
    }
}

/*****************************************************************************
 * Private subroutine.
 * Compute sky color according to time-of-day.
 *****************************************************************************/
RGBA  // CLASS_METHOD
SkyDomePainter::ComputeSkyColor( const fp hour )
{
    if ( SimTime::IfDayTime( hour ) )
    {
        return SKY_CONF.mSkyColor;
    }
    else if ( SimTime::IfMorningTime( hour ) or SimTime::IfEveningTime( hour ) )
    {
        return sky::defs::SKY_COLOR_TWILIGHT;
    }
    else  // night
    {
        return sky::defs::SKY_COLOR_NIGHT;
    }
}

} // namespace sky
