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


		#include "core/geometry_bih.h"
		#include "core/graphic_resource_factory.h"
		#include "metafile/nml_object.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;


//------------------------------------------------------------------------------
bool				GeometryBIH::FastPolyTest(uint ip, Vector4 &s, Vector4 &d, float t_max)
{
	Polygon &pol = geometry->pol[ip];
	Vector4 &pn = geometry->pol_normal[ip];

	float dnv = d.Dot(pn);
	if	(dnv >= 0.f)
		return false;	// backface

	float t = (geometry->vtx[pol.binding[0]].Dot(pn) - s.Dot(pn)) / dnv;
	if	((t < 0) || ((t_max > 0) && (t > t_max)))
		return false;	// Behind origin or beyond ray length.

	// Make sure point is in polygon.
	GeometryBIHAccel &ca = acc[ip];

	// Note that the intersection point if offset toward the polygon origin.
	float pu = s[ca.cu] + d[ca.cu] * t - geometry->vtx[pol.binding[0]][ca.cu];
	float pv = s[ca.cv] + d[ca.cv] * t - geometry->vtx[pol.binding[0]][ca.cv];

	const float ray_epsilon = -0.000001f;

	float *k = ca.k;
	for	(int m = 1; m < (pol.vtx_count - 1); ++m)
	{
		float u = pv * k[0] + pu * k[1], v, w;
		if	(u < ray_epsilon)
			goto next;
		v = pu * k[2] + pv * k[3];
		if	(v < ray_epsilon)
			goto next;
		w = 1 - u - v;
		if	(w < ray_epsilon)
			goto next;

		return true;
next:;
		k += 4;
	}
	return false;
}
void				GeometryBIH::TraceLeaf(BIH::Node *leaf, float tmin, float tmax, BIH::Trace &, void *parm)
{
	GeometryTrace *trace = (GeometryTrace *)parm;

	uint *leaf_indice = (uint *)leaf->p;
	Vector4 *vtx = geometry->vtx;

	// Test each polygon in leaf.
	for	(uint n = 0; n < leaf->count; ++n)
	{
		uint idx = leaf_indice[n];
		Vector4 &pn = geometry->pol_normal[idx];
		Polygon &pol = geometry->pol[idx];
		Material *material = material_table[pol.material].material;

		trace->tri_test += pol.vtx_count - 2;

		// Reject back facing polygons.
		bool backface = false;
		float dnv = trace->d.Dot(pn);

		if	(dnv >= 0.f)
		{
			if	(material->renderword & Material::Render_DoubleSided)
				backface = true;
			else
				continue;
		}
		float t = (vtx[pol.binding[0]].Dot(pn) - trace->s.Dot(pn)) / dnv;

		// Reject intersections further away than the current best one.
		if	(trace->has_i && (t >= trace->i_t))
			continue;
		// Plane is outside ray boundaries.
		if	((t < tmin) || (t > tmax))
			continue;

		// Make sure point is in polygon.
		GeometryBIHAccel &ca = acc[idx];

		// Note that the intersection point if offset toward the polygon origin.
		float pu = trace->s[ca.cu] + trace->d[ca.cu] * t - vtx[pol.binding[0]][ca.cu],
			  pv = trace->s[ca.cv] + trace->d[ca.cv] * t - vtx[pol.binding[0]][ca.cv];

		#define	RAY_EPSILON	-0.000001f

		float *k = ca.k;
		for	(int m = 1; m < (pol.vtx_count - 1); ++m)
		{
			float u = pv * k[0] + pu * k[1], v, w;
			if	(u < RAY_EPSILON)
				goto next;
			v = pu * k[2] + pv * k[3];
			if	(v < RAY_EPSILON)
				goto next;
			w = 1 - u - v;
			if	(w < RAY_EPSILON)
				goto next;

			trace->has_i = true;
			trace->i_t = t;

			trace->ip = idx;
			trace->it = m - 1;
			trace->bi = pol_index[idx];
			trace->u = u;
			trace->v = v;
			trace->w = w;
			trace->g = geometry;
			trace->m = material;
			trace->st =	material_table[pol.material].shader_tree;
			trace->backface = backface;

			break;		// No need to look further.
next:;
			k += 4;
		}
	}
}
void				GeometryBIH::RaytraceGeometry(GeometryTrace &trace, const Vector4 &s, const Vector4 &d, float l)
{
	trace.ip = -1;
	trace.tri_test = 0;
	trace.has_i = false;
	trace.i_t = -1;
	trace.s = s;
	trace.d = d;

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

//------------------------------------------------------------------------------
bool				GeometryBIH::BuildFromGeometry(ResourceFactory &gf, Geometry *g)
{
	Free();

	// Build geometry LUTs.
	geometry = g;
	geometry->ComputePolygonIndex(pol_index);

	// Load geometry resources.
	material_table.Allocate(geometry->material_table.GetCount());
	for	(uint n = 0; n < material_table.GetCount(); ++n)
	{
		sMaterial m(gf.LoadMaterial(geometry->material_table[n].name));

		if	(m.IsNull())
			return false;

		if	(!m->shader.IsEmpty())
		{
			sShaderTree tree(new ShaderTree);
			if	(NML::LoadFromFile(*tree, m->shader))
				material_table[n].shader_tree = tree;
		}
		material_table[n].material = m;
	}

	// Build volume array to build tree.
	Array <MinMax> varray(geometry->pol.GetCount());
	if	(!varray)
		__ERR__(__LOG_E__ << "Failed to allocate volume objects.\n", false);

	if	(!acc.Allocate(geometry->pol.GetCount()))
		__ERR__(__LOG_E__ << "Failed to allocate point in polygon acceleration array.\n", false);

	geometry->ComputePolygonNormal();

	for	(uint n = 0; n < geometry->pol.GetCount(); ++n)
	{
		Polygon &pol = geometry->pol[n];
		if	(pol.vtx_count < 3)
			continue;

		Vector4	*vtx = geometry->vtx;
		Vector4	u_edge = vtx[pol.binding[1]] - vtx[pol.binding[0]],
				v_edge = vtx[pol.binding[2]] - vtx[pol.binding[0]];
		geometry->pol_normal[n] = u_edge.Cross(v_edge).Normalized();

		// Compute plane determinant.
		acc[n].d = -vtx[pol.binding[0]].Dot(geometry->pol_normal[n]);

		// Determine most significant axis.
		Vector4	m = (vtx[pol.binding[1]] - vtx[pol.binding[0]]).Cross(vtx[pol.binding[2]] - vtx[pol.binding[0]]);
		float x = Types::Abs(m.x), y = Types::Abs(m.y), z = Types::Abs(m.z);

		uint axis = 2;
		if	((x >= y) && (x >= z))
			axis = 0;
		else if	((y >= x) && (y >= z))
			axis = 1;

		char	cu = (axis + 1) % 3,
				cv = (axis + 2) % 3;
		acc[n].cu = cu; acc[n].cv = cv;

		// Compute point in triangle fixed coefficients.
		if	(acc[n].k.Allocate((pol.vtx_count - 2) * 4))
		{
			float *pk = acc[n].k.c_ptr();

			for	(int i = 1; i < (pol.vtx_count - 1); ++i)
			{
				Vector4	b = vtx[pol.binding[i + 1]] - vtx[pol.binding[0]],
						c = vtx[pol.binding[i]] - vtx[pol.binding[0]];

				float k = 1.f / (b[cu] * c[cv] - b[cv] * c[cu]);
				*pk++ = b[cu] * k;
				*pk++ = -b[cv] * k;
				*pk++ = c[cv] * k;
				*pk++ = -c[cu] * k;
			}

			// Build volume array.
			varray[n].mn = varray[n].mx = geometry->vtx[pol.binding[0]];
			for	(uint m = 1; m < pol.vtx_count; ++m)
			{
				varray[n].mn.x = Types::Min(vtx[pol.binding[m]].x, varray[n].mn.x);
				varray[n].mn.y = Types::Min(vtx[pol.binding[m]].y, varray[n].mn.y);
				varray[n].mn.z = Types::Min(vtx[pol.binding[m]].z, varray[n].mn.z);
				varray[n].mx.x = Types::Max(vtx[pol.binding[m]].x, varray[n].mx.x);
				varray[n].mx.y = Types::Max(vtx[pol.binding[m]].y, varray[n].mx.y);
				varray[n].mx.z = Types::Max(vtx[pol.binding[m]].z, varray[n].mx.z);
			}
		}
	}
	return Build(geometry->pol.GetCount(), &varray[0]);
}
void				GeometryBIH::Free()
{
	geometry = NULL;
	material_table.Free();

	acc.Free();
	Tree::Free();
}
//------------------------------------------------------------------------------
