#include "vObject.h"
#include "SurfManager.h"
#include "RingManager.h"

//============================================
//	PLANET class
//	*	Atmosphere
//	*	Rings
//	*	Bases
//	*	Clouds
//============================================

vPlanet::vPlanet( OBJHANDLE _obj ) : vObject( _obj ) {
//name, label
//basic
	Base = NULL;
	nbase = 0;

	rad = (float)oapiGetSize( obj );
	render_rad = (float)(rad*0.1);
	dist_scale = 1.0f;
	patchres = 0;
	ring_res = 0;

	PlanetMesh = NULL;
	SM = NULL;
	HM = NULL;
	RingData = NULL;
	CloudData = NULL;
	Mass = oapiGetMass( obj );

//add surface manager/mesh
	if( *(int*)gc->GetConfigParam( CFGPRM_SURFACEMAXLEVEL ) != 0 && *(int*)oapiGetObjectParam( obj, OBJPRM_PLANET_SURFACEMAXLEVEL ) )
		SM = new SurfaceManager( this );
	else {
		MESHHANDLE hMesh = oapiLoadMesh( name );
		if( hMesh ) {
			PlanetMesh = new D3D11Mesh( hMesh, false );
			oapiDeleteMesh( hMesh );
		}
	}

//add atmospheric haze if planet has atmosphere + param
	if( *(bool*)gc->GetConfigParam( CFGPRM_ATMHAZE ) && oapiPlanetHasAtmosphere( obj ) )
		HM = new HazeManager( this );

	bRipple = *(bool*)gc->GetConfigParam( CFGPRM_SURFACERIPPLE ) && *(bool*)oapiGetObjectParam( obj, OBJPRM_PLANET_SURFACERIPPLE );
	if( bRipple )
		SM->SetMicrotexture( "waves.dds" );
	shadow_alpha = (float)(*(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_SHADOWCOLOUR ));
	bVesselShadows = *(bool*)gc->GetConfigParam( CFGPRM_VESSELSHADOWS ) && shadow_alpha >= 0.01;

//add clouds if planet has clouds
	if( *(bool*)gc->GetConfigParam( CFGPRM_CLOUDS ) && *(bool*)oapiGetObjectParam( obj, OBJPRM_PLANET_HASCLOUDS ) ) {
		CloudData = new CLOUDPRM;
		memset( CloudData, 0, sizeof(CLOUDPRM) );
		CloudData->CM = new CloudManager( this );			//cloud manager class
		CloudData->cloud_rad = rad + *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDALT );
		CloudData->bCloudShadow = *(bool*)gc->GetConfigParam( CFGPRM_CLOUDSHADOWS );
		if( CloudData->bCloudShadow ) {
			CloudData->fShadowAlpha = 1.0f - *(float*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDSHADOWCOL );
			if( CloudData->fShadowAlpha < 0.01f )
				CloudData->bCloudShadow = false;
		}
		if( *(bool*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDMICROTEX ) ) {
			CloudData->CM->SetMicrotexture( "cloud1.dds" );
			CloudData->micro_alt0 = *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDMICROALTMIN );
			CloudData->micro_alt1 = *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDMICROALTMAX );
		}
	}

//add rings if planet has rings
	if( *(bool*)oapiGetObjectParam( obj, OBJPRM_PLANET_HASRINGS ) ) {
		RingData = new RINGPRM;
		memset( RingData, 0, sizeof(RINGPRM) );
		RingData->minrad = *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_RINGMINRAD );
		RingData->maxrad = *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_RINGMAXRAD );
		RingData->RM = new RingManager( this, RingData->minrad, RingData->maxrad );
		render_rad = (float)(rad*RingData->maxrad);
	}

//Fog and atm consts
	memcpy( &Fog, oapiGetObjectParam( obj, OBJPRM_PLANET_FOGPARAM ), sizeof(FogParam) );
	if( *(bool*)gc->GetConfigParam( CFGPRM_ATMFOG ) && Fog.dens_0 > 0 )		bFog = true;
	else																	bFog = false;
	atmc = oapiGetPlanetAtmConstants( obj );

	nbase = oapiGetBaseCount( obj );
	if( nbase ) {
		Base = new vBase *[nbase];
		for( DWORD j = 0; j < nbase; j++ )
			Base[j] = new vBase( this, obj, oapiGetBaseByIndex( obj, j ) );
	}

//init constants
	DWORD dAmbient = *(DWORD*)gc->GetConfigParam( CFGPRM_AMBIENTLEVEL );
	AP.GlobalAmb = ((float)dAmbient)*0.0039f;
	if( atmc ) {
		AP.Ambient0 = min( 0.7f, log( (float)atmc->rho0 + 1.0f)*0.4f );
		AP.Dispersion = max( 0.02f, min( 0.9f, log( (float)atmc->rho0 + 1.0f ) ) );
	}
	else {
		AP.Ambient0 = 0.0f;
		AP.Dispersion = 0.0f;
	}
}

vPlanet::~vPlanet() {
	if( PlanetMesh )	delete PlanetMesh;
	if( SM )	delete SM;
	if( HM )	delete HM;
	if( CloudData ) {
		delete CloudData->CM;
		delete CloudData;
	}
	if( RingData ) {
		delete RingData->RM;
		delete RingData;
	}
	if( nbase ) {
		for( DWORD j = 0; j < nbase; j++ )
			delete Base[j];
		delete [ ] Base;
	}
}

//================================================
//			Update
//================================================

void vPlanet::Update() {
	DWORD i, j;

	vObject::SaveData();	//not releveant in the case of a planet

	if( !active )	return;

	vObject::Update();

	if( nbase )
		for( j = 0; j < nbase; j++ )
			Base[j]->Update();

	if( patchres == 0 )		//nothing to do
		return;

	float rad_scale = rad;
	bool rescale = false;
	dist_scale = 1.0f;

	const static double farplane = 1e6;			//?
	const static double render_rad = 0.1*rad;	//?
//change scale if needed
	if( CDist+render_rad > farplane && CDist-rad > 1e4 ) {
		rescale = true;
		dist_scale = (float)(farplane/(CDist+render_rad));
	}
//rescale
	if( rescale ) {
		rad_scale *= dist_scale;
		mWorld._41 *= dist_scale;
		mWorld._42 *= dist_scale;
		mWorld._43 *= dist_scale;
	}
//scale world matrix	
	mWorld._11 *= rad_scale; mWorld._12 *= rad_scale; mWorld._13 *= rad_scale;
	mWorld._21 *= rad_scale; mWorld._22 *= rad_scale; mWorld._23 *= rad_scale;
	mWorld._31 *= rad_scale; mWorld._32 *= rad_scale; mWorld._33 *= rad_scale;
//clouds
	if( CloudData ) {
		CloudData->RenderMode = ( CDist < CloudData->cloud_rad ? 1 : 0 );
		if( CDist > CloudData->cloud_rad*(1.0-1.5e-4) )	//camera can be "in" the cloud layer
			CloudData->RenderMode |= 2;
		if( CloudData->RenderMode & 1 ) {
			CloudData->viewap = acos( rad/CloudData->cloud_rad );
			if( rad < CDist )
				CloudData->viewap += acos( rad/CDist );
		}
		else
			CloudData->viewap = 0.0;

		float cloud_scale = (float)(CloudData->cloud_rad/rad);
		double cloud_rot = *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDROTATION );

		//world matrix for cloud shadows
		memcpy( &CloudData->mW_cloudshadow, &mWorld, 64 );

		if( cloud_rot ) {
			static D3DXMATRIX crot(	1, 0, 0, 0,
									0, 1, 0, 0,
									0, 0, 1, 0,
									0, 0, 0, 1	);
			crot._11 = crot._33 = (float)cos( cloud_rot );
			crot._13 = -(crot._31 = (float)sin( cloud_rot ) );
			D3DXMatrixMultiply( &CloudData->mW_cloudshadow, &crot, &CloudData->mW_cloudshadow );
		}

		//world matrix for clouds
		memcpy( &CloudData->mW_cloud, &CloudData->mW_cloudshadow, 64 );

		for( i = 0; i < 3; i++ )
			for( j = 0; j < 3; j++ )
				CloudData->mW_cloud.m[i][j] *= cloud_scale;		//cloud should be above ground and above shadows

		//set microtexture intensity
		double alt = CDist - rad;
		double lvl = (CloudData->micro_alt1 - alt)/(CloudData->micro_alt1 - CloudData->micro_alt0);
		CloudData->CM->SetMicrolevel( max( 0.0, min( 1.0, lvl ) ) );
	}

	//base visuals
	//no bases so far
	InitLegacyAtmosphere();
	
	if( SM )
		SM->SaveParams( &mWorld, dist_scale, patchres, 0.0, bFog );
	if( HM )
		HM->SaveParams();
	if( RingData )	RingData->RM->SaveParams();
	if( CloudData ) {
		CloudData->CM->SaveParams( &CloudData->mW_cloud, dist_scale, min( patchres, 8 ), CloudData->viewap );
		CloudData->CM->SaveParamsShadows( &CloudData->mW_cloudshadow, dist_scale, min( patchres, 8 ), CloudData->viewap, CloudData->fShadowAlpha );
	}
}

void vPlanet::CheckResolution() {
	double alt = max( 1.0, CDist - rad );
	double apr = rad * cfg->cHeight*0.5 / ( alt*tan( SC->GetCamAperture() ));

	int new_patchres;
	double ntx;

	if( apr < 2.5 ) {
		new_patchres = 0;
		ntx = 0;
	}
	else {
		static const double scal2 = 1.0/log(2.0);

		ntx = PI2 * apr;		
		new_patchres = min( max( (int)(scal2*log( ntx ) - 5.0), 1 ), SURF_MAX_PATCHLEVEL );
	}

	if( new_patchres != patchres ) {
		if( RingData )
			RingData->res = ( new_patchres <= 3 ? 0 : (new_patchres <= 4 ? 1 : 2 ));
		patchres = new_patchres;
	}
}

//================================================
//			Render
//================================================

void vPlanet::Render() {
	DWORD j;

	if( !active )		return;
	if( patchres == 0 ) return;

	bool fog = bFog;

	DWORD bg_color = SC->GetBgColor();
	bool add_ambient = ((bg_color & 0xFFFFFF) && (obj != SC->GetProxyBody()));

	if( RingData )									//render planetary rings
		RingData->RM->Render( &mWorld, RingData->res, false );

	if( CloudData && (CloudData->RenderMode & 1) )	//render clouds from below
		CloudData->CM->Render( false );
			
	if( HM )										//render atmosphere haze
		HM->Render( &mWorld, false );

	if( bFog && obj == SC->GetProxyBody() ) {
		fog = InitFogParams( fog );
		SC->SetAtmParams( &AP );
	}

	if( !fog )
		AP.HazeMode = 0;

	if( !PlanetMesh ) {
		if( bFog )		AP.FogDensity /= dist_scale;
		SM->Render();					//render planet surface		

		if( nbase ) {
			if( bFog )		AP.FogDensity *= dist_scale;

			D3D11Mesh::InitRenderBaseTiles();
			for( j = 0; j < nbase; j++ )		//render base tiles
				Base[j]->RenderSurface();
			
			D3D11Mesh::InitRender();
			for( j = 0; j < nbase; j++ )		//render base structures before shadows
				Base[j]->RenderStructuresBS();

			D3D11Mesh::InitRenderShadows();
			for( j = 0; j < nbase; j++ )		//render shadows on surface and strustures
				Base[j]->RenderShadows();

			D3D11Mesh::InitRender();
			for( j = 0; j < nbase; j++ )
				Base[j]->RenderStructuresAS();
		}
	}
	else {
		D3D11Mesh::InitRender();

		def_SunLight.SunDir = AP.SunDir;
		memcpy( &D3D11Mesh::VSCB_per_object.Sun, &def_SunLight, 64 );
		D3D11Mesh::VSCB_per_object.VP = *SC->GetVP();
		D3D11Mesh::VSCB_per_object.ActiveLightsCount = 0;
		iCtx->UpdateSubresource( D3D11Mesh::cb_VS_per_object, 0, NULL, &D3D11Mesh::VSCB_per_object, 0, 0 );

		memcpy( &D3D11Mesh::PSCB_per_object.Sun, &def_SunLight, 64 );
		iCtx->UpdateSubresource( D3D11Mesh::cb_PS_per_object, 0, NULL, &D3D11Mesh::PSCB_per_object, 0, 0 );

		PlanetMesh->Render( this, &mWorld, NULL, false );
	}

	if( fog )
		AP.HazeMode = 0;

	if( CloudData && CloudData->bCloudShadow )		//render cloud shadows on surface
		CloudData->CM->RenderShadow();

	if( CloudData && (CloudData->RenderMode & 2) )	//render clouds from above
		CloudData->CM->Render( true );

	if( HM )										//haze across surface
		HM->Render( &mWorld, true );

	if( RingData )
		RingData->RM->Render( &mWorld, RingData->res, true );

	if( SC->GetProxyBody() == obj )
		SC->RenderVesselShadows( this );
}

//atmosphere params
void vPlanet::InitLegacyAtmosphere() {
	VECTOR3 gpos = GPos;
	AP.SunAppRad = (float)( SunSize / length(pSun - gpos) );
	normalise( gpos );
	AP.SunDir.x = (float)gpos.x;
	AP.SunDir.y = (float)gpos.y;
	AP.SunDir.z = (float)gpos.z;
}

bool vPlanet::InitFogParams( bool bfog ) {
	float fogfactor = 0.0f;

	double h = max( 1.0, CDist - rad );	//cdist!

	VECTOR3 fogcol = Fog.col;
	double h_ref = Fog.alt_ref;
	double fog_0 = Fog.dens_0;
	double fog_ref = Fog.dens_ref;
	double scl = h_ref*fog_ref;

	if( h < h_ref )		fogfactor = (float)(h/h_ref*(fog_ref - fog_0) + fog_0 );
	else				fogfactor = (float)(scl/h);

	if( fogfactor < 0.0f )
		return false;
	else {
		double cosa = dotp( unit(GPos), unit( SC->GetCamPos() ) );	//cpos!
		double bright = 0.5*max( 0.0, min( 1.0, cosa + 0.3 ));

		AP.FogColor.r = (float)(bright*(min( 1.0, fogcol.x ) + 0.5 ) );
		AP.FogColor.g = (float)(bright*(min( 1.0, fogcol.y ) + 0.5 ) );
		AP.FogColor.b = (float)(bright*(min( 1.0, fogcol.z ) + 0.5 ) );
		AP.FogDensity = fogfactor;///dist_scale;
		AP.HazeMode = 2;
		return true;
	}
}