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


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

		using namespace GS::Core;


//------------------------------------------------------------------------------
Geometry			*GS::Core::MergeGeometry(Geometry *geo_a, Geometry *geo_b, const Matrix4 *mtx_a, const Matrix4 *mtx_b)
{
	if	(!geo_a || !geo_b)
		__ERR__(__LOG_E__ << "Cannot merge NULL geometry.\n", NULL)

	if	(!mtx_a)
		mtx_a = &Matrix4::IdentityMatrix();
	if	(!mtx_b)
		mtx_b = &Matrix4::IdentityMatrix();

	// Allocate a new geometry.
	Geometry *geo_o = new Geometry;
	if	(!geo_o)
		__ERR__(__LOG_E__ << "Failed to allocate merged geometry.\n", NULL)

	geo_o->name = geo_a->name;		// Nothing fancy here, the string length would blow up on large merge.

	// Update data for all geometries.
	if	(geo_a->pol_normal || geo_b->pol_normal)
	{
		geo_a->ComputePolygonNormal();
		geo_b->ComputePolygonNormal();
	}
	if	(geo_a->vtx_normal || geo_b->vtx_normal)
	{
		geo_a->ComputeVertexNormal();
		geo_b->ComputeVertexNormal();
	}

	// Append vertex.
	if	(geo_o->vtx.Allocate(geo_a->vtx.GetCount() + geo_b->vtx.GetCount()))
	{
		Vector4 *pvtx = &geo_o->vtx[0];
		for (uint n = 0; n < geo_a->vtx.GetCount(); ++n)
			*pvtx++ = geo_a->vtx[n] * *mtx_a;
		for (uint n = 0; n < geo_b->vtx.GetCount(); ++n)
			*pvtx++ = geo_b->vtx[n] * *mtx_b;
	}

	// Append polygon.
	if	(geo_o->binding.Allocate(geo_a->binding.GetCount() + geo_b->binding.GetCount()))
	{
		uint *pbind = &geo_o->binding[0];
		for (uint n = 0; n < geo_a->binding.GetCount(); ++n)
			*pbind++ = geo_a->binding[n];
		for (uint n = 0; n < geo_b->binding.GetCount(); ++n)
			*pbind++ = geo_b->binding[n] + geo_a->vtx.GetCount();
	}

	if	(geo_o->pol.Allocate(geo_a->pol.GetCount() + geo_b->pol.GetCount()))
	{
		uint total_binding = 0;

		Polygon *ppol = &geo_o->pol[0];
		for (uint n = 0; n < geo_a->pol.GetCount(); ++n)
		{
			ppol->vtx_count = geo_a->pol[n].vtx_count;
			ppol->material = geo_a->pol[n].material;
			ppol->binding = &geo_o->binding[total_binding];
			total_binding += ppol->vtx_count;
			ppol++;
		}
		for (uint n = 0; n < geo_b->pol.GetCount(); ++n)
		{
			ppol->vtx_count = geo_b->pol[n].vtx_count;
			ppol->material = (ushort)(geo_b->pol[n].material + geo_a->material_table.GetCount());
			ppol->binding = &geo_o->binding[total_binding];
			total_binding += ppol->vtx_count;
			ppol++;
		}
	}

	// Append polygon normal.
	static Vector4 default_normal(0, 0, 1);

	if	(geo_a->pol_normal || geo_b->pol_normal)
		if	(geo_o->pol_normal.Allocate(geo_o->pol.GetCount()))
		{
			Vector4 *ppnrm = &geo_o->pol_normal[0];
			for (uint n = 0; n < geo_a->pol.GetCount(); ++n)
			{
				if	(geo_a->pol_normal)
				{
					mtx_a->ApplyRotation(ppnrm, &geo_a->pol_normal[n]);
					*ppnrm++ = ppnrm->Normalized();
				}
				else
					*ppnrm++ = default_normal;
			}
			for (uint n = 0; n < geo_b->pol.GetCount(); ++n)
			{
				if	(geo_b->pol_normal)
				{
					mtx_b->ApplyRotation(ppnrm, &geo_b->pol_normal[n]);
					*ppnrm++ = ppnrm->Normalized();
				}
				else
					*ppnrm++ = default_normal;
			}
		}

	// Append vertex normal.
	if	(geo_a->vtx_normal || geo_b->vtx_normal)
		if	(geo_o->vtx_normal.Allocate(geo_o->binding.GetCount()))
		{
			Vector4 *pvnrm = &geo_o->vtx_normal[0];
			for (uint n = 0; n < geo_a->binding.GetCount(); ++n)
			{
				if	(geo_a->vtx_normal)
				{
					mtx_a->ApplyRotation(pvnrm, &geo_a->vtx_normal[n]);
					*pvnrm++ = pvnrm->Normalized();
				}
				else
					*pvnrm++ = Vector4(0, 0, 1);
			}
			for (uint n = 0; n < geo_b->binding.GetCount(); ++n)
			{
				if	(geo_b->vtx_normal)
				{
					mtx_b->ApplyRotation(pvnrm, &geo_b->vtx_normal[n]);
					*pvnrm++ = pvnrm->Normalized();
				}
				else
					*pvnrm++ = Vector4(0, 0, 1);
			}
		}

	// Append RGB.
	if	(geo_a->rgb || geo_b->rgb)
		if	(geo_o->rgb.Allocate(geo_o->binding.GetCount()))
		{
			Color *prgb = &geo_o->rgb[0];
			for (uint n = 0; n < geo_a->binding.GetCount(); ++n)
				*prgb++ = geo_a->rgb ? geo_a->rgb[n] : Color::White;
			for (uint n = 0; n < geo_b->binding.GetCount(); ++n)
				*prgb++ = geo_b->rgb ? geo_b->rgb[n] : Color::White;
		}

	// Append all UV channels.
	static Vector2 default_uv(0.5, 0.5);

	for (uint n = 0; n < __UV_PER_GEOMETRY__; ++n)
		if	(geo_a->uv[n] || geo_b->uv[n])
			if	(geo_o->uv[n].Allocate(geo_o->binding.GetCount()))
			{
				Vector2 *puv = &geo_o->uv[n][0];

				if	(geo_a->uv[n])
					for (uint v = 0; v < geo_a->binding.GetCount(); ++v)
						*puv++ = geo_a->uv[n][v];
				else
					for (uint v = 0; v < geo_a->binding.GetCount(); ++v)
						*puv++ = default_uv;

				if	(geo_b->uv[n])
					for (uint v = 0; v < geo_b->binding.GetCount(); ++v)
						*puv++ = geo_b->uv[n][v];
				else
					for (uint v = 0; v < geo_b->binding.GetCount(); ++v)
						*puv++ = default_uv;
			}

	// Append all materials.
	geo_o->material_table.Allocate(geo_a->material_table.GetCount() + geo_b->material_table.GetCount());
	Geometry::MaterialSlot *pslot = geo_o->material_table.c_ptr();

	for (uint n = 0; n < geo_a->material_table.GetCount(); ++n)
	{
		pslot->name = geo_a->material_table[n].name;
		pslot->use_cache = geo_a->material_table[n].use_cache;
		++pslot;
	}
	for (uint n = 0; n < geo_b->material_table.GetCount(); ++n)
	{
		pslot->name = geo_b->material_table[n].name;
		pslot->use_cache = geo_b->material_table[n].use_cache;
		++pslot;
	}

	// Merge materials.
	geo_o->MergeDuplicateMaterials();

	return geo_o;
}
//------------------------------------------------------------------------------
