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


		#include "core/light.h"
		#include "core/resource_factories.h"
		#include "core/render_resource_factory.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;


//------------------------------------------------------------------------------
void				Light::ComputeProjectionMatrix(Matrix4 &m) const
{
	switch (model)
	{
		case Model_Spot:
		{
			const float	q = volume_range / (volume_range - z_near),
						fov = (cone_angle + edge_angle),
						zoom_factor = float(Math::Cos(fov) / Math::Sin(fov));

			// Default aspect ration: PC(1:1).
			m.Set
				(
					zoom_factor, 0, 0, 0,
					0, zoom_factor, 0, 0,
					0, 0, q, 1,
					0, 0, -q * z_near, 0
				);
		}
		break;

		default:
			m = Matrix4::IdentityMatrix();
			break;
	}
}
void				Light::ComputeFrustrum(Frustum &f, float zn, float zf) const
{
	if	(model == Model_Spot)
		f.SetPerspective((cone_angle + edge_angle) * 2, zn != -1 ? zn : z_near, zf != -1 ? zf : volume_range, &GetMatrix(), 1, 1);
}
void				Light::ComputeMatrix()
{
	bool update_world = item_flags.IsSet(ItemFlagWorldMatrixDirty);

	Item::ComputeMatrix();

	// Remove scale from world matrix.
	if	(update_world)
	{
		Vector4 u = matrix.GetRow(0).Normalized();
		matrix.m[0][0] = u.x; matrix.m[1][0] = u.y; matrix.m[2][0] = u.z;
		Vector4 v = matrix.GetRow(1).Normalized();
		matrix.m[0][1] = v.x; matrix.m[1][1] = v.y; matrix.m[2][1] = v.z;
		Vector4 w = matrix.GetRow(2).Normalized();
		matrix.m[0][2] = w.x; matrix.m[1][2] = w.y; matrix.m[2][2] = w.z;
	}
	ComputeFrustrum(frustum);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Light::SampleColor(const Vector4 &location, const Vector4 &normal, Color &diff, Color &spec, Vector4 *view, float gloss)
{
	float idiff, ispec;

	if	(!SampleEnergy(location, normal, &idiff, &ispec, view, gloss, true, false))
	{
		diff.Set();
		spec.Set();
		return false;
	}

	diff = diffuse_color * diffuse_intensity * idiff;
	spec = specular_color * specular_intensity * ispec;
	return true;
}
bool				Light::SampleEnergy(const Vector4 &loc, const Vector4 &normal, float *diff, float *spec, Vector4 *view, float gloss, bool halfway, bool cooktorrance)
{
	if	(!diff && !spec)
		return false;

	Vector4 dt;
	float a = 1;

	switch (model)
	{
		case Model_Linear:
			dt = GetMatrix().GetRow(2).Reversed().Normalized();
			break;

		default:
		{
			dt = GetMatrix().GetRow(3) - loc;
			float dt_length = dt.Len();
			dt /= dt_length;

			// Compute light attenuation.
			if	(range)
			{
				float distance = dt_length / range;

				switch (falloff)
				{
/*
					case Falloff_InvDist:
						if	(distance)
							a = 1 / distance;
						break;

					case Falloff_InvDist2:
						if	(distance)
							a = 1 / (distance * distance);
						break;
*/
					default:
					case Falloff_Linear:
						a = 1 - distance;
						break;
				}
				if	(a <= 0)
					return false;
			}

			if	(model == Model_Spot)
			{
				float c = Math::ACos(dt.Reversed().Dot(GetMatrix().GetRow(2).Normalized()));
				if	((c < 0) || (c > (cone_angle + edge_angle)))
					return false;
				if	(c > cone_angle)
					a *= 1 - (c - cone_angle) / edge_angle;
			}
		}
		break;
	}

	// Sample diffuse.
	float e = normal.Dot(dt);
	if	(e < 0)
		return false;

	if	(diff)
		*diff = e * a * diffuse_intensity;

	/*
		Sample specular.
		Use the halfway vector method if you'd like to avoid a vector reflection.
	*/
	if	(spec)
	{
		*spec = 0;

		if	(view)
		{
			Vector4 local_view = view->Normalized();
			float e = 0;

			if	(cooktorrance)
				__LOG_W__ << "Cook-Torrance specular model not implemented.\n";

			else
			{
				if	(halfway)
						e = (dt - local_view).Normalized().Dot(normal);
				else	e = local_view.Reflected(normal).Dot(dt);

				e = (e > 0) ? Math::Pow(e, gloss * 96.f) : 0;
			}
			*spec = e * a * specular_intensity;
		}
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Light::RenderSetup(ResourceFactories *f)
{
	render_data = new RenderData;
	if	(f && f->render)
		if	(!projection_texture.IsEmpty())
			render_data->projection_texture = f->render->LoadTexture(projection_texture);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Light::SetDefaults()
{
	model = Light::Model_Point;
	shadow = Light::Shadow_None;

	range = Units::Mtr(0.f);
	volume_range = Units::Mtr(500.f);
	clip_distance = Units::Mtr(300.f);
	cone_angle = Units::Deg(30.f);
	edge_angle = Units::Deg(30.f);

	shadow_cast_all = false;
	shadow_bias = 0.01f;
	shadow_distribution = 0.9f;
	shadow_range = Units::Mtr(100.f);
	shadow_color.Set(0, 0, 0);

	volumetric = false;
	volumetric_sample_step = 0.5f;
	volumetric_range = 8.0f;
	volumetric_thickness = 1.0f;

	z_near = Units::Cm(2.f);
	falloff = Falloff_Linear;

	diffuse_color = Color::White;
	diffuse_intensity = 1;
	specular_color = Color::White;
	specular_intensity = 0;
}
Light::Light()
{
	SetDefaults();
}
//------------------------------------------------------------------------------
