/*****************************************************************************
 * $LastChangedDate: 2011-02-04 19:23:35 -0500 (Fri, 04 Feb 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Cloud particle system.
 *//*
 * 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_CLOUDS_CC 1
#include "base/module.hh"
#include "base/random.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs_trig.hh"
#include "math/graphics.hh"
using namespace math;
#include "glue/module.hh"
#include "glue/sim_time.hh"
using namespace glue;
#include "view/module.hh"
#include "view/view.hh"
#include "view/events.hh"
using namespace view;
#include "fx/module.hh"
#include "fx/texture.hh"
using namespace fx;
#include "world/module.hh"
using namespace world;
#include "sky/module.hh"
#include "sky/clouds.hh"
#include "sky/conf.hh"
#include "sky/sky_dome.hh"
#include "sky/weather.hh"

namespace sky {

// Area of sky populated with clouds is reduced to avoid overlapping sky dome.
INTERN const fp SKY_WIDTH_RATIO = 0.85f;

INTERN const NodeSort::Attribs CLOUD_NODE_SORT_ATTRIBS( NodeSort::Attribs::TRANSPARENT );

////////////////////////////////////////////////////////////////////////////////
/// @brief Listener to rotates clouds for every view.
///
CLASS_EVENT_LISTENER( CloudPreDrawViewListener, shptr<View>, CloudParticleSystem, mCloudParticleSystem )

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  CloudParticle  ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
CloudParticle::CloudParticle( const Sprite::Args& spriteArgs )
:   BigParticle(spriteArgs,fx::defs::LIFETIME_FOREVER)
{
    // NOP
}

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

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  Clouds (plural)  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 * For speed, timer-tick is disabled (so clouds won't move).
 *****************************************************************************/
CloudParticleSystem::CloudParticleSystem( SkyDome& skyDome, const BoxVolume& skyVolume,
                                          const uint cloudCountDivisor, const string& textureName )
:   BigParticleSystem(BigParticleSystem::Args(fx::defs::LIFETIME_FOREVER,
                                              TimerDefs::TIMER_DISABLED,  // default stateBits won't be used, since  
                                              NodeSort(CLOUD_NODE_SORT_ATTRIBS,
                                              Texture::NewTextureAsStateSet(textureName)))),
    mPreDrawViewListener(new CloudPreDrawViewListener(*this)),   // passed for specific cloud textures later
    mRollDeg(0.0f)
{
CHECK_TYPESIG(&skyDome,TYPESIG_SKY_DOME);

    // Clouds are too white-and-bright for night.
    if ( SKY_CONF.mCloudsEnabled and not GET_SIM_TIME().IfNightTime() )
    {
        MakeClouds( skyDome, skyVolume, cloudCountDivisor );
    }

    // Listener to rotate cloud sprites before rendering a view.
    // Register functors to be called before rendering this View.
    EVENT_VIEW_PRE_DRAW_ONE_VIEW.Listen( mPreDrawViewListener.PTR() );
}

CloudParticleSystem::~CloudParticleSystem()
{
    // Unregister or crash into destroyed object!
    EVENT_VIEW_PRE_DRAW_ONE_VIEW.Unlisten( mPreDrawViewListener.PTR() );
}

/*****************************************************************************
 * ctor subroutine.
 *****************************************************************************/
void
CloudParticleSystem::MakeClouds( SkyDome& skyDome, const BoxVolume& skyVolume,
                                 const uint cloudCountDivisor )
{
CHECK_TYPESIG(&skyDome,TYPESIG_SKY_DOME);

    const bool      flatWorld       = GET_WORLD().IfFlat();
    sky::Conf::CloudConf& cloudConf = flatWorld ? SKY_CONF.mCloudConfFlat
                                                : SKY_CONF.mCloudConfSphere;
    const uint      cloudCount      = cloudConf.mCloudCount / cloudCountDivisor;
    const fp        skyWidth        = ABS( skyVolume.mMax[LAT] - skyVolume.mMin[LAT] ) * SKY_WIDTH_RATIO;
    const fp        skyWidthHalf    = skyWidth * 0.5f;
    const fp        altMin          = flatWorld ? world::conv::Meters2Sim( cloudConf.mCloudMinAlt ) + skyVolume.mMin[ALT]
                                                : world::conv::Meters2Sim( cloudConf.mCloudMinAlt );
    const fp        altMax          = flatWorld ? world::conv::Meters2Sim( cloudConf.mCloudMaxAlt ) + skyVolume.mMax[ALT]
                                                : world::conv::Meters2Sim( cloudConf.mCloudMaxAlt );
    const fp        altDelta        = altMax - altMin;
    const fp        widthMin        = world::conv::Meters2Sim( cloudConf.mCloudMinWidth );
    const fp        widthDelta      = world::conv::Meters2Sim( cloudConf.mCloudMaxWidth ) - widthMin;
    RefPtr<osg::StateSet> stateSet  = GetNodeSort().GetStateSet();
    const RGBA      color           = GET_WEATHER().IfSnowOrRain() ? RGBA( 0x50, 0x50, 0x80, 0xc0 )
                                                                   : RGBA( 0xff, 0xff, 0xff, 0x80 );
    WorldVertex     pos;

ASSERT( cloudCount < 50*THOUSAND );
ASSERT( skyWidth > 0 );
ASSERT( altDelta > 0 );
ASSERT( widthDelta > 0 );

    // Loop to create cloud sprites.
    // Since some cloud args will be rejected (outside skydome),
    // the number of loops will be greater than cloudCount.
    uint curCloudCount, i;  // curCloudCount counts each actual cloud
    for ( curCloudCount = i = 0;
          (curCloudCount < cloudCount) and (i < cloudCount * 4);
          ++i )
    {
        if ( flatWorld )
        {
            pos[XX] = Random::random_f(skyWidth) - skyWidthHalf;
            pos[YY] = Random::random_f(skyWidth) - skyWidthHalf;
            pos[ZZ] = altMin + Random::random_f(altDelta);
        }
        else
        {
            SphereVertex sv( Random::random_f(180.0f) - 90.0f,
                             Random::random_f(360.0f) - 180.0f,
                             Meter(altMin + Random::random_f(altDelta)) );
            pos = world::conv::SphereVertex2WorldVertex( sv );
        }

        // Skip if cloud would be outside sky-dome.
        const fp width = widthMin + Random::random_f( widthDelta );
        if ( skyDome.IfInside( pos, width * 0.5f ) )
        {
            // Create a cloud sprite.
            ++curCloudCount;

            const Sprite::Args spriteArgs( pos,
                                           width,
                                           stateSet,
                                           color );

            AttachParticle( new CloudParticle( spriteArgs ) );
        }
    }
}

/*****************************************************************************
 * Rotate clouds according to view.
 * As an illusion, clouds sprites are counter-rolled as player rolls.
 *****************************************************************************/
void
CloudPreDrawViewListener::operator()( shptr<View> view )
{
    // Reflect.
    mCloudParticleSystem.PreDraw( view );
}

void
CloudParticleSystem::PreDraw( shptr<View> view )
{
    // Every View tracks its "roll degree" when its view matrix is recomputed.
    // Rotating all the Sprites is very slow, so bypass if change in roll degree is nearly zero.
    const Degree rollDeg = view->GetRollDegree();
    if ( ABS( rollDeg - mRollDeg ) > 0.1f )
    {
        mRollDeg = rollDeg;  // current degree of roll of all cloud sprites
        const Radian rollRad = Deg2Rad( rollDeg );

        // Rotate (or not if the roll angle is zero) all the cloud sprites.
        for ( BigParticles::iterator iter = mBigParticles.begin();
              iter != mBigParticles.end();
              ++iter )
        {
            // As an illusion, clouds sprites are counter-rolled as player rolls.
            (*iter)->RotateRoll( rollRad, Sprite::ROTATE_ABS );  // Particle->RotateRoll()
        }
    }
}

} // namespace sky
