#include "DirectionalLight.h"
#include "ShadowScene.h"
#include "ShadowApp.h"

DirectionalLight::DirectionalLight(ShadowScene* const pScene) : SceneObject(pScene)
{
	m_pSM = pScene->GetShadowApp()->GetShadowMapping();
	m_Color = DXColor(1, 1, 1, 1);
	m_Dir = DXVector3(0, 0, -1);
//	m_NumPlanes = 0;
	m_ExactBound = NULL;
	m_Universe = AABB(-DXVector3(FLT_MAX, FLT_MAX, FLT_MAX), DXVector3(FLT_MAX, FLT_MAX, FLT_MAX));
	m_PassesDone = 0;
}

DirectionalLight::~DirectionalLight(void)
{
	delete m_ExactBound;
}

void DirectionalLight::Update(void)
{
	m_PassesDone = 0;
	m_RLShadowCasters.Clear();

	UpdateShadowBound(m_pScene->GetCamera());

	m_pSM->Update(m_pScene->GetVisibleBound(), m_pScene->GetCamera(), m_Dir);	

	UpdateShadowFarPlane(m_pScene->GetVisibleMesh());

	/*Camera c;
	c.SetViewMatrix(m_pSM->GetLightView());
	c.SetProjMatrix(m_pSM->GetLightProj());

	m_LightFrustum.planes.clear();
	ViewFrustum vf(c);
	for (int i = 0; i < 6; i++)
		m_LightFrustum.planes.push_back(vf.ClipPlanes[i]);*/

	m_pScene->GetSpatialPartitioningTree()->TraverseVisibleObjects(m_LightFrustum, GatherShadowCasters,this);
}

void DirectionalLight::RenderShadowMap(void)
{
	RenderShadowMap(m_RLShadowCasters);
}

void DirectionalLight::RenderShadowMap(RenderList& rl)
{
	if(!m_pScene->GetShadowApp()->GetShowSM())
		//m_pSM->GetShadowMap()->StartRender(0);
		m_pSM->StartShadowMapRendering();
	else
		m_pSM->Jitter(m_pScene->GetShadowApp()->GetMaxJitter()); //to visulalize jitter when showsm

	m_pScene->GetShadowApp()->GetDevice()->Clear( D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0L );

	if (!m_pScene->GetShadowApp()->GetShowSM() && m_pSM->GetUseNVShadowMap())
	{
		if (!m_pSM->GetIDBufferEnable())
			m_pScene->GetShadowApp()->GetDevice()->SetColorWriteEnable(0);

		//do biasing old fashion way
		//float constbias = m_pScene->GetShadowApp()->GetConstBias() / 16777215.f; // 2^24 cause of 24 bit depth
		//float slopebias = m_pScene->GetShadowApp()->GetSlopeBias();
		//m_pScene->GetShadowApp()->GetDevice()->GetDevice()->SetRenderState(D3DRS_DEPTHBIAS, *(DWORD*)&constbias);
		//m_pScene->GetShadowApp()->GetDevice()->GetDevice()->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, *(DWORD*)&slopebias);
	}
	
	rl.Render(m_pScene->GetShadowApp()->GetDevice());

	if (!m_pScene->GetShadowApp()->GetShowSM() && m_pSM->GetUseNVShadowMap())
	{
		if (!m_pSM->GetIDBufferEnable())
			m_pScene->GetShadowApp()->GetDevice()->SetColorWriteEnable(D3DCOLORWRITEENABLE_ALPHA | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_RED);

		float z = 0;
		m_pScene->GetShadowApp()->GetDevice()->GetDevice()->SetRenderState(D3DRS_DEPTHBIAS, *(DWORD*)&z);
		m_pScene->GetShadowApp()->GetDevice()->GetDevice()->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, *(DWORD*)&z);
	}
		
	if(!m_pScene->GetShadowApp()->GetShowSM())
		//m_pSM->GetShadowMap()->EndRender();
		m_pSM->EndShadowMapRendering();

	m_PassesDone++;
}

void DirectionalLight::UpdateShadowBound(const Camera& cam)
{
	ViewFrustum viewFrustum(cam);

	////get V n S (Visible)
	//delete m_VisibleMesh;
	//m_VisibleMesh = new BoundingMesh(viewFrustum);
	//m_VisibleMesh->ClipByAABB(m_VisibleBound);

	Camera camLight;
	camLight.SetPosition(cam.GetPosition());  
	camLight.SetLookAt(cam.GetPosition() + m_Dir, cam.GetViewDirVector());

	BoundingMesh *vns = new BoundingMesh(*m_pScene->GetVisibleMesh());
	//transform points in lightspace
	vns->TransformPoints(camLight.GetViewProjMatrix());

	//build an OBB of the lightspace
	AABB aabbVnS = vns->BuildAABB();
	//this aabb is in lightspace
	OBB obbLVS(aabbVnS.GetMin(), aabbVnS.GetMax());
	DXMatrix matlvsinv = camLight.GetViewProjMatrix();
	matlvsinv.Invert();
	obbLVS.SetOrientation(matlvsinv);

	//add some offset in light direction, 
	obbLVS.SetVolume(obbLVS.GetMin() - DXVector3(0, 0, 1000), obbLVS.GetMax() + DXVector3(0, 0, 30));

	m_BoxLVS = obbLVS;

	//find view frustum planes on the oposite side of the light, needed to better cull shadow casters
	//m_NumPlanes = 0;
	m_LightFrustum.planes.clear();
	if (m_pScene->GetShadowApp()->GetShadowMethod() != SM_ZTILED)
	{
		for(int i=0; i < 6; i++)
		{
			if(viewFrustum.ClipPlanes[i].DotNormal(m_Dir) < 0)
			{
				m_LightFrustum.planes.push_back(viewFrustum.ClipPlanes[i]);
				//m_Bound[m_NumPlanes++] = DXPlane((float*)&viewFrustum.ClipPlanes[i]);
			}
		}
	}

	//also cull be shadowbound
	D3DXPLANE p;

	//D3DXPlaneFromPointNormal(&p, (D3DXVECTOR3*)&m_ShadowFarPoint, (D3DXVECTOR3*)&m_ShadowFarPlaneNormal);
	//m_AntiLightPlanes[m_NumAntiLightPlanes++] = p;

	DXMatrix invt;
	D3DXMatrixInverse((D3DXMATRIX*)&invt, NULL, (D3DXMATRIX*)&m_BoxLVS.GetOrientation());
	D3DXMatrixTranspose((D3DXMATRIX*)&invt, (D3DXMATRIX*)&invt);

	D3DXPlaneFromPointNormal(&p, (D3DXVECTOR3*)&m_BoxLVS.GetMax(), (D3DXVECTOR3*)&-DXVector3(1,0,0));
	D3DXPlaneTransform(&p, &p, (D3DXMATRIX*)&invt);
	//m_Bound[m_NumPlanes++] = DXPlane((float*)p);
	m_LightFrustum.planes.push_back(DXPlane((float*)p));

	D3DXPlaneFromPointNormal(&p, (D3DXVECTOR3*)&m_BoxLVS.GetMax(), (D3DXVECTOR3*)&-DXVector3(0,1,0));
	D3DXPlaneTransform(&p, &p, (D3DXMATRIX*)&invt);
	//m_Bound[m_NumPlanes++] = DXPlane((float*)p);
	m_LightFrustum.planes.push_back(DXPlane((float*)p));

	D3DXPlaneFromPointNormal(&p, (D3DXVECTOR3*)&m_BoxLVS.GetMax(), (D3DXVECTOR3*)&-DXVector3(0,0,1));
	D3DXPlaneTransform(&p, &p, (D3DXMATRIX*)&invt);
	//m_Bound[m_NumPlanes++] = DXPlane((float*)p);
	m_LightFrustum.planes.push_back(DXPlane((float*)p));

	
	D3DXPlaneFromPointNormal(&p, (D3DXVECTOR3*)&m_BoxLVS.GetMin(), (D3DXVECTOR3*)&DXVector3(1,0,0));
	D3DXPlaneTransform(&p, &p, (D3DXMATRIX*)&invt);
	//m_Bound[m_NumPlanes++] = DXPlane((float*)p);
	m_LightFrustum.planes.push_back(DXPlane((float*)p));

	D3DXPlaneFromPointNormal(&p, (D3DXVECTOR3*)&m_BoxLVS.GetMin(), (D3DXVECTOR3*)&DXVector3(0,1,0));
	D3DXPlaneTransform(&p, &p, (D3DXMATRIX*)&invt);
	//m_Bound[m_NumPlanes++] = DXPlane((float*)p);
	m_LightFrustum.planes.push_back(DXPlane((float*)p));

	D3DXPlaneFromPointNormal(&p, (D3DXVECTOR3*)&m_BoxLVS.GetMin(), (D3DXVECTOR3*)&DXVector3(0,0,1));
	D3DXPlaneTransform(&p, &p, (D3DXMATRIX*)&invt);
	//m_Bound[m_NumPlanes++] = DXPlane((float*)p);
	m_LightFrustum.planes.push_back(DXPlane((float*)p));

	delete vns;
}

void DirectionalLight::UpdateShadowFarPlane(const BoundingMesh* const b)
{
	//BoundingMesh shb(b->BuildAABB()); 
	BoundingMesh bb(*(m_pSM->GetB()));
	bb.TransformPoints(m_pSM->GetLightView() * m_pSM->GetLightProj());
	AABB h = bb.BuildAABB();
	OBB obbLightVolume(h.GetMin(), h.GetMax());
	DXMatrix matlvsinv = m_pSM->GetLightView() * m_pSM->GetLightProj();
	matlvsinv.Invert();
	obbLightVolume.SetOrientation(matlvsinv);

	BoundingMesh shb(obbLightVolume);

	//find far point of visible frustum from which a plane perpendicular to the lightdir can be set, this will be successively moved towards the camera
	//first calculate normal of plane
	m_ShadowFar = -FLT_MAX;
	if(m_pScene->GetCamera().GetViewDirVector().Dot(m_Dir) > 0) //farplane is
	{
		const DXVector3* p = shb.GetPoints();
		//just take farest point
		for(int i=0; i < shb.GetTriangleCount() * 3; i++)
		{
			const DXVector3 d = *p - m_pScene->GetCamera().GetPosition();
			float dz = m_pScene->GetCamera().GetViewDirVector().Dot(d);
			if(dz > m_ShadowFar)
			{
				m_ShadowFarPoint = *p;
				m_ShadowFar = dz;
			}
			p++;
		}
		//plane normal is -viewdir
		m_ShadowFarPlaneNormal = -m_pScene->GetCamera().GetViewDirVector();
	}
	else
	{
		//plane normal
		D3DXVec3Normalize((D3DXVECTOR3*)&m_ShadowFarPlaneNormal, 
			D3DXVec3Cross((D3DXVECTOR3*)&m_ShadowFarPlaneNormal, (D3DXVECTOR3*)&m_pScene->GetCamera().GetViewUpVector(), 
			D3DXVec3Cross((D3DXVECTOR3*)&m_ShadowFarPlaneNormal, (D3DXVECTOR3*)&m_pScene->GetCamera().GetViewDirVector(), (D3DXVECTOR3*)&m_Dir)));

		//transform points in plane space
		DXMatrix look;
		D3DXMatrixLookAtLH ((D3DXMATRIX*)&look, &D3DXVECTOR3(0,0,0), (D3DXVECTOR3*)&m_ShadowFarPlaneNormal, &D3DXVECTOR3(0,0,1));
		shb.TransformPoints(look);

		DXMatrix lookInv = look;
		lookInv.Invert();

		//find point with zmin
		const DXVector3* p = shb.GetPoints(); 

		float zmin = FLT_MAX;
		for(int i=0; i < shb.GetTriangleCount() * 3; i++)
		{
			if(zmin > (*p).z)
			{
				zmin = (*p).z;
				DXVector3 pw;
				D3DXVec3TransformCoord((D3DXVECTOR3*)&pw, (D3DXVECTOR3*)p, (D3DXMATRIX*)&lookInv);

				m_ShadowFarPoint = pw;
				const DXVector3 d = pw - m_pScene->GetCamera().GetPosition();
				m_ShadowFar = m_pScene->GetCamera().GetViewDirVector().Dot(d);
			}
			p++;
		}

		//iterate all points and check if none is culled
		/*for(int i=0; i < shb.GetTriangleCount() * 3; i++, p++)
		{
			D3DXPLANE pl;
			D3DXPlaneFromPointNormal(&pl, (D3DXVECTOR3*)p, m_ShadowFarPlaneNormal);

			const DXVector3* p2 = shb.GetPoints();
			int j = 0;
			for(j; j < shb.GetTriangleCount() * 3; j++, p2++)
			{
				if(j == i)
					continue;

				if(D3DXPlaneDotCoord(&pl, (D3DXVECTOR3*)p2) < 0)
					break;
			}

			//point found
			if(j == shb.GetTriangleCount() * 3)
			{
				m_ShadowFarPoint = *p;
				m_ShadowFar = (m_ShadowFarPoint - m_Cam.GetPosition()).Dot(m_Cam.GetViewDirVector());
				break; 
			} 
		}*/
	}
}