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


		#include "core/emitter.h"
		#include "core/camera.h"
		#include "core/resource_factories.h"
		#include "core/graphic_resource_factory.h"
		#include "core/render_resource_factory.h"
		#include "timing/benchmark.h"
		#include "rand/rand.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;


//------------------------------------------------------------------------------
ParticleModel::ParticleModel()
{
	gravity.Set(0, 0, 0);

	SetColor(Color(1, 1, 1, 1));
//	size_curve.SetDefaultValue(1);

	time_to_live.setSec(4.f);
	damping = 1.f;
}
void				ParticleModel::SetColor(const Color &color)
{
//	red_curve.SetDefaultValue(color.x);
//	green_curve.SetDefaultValue(color.y);
//	blue_curve.SetDefaultValue(color.z);
//	opacity_curve.SetDefaultValue(color.w);
}
void				ParticleModel::AddColorPoint(const Time &t, const Color &color)
{
	red_curve.Insert(CurvePoint(t, color.x));
	green_curve.Insert(CurvePoint(t, color.y));
	blue_curve.Insert(CurvePoint(t, color.z));
	opacity_curve.Insert(CurvePoint(t, color.w));
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				ParticleModel::RenderSetup(ResourceFactories *f)
{
	if	((render_data = new RenderData) != NULL)
		if	(f && f->render)
			render_data->material = f->render->LoadMaterial(material);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Emitter::ComputeMinMax(MinMax &minmax) const
{
	minmax.mn = minmax.mx = GetMatrix().GetRow(3);
	if	(!GetParticleCount())
		return;

	for (uint n = 0; n < GetParticleCount(); ++n)
	{
		Particle *p = GetParticle(n);
		minmax.Grow(MinMax(p->position - Vector4(p->size, p->size, p->size), p->position + Vector4(p->size, p->size, p->size)));
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
uint				Emitter::GetRenderablePrimitiveList(const Camera &view, const Camera &default_view, Stack <Render::Primitive *> &list, Renderable::Context context, bool cull)
{
	if	(cull)
	{
		MinMax minmax;
		ComputeMinMax(minmax);

		if	(view.frustum.ClassifyMinMax(minmax) == Frustum::Outside)
			return 1;
	}

	is_seen = true;

	const Matrix4 &vm = view.GetMatrix();
	list.Push(new Render::Primitive(this, this, vm.GetRow(2).Dot(GetMatrix().GetRow(3) - vm.GetRow(3))));

	return 1;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Emitter::Sort(const Matrix4 &view)
{
	if	(!particle_pool || !is_seen)
		return;

	Vector4	view_front = view.GetRow(2).Normalized(),
			view_pos = view.GetRow(3);

	alive_count = 0;
	for (uint n = 0; n < particle_pool.GetCount(); ++n)
		if	(particle_pool[n].IsAlive())
		{
			sort_array[alive_count].v = (particle_pool[n].position - view_pos).Dot(view_front);
			sort_array[alive_count].o = n;
			alive_count++;
		}

	GS::Sort <float, uint> ::QuickSort(alive_count, sort_array);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Emitter::Update(const Time &dt)
{
	if	(!particle_pool || !is_seen)
		return;

	// Spawn particles.
	uint n = 0;
	for (birth_time += dt * birth_rate * birth_rate_scale; birth_time.toSec() > 1; birth_time -= Time::fromSec(1))
	{
		// Seek next free particle.
		for ( ; n < particle_pool.GetCount(); ++n)
			if	(particle_pool[n].time.toSec() < 0)
				break;

		// Emitter pool exhausted.
		if	(n == particle_pool.GetCount())
			break;

		// Spawn particle.
		Particle &p = particle_pool[n];

		p.time = birth_time / birth_rate;
		p.angle = 0;
		p.size = 0;
		p.color.Set(0, 0, 0, 0);

		ModelParticle(p, *this, time);
	}

	// Update running particles and drop dead ones.
	if	(render_data.IsValid())
		if	(ParticleModel *model = render_data->particle_model)
		{
			float damping = Math::Pow(model->damping, dt.toSec());

			for (n = 0; n < particle_pool.GetCount(); ++n)
			{
				Particle &p = particle_pool[n];
				if	(!p.IsAlive())
					continue;

				// Kill particle.
				p.time += dt;

				if	(p.time >= model->time_to_live)
					p.time.setSec(-1);

				else
				{
					float k_dt = dt.toSec();

					p.position += p.velocity * k_dt;
					p.velocity += render_data->particle_model->gravity * k_dt;
					p.velocity *= damping;

					model->size_curve.Evaluate(p.time, &p.size);
					model->angle_curve.Evaluate(p.time, &p.angle);

					model->red_curve.Evaluate(p.time, &p.color.x);
					model->green_curve.Evaluate(p.time, &p.color.y);
					model->blue_curve.Evaluate(p.time, &p.color.z);
					model->opacity_curve.Evaluate(p.time, &p.color.w);

					p.size *= p.size_scale;
					p.color.w *= p.opacity_scale;
				}
			}
		}

	is_seen = false;
	time += dt;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Emitter::RenderSetup(ResourceFactories *f)
{
	if	((render_data = new RenderData) != NULL)
		if	(f && f->graphic)
			if	((render_data->particle_model = f->graphic->LoadParticleModel(particle_model)) != NULL)
				render_data->particle_model->RenderSetup(f);
}
bool				Emitter::Setup()
{
	alive_count = 0;
	if	(!particle_pool.Allocate(pool_size) || !sort_array.Allocate(pool_size))
		__ERR__(__LOG_E__ << "Failed to allocate emitter particle pool (size " << pool_size << ").\n", false)

	for (uint n = 0; n < pool_size; ++n)
		particle_pool[n].time.setSec(-1);

	time.setSec(0);
	birth_time.setSec(0);

	is_seen = true;
	return true;
}
void				Emitter::Free()
{
	particle_pool.Free();
	sort_array.Free();
	alive_count = 0;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Emitter::SetSprayModel(float angle)
{
	model = Model_Spray;
	spray_angle = angle;
}
void				Emitter::ModelParticle(Particle &p, const Item &i, const Time &/*emitter_time*/)
{
	Vector4 ip = i.GetMatrix().GetRow(3);

	switch (model)
	{
		default:
			p.position = ip;
			p.velocity.Set(0, 0, 0);
			break;

		case Model_Spray:
		{
			p.position = ip;

			using namespace Random;

			float a = (FRand(2.f) - 1.f) * spray_angle;
			Vector4 d(Math::Sin(a), 0, Math::Cos(a));
			Matrix3::RotationMatrixZAxis(FRand(Units::Deg(360.f))).Apply(&p.velocity, &d);
			p.velocity = (p.velocity.Normalized() * FRRand(birth_speed_min, birth_speed_max)) * birth_speed_scale * Matrix3::FromMatrix4(i.GetMatrix());
		}
		break;
	}

	p.size_scale = birth_size_scale;
	p.opacity_scale = birth_opacity_scale;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Emitter::Emitter()
{
	is_seen = true;

	birth_speed_min = 1;
	birth_speed_max = 1.5;
	pool_size = 500;
	birth_rate = (float)pool_size / Units::Sec(5.f);

	birth_rate_scale = 1;
	birth_opacity_scale = 1;
	birth_speed_scale = 1;
	birth_size_scale = 1;

	SetSprayModel(Units::Deg(45.f));

	alive_count = 0;
}
//------------------------------------------------------------------------------
