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


		#include "core/object.h"
		#include "core/camera.h"
		#include "core/geometry.h"
		#include "core/resource_factories.h"
		#include "core/render_resource_factory.h"
		#include "timing/benchmark.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;

		float Object::lod_bias = 0.f;


//------------------------------------------------------------------------------
void				Object::RenderSetup(ResourceFactories *f)
{
	render_data = new RenderData;

	if	(f && f->render)
	{
		render_data->geometry = f->render->LoadGeometry(geometry, !cache_geometry);

		if	(render_data->geometry.IsValid())
		{
			// Assert skin correctness.
			uint bone_count = render_data->geometry->bone_bind_matrix.GetCount();

			if	(bone_count > 0)
			{
				if	(skin.IsNull() || (skin->bones_mtx.GetCount() != bone_count))
				{
					AllocateSkin(render_data->geometry->bone_bind_matrix.GetCount());

					// Initialize skin to the binding position.
					for	(uint n = 0; n < render_data->geometry->bone_bind_matrix.GetCount(); ++n)
						skin->previous_bones_mtx[n] = skin->bones_mtx[n] = render_data->geometry->bone_bind_matrix[n];
				}
			}
			else
				FreeSkin();
		}
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static float		GetGeometrySortValue(const Matrix4 &vm, Render::Geometry *g, const Matrix4 &gm)
{	return vm.GetRow(2).Dot((g->hotspot * gm) - vm.GetRow(3));	}

void				Object::ComputeRenderableMinMax(MinMax &mm)
{
	ComputeLocalMinMax(mm);
	OBB obb = OBB::FromMinMax(mm);
	obb.Transform(GetMatrix());
	obb.ComputeMinMax(mm);
}
uint				Object::GetRenderablePrimitiveList(const Camera &view, const Camera &default_view, Stack <Render::Primitive *> &list, Renderable::Context context, bool cull)
{
	Render::Geometry *g = render_data.IsValid() ? render_data->geometry.c_ptr() : NULL;
	if	(!g)
		return 1;

	// Recursive LOD selection.
	{
		float d2 = Vector4::Dist2(default_view.GetMatrix().GetRow(3), GetMatrix().GetRow(3)) + lod_bias * lod_bias;

		d2 /= GetScale().Len2(); // [EJ] scale affects lod selection

		for	( ; g; g = g->lod_proxy)
		{
			// If closer than lod, stop there.
			if	(d2 < (g->lod_distance * g->lod_distance))
				break;

			// Null lod.
			if	(g->flag.IsSet(Geometry::FlagNullLodProxy))
				return 1;

			// No lod to follow, stop there.
			if	(g->lod_proxy.IsNull())
				break;
		}
	}

	// Check context proxy on the selected lod.
	if	(context == Renderable::Context_Shadow)
	{
		// Null proxy.
		if	(g->flag.IsSet(Geometry::FlagNullShadowProxy))
			return 1;

		if	(g->shadow_proxy.IsValid())
			g = g->shadow_proxy;
	}

	// Test geometry hidden flag.
	if	(g->flag.IsSet(Geometry::FlagHidden))
		return 1;

	// Culling.
	if	(cull)
	{
		MinMax minmax;
		ComputeLocalMinMax(minmax);

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

	list.Push(new Render::Primitive(g, this, GetGeometrySortValue(view.GetMatrix(), g, GetMatrix())));

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

//------------------------------------------------------------------------------
bool				Object::GetBindMatrix(uint n, Matrix4 &m) const
{
	if	(render_data.IsNull() || render_data->geometry.IsNull())
		return false;
	if	(n >= render_data->geometry->bone_bind_matrix.GetCount())
		return false;

	m = render_data->geometry->bone_bind_matrix[n];
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Object::ComputeLocalMinMax(MinMax &minmax) const
{
	if	(render_data.IsNull() || render_data->geometry.IsNull())
		Item::ComputeLocalMinMax(minmax);

	else
	{
		if	(HasSkin())
		{
			// Compute skin minmax.
			OBB obb;
			for (uint n = 0; n < GetBoneCount(); ++n)
				if	(skin->ComputeBoneBoundingVolume(n, obb))
				{
					MinMax bmm;
					obb.ComputeMinMax(bmm);
					if	(n)
							minmax.Grow(bmm);
					else	minmax = bmm;
				}
		}
		else
			minmax = render_data->geometry->minmax;
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Object::UpdateSkin()
{
	if	(render_data.IsValid() && render_data->geometry.IsValid())
		for (uint n = 0; n < GetBoneCount(); ++n)
			if	(Item *bone = GetBone(n))
			{
				skin->previous_bones_mtx[n] = GetInverseMatrix() * (bone->GetPreviousMatrix() * render_data->geometry->bone_bind_matrix[n]);
				skin->bones_mtx[n] = GetInverseMatrix() * (bone->GetMatrix() * render_data->geometry->bone_bind_matrix[n]);
			}
			else
			{
				skin->previous_bones_mtx[n] = Matrix4::IdentityMatrix();
				skin->bones_mtx[n] = Matrix4::IdentityMatrix();
			}
}
bool				Object::BindBone(uint n, Item *bone)
{
	if	(skin.IsNull() || (n >= skin->bones.GetCount()))
		return false;

	skin->bones[n] = bone;
	if	(bone)
		bone->item_flags.Set(ItemFlagBoneHint);
	return true;
}
bool				Object::AllocateSkin(uint bone_count)
{
	skin = new Skin;

	if	(!skin->bones.Allocate(bone_count) || !skin->bones_mtx.Allocate(bone_count) || !skin->previous_bones_mtx.Allocate(bone_count) || !skin->bones_minmax.Allocate(bone_count))
	{
		FreeSkin();
		__ERR__(__LOG_E__ << "Failed to allocate skinning structure.\n", false);
	}
	for (uint n = 0; n < bone_count; ++n)
	{
		skin->bones[n] = NULL;
		skin->bones_mtx[n] = Matrix4::IdentityMatrix();
		skin->previous_bones_mtx[n] = Matrix4::IdentityMatrix();
	}
	return true;
}
void				Object::FreeSkin()
{
	skin = NULL;
}
//------------------------------------------------------------------------------

Object::Object() : cache_geometry(true) {}
