#include "ShadowScene.h"

#include "ShadowApp.h"

#include "DirectionalLight.h"

ShadowScene::ShadowScene(ShadowApp* const pApp)
{
	m_pApp = pApp;
	m_VisibleMesh = NULL;

	m_pLight = new DirectionalLight(this);

	m_pSG = new SceneGraph();
	m_pSPT = NULL;

	m_LightAngX = 0.5f;
	m_LightAngZ = 0;

	m_pSkyVB = NULL;
	m_pSkyIB = NULL;
	m_pSkyTexture = NULL;
	m_pSkyFX = NULL;
	m_pSkyVDecl = NULL;

	CreateSkyBox();

	InitCamPositions();

	ResetCam();
}

ShadowScene::~ShadowScene(void)
{
	SAFERELEASE(m_pSkyTexture);
	SAFERELEASE(m_pSkyVB);
	SAFERELEASE(m_pSkyIB);
	
	delete m_pSkyVDecl;
	delete m_VisibleMesh;
	delete m_pSG;
	delete m_pLight;
	delete m_pSPT;

	m_Objects.Clear();
}

void ShadowScene::InitCamPositions(void)
{
	ZeroMemory(m_CamPos, sizeof(DXVector3) * CAMPOSITIONS);
	ZeroMemory(m_CamDir, sizeof(DXVector3) * CAMPOSITIONS);
	ZeroMemory(m_CamUp, sizeof(DXVector3) * CAMPOSITIONS);
	ZeroMemory(m_CamLightAngX, sizeof(float) * CAMPOSITIONS);
	ZeroMemory(m_CamLightAngZ, sizeof(float) * CAMPOSITIONS);
}

void ShadowScene::ResetCam(void)
{
	m_Cam.SetPosition(DXVector3(0, 0, 0));
	m_Cam.SetLookAt(DXVector3(0, 1, 0), DXVector3(0, 0, 1));
	m_Cam.SetPerspectiveProj(m_pApp->GetCurrentMode().Width, m_pApp->GetCurrentMode().Height, 1, 2000);

	m_DebugCam = m_Cam;
	m_DebugCam.SetPerspectiveProj(m_pApp->GetCurrentMode().Width, m_pApp->GetCurrentMode().Height, 1, 4000);
}

void ShadowScene::Update(void)
{
	DXVector4 v(50, 20, 300, 1);
	DXMatrix proj = m_Cam.GetProjMatrix();
	DXMatrix projinv = DXMatrix::InvertMatrix(proj);
	v = proj.Transform(v);
	
	float projz = v.z / v.w;

	v /= v.w;

	DXVector4 v0(-1, -1, projz, 1);
	DXVector4 v1(1, 1, projz, 1);

	v0 = projinv.Transform(v0);
	v1 = projinv.Transform(v1);

	float projinvvecw = projz * projinv._34 + projinv._44;

	DXVector3 f0 = DXVector3(v0.x, v0.y, v0.z) / v0.w;
	DXVector3 f1 = DXVector3(v1.x, v1.y, v1.z) / v1.w;

	float dx = (f1.x - f0.x) / m_pApp->GetCurrentMode().Width;
	float dy = (f1.y - f0.y) / m_pApp->GetCurrentMode().Height;


	DXVector4 vxy(0.33, 0.17, 0, 0);
	vxy = projinv.Transform(vxy);

	vxy.z = 1;
	vxy /= projinvvecw;

	DXVector3 vn1(vxy.x, vxy.y, vxy.z);
	vn1.Normalize();

	DXVector3 n(-0.4,-0.5,1);
	n.Normalize();

	DXPlane p(n.x, n.y, n.z, n.Dot(DXVector3(vxy.x, vxy.y, vxy.z)));

	//float3 viewvec0 = normalize(float3(vxy + float2(-dxyhalf, -dxyhalf), 1 / projw));
	DXVector3 viewvec0(vxy.x -dx/2, vxy.y -dy/2, 1 / projinvvecw);
	viewvec0.Normalize();

	//float t0 = -(psurf.w) / dot(viewvec0.xyz, psurf.xyz);
	float t0 = - p.d / viewvec0.Dot(p.GetNormal());

	//float3 p0 = t0 * viewvec0;
	DXVector3 p0 = t0 * viewvec0;

	/*float depth = tex2D(g_sDepth, fragcoord).r;
	float4 vp = float4(fragcoord * float2(2, -2) - float2(1, -1) + float2(-1, 1) / g_RTSize, depth, 1);
	float4 v = mul(vp, g_matInvViewProj);
	wpos = v.xyz/v.w;*/

 
	v = v; 

	if (!m_pSPT)
		return;

	//float a = m_Daytime * D3DX_PI * 2;
	float a = m_LightAngX * D3DX_PI * 2;
	float b = m_LightAngZ * D3DX_PI * 2;

	DXVector3 sundir(sin(a) * cos(b), sin(b) * sin(a), cos(a));
	DXColor suncolor(0.8f, 0.8f, 0.8f, 1);

	m_AmbientColor = DXColor(0.2f, 0.2f, 0.2f, 1);
	
	if(sundir.z > 0)
	{
		if(sundir.z > 0.2)
			suncolor *= 0;
		else
			suncolor *= 1 - sundir.z / 0.2f;
	}

	m_pLight->SetColor(suncolor);
	m_pLight->SetDirection(sundir);

	m_Cam.SetPerspectiveProj(m_pApp->GetCurrentMode().Width, m_pApp->GetCurrentMode().Height, m_Cam.GetNearPlane(), m_Cam.GetFarPlane());
	//m_Cam.SetPerspectiveProjFov(D3DX_PI / 3, m_pApp->GetCurrentMode().Width / (float)m_pApp->GetCurrentMode().Height, 1, 2000);
	m_DebugCam.SetPerspectiveProj(m_pApp->GetCurrentMode().Width, m_pApp->GetCurrentMode().Height, m_DebugCam.GetNearPlane(), m_DebugCam.GetFarPlane());

	m_VisibleBound.SetVolume(DXVector3(FLT_MAX, FLT_MAX, FLT_MAX), -DXVector3(FLT_MAX, FLT_MAX, FLT_MAX));
	
	if (m_pSPT->GetRoot())
		m_SceneBound = m_pSPT->GetRoot()->m_Bound;
	else
		m_SceneBound.SetVolume(DXVector3(FLT_MAX, FLT_MAX, FLT_MAX), -DXVector3(FLT_MAX, FLT_MAX, FLT_MAX));

	if (m_pApp->GetUpdateDynamicObjects() && m_pApp->GetDrawObjects())
		m_pSG->Update();

	ViewFrustum vf(m_Cam);
	Frustum f;
	for (int i = 0; i < 6; i++)
		f.planes.push_back(vf.ClipPlanes[i]);

	m_RL.Clear();
	m_pSPT->TraverseVisibleObjects(f, GatherDrawCalls, this);

	/*OnUpdate();

	if(m_Terrain)
	{
		m_Terrain->Update();
	}

	if(m_Config.DrawObjects)
	{
		FOREACH(obj, m_Objects, Object)
		{
			obj->GetInfo()->Update(m_Clock.AnimMultiplier());
		}

		if(!m_Config.DynamicObjects)
		{
			FOREACH(obj, m_AirPlanes, AirPlane)
			{
				obj->GetInfo()->Update(m_Clock.AnimMultiplier());
			}
		}
	}*/

	//update scene bounce, light volume

	//get V n S (Visible)

	UpdateVisibleMesh(m_Cam);
	m_pLight->Update();

		/*if(!m_Config.UseDeferred && m_UseDebugCam)
	{
		float currfar = m_ShadowFar;
		for(int i = 0; i < m_Config.MaxJitter; i++)
		{
			currfar = 67; 
			m_Cam.SetPerspectiveProj(GetCurrentMode().Width, GetCurrentMode().Height, 1, currfar);
 
			Frustum f(m_Cam);
			BoundingMesh b(f);
			b.ClipByAABB(m_VisibleBound);
			m_SM->Update(b.BuildAABB(), m_Cam, m_SunDir);
			UpdateShadowFarPlane(&b);

			//m_Cam.SetPerspectiveProj(GetCurrentMode().Width, GetCurrentMode().Height, 1, currfar);
		}
	}*/

	/*if(m_Terrain)
	{
		m_Terrain->UpdateShadow();
	}

	if(m_Config.DrawObjects)
	{
		FOREACH(obj, m_Objects, Object)
		{
			obj->GetInfo()->UpdateShadow();
		}

		//if(!m_Config.NoDynamicObjects)
		//{
		//	FOREACH(obj, m_AirPlanes, AirPlane)
		//	{
		//		obj->GetInfo()->Update(m_Clock.AnimMultiplier());
		//	}
		//}
	}*/
}

void ShadowScene::UpdateVisibleMesh(const Camera& cam)
{
	delete m_VisibleMesh;
	m_VisibleMesh = new BoundingMesh(ViewFrustum(cam));
	m_VisibleMesh->ClipByAABB(m_VisibleBound);
}

void ShadowScene::Render(void)
{
	m_RL.Sort();

	m_RL.SetCamera(m_pApp->GetActiveCam());
	//m_RL.SetCamera(m_Cam);
	m_RL.SetAmbientLight(m_AmbientColor);
	m_RL.SetTexAnisotropy(1);
	m_RL.SetTexFilter(TEXF_LINEAR);
	m_RL.SetLight(m_pLight->GetDirection(), m_pLight->GetColor());
	m_RL.SetSMBias(m_pApp->GetConstBias(), m_pApp->GetSlopeBias());
	m_RL.SetLambertScale(m_pApp->GetLambertScale());

	if(m_pApp->UseDeferred() && !m_pApp->GetShowSM())
	{ 
		m_pApp->RenderSceneDS();

		RenderSkyBox(true);
	}
	else
	{
		m_pLight->RenderShadowMap();

		if(!m_pApp->GetShowSM())
		{	
			m_RL.Render(m_pApp->GetDevice());
			
			if(m_pApp->GetUseDebugCam())
				RenderDebugCam(); 
			
			RenderSkyBox(false);
		}
	}
}

//void ShadowScene::RenderSceneDS(void)
//{
//}

void ShadowScene::MoveCam(void)
{
	Camera* const cam = m_pApp->GetUseDebugCam() ? &m_DebugCam : &m_Cam;

	if(m_pApp->GetDI()->GetMouseButton(MBTN_MID) & BUTTONPRESSED) //slide
    {
		float SlideScale = 0.5f;
        cam->MoveOriented(DXVector3(m_pApp->GetDI()->GetMouseDX() * SlideScale, -m_pApp->GetDI()->GetMouseDY() * SlideScale, 0));

		m_pApp->GetDI()->SetMousePos(m_pApp->m_MouseDownPos.x, m_pApp->m_MouseDownPos.y);
    }
    else if(m_pApp->GetDI()->GetMouseButton(MBTN_RIGHT) & BUTTONPRESSED) //rotate 
    {
		float RotScale = 1 / 200.0f;
        cam->Rotate(DXVector3(0, 0, -m_pApp->GetDI()->GetMouseDX() * RotScale));
        cam->RotateOriented(DXVector3(-m_pApp->GetDI()->GetMouseDY() * RotScale, 0, 0));

		m_pApp->GetDI()->SetMousePos(m_pApp->m_MouseDownPos.x, m_pApp->m_MouseDownPos.y);
    }
                
    float MoveScale = m_pApp->GetClock().AnimMultiplier() * 30;
	float WheelScale = 0.01f;

	if(m_pApp->GetDI()->GetKey(DIK_LCONTROL) & KEY_PRESS)
	{
		MoveScale *= 10;
		WheelScale *= 10;
	}    

	DXVector3 MoveDir(0, 0, 0);
	MoveDir.z = m_pApp->GetDI()->GetMouseDZ();
	
	if (m_pApp->GetDI()->GetKey(DIK_A) & KEY_PRESS)
        MoveDir.x = -1;
    else if (m_pApp->GetDI()->GetKey(DIK_D) & KEY_PRESS)
        MoveDir.x = 1;

    if (m_pApp->GetDI()->GetKey(DIK_W) & KEY_PRESS)
        MoveDir.y = -1;
    else if (m_pApp->GetDI()->GetKey(DIK_S) & KEY_PRESS)
        MoveDir.y = 1;

    cam->MoveOriented(DXVector3(MoveDir.x * MoveScale, 0, MoveDir.z * WheelScale + -MoveDir.y * MoveScale));
}

void ShadowScene::RenderDebugCam(void)
{
	m_pApp->DrawFrustum(m_Cam, 0xffffff);

	if(m_VisibleMesh)
		m_pApp->DrawBoundingMesh(*m_VisibleMesh, 0xff000ff);

	Camera c;
	c.SetViewMatrix(DXMatrix((float*)&(m_pApp->GetShadowMapping()->GetLightView())));
	c.SetProjMatrix(DXMatrix((float*)&(m_pApp->GetShadowMapping()->GetLightProj())));

	m_pApp->DrawFrustum(c, 0xffff00);

	m_pApp->DrawBoundingBox(m_pLight->GetShadowFrustum());
}

void ShadowScene::CreateSkyBox(void)
{
	D3DVERTEXELEMENT9 declSky[] =
    {
        { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },  
        D3DDECL_END()
    };
	m_pSkyVDecl = new VertexDeclaration(m_pApp->GetDevice(), declSky);


	m_pSkyFX = m_pApp->GetDevice()->GetShaderManager()->CreateFX("Shaders\\Skybox.fx");


	m_pSkyVB = m_pApp->GetDevice()->GetBufferManager()->CreateVB(sizeof(float)*3*8, 0, 0, POOL_MANAGED);
	m_pSkyIB = m_pApp->GetDevice()->GetBufferManager()->CreateIB(sizeof(WORD)*3*2*6, 0, false, POOL_MANAGED);

	float* vb = (float*)m_pSkyVB->Lock();

	float l = 1 / sqrt(3.0f); //normalized axis length of (1, 1, 1)

	*vb++ = -l;	*vb++= -l;	*vb++ = -l;
	*vb++ = l;	*vb++= -l;	*vb++ = -l;
	*vb++ = -l;	*vb++= l;	*vb++ = -l;
	*vb++ = l;	*vb++= l;	*vb++ = -l;

	*vb++ = -l;	*vb++= -l;	*vb++ = l;
	*vb++ = l;	*vb++= -l;	*vb++ = l;
	*vb++ = -l;	*vb++= l;	*vb++ = l;
	*vb++ = l;	*vb++= l;	*vb++ = l;

	m_pSkyVB->Unlock();


	WORD* ib = (WORD*)m_pSkyIB->Lock();

	//-z quad
    *ib++ = 0;	*ib++ = 2; *ib++ = 1;
	*ib++ = 1;	*ib++ = 2; *ib++ = 3;
    
    //-x quad
	*ib++ = 0;	*ib++ = 4; *ib++ = 2;
	*ib++ = 2;	*ib++ = 4; *ib++ = 6;
    
    //-y quad
	*ib++ = 0;	*ib++ = 1; *ib++ = 4;
	*ib++ = 4;	*ib++ = 1; *ib++ = 5;
    
    //+z quad
	*ib++ = 4;	*ib++ = 5; *ib++ = 6;
	*ib++ = 6;	*ib++ = 5; *ib++ = 7;
    
    //+x quad
	*ib++ = 1;	*ib++ = 3; *ib++ = 5;
	*ib++ = 5;	*ib++ = 3; *ib++ = 7;
    
    //+y quad
	*ib++ = 2;	*ib++ = 6; *ib++ = 3;
	*ib++ = 3;	*ib++ = 6; *ib++ = 7;
    
	m_pSkyIB->Unlock();
}

void ShadowScene::RenderSkyBox(const bool ds)
{
	if (!m_pApp->GetUseDebugCam() && m_pSkyTexture && m_pSkyVB && m_pSkyIB && m_pSkyVDecl && m_pSkyFX && m_pSkyFX->IsCompiled())
	{
		if (ds)
			m_pApp->m_DSDiffuse->GetDepthStencil()->StartRender();

		m_pApp->GetDevice()->SetStreamSource(0, m_pSkyVB->GetBuffer(), 0, sizeof(float)*3);
		m_pApp->GetDevice()->SetIndices(m_pSkyIB->GetBuffer());
		m_pApp->GetDevice()->SetVertexDeclaration(m_pSkyVDecl);

		m_pSkyFX->SetMatrix("g_matProj", m_pApp->GetActiveCam().GetProjMatrix());
		m_pSkyFX->SetMatrix("g_matView", m_pApp->GetActiveCam().GetViewMatrix());
		m_pSkyFX->SetFloatArray("g_LightDir", m_pApp->GetActiveScene()->GetLight()->GetDirection(), 3);
		m_pSkyFX->SetFloatArray("g_LightColor", m_pApp->GetActiveScene()->GetLight()->GetColor(), 4);
		m_pSkyFX->SetFloatArray("g_AmbientColor", m_AmbientColor, 4);
		m_pSkyFX->SetTexture("g_texSky", m_pSkyTexture); 
		m_pSkyFX->SetTechnique("RenderSky");

		RENDERFX(m_pSkyFX, 
			m_pApp->GetDevice()->DrawIndexedPrimitive(PT_TRIANGLELIST, 0, 0, 8, 0, 6 * 2);
		);

		if (ds)
			m_pApp->m_DSDiffuse->GetDepthStencil()->EndRender();
	}
}

void ShadowScene::UpdateTechnique(void)
{
	m_pSPT->TraverseTree(UpdateTechniqueCB, NULL);
}