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


		#include <math.h>
		#include "raytracer/raytracer_core.h"
		#include "core/camera.h"
		#include "core/geometry.h"
		#include "core/shader_block.h"
		#include "scene3d/scene.h"

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


//------------------------------------------------------------------------------
GS::Vector4	Raytracer::SampleMaterialSink(const Trace &trace, ShaderTree::ShaderSinkType sink)
{
	if	(trace.st)
	{
		// Evaluate sink.
		if	(ShaderBlock *block = trace.st->sink[sink])
		{
			ShaderBlockValue block_out;
			if	(EvaluateShaderBlock(trace, block, block_out))
				return block_out.v;
		}

		// Default values.
		switch (sink)
		{
			case ShaderTree::SinkNormal:		return Vector4(0, 0, 1);
			case ShaderTree::SinkDiffuse:		return trace.m->diffuse;
			case ShaderTree::SinkModulate:		return Vector4(1, 1, 1);
			case ShaderTree::SinkSpecular:		return trace.m->specular;
			case ShaderTree::SinkGlossiness:	return Vector4(trace.m->glossiness, 0, 0);
			case ShaderTree::SinkConstant:		return Vector4(0, 0, 0);
			case ShaderTree::SinkOpacity:		return Vector4(1, 0, 0);
			case ShaderTree::SinkReflection:	return Vector4(trace.m->reflection, 0, 0);
		}
	}
	return Vector4(1, 0, 0, 1);
}
GS::Vector4	Raytracer::SampleMaterialAttribute(const Trace &trace, MaterialChannel channel)
{
	Color sample(1, 1, 1);
	if	(!trace.m)
		return sample;

	Material::TextureStage *stage = trace.m->GetChannelStage(channel);

	/*
		Texture sampling.
		@TODO This is insanely slow.
	*/
	if	(stage && trace.g)
	{
		float sample_uv_u = 0, sample_uv_v = 0;

		switch (stage->uv_mode)
		{
			case Material::UV_SphericalEnvironment:			
			{
				Vector4	w;
				scene->current_camera->GetInverseMatrix().ApplyRotation(&w, &trace.n);

				// Find the Euler vector from the reflection normal.
				Vector4 euler_vec = trace.d - (w * 2.0f * fabs((w*-1.0f).Dot(trace.d)));
				euler_vec.Normalize();

				// Euler to UV coordinate.
//				float Y = (1.0f - euler_vec.y) * 0.5f;

				Vector4	XZ(euler_vec.x, euler_vec.z, 0.0);
				XZ.Normalize();
				float DotX = /*nVector(1.0f, 0.0f, 0.0f).Dot(XZ)*/XZ.x;

				// Set from -1;1 to 0;1.
				DotX = (1.0f - DotX) * 0.5f;

				float DotY = /*nVector(0.0f, 1.0f, 0.0f).Dot(XZ)*/XZ.y;
				// Set -1 or 1.
				DotY = (DotY >= 0 ? 1.0f :-1.0f);

				float value_angle = DotX * DotY;
				// Set from -1;1 to 0;1.
				value_angle = (1.0f - value_angle) * 0.5f;

				sample_uv_u = DotX;
				sample_uv_v = value_angle;
			}
			break;

			case Material::UV_LSN:
			{
				// Derive UV coordinates from intersection normal.
				Vector4 w;
				scene->current_camera->GetInverseMatrix().ApplyRotation(&w, &trace.n);

				sample_uv_u = w.x * 0.5f + 0.5f;
				sample_uv_v = w.y * 0.5f + 0.5f;
			}
			break;

			case Material::UV_FrontMap:
			{
				// Derive UV coordinated from view item projection matrix.
				Vector4 s;
				scene->current_camera->WorldToScreen(fRect(0, 0, viewport.x, viewport.y), trace.pi, s, false);

				float k_ar = viewport.y / viewport.x;
				sample_uv_u = (s.x - 0.5f) * k_ar + 0.5f;
				sample_uv_v = s.y;
			}
			break;

			case Material::UV_UV:
				// Compute UV from geometry topology.
				if	(Vector2 *uv = (stage->uv_index < __UV_PER_GEOMETRY__) ? &trace.g->uv[stage->uv_index][0] : NULL)
				{
					Vector2 &uv0 = uv[trace.bi], &uv1 = uv[trace.bi + trace.it + 1], &uv2 = uv[trace.bi + trace.it + 2];
					sample_uv_u = trace.w * uv0.x + trace.u * uv1.x + trace.v * uv2.x,
					sample_uv_v = trace.w * uv0.y + trace.u * uv1.y + trace.v * uv2.y;
				}
				break;
		}

		// UV matrix.
		Vector4	sample_uv = Vector4(sample_uv_u, sample_uv_v, 0.0) * stage->uv_matrix;

		// Handle wrapping.
/*
		if	(stage->wrap_u)
		{
			if	(sample_uv.x < 0)
					sample_uv.x = sample_uv.x - (int)sample_uv.x + 1;
			else	sample_uv.x = sample_uv.x - (int)sample_uv.x;
		}
		if	(stage->wrap_v)
		{
			if	(sample_uv.y < 0)
					sample_uv.y = sample_uv.y - (int)sample_uv.y + 1;
			else	sample_uv.y = sample_uv.y - (int)sample_uv.y;
		}
*/
		// FIXME performance bottleneck!
		if	(Picture *p = gf->LoadPicture(stage->t))
			p->SampleRGBA(sample_uv.x, sample_uv.y, sample);
	}
	else
		switch (channel)
		{
			case Channel_Diffuse:
				sample *= trace.m->diffuse;
				break;
			case Channel_Specular:
				sample *= trace.m->specular;
				break;
			case Channel_SelfIllum:
				sample *= trace.m->self;
				break;

			default: break;
		}

	return sample;
}
float		Raytracer::SampleMaterialOpacity(const Trace &trace)
{
	float opacity = 1.f;

	if	(trace.m->GetChannelStage(Channel_Opacity))
	{
		Vector4	sample = SampleMaterialAttribute(trace, Channel_Opacity);
		opacity = sample.w;
	}
	return opacity * trace.m->opacity;
}
//------------------------------------------------------------------------------
