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


		#include "core/geometry.h"
		#include "core/embedded_resource_handler_interface.h"
		#include "timing/benchmark.h"
		#include "metafile/nml.h"
		#include "math/matrix4.h"
		#include "ascii/parser.h"
		#include "memory/endian.h"
		#include "log/log.h"

		using namespace GS::Core;
		using namespace GS::AsciiParser;


//------------------------------------------------------------------------------
void				Geometry::ParseVertex(const Tag *pt)
{
	NMLTagForeach(vt, *pt)
	{
		if	(vt->name == "Count")
			vtx.Allocate(vt->GetInteger());

		else	if	(vt->name == "Data")
		{
			float *fbff = (float *)vt->GetValue().GetBinaryBuffer();

			if	(vtx)
				for	(uint n = 0; n < vtx.GetCount(); ++n)
				{
					for	(int m = 0; m < 3; ++m)
						Endian::ToHost(&fbff[n * 3 + m], 4, Endian::Intel);
					vtx[n].Set(fbff[n * 3 + 0], fbff[n * 3 + 1], fbff[n * 3 + 2]);
				}
			else	__LOG_E__ << "Incomplete vertex chunk loading '" << name << "', expected vertex count integer tag.\n";
		}
	}
}
void				Geometry::ParseAsciiVertex(const Tag *pt)
{
	NMLTagForeach(vt, *pt)
	{
		if	(vt->name == "Count")
			vtx.Allocate(vt->GetInteger());

		else	if	(vt->name == "Data")
		{
			if	(vtx)
			{
				uint cvtx = 0;

				NMLTagForeach(vx, *vt)
				{
					if	(cvtx == vtx.GetCount())
					{
						__LOG_E__ << "Corrupter <AVertex> tag, more vertices than expected.\n";
						break;
					}
					vtx[cvtx++].FromMetaTag(*vx);
				}
			}
			else	__LOG_E__ << "Incomplete vertex chunk loading '" << name << "', expected vertex count integer tag.\n";
		}
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Geometry::ParseSkin(const Tag *pt)
{
	// Retrieve bone id list.
	Tag *bonetag = pt->GetTag("Bones"),
			 *bindtag = pt->GetTag("Binds");

	skin.Free();
	bone_name.Free();
	bone_bind_matrix.Free();

	if	(bonetag)
	{
		AllocateBone(bonetag->GetChildCount());

		if	(!bone_name)
			__LOG_E__ << "Failed to allocate bone id list.\n";

		else
		{
			int	n = 0;
			NMLTagForeach(b, *bonetag)
				bone_name[n++] = b->GetString();
		}

		if	(bindtag && (bindtag->GetChildCount() == bone_name.GetCount()))
		{
			int	n = 0;
			NMLTagForeach(m, *bindtag)
				bone_bind_matrix[n++].FromMetaTag(*m);
		}
	}

	// Retrieve skin weights.
	Tag *weighttag = pt->GetTag("Weights");

	if	(bone_name.GetCount() && weighttag)
	{
		if	(weighttag->GetChildCount() != vtx.GetCount())
			__LOG_W__ << "Incoherent weight count (" << weighttag->GetChildCount() <<" for " << vtx.GetCount() << " vertice (is the skin being declared before the vertice?)).\n";

		if	(!skin.Allocate(weighttag->GetChildCount()))
			__LOG_E__ << "Failed to allocate skin weights array.\n";

		else
		{
			int	n = 0;
			NMLTagForeach(b, *weighttag)
			{
				const char *p = b->GetString(), *e = p + String::strlen(p);

				skin[n].bone_index[0] = (ushort)String::atoi(p);
				p = NextEntry(p, e) + 1;
				skin[n].bone_index[1] = (ushort)String::atoi(p);
				p = NextEntry(p, e) + 1;
				skin[n].bone_index[2] = (ushort)String::atoi(p);
				p = NextEntry(p, e) + 1;
				skin[n].bone_index[3] = (ushort)String::atoi(p);
				p = NextEntry(p, e) + 1;

				skin[n].w[0] = ((float)String::atoi(p)) / 255.f;
				p = NextEntry(p, e) + 1;
				skin[n].w[1] = ((float)String::atoi(p)) / 255.f;
				p = NextEntry(p, e) + 1;
				skin[n].w[2] = ((float)String::atoi(p)) / 255.f;
				p = NextEntry(p, e) + 1;
				skin[n].w[3] = ((float)String::atoi(p)) / 255.f;

				// Re-normalize weights.
				float tw = 0.f;
				for (int w = 0; w < 4; ++w)
					tw += skin[n].w[w];

				float k = 1.f / tw;
				for (int w = 0; w < 4; ++w)
					skin[n].w[w] *= k;

				++n;
			}
		}
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Geometry::ParsePolygon(const Tag *pt)
{
	NMLTagForeach(vt, *pt)
	{
		if	(vt->name == "Count")
			pol.Allocate(vt->GetInteger());

		else	if	(vt->name == "BindingCount")
				binding.Allocate(vt->GetInteger());

		else	if	(vt->name == "Data")
		{
			if	(pol && binding)
			{
				uint *pbuffer = (uint *)vt->GetValue().GetBinaryBuffer();
				uint *pbinding = binding;

				for	(uint m = 0; m < pol.GetCount(); m++)
				{
					Endian::ToHost(pbuffer, 4, Endian::Intel);
					pol[m].vtx_count = (ushort)*pbuffer++;

					pol[m].binding = pbinding;
					for	(uint j = 0; j < pol[m].vtx_count; j++)
					{
						Endian::ToHost(pbuffer, 4, Endian::Intel);
						*pbinding++ = *pbuffer++;
					}

					Endian::ToHost(pbuffer, 4, Endian::Intel);
					pol[m].material = (ushort)*pbuffer++;
				}
			}
			else	__LOG_E__ << "Could not allocate polygon/binding memory.\n";
		}
	}
}
void				Geometry::ParseAsciiPolygon(const Tag *pt)
{
	NMLTagForeach(vt, *pt)
	{
		if	(vt->name == "Count")
			pol.Allocate(vt->GetInteger());

		else	if	(vt->name == "BindingCount")
				binding.Allocate(vt->GetInteger());

		else	if	(vt->name == "Data")
		{
			if	(binding && pol)
			{
				static String _PolyIndex("Index");

				uint *pbinding = binding;
				uint cpoly = 0;

				NMLTagForeach(poly, *vt)
				{
					if	(cpoly == pol.GetCount())
					{
						__LOG_E__ << "Corrupted <APolygon> tag, too many polygons.\n";
						break;
					}

					Tag *count_tag = poly->GetTag("Count;"),
							 *material_tag = poly->GetTag("Material;");

					pol[cpoly].vtx_count = 0;
					pol[cpoly].binding = pbinding;
					pol[cpoly].material = 0;

					if	(count_tag && material_tag)
					{
						pol[cpoly].vtx_count = (ushort)count_tag->GetInteger();
						pol[cpoly].material = (ushort)material_tag->GetInteger();

						NMLTagForeach(tag, *poly)
							if	(tag->name == _PolyIndex)
								*pbinding++ = tag->GetInteger();
					}
					else	__LOG_E__ << "Corrupt <Polygon> tag in <APolygon>.\n";

					++cpoly;
				}
			}
			else	__LOG_E__ << "Could not allocate polygon/binding memory.\n";
		}
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Geometry::ParsePolygonNormal(const Tag *pt)
{
	NMLTagForeach(vt, *pt)
	{
		if	(vt->name == "Count")
			pol_normal.Allocate(vt->GetInteger());

		else	if	(vt->name == "Data")
		{
			if	(pol_normal)
			{
				char *pbuffer = (char *)vt->GetValue().GetBinaryBuffer();

				for	(uint m = 0; m < pol.GetCount(); m++)
				{
					pol_normal[m].x = (float)(pbuffer[0]) / 127.f;
					pol_normal[m].y = (float)(pbuffer[1]) / 127.f;
					pol_normal[m].z = (float)(pbuffer[2]) / 127.f;
					pol_normal[m].Normalize();
					pbuffer += 3;
				}
			}
			else	__LOG_E__ << "Incomplete polygon normal chunk loading '" << name << "'.\n";
		}
	}
}
void				Geometry::ParseVertexNormal(const Tag *pt)
{
	Tag *vt = pt->GetTag("Count;");
	if	(!vt)
		__ERRRAW__(__LOG_E__ << "No count tag in <VNormal>.\n")
	vtx_normal.Allocate(vt->GetInteger());

	vt = pt->GetTag("Data;");
	if	(!vt)
		__ERRRAW__(__LOG_E__ << "No data tag in <VNormal>.\n")

	if	(vtx_normal)
	{
		signed char	*pbuffer = (signed char *)vt->GetValue().GetBinaryBuffer();

		for	(uint m = 0; m < vtx_normal.GetCount(); ++m)
		{
			vtx_normal[m].x = ((float)pbuffer[0]) / 127.f;
			vtx_normal[m].y = ((float)pbuffer[1]) / 127.f;
			vtx_normal[m].z = ((float)pbuffer[2]) / 127.f;

			vtx_normal[m].Normalize();
			pbuffer += 3;
		}
	}
	else	__LOG_E__ << "Incomplete vertex normal chunk loading '" << name << "'.\n";
}
void				Geometry::ParseVertexTangent(const Tag *pt)
{
	Tag *vt = pt->GetTag("Count;");
	if	(!vt)
		__ERRRAW__(__LOG_E__ << "No count tag in <VTangent>.\n")
	vtx_tangent.Allocate(vt->GetInteger());

	vt = pt->GetTag("Data;");
	if	(!vt)
		__ERRRAW__(__LOG_E__ << "No data tag in <VTangent>.\n")

	// Allocate vertex normals.
	if	(vtx_tangent)
	{
		signed char	*pbuffer = (signed char *)vt->GetValue().GetBinaryBuffer();

		for	(uint m = 0; m < vtx_tangent.GetCount(); ++m)
		{
			Vector4 &T = vtx_tangent[m].T, &B = vtx_tangent[m].B;

			T.x = ((float)pbuffer[0]) / 127.f;
			T.y = ((float)pbuffer[1]) / 127.f;
			T.z = ((float)pbuffer[2]) / 127.f;
			T.Normalize();

			B.x = ((float)pbuffer[3]) / 127.f;
			B.y = ((float)pbuffer[4]) / 127.f;
			B.z = ((float)pbuffer[5]) / 127.f;
			B.Normalize();

			pbuffer += 6;
		}
	}
	else	__LOG_E__ << "Incomplete vertex tangent chunk loading '" << name << "'.\n";
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Geometry::ParseAsciiRGB(const Tag *pt)
{
	Tag *vt = pt->GetTag("Count;");
	if	(!vt)
		__ERRRAW__(__LOG_E__ << "No count tag in <ARGB>.\n")
	rgb.Allocate(vt->GetInteger());

	vt = pt->GetTag("Data;");
	if	(!vt)
		__ERRRAW__(__LOG_E__ << "No data tag in <ARGB>.\n")

	if	(rgb)
	{
		uint rgb_count = 0;

		NMLTagForeach(rt, *vt)
		{
			if	(rgb_count == rgb.GetCount())
			{
				__LOG_E__ << "Too many <Value> tags in RGB list of geometry '" << name << "'.\n";
				break;
			}
			Tag *r_tag = rt->GetTypedTag("R;", Variant::VariantFloat),
					 *g_tag = rt->GetTypedTag("G;", Variant::VariantFloat),
					 *b_tag = rt->GetTypedTag("B;", Variant::VariantFloat),
					 *a_tag = rt->GetTypedTag("A;", Variant::VariantFloat);

			rgb[rgb_count].x = r_tag ? r_tag->GetReal() : 0;
			rgb[rgb_count].y = g_tag ? g_tag->GetReal() : 0;
			rgb[rgb_count].z = b_tag ? b_tag->GetReal() : 0;
			rgb[rgb_count].w = a_tag ? a_tag->GetReal() : 1;
			++rgb_count;
		}

		if	(rgb_count != rgb.GetCount())
			__LOG_W__ << "Not enough <Value> tags in RGB list of geometry '" << name << "'.\n";
	}
	else	__LOG_E__ << "Could not allocate RGB for geometry '" << name << "'.\n";
}
void				Geometry::ParseRGB(const Tag *pt)
{
	if	(Tag *vt = pt->GetTag("Data"))
	{
		float *pbuffer = (float *)vt->GetValue().GetBinaryBuffer();
		size_t size = vt->GetValue().GetBinarySize();
		
		if	(rgb.Allocate(binding.GetCount()))
		{
			uint components = (size == binding.GetCount() * 4) ? 4 : 3;

			for	(uint m = 0; m < rgb.GetCount(); m++)
			{
				for	(uint n = 0; n < components; ++n)
					Endian::ToHost(&pbuffer[n], 4, Endian::Intel);

				rgb[m].Set(pbuffer[0], pbuffer[1], pbuffer[2]);
				rgb[m].w = components == 4 ? pbuffer[3] : 1.f;

				pbuffer += components;
			}
		}
		else	__LOG_E__ << "Could not allocate RGB channel for geometry '" << name << "'.\n";
	}
	else	__LOG_E__ << "Expected <Data> tag under <RGB> tag.\n";
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Geometry::ParseUV(const Tag *pt)
{
	uint current_uv = 0;

	NMLTagForeach(vt, *pt)
	{
		if	(vt->name != "Data")
			continue;

		if	(current_uv == __UV_PER_GEOMETRY__)
		{
			__LOG_W__ << "Geometry '" << name << "' requires more UV channels than this build can handle.\n";
			__LOG_W__ << "Please increase nUVMapCount and recompile to fully import this object.\n";
			break;
		}

		float *pbuffer = (float *)vt->GetValue().GetBinaryBuffer();

		if	(uv[current_uv].Allocate(binding.GetCount()))
			for	(uint m = 0; m < binding.GetCount(); ++m)
			{
				for	(int n = 0; n < 2; ++n)
					Endian::ToHost(&pbuffer[n], 4, Endian::Intel);
				uv[current_uv][m].x = pbuffer[0];
				uv[current_uv][m].y = pbuffer[1];
				pbuffer += 2;
			}
		else
			__LOG_E__ << "could not allocate UV channel for geometry '" << name << "'.\n";

		++current_uv;
	}
}
void				Geometry::ParseAsciiUV(const Tag *pt)
{
	uint current_uv = 0;

	NMLTagForeach(vt, *pt)
	{
		if	(vt->name != "Data")
			continue;

		if	(uv[current_uv].Allocate(binding.GetCount()))
		{
			uint uv_index = 0;

			NMLTagForeach(uvt, *vt)
			{
				if	(uv_index == binding.GetCount())
				{
					__LOG_E__ << "Too many <Value> tags in UV channel " << current_uv << " of geometry '" << name << "'.\n";
					break;
				}
				Tag *u_tag = uvt->GetTypedTag("U;", Variant::VariantFloat),
						 *v_tag = uvt->GetTypedTag("V;", Variant::VariantFloat);

				uv[current_uv][uv_index].x = u_tag ? u_tag->GetReal() : 0;
				uv[current_uv][uv_index].y = v_tag ? v_tag->GetReal() : 0;
				uv_index++;
			}
		}
		else	__LOG_E__ << "could not allocate UV channel for geometry '" << name << "'.\n";

		if	(++current_uv == __UV_PER_GEOMETRY__)
		{
			__LOG_W__ << "'" << name << "' requires more UV channels than this build can handle.\n";
			__LOG_W__ << "   Please increase nUVMapCount and recompile nEngine to fully import this object.\n";
		}
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Geometry::ParseMaterials(const Tag *pt)
{
	// First pass, material count.
	uint slot_count = 0;
	NMLTagForeach(mt, *pt)
		if	((mt->name == "Material") || (mt->name == "MaterialRef") || (mt->name == "MaterialRefEx"))
			slot_count++;

	// Allocate slots.
	if	(material_table.Allocate(slot_count))
	{
		slot_count = 0;

		NMLTagForeach(mt, *pt)
		{
			String mref;
			bool use_cache = true;

			if	(mt->name == "Material")
				IEmbeddedResourceHandler::Get()->ExtractEmbeddedMaterial(mref, *mt, name, slot_count);
			else	if	(mt->name == "MaterialRef")
				mref = mt->GetString();
			else	if	(mt->name == "MaterialRefEx")
			{
				if	(Tag *t = mt->GetTypedTag("Name", Variant::VariantString))
					mref = t->GetString();
				if	(Tag *t = mt->GetTypedTag("UseCache", Variant::VariantBool))
					use_cache = t->GetBool();
			}

			material_table[slot_count].name = mref;
			material_table[slot_count].use_cache = use_cache;

			++slot_count;
		}
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Geometry::ParseMisc(const Tag &tag)
{
	NMLTagForeach(pt, tag)
	{
		if	(pt->name == "LodDistance")
			lod_distance = pt->GetReal();
		else	if	(pt->name == "LodNull")
			flag.Raise(FlagNullLodProxy, pt->GetBool());

		else	if	(pt->name == "ShadowNull")
			flag.Raise(FlagNullShadowProxy, pt->GetBool());

		else	if	(pt->name == "CopyLock")
			copy_lock = pt->GetString();
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Geometry::FromMetaTag(const Tag &tag)
{
	Benchmark bench(true);

	// Build binary objects base name.
	if	(tag.name != "Geometry")
		__ERR__(__LOG_E__ << "Could not parse material, incorrect root tag (" << tag.name << ")...\n", false)
	Free();

	// Parse root tags.
	Tag *pt;

	if	((pt = tag.GetTag("Materials")) != NULL)
		ParseMaterials(pt);

	if	((pt = tag.GetTag("Vertex")) != NULL)
		ParseVertex(pt);
	else
		if	((pt = tag.GetTag("AVertex")) != NULL)
			ParseAsciiVertex(pt);

	if	((pt = tag.GetTag("Polygon")) != NULL)
		ParsePolygon(pt);
	else
		if	((pt = tag.GetTag("APolygon")) != NULL)
			ParseAsciiPolygon(pt);

	if	((pt = tag.GetTag("PNormal")) != NULL)
		ParsePolygonNormal(pt);

	if	((pt = tag.GetTag("VNormal")) != NULL)
		ParseVertexNormal(pt);

	if	((pt = tag.GetTag("VTangent")) != NULL)
		ParseVertexTangent(pt);

	if	((pt = tag.GetTag("Skin")) != NULL)
		ParseSkin(pt);

	if	((pt = tag.GetTag("RGB")) != NULL)
		ParseRGB(pt);
	else
		if	((pt = tag.GetTag("ARGB")) != NULL)
			ParseAsciiRGB(pt);

	if	((pt = tag.GetTag("UV")) != NULL)
		ParseUV(pt);
	else
		if	((pt = tag.GetTag("AUV")) != NULL)
			ParseAsciiUV(pt);

	if	((pt = tag.GetTag("LodProxy")) != NULL)
		lod_proxy = pt->GetString();

	if	((pt = tag.GetTag("ShadowProxy")) != NULL)
		shadow_proxy = pt->GetString();

	ParseMisc(tag);

	// Quick integrity check.
	if	(!vtx.GetCount())
		__ERR__(__LOG_E__ << "No vertice in geometry '" << name << "'.\n", false)
	if	(!pol.GetCount())
		__ERR__(__LOG_E__ << "No polygon in geometry '" << name << "'.\n", false)

	if	(vtx_tangent.IsNull())
		ComputeVertexTangent();

	bench.Stop();
	__LOG__ << "Geometry::FromMetaTag(Tag &tag) done in " << bench.GetMs() << "ms. " << vtx.GetCount() << " vertice, " << pol.GetCount() << " polygon(s), " << material_table.GetCount() << " material(s).\n";
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Tag			*Geometry::AsMetaTag() const
{
	// Integrity check.
	if	(!vtx.GetCount() || !pol.GetCount() || !material_table.GetCount())
		__ERR__(__LOG_E__ << "Cannot serialize '" << name << "', geometry is empty.\n", NULL)

	Tag *root = new Tag("Geometry");
	if	(!root)
		__ERR__(__LOG_E__ << "Could not create root tag to serialize geometry '" << name << "'.\n", NULL)

	uint n;

	// Vertice.
	if	(Tag *vertex = root->AddChild("Vertex"))
	{
		vertex->AddChild("Count", (int)vtx.GetCount());

		if	(float *vtx_rl = new float[vtx.GetCount() * 3])
		{
			for	(n = 0; n < vtx.GetCount(); n++)
			{
				vtx_rl[n * 3 + 0] = vtx[n].x;
				vtx_rl[n * 3 + 1] = vtx[n].y;
				vtx_rl[n * 3 + 2] = vtx[n].z;
			}
			vertex->AddChild("Data", (uchar *)vtx_rl, vtx.GetCount() * 3 * sizeof(float));
			_safe_delete_array(vtx_rl);
		}
		else
			__LOG_E__ << "Failed to allocate raw vertice buffer.\n";
	}

	// Polygons.
	if	(Tag *polygon = root->AddChild("Polygon"))
	{
		polygon->AddChild("Count", (int)pol.GetCount());
		polygon->AddChild("BindingCount", (int)binding.GetCount());

		if	(uint *pol_u4 = new uint[binding.GetCount() + pol.GetCount() * 2])
		{
			uint *ptr = pol_u4;
			for	(n = 0; n < pol.GetCount(); n++)
			{
				*ptr++ = pol[n].vtx_count;
				for	(uint m = 0; m < pol[n].vtx_count; m++)
					*ptr++ = pol[n].binding[m];
				*ptr++ = (uint)pol[n].material;
			}
			polygon->AddChild("Data", (uchar *)pol_u4, (binding.GetCount() + pol.GetCount() * 2) * sizeof(uint));
			_safe_delete_array(pol_u4);
		}
		else
			__LOG_E__ << "Failed to allocate raw polygon buffer.\n";
	}

	// Polygon normals.
	if	(pol_normal)
		if	(Tag *pnormal = root->AddChild("PNormal"))
		{
			pnormal->AddChild("Count", (int)pol.GetCount());

			if	(char *pnrm = new char[pol.GetCount() * 3])
			{
				char *ptr = pnrm;
				for	(n = 0; n < pol.GetCount(); n++)
				{
					*ptr++ = (char)(pol_normal[n].x * 127.f);
					*ptr++ = (char)(pol_normal[n].y * 127.f);
					*ptr++ = (char)(pol_normal[n].z * 127.f);
				}
				pnormal->AddChild("Data", (uchar *)pnrm, pol.GetCount() * 3);
				_safe_delete_array(pnrm);
			}
			else
				__LOG_E__ << "Failed to allocate raw normal buffer.\n";
		}

	// Vertex normals.
	if	(vtx_normal)
		if	(Tag *vnormal = root->AddChild("VNormal"))
		{
			vnormal->AddChild("Count", (int)binding.GetCount());

			if	(char *vnrm = new char[binding.GetCount() * 3])
			{
				char *ptr = vnrm;
				for	(n = 0; n < binding.GetCount(); n++)
				{
					*ptr++ = (char)(vtx_normal[n].x * 127.f);
					*ptr++ = (char)(vtx_normal[n].y * 127.f);
					*ptr++ = (char)(vtx_normal[n].z * 127.f);
				}
				vnormal->AddChild("Data", (uchar *)vnrm, binding.GetCount() * 3);
				_safe_delete_array(vnrm);
			}
			else
				__LOG_E__ << "Failed to allocate raw normal buffer.\n";
		}

	// Vertex tangent frames.
	if	(vtx_tangent)
		if	(Tag *vtangent = root->AddChild("VTangent"))
		{
			vtangent->AddChild("Count", (int)binding.GetCount());

			if	(char *vfrm = new char[binding.GetCount() * 6])
			{
				char *ptr = vfrm;
				for	(n = 0; n < binding.GetCount(); n++)
				{
					*ptr++ = (char)(vtx_tangent[n].T.x * 127.f);
					*ptr++ = (char)(vtx_tangent[n].T.y * 127.f);
					*ptr++ = (char)(vtx_tangent[n].T.z * 127.f);
					*ptr++ = (char)(vtx_tangent[n].B.x * 127.f);
					*ptr++ = (char)(vtx_tangent[n].B.y * 127.f);
					*ptr++ = (char)(vtx_tangent[n].B.z * 127.f);
				}
				vtangent->AddChild("Data", (uchar *)vfrm, binding.GetCount() * 6);
				_safe_delete_array(vfrm);
			}
			else
				__LOG_E__ << "Failed to allocate raw tangent frame buffer.\n";
		}

	// Output RGB.
	if	(rgb)
		if	(Tag *rgbtag = root->AddChild("RGB"))
		{
			// Test if we need to output alpha.
			uint num_comp = 3;
			for	(uint m = 0; m < binding.GetCount(); ++m)
				if	(rgb[m].w < 1.f)
				{
					num_comp = 4;
					break;
				}

			// Output buffer.
			if	(float *rgbbuf = new float[binding.GetCount() * num_comp])
			{
				float *prgb = rgbbuf;
				for	(uint m = 0; m < binding.GetCount(); ++m)
				{
					prgb[0] = rgb[m].x;
					prgb[1] = rgb[m].y;
					prgb[2] = rgb[m].z;
					if	(num_comp == 4)
						prgb[3] = rgb[m].w;
					prgb += num_comp;
				}
				rgbtag->AddChild("Data", (uchar *)rgbbuf, binding.GetCount() * num_comp * sizeof(float));
				_safe_delete_array(rgbbuf);
			}
		}

	// Output UVs.
	if	(GetUVCount())
		if	(Tag *uvtag = root->AddChild("UV"))
		{
			uvtag->AddChild("Count", (int)GetUVCount());	// LEGACY

			if	(float *uvbuf = new float[binding.GetCount() * 2])
			{
				for	(n = 0; n < __UV_PER_GEOMETRY__; ++n)
					if	(uv[n])
					{
						for	(uint m = 0; m < binding.GetCount(); m++)
						{
							uvbuf[m * 2 + 0] = uv[n][m].x;
							uvbuf[m * 2 + 1] = uv[n][m].y;
						}
						uvtag->AddChild("Data", (uchar *)uvbuf, binding.GetCount() * 2 * sizeof(float));
					}
				_safe_delete_array(uvbuf);
			}
			else
				__LOG_E__ << "Failed to allocate raw UVs buffer.\n";
		}

	// Output vertex weights.
	if	(bone_name.GetCount())
	{
		Tag *skintag = root->AddChild("Skin");

		// Save bone id list.
		if	(Tag *bonetag = skintag->AddChild("Bones"))
			for (uint n = 0; n < bone_name.GetCount(); ++n)
				bonetag->AddChild("Id", bone_name[n]);
		else
			__LOG_E__ << "Failed to allocate bone id tag.\n";

		// Save bone binding matrix.
		if	(Tag *bindtag = skintag->AddChild("Binds"))
			for (uint n = 0; n < bone_name.GetCount(); ++n)
				bindtag->AddChild(bone_bind_matrix[n].AsMetaTag("Matrix"));
		else
			__LOG_E__ << "Failed to allocate bone binding tag.\n";

		// Save bone association/weights.
		Tag *weighttag = skintag->AddChild("Weights");
		for	(uint n = 0; n < vtx.GetCount(); ++n)
			weighttag->AddChild
						(
							"W", String::Format
							(	"%d,%d,%d,%d:%d,%d,%d,%d",
								skin[n].bone_index[0], skin[n].bone_index[1], skin[n].bone_index[2], skin[n].bone_index[3],
								(int)(skin[n].w[0] * 255.f), (int)(skin[n].w[1] * 255.f), (int)(skin[n].w[2] * 255.f), (int)(skin[n].w[3] * 255.f)).toUtf8()
						);
	}

	// Proxies.
	if	(!lod_proxy.IsEmpty())
		root->AddChild("LodProxy", lod_proxy.c_str());
	if	(lod_distance != Units::Mtr(100.f))
		root->AddChild("LodDistance", lod_distance);
	if	(flag.IsSet(FlagNullLodProxy))
		root->AddChild("LodNull", true);
	if	(!shadow_proxy.IsEmpty())
		root->AddChild("ShadowProxy", shadow_proxy.c_str());
	if	(flag.IsSet(FlagNullShadowProxy))
		root->AddChild("ShadowNull", true);

	// Serialize materials.
	if	(Tag *mtag = root->AddChild("Materials"))
		for	(n = 0; n < material_table.GetCount(); n++)
			if	(Tag *extag = mtag->AddChild("MaterialRefEx"))
			{
				extag->AddChild("Name", material_table[n].name.c_str());
				if	(!material_table[n].use_cache)
					extag->AddChild("UseCache", material_table[n].use_cache);
			}

	// Copyright lock.
	if	(!copy_lock.IsEmpty())
		root->AddChild("CopyLock", copy_lock);

	return root;
}
//------------------------------------------------------------------------------
