/*****************************************************************************
 * $Id: game_pony_cloud.cc,v 1.14 2006/08/10 03:18:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Perlin Cloud class.
 *//*
 * LEGAL:	COPYRIGHT (C) 2006 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define GAME_PONY_CLOUD_CC 1
#include "game_pony_common.hh"
#include "game_pony_cloud.hh"
#include "gfx_random.hh"
#include "gfx_noise.hh"

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////  PerlinCloud  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

MutableTexture* PerlinCloud::msTexture;
bool PerlinCloud::msGenerated;

/*****************************************************************************
 * Constructor.
 *****************************************************************************/
PerlinCloud::PerlinCloud( QuadrantPtrc q, const Position& pos )
	: Parent(q), mPos(pos)
{
	const fp sx = WORLD(1500.0 + random_f(1500.0));
	const fp sy = WORLD(500.0  + random_f(250.0));
	SetRadius( MAX(sx,sy)/2 );
	mLocalVertexs[0].Set(  sx, -sy, 0.0 );	// SE
	mLocalVertexs[1].Set(  sx,  sy, 0.0 );	// NE
	mLocalVertexs[2].Set( -sx,  sy, 0.0 );	// NW
	mLocalVertexs[3].Set( -sx, -sy, 0.0 );	// SW

	IdentityMatrix(m);
	m[Ox] = pos[XX];
	m[Oy] = pos[YY];
	m[Oz] = pos[ZZ];

	if ( msTexture == NULL )
	{
		msTexture = new MutableTexture( TEX_WIDTH, TEX_HEIGHT );
	}

	// TODO/FIXME: Generate cloud texture once (should be done when VP changes!!)
	Generate();

	FinalConstruction();
	DEBUG_CODE( CheckObjectCtor(); )
}
PerlinCloud::~PerlinCloud()
{
	// DELETE_NULL( mTexture );  // for per-cloud texture (for now, texture is shared)
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Cloud noise function.
 *****************************************************************************/
fp
PerlinCloud::CloudNoise( const Vector3& vec )
{
	const fp radius = (TEX_WIDTH/2 + TEX_HEIGHT/2) / 2;  // cloud radius
	fp xd = vec.x - radius;
	fp yd = vec.y - radius;

	fp dist = sqrtf( xd * xd + yd * yd );
	fp distNorm = dist / radius;

	fp color1 = 1.0 - pow(distNorm,1.10);
	color1 = Clamp1( color1 );

	fp color2 = PerlinNoise::Calc( vec );
	color2 = Clamp1( color2 );

	fp color = color1 + (color1 * color1 * color2);
	color = (color + 1.0) * 0.5;
	color = pow(color,1.8);
//	color += 0.2;
	color = Clamp( color, 0.0, 0.87 );
	if ( color < 0.3 ) color = 0.0;

	return color;
}

/*****************************************************************************
 * Generate the cloud.
 *****************************************************************************/
void
PerlinCloud::Generate( void )
{
	if ( msGenerated ) return;

	PerlinNoise::Set( 0xabcd, 0.110, 0.013, 2 );

	// Generate a 2D texture that maps to the generated 2D quad.
    uint i = 0;
	const uint sx = TEX_WIDTH;
	const uint sy = TEX_HEIGHT;
	int z = 0;
	uint8* buf = msTexture->GetBuf(); ASSERT( buf != NULL );
	DEBUG_CODE( const uint bufLen = msTexture->GetBufLen(); )
	for ( uint x = 0; x < sx; ++x )
	{
		DEBUG_CODE( ASSERT( i < bufLen ); )
        for ( uint y = 0; y < sy; ++y )
        {
			Vector3 vec( x, y, z );
			fp color = CloudNoise( vec );
            buf[i+0] = ClampFF(color + 0.1f);
            buf[i+1] = ClampFF(color + 0.1f + 0.02f);
            buf[i+2] = ClampFF(color + 0.1f + 0.20f);
            buf[i+3] = ClampFF(color - 0.40f);
            i += 4;
        }
	}
	msTexture->BufferWasChanged();  // tell MutableTexture to change

	msGenerated = true;
}

/*****************************************************************************
 * Compute world vertexs.
 * Cloud geometry is oriented to face the eye.
 *****************************************************************************/
const WorldVertex*
PerlinCloud::GetWorldVertexs( void )
{
	// Generate a 2D quad inside the cloud that faces the viewpoint (eye -Z axis).
	OrientMatrix( m, gEng.mEye.GetPosition() );
	for ( uint i = 0; i < GetVertexCnt(); ++i )
	{
		fp x = mLocalVertexs[i].x;
		fp y = mLocalVertexs[i].y;
		fp z = mLocalVertexs[i].z;
		mWorldVertexs[i].x = x * m[Xx] + y * m[Xy] + z * m[Xz] + m[Ox];
		mWorldVertexs[i].y = x * m[Yx] + y * m[Yy] + z * m[Yz] + m[Oy];
		mWorldVertexs[i].z = x * m[Zx] + y * m[Zy] + z * m[Zz] + m[Oz];
	}
	return mWorldVertexs;
}

/*****************************************************************************
 * Draw.
 *****************************************************************************/
void
PerlinCloud::Draw( void )
{
	// TODO/FIXME: Regenerate the cloud when the viewpoint has moved past a threshold.
	Generate();
	const EyeVertex* ev = GetEyeVertexs();
	static const RGBA rgba( 1.0, 1.0, 1.0, 1.0 );
	GFX_RGBA( rgba );
	GFX_BEGIN_ALPHA_BLEND();
	GFX_BEGIN( GP_QUADS, msTexture );
        msTexture->Map( 0.0,  0.0 ); GFX_VERTEXV( &ev[0] );
        msTexture->Map( 0.0,  1.0 ); GFX_VERTEXV( &ev[1] );
        msTexture->Map( 1.0,  1.0 ); GFX_VERTEXV( &ev[2] );
        msTexture->Map( 1.0,  0.0 ); GFX_VERTEXV( &ev[3] );
	GFX_END( msTexture );
	GFX_END_ALPHA_BLEND();
}
