/*****************************************************************************
 * $Id: mod_pony_cloud.cc,v 1.1 2006/11/24 21:56:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	High-level cloud code.
 * @verbatim
 * -----------------------------------------------------------------------------
 *
 * Overview of clouds:
 * -------------------
 *
 * In a feeble attempt to model the grand infinity of variations in clouds,
 * the C++ code fills a finite set of Cloud::Shape structs with random values.
 * A Cloud::Shape defines the shape and appearance of a cloud.
 * Each Shape is associated with a texture or Billboard object.
 * Because of finite system resources, the limited variety of clouds
 * are duplicated at different positions and scales (radius).
 *
 * // Create a set of Cloud::Shape objects:
 * do this once:
 *   for n in cloudShapeCnt:
 *     make random Shape
 *     add random Shape to STL container
 *
 * // Make n clouds out of the set of Cloud::Shape:
 * for n in cloudCnt:
 *     pick random Cloud::Shape
 *     pick random position
 *     pick random radius // scale
 *     new Cloud( Shape, pos, radius )
 *
 * Cloud::Cloud( shape, pos, radius ):
 *     if shape never encountered:
 *         make texture or Billboard
 *         store texture or Billboard in static STL container
 *     else:
 *         get texture or Billboard from static STL container
 *     [cloud construction: mPos(pos), SetRadius(radius), etc]
 *
 * CloudBillboardMap class:
 * ------------------------
 *
 * This class helps to associate a Cloud Shape with a Billboard.
 * It is meant to be a static member of Object-derived Cloud classes.
 * It helps their ctors get a Billboard object associated with a CloudShape.
 *
 * -----------------------------------------------------------------------------
 * @endverbatim
 *//*
 * LEGAL:	COPYRIGHT (C) 2006 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define MOD_PONY_CLOUD_CC 1
#include "mod_pony_common.hh"
#include "gfx_random.hh"
#include "mod_pony_cloud.hh"
#include "mod_pony_cloud_cumulus.hh"
#include "mod_pony_cloud_cirrus.hh"

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  Cloud::TypeCombo  //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * In meterology, clouds are described by combined words such as "altocumulus".
 * The C++ class TypeCombo works similarly by combining type enums into one object.
 * Some combinations of cloud types are non-sensical.
 * TypeCombo will change an invalid combo into another combo that's valid.
 *****************************************************************************/
Cloud::TypeCombo::TypeCombo( const eType1 type1, const eType2 type2, const eType3 type3 )
{
    Set( type1, type2, type3 );
}
void
Cloud::TypeCombo::Set( const eType1 type1, const eType2 type2, const eType3 type3 )
{
    // Assume combo is valid.
    mType1 = type1;
    mType2 = type2;
    mType3 = type3;

    // Correction for sub-type 2.
    switch ( type2 )
    {
        default:
        case eType2_NIMBUS:      // vertical
        case eType2_FRACTO:      // fragmented
        case eType2_CIRRO:       // curly
        case eType2_CIRRUS:      // fibrous
        case eType2_ALTO:        // high
            // ok
        break;

        case eType2_RAIN:        // light/medium rain
        case eType2_STORM:       // heavy rain, lightning
            // Correction: rain/storm clouds are always cumulus, never stratus (?).
            mType1 = eType1_CUMULUS;
        break;
    }

    // Correction for sub-type 3.
    switch ( type3 )
    {
        default:
            // ok
        break;

        case eType3_BOX:
        case eType3_SPHERE:
        case eType3_PYRAMID:
            // Correction: stratus is always flat.
            if ( type1 == eType1_STRATUS )
                mType3 = eType3_DEFAULT;
        break;
    }
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////  Cloud  /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Top-level factory function to create different types of cloud Objects.
 *****************************************************************************/
void
Cloud::Factory( const TypeCombo& typeCombo, QuadrantPtr quadrant )
{
    // Deserts (in this simulation) don't have any clouds.
	if ( gWorldParm.mTerrain.mDesert )
        return;

    // This top level is for the general type of cloud.
    // Defer to a subroutine that will make a randomized cloud
    // (random position in quadrant, color, alpha, etc).
    switch ( typeCombo.GetType1() )
    {
        case eType1_CUMULUS:
            FactoryCumulus( typeCombo, quadrant );
        break;

        case eType1_STRATUS:
            FactoryStratus( typeCombo, quadrant );
        break;

        default:
            ASSERT(0);  // unexpected
        break;
    }
}

/*****************************************************************************
 * Top-level of cumulus cloud factory.
 *****************************************************************************/
void
Cloud::FactoryCumulus( const TypeCombo& typeCombo, QuadrantPtr quadrant )
{
	IF_ONCE { *gStreamOut << "Generating textures (patience)..." << endl; }

    switch ( typeCombo.GetType3() )
    {
        default:
        case eType3_SPHERE:
            FactoryCumulusSphere( typeCombo, quadrant );
        break;
    }
}

/*****************************************************************************
 * Factory to make a spherical group of cumulus clouds.
 *****************************************************************************/
void
Cloud::FactoryCumulusSphere( const TypeCombo& typeCombo, QuadrantPtr quadrant )
{
    // Shape parameters are used to construct Object-derived cloud objects.
    // Allocate them only once.
    static vector<CumulusCloud::Shape> sShapes;
    if ( sShapes.empty() )
    {
        #define ADD_CUMULUS_CLOUD_SHAPE( COLOR, ALPHA,							\
                                         NOISE_FREQ, NOISE_FREQ_SCALE_LIMIT,    \
                                         DIST_NORM_FACTOR, AXIS, DEG )			\
        {																		\
        sShapes.push_back( CumulusCloud::Shape( COLOR,							\
                                                ALPHA,							\
                                                NOISE_FREQ,						\
                                                NOISE_FREQ_SCALE_LIMIT,			\
                                                DIST_NORM_FACTOR ) );			\
        }
        const fp color = 0.45;
        const fp alpha = 0.6;
        ADD_CUMULUS_CLOUD_SHAPE( color+random_f(0.3), alpha+random_f(0.2), 0.00647, 8, 1.00, AXIS_X,  0.0 );
        ADD_CUMULUS_CLOUD_SHAPE( color+random_f(0.3), alpha+random_f(0.2), 0.00612, 8, 1.03, AXIS_X, 20.0 );
        ADD_CUMULUS_CLOUD_SHAPE( color+random_f(0.3), alpha+random_f(0.2), 0.00589, 4, 1.11, AXIS_Z, 35.0 );
        ADD_CUMULUS_CLOUD_SHAPE( color+random_f(0.3), alpha+random_f(0.2), 0.00702, 8, 0.95, AXIS_X, 75.0 );
        ADD_CUMULUS_CLOUD_SHAPE( color+random_f(0.3), alpha+random_f(0.2), 0.00497, 4, 1.08, AXIS_X, 15.0 );
        ADD_CUMULUS_CLOUD_SHAPE( color+random_f(0.3), alpha+random_f(0.2), 0.00674, 4, 1.12, AXIS_X, 15.0 );
	}

	// Randomly make clouds in this quadrant.
	const uint cloudsPerClump = 7;

	// Random position.
	Position pos;
	quadrant->RandomPosition( pos );
	pos.y = Max( pos.y, WORLD(8000.0) );  // prevent too low clouds

    // Group several cumulus clouds to form a spherical clump.
    for ( uint j = 0; j < cloudsPerClump; ++j )
    {
        Vector3 v( 1.0, 0.0, 0.0 );
        fp x, y;

        x = v.x;
        y = v.y;
        fp rad1 = random_f( M_PI*2.0 );
        v.x = x*sin(rad1) + y*cos(rad1);
        v.y = x*cos(rad1) - y*sin(rad1);

        x = v.y;
        y = v.z;
        fp rad2 = random_f( M_PI*2.0 );
        v.y = x*sin(rad2) + y*cos(rad2);
        v.z = x*cos(rad2) - y*sin(rad2);

        // Expand cumulus cloud nears its middle.
        fp radius = WORLD(2500.0 + random_f(1750.0));
        if ( ABS(v.y) < 0.2 )
        {
            radius *= 0.75;
            v = v * 1.95;
        }
        else if ( ABS(v.y) < 0.4 )
        {
            radius *= 0.85;
            v = v * 1.60;
        }

        v = v * WORLD(2000.0 + random_f(1000.0));
        Position pos2( pos.x + v.x,
                       pos.y + v.y,
                       pos.z + v.z );

        uint shapeIdx = random_ui( sShapes.size() );  ASSERT( shapeIdx < sShapes.size() );
		if ( gWorld.IfInside(Posrad(pos2,radius)) )
			new CumulusCloud( quadrant, pos2, radius, sShapes[shapeIdx] );
    }
}

/*****************************************************************************
 * Top-level of stratus cloud factory.
 *****************************************************************************/
void
Cloud::FactoryStratus( const TypeCombo& typeCombo, QuadrantPtr quadrant )
{
    static vector<CirrusCloud::Shape> sShapes;
    if ( sShapes.empty() )
    {
        sShapes.push_back( CirrusCloud::Shape( "cloudCirrus1.tga" ) );
        sShapes.push_back( CirrusCloud::Shape( "cloudCirrus2.tga" ) );
        sShapes.push_back( CirrusCloud::Shape( "cloudCirrus3.tga" ) );
        sShapes.push_back( CirrusCloud::Shape( "cloudCirrus4.tga" ) );
        sShapes.push_back( CirrusCloud::Shape( "cloudCirrus5.tga" ) );
	}

	// Random position.
	Position pos;
	quadrant->RandomPosition( pos );
	pos.y = Max( pos.y, WORLD(25000.0) );  // prevent too low clouds

    // Random radius.
    fp radius = Min(WORLD(15000.0 + random_f(10000.0)), gWorld.GetQuadrantEdge());

    // Random shape.
    uint randomShapeIdx = random_ui( sShapes.size() );  ASSERT( randomShapeIdx < sShapes.size() );
	if ( gWorld.IfInside(Posrad(pos,radius)) )
		new CirrusCloud( quadrant, pos, radius, sShapes[randomShapeIdx] );
}

/*****************************************************************************
 * Support for filter RGBAs from Targa files.
 * If I knew how to pre-filter using GIMP, this would be eliminated.
 *****************************************************************************/
static int sColorBias = 0;
static int sAlphaBias = 0;

static void
TextureFilterFunc_ColorToAlpha( uint8* p, uint8 r, uint8 g, uint8 b, uint8 a )
{
    p[RR] = p[GG] = p[BB] = ClampFF( int(r) + int(sColorBias) );  // use red vale for all colors
    p[AA] =                 ClampFF( int(r) + int(sAlphaBias) );  // use red for alpha but separate bias
}

void
Listener_ModulePony_TextureFilter_Cloud( string& textureName )
{
    if ( textureName == "cloudCirrus1.tga"
      || textureName == "cloudCirrus2.tga"
      || textureName == "cloudCirrus3.tga"
      || textureName == "cloudCirrus4.tga"
      || textureName == "cloudCirrus5.tga" )
    {
        sColorBias = 110;
        sAlphaBias = -45;
        gEng.SetTextureFilterFunc( TextureFilterFunc_ColorToAlpha );
    }
}
