/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include <cmath>
		#include "gpu/gpu_renderer.h"
		#include "core/light.h"
		#include "log/log.h"

		using namespace GS::Core;
		using namespace GS::GPU;


struct	UVOffset
{ float x, y; };

//------------------------------------------------------------------------------
void				Renderer::CreateShadowMaps()
{
	gpu_config.enable_shadow = registry.GetBool("ShadowMapping:Enable", true);
	gpu_config.shadow_size = registry.GetInteger("ShadowMapping:Size", 1024);

	__LOG__ << "Creating shadow maps (" << gpu_config.shadow_size << "x" << gpu_config.shadow_size << ").\n";

	if	(gpu_config.enable_shadow)
	{
		shadow_map = NewTexture("shadow_map");
		shadow_map->Create(NULL, gpu_config.shadow_size, gpu_config.shadow_size, Render::Texture::FormatDepth, Render::Texture::NoAA, Render::Texture::Usage(Render::Texture::IsRenderTarget | Render::Texture::IsShaderResource));
		shadow_map->ConfigureAsShadowMap();

		shadow_map_fbo->SetDepthTexture(shadow_map);
	}
}
void				Renderer::FreeShadowMaps()
{
	shadow_map = NULL;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
struct	ShadowRenderData : public Light::RenderData::ShadowRenderData
{
		Renderer::ShadowMapSplit split;
};
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static float		ComputeSplitZBoundary(const Camera &lod_view, const Light &light, int index, int count)
{
	if	(index <= 0)
		return lod_view.GetNearClippingPlane();

	float k = float(index) / count;

	float z_log = lod_view.GetNearClippingPlane() * powf(light.shadow_range / lod_view.GetNearClippingPlane(), k);
	float z_lin = lod_view.GetNearClippingPlane() + (light.shadow_range - lod_view.GetNearClippingPlane()) * k;

	return z_log * light.shadow_distribution + z_lin * (1.f - light.shadow_distribution);
}
static void			FitViewToFrustum(Camera &view, const GS::Frustum &slice, float d)
{
	using namespace GS;	

	/*
		Compute split view item properties so that the split view frustum act as
		a perfect fit for the split frustum.
	*/
	const Vector4 *fv = slice.GetVertices();

	Vector4 vfv[8];
	view.GetInverseMatrix().Apply(vfv, fv, 8);
	const Matrix4 &m = view.GetMatrix();

	Vector4 mm[2];
	mm[0] = mm[1] = vfv[0];
	for (int n = 1; n < 8; ++n)
	{
		mm[0] = Vector4::Minimum(mm[0], vfv[n]);
		mm[1] = Vector4::Maximum(mm[1], vfv[n]);
	}
	Vector4 focus = ((mm[0] + mm[1]) * 0.5) * m;

	// Position the shadow map light item,
	view.SetPosition(focus - m.GetRow(2) * d);

	// View clipping planes.
	Vector4 view_front = m.GetRow(2);
	view.z_far = view_front.Dot(fv[0] - view.GetPosition());
	for (int n = 1; n < 8; ++n)
		view.z_far = GS::Types::Max(view.z_far, view_front.Dot(fv[n] - view.GetPosition()));

	// View dimensions.
	Vector4 view_left = view.GetMatrix().GetRow(0),
			view_top = view.GetMatrix().GetRow(1);

	Vector4 dt = fv[0] - view.GetPosition();
	float h_width = std::fabs(view_left.Dot(dt)),
			h_height = std::fabs(view_top.Dot(dt));

	for (int n = 1; n < 8; ++n)
	{
		dt = fv[n] - view.GetPosition();
		h_width = GS::Types::Max <float> (h_width, std::fabs(view_left.Dot(dt)));
		h_height = GS::Types::Max <float> (h_height, std::fabs(view_top.Dot(dt)));
	}
	view.ortho_w = h_width * 2.f;
	view.ortho_h = h_height * 2.f;
}
bool				Renderer::LightPreparePSSM(const Light &l, bool build_dlist) const
{
	Light::RenderData *light_render_data = (Light::RenderData *)l.render_data.c_ptr();
	if	(!light_render_data || !light_render_data->shadow_data.Allocate(4))
		return false;

	int split_count = registry.GetInteger("ShadowMapping:PSSM:Split", 3);

	for	(int n = 0; n < split_count; ++n)
	{
		Light::RenderData::ShadowData &data = light_render_data->shadow_data[n];

		if	(data.render_data.IsNull())
			data.render_data = new ShadowRenderData;
		ShadowRenderData *pssm_data = (ShadowRenderData *)data.render_data.c_ptr();

		ShadowMapSplit &split = pssm_data->split;

		static UVOffset split_offset[] = { { 0.0, 0.0 }, { 0.5, 0.0 }, { 0.0, 0.5 }, { 0.5, 0.5 } };
		split.rect = fRect::FromWidthHeight(split_offset[n].x * gpu_config.shadow_size, split_offset[n].y * gpu_config.shadow_size, gpu_config.shadow_size / 2.f, gpu_config.shadow_size / 2.f);

		// Compute frustum slice.
		Frustum slice;
		float znear = ComputeSplitZBoundary(*view_item, l, n, split_count), zfar = ComputeSplitZBoundary(*view_item, l, n + 1, split_count);
		view_item->ComputeFrustum(slice, GetViewport(), znear, zfar);

		// Adjust view item.
		split.view.is_orthographic = true;
		split.view.aspect_ratio = 1;
		split.view.SnapshotTransformation(l.GetMatrix());

		FitViewToFrustum(split.view, slice, l.shadow_range * 4.f);
		split.view.ComputeFrustum(split.view.frustum, split.rect);

		// Cull.
		if	(build_dlist)
		{
			BuildRenderablePrimitiveList(split.view, *view_item, split.list.primitive_list, Renderable::Context_Shadow);
			BuildDisplayLists(split.list.primitive_list, split.list.display_lists);
		}

		// Store slice view to light matrix.
		data.slice_distance = zfar;
		data.imatrix = split.view.GetInverseMatrix();
		split.view.ComputeProjectionMatrix(split.rect, data.pmatrix);
		if	(!gpu_config.tex_origin_is_top_left)
			data.pmatrix = Matrix4::ScaleMatrix(Vector4(1.0, -1.0, 1.0)) *  data.pmatrix;
		data.pmatrix = Matrix4::ScaleMatrix(Vector4(0.5, 0.5, 0.5)) * Matrix4::TranslationMatrix(Vector4(1, 1, 1)) * data.pmatrix;

		// Crop on PSSM region.
		data.pmatrix = Matrix4::TranslationMatrix(Vector4(split_offset[n].x, split_offset[n].y, 0.0)) * Matrix4::ScaleMatrix(Vector4(0.5, 0.5, 1.0)) * data.pmatrix;
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Renderer::LightRenderPSSM(Light &l)
{
	Light::RenderData *data = (Light::RenderData *)l.render_data.c_ptr();
	ViewConfig view = BackupView();

	SetViewport(fRect(0, 0, (float)gpu_config.shadow_size, (float)gpu_config.shadow_size));
	Clear(0, 0, 0, 0, 1, ClearDepth);

	int split_count = registry.GetInteger("ShadowMapping:PSSM:Split", 3);
	for	(int n = 0; n < split_count; ++n)
	{
		ShadowRenderData *shd_data = (ShadowRenderData *)data->shadow_data[n].render_data.c_ptr();

		SetViewport(shd_data->split.rect);
		SetClippingRect(&shd_data->split.rect);

		DrawContext dc(DrawContext::Opaque, DrawContext::Base, MaterialShader::Depth);

		view_item = &shd_data->split.view;
		ApplyCamera();
		DrawList(shd_data->split.list.display_lists[0], dc);
		DrawList(shd_data->split.list.display_lists[2], dc);
		shd_data->split.list.Clear(false);	// limit dynamic allocations
	}
	RestoreView(view);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Renderer::LightPreparePSM(const Light &l, bool build_dlist) const
{
	Light::RenderData *light_render_data = (Light::RenderData *)l.render_data.c_ptr();
	if	(!light_render_data || !light_render_data->shadow_data.Allocate(6))
		return false;

	for	(uint n = 0; n < 6; ++n)
	{
		Light::RenderData::ShadowData &data = light_render_data->shadow_data[n];

		if	(data.render_data.IsNull())
			data.render_data = new ShadowRenderData;
		ShadowRenderData *shd_data = (ShadowRenderData *)data.render_data.c_ptr();

		ShadowMapSplit &split = shd_data->split;

		static Vector4 view_angle[6] = { Vector4(0, 0, 0), Vector4(0, Units::Deg(90.f), 0), Vector4(0, Units::Deg(180.f), 0), Vector4(0, Units::Deg(270.f), 0), Vector4(Units::Deg(90.f), 0, 0), Vector4(Units::Deg(270.f), 0, 0) };
		static UVOffset split_offset[] = { { 0, 0 }, { 0.33f, 0 }, { 0.66f, 0 }, { 0, 0.5f }, { 0.33f, 0.5f }, { 0.66f, 0.5f } };

		split.view.aspect_ratio = 1;
		split.view.SetFov(Units::Deg(95.f));	// Add a 5° safe area to avoid artifacts at transition.
		split.view.SnapshotTransformation(l.GetMatrix() * Matrix4::FromMatrix3(Matrix3::FromEuler(view_angle[n])));

		split.rect = fRect::FromWidthHeight(split_offset[n].x * gpu_config.shadow_size, split_offset[n].y * gpu_config.shadow_size, gpu_config.shadow_size / 3.f, gpu_config.shadow_size / 2.f);

		if	(build_dlist)
		{
			BuildRenderablePrimitiveList(split.view, *view_item, split.list.primitive_list, Renderable::Context_Shadow);
			BuildDisplayLists(split.list.primitive_list, split.list.display_lists);
		}

		// Store cube view to light matrix.
		data.imatrix = split.view.GetInverseMatrix();
		split.view.ComputeProjectionMatrix(split.rect, data.pmatrix);	// FIXME watch out, do we need the split viewport (split.rect) or the complete viewport???
		if	(!gpu_config.tex_origin_is_top_left)
			data.pmatrix = Matrix4::ScaleMatrix(Vector4(1, -1, 1)) *  data.pmatrix;
		data.pmatrix = Matrix4::ScaleMatrix(Vector4(0.5f, 0.5f, 0.5f)) * Matrix4::TranslationMatrix(Vector4(1, 1, 1)) * data.pmatrix;

		// Crop on view region.
		data.pmatrix = Matrix4::TranslationMatrix(Vector4(split_offset[n].x, split_offset[n].y, 0.f)) * Matrix4::ScaleMatrix(Vector4(0.33f, 0.5f, 1)) * data.pmatrix;
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Renderer::LightRenderPSM(Light &l)
{
	Light::RenderData *data = (Light::RenderData *)l.render_data.c_ptr();
	ViewConfig view = BackupView();

	SetViewport(fRect(0, 0, (float)gpu_config.shadow_size, (float)gpu_config.shadow_size));
	Clear(0, 0, 0, 0, 1, ClearDepth);

	for	(uint n = 0; n < 6; ++n)
	{
		ShadowRenderData *psm_data = (ShadowRenderData *)data->shadow_data[n].render_data.c_ptr();

		SetViewport(psm_data->split.rect);
		SetClippingRect(&psm_data->split.rect);

		view_item = &psm_data->split.view;
		ApplyCamera();

		DrawContext	dc(DrawContext::Opaque, DrawContext::Base, MaterialShader::Depth);
		DrawList(psm_data->split.list.display_lists[0], dc);
		DrawList(psm_data->split.list.display_lists[2], dc);
		psm_data->split.list.Clear(false);	// limit dynamic allocations
	}
	RestoreView(view);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Renderer::LightPrepareSSM(const Light &l, bool build_dlist) const
{
	Light::RenderData *data = (Light::RenderData *)l.render_data.c_ptr();
	if	(!data || !data->shadow_data.Allocate(1))
		return false;

	if	(data->shadow_data[0].render_data.IsNull())
		data->shadow_data[0].render_data = new ShadowRenderData;
	ShadowRenderData *shd_data = (ShadowRenderData *)data->shadow_data[0].render_data.c_ptr();

	Renderer::ShadowMapSplit &split = shd_data->split;
	split.view.AlignTo(l);

	if	(build_dlist)
	{
		BuildRenderablePrimitiveList(split.view, *view_item, split.list.primitive_list, Renderable::Context_Shadow);
		BuildDisplayLists(split.list.primitive_list, split.list.display_lists);
	}

	// Store shadow matrices.
	data->shadow_data[0].imatrix = split.view.GetInverseMatrix();
	l.ComputeProjectionMatrix(data->shadow_data[0].pmatrix);
	if	(!gpu_config.tex_origin_is_top_left)
		data->shadow_data[0].pmatrix = Matrix4::ScaleMatrix(Vector4(1.0, -1.0, 1.0)) *  data->shadow_data[0].pmatrix;
	data->shadow_data[0].pmatrix = Matrix4::ScaleMatrix(Vector4(0.5, 0.5, 0.5)) * Matrix4::TranslationMatrix(Vector4(1, 1, 1)) * data->shadow_data[0].pmatrix;

	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Renderer::LightRenderSSM(Light &l)
{
	Light::RenderData *data = (Light::RenderData *)l.render_data.c_ptr();
	ViewConfig view = BackupView();

	SetViewport(fRect(0, 0, (float)gpu_config.shadow_size, (float)gpu_config.shadow_size));
	Clear(0, 0, 0, 0, 1, ClearDepth);

	ShadowRenderData *render_data = (ShadowRenderData *)data->shadow_data[0].render_data.c_ptr();

	view_item = &render_data->split.view;
	ApplyCamera();

	DrawContext	dc(DrawContext::Opaque, DrawContext::Base, MaterialShader::Depth);
	DrawList(render_data->split.list.display_lists[0], dc);
	DrawList(render_data->split.list.display_lists[2], dc);
	render_data->split.list.Clear(false);	// limit dynamic allocations

	RestoreView(view);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Renderer::ViewConfig Renderer::BackupView() const
{
	return ViewConfig(view_item, viewport);
}
void				Renderer::RestoreViewport(const ViewConfig &config)
{
	SetViewport(config.viewport);
}
void				Renderer::RestoreView(const ViewConfig &config)
{
	RestoreViewport(config);
	view_item = config.camera;
	ApplyCamera();
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Renderer::PrepareShadowMap(const Light &l, bool build_dlist) const
{
	switch (l.model)
	{
		case Light::Model_Spot:		return LightPrepareSSM(l, build_dlist);
		case Light::Model_Point:	return LightPreparePSM(l, build_dlist);
		case Light::Model_Linear:	return LightPreparePSSM(l, build_dlist);
	}
	return false;
}
void				Renderer::RenderShadowMap(Light &l)
{
	PerfBeginEvent(__FUNCTION__, Color::Blue);

	SetCurrentFBO(shadow_map_fbo);
	SetCullFunc(CullBack);

	fRect old_clipping = GetClippingRect();
	SetClippingRect(NULL);

	switch (l.model)
	{
		case Light::Model_Spot:		LightRenderSSM(l);	break;
		case Light::Model_Point:	LightRenderPSM(l);	break;
		case Light::Model_Linear:	LightRenderPSSM(l);	break;
	}

	SetClippingRect(&old_clipping);

	SetCullFunc(CullFront);
	SetCurrentFBO(NULL);

	PerfEndEvent();
}
//------------------------------------------------------------------------------
