//////////////////////////////////////////////////////////////////////////////////////////
//
//  FILE        :  Asura_Direct3D_ShadowBuffer.cpp
//
//  CREATED     :  Richard J. Fine  (15.11.2004)
//
//  Copyright (C) 2004 Rebellion, All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////////////////


#include "Core/Asura.h"

// This file's header.
#include "Rendering/Asura_Direct3D_ShadowBuffer.h"

// Asura Platform-independent
#include "Animation/Asura_Animation.h"
#include "Rendering/Asura_Fog.h"
#include "Rendering/Asura_Module.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_ShadowBuffer.h"

// Asura PC
#include "Rendering/Asura_Direct3D_Caps.h"
#include "Rendering/Asura_Direct3D_ShadowBuffer_DepthTexture.h"
#include "Rendering/Asura_PlatformRenderEnvironment.h"

int Asura_Direct3D_ShadowBuffer::s_iNumInstancesCollectingShadows = 0;

//------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
//
// Asura_ShadowBuffer_System
//
////////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------------
void Asura_ShadowBuffer_System::PreAllocateBuffer(const Asura_Animation* pxAnim, BUFFERSIZE eBufferSize, float fScale)
{
}

Asura_Direct3D_ShadowBuffer* Asura_Direct3D_ShadowBuffer::s_pxCurrentShadowBuffer = NULL;

//------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
//
// Asura_Direct3D_ShadowBuffer
//
////////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
// Strategy-handling code
////////////////////////////////////////////////////////////////////////////////

void Asura_Direct3D_ShadowBuffer::SelectBufferStrategy()
{
	if(m_pxStrategy)
	{
		return;
	}

	// When we add support for floating-point textures, that would go here

	// Test for depth textures
	if(Asura_Direct3D_ShadowBufferStrategy_DepthTexture::IsSupported())
	{
		m_pxStrategy = new Asura_Direct3D_ShadowBufferStrategy_DepthTexture(this);
		return;
	}
	
	// no available strategies
	m_pxStrategy = 0;
	Asura_Assert(false, "No available shadowbuffer strategy");
}

bool Asura_Direct3D_ShadowBuffer::IsSupported()
{
	// KOR - currently requires modules to work
#ifndef ASURA_TARGET_SM3
	if(Asura_Direct3D_ShadowBufferStrategy_DepthTexture::IsSupported())
		return true;
#endif

	return false;
}

////////////////////////////////////////////////////////////////////////////////
// Strategy-independent code
////////////////////////////////////////////////////////////////////////////////

Asura_Direct3D_ShadowBuffer::Asura_Direct3D_ShadowBuffer(u_int uBufferWidth /* = DEFAULT_WIDTH */, u_int uBufferHeight /* = DEFAULT_HEIGHT */)
	: m_pxStrategy(NULL), m_eBufferStatus(BUFFER_UNUSED), m_bShadowTransformIsDirty(true), m_uModulesFound(0), m_pxAssociatedAnim(NULL)
{
	SelectBufferStrategy();

	m_uBufferWidth = uBufferWidth;
	m_uBufferHeight = uBufferHeight;

	// Set up a viewport for this buffer size
	m_xViewport.X = 0;
	m_xViewport.Y = 0;
	m_xViewport.Width = m_uBufferWidth;
	m_xViewport.Height = m_uBufferHeight;
	m_xViewport.MinZ = 0.0f;
	m_xViewport.MaxZ = 1.0f;

	D3DXMatrixIdentity(&m_xShadowViewportMatrix);
	m_xShadowViewportMatrix._11 = 0.5f;
	m_xShadowViewportMatrix._22 =  -0.5f;

	// _33 used to be (2^ZBufferBitDepth - 1) under DX8, but it changed for DX9
	// See http://developer.nvidia.com/object/General_FAQ.html#G5

	m_xShadowViewportMatrix._33 = 1.0f;
	//m_xShadowViewportMatrix._33 = m_pxStrategy->GetZScale();

	m_xShadowViewportMatrix._41 = (0.5f / (float)m_uBufferWidth) + 0.5f;
	m_xShadowViewportMatrix._42 = (0.5f / (float)m_uBufferHeight) + 0.5f;

}

Asura_Direct3D_ShadowBuffer::~Asura_Direct3D_ShadowBuffer()
{
	if(m_pxStrategy)
	{
		m_pxStrategy->ReleaseResources();
		delete m_pxStrategy;
	}
}

void Asura_Direct3D_ShadowBuffer::MakeClean()
{
	SelectBufferStrategy();
	m_pxStrategy->Clean();
	m_eBufferStatus = BUFFER_CLEAN;
}

void Asura_Direct3D_ShadowBuffer::SetLightPosition(const Asura_Vector_3& xLightPosition)
{
	// Light-view matrix
	m_xViewMatrix._11 =  1;					m_xViewMatrix._12 =  0;					m_xViewMatrix._13 =  0;					m_xViewMatrix._14 =  0;
	m_xViewMatrix._21 =  0;					m_xViewMatrix._22 =  0;					m_xViewMatrix._23 =  1;					m_xViewMatrix._24 =  0;
	m_xViewMatrix._31 =  0;					m_xViewMatrix._32 =  1;					m_xViewMatrix._33 =  0;					m_xViewMatrix._34 =  0;
	m_xViewMatrix._41 =  -xLightPosition.x;	m_xViewMatrix._42 =  -xLightPosition.z;	m_xViewMatrix._43 = -xLightPosition.y;	m_xViewMatrix._44 =  1;

	m_xLightPosition = xLightPosition;

	m_bShadowTransformIsDirty = true;
}

void Asura_Direct3D_ShadowBuffer::SetPerspectiveEnclosingSphere(const Asura_Vector_3& xSphereCenter, float fSphereRadius, float fDistance)
{
	// Assume that we already know the light position

	// We want to build a frustum that encloses the sphere described by xSphereCenter and fSphereRadius
	// i.e. the sphere touches the edges of the frustum

	float fDistanceToLight = (xSphereCenter - m_xLightPosition).Magnitude();

	// the fov is 2 arctan( radius / distance)
	
	float fFov = 2 * Asura_Maths::ArcTangent( fSphereRadius, fDistanceToLight );

	D3DXMatrixPerspectiveFovLH(&m_xProjectionMatrix, fFov, 1.f, fDistanceToLight - fSphereRadius, fDistance);

	m_bShadowTransformIsDirty = true;
}

void Asura_Direct3D_ShadowBuffer::RecalcShadowTransform()
{
	if(!m_bShadowTransformIsDirty)return;

	SelectBufferStrategy();

	// Shadow transform is lightview * lightproj
	D3DXMATRIX xViewProj;
	D3DXMatrixMultiply(&xViewProj, &m_xViewMatrix, &m_xProjectionMatrix);
	D3DXMatrixMultiply(&m_xShadowMatrix, &xViewProj, &m_xShadowViewportMatrix);
	//D3DXMatrixMultiply(&m_xShadowMatrix, &m_xViewMatrix, &m_xProjectionMatrix);

	// Also recalc the world-space shadow box
	D3DXVECTOR3 xExtents[8] = { D3DXVECTOR3(-1.0f, -1.0f, 0.0f),
								D3DXVECTOR3(-1.0f, -1.0f, 1.0f),
								D3DXVECTOR3(-1.0f,  1.0f, 0.0f),
								D3DXVECTOR3(-1.0f,  1.0f, 1.0f),
								D3DXVECTOR3( 1.0f, -1.0f, 0.0f),
								D3DXVECTOR3( 1.0f, -1.0f, 1.0f),
								D3DXVECTOR3( 1.0f,  1.0f, 0.0f),
								D3DXVECTOR3( 1.0f,  1.0f, 1.0f) };

	D3DXMatrixInverse(&xViewProj, NULL, &xViewProj);

	D3DXVECTOR3 xRes;
	D3DXVec3TransformCoord(&xRes, &xExtents[0], &xViewProj);
	m_xWorldSpaceShadowBox.SetToPoint(Asura_Vector_3(xRes));

	for(int i = 1; i < 8; ++i)
	{
		D3DXVec3TransformCoord(&xRes, &xExtents[i], &xViewProj);
		m_xWorldSpaceShadowBox.Set(Asura_Vector_3(xRes));
	}

	m_bShadowTransformIsDirty = false;
}

const D3DXMATRIX& Asura_Direct3D_ShadowBuffer::GetShadowSpaceTransform()
{
	RecalcShadowTransform();
	return m_xShadowMatrix;
}

const Asura_Bounding_Box& Asura_Direct3D_ShadowBuffer::GetWorldSpaceShadowBox()
{
	RecalcShadowTransform();
	return m_xWorldSpaceShadowBox;
}

#ifdef _DEBUG
void Asura_Direct3D_ShadowBuffer::DrawShadowBox()
{
	// Shadow box
	RecalcShadowTransform();
	Asura_Vector_3 xPos; xPos.Zero();
	Asura_Matrix_3x3 xOri; xOri.Identity();
	Asura_Render::RenderBB(m_xWorldSpaceShadowBox, xPos, xOri);

	// Light + lightLook
	D3DXVECTOR3 xVec[2] = { D3DXVECTOR3(0.0f, 0.0f, 0.0f),
							D3DXVECTOR3(0.0f, 0.0f, 3.0f), };
	D3DXMATRIX xInverse;
	D3DXMatrixInverse(&xInverse, NULL, &m_xViewMatrix);
	D3DXVec3TransformCoord(&xVec[0], &xVec[0], &xInverse);
	D3DXVec3TransformCoord(&xVec[1], &xVec[1], &xInverse);
	Asura_Render::RenderLine(Asura_Vector_3(xVec[0]), Asura_Vector_3(xVec[1]), 0xFFFFFFFF);

	// base plane
	Asura_Render::RenderLine(Asura_Vector_3(m_xWorldSpaceShadowBox.MinX, m_fBaseLevel, m_xWorldSpaceShadowBox.MinZ), Asura_Vector_3(m_xWorldSpaceShadowBox.MaxX, m_fBaseLevel, m_xWorldSpaceShadowBox.MinZ), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(m_xWorldSpaceShadowBox.MaxX, m_fBaseLevel, m_xWorldSpaceShadowBox.MinZ), Asura_Vector_3(m_xWorldSpaceShadowBox.MaxX, m_fBaseLevel, m_xWorldSpaceShadowBox.MaxZ), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(m_xWorldSpaceShadowBox.MaxX, m_fBaseLevel, m_xWorldSpaceShadowBox.MaxZ), Asura_Vector_3(m_xWorldSpaceShadowBox.MinX, m_fBaseLevel, m_xWorldSpaceShadowBox.MaxZ), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(m_xWorldSpaceShadowBox.MinX, m_fBaseLevel, m_xWorldSpaceShadowBox.MaxZ), Asura_Vector_3(m_xWorldSpaceShadowBox.MinX, m_fBaseLevel, m_xWorldSpaceShadowBox.MinZ), 0xFFFFFFFF);

	//frustum
	D3DXMatrixMultiply(&xInverse, &m_xViewMatrix, &m_xProjectionMatrix);
	D3DXMatrixInverse(&xInverse, NULL, &xInverse);

	D3DXVECTOR3 xF[8] = { D3DXVECTOR3(-1.0f, -1.0f, 0.0f),
						  D3DXVECTOR3( 1.0f, -1.0f, 0.0f),
						  D3DXVECTOR3( 1.0f,  1.0f, 0.0f),
						  D3DXVECTOR3(-1.0f,  1.0f, 0.0f),
						  D3DXVECTOR3(-1.0f, -1.0f, 1.0f),
						  D3DXVECTOR3( 1.0f, -1.0f, 1.0f),
						  D3DXVECTOR3( 1.0f,  1.0f, 1.0f),
						  D3DXVECTOR3(-1.0f,  1.0f, 1.0f) };
	for(int i = 0; i < 8; ++i)
	{
		D3DXVec3TransformCoord(&xF[i], &xF[i], &xInverse);
	}

	Asura_Render::RenderLine(Asura_Vector_3(xF[0]), Asura_Vector_3(xF[1]), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(xF[1]), Asura_Vector_3(xF[2]), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(xF[2]), Asura_Vector_3(xF[3]), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(xF[3]), Asura_Vector_3(xF[0]), 0xFFFFFFFF);

	Asura_Render::RenderLine(Asura_Vector_3(xF[4]), Asura_Vector_3(xF[5]), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(xF[5]), Asura_Vector_3(xF[6]), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(xF[6]), Asura_Vector_3(xF[7]), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(xF[7]), Asura_Vector_3(xF[4]), 0xFFFFFFFF);

	Asura_Render::RenderLine(Asura_Vector_3(xF[0]), Asura_Vector_3(xF[4]), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(xF[1]), Asura_Vector_3(xF[5]), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(xF[2]), Asura_Vector_3(xF[6]), 0xFFFFFFFF);
	Asura_Render::RenderLine(Asura_Vector_3(xF[3]), Asura_Vector_3(xF[7]), 0xFFFFFFFF);
}
#endif

void Asura_Direct3D_ShadowBuffer::FindAffectedModules()
{
	// Do a simple BB test against the module tree
	RecalcShadowTransform();

	Asura_Assert(s_pxCurrentShadowBuffer == NULL, "Something else is in the middle of testing still!");
	s_pxCurrentShadowBuffer = this;
	m_uModulesFound = 0;

	Asura_Module_System::TestModuleTree(m_xWorldSpaceShadowBox, FindAffectedModulesCallback);

	s_pxCurrentShadowBuffer = NULL;
}

void Asura_Direct3D_ShadowBuffer::FindAffectedModulesCallback(const u_int uModuleIndex)
{
	Asura_Assert(s_pxCurrentShadowBuffer, "Testing with no current shadow buffer set!");

	// if the module isn't modlinked in, ignore it
	if(!Asura_Module_System::IsModuleLinkedInForCurrentCamera(uModuleIndex))return;
	// how about a frustum cull, then?
	if(!Asura_Module_System::IsModuleInFrustum(uModuleIndex))return;

	if(s_pxCurrentShadowBuffer->m_uModulesFound < AFFECTED_MODULES_LIST_SIZE) 
	{
		// I guess it qualifies, then.
		// Add it to the list.
		s_pxCurrentShadowBuffer->m_ausModuleIDs[s_pxCurrentShadowBuffer->m_uModulesFound] = static_cast<u_short>(uModuleIndex);
		++s_pxCurrentShadowBuffer->m_uModulesFound;
	}
}

void Asura_Direct3D_ShadowBuffer::BeginCollectingShadows()
{
	// Set up our transform matrices and viewport

	D3DPERFCALL(BeginEvent(0xff84dd54, L"Render into shadow buffer"));

	RecalcShadowTransform();
	
	// Save out the old
	Asura_Direct3D::D3DDevice->GetViewport(&m_xOldViewport);
	m_xOldViewMatrix = Asura_Direct3D::GetViewMatrix();
	m_xOldProjectionMatrix = Asura_Direct3D::GetProjectionMatrix();

	// Bring in the new
	Asura_Direct3D::D3DDevice->SetViewport(&m_xViewport);
	Asura_Direct3D::SetCameraMatrices(m_xViewMatrix, m_xProjectionMatrix);

	SelectBufferStrategy();
	m_pxStrategy->BeginCollectingShadows();

#ifdef ASURA_ASSERT
	m_bIsCurrentlyRenderingToBuffer = true;
#endif
	m_eBufferStatus = BUFFER_DIRTY;

	s_iNumInstancesCollectingShadows++;
}

void Asura_Direct3D_ShadowBuffer::EndCollectingShadows()
{
	// we should have gotten a strategy when in BeginCollectingShadows
	Asura_Assert(m_bIsCurrentlyRenderingToBuffer, "Forgot to call BeginCollectingShadows");
	Asura_Assert(m_pxStrategy, "No strategy - should not happen");
	
	if(m_pxStrategy)m_pxStrategy->EndCollectingShadows();

	// restore old matrices
	Asura_Direct3D::D3DDevice->SetViewport(&m_xOldViewport);
	Asura_Direct3D::SetCameraMatrices(m_xOldViewMatrix, m_xOldProjectionMatrix);

	if (s_iNumInstancesCollectingShadows > 0)
	{
		s_iNumInstancesCollectingShadows--;
	}

	D3DPERFCALL(EndEvent());
}

void Asura_Direct3D_ShadowBuffer::RenderShadowsOnEnvironment(float fAlpha)
{
	Asura_Assert(m_pxStrategy, "No strategy - did you collect shadows?");

	m_pxStrategy->BeginRenderingShadows();

	// We need to do this for each camera
	for(int i = 0; i < Asura_Camera::GetNumberOfCameras(); ++i)
	{
		Asura_Camera::SetCameraIndex(i);
		Asura_Render::ApplyCameraSettings();

		// Calculate the distance-reduced alpha value for this shadow on this camera
		float fDistance = (m_xWorldSpaceShadowBox.CalculateCentre() - (*Asura_Camera::GetPosition())).Magnitude();

		// shadows should have been culled for distances greater than the zero-alpha level
		float fFadeValue = 1.0f - ((fDistance*Asura_Camera::GetLODMetric() - 20.0f) / 5.0f);
		fFadeValue = Asura_Maths::ClampToRange(fFadeValue, 0.0f, 1.0f);

		// also we might as well apply fog
		float fFogValue = Asura_Fog::GetFogDensityAtDistance(fDistance);
		fFadeValue *= (1.f - fFogValue);

		Asura_Direct3D_EnvRenderer::BeginEnvironmentShadows(GetShadowSpaceTransform(), m_fBaseLevel, 0.2f, fAlpha*fFadeValue );
		for(u_int u = 0; u < m_uModulesFound; ++u)
		{
			if(Asura_Module_System::IsModuleVisible(m_ausModuleIDs[u]))
			{
				Asura_Direct3D_EnvRenderer::RenderModuleShadows(m_ausModuleIDs[u]);
			}
		}
	}

	m_pxStrategy->EndRenderingShadows();
}

//------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
//
// Asura_Direct3D_ShadowBufferSystem
//
////////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------------

Asura_Direct3D_ShadowBuffer* Asura_Direct3D_ShadowBufferSystem::s_apxShadowBuffers[Asura_Direct3D_ShadowBufferSystem::NUMBER_OF_SHADOW_BUFFERS];

void Asura_Direct3D_ShadowBufferSystem::Initialise()
{
	memset(s_apxShadowBuffers, 0, sizeof(s_apxShadowBuffers));

	if(!Asura_Direct3D_ShadowBuffer::IsSupported()) return;

	// Allocate all shadow buffers
	for(u_int u = 0; u < NUMBER_OF_SHADOW_BUFFERS; ++u)
	{
		s_apxShadowBuffers[u] = new Asura_Direct3D_ShadowBuffer();
	}
}

void Asura_Direct3D_ShadowBufferSystem::PreReset()
{
	// Destroy strategies
	for(u_int u = 0; u < NUMBER_OF_SHADOW_BUFFERS; ++u)
	{
		if(s_apxShadowBuffers[u] && s_apxShadowBuffers[u]->m_pxStrategy)
		{
			delete s_apxShadowBuffers[u]->m_pxStrategy;
			s_apxShadowBuffers[u]->m_pxStrategy = NULL;
		}
	}
}

void Asura_Direct3D_ShadowBufferSystem::PostReset()
{
	// strategies will be refreshed as the buffers are used
}

void Asura_Direct3D_ShadowBufferSystem::Shutdown()
{
	for(u_int u = 0; u < NUMBER_OF_SHADOW_BUFFERS; ++u)
	{
		if(s_apxShadowBuffers[u]) delete s_apxShadowBuffers[u];
		s_apxShadowBuffers[u] = NULL;
	}
}

Asura_Direct3D_ShadowBuffer* Asura_Direct3D_ShadowBufferSystem::GetShadowBufferForAnim(const Asura_Animation* pxAnim, const Asura_Vector_3& xBoundingSphereCentre, float fSphereRadius)
{
	Asura_Assert(pxAnim, "Tried to get shadow buffer for a null anim.");

	if(!Asura_Direct3D_ShadowBuffer::IsSupported())return 0;

	// Track back till we get the uberparent of this anim
	while(pxAnim->GetParent())
	{
		pxAnim = pxAnim->GetParent();
	}

	// Check through our buffers. Is there already one dedicated to this uberparent?
	for(int i = 0; i < NUMBER_OF_SHADOW_BUFFERS; ++i)
	{
		if((!s_apxShadowBuffers[i]->IsUnused()) && (s_apxShadowBuffers[i]->GetAssociatedAnim() == pxAnim))
		{
			// it's this one
			return s_apxShadowBuffers[i];
		}
	}

	// Apparently not. Can we allocate one?
	for(int i = 0; i < NUMBER_OF_SHADOW_BUFFERS; ++i)
	{
		Asura_Direct3D_ShadowBuffer* xShadowBuffer = s_apxShadowBuffers[i];
		if(xShadowBuffer->IsUnused())
		{
			// yup, we can
			// Clean it
			xShadowBuffer->MakeClean();

			Asura_Vector_3 xBSCentre(pxAnim->GetBoundingSphereCentre());
			float fBSRadius = pxAnim->GetTightBoundingSphereRadius();
			if (fBSRadius < 0)
			{
				fBSRadius = 0.0f;
			}

			// Set up the light position as being five metres above the top of the bounding sphere
			Asura_Vector_3 xLightPosition = xBSCentre;
			xLightPosition.y -= fBSRadius + 5.0f;
			xShadowBuffer->SetLightPosition(xLightPosition);

			// Set up a perspective light projection
			xShadowBuffer->SetPerspectiveEnclosingSphere(xBSCentre, fBSRadius, fBSRadius * 2.0f + 8.0f);

			// Use the bottom of the bounding sphere as the base height
			xShadowBuffer->m_fBaseLevel = xBSCentre.y + fBSRadius;

			// Build the affected modules list
			xShadowBuffer->FindAffectedModules();

			// Mark the buffer as being set for this anim
			xShadowBuffer->m_pxAssociatedAnim = pxAnim;

			return xShadowBuffer;
		}
	}

	return 0;
}

void Asura_Direct3D_ShadowBufferSystem::BeginNewFrame()
{
	if(!Asura_Direct3D_ShadowBuffer::IsSupported())return;

	// scratch all buffers
	for(u_int u = 0; u < NUMBER_OF_SHADOW_BUFFERS; ++u)
	{
		s_apxShadowBuffers[u]->m_eBufferStatus = Asura_Direct3D_ShadowBuffer::BUFFER_UNUSED;
	}

}

void Asura_Direct3D_ShadowBufferSystem::FinishedCollectingShadows()
{
	if(!Asura_Direct3D_ShadowBuffer::IsSupported())return;

	D3DPERFCALL(BeginEvent(0xff887d6d, L"Render shadows onto environment"));

	// render all dirty buffers
	for(u_int u = 0; u < NUMBER_OF_SHADOW_BUFFERS; ++u)
	{
		Asura_Direct3D_ShadowBuffer* pxShadowBuffer = s_apxShadowBuffers[u];
		if(pxShadowBuffer->IsDirty())
		{
			pxShadowBuffer->RenderShadowsOnEnvironment(0.25f);
		}
	}

	D3DPERFCALL(EndEvent());
}
