/*****************************************************************************
 * $Id: mod_pony_sky.cc,v 1.4 2006/11/25 22:41:54 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Sky class.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define MOD_PONY_SKY_CC 1
#define SKY_TOP_TEXTURED 0
#include "mod_pony_common.hh"
#include "mod_pony_sky.hh"
#include "mod_pony_cloud.hh"
#include "gfx_random.hh"

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////  Sky  /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

uint Sky::msFrameDrawn = FRAME_CNT_INVALID;

/*****************************************************************************
 * Sky constructor.
 *****************************************************************************/
Sky::Sky( QuadrantPtrConst q )
:	Omni(q), mPrevEye(0,0,0)
{
    MakeClouds();

	DEBUG_CODE( CheckObjectCtor(); )
}
Sky::~Sky()
{
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Make clouds.
 *****************************************************************************/
void
Sky::MakeClouds( void )
{
    Cloud::TypeCombo    cloudType;

    cloudType.Set( Cloud::eType1_CUMULUS );
    MakeClouds( cloudType, 5 );  // cloudType, cloudCnt

    cloudType.Set( Cloud::eType1_STRATUS, Cloud::eType2_CIRRUS );
    MakeClouds( cloudType, 5 );
}
// subroutine
void
Sky::MakeClouds( const Cloud::TypeCombo& cloudType, uint cloudCnt )
{
    // Adjust cloud cnt arg for cloud density.
    cloudCnt = uint(cloudCnt * gWorldParm.mCloud.mDensity * gWorldParm.mObjRatio);

	for ( uint i = 0; i < cloudCnt; ++i )
	{
		Cloud::Factory( cloudType, GetQuadrant() );
	}
}

/*****************************************************************************
 * Draw sky.
 *****************************************************************************/
void
Sky::Draw( void )
{
	if ( ! IfOmniDrawnThisFrame() )
	{
		DrawSkyCylinder();
	}
}

/*****************************************************************************
 * Draw sky as a cylinder but with the top circle reduced (a frustrum/cylider).
 *
 *     The sky cylinder encloses the tic-tac-toe quadrants.
 *     radius = locusWidth/2
 *     +------+------+------+------+------+
 *     |     +|------|------|------|+     |
 *     |    / |      |      |      | \    |
 *     +------+======+======+======+------+
 *     |   |  ||     |      |     ||  |   |
 *     |   |  ||     |      |     ||  |   |
 *     +------+------+------+------+------+
 *     |   |  ||     |  .   |     ||  |   |
 *     |   |  ||     | eye  |     ||  |   |
 *     +------+------+------+------+------+
 *     |   |  ||     |      |     ||  |   |
 *     |   |  ||     |      |     ||  |   |
 *     +------+======+======+======+------+
 *     |    \ |      |      |      | /    |
 *     |     +|------|------|------|+     |
 *     +------+------+------+------+------+
 *     
 *     |------------ locusWidth ----------|
 *
 *****************************************************************************/
void
Sky::DrawSkyCylinder( void )
{

    // (XX,ZZ)=lat,long (YY)=alt
	const fp			radius = gWorld.mLocus.GetEdge( XX ) / 2.2;  // make radius slightly smaller than half
	const fp			height = gWorld.mLocus.GetEdge( YY );        // as land tiles don't quite extend enough
	static const RGBA	rgbaBlue( 0.0, 0.0, 1.0, 1.0 );
	static const RGBA	rgbaSky( RGB_SKYBLUE, 1.0 );
	const bool			sunsetOrSunrise = gModule.mWeather.IfSunsetOrSunrise();
	const bool			savedDepthMask = GFX_DEPTH_MASK( false );
	const int			angleInc = 360/mSideCnt;
	int					angle, i;

	// Calculate vertexes of cylinder (in its own coord system).
	for ( angle = i = 0;
          angle <= 360;
          angle += angleInc, ++i )
	{
		// cylinder bottom
		mVg[i].x = cos_deg(angle) * radius - gEng.mEye.mCm[Ox];
		mVg[i].y = ALTITUDE_LAND - WORLD(100.0);  // slightly below (prevent thin blue line where earth/sky meet)
		mVg[i].z = sin_deg(angle) * radius - gEng.mEye.mCm[Oz];

		// cylinder top
		mVs[i].x = cos_deg(angle) * radius - gEng.mEye.mCm[Ox];
		mVs[i].y = height;
		mVs[i].z = sin_deg(angle) * radius - gEng.mEye.mCm[Oz];
	}

	// Draw sky depending on time-of-day.
	Texture2Ptr textureSide;
	fp tc;
	if ( sunsetOrSunrise )
	{
		tc = 1.0;
		textureSide = gEng.GetTexture( "skySide2Sunset_128.tga" );
	    textureSide->SetFunc( Texture2::eFunc_MODULATE );
		GFX_SHADE( eShade_SMOOTH );
	}
	else
	{
		tc = 4.0;
		textureSide = gEng.GetTexture( "skySide2_128.tga" );
	    textureSide->SetFunc( Texture2::eFunc_REPLACE );
		GFX_RGBA( gEng.GetFogDensity() >= 1.0 ? rgbaBlue : rgbaSky );
	}

	// Draw sides of cylinder.
   	GFX_BEGIN( GP_QUADS, textureSide );
	for ( angle = i = 0;
          angle + angleInc <= 360;  // one extra iteration to enclose back to first pair of vertexs
          angle += angleInc, ++i )
	{
		// Upright side #1.
		if ( sunsetOrSunrise )
		{
			int color = int(float(angle%360)/360.0*255.0);
			GFX_RGBA( RGBA(color,color,200,100) );
		}
		GFX_MAP_TEXTURE_2D( 0.0, 0.0 ); GFX_TRANSFORM_VERTEX( mVg[i] );
		GFX_MAP_TEXTURE_2D( 0.0,  tc ); GFX_TRANSFORM_VERTEX( mVs[i] );

		// Upright side #2.
		if ( sunsetOrSunrise )
		{
			int color = int(float((angle+angleInc)%360)/360.0*255.0);
			GFX_RGBA( RGBA(color,color,200,100) );
		}
		GFX_MAP_TEXTURE_2D(  tc,  tc ); GFX_TRANSFORM_VERTEX( mVs[i+1] );
		GFX_MAP_TEXTURE_2D(  tc, 0.0 ); GFX_TRANSFORM_VERTEX( mVg[i+1] );
	}
	GFX_END( textureSide );

	if ( sunsetOrSunrise )
	{
		GFX_SHADE( eShade_NORMAL );
	}

	// Draw top of cylinder.
	// Draw it as an over-size quad which eases specifying texture coords.
    WorldVertex wvTop[4];
    fp rx = radius * 1.25 - gEng.mEye.mCm[Ox];
    fp rz = radius * 1.25 - gEng.mEye.mCm[Oz];
    wvTop[0].Set(  rx, height,  rz );
    wvTop[1].Set( -rx, height,  rz );
    wvTop[2].Set( -rx, height, -rz );
    wvTop[3].Set(  rx, height, -rz );
#if SKY_TOP_TEXTURED
    Texture2Ptr textureTop = gEng.GetTexture( "skyTop2_128.tga" );
    textureTop->SetWrap( Texture2::eWrap_REPEAT );
    textureTop->SetFunc( Texture2::eFunc_REPLACE );
    const fp tc = 8.0;
   	GFX_BEGIN( GP_QUADS, textureTop );
		GFX_MAP_TEXTURE_2D( 0.0, 0.0 ); GFX_TRANSFORM_VERTEX( wvTop[0] );
		GFX_MAP_TEXTURE_2D( 0.0,  tc ); GFX_TRANSFORM_VERTEX( wvTop[1] );
		GFX_MAP_TEXTURE_2D( tc,   tc ); GFX_TRANSFORM_VERTEX( wvTop[2] );
		GFX_MAP_TEXTURE_2D( tc,  0.0 ); GFX_TRANSFORM_VERTEX( wvTop[3] );
	GFX_END( textureTop );
#else
	if ( sunsetOrSunrise )
		GFX_RGBA( RGBA(198,112,104,0) );  // skySide2Sunset_128.tga
	else
		GFX_RGBA( RGBA(114,152,197,0) );  // skySide2_128.tga blue

   	GFX_BEGIN( GP_QUADS );
		GFX_TRANSFORM_VERTEX( wvTop[0] );
		GFX_TRANSFORM_VERTEX( wvTop[1] );
		GFX_TRANSFORM_VERTEX( wvTop[2] );
		GFX_TRANSFORM_VERTEX( wvTop[3] );
	GFX_END();
#endif

	GFX_DEPTH_MASK( savedDepthMask );
}

/*****************************************************************************
 * @return Distance between object and viewplane.
 *****************************************************************************/
fp
Sky::GetDistance( void )
{
	// See Terrain::Distance().
	return Parent::GetDistance();
}
