/*****************************************************************************
 * $LastChangedDate: 2011-02-04 19:23:35 -0500 (Fri, 04 Feb 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Explosion particle-system (private implementation of FX class).
 *//*
 * 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 FX_PARTSYS_EXPLOSION_CC 1
#include "base/module.hh"
#include "base/random.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs_trig.hh"
#include "math/matrix.hh"
#include "math/matrix_funcs.hh"
using namespace math;
#include "graph/module.hh"
using namespace graph;
#include "object/module.hh"
using namespace object;
#include "fx/module.hh"
#include "fx/defs.hh"
#include "fx/texture.hh"
#include "fx/partsys_explosion.hh"

namespace fx {

INTERN const Milliseconds EXPLOSION_PARTICLE_LIFETIME( 8*1000 );
INTERN const uint         EXPLOSION_PARTICLE_LIMIT = 100;
INTERN const uint         EXPLOSION_PARTICLES_PER_SECOND( 40 );
INTERN const int          EXPLOSION_SUB_TICK_MASK = 1;  // sub-tick resolution
INTERN const fp           EXPLOSION_PARTICLE_SPIN_DEG = 12.0f;
INTERN const fp           EXPLOSION_PARTICLE_SPIN_DEG_HALF = 0.5f * EXPLOSION_PARTICLE_SPIN_DEG;

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  ExplosionParticle  ///////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
ExplosionParticle::ExplosionParticle( const Sprite::Args& spriteArgs,
                                      const fp explosionRadius )
:   BigParticle(spriteArgs,EXPLOSION_PARTICLE_LIFETIME),
    mExplosionRadius(explosionRadius),
    mStep(0,0,0),
    mFadeDec(1),
    mTickCount(0),
    mSpinRadian(Deg2Rad(EXPLOSION_PARTICLE_SPIN_DEG_HALF - Random::random_f(EXPLOSION_PARTICLE_SPIN_DEG)))
{
    // Color:
    // Usually start with a randomized orange/yellow color.
    // Occasionally start with a bright white color.
    int r = 0xff;
    int g = 0xff;
    int b = 0xff;
    int a = 0xa0;
    mFadeDec = 5;  // fade-out white quickly
    if ( EX( Random::random_ui(10) > 1 ) )
    {
        r = 0xff - Random::random_ui(0x10);
        g = 0xc0 - Random::random_ui(0x18);
        b =        Random::random_ui(0x10);
        a = 0xa0 - Random::random_ui(0x10);
        mFadeDec = 2;
    }
    SetColor0( RGBA(r,g,b,a) );

    // Direction of particle:
    // Goal is to make fireballs shoot mostly upwards.
    // Start with a vector pointing level (orthagonal to up).
    // Yaw it full circle.
    // Then pitch up.
    const Radian yaw( Random::random_f( math::PI_DOUBLE ) );  // 360 = pi*2
    const Radian pitch( -Deg2Rad( 60.0f + Random::random_f(30.0f) ) );
    Matrix matrix;
    MatrixRotateLocal( matrix, ZZ, yaw );
    MatrixRotateLocal( matrix, YY, pitch );  // Y (dunno)
    const WorldVertex v( spriteArgs.mWidth * (0.01f + Random::random_f(0.03f)),
                         0.0f,
                         0.0f );
    mStep = v * matrix;
}

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

/*****************************************************************************
 * Animate explosion particle.
 *****************************************************************************/
void
ExplosionParticle::Tick( const Milliseconds millisecElapsed )
{
CHECK_TYPESIG(this,TYPESIG_BIG_PARTICLE);

    ++mTickCount;

    // Move particle one step.
    SetPosition( GetPosition() + mStep );
    mStep = mStep * 0.995f;  // gradually slow

    // Fade-out particle's color (alpha slower).
    if ( (mTickCount & EXPLOSION_SUB_TICK_MASK) == 0 )  // sub-tick resolution
    {
        RGBA color = GetColor0();
        color[RR] = ClampFF( int(color[RR]) - mFadeDec );
        color[GG] = ClampFF( int(color[GG]) - mFadeDec );
        color[BB] = ClampFF( int(color[BB]) - mFadeDec );
        color[AA] = ClampFF( int(color[AA]) - 1 );
        SetColor0( color );
    }

    // Spin the particle.
    RotateRoll( mSpinRadian );
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////  ExplosionParticleSystem  ///////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 * @param   lifetime
 *          Duration of explosion (milliseconds).
 * @param   position
 *          Center of explosion.
 * @param   radius
 *          Approx max radius of explosion.
 *****************************************************************************/
ExplosionParticleSystem::ExplosionParticleSystem( const Milliseconds lifetime,
                                                  const WorldVertex& position,
                                                  const fp radius )
:   BigParticleSystem(BigParticleSystem::Args(lifetime,
                                              TimerDefs::TIMER_ENABLED,
                                              NodeSort(fx::defs::PARTSYS_NODE_SORT_ATTRIBS))),
    mPosition(position),
    mRadius(radius),
    mTimeOfDeath(GET_TIMER().GetElapsedTime() + EXPLOSION_PARTICLE_LIFETIME),
    mTimeOfPrevTick(GET_TIMER().GetElapsedTime()),
    // For state-sorting, create stateset to be shared by all particles.
    mStateSet(Texture::NewTextureAsStateSet("textures/smoke.png"))
{
ASSERT( radius > 0 );

    // NOP.  Tick() does it.
}

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

/*****************************************************************************
 * Every tick, decide:
 * - whether to expire entire particle system
 *   (base ParticleSystemFX decides this)
 * - whether to create a new Particle
 *****************************************************************************/
void
ExplosionParticleSystem::Tick( const Milliseconds millisecElapsed )
{
CHECK_TYPESIG(this,TYPESIG_PARTICLE_SYSTEM);

    // Call parent's method.  It may expire this.
    Parent::Tick( millisecElapsed );

    // Expired?  If not, create more Particles?
    if ( not IfExpired() )
    {
        // Kill the oldest particles to make way for new ones.
        // Let hot-fire particles replace old-smoke particles.
        const int excess = GetParticleCount() - EXPLOSION_PARTICLE_LIMIT;
        if ( excess > 0 )
        {
            DetachOldestParticles( excess );
        }

        // From time between now and previous tick,
        // compute how many Particles to create.
        const Milliseconds timeDelta = millisecElapsed - mTimeOfPrevTick;
        ASSERT( timeDelta >= Milliseconds(0) );
        const uint particleCount = Max<uint>( 1, uint( timeDelta.FPX() / 1000.0 * EXPLOSION_PARTICLES_PER_SECOND ) );
        MakeParticles( particleCount );
    }

    // Update time-stamp.
    mTimeOfPrevTick = millisecElapsed;
}

/*****************************************************************************
 * Make Particles.
 *****************************************************************************/
void
ExplosionParticleSystem::MakeParticles( const uint count )
{
CHECK_TYPESIG(this,TYPESIG_PARTICLE_SYSTEM);
ASSERT( count < fx::defs::MAX_PARTICLES_ONE_SYSTEM );

    for ( uint i = 0; i < count; ++i )
    {
        // Create and attach to this ParticleSystem.
        const fp n = mRadius * 0.3f;  // start at random offsets from center
        const fp n2 = n + n;
        Sprite::Args spriteArgs( WorldVertex( mPosition[XX] + n - Random::random_f(n2),
                                              mPosition[YY] + n - Random::random_f(n2),
                                              mPosition[ZZ] + n - Random::random_f(n2) ),
                                 mRadius * (0.5f + Random::random_f(1.0f)),
                                 mStateSet,
                                 RGBA() );  // color passed isn't important
        AttachParticle( new ExplosionParticle( spriteArgs, mRadius ) );  // implied stateBits from ctor arg
    }
}

} // namespace fx
