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


		#include "core/geometry.h"
		#include "math/matrix4.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;

		static const float HomogeneousDistance = Units::Mm(0.01f);


//------------------------------------------------------------------------------
bool				Core::ComputeVertexArrayMinMax(const Array <Vector4> &vtx, MinMax &mm, const Matrix4 *mtx)
{
	if	(!vtx.GetCount())
		return false;

	Vector4 tvt = mtx ? vtx[0] * mtx[0] : vtx[0], mn = tvt, mx = tvt;

	if	(mtx)
		for	(uint n = 0; n < vtx.GetCount(); ++n)
		{
			tvt = vtx[n] * mtx[0];
			if	(tvt.x > mx.x)	mx.x = tvt.x;
			if	(tvt.y > mx.y)	mx.y = tvt.y;
			if	(tvt.z > mx.z)	mx.z = tvt.z;
			if	(tvt.x < mn.x)	mn.x = tvt.x;
			if	(tvt.y < mn.y)	mn.y = tvt.y;
			if	(tvt.z < mn.z)	mn.z = tvt.z;
		}
	else
		for	(uint n = 0; n < vtx.GetCount(); ++n)
		{
			tvt = vtx[n];
			if	(tvt.x > mx.x)	mx.x = tvt.x;
			if	(tvt.y > mx.y)	mx.y = tvt.y;
			if	(tvt.z > mx.z)	mx.z = tvt.z;
			if	(tvt.x < mn.x)	mn.x = tvt.x;
			if	(tvt.y < mn.y)	mn.y = tvt.y;
			if	(tvt.z < mn.z)	mn.z = tvt.z;
		}

	mm.mn = mn;
	mm.mx = mx;
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
uint				Geometry::GetUVCount() const
{
	uint c = 0;
	for	(uint n = 0; n < __UV_PER_GEOMETRY__; ++n)
		if	(uv[n])
			++c;
	return c;
}
uint				Geometry::GetBoneCount() const
{	return bone_name.GetCount();	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Geometry::ComputeBoneBoundingVolumes(Array <MinMax> &bone_mm) const
{
	uint bone_count = bone_bind_matrix.GetCount();

	if	(skin.IsNull() || !bone_count)
		return false;

	if	(!bone_mm.Allocate(bone_count))
		__ERR__(__LOG_E__ << "Failed to allocate bon minmax array.\n", false)

	Array <bool> bone_mm_init(bone_count);
	for (uint n = 0; n < bone_count; ++n)
		bone_mm_init[n] = false;		

	for (uint v = 0; v < vtx.GetCount(); ++v)
		for (int b = 0; b < __PV_BONE_LIMIT__; ++b)
			if	(skin[v].w[b] > 0.f)
			{
				int idx = skin[v].bone_index[b];

				if	(!bone_mm_init[idx])
				{
					bone_mm[idx].Set(vtx[v], vtx[v]);
					bone_mm_init[idx] = true;
				}
				else
					bone_mm[idx].Grow(vtx[v]);
			}

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

//------------------------------------------------------------------------------
uint				Geometry::ComputePolygonBindingCount() const
{
	uint c = 0;
	for	(uint n = 0; n < pol.GetCount(); ++n)
		c += pol[n].vtx_count;
	return c;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
MinMax				Geometry::ComputeMinMax(const Matrix4 *mtx) const
{
	MinMax mm;
	ComputeVertexArrayMinMax(vtx, mm, mtx);
	return mm;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Geometry::AllocateVertex(uint count)
{	return vtx.Allocate(count);	}
bool				Geometry::AllocatePolygon(uint count)
{
	binding.Free();
	return pol.Allocate(count);
}
bool				Geometry::AllocatePolygonBinding()
{
	uint count = ComputePolygonBindingCount();

	if	(!binding.Allocate(count))
		return false;

	count = 0;
	for (uint n = 0; n < pol.GetCount(); ++n)
	{
		pol[n].binding = &binding[count];
		count += pol[n].vtx_count;
	}
	return true;
}
bool				Geometry::AllocateBone(uint count)
{	return bone_name.Allocate(count) && bone_bind_matrix.Allocate(count);	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Geometry::ComputePolygonIndex(Array <uint> &i) const
{
	if	(i.Allocate(pol.GetCount()))
		for	(uint pc = 0, ci = 0; pc < pol.GetCount(); ++pc)
		{
			i[pc] = ci;
			ci += pol[pc].vtx_count;
		}
}
void				Geometry::ComputeVertexToPolygon(Array <VertexToPolygon> &vtx_to_pol) const
{
	if	(!pol || !vtx)
		return;

	Array <uint> pol_per_vtx(vtx.GetCount());
	if	(!pol_per_vtx)
		return;

	Memory::Set(&pol_per_vtx[0], 0, pol_per_vtx.GetSize());

	uint p, v;
	for	(p = 0; p < pol.GetCount(); ++p)
		for	(v = 0; v < pol[p].vtx_count; ++v)
			pol_per_vtx[pol[p].binding[v]]++;

	vtx_to_pol.Allocate(vtx.GetCount());
	for	(v = 0; v < vtx.GetCount(); ++v)
	{
		vtx_to_pol[v].pol_count = 0;
		vtx_to_pol[v].pol_index.Allocate(pol_per_vtx[v]);
	}

	for	(p = 0; p < pol.GetCount(); ++p)
		for	(v = 0; v < pol[p].vtx_count; ++v)
			vtx_to_pol[pol[p].binding[v]].pol_index[vtx_to_pol[pol[p].binding[v]].pol_count++] = p;
}
void				Geometry::ComputeVertexToVertex(Array <VertexToVertex> &vtx_to_vtx, const Array <VertexToPolygon> *vtx_to_pol) const
{
	if	(!pol || !vtx)
		return;

	// Allocate vertex to vertex buffer.
	if	(!vtx_to_vtx.Allocate(vtx.GetCount()))
		__ERRRAW__(__LOG_E__ << "Could not allocate memory.\n")

	// Allocate work area.
	#define	__VertexToVertexTempListSize	1024
	PolygonVertex tmp_vtx_to_vtx[__VertexToVertexTempListSize];

	// Compute vertex to polygon if not provided.
	Array <VertexToPolygon> _vtx_to_pol;
	if	(!vtx_to_pol)
	{
		vtx_to_pol = &_vtx_to_pol;
		ComputeVertexToPolygon(_vtx_to_pol);
	}

	for	(int pass = 0; pass < 2; ++pass)
		for	(uint v = 0; v < vtx.GetCount(); ++v)
		{
			vtx_to_vtx[v].vtx_count = 0;

			uint vtx_vtx_count = 0;
			for	(uint p = 0; p < (*vtx_to_pol)[v].pol_count; ++p)
			{
				uint pol_index = (*vtx_to_pol)[v].pol_index[p];
				Polygon *poly = &pol[pol_index];

				int ci;
				for	(ci = 0; ci < poly->vtx_count; ++ci)
					if	(poly->binding[ci] == v)
						break;

				for	(int _c = (ci - 1); _c <= (ci + 1); _c += 2)
				{
					int vtx_index = _c;
					if	(vtx_index < 0)
						vtx_index += poly->vtx_count;
					if	(vtx_index >= poly->vtx_count)
						vtx_index -= poly->vtx_count;

					// Invalidate already registered candidate.
					bool insert = true;
					for	(uint nl = 0; nl < vtx_vtx_count; ++nl)
						if	((tmp_vtx_to_vtx[nl].pol_index == pol_index) && (tmp_vtx_to_vtx[nl].vtx_index == (uint)vtx_index))
						{
							insert = false;
							break;
						}

					if	(insert)
					{
						tmp_vtx_to_vtx[vtx_vtx_count].pol_index = pol_index;
						tmp_vtx_to_vtx[vtx_vtx_count].vtx_index = vtx_index;

						if	(vtx_vtx_count == __VertexToVertexTempListSize)
						{
							__LOG_E__ << "Temporary list exceeded, vertex to vertex LUT corrupted.\n";
							vtx_vtx_count = __VertexToVertexTempListSize - 1;
						}

						if	(pass == 1)
						{
							vtx_to_vtx[v].vtx[vtx_vtx_count].pol_index = pol_index;
							vtx_to_vtx[v].vtx[vtx_vtx_count].vtx_index = vtx_index;
						}
						++vtx_vtx_count;
					}
				}
			}

			// Allocate vertex container for this vertex.
			if	(pass == 0)
				vtx_to_vtx[v].vtx.Allocate(vtx_vtx_count);
		}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Geometry::FlagHomogeneousVertex(Array <bool> &flag, const Array <uint> &pol_index, const Array <VertexToPolygon> &vtx_to_pol, int mat) const
{
	if	(!flag.Allocate(vtx.GetCount()))
		__ERRRAW__(__LOG_E__ << "Could not allocate homogeneous vertex table.\n")
	for	(uint n = 0; n < vtx.GetCount(); ++n)
		flag[n] = true;

#if		0
	// Test for early exit in case no polygon uses this material.
	for	(uint n = 0; n < pol.GetCount(); n++)
		if	(pol[n].material == n)
			break;
	if	(n == pol.GetCount())
		return;
#endif

	for	(uint m = 0; m < vtx.GetCount(); ++m)
	{
		uint ngeo = vtx_to_pol[m].pol_count;
		for	(uint ac = 0; ac < ngeo; ++ac)
			for	(uint bc = 0; bc < ngeo; ++bc)
			{
				uint	i_ac = vtx_to_pol[m].pol_index[ac],
						i_bc = vtx_to_pol[m].pol_index[bc];
				Polygon *apoly = &pol[i_ac], *bpoly = &pol[i_bc];

				if	(apoly == bpoly)
					continue;
				if	(apoly->material == bpoly->material)
				{
					if	((mat == -1) || (apoly->material == (uint)mat))
					{
						uint _u, _v;
						for	(_u = 0; _u < apoly->vtx_count; ++_u)
							if	( apoly->binding[_u] == m )
								break;
						for	(_v = 0; _v < bpoly->vtx_count; ++_v)
							if	( bpoly->binding[_v] == m )
								break;

						if	(vtx_normal)
							if	(Vector4::Dist2(vtx_normal[pol_index[i_ac] + _u], vtx_normal[pol_index[i_bc] + _v]) > HomogeneousDistance)
								flag[m] = false;
/*
						if	(vtx_tangent)
							if	(
									(nVector::Dist2(vtx_tangent[pol_index[i_ac] + _u].B, vtx_tangent[pol_index[i_bc] + _v].B) > HomogeneousDistance) ||
									(nVector::Dist2(vtx_tangent[pol_index[i_ac] + _u].T, vtx_tangent[pol_index[i_bc] + _v].T) > HomogeneousDistance)
								)
								vtx_homogeneous[m] = false;
*/
						if	(rgb)
							if	(Vector4::Dist2(rgb[pol_index[i_ac] + _u], rgb[pol_index[i_bc] + _v]) > HomogeneousDistance)
								flag[m] = false;

						for	(uint cuv = 0; cuv < __UV_PER_GEOMETRY__; ++cuv)
							if	(uv[cuv])
								if	(Vector2::Dist2(uv[cuv][pol_index[i_ac] + _u], uv[cuv][pol_index[i_bc] + _v]) > HomogeneousDistance)
									flag[m] = false;
					}
				}
				else
					flag[m] = false;

				if	(!flag[m])
					goto nxth;
			}
		nxth:;
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
uint				Geometry::MergeDuplicateMaterials()
{
	if	(material_table.GetCount() < 2)
		return 0;

	__LOG_H__ << "Merging materials in geometry '" << name << "'...\n";

	// Build the drop table.
	uint old_slot_count = material_table.GetCount();
	Array <bool> drop(material_table.GetCount());
	for (uint n = 0; n < material_table.GetCount(); ++n)
		drop[n] = false;

	Array <uint> material_remap(material_table.GetCount());
	for (uint n = 0; n < material_table.GetCount(); ++n)
		material_remap[n] = n;

	// Flag materials to drop.
	for (uint n = 0; n < material_table.GetCount(); ++n)
	{
		if	(drop[n])	// Already dropped.
			continue;

		for (uint m = n + 1; m < material_table.GetCount(); ++m)
		{
			// Check by material name.
			if	(material_table[n].name != material_table[m].name)
				goto skip_material_drop;

			// Drop material.
			drop[m] = true;
			material_remap[m] = n;

	skip_material_drop:;
		}
	}

	// Create the new material array.
	uint new_material_slot_count = 0;
	for (uint n = 0; n < material_table.GetCount(); ++n)
		if	(!drop[n])
			new_material_slot_count++;

	Array <Geometry::MaterialSlot> new_material_slot(new_material_slot_count);
	new_material_slot_count = 0;
	for	(uint n = 0; n < material_table.GetCount(); ++n)
		if	(!drop[n])
		{
			for (uint m = 0; m < material_table.GetCount(); ++m)
				if	(material_remap[m] == n)
					material_remap[m] = new_material_slot_count;

			new_material_slot[new_material_slot_count].name = material_table[n].name;
			new_material_slot[new_material_slot_count].use_cache = material_table[n].use_cache;
			++new_material_slot_count;
		}

	material_table.Transfer(new_material_slot);

	// Remap polygon references.
	for (uint n = 0; n < pol.GetCount(); ++n)
		pol[n].material = (ushort)material_remap[pol[n].material];

	uint merge_count = old_slot_count - material_table.GetCount();

	__LOG__ << "Done, merged " << merge_count << " material slot(s).\n";
	return merge_count;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Geometry::Free()
{
	bone_name.Free();
	skin.Free();
	bone_bind_matrix.Free();

	vtx.Free();
	vtx_normal.Free();
	vtx_tangent.Free();
	pol_normal.Free();
	pol_tangent.Free();
	pol.Free();
	binding.Free();

	for	(uint n = 0; n < __UV_PER_GEOMETRY__; n++)
		uv[n].Free();
	rgb.Free();

	material_table.Free();

	lod_proxy = NULL;
	lod_distance = Units::Mtr(100.f);

	shadow_proxy = NULL;

	flag.Raise(FlagNullShadowProxy | FlagNullLodProxy, false);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Geometry::Geometry()
{
	lod_distance = Units::Mtr(100.f);
}
Geometry::~Geometry()
{
	Free();
}
//------------------------------------------------------------------------------
