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


		#include <cmath>
		#include "raytracer/raytracer_core.h"
		#include "core/shader_block.h"
		#include "core/geometry.h"
		#include "core/object.h"

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


//------------------------------------------------------------------------------
bool			Raytracer::EvaluateShaderBlock(const Trace &trace, const ShaderBlock *block, ShaderBlockValue &out) 
{
	ShaderBlockValue in[4];		// No more than 4 inputs supported.

	// Validity check.
	if	(!block)
		return true;

	// Evaluate inputs.
	for (uint n = 0; n < block->GetInputCount(); ++n)
		if	(!EvaluateShaderBlock(trace, block->GetInput(n), in[n]))
			return false;

	// Evaluate block.
	switch (block->type)
	{
		case ShaderBlock::TypeGeometryVertex:
			out.Set(trace.pi * trace.o->GetInverseMatrix());
			break;

		case ShaderBlock::TypeGeometryNormal:
			if	(Vector4 *nrm = trace.g->vtx_normal)
			{
				Vector4 &nm0 = nrm[trace.bi],
						&nm1 = nrm[trace.bi + trace.it + 1],
						&nm2 = nrm[trace.bi + trace.it + 2];

				out.Set(nm0 * trace.w + nm1 * trace.u + nm2 * trace.v);
			}
			break;

		case ShaderBlock::TypeGeometryVertexColor:
			if	(Vector4 *rgb = trace.g->rgb)
			{
				Vector4	&cl0 = rgb[trace.bi],
						&cl1 = rgb[trace.bi + trace.it + 1],
						&cl2 = rgb[trace.bi + trace.it + 2];

				out.Set(cl0 * trace.w + cl1 * trace.u + cl2 * trace.v);
			}
			break;

		case ShaderBlock::TypeGeometryUV:
		{
			GeometryUVShaderBlock *b = (GeometryUVShaderBlock *)block;

			if	(Vector2 *uv = trace.g->uv[b->channel])
			{
				Vector2	&uv0 = uv[trace.bi],
					&uv1 = uv[trace.bi + trace.it + 1],
					&uv2 = uv[trace.bi + trace.it + 2];

				out.Set(Vector4(trace.w * uv0.x + trace.u * uv1.x + trace.v * uv2.x, trace.w * uv0.y + trace.u * uv1.y + trace.v * uv2.y, 0, 0));
			}
		}
		break;

		case ShaderBlock::TypeGeometrySkinning:
			break;
		case ShaderBlock::TypeGeometryTangentFrame:
		{
			Vector4	sample = (	trace.g->vtx_normal[trace.bi] * trace.w +
								trace.g->vtx_normal[trace.bi + trace.it + 1] * trace.u +
								trace.g->vtx_normal[trace.bi + trace.it + 2] * trace.v	).Normalized();

			Vector4	T, B;

			if	(trace.g->vtx_tangent)
			{
				// Interpolated tangent basis.
				T =	(trace.g->vtx_tangent[trace.bi + 0].T * trace.w +
						trace.g->vtx_tangent[trace.bi + trace.it + 1].T * trace.u +
						trace.g->vtx_tangent[trace.bi + trace.it + 2].T * trace.v	).Normalized();
				B =	(trace.g->vtx_tangent[trace.bi + 0].B * trace.w +
						trace.g->vtx_tangent[trace.bi + trace.it + 1].B * trace.u +
						trace.g->vtx_tangent[trace.bi + trace.it + 2].B * trace.v	).Normalized();
			}
			else
			{
				T.Set(1, 0, 0);
				B.Set(0, 1, 0);
			}

			// Build tangent frame.
			Matrix3 tangent_matrix(T, B, sample);
			out.Set(tangent_matrix);
		}
		break;

		case ShaderBlock::TypeTexture:
			out.Set(((TextureShaderBlock *)block)->texture);
			break;

		case ShaderBlock::TypeTextureSampler:
		{
			if	(in[0].t)
			{
				Color sample;
				if	(Picture *p = gf->LoadPicture(in[0].t))
					p->SampleRGBA(in[1].v.x < 0 ? 1 + fmodf(in[1].v.x, 1) : fmodf(in[1].v.x, 1), in[1].v.y < 0 ? 1 + fmodf(in[1].v.y, 1) : fmodf(in[1].v.y, 1), sample);
				out.Set(sample);
			}
			else
				out.Set(Vector4(0, 0, 0));
		}
		break;

		case ShaderBlock::TypeConstant:
		{
			ConstantShaderBlock *b = (ConstantShaderBlock *)block;
			out.Set(Vector4(b->constant[0], b->constant[1], b->constant[2], b->constant[3]));
		}
		break;

		case ShaderBlock::TypeColor:
		{
			ColorShaderBlock *b = (ColorShaderBlock *)block;
			out.Set(b->color);
		}
		break;

		case ShaderBlock::TypeMaterialParam:
		{
			MaterialParamShaderBlock *b = (MaterialParamShaderBlock *)block;
			switch (b->param)
			{
				case MaterialParamShaderBlock::MaterialAmbient:
					out.Set(trace.m->ambient);
					break;
				case MaterialParamShaderBlock::MaterialDiffuse:
					out.Set(trace.m->diffuse);
					break;
				case MaterialParamShaderBlock::MaterialSpecular:
					out.Set(trace.m->specular);
					break;
				case MaterialParamShaderBlock::MaterialSelf:
					out.Set(trace.m->self);
					break;
				case MaterialParamShaderBlock::MaterialGlossiness:
					out.Set(trace.m->glossiness);
					break;
				case MaterialParamShaderBlock::MaterialOpacity:
					out.Set(trace.m->opacity);
					break;
				case MaterialParamShaderBlock::MaterialReflection:
					out.Set(trace.m->reflection);
					break;
			}
		}
		break;

		case ShaderBlock::TypeScreenUV:
			out.Set(Vector4(0.5f,0.5f,0.5f));
			break;
		case ShaderBlock::TypeViewVector:
			out.Set(trace.d);
			break;

		case ShaderBlock::TypeNormalViewMatrix:
			out.Set(Matrix3::FromOrthonormalBasis(trace.d).Transposed() * trace.o->GetRotationMatrix());
			break;
		case ShaderBlock::TypeNormalMatrix:
			out.Set(trace.o->GetRotationMatrix());
			break;
		case ShaderBlock::TypeModelViewMatrix:
		{
			Matrix4 view_matrix = Matrix4::FromMatrix3(Matrix3::FromOrthonormalBasis(trace.d).Transposed());
			view_matrix.SetRow(3, trace.s.Reversed());
			out.Set(view_matrix * trace.o->GetMatrix());
		}
		break;
		case ShaderBlock::TypeModelMatrix:
			out.Set(trace.o->GetMatrix());
			break;

		case ShaderBlock::TypeMix:	out.Set(in[0].v * in[2].v.x + in[1].v * (1 - in[2].v.x));	break;
		case ShaderBlock::TypeAdd:	out.Set(in[0].v + in[1].v);		break;
		case ShaderBlock::TypeMul:
		{
			if	(in[0].type == in[1].type)
				switch	(in[0].type)
				{
					case ShaderBlockValue::BlockValueVector:	out.Set(in[0].v * in[1].v);		break;
					case ShaderBlockValue::BlockValueMatrix3:	out.Set(in[0].m3 * in[1].m3);	break;
					case ShaderBlockValue::BlockValueMatrix4:	out.Set(in[0].m4 * in[1].m4);	break;
				}
			else
			{
				ShaderBlockValue *_a = &in[0], *_b = &in[1];
				if	(_b->type < _a->type)
				{	ShaderBlockValue *tmp = _a; _a = _b; _b = tmp;		}

				if	(_a->type == ShaderBlockValue::BlockValueVector)
				{
							if	(_b->type == ShaderBlockValue::BlockValueMatrix3)
							out.Set(_a->v * _b->m3);
					else	if	(_b->type == ShaderBlockValue::BlockValueMatrix4)
							out.Set(_a->v * _b->m4);
				}
			}
		}
		break;

		case ShaderBlock::TypeSub:		out.Set(in[0].v - in[1].v);			break;
		case ShaderBlock::TypeDiv:		out.Set(in[0].v / in[1].v);			break;

		case ShaderBlock::TypeDot:		out.Set(in[0].v.Dot(in[1].v));		break;
		case ShaderBlock::TypeCross:	out.Set(in[0].v.Cross(in[1].v));	break;

		case ShaderBlock::TypeClamp:
			out.Set(Vector4(
								Types::Clamp(in[0].v.x, in[1].v.x, in[2].v.x),
								Types::Clamp(in[0].v.y, in[1].v.y, in[2].v.y),
								Types::Clamp(in[0].v.z, in[1].v.z, in[2].v.z),
								Types::Clamp(in[0].v.w, in[1].v.w, in[2].v.w)	)	);
			break;

		case ShaderBlock::TypeNormalize:	out.Set(in[0].v.Normalized());	break;

		case ShaderBlock::TypeSwizzle:
		{
			SwizzleShaderBlock *b = (SwizzleShaderBlock *)block;

			Vector4	v(0, 0, 0);
			for (int n = 0; n < 4; ++n)
				if	(b->swizzle[n] != SwizzleShaderBlock::SwizzleNone)
					v[n] = in[0].v[b->swizzle[n] - SwizzleShaderBlock::SwizzleX];

			out.Set(v);
		}
		break;

		case ShaderBlock::TypeBuild:
		{
			BuildShaderBlock *b = (BuildShaderBlock *)block;

			Vector4	v(0, 0, 0);
			for (int n = 0; n < 4; ++n)
			{
						if	(b->build[n] == BuildShaderBlock::BuildOne)
						v[n] = 1;
				else	if	(b->build[n] == BuildShaderBlock::BuildZero)
						v[n] = 0;
				else	v[n] = in[n].v[b->build[n] - BuildShaderBlock::BuildX];
			}

			out.Set(v);
		}
		break;

		case ShaderBlock::TypeSin:	out.Set(sin(in[0].v.x));	break;
		case ShaderBlock::TypeCos:	out.Set(cos(in[0].v.x));	break;

		case ShaderBlock::TypeUnpackColorToVector:
			out.Set((in[0].v - Vector4(0.5, 0.5, 0.0)) * Vector4(2.0, 2.0, 1.0));
			break;
		case ShaderBlock::TypePackVectorToColor:
			out.Set((in[0].v + Vector4(1.0, 1.0, 0.0)) * Vector4(0.5, 0.5, 1.0));
			break;

		case ShaderBlock::TypeClock:
			out.Set(render_clock);
			break;

		case ShaderBlock::TypePow:
			out.Set(float(pow(in[0].v.x, in[1].v.x)));
			break;

		case ShaderBlock::TypeAbs:
			if	(in[0].type == ShaderBlockValue::BlockValueVector)
			{
				Vector4	o = in[0].v.Abs();
				out.Set(Vector4(o.x, o.y, o.z));
			}
			break;
	}
	return true;
}
//------------------------------------------------------------------------------
