// Copyright (C) 2002-2007 Nikolaus Gebhardt / Thomas Alten
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "CQuake3ShaderSceneNode.h"
#include "IVideoDriver.h"
#include "ICameraSceneNode.h"
#include "SViewFrustum.h"
#include "IMeshManipulator.h"
#include "SMesh.h"
#include "IMaterialRenderer.h"

namespace irr
{
namespace scene
{


CQuake3ShaderSceneNode::CQuake3ShaderSceneNode(
			scene::ISceneNode* parent, scene::ISceneManager* mgr,s32 id,
			io::IFileSystem *fileSystem, scene::IMeshBuffer *buffer,
			const quake3::SShader * shader)
	: scene::ISceneNode(parent, mgr, id), Shader ( shader ),TimeAbs ( 0.f )
{
	#ifdef _DEBUG
		core::stringc dName = "CQuake3ShaderSceneNode ";
		dName += Shader->name;

		setDebugName( dName.c_str() );
	#endif

	// name the Scene Node
	this->Name = Shader->name;

	// clone meshbuffer to modifiable buffer
	cloneBuffer ( static_cast< scene::SMeshBufferLightMap *> ( buffer ) );

	// load all Textures in all stages
	loadTextures ( fileSystem );

	setAutomaticCulling ( scene::EAC_BOX );
}


/*
*/
CQuake3ShaderSceneNode::~CQuake3ShaderSceneNode ()
{
}

/*
	create single copies
*/
void CQuake3ShaderSceneNode::cloneBuffer ( scene::SMeshBufferLightMap * buffer )
{
	Original.Material = buffer->Material;
	MeshBuffer.Material = buffer->Material;

	Original.Indices = buffer->Indices;
	MeshBuffer.Indices = buffer->Indices;

	const u32 vsize = buffer->Vertices.size ();

	Original.Vertices.set_used ( vsize );
	MeshBuffer.Vertices.set_used ( vsize );
	for ( u32 i = 0; i!= vsize; ++i )
	{
		const video::S3DVertex2TCoords * src = &buffer->Vertices[i];

		// Original has same Vertex Format
		Original.Vertices[i] = *src;

		// we have a different vertex format
		MeshBuffer.Vertices[i].Pos = src->Pos;
		MeshBuffer.Vertices[i].TCoords = src->TCoords;
		MeshBuffer.Vertices[i].Color = 0xFFFFFFFF;
	}

	MeshBuffer.recalculateBoundingBox ();

#if 1
	// move the (temp) Mesh
	{
	// original bounding box
		core::aabbox3df b = MeshBuffer.getBoundingBox();

		// set Scene Node Position
		setPosition ( b.getCenter() );

		scene::SMesh * mesh = new SMesh ();
		mesh->addMeshBuffer ( &Original );
		mesh->addMeshBuffer ( &MeshBuffer );

		core::matrix4 m;
		m.setTranslation ( -b.getCenter() );
		SceneManager->getMeshManipulator()->transformMesh ( mesh, m );
		mesh->drop ();

		MeshBuffer.recalculateBoundingBox ();
	}
#endif
	// used for sorting
	MeshBuffer.Material.setTexture(0, (video::ITexture*) Shader);
}



/*
	load the textures for all stages
*/
void CQuake3ShaderSceneNode::loadTextures ( io::IFileSystem * fileSystem )
{
	const quake3::SVarGroup *group;
	u32 i;

	// clear all stages and prefill empty
	Q3Texture.clear ();
	for ( i = 0; i != Shader->VarGroup->VariableGroup.size (); ++i )
	{
		Q3Texture.push_back ( SQ3Texture() );
	}

	u32 pos;

	// get texture map
	for ( i = 0; i < Shader->VarGroup->VariableGroup.size (); ++i )
	{
		group = Shader->getGroup ( i );

		const core::stringc &mapname = group->get ( "map" );
		if ( 0 == mapname.size () )
			continue;

		// our lightmap is passed in material.Texture[2]
		if ( mapname == "$lightmap" )
		{
			Q3Texture [i].Texture.push_back ( Original.getMaterial().getTexture(1) );
		}
		else
		{
			pos = 0;
			quake3::getTextures ( Q3Texture [i].Texture, mapname, pos, fileSystem, SceneManager->getVideoDriver() );
		}
	}

	// get anim map
	for ( i = 0; i < Shader->VarGroup->VariableGroup.size (); ++i )
	{
		if ( Q3Texture [i].Texture.size() )
			continue;

		group = Shader->getGroup ( i );

		const core::stringc &animmap = group->get ( "animmap" );
		if ( 0 == animmap.size () )
			continue;

		// first parameter is frequency
		pos = 0;
		Q3Texture [i].TextureFrequency = core::max_ ( 0.0001f, quake3::getAsFloat ( animmap, pos ) );

		quake3::getTextures ( Q3Texture [i].Texture, animmap, pos,fileSystem, SceneManager->getVideoDriver() );
	}

	// get clamp map
	for ( i = 0; i < Shader->VarGroup->VariableGroup.size (); ++i )
	{
		if ( Q3Texture [i].Texture.size() )
			continue;

		group = Shader->getGroup ( i );

		const core::stringc &clampmap = group->get ( "clampmap" );
		if ( 0 == clampmap.size () )
			continue;

		Q3Texture [i].TextureAddressMode = video::ETC_CLAMP;
		pos = 0;
		quake3::getTextures ( Q3Texture [i].Texture, clampmap, pos,fileSystem, SceneManager->getVideoDriver() );
	}
}

/*
	Register each texture stage, if first is visible
*/
void CQuake3ShaderSceneNode::OnRegisterSceneNode()
{
	if ( isVisible() )
	{
		SceneManager->registerNodeForRendering(this, isTransparent () ? ESNRP_TRANSPARENT: ESNRP_SOLID );
	}
	ISceneNode::OnRegisterSceneNode ();
}

/*
	is this a transparent node ?
*/
bool CQuake3ShaderSceneNode::isTransparent ()
{
	bool ret = false;

	// generic stage
	const quake3::SVarGroup *group;
	
	group = Shader->getGroup ( 1 );
/*
	if ( group->isDefined ( "surfaceparm", "nonsolid" ) )
	{
		ret = true;
	}
*/
	if ( group->isDefined ( "surfaceparm", "trans" ) )
	{
		ret = true;
	}
	else
	{
		for ( u32 stage = 0; stage < Shader->VarGroup->VariableGroup.size (); ++stage )
		{
			if ( 0 == Q3Texture [ stage].Texture.size() )
				continue;

			group = Shader->getGroup ( stage );
			quake3::SBlendFunc blendfunc;
			quake3::getBlendFunc ( group->get ( "blendfunc" ), blendfunc );
			quake3::getBlendFunc ( group->get ( "alphafunc" ), blendfunc );

			ret = blendfunc.isTransparent;
			break;
		}
	}

	return ret;
}


/*
	render in multipass technique
*/
void CQuake3ShaderSceneNode::render()
{
	video::IVideoDriver* driver = SceneManager->getVideoDriver();


	bool isTransparentPass =
		SceneManager->getSceneNodeRenderPass() == scene::ESNRP_TRANSPARENT;

	video::SMaterial material;
	const quake3::SVarGroup *group;

	material.Lighting = false;
	material.setTexture(1, 0);
	material.NormalizeNormals = false;

	// generic stage
	group = Shader->getGroup ( 1 );
	material.BackfaceCulling = quake3::isDisabled ( group->get ( "cull" ) );

//	u32 zEnable = group->getIndex ( "polygonoffset" ) >= 0;

	driver->setTransform(video::ETS_WORLD, AbsoluteTransformation );

	u32 drawCount = 0;
	for ( u32 stage = 0; stage < Shader->VarGroup->VariableGroup.size (); ++stage )
	//for ( u32 stage = 2; stage < 3; ++stage )
	{
		SQ3Texture &q = Q3Texture [ stage];

		// advance current stage
		core::matrix4 texture;
		animate ( stage, texture );

		// stage 1 finished, no drawing stage ( vertex transform only )
		if ( 0 == q.Texture.size() )
			continue;

		// current stage
		group = Shader->getGroup ( stage );

		material.setTexture(0, q.Texture [ q.TextureIndex ]);
		material.ZBuffer = quake3::getDepthFunction ( group->get ( "depthfunc" ) );

		if ( group->getIndex ( "depthwrite" ) >= 0 )
		{
			material.ZWriteEnable = true;
		}
		else
		{
			material.ZWriteEnable = drawCount ? false: !isTransparentPass;
		}

		//resolve quake3 blendfunction to irrlicht Material Type
		quake3::SBlendFunc blendfunc;
		quake3::getBlendFunc ( group->get ( "blendfunc" ), blendfunc );
		quake3::getBlendFunc ( group->get ( "alphafunc" ), blendfunc );

		material.MaterialType = blendfunc.type;
		material.MaterialTypeParam = blendfunc.param;

		material.TextureLayer[0].TextureWrap = q.TextureAddressMode;
		material.setTextureMatrix ( 0, texture );

		driver->setMaterial( material );
		driver->drawMeshBuffer( &MeshBuffer );
		drawCount += 1;
	}

	if ( DebugDataVisible & scene::EDS_BBOX )
	{
		video::SMaterial deb_m;
		deb_m.Lighting = false;
		driver->setMaterial(deb_m);
		driver->draw3DBox( getBoundingBox(), video::SColor(0,255,255,255));
	}
}



/*!
*/
void CQuake3ShaderSceneNode::vertextransform_wave ( f32 dt, quake3::SModifierFunction &function )
{
	function.wave = core::reciprocal ( function.wave );

	const f32 phase = function.phase;

	const u32 vsize = MeshBuffer.Vertices.size();
	for ( u32 i = 0; i != vsize; ++i )
	{
		const video::S3DVertex2TCoords &src = Original.Vertices[i];
		video::S3DVertex &dst = MeshBuffer.Vertices[i];

		f32 wavephase = (src.Pos.X + src.Pos.Y + src.Pos.Z) * function.wave;
		function.phase = phase + wavephase;

		const f32 f = function.evaluate ( dt );

		dst.Pos.X = src.Pos.X + f * src.Normal.X;
		dst.Pos.Y = src.Pos.Y + f * src.Normal.Y;
		dst.Pos.Z = src.Pos.Z + f * src.Normal.Z;
	}
}

/*!
*/
void CQuake3ShaderSceneNode::vertextransform_bulge ( f32 dt, quake3::SModifierFunction &function )
{
	function.func = 0;
	function.wave = core::reciprocal ( function.bulgewidth );

	dt *= function.bulgespeed * 0.1f;
	const f32 phase = function.phase;

	const u32 vsize = MeshBuffer.Vertices.size();
	for ( u32 i = 0; i != vsize; ++i )
	{
		const video::S3DVertex2TCoords &src = Original.Vertices[i];
		video::S3DVertex &dst = MeshBuffer.Vertices[i];

		f32 wavephase = (Original.Vertices[i].TCoords.X ) * function.wave;
		function.phase = phase + wavephase;

		const f32 f = function.evaluate ( dt );

		dst.Pos.X = src.Pos.X + f * src.Normal.X;
		dst.Pos.Y = src.Pos.Y + f * src.Normal.Y;
		dst.Pos.Z = src.Pos.Z + f * src.Normal.Z;
	}
}

/*!
*/
void CQuake3ShaderSceneNode::vertextransform_autosprite ( f32 dt, quake3::SModifierFunction &function )
{
	const core::matrix4 &m = SceneManager->getActiveCamera()->getViewFrustum()->Matrices [ video::ETS_VIEW ];
	const core::vector3df view ( -m[2], -m[6] , -m[10] );

	const u32 vsize = MeshBuffer.Vertices.size();

	core::aabbox3df box;
	u32 g;

	for ( u32 i = 0; i < vsize; i += 4 )
	{
		// in pairs of 4
		box.reset ( Original.Vertices[i].Pos );
		for ( g = 1; g != 4; ++g )
		{
			box.addInternalPoint ( Original.Vertices[i + g].Pos );
		}

		core::vector3df c = box.getCenter ();
		f32 sh = 0.5f * ( box.MaxEdge.Z - box.MinEdge.Z );
		f32 sv = 0.5f * ( box.MaxEdge.Y - box.MinEdge.Y );

		const core::vector3df h ( m[0] * sh, m[4] * sh, m[8] * sh );
		const core::vector3df v ( m[1] * sv, m[5] * sv, m[9] * sv );

		MeshBuffer.Vertices[ i + 0 ].Pos = c + h + v;
		MeshBuffer.Vertices[ i + 1 ].Pos = c - h - v;
		MeshBuffer.Vertices[ i + 2 ].Pos = c + h - v;
		MeshBuffer.Vertices[ i + 3 ].Pos = c - h + v;

		MeshBuffer.Vertices[ i + 0 ].Normal = view;
		MeshBuffer.Vertices[ i + 1 ].Normal = view;
		MeshBuffer.Vertices[ i + 2 ].Normal = view;
		MeshBuffer.Vertices[ i + 3 ].Normal = view;
	}
}

/*
	Generate Vertex Color
*/
void CQuake3ShaderSceneNode::vertextransform_rgbgen ( f32 dt, quake3::SModifierFunction &function )
{
	u32 i;
	const u32 vsize = MeshBuffer.Vertices.size();

	switch ( function.masterfunc1 )
	{
		case 6:
			//identity
			for ( i = 0; i != vsize; ++i )
				MeshBuffer.Vertices[i].Color = 0xFFFFFFFF;
			break;
		case 7:
			// vertex
			for ( i = 0; i != vsize; ++i )
				MeshBuffer.Vertices[i].Color = Original.Vertices[i].Color;
			break;
		case 5:
		{
			// wave
			f32 f = function.evaluate ( dt ) * 255.f;
			s32 value = core::clamp ( core::floor32 ( f ), 0, 255 );
			value |= value << 8;
			value |= value << 16;

			for ( i = 0; i != vsize; ++i )
				MeshBuffer.Vertices[i].Color = value;
		} break;
	}
}



/*
	Generate Texture Coordinates
*/
void CQuake3ShaderSceneNode::vertextransform_tcgen ( f32 dt, quake3::SModifierFunction &function )
{
	u32 i;
	const u32 vsize = MeshBuffer.Vertices.size();

	switch ( function.tcgen )
	{
		case 12:
			//tcgen turb
		{
			function.wave = core::reciprocal ( function.wave );

			const f32 phase = function.phase;

			for ( i = 0; i != vsize; ++i )
			{
				const video::S3DVertex2TCoords &src = Original.Vertices[i];
				video::S3DVertex &dst = MeshBuffer.Vertices[i];

				f32 wavephase = (src.Pos.X + src.Pos.Y + src.Pos.Z) * function.wave;
				function.phase = phase + wavephase;

				const f32 f = function.evaluate ( dt );

				dst.TCoords.X = src.TCoords.X + f * src.Normal.X;
				dst.TCoords.Y = src.TCoords.Y + f * src.Normal.Y;
			}
		}
		break;

		case 8:
			// tcgen texture
			for ( i = 0; i != vsize; ++i )
				MeshBuffer.Vertices[i].TCoords = Original.Vertices[i].TCoords;
			break;
		case 9:
			// tcgen lightmap
			for ( i = 0; i != vsize; ++i )
				MeshBuffer.Vertices[i].TCoords = Original.Vertices[i].TCoords2;
			break;
		case 10:
		{
			// tcgen environment ( D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR )

			// using eye linear, sphere map may be cooler;-)
			// modelmatrix is identity
			const core::matrix4 &view = SceneManager->getActiveCamera()->getViewFrustum()->Matrices [ video::ETS_VIEW ];
			const core::matrix4 &viewinverse = SceneManager->getActiveCamera()->getViewFrustum()->Matrices [ SViewFrustum::ETS_VIEW_MODEL_INVERSE_3 ];

			// eyePlane
			core::vector3df eyePlaneS;
			core::vector3df eyePlaneT;

			viewinverse.transformVect ( eyePlaneS, core::vector3df ( 1.f, 0.f, 0.f ) );
			viewinverse.transformVect ( eyePlaneT, core::vector3df ( 0.f, 1.f, 0.f ) );

			eyePlaneS.normalize ();
			eyePlaneT.normalize ();

			core::vector3df v;
			for ( i = 0; i != vsize; ++i )
			{
				// vertex in eye space
				view.transformVect ( v, Original.Vertices[i].Pos );
				v.normalize();

				MeshBuffer.Vertices[i].TCoords.X = (1.f + eyePlaneS.dotProduct ( v ) ) * 0.5f;
				MeshBuffer.Vertices[i].TCoords.Y = 1.f - ( (1.f + eyePlaneT.dotProduct ( v ) ) * 0.5f );
			}

		} break;

	}
}



/*
	Transform Texture Coordinates
*/
void CQuake3ShaderSceneNode::transformtex ( const core::matrix4 &m, const u32 addressMode )
{
	u32 i;
	const u32 vsize = MeshBuffer.Vertices.size();

	f32 tx1;
	f32 ty1;

	if ( addressMode )
	{
		for ( i = 0; i != vsize; ++i )
		{
			core::vector2df &tx = MeshBuffer.Vertices[i].TCoords;

			tx1 = m[0] * tx.X + m[4] * tx.Y + m[8];
			ty1 = m[1] * tx.X + m[5] * tx.Y + m[9];

			tx.X = tx1;
			tx.Y = ty1;
		}
	}
	else
	{

		for ( i = 0; i != vsize; ++i )
		{
			core::vector2df &tx = MeshBuffer.Vertices[i].TCoords;

			tx1 = m[0] * tx.X + m[4] * tx.Y + m[8];
			ty1 = m[1] * tx.X + m[5] * tx.Y + m[9];

			tx.X = tx1 <= 0.f ? 0.f : tx1 >= 1.f ? 1.f : tx1;
			tx.Y = ty1 <= 0.f ? 0.f : ty1 >= 1.f ? 1.f : ty1;

			//tx.X = core::clamp ( tx1, 0.f, 1.f );
			//tx.Y = core::clamp ( ty1, 0.f, 1.f );
		}
	}
}




/*
	Texture & Vertex Transform Animator

	Return a Texture Transformation for this stage
	Vertex transformation are called if found

*/
void CQuake3ShaderSceneNode::animate( u32 stage,core::matrix4 &texture )
{
	const quake3::SVarGroup *group = Shader->getGroup ( stage );

	// select current texture
	if ( Q3Texture [ stage ].TextureFrequency != 0.f )
	{
		s32 v = core::floor32 ( TimeAbs * Q3Texture [ stage ].TextureFrequency );
		Q3Texture [ stage ].TextureIndex = v % Q3Texture [ stage ].Texture.size();
	}

	core::matrix4 m2;
	quake3::SModifierFunction function;

	f32 f0;
	f32 f1;

	// walk group for all modifiers
	for ( u32 g = 0; g != group->Variable.size (); ++g )
	{
		const quake3::SVariable &v = group->Variable[g];

		// get the modifier 
		static const c8 * modifierList[] =
		{ 
			"tcmod","deformvertexes","rgbgen","tcgen","map"
		};

		u32 pos = 0;
		function.masterfunc0 = quake3::isEqual ( v.name, pos, modifierList, 5 );

		if ( -2 == function.masterfunc0 )
			continue;

		switch ( function.masterfunc0 )
		{
			//tcmod
			case 0:
				m2.makeIdentity ();
				break;
		}

		// get the modifier function
		static const c8 * funclist[] =
		{ 
			"scroll","scale","rotate","stretch","turb",
			"wave","identity","vertex",
			"texture","lightmap","environment","$lightmap",
			"bulge","autosprite","autosprite2"
		};

		pos = 0;
		function.masterfunc1 = quake3::isEqual ( v.content, pos, funclist, 14 );

		switch ( function.masterfunc1 )
		{
			case 0:
				// scroll
				f0 = quake3::getAsFloat ( v.content, pos ) * TimeAbs;
				f1 = quake3::getAsFloat ( v.content, pos ) * TimeAbs;
				m2.setTextureTranslate ( -f0, f1 );
				break;
			case 1:
				// scale
				f0 = quake3::getAsFloat ( v.content, pos );
				f1 = quake3::getAsFloat ( v.content, pos );
				m2.setTextureScale ( f0, f1 );
				break;
			case 2:
				//rotate
				m2.setTextureRotationCenter ( quake3::getAsFloat ( v.content, pos ) * core::DEGTORAD * TimeAbs );
				m2 = m2.getTransposed();
				break;
			case 3:
			case 4:
			case 5:
			case 6:
			case 7:
			{
				// turb == sin, default == sin
				function.func = 0;

				if ( function.masterfunc1 == 5 && function.masterfunc0 == 1)
				{
					// deformvertexes, wave
					function.wave = quake3::getAsFloat ( v.content, pos );
				}

				if (	function.masterfunc1 == 3 ||
						function.masterfunc1 == 4 ||
						function.masterfunc1 == 5
						)
				{
					// stretch, wave, tub
					quake3::getModifierFunc ( function, v.content, pos );
				}

				switch ( function.masterfunc1 )
				{
					case 3:
						// stretch
						f0 = core::reciprocal ( function.evaluate ( TimeAbs ) );
						m2.setTextureScaleCenter ( f0, f0 );
						m2 = m2.getTransposed();
						break;
					case 4:
					{
						// turb
/*
						quake3::SModifierFunction ampFunction;
						ampFunction.func = 0;
						ampFunction.freq = function.freq;
						ampFunction.base = 1.f;
						ampFunction.amp = 0.2f;
						f2 = ampFunction.evaluate ( TimeAbs );

						f0 = function.evaluate ( TimeAbs );
						function.func = 1;
						f1 = function.evaluate ( TimeAbs );

						m2.setTextureTranslate ( f0, f1 );
						m2.setTextureScaleCenter ( f2, f2 );
						//m2.setTextureScale ( f2, f2 );
*/
					}break;
					case 5:
						if ( function.masterfunc0 == 1 )
						{
							vertextransform_wave ( TimeAbs, function );
						}
						else
						{
							vertextransform_rgbgen ( TimeAbs, function );
						}
						break;
					case 6:
					case 7:
						vertextransform_rgbgen ( TimeAbs, function );
						break;
				}
				
			} break;
			case 8:
			case 9:
			case 10:
				// "texture","lightmap","environment"
				function.tcgen = function.masterfunc1;
				break;
			case 11:
				// map == lightmap, tcgen == lightmap
				function.tcgen = 9;
				break;
			case 12:
				// deformvertexes bulge
				function.bulgewidth = quake3::getAsFloat ( v.content, pos );
				function.bulgeheight = quake3::getAsFloat ( v.content, pos );
				function.bulgespeed = quake3::getAsFloat ( v.content, pos );

				vertextransform_bulge ( TimeAbs, function );
				break;

			case 13:
			case 14:
				// deformvertexes autosprite
				vertextransform_autosprite ( TimeAbs, function);
				break;

		} // func

		switch ( function.masterfunc0 )
		{
			case 0:
				texture *= m2;
				break;
		}

	} // group

	// texture coordinate modifier
	vertextransform_tcgen ( TimeAbs, function);
}



void CQuake3ShaderSceneNode::OnAnimate(u32 timeMs)
{
	TimeAbs = f32( timeMs ) * ( 1.f/1000.f);
	ISceneNode::OnAnimate ( timeMs );
}

const core::aabbox3d<f32>& CQuake3ShaderSceneNode::getBoundingBox() const
{
	return MeshBuffer.getBoundingBox ();
}


u32 CQuake3ShaderSceneNode::getMaterialCount() const
{
	return Q3Texture.size();
}

video::SMaterial& CQuake3ShaderSceneNode::getMaterial(u32 i)
{
	video::SMaterial& m = MeshBuffer.getMaterial();
	m.setTexture(0, 0);
	if ( Q3Texture [ i ].TextureIndex )
		m.setTexture(0, Q3Texture [ i ].Texture [ Q3Texture [ i ].TextureIndex ]);
	return m;
}	


} // end namespace scene
} // end namespace irr

