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

#include "IrrCompileConfig.h"

#ifdef _IRR_COMPILE_WITH_BSP_LOADER_

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

namespace irr
{
namespace scene
{

// who, if not you..
using namespace quake3;

/*!
*/
CQuake3ShaderSceneNode::CQuake3ShaderSceneNode(
			scene::ISceneNode* parent, scene::ISceneManager* mgr,s32 id,
			io::IFileSystem *fileSystem, scene::IMeshBuffer *original,
			const IShader * shader)
: scene::IMeshSceneNode(parent, mgr, id, 
		core::vector3df(0.f, 0.f, 0.f),
		core::vector3df(0.f, 0.f, 0.f),
		core::vector3df(1.f, 1.f, 1.f)),
	Shader(shader), Mesh(0), Original(0), MeshBuffer(0), 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;

	// take lightmap vertex type
	MeshBuffer = new SMeshBuffer();

	Mesh = new SMesh ();
	Mesh->addMeshBuffer ( MeshBuffer );
	MeshBuffer->drop ();

	//Original = new SMeshBufferLightMap();
	Original = (scene::SMeshBufferLightMap*) original;
	Original->grab();

	// clone meshbuffer to modifiable buffer
	cloneBuffer( MeshBuffer, (scene::SMeshBufferLightMap*) original, 
							original->getMaterial().ColorMask != 0
						);

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

	setAutomaticCulling( scene::EAC_OFF );

}

/*!
*/
CQuake3ShaderSceneNode::~CQuake3ShaderSceneNode()
{
	if (Mesh)
		Mesh->drop();

	if (Original)
		Original->drop();
}



/*
	create single copies
*/
void CQuake3ShaderSceneNode::cloneBuffer( scene::SMeshBuffer *dest, scene::SMeshBufferLightMap * buffer, bool translateCenter )
{
	dest->Material = buffer->Material;
	dest->Indices = buffer->Indices;

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

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

		dst.Pos = src.Pos;
		dst.Normal = src.Normal;
		dst.Color = 0xFFFFFFFF;
		dst.TCoords = src.TCoords;

		if ( i == 0 )
			dest->BoundingBox.reset ( src.Pos );
		else
			dest->BoundingBox.addInternalPoint ( src.Pos );
	}

	// move the (temp) Mesh to a ScenePosititon
	// set Scene Node Position

	if ( translateCenter )
	{
		MeshOffset = dest->BoundingBox.getCenter();
		setPosition( MeshOffset );

		core::matrix4 m;
		m.setTranslation( -MeshOffset );
		SceneManager->getMeshManipulator()->transform( dest, m );
	}
	

	// No Texture!. Use Shader-Pointer for sorting
	dest->Material.setTexture(0, (video::ITexture*) Shader);
}


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

	video::IVideoDriver *driver = SceneManager->getVideoDriver();

	// generic stage
	u32 mipmap = 0;
	group = Shader->getGroup( 1 );
	if ( group->isDefined ( "nomipmaps" ) )
	{
		mipmap = 2 | (driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS)? 1: 0 );
		driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
	}

	// clear all stages and prefill empty
	Q3Texture.setAllocStrategy ( core::ALLOC_STRATEGY_SAFE );
	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;
			getTextures( Q3Texture [i].Texture, mapname, pos, fileSystem, driver );
		}
	}

	// 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, getAsFloat( animmap, pos ) );

		getTextures( Q3Texture [i].Texture, animmap, pos,fileSystem, driver );
	}

	// 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_TO_EDGE;
		pos = 0;
		getTextures( Q3Texture [i].Texture, clampmap, pos,fileSystem, driver );
	}

	if ( mipmap & 2 )
		driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, mipmap & 1);
}

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

/*
	is this a transparent node ?
*/
E_SCENE_NODE_RENDER_PASS CQuake3ShaderSceneNode::getRenderStage() const
{
	E_SCENE_NODE_RENDER_PASS ret = ESNRP_SOLID;

	// generic stage
	const SVarGroup *group;

	group = Shader->getGroup( 1 );
/*
	else
	if ( group->getIndex( "portal" ) >= 0 )
	{
		ret = ESNRP_TRANSPARENT_EFFECT;
	}
	else
*/	
	if ( group->isDefined( "sort", "opaque" ) )
	{
		ret = ESNRP_SOLID;
	}
	else
	if ( group->isDefined( "sort", "additive" ) )
	{
		ret = ESNRP_TRANSPARENT;
	}
	else
	if (	strstr ( Shader->name.c_str(), "flame" ) ||
			group->isDefined( "surfaceparm", "water" ) ||
			group->isDefined( "sort", "underwater" ) ||
			group->isDefined( "sort", "underwater" )
		)
	{
		ret = ESNRP_TRANSPARENT_EFFECT;
	}
	else
	{
		// Look if first drawing stage needs graphical underlay
		for ( u32 stage = 2; stage < Shader->VarGroup->VariableGroup.size(); ++stage )
		{
			if ( 0 == Q3Texture [ stage ].Texture.size() )
				continue;

			group = Shader->getGroup( stage );

			SBlendFunc blendfunc ( video::EMFN_MODULATE_1X );
			getBlendFunc( group->get( "blendfunc" ), blendfunc );
			getBlendFunc( group->get( "alphafunc" ), blendfunc );

			//ret = blendfunc.isTransparent ? ESNRP_TRANSPARENT : ESNRP_SOLID;
			if ( blendfunc.isTransparent )
			{
				ret = ESNRP_TRANSPARENT;
			}
			break;
		}
	}

	return ret;
}


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

	video::SMaterial material;
	const SVarGroup *group;

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

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

	u32 pushProjection = 0;
	core::matrix4 projection ( core::matrix4::EM4CONST_NOTHING );

	// decal ( solve z-fighting )
	if ( group->isDefined( "polygonoffset" ) )
	{
		projection = driver->getTransform( video::ETS_PROJECTION );

		core::matrix4 decalProjection ( projection );

/*
		f32 n = SceneManager->getActiveCamera()->getNearValue();
		f32 f = SceneManager->getActiveCamera()->getFarValue ();

		f32 delta = 0.01f;
		f32 pz = 0.2f;
		f32 epsilon = -2.f * f * n * delta / ( ( f + n ) * pz * ( pz + delta ) );
		decalProjection[10] *= 1.f + epsilon;
*/
		// TODO: involve camera
		decalProjection[10] -= 0.0002f;
		driver->setTransform( video::ETS_PROJECTION, decalProjection );
		pushProjection |= 1;
	}

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

	//! render all stages
	u32 drawCount = (pass == ESNRP_TRANSPARENT_EFFECT) ? 1 : 0;
	core::matrix4 textureMatrix ( core::matrix4::EM4CONST_NOTHING );
	for ( u32 stage = 1; stage < Shader->VarGroup->VariableGroup.size(); ++stage )
	{
		SQ3Texture &q = Q3Texture[stage];

		// advance current stage
		textureMatrix.makeIdentity();
		animate( stage, textureMatrix );

		// stage finished, no drawing stage ( vertex transform only )
		video::ITexture * tex = q.Texture.size() ? q.Texture [ q.TextureIndex ] : 0;
		if ( 0 == tex )
			continue;

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

		material.setTexture(0, tex );
		material.ZBuffer = getDepthFunction( group->get( "depthfunc" ) );

		if ( group->isDefined( "depthwrite" ) )
		{
			material.ZWriteEnable = true;
		}
		else
		{
			material.ZWriteEnable = drawCount == 0;
		}

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

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

		material.TextureLayer[0].TextureWrap = q.TextureAddressMode;
		//material.TextureLayer[0].TrilinearFilter = 1;
		//material.TextureLayer[0].AnisotropicFilter = 0xFF;
		material.setTextureMatrix( 0, textureMatrix );

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

	}

	if ( DebugDataVisible & scene::EDS_MESH_WIRE_OVERLAY )
	{
		video::SMaterial deb_m;
		deb_m.Wireframe = true;
		deb_m.Lighting = false;
		deb_m.BackfaceCulling = material.BackfaceCulling;
		driver->setMaterial( deb_m );

		driver->drawMeshBuffer( MeshBuffer );
	}

	// show normals
	if ( DebugDataVisible & scene::EDS_NORMALS )
	{
		video::SMaterial deb_m;

		IAnimatedMesh * arrow = SceneManager->addArrowMesh (
				"__debugnormalq3", 
				0xFFECEC00,0xFF999900, 
				4, 8,
				8.f, 6.f,
				0.5f,1.f
			);
		if ( 0 == arrow )
		{
			arrow = SceneManager->getMesh ( "__debugnormalq3" );
		}
		const IMesh *mesh = arrow->getMesh ( 0 );

		// find a good scaling factor

		core::matrix4 m2;

		// draw normals
		const scene::IMeshBuffer* mb = MeshBuffer;
		const u32 vSize = video::getVertexPitchFromType(mb->getVertexType());
		const video::S3DVertex* v = ( const video::S3DVertex*)mb->getVertices();

		//f32 colCycle = 270.f / (f32) core::s32_max ( mb->getVertexCount() - 1, 1 );

		for ( u32 i=0; i != mb->getVertexCount(); ++i )
		{
			// Align to v->normal
			m2.buildRotateFromTo ( core::vector3df ( 0.f, 1.f, 0 ), v->Normal );
			m2.setTranslation ( v->Pos + AbsoluteTransformation.getTranslation () );
/*
			core::quaternion quatRot( v->Normal.Z, 0.f, -v->Normal.X, 1 + v->Normal.Y );
			quatRot.normalize();
			quatRot.getMatrix ( m2, v->Pos );

			m2 [ 12 ] += AbsoluteTransformation [ 12 ];
			m2 [ 13 ] += AbsoluteTransformation [ 13 ];
			m2 [ 14 ] += AbsoluteTransformation [ 14 ];
*/
			driver->setTransform(video::ETS_WORLD, m2 );

			deb_m.Lighting = true;
/*
			irr::video::SColorHSL color;
			irr::video::SColor rgb(0);
			color.Hue = i * colCycle * core::DEGTORAD;
			color.Saturation = 1.f;
			color.Luminance = 0.5f;
			color.toRGB(  deb_m.EmissiveColor );
*/
			switch ( i )
			{
				case 0: deb_m.EmissiveColor.set(0xFFFFFFFF); break;
				case 1: deb_m.EmissiveColor.set(0xFFFF0000); break;
				case 2: deb_m.EmissiveColor.set(0xFF00FF00); break;
				case 3: deb_m.EmissiveColor.set(0xFF0000FF); break;
				default:
					deb_m.EmissiveColor = v->Color; break;
			}
			driver->setMaterial( deb_m );

			for ( u32 a = 0; a != mesh->getMeshBufferCount(); ++a )
				driver->drawMeshBuffer ( mesh->getMeshBuffer ( a ) );

			v = (const video::S3DVertex*) ( (u8*) v + vSize );
		}
		driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
	}


	if ( pushProjection & 1 )
	{
		driver->setTransform( video::ETS_PROJECTION, projection );
	}

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

}


/*!
3.3.1 deformVertexes wave <div> <func> <base> <amplitude> <phase> <freq>
	Designed for water surfaces, modifying the values differently at each point.
	It accepts the standard wave functions of the type sin, triangle, square, sawtooth
	or inversesawtooth. The "div" parameter is used to control the wave "spread" 
	- a value equal to the tessSize of the surface is a good default value
	(tessSize is subdivision size, in game units, used for the shader when seen in the game world) .
*/
void CQuake3ShaderSceneNode::deformvertexes_wave( f32 dt, SModifierFunction &function )
{
	function.wave = core::reciprocal( function.wave );

	const f32 phase = function.phase;

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

		if ( 0 == function.count )
			dst.Pos = src.Pos - MeshOffset;

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

		const f32 f = function.evaluate( dt );

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

		if ( i == 0 )
			MeshBuffer->BoundingBox.reset ( dst.Pos );
		else
			MeshBuffer->BoundingBox.addInternalPoint ( dst.Pos );
	}
	function.count = 1;
}

/*!
	deformVertexes move x y z func base amplitude phase freq
	The move parameter is used to make a brush, curve patch or model 
	appear to move together as a unit. The x y z values are the distance
	and direction in game units the object appears to move relative to 
	it's point of origin in the map. The func base amplitude phase freq values are
	the same as found in other waveform manipulations.

	The product of the function modifies the values x, y, and z.
	Therefore, if you have an amplitude of 5 and an x value of 2, 
	the object will travel 10 units from its point of origin along the x axis. 
	This results in a total of 20 units of motion along the x axis, since the
	amplitude is the variation both above and below the base.

	It must be noted that an object made with this shader does not actually
	change position, it only appears to.

	Design Notes:
	If an object is made up of surfaces with different shaders, all must have 
	matching deformVertexes move values or the object will appear to tear itself apart.
*/
void CQuake3ShaderSceneNode::deformvertexes_move( f32 dt, SModifierFunction &function )
{
	function.wave = core::reciprocal( function.wave );
	const f32 f = function.evaluate( dt );

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

		if ( 0 == function.count )
			dst.Pos = src.Pos - MeshOffset;

		dst.Pos.X += f * function.x;
		dst.Pos.Y += f * function.y;
		dst.Pos.Z += f * function.z;

		if ( i == 0 )
			MeshBuffer->BoundingBox.reset ( dst.Pos );
		else
			MeshBuffer->BoundingBox.addInternalPoint ( dst.Pos );
	}
	function.count = 1;

}

/*!
	3.3.2 deformVertexes normal <div> <func> <base> <amplitude ~0.1-~0.5> <frequency ~1.0-~4.0>
		This deformation affects the normals of a vertex without actually moving it,
		which will effect later shader options like lighting and especially environment mapping.
		If the shader stages don't use normals in any of their calculations, there will
		be no visible effect.

		Design Notes: Putting values of 0.1 t o 0.5 in Amplitude and 1.0 to 4.0 in the
		Frequency can produce some satisfying results. Some things that have been 
		done with it: A small fluttering bat, falling leaves, rain, flags.
*/
void CQuake3ShaderSceneNode::deformvertexes_normal( f32 dt, SModifierFunction &function )
{
	function.func = SINUS;
	const u32 vsize = Original->Vertices.size();
	for ( u32 i = 0; i != vsize; ++i )
	{
		const video::S3DVertex2TCoords &src = Original->Vertices[i];
		video::S3DVertex &dst = MeshBuffer->Vertices[i];

		function.base = atan2f ( src.Pos.X, src.Pos.Y );
		function.phase = src.Pos.X + src.Pos.Z;

		const f32 lat = function.evaluate( dt );

		function.base = src.Normal.Y;
		function.phase = src.Normal.Z + src.Normal.X;

		const f32 lng = function.evaluate( dt );

		dst.Normal.X = cosf ( lat ) * sinf ( lng );
		dst.Normal.Y = sinf ( lat ) * sinf ( lng );
		dst.Normal.Z = cosf ( lng );

	}
}


/*!
	3.3.3 deformVertexes bulge <bulgeWidth> <bulgeHeight> <bulgeSpeed>
	This forces a bulge to move along the given s and t directions. Designed for use
	on curved pipes.

	Specific parameter definitions for deform keywords:
		<div>	This is roughly defined as the size of the waves that occur.
				It is measured in game units. Smaller values create a greater 
				density of smaller wave forms occurring in a given area. 
				Larger values create a lesser density of waves, or otherwise put,
				the appearance of larger waves. To look correct this value should 
				closely correspond to the value (in pixels) set for tessSize (tessellation size)
				of the texture. A value of 100.0 is a good default value 
				(which means your tessSize should be close to that for things to look "wavelike").

		<func>	This is the type of wave form being created. Sin stands for sine wave, 
				a regular smoothly flowing wave. Triangle is a wave with a sharp ascent
				and a sharp decay. It will make a choppy looking wave forms.
				A square wave is simply on or off for the period of the 
				frequency with no in between. The sawtooth wave has the ascent of a
				triangle wave, but has the decay cut off sharply like a square wave. 
				An inversesawtooth wave reverses this.

		<base>	This is the distance, in game units that the apparent surface of the 
				texture is displaced from the actual surface of the brush as placed 
				in the editor. A positive value appears above the brush surface. 
				A negative value appears below the brush surface. 
				An example of this is the Quad effect, which essentially is a 
				shell with a positive base value to stand it away from the model 
				surface and a 0 (zero) value for amplitude.

		<amplitude> The distance that the deformation moves away from the base value. 
					See Wave Forms in the introduction for a description of amplitude.

		<phase> See Wave Forms in the introduction for a description of phase)

		<frequency> See Wave Forms in the introduction for a description of frequency)

		Design Note: The div and amplitude parameters, when used in conjunction with
		liquid volumes like water should take into consideration how much the water
		will be moving. A large ocean area would have have massive swells (big div values) 
		that rose and fell dramatically (big amplitude values). While a small, quiet pool
		may move very little.
*/
void CQuake3ShaderSceneNode::deformvertexes_bulge( f32 dt, SModifierFunction &function )
{
	function.func = SINUS;
	function.wave = core::reciprocal( function.bulgewidth );

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

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

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

		const f32 f = function.evaluate( dt );

		if ( 0 == function.count )
			dst.Pos = src.Pos - MeshOffset;

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

		if ( i == 0 )
			MeshBuffer->BoundingBox.reset ( dst.Pos );
		else
			MeshBuffer->BoundingBox.addInternalPoint ( dst.Pos );
	}

	function.count = 1;
}

						
/*!
	deformVertexes autosprite

	This function can be used to make any given triangle quad 
	(pair of triangles that form a square rectangle) automatically behave
	like a sprite without having to make it a separate entity. This means 
	that the "sprite" on which the texture is placed will rotate to always 
	appear at right angles to the player's view as a sprite would. Any four-sided
	brush side, flat patch, or pair of triangles in a model can have the autosprite
	effect on it. The brush face containing a texture with this shader keyword must
	be square.
*/
void CQuake3ShaderSceneNode::deformvertexes_autosprite( f32 dt, SModifierFunction &function )
{
	u32 vsize = Original->Vertices.size();
	u32 g;
	u32 i;

	const core::vector3df& camPos = SceneManager->getActiveCamera()->getPosition();

	video::S3DVertex * dv = MeshBuffer->Vertices.pointer();
	const video::S3DVertex2TCoords * vin = Original->Vertices.pointer();

	core::matrix4 lookat ( core::matrix4::EM4CONST_NOTHING );
	core::quaternion q;
	for ( i = 0; i < vsize; i += 4 )
	{
		// quad-plane
		core::vector3df center = 0.25f * ( vin[i+0].Pos + vin[i+1].Pos + vin[i+2].Pos + vin[i+3].Pos );
		core::vector3df forward = camPos - center;

		q.rotationFromTo ( vin[i].Normal, forward );
		q.getMatrixCenter ( lookat, center, MeshOffset );

		for ( g = 0; g < 4; ++g )
		{
			lookat.transformVect ( dv[i+g].Pos, vin[i+g].Pos );
			lookat.rotateVect ( dv[i+g].Normal, vin[i+g].Normal );
		}

	}
	function.count = 1;
}


/*!
	deformVertexes autosprite2
	Is a slightly modified "sprite" that only rotates around the middle of its longest axis.
	This allows you to make a pillar of fire that you can walk around, or an energy beam 
	stretched across the room.
*/

struct sortaxis
{
	core::vector3df v;
	bool operator < ( const sortaxis &other ) const
	{
		return v.getLengthSQ () < other.v.getLengthSQ ();
	}
};
/*!
*/
void CQuake3ShaderSceneNode::deformvertexes_autosprite2( f32 dt, SModifierFunction &function )
{
	u32 vsize = Original->Vertices.size();
	u32 g;
	u32 i;

	const core::vector3df camPos = SceneManager->getActiveCamera()->getAbsolutePosition();

	video::S3DVertex * dv = MeshBuffer->Vertices.pointer();
	const video::S3DVertex2TCoords * vin = Original->Vertices.pointer();

	core::matrix4 lookat ( core::matrix4::EM4CONST_NOTHING );

	core::array < sortaxis > axis;
	axis.set_used ( 3 );

	for ( i = 0; i < vsize; i += 4 )
	{
		// quad-plane
		core::vector3df center = 0.25f * ( vin[i+0].Pos + vin[i+1].Pos + vin[i+2].Pos + vin[i+3].Pos );

		// longes axe
		axis[0].v = vin[i+1].Pos - vin[i+0].Pos;
		axis[1].v = vin[i+2].Pos - vin[i+0].Pos;
		axis[2].v = vin[i+3].Pos - vin[i+0].Pos;
		axis.set_sorted ( false );
		axis.sort ();

		lookat.buildAxisAlignedBillboard ( camPos, center, MeshOffset, axis[1].v, vin[i+0].Normal );

		for ( g = 0; g < 4; ++g )
		{
			lookat.transformVect ( dv[i+g].Pos, vin[i+g].Pos );
			lookat.rotateVect ( dv[i+g].Normal, vin[i+g].Normal );
		}
	}
	function.count = 1;
}

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

	switch ( function.rgbgen )
	{
		case IDENTITY:
			//rgbgen identity
			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].Color.set(0xFFFFFFFF);
			break;

		case IDENTITYLIGHTING:
			// rgbgen identitylighting TODO: overbright
			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].Color.set(0xFF7F7F7F);
			break;

		case EXACTVERTEX:		
			// alphagen exactvertex TODO lighting
		case VERTEX:
			// rgbgen vertex
			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].Color=Original->Vertices[i].Color;
			break;
		case WAVE:
		{
			// rgbGen wave <func> <base> <amp> <phase> <freq>
			f32 f = function.evaluate( dt ) * 255.f;
			s32 value = core::clamp( core::floor32(f), 0, 255 );
			value = 0xFF000000 | value << 16 | value << 8 | value;

			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].Color.set(value);
		} break;
		case CONSTANT:
		{
			//rgbgen const ( x y z )
			video::SColorf cf( function.x, function.y, function.z );
			video::SColor col = cf.toSColor();
			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].Color=col;
		} break;
		default:
			break;
	}
}

/*
	Generate Vertex Color, Alpha
*/
void CQuake3ShaderSceneNode::vertextransform_alphagen( f32 dt, SModifierFunction &function )
{
	u32 i;
	const u32 vsize = Original->Vertices.size();

	switch ( function.alphagen )
	{
		case IDENTITY:
			//alphagen identity
			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].Color.setAlpha ( 0xFF );
			break;

		case EXACTVERTEX:	
			// alphagen exactvertex TODO lighting
		case VERTEX:
			// alphagen vertex
			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].Color.setAlpha ( Original->Vertices[i].Color.getAlpha() );
			break;
		case CONSTANT:
		{
			// alphagen const
			u32 a = (u32) ( function.x * 255.f );
			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].Color.setAlpha ( a );
		} break;

		case LIGHTINGSPECULAR:
		{
			// alphagen lightingspecular TODO!!!
			const SViewFrustum *frustum = SceneManager->getActiveCamera()->getViewFrustum();
			const core::matrix4 &view = frustum->getTransform ( video::ETS_VIEW );

			const f32 *m = view.pointer();

			for ( i = 0; i != vsize; ++i )
			{
				const core::vector3df &n = Original->Vertices[i].Normal;
				MeshBuffer->Vertices[i].Color.setAlpha ((u32)( 128.f *(1.f+(n.X*m[0]+n.Y*m[1]+n.Z*m[2]))));
			}

		} break;


		case WAVE:
		{
			// alphagen wave
			f32 f = function.evaluate( dt ) * 255.f;
			s32 value = core::clamp( core::floor32(f), 0, 255 );

			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].Color.setAlpha ( value );
		} break;
		default:
			break;
	}
}



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

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

			const f32 phase = function.phase;

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

				const 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 TEXTURE:
			// tcgen texture
			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].TCoords = Original->Vertices[i].TCoords;
			break;
		case LIGHTMAP:
			// tcgen lightmap
			for ( i = 0; i != vsize; ++i )
				MeshBuffer->Vertices[i].TCoords = Original->Vertices[i].TCoords2;
			break;
		case ENVIRONMENT:
		{
			// tcgen environment
			const SViewFrustum *frustum = SceneManager->getActiveCamera()->getViewFrustum();
			const core::matrix4 &view = frustum->getTransform ( video::ETS_VIEW );

			const f32 *m = view.pointer();

			core::vector3df n;
			for ( i = 0; i != vsize; ++i )
			{
				//const core::vector3df &n = Original->Vertices[i].Normal;

				n = frustum->cameraPosition - Original->Vertices[i].Pos;
				n.normalize();
				n += Original->Vertices[i].Normal;
				n.normalize();

				MeshBuffer->Vertices[i].TCoords.X = 0.5f*(1.f+(n.X*m[0]+n.Y*m[1]+n.Z*m[2])); 
				MeshBuffer->Vertices[i].TCoords.Y = 0.5f*(1.f+(n.X*m[4]+n.Y*m[5]+n.Z*m[6])); 
			}

		} break;
		default:
			break;
	}
}


#if 0
/*
	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 );
		}
	}
}

#endif


/*
	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 SVarGroup *group = Shader->getGroup( stage );

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

	core::matrix4 m2;
	SModifierFunction function;

	f32 f[16];

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

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

		u32 pos = 0;
		function.masterfunc0 = (eQ3ModifierFunction) isEqual( v.name, pos, modifierList, 6 );

		if ( UNKNOWN == function.masterfunc0 )
			continue;

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

		// get the modifier function
		static const c8 * funclist[] =
		{ 
			"scroll","scale","rotate","stretch","turb",
			"wave","identity","vertex",
			"texture","lightmap","environment","$lightmap",
			"bulge","autosprite","autosprite2","transform",
			"exactvertex","const","lightingspecular","move","normal",
			"identitylighting"
		};
		static const c8 * groupToken[] = { "(", ")" };

		pos = 0;
		function.masterfunc1 = (eQ3ModifierFunction) isEqual( v.content, pos, funclist, 22 );
		if ( function.masterfunc1 != UNKNOWN )
			function.masterfunc1 = (eQ3ModifierFunction) ((u32) function.masterfunc1 + FUNCTION2 + 1);

		switch ( function.masterfunc1 )
		{
			case SCROLL:
				// tcMod scroll <sSpeed> <tSpeed>
				f[0] = getAsFloat( v.content, pos ) * TimeAbs;
				f[1] = getAsFloat( v.content, pos ) * TimeAbs;
				m2.setTextureTranslate( f[0], f[1] );
				break;
			case SCALE:
				// tcmod scale <sScale> <tScale>
				f[0] = getAsFloat( v.content, pos );
				f[1] = getAsFloat( v.content, pos );
				m2.setTextureScale( f[0], f[1] );
				break;
			case ROTATE:
				// tcmod rotate <degress per second>
				m2.setTextureRotationCenter(	getAsFloat( v.content, pos ) * 
												core::DEGTORAD *
												TimeAbs
											);
				break;
			case TRANSFORM:
				// tcMod <transform> <m00> <m01> <m10> <m11> <t0> <t1>
				memset(f, 0, sizeof ( f ));
				f[10] = f[15] = 1.f;

				f[0] = getAsFloat( v.content, pos );
				f[1] = getAsFloat( v.content, pos );
				f[4] = getAsFloat( v.content, pos );
				f[5] = getAsFloat( v.content, pos );
				f[8] = getAsFloat( v.content, pos );
				f[9] = getAsFloat( v.content, pos );
				m2.setM ( f );
				break;

			case STRETCH:	// stretch
			case TURBULENCE: // turb
			case WAVE: // wave
			case IDENTITY: // identity
			case IDENTITYLIGHTING:
			case VERTEX: // vertex
			case MOVE:
			case CONSTANT:
			{
				// turb == sin, default == sin
				function.func = SINUS;

				if ( function.masterfunc0 == DEFORMVERTEXES )
				{
					switch ( function.masterfunc1 )
					{
						case WAVE:
							// deformvertexes wave
							function.wave = getAsFloat( v.content, pos );
							break;
						case MOVE:
							//deformvertexes move
							function.x = getAsFloat( v.content, pos );
							function.z = getAsFloat( v.content, pos );
							function.y = getAsFloat( v.content, pos );
							break;
						default:
							break;
					}
				}

				switch ( function.masterfunc1 )
				{
					case STRETCH:
					case TURBULENCE:
					case WAVE:
					case MOVE:
						getModifierFunc( function, v.content, pos );
						break;
					default:
						break;
				}

				switch ( function.masterfunc1 )
				{
					case STRETCH:
						//tcMod stretch <func> <base> <amplitude> <phase> <frequency>
						f[0] = core::reciprocal( function.evaluate(TimeAbs) );
						m2.setTextureScaleCenter( f[0], f[0] );
						break;
					case TURBULENCE:
						//tcMod turb <base> <amplitude> <phase> <freq>
						//function.tcgen = TURBULENCE;
						m2.setTextureRotationCenter(	function.frequency * 
														core::DEGTORAD * 
														TimeAbs
													);
						break;
					case WAVE:
					case IDENTITY:
					case IDENTITYLIGHTING:
					case VERTEX:
					case EXACTVERTEX:
					case CONSTANT:
					case LIGHTINGSPECULAR:
					case MOVE:
						switch ( function.masterfunc0 )
						{
							case DEFORMVERTEXES:
								switch ( function.masterfunc1 )
								{
									case WAVE:
										deformvertexes_wave( TimeAbs, function );
										break;
									case MOVE:
										deformvertexes_move( TimeAbs, function );
										break;
									default:
										break;
								}
								break;
							case RGBGEN:
								function.rgbgen = function.masterfunc1;
								if ( function.rgbgen == CONSTANT )
								{
									isEqual ( v.content, pos, groupToken, 2 );
									function.x = getAsFloat( v.content, pos );
									function.y = getAsFloat( v.content, pos );
									function.z = getAsFloat( v.content, pos );
								}
								//vertextransform_rgbgen( TimeAbs, function );
								break;
							case ALPHAGEN:
								function.alphagen = function.masterfunc1;
								if ( function.alphagen == CONSTANT )
								{
									function.x = getAsFloat( v.content, pos );
								}

								//vertextransform_alphagen( TimeAbs, function );
								break;
							default:
								break;
						}
						break;
					default:
						break;
				}
				
			} break;
			case TEXTURE:
			case LIGHTMAP:
			case ENVIRONMENT:
				// "texture","lightmap","environment"
				function.tcgen = function.masterfunc1;
				break;
			case DOLLAR_LIGHTMAP:
				// map == lightmap, tcgen == lightmap
				function.tcgen = LIGHTMAP;
				break;
			case BULGE:
				// deformvertexes bulge
				function.bulgewidth = getAsFloat( v.content, pos );
				function.bulgeheight = getAsFloat( v.content, pos );
				function.bulgespeed = getAsFloat( v.content, pos );

				deformvertexes_bulge(TimeAbs, function);
				break;

			case NORMAL:
				// deformvertexes normal
				function.amp = getAsFloat( v.content, pos );
				function.frequency = getAsFloat( v.content, pos );

				deformvertexes_normal(TimeAbs, function);
				break;

			case AUTOSPRITE:
				// deformvertexes autosprite
				deformvertexes_autosprite(TimeAbs, function);
				break;

			case AUTOSPRITE2:
				// deformvertexes autosprite2
				deformvertexes_autosprite2(TimeAbs, function);
				break;
			default:
				break;
		} // func

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

	} // group

	vertextransform_rgbgen( TimeAbs, function );
	vertextransform_alphagen( TimeAbs, function );
	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->Material;
	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

#endif

