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


		#include "raytracer/raytracer_scene.h"
		#include "scene3d/mobject.h"
		#include "scene3d/mlight.h"
		#include "scene3d/scene.h"
		#include "scene3d/instance.h"
		#include "scene3d/group.h"
		#include "core/geometry_bih.h"
		#include "metafile/nml_object.h"

		using namespace GS;
		using namespace GS::Core;
		using namespace GS::Raytrace;


//------------------------------------------------------------------------------
void				SceneBIH::ResetStats()
{
	ray_count = 0;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				SceneBIH::TraceLeaf(BIH::Node *leaf, float tmin, float tmax, BIH::Trace &trace, void *parm)
{
	Vector4 &s = trace.s, &d = trace.d;
	Trace *s_trace = (Trace *)parm;
	uint *leaf_indice = (uint *)leaf->p;

	for	(uint n = 0; n < leaf->count; ++n)
	{
		Core::Object *o = obj[leaf_indice[n]].o;
		IGeometryTree *tree = obj[leaf_indice[n]].tree;

		// Raytrace object in local space.
		Vector4 local_s = s * o->GetInverseMatrix(), local_d;
		o->GetInverseMatrix().ApplyRotation(&local_d, &d);

		GeometryTrace geo_trace;
		tree->RaytraceGeometry(geo_trace, local_s, local_d, tmax);

		if	(!geo_trace.has_i)
			continue;

		// Integrate result.
		if	(!s_trace->has_i || (geo_trace.i_t < s_trace->i_t))
		{
			/*
				Note:	Do not copy the complete geo_trace, we do not want
						to duplicate trace stacks.
			*/
			*((GeometryTraceBase *)s_trace) = ((GeometryTraceBase &)geo_trace);

			s_trace->has_i = true;

			s_trace->o = o;
			s_trace->tri_test += geo_trace.tri_test;
		}
	}
}
void				SceneBIH::RaytraceScene(Trace &trace, const Vector4 &s, const Vector4 &d, float l)
{
	ray_count++;

	trace.s = s;
	trace.d = d;

	BIH::Trace bih_trace;
	Tree::Raytrace(bih_trace, s, d, l, (void *)&trace);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Geometry			*SceneBIH::TranslateGeometry(Geometry *g) const
{
	if	(obj && g)
		for	(uint n = 0; n < obj.GetCount(); ++n)
			if	(obj[n].g == g)
				return obj[n].og;
	return g;
}
void				SceneBIH::AddObject(ResourceFactory &gf, S3D::MObject *o, uint &obj_count, MinMax *varray, bool shadow)
{
	if	(!o->isActive())
		return;
	if	(o->geometry.IsEmpty() || !o->GetBaseItem()->opacity)
		return;
	if	(o->mitem_flags.IsSet(S3D::MItem::Flag_IsHelper | S3D::MItem::Flag_EditorHidden | S3D::MItem::Flag_EditorLocked))
		return;

	// Grab object geometry.
	Geometry *g = gf.LoadGeometry(o->geometry);
	if	(!g)
		return;

	obj[obj_count].og = g;	// Store original geometry to map back from skinned geometry.
	if	(!g->material_table.GetCount() || !g->pol.GetCount())
		return;

	if	(shadow)
	{
		if	(g->flag.IsSet(Geometry::FlagNullShadowProxy))
			return;
		if	(!g->shadow_proxy.IsEmpty())
			g = gf.LoadGeometry(g->shadow_proxy);
	}

	// Perform skinning.
	if	(o->HasSkin() && g->skin)
	{
		Skin *skin = o->GetSkin();

		// Serialize geometry.
		using namespace NML;

		File file;
		file.AddRoot(g->AsMetaTag());

		Geometry *sg = new Geometry;
		LoadFromFile(*sg, file);

		// Build required structures upfront.
		sg->ComputeVertexNormal();
		sg->ComputeVertexTangent();

		// Vertex skinning.
		for	(uint n = 0; n < sg->vtx.GetCount(); ++n)
		{
			Vector4 v(0, 0, 0);
			for	(int b = 0; b < 4; ++b)
			{
				if	(!sg->skin[n].w[b])
					break;
				v += (sg->vtx[n] * skin->bones_mtx[sg->skin[n].bone_index[b]]) * sg->skin[n].w[b];
			}
			sg->vtx[n] = v;
		}

		// Normal skinning.
		Vector4 s, w;
		int tt = 0;
		for (uint p = 0; p < sg->pol.GetCount(); ++p)
			for (uint n = 0; n < sg->pol[p].vtx_count; ++n)
			{
				s.Set(0, 0, 0);
				for	(int b = 0; b < 4; ++b)
				{
					int i = sg->pol[p].binding[n];
					if	(!sg->skin[i].w[b])
						break;
					skin->bones_mtx[sg->skin[i].bone_index[b]].ApplyRotation(&w, &sg->vtx_normal[tt]);
					s += w * sg->skin[i].w[b];
				}
				sg->vtx_normal[tt++] = s;
			}

			// Tangent base skinning.
			Vector4 _b, _t;
			tt = 0;
			for (uint p = 0; p < sg->pol.GetCount(); ++p)
				for (uint n = 0; n < sg->pol[p].vtx_count; ++n)
				{
					_b.Set(0, 0, 0);
					_t.Set(0, 0, 0);
					for	(int b = 0; b < 4; ++b)
					{
						int i = sg->pol[p].binding[n];
						if	(!sg->skin[i].w[b])
							break;

						skin->bones_mtx[sg->skin[i].bone_index[b]].ApplyRotation(&w, &sg->vtx_tangent[tt].B);
						_b += w * sg->skin[i].w[b];
						skin->bones_mtx[sg->skin[i].bone_index[b]].ApplyRotation(&w, &sg->vtx_tangent[tt].T);
						_t += w * sg->skin[i].w[b];
					}
					sg->vtx_tangent[tt].B = _b;
					sg->vtx_tangent[tt].T = _t;
					tt++;
				}

				// Use as the base geometry.
				// but first copy the material from the base material
				sg->material_table.Allocate(g->material_table.GetCount());
				for	(uint k = 0; k < g->material_table.GetCount(); ++k)
					sg->material_table[k] = g->material_table[k];

				g = sg;
	}

	// Prepare geometry.
	IGeometryTree *tree = new GeometryBIHTree;
	tree->BuildFromGeometry(gf, g);

	// Build minmax for the transformed geometry.
	varray[obj_count] = g->ComputeMinMax(&o->GetMatrix());

	obj[obj_count].g = g;
	obj[obj_count].tree = tree;
	obj[obj_count++].o = o;
}
bool				SceneBIH::SetScene(ResourceFactory &gf, const S3D::Scene *s, bool shadow)
{
	Free();

	using namespace S3D;

	SharedList <MObject *> objects;
	s->GetItemListByType(objects);
	SharedList <Instance *> instances;
	s->GetItemListByType(instances);

	// Grab the scene content.
	uint obj_count = 0;
	ListForeachPtr(MObject *, o, objects)
		if	(!o->geometry.IsEmpty())
			obj_count++;

	// Count the instance objects.
	ListForeachPtr(Instance *, i, instances)
	{
		if	(!i->instance_scene)
		{
			if	(!(i->instance_scene = new Scene(s->GetVM())))
				continue;

			i->instance_scene->FromMetaFileStoreGroup(i->template_path, &i->instance_group, SceneIOObject | SceneIOLight);
			if	(i->instance_group != NULL)
				i->instance_group->SetRootItem(i);
		}

		if	(i->instance_group)
			ListForeachPtr(MItem *, ig, i->instance_group->GetItemList())
				if	(ig->GetItemType() == Type_Object && !((MObject *)ig)->geometry.IsEmpty())
					obj_count++;
	}

	if	(!obj_count)
		return true;

	if	(!obj.Allocate(obj_count))
		__ERR__(__LOG_E__ << "Failed to grab scene to raytracer.\n", false)

	// Build scene tree and object trees.
	Array <MinMax> varray(obj_count);
	if	(!varray)
		__ERR__(__LOG_E__ << "Failed to allocate volume array to build scene tree.\n", false)

	obj_count = 0;

	ListForeachPtr(MObject *, o, objects)
		AddObject(gf, o, obj_count, varray, shadow);

	// Add the instance objects.
	ListForeachPtr(Instance *, i, instances)
		if	(i->instance_group)
			ListForeachPtr(MItem *, ig, i->instance_group->GetItemList())
				if	(ig->GetItemType() == Type_Object && !((MObject *)ig)->geometry.IsEmpty())
					AddObject(gf, ((MObject *)ig), obj_count, varray, shadow);

	// Build tree.
	if	(!Build(obj_count, varray))
		return false;
	return true;
}
void				SceneBIH::Free()
{
	obj.Free();
	Tree::Free();
}
//------------------------------------------------------------------------------
