#include "vObject.h"

vBase::vBase( vPlanet *_PLN, OBJHANDLE _planet, OBJHANDLE obj ) : vObject( obj ) {
	DWORD j;

//	PLN = _PLN;
//	planet = _planet;

	tspec = NULL;
	tilemesh = NULL;
	shadow_mesh = NULL;
	structs_bs = structs_as = NULL;
	nstructs_bs = nstructs_as = nshadow_mesh = 0;
	nshmesh	= 0;
	PLN = _PLN;
	planet = _planet;

	//load surface tiles:
	ntile = gc->GetBaseTileList( obj, &tspec );

	if( ntile ) {
		MESHGROUPEX **grps = new MESHGROUPEX *[ntile];
		SURFHANDLE *texs = new SURFHANDLE [ntile];

		for( j = 0; j < ntile; j++ ) {
			DWORD ng = oapiMeshGroupCount( tspec[j].mesh );
			if( ng == 1 ) {		//tiles should have 1 group
				grps[j] = oapiMeshGroupEx( tspec[j].mesh, 0 );
				texs[j] = tspec[j].tex;
			}
			else
				oapiWriteLog( "Incorrect number of base tile groups" );
		}
		tilemesh = new D3D11Mesh( ntile, grps, texs );
	}

	//load meshes for structures
	MESHHANDLE *sbs, *sas;
	DWORD nsbs, nsas;

	gc->GetBaseStructures( obj, &sbs, &nsbs, &sas, &nsas );

	if( nstructs_bs = nsbs ) {
		structs_bs = new D3D11Mesh *[nstructs_bs];
		for( j = 0; j < nstructs_bs; j++ )
			structs_bs[j] = new D3D11Mesh( sbs[j], true );
	}

	if( nstructs_as = nsas ) {
		structs_as = new D3D11Mesh *[nstructs_as];
		for( j = 0; j < nstructs_as; j++ )
			structs_as[j] = new D3D11Mesh( sas[j], true );
	}

	SetupShadowMeshes();

	p_night = false;
	//setup lights !
}

vBase::~vBase() {
	if( tilemesh )
		delete tilemesh;
	if( structs_bs ) {
		for( DWORD j = 0; j < nstructs_bs; j++ )
			delete structs_bs[j];
		delete [ ] structs_bs;
	}
	if( structs_as ) {
		for( DWORD j = 0; j < nstructs_as; j++ )
			delete structs_as[j];
		delete [ ] structs_as;
	}
	if( shadow_mesh ) {
		for( DWORD j = 0; j < nshmesh; j++ ) {
			REL( shadow_mesh[j].VB );
			REL( shadow_mesh[j].IB );
		}
		delete [ ] shadow_mesh;
	}
}

void vBase::SetupShadowMeshes() {
	nshmesh = 0;

	//get base shadow geometry
	DWORD ngrp, nssh, j, i, k, m, nmesh;
	MESHHANDLE *ssh;
	double *ecorr;

	gc->GetBaseShadowGeometry( obj, &ssh, &ecorr, &nssh );

	if( !nssh )
		return;

	struct EGROUP {
		MESHHANDLE *mesh;
		DWORD
			nmesh,
			nvtx,
			nidx;
		int bin;
	} *eg;

	const double d_ecorr = 0.2;

	for( i = 0; i < nssh; i++ ) {
		int bin = (int)(ecorr[i]/d_ecorr);

		for( j = 0; j < nshmesh; j++ )
			if( bin == eg[j].bin )
				break;

		if( j == nshmesh ) {
			EGROUP *tmp = new EGROUP [nshmesh+1];
			if( nshmesh ) {
				memcpy( tmp, eg, sizeof(EGROUP)*nshmesh );
				delete [ ] eg;
			}
			eg = tmp;
			eg[nshmesh].nmesh = eg[nshmesh].nvtx = eg[nshmesh].nidx = 0;
			eg[nshmesh].bin = bin;
			nshmesh++;
		}

		nmesh = eg[j].nmesh;
		MESHHANDLE *tmp = new MESHHANDLE [nmesh+1];
		if( nmesh ) {
			memcpy( tmp, eg[j].mesh, sizeof(MESHHANDLE)*nmesh );
			delete [ ] eg[j].mesh;
		}
		eg[j].mesh = tmp;
		eg[j].mesh[nmesh] = ssh[i];
		ngrp = oapiMeshGroupCount( ssh[i] );
		for( k = 0; k < ngrp; k++ ) {
			MESHGROUP *grp = oapiMeshGroup( ssh[i], k );
			if( grp ) {
			//	if( grp->UsrFlag & 1 )	//"no shadows" flag
			//		continue;
				eg[j].nvtx += grp->nVtx;
				eg[j].nidx += grp->nIdx;
			}
		}
		eg[j].nmesh++;
	}

	shadow_mesh = new ShadowMesh [nshmesh];

	D3DVECTOR *vtx;
	WORD *idx;
	D3D11_BUFFER_DESC bdesc;
	D3D11_SUBRESOURCE_DATA sdata;
	
	for( i = 0; i < nshmesh; i++ ) {
		vtx = new D3DVECTOR [eg[i].nvtx];
		idx = new WORD [eg[i].nidx];

		shadow_mesh[i].nidx = 0;
		shadow_mesh[i].nvtx = 0;
		shadow_mesh[i].ecorr = (eg[i].bin - 0.5)*d_ecorr;

		for( j = 0; j < eg[i].nmesh; j++ ) {
			MESHHANDLE mesh = eg[i].mesh[j];
			ngrp = oapiMeshGroupCount( mesh );

			for( k = 0; k < ngrp; k++ ) {
				MESHGROUP *grp = oapiMeshGroup( mesh, k );
			//	if( grp->UsrFlag & 1 )	//"no shadows" flag
			//		continue;

				D3DVECTOR *vtgt = vtx + shadow_mesh[i].nvtx;
				WORD *itgt = idx + shadow_mesh[i].nidx;

				NTVERTEX *vsrc = grp->Vtx;
				WORD *isrc = grp->Idx;

				WORD iofs = (WORD)shadow_mesh[i].nvtx;
				for( m = 0; m < grp->nVtx; m++ ) {
					vtgt[m].x = vsrc[m].x;
					vtgt[m].y = vsrc[m].y;
					vtgt[m].z = vsrc[m].z;
				}

				for( m = 0; m < grp->nIdx; m++ )
					*itgt++ = *isrc++ + iofs;

				shadow_mesh[i].nvtx += grp->nVtx;
				shadow_mesh[i].nidx += grp->nIdx;
			}
		}
		
		ZeroMemory( &bdesc, sizeof(bdesc) );
		bdesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
		bdesc.ByteWidth = 12*shadow_mesh[i].nvtx;
		bdesc.CPUAccessFlags = 0;
		bdesc.MiscFlags = 0;
		bdesc.StructureByteStride = 0;
		bdesc.Usage = D3D11_USAGE_IMMUTABLE;

		ZeroMemory( &sdata, sizeof(sdata) );
		sdata.pSysMem = vtx;

		HR( Dev->CreateBuffer( &bdesc, &sdata, &shadow_mesh[i].VB ) );

		ZeroMemory( &bdesc, sizeof(bdesc) );
		bdesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
		bdesc.ByteWidth = 2*shadow_mesh[i].nidx;
		bdesc.CPUAccessFlags = 0;
		bdesc.MiscFlags = 0;
		bdesc.StructureByteStride = 0;
		bdesc.Usage = D3D11_USAGE_IMMUTABLE;

		ZeroMemory( &sdata, sizeof(sdata) );
		sdata.pSysMem = idx;

		HR( Dev->CreateBuffer( &bdesc, &sdata, &shadow_mesh[i].IB ) );

		delete [ ] vtx;
		delete [ ] idx;
	}

	for( i = 0; i < nshmesh; i++ )
		delete [ ] eg[i].mesh;
	delete [ ] eg;
}

void vBase::Update() {
	static double csun_lights = RAD;

	vObject::SaveData();
	if( !active )	return;

	vObject::Update();

	//oapi functions:
	oapiGetGlobalPos( planet, &RP.pp );		// planet global pos
	oapiGetGlobalPos( obj, &RP.sd );		// base global pos
	oapiGetRotationMatrix( obj, &RP.vR );

	static double nxtsimt = 0.0;
	double simt = oapiGetSimTime();
	if( simt > nxtsimt ) {
		SunLight = D3D11Mesh::WhiteSunLight;

		VECTOR3
			S = pSun - GPos,
			P = unit( GPos - PLN->GPos );

		float s = (float)length( S );
		float
			rs = float(SunSize)/s,
			h = float( dotp( S, P ) )/s,
			d = 0.173f,
			ae = 0.242f,
			aq = 0.342f,
			amb0 = 0.0f,
			disp = 0.0f,
			amb = 0.0f;

		const ATMCONST *atmc = PLN->atmc;

		if( atmc ) {			
			amb0 = min( 0.7f, log( float( atmc->rho0 ) + 1.0f )*0.4f );
			disp = max( 0.02f, min( 0.9f, log( float( atmc->rho0 ) + 1.0f ) ) );
		}

		D3DXVECTOR3	_one = D3DXVECTOR3( 1, 1, 1 );
		D3DXVECTOR3
			lcol,
			r0 = _one - D3DXVECTOR3( 0.4f, 0.65f, 1.0f )*disp;

		if( atmc ) {
			lcol = ( r0 + ( _one - r0 )*saturate(h/d))*saturate((h+rs)/(2.0f*rs));
			amb = saturate( (h+ae)/aq );
			amb = saturate( max( amb0*amb - 0.05f, fAmbient ) );
			lcol *= 1.0f - amb*0.5f;		// reduce direct light component to avoid overexposure
		}
		else {
			lcol = r0 * saturate( (h+rs)/(2.0f*rs) ); 
			amb  = fAmbient;
			lcol *= 1.0f - amb*0.5f;
		}

		SunLight.ambient = D3DXCOLOR( amb, amb, amb, 1.0f );
		SunLight.diffuse = SunLight.specular = D3DXCOLOR( lcol.x, lcol.y, lcol.z, 1.0f );
		SunLight.SunDir = PLN->AP.SunDir;

		nxtsimt = simt + 0.5f;

		VECTOR3 sdir, pos = GPos;
		normalise( pos );
		sdir = tmul( mROT, -pos );
		double csun = sdir.y;
		bool night = csun < csun_lights;
		if( p_night != night ) {
			DWORD j;
			for( j = 0; j < nstructs_bs; j++ )
				structs_bs[j]->SetTextureMix( 1, night ? 1.0f : 0.0f );
			for( j = 0; j < nstructs_as; j++ )
				structs_as[j]->SetTextureMix( 1, night ? 1.0f : 0.0f );
			p_night = night;
		}
	}
}

void vBase::RenderSurface() {
	if( !active )	return;

	if( tilemesh ) {
		memcpy( &D3D11Mesh::PSCB_per_object.Sun, &SunLight, 64 );
		iCtx->UpdateSubresource( D3D11Mesh::cb_PS_per_object, 0, NULL, &D3D11Mesh::PSCB_per_object, 0, 0 );

		tilemesh->RenderBaseTiles( this, &mWorld );
	}
}

void vBase::RenderStructuresBS() {
	if( !active )	return;

	if( nstructs_bs ) {
		memcpy( &D3D11Mesh::VSCB_per_object.Sun, &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, &SunLight, 64 );
		iCtx->UpdateSubresource( D3D11Mesh::cb_PS_per_object, 0, NULL, &D3D11Mesh::PSCB_per_object, 0, 0 );

		for( UINT j = 0; j < nstructs_bs; j++ )
			structs_bs[j]->Render( this, &mWorld );
	}
}

void vBase::RenderStructuresAS() {
	if( !active )	return;

	if( nstructs_as ) {
		memcpy( &D3D11Mesh::VSCB_per_object.Sun, &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, &SunLight, 64 );
		iCtx->UpdateSubresource( D3D11Mesh::cb_PS_per_object, 0, NULL, &D3D11Mesh::PSCB_per_object, 0, 0 );
		
		for( UINT j = 0; j < nstructs_as; j++ )
			structs_as[j]->Render( this, &mWorld );
	}
}

void vBase::InitRenderShadows() {
	iCtx->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
	iCtx->IASetInputLayout( D3D11Mesh::IL_D3DXVECTOR3 );
	iCtx->VSSetShader( D3D11Mesh::VS_Shadow, NULL, NULL );
	iCtx->VSSetConstantBuffers( 0, 1, &cb_D3DXMATRIX_x1 );
	iCtx->PSSetShader( D3D11Mesh::PS_Shadow, NULL, NULL );
	iCtx->PSSetConstantBuffers( 0, 1, &cb_D3DXVECTOR4 );
	iCtx->OMSetDepthStencilState( D3D11Mesh::DSS_Shadow, 1 );
	iCtx->RSSetState( RS_CullBack_Solid );
}

void vBase::RenderShadows() {
	//same for vessel shadows
	if( !active )	return;
	if( !nstructs_as )	return;

	float alpha = 0.5f;

	static const double eps = 1e-2, shadow_elev_limit = 0.07;
	double d = PLN->rad, alt;

	VECTOR3 sd = GPos, pvr;
	pvr = sd - PLN->GPos;
	normalise( pvr );
	pvr *= PLN->rad;
	sd = PLN->GPos - pvr;

	normalise( sd );

	// calculate the intersection of the vessel's shadow with the planet surface
	double fac1 = dotp( sd, pvr );
	if( fac1 > 0.0 )				return;		// shadow doesn't intersect planet surface	
	double csun = -fac1/d;						// sun elevation above horizon
	if( csun < shadow_elev_limit )	return;		// sun too low to cast shadow
	double arg  = fac1*fac1 - ( dotp( pvr, pvr ) - PLN->rad*PLN->rad );
	if( arg <= 0.0 )				return;		// shadow doesn't intersect with planet surface
	double a = -fac1 - sqrt(arg);

	VECTOR3 sdv = tmul( mROT, sd );	// projection direction in vessel frame
	VECTOR3 shp = sdv*a;			// projection point
	VECTOR3 hnp = sd*a + pvr;
	normalise( hnp );				// horizon normal
	VECTOR3 hn = tmul( mROT, hnp );	// horizon normal in vessel frame

	// perform projections
	double nr0	= dotp( hn, shp );
	double nd	= dotp( hn, sdv );
	VECTOR3 sdvs = sdv/nd;

	D3DXMATRIX mProj, mProjWorld, mProjWorldShift;

	mProj._11 = 1.0f - (float)(sdvs.x*hn.x);
	mProj._12 =      - (float)(sdvs.y*hn.x);
	mProj._13 =      - (float)(sdvs.z*hn.x);
	mProj._14 = 0;
	mProj._21 =      - (float)(sdvs.x*hn.y);
	mProj._22 = 1.0f - (float)(sdvs.y*hn.y);
	mProj._23 =      - (float)(sdvs.z*hn.y);
	mProj._24 = 0;
	mProj._31 =      - (float)(sdvs.x*hn.z);
	mProj._32 =      - (float)(sdvs.y*hn.z);
	mProj._33 = 1.0f - (float)(sdvs.z*hn.z);
	mProj._34 = 0;
	mProj._41 =        (float)(sdvs.x*nr0);
	mProj._42 =        (float)(sdvs.y*nr0);
	mProj._43 =        (float)(sdvs.z*nr0);
	mProj._44 = 1;

	D3DXMatrixMultiply( &mProjWorld, &mProj, &mWorld );

	float scale = float( min( 1, ( csun - 0.07 )/0.015 ) );
	if( scale < 1 )		alpha *= scale;

	D3DXMATRIX mProjWorldOfs;
	for( DWORD j = 0; j < nstructs_as; j++ )
		structs_as[j]->RenderShadows( &mProjWorld, 0.5f );

/*
	if( !nshmesh )	return;

	static const double shadow_elev_limit = 0.07;

	double d, csun, nr0;
	VECTOR3 pvr;

	pvr = RP.sd - RP.pp;				// planet-relative base position
	d = length( pvr );					// planet radius at base location
	normalise( RP.sd );					// shadow projection direction

	double fac1 = dotp( RP.sd, pvr );
	if( fac1 > 0.0 )					// base is on planet night-side
		return;
	csun = -fac1/d;						// sun elevation above horizon
	if( csun < shadow_elev_limit )		// sun too low to cast shadow
		return;

	VECTOR3
		sdv = tmul( RP.vR, RP.sd ),		// projection direction in base frame
		hnp = pvr;
	normalise( hnp );
	VECTOR3 hn = tmul( RP.vR, hnp );	// horizon normal in vessel frame

	// perform projections
	double nd = dotp( hn, sdv );
	VECTOR3 sdvs = sdv/nd;
	if( !sdvs.y )						// required for plane offset correction
		return;

	DWORD i;
	const UINT
		D3DVECTORStride = 12,
		VBOffset = 0;

	// build shadow projection matrix
	D3DXMATRIX mProj, mProjWorld, mProjWorldShift;
	mProj._11 = (float)(1.0 - sdvs.x*hn.x);
	mProj._12 = (float)(    - sdvs.y*hn.x);
	mProj._13 = (float)(    - sdvs.z*hn.x);
	mProj._14 = 0;
	mProj._21 = (float)(    - sdvs.x*hn.y);
	mProj._22 = (float)(1.0 - sdvs.y*hn.y);
	mProj._23 = (float)(    - sdvs.z*hn.y);
	mProj._24 = 0;
	mProj._31 = (float)(    - sdvs.x*hn.z);
	mProj._32 = (float)(    - sdvs.y*hn.z);
	mProj._33 = (float)(1.0 - sdvs.z*hn.z);
	mProj._34 = 0;
	mProj._41 = 0;
	mProj._42 = 0;
	mProj._43 = 0;
	mProj._44 = 1.0f;

	D3DXMatrixMultiply( &mProjWorld, &mProj, &mWorld );
	mProjWorldShift = mProjWorld;
//	memcpy( );	later
	
	float scale = float( min( 1, (csun - 0.07)/0.015) );
	if( scale < 1 )
		alpha *= scale;

	D3DXVECTOR4 v( alpha, 0.5f, 0.5f, 0.5f );
	iCtx->UpdateSubresource( cb_D3DXVECTOR4, 0, NULL, &v, 0, 0 );

	for( i = 0; i < nshmesh; i++ ) {

		// add shadow plane offset to transformation
		nr0 = shadow_mesh[i].ecorr/sdvs.y;
		mProjWorldShift._41 = mProjWorld._41 + (float)( nr0*( sdvs.x*mWorld._11 + sdvs.y*mWorld._21 + sdvs.z*mWorld._31 ) );
		mProjWorldShift._42 = mProjWorld._42 + (float)( nr0*( sdvs.x*mWorld._12 + sdvs.y*mWorld._22 + sdvs.z*mWorld._32 ) );
		mProjWorldShift._43 = mProjWorld._43 + (float)( nr0*( sdvs.x*mWorld._13 + sdvs.y*mWorld._23 + sdvs.z*mWorld._33 ) );

		mProjWorldShift = mWorld;
		mProjWorldShift *= *SC->GetVP();

		iCtx->UpdateSubresource( cb_D3DXMATRIX_x1, 0, NULL, &mProjWorldShift, 0, 0 );

		iCtx->IASetVertexBuffers( 0, 1, &shadow_mesh[i].VB, &D3DVECTORStride, &VBOffset );
		iCtx->IASetIndexBuffer( shadow_mesh[i].IB, DXGI_FORMAT_R16_UINT, 0 );

		iCtx->DrawIndexed( shadow_mesh[i].nidx, 0, 0 );
	}*/
}