text: // Copyright (C) 2002-2007 Nikolaus Gebhardt
  // This file is part of the "Irrlicht Engine".
  // For conditions of distribution and use, see copyright notice in irrlicht.h
  
  #include "CQ3LevelMesh.h"
  #include "ISceneManager.h"
  #include "os.h"
  #include "SMeshBufferLightMap.h"
  #include "irrString.h"
  #include "CColorConverter.h"
  #include "ILightSceneNode.h"
  #include "fast_atof.h"
  
  namespace irr
  {
  namespace scene
  {
  
  
  //! constructor
  CQ3LevelMesh::CQ3LevelMesh(io::IFileSystem* fs, video::IVideoDriver* driver, scene::ISceneManager* smgr)
  : Textures(0), LightMaps(0),
   Vertices(0), Faces(0),	Planes(0), Nodes(0), Leafs(0), LeafFaces(0),
  	MeshVerts(0), Brushes(0), Driver(driver), FileSystem(fs), SceneManager ( smgr )
  {
  	#ifdef _DEBUG
  	IUnknown::setDebugName("CQ3LevelMesh");
  	#endif	
  
  	s32 i;
  	for ( i = 0; i!= quake3::E_Q3_MESH_SIZE; ++i )
  	{
  		Mesh[i] = 0;
  	}
  
  	if (Driver)
  		Driver->grab();
  
  	if (FileSystem)
  		FileSystem->grab();
  
  	// load default shaders
  	InitShader ();
  
  
  }
  
  
  //! destructor
  CQ3LevelMesh::~CQ3LevelMesh()
  {
  	if (Textures)
  		delete [] Textures;
  
  	if (LightMaps)
  		delete [] LightMaps;
  	
  	if (Vertices)
  		delete [] Vertices;
  
  	if (Faces)
  		delete [] Faces;
  
  	if (Planes)
  		delete [] Planes;
  
  	if (Nodes)
  		delete [] Nodes;
  
  	if (Leafs)
  		delete [] Leafs;
  
  	if (LeafFaces)
  		delete [] LeafFaces;
  	
  	if (MeshVerts)
  		delete [] MeshVerts;
  
  	if (Brushes)
  		delete [] Brushes;
  
  	if (Driver)
  		Driver->drop();
  
  	if (FileSystem)
  		FileSystem->drop();
  
  	s32 i;
  	for ( i = 0; i!= quake3::E_Q3_MESH_SIZE; ++i )
  	{
  		if (Mesh[i])
  			Mesh[i]->drop();
  	}
  
  	ReleaseShader ();
  	ReleaseEntity ();
  }
  
  
  
  //! loads a level from a .bsp-File. Also tries to load all needed textures. Returns true if successful.
  bool CQ3LevelMesh::loadFile(io::IReadFile* file)
  {
  	if (!file)
  		return false;
  
  	LevelName = file->getFileName();
  
  	tBSPHeader header;
  	file->read(&header, sizeof(tBSPHeader));
  
  	#ifdef __BIG_ENDIAN__
  		header.strID = os::Byteswap::byteswap(header.strID);
  		header.version = os::Byteswap::byteswap(header.version);
  	#endif
  
  	if (header.strID != 0x50534249 || header.version != 0x2e)
  	{
  		os::Printer::log("Could not load .bsp file, unknown header.", file->getFileName(), ELL_ERROR);
  		return false;
  	}
  
  	// now read lumps
  
  	file->read(&Lumps[0], sizeof(tBSPLump)*kMaxLumps);
  
  	#ifdef __BIG_ENDIAN__
  	for (int i=0;i<kMaxLumps;++i)
  	{
  		Lumps[i].offset = os::Byteswap::byteswap(Lumps[i].offset);
  		Lumps[i].length = os::Byteswap::byteswap(Lumps[i].length);
  	}
  	#endif
  
  	s32 i;
  	for ( i = 0; i!= quake3::E_Q3_MESH_SIZE; ++i )
  	{
  		Mesh[i] = new SMesh();
  	}
  	ReleaseEntity ();
  
  	// load everything
  
  	loadTextures(&Lumps[kTextures], file);			// Load the textures
  	loadLightmaps(&Lumps[kLightmaps], file);		// Load the lightmaps
  	loadVerts(&Lumps[kVertices], file);				// Load the vertices
  	loadFaces(&Lumps[kFaces], file);				// Load the faces
  	loadPlanes(&Lumps[kPlanes], file);				// Load the Planes of the BSP
  	loadNodes(&Lumps[kNodes], file);				// load the Nodes of the BSP
  	loadLeafs(&Lumps[kLeafs], file);				// load the Leafs of the BSP
  	loadLeafFaces(&Lumps[kLeafFaces], file);		// load the Faces of the Leafs of the BSP
  	loadVisData(&Lumps[kVisData], file);			// load the visibility data of the clusters
  	loadEntities(&Lumps[kEntities], file);			// load the entities
  	loadModels(&Lumps[kModels], file);				// load the models
  	loadMeshVerts(&Lumps[kMeshVerts], file);		// load the mesh vertices
  	loadBrushes(&Lumps[kBrushes], file);			// load the brushes of the BSP
  	loadBrushSides(&Lumps[kBrushSides], file);		// load the brushsides of the BSP
  	loadLeafBrushes(&Lumps[kLeafBrushes], file);	// load the brushes of the leaf
  	loadShaders(&Lumps[kShaders], file );			// load the shaderes
  
  	PatchTesselation = 8;
  
  	//constructMesh();
  	//loadTextures();
  
  
  	loadTextures2();
  	constructMesh2();
  
  	cleanMeshes ();
  	calcBoundingBoxes ();
  
  	return true;
  }
  
  
  
  //! returns the amount of frames in milliseconds. If the amount is 1, it is a static (=non animated) mesh.
  s32 CQ3LevelMesh::getFrameCount()
  {
  	return 1;
  }
  
  
  void CQ3LevelMesh::releaseMesh ( s32 index )
  {
  	if ( Mesh [ index ] )
  	{
  		Mesh [index]->drop ();
  		Mesh [index] = 0;
  	}
  }
  
  //! returns the animated mesh based on a detail level. 0 is the lowest, 255 the highest detail. Note, that some Meshes will ignore the detail level.
  IMesh* CQ3LevelMesh::getMesh(s32 frameInMs, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop)
  {
  	return Mesh[ frameInMs ];
  }
  
  
  void CQ3LevelMesh::loadTextures(tBSPLump* l, io::IReadFile* file)
  {
  	NumTextures = l->length / sizeof(tBSPTexture);
  	Textures = new tBSPTexture[NumTextures];
  
  	file->seek(l->offset);
  	file->read(Textures, l->length);
  
  	#ifdef __BIG_ENDIAN__
  	for (int i=0;i<NumTextures;i++)
  	{
  		Textures[i].flags = os::Byteswap::byteswap(Textures[i].flags);
  		Textures[i].contents = os::Byteswap::byteswap(Textures[i].contents);
  	}
  	#endif
  }
  
  
  void CQ3LevelMesh::loadLightmaps(tBSPLump* l, io::IReadFile* file)
  {
  	NumLightMaps = l->length / sizeof(tBSPLightmap);
  	LightMaps = new tBSPLightmap[NumLightMaps];
  
  	file->seek(l->offset);
  	file->read(LightMaps, l->length);
  }
  
  
  void CQ3LevelMesh::loadVerts(tBSPLump* l, io::IReadFile* file)
  {
  	NumVertices = l->length / sizeof(tBSPVertex);
  	Vertices = new tBSPVertex[NumVertices];
  
  	file->seek(l->offset);
  	file->read(Vertices, l->length);
  
  	#ifdef __BIG_ENDIAN__
  	for (int i=0;i<NumVertices;i++)
  	{
  		Vertices[i].vPosition[0] = os::Byteswap::byteswap(Vertices[i].vPosition[0]);
  		Vertices[i].vPosition[1] = os::Byteswap::byteswap(Vertices[i].vPosition[1]);
  		Vertices[i].vPosition[2] = os::Byteswap::byteswap(Vertices[i].vPosition[2]);
  		Vertices[i].vTextureCoord[0] = os::Byteswap::byteswap(Vertices[i].vTextureCoord[0]);
  		Vertices[i].vTextureCoord[1] = os::Byteswap::byteswap(Vertices[i].vTextureCoord[1]);
  		Vertices[i].vLightmapCoord[0] = os::Byteswap::byteswap(Vertices[i].vLightmapCoord[0]);
  		Vertices[i].vLightmapCoord[1] = os::Byteswap::byteswap(Vertices[i].vLightmapCoord[1]);
  		Vertices[i].vNormal[0] = os::Byteswap::byteswap(Vertices[i].vNormal[0]);
  		Vertices[i].vNormal[1] = os::Byteswap::byteswap(Vertices[i].vNormal[1]);
  		Vertices[i].vNormal[2] = os::Byteswap::byteswap(Vertices[i].vNormal[2]);
  	}
  	#endif
  }
  
  
  void CQ3LevelMesh::loadFaces(tBSPLump* l, io::IReadFile* file)
  {
  	NumFaces = l->length / sizeof(tBSPFace);
  	Faces = new tBSPFace[NumFaces];
  
  	file->seek(l->offset);
  	file->read(Faces, l->length);
  
  	#ifdef __BIG_ENDIAN__
  	for ( u32 i=0;i<NumFaces;i++)
  	{
  		Faces[i].textureID = os::Byteswap::byteswap(Faces[i].textureID);
  		Faces[i].effect = os::Byteswap::byteswap(Faces[i].effect);
  		Faces[i].type = os::Byteswap::byteswap(Faces[i].type);
  		Faces[i].vertexIndex = os::Byteswap::byteswap(Faces[i].vertexIndex);
  		Faces[i].numOfVerts = os::Byteswap::byteswap(Faces[i].numOfVerts);
  		Faces[i].meshVertIndex = os::Byteswap::byteswap(Faces[i].meshVertIndex);
  		Faces[i].numMeshVerts = os::Byteswap::byteswap(Faces[i].numMeshVerts);
  		Faces[i].lightmapID = os::Byteswap::byteswap(Faces[i].lightmapID);
  		Faces[i].lMapCorner[0] = os::Byteswap::byteswap(Faces[i].lMapCorner[0]);
  		Faces[i].lMapCorner[1] = os::Byteswap::byteswap(Faces[i].lMapCorner[1]);
  		Faces[i].lMapSize[0] = os::Byteswap::byteswap(Faces[i].lMapSize[0]);
  		Faces[i].lMapSize[1] = os::Byteswap::byteswap(Faces[i].lMapSize[1]);
  		Faces[i].lMapPos[0] = os::Byteswap::byteswap(Faces[i].lMapPos[0]);
  		Faces[i].lMapPos[1] = os::Byteswap::byteswap(Faces[i].lMapPos[1]);
  		Faces[i].lMapPos[2] = os::Byteswap::byteswap(Faces[i].lMapPos[2]);
  		Faces[i].lMapBitsets[0][0] = os::Byteswap::byteswap(Faces[i].lMapBitsets[0][0]);
  		Faces[i].lMapBitsets[0][1] = os::Byteswap::byteswap(Faces[i].lMapBitsets[0][1]);
  		Faces[i].lMapBitsets[0][2] = os::Byteswap::byteswap(Faces[i].lMapBitsets[0][2]);
  		Faces[i].lMapBitsets[1][0] = os::Byteswap::byteswap(Faces[i].lMapBitsets[1][0]);
  		Faces[i].lMapBitsets[1][1] = os::Byteswap::byteswap(Faces[i].lMapBitsets[1][1]);
  		Faces[i].lMapBitsets[1][2] = os::Byteswap::byteswap(Faces[i].lMapBitsets[1][2]);
  		Faces[i].vNormal[0] = os::Byteswap::byteswap(Faces[i].vNormal[0]);
  		Faces[i].vNormal[1] = os::Byteswap::byteswap(Faces[i].vNormal[1]);
  		Faces[i].vNormal[2] = os::Byteswap::byteswap(Faces[i].vNormal[2]);
  		Faces[i].size[0] = os::Byteswap::byteswap(Faces[i].size[0]);
  		Faces[i].size[1] = os::Byteswap::byteswap(Faces[i].size[1]);
  	}
  	#endif
  }
  
  
  void CQ3LevelMesh::loadPlanes(tBSPLump* l, io::IReadFile* file)
  {
  	// ignore
  }
  
  
  void CQ3LevelMesh::loadNodes(tBSPLump* l, io::IReadFile* file)
  {
  	// ignore
  }
  
  
  void CQ3LevelMesh::loadLeafs(tBSPLump* l, io::IReadFile* file)
  {
  	// ignore
  }
  
  
  void CQ3LevelMesh::loadLeafFaces(tBSPLump* l, io::IReadFile* file)
  {
  	// ignore
  }
  
  
  void CQ3LevelMesh::loadVisData(tBSPLump* l, io::IReadFile* file)
  {
  	// ignore
  }
  
  
  void CQ3LevelMesh::loadEntities(tBSPLump* l, io::IReadFile* file)
  {
  	core::array<u8> entity;
  	entity.set_used ( l->length + 2 );
  	entity[l->length + 1 ] = 0;
  
  	file->seek(l->offset);
  	file->read ( entity.pointer(), l->length);
  
  	parser_parse ( entity.pointer(), l->length, &CQ3LevelMesh::scriptcallback_entity );
  }
  
  // load shaders named in bsp
  void CQ3LevelMesh::loadShaders(tBSPLump* l, io::IReadFile* file)
  {
  	u32 files = l->length / sizeof(tBSPShader);
  
  	file->seek ( l->offset );
  
  	tBSPShader def;
  	for ( u32 i = 0; i!= files; ++i )
  	{
  		file->read ( &def, sizeof ( def ) );
  		getShader ( def.strName, 1 );
  	}
  }
  
  
  void CQ3LevelMesh::loadModels(tBSPLump* l, io::IReadFile* file)
  {
  	// ignore
  }
  
  
  void CQ3LevelMesh::loadMeshVerts(tBSPLump* l, io::IReadFile* file)
  {
  	NumMeshVerts = l->length / sizeof(s32);
  	MeshVerts = new s32[NumMeshVerts];
  
  	file->seek(l->offset);
  	file->read(MeshVerts, l->length);
  
  	#ifdef __BIG_ENDIAN__
  	for (int i=0;i<NumMeshVerts;i++)
  		MeshVerts[i] = os::Byteswap::byteswap(MeshVerts[i]);
  	#endif
  }
  
  
  void CQ3LevelMesh::loadBrushes(tBSPLump* l, io::IReadFile* file)
  {
  	// ignore
  }
  
  
  void CQ3LevelMesh::loadBrushSides(tBSPLump* l, io::IReadFile* file)
  {
  	// ignore
  }
  
  
  void CQ3LevelMesh::loadLeafBrushes(tBSPLump* l, io::IReadFile* file)
  {
  	// ignore
  }
  
  
  inline bool isQ3WhiteSpace ( const u8 symbol )
  {
  	return symbol == ' ' || symbol == '\t' || symbol == '\r';
  }
  
  
  void CQ3LevelMesh::parser_nextToken ()
  {
  	u8 symbol;
  
  	Parser.token = "";
  	Parser.tokenresult = Q3_TOKEN_UNRESOLVED;
  
  	// skip white space
  	do
  	{
  		if ( Parser.index >= Parser.sourcesize )
  		{
  			Parser.tokenresult = Q3_TOKEN_EOF;
  			return;
  		}
  
  		symbol = Parser.source [ Parser.index ];
  		Parser.index += 1;
  	} while ( isQ3WhiteSpace ( symbol ) );
  
  	// first symbol, one symbol
  	switch ( symbol )
  	{
  		case 0:
  			Parser.tokenresult = Q3_TOKEN_EOF;
  			return;
  
  		case '/':
  			// comment or divide
  			if ( Parser.index >= Parser.sourcesize )
  			{
  				Parser.tokenresult = Q3_TOKEN_EOF;
  				return;
  			}
  			symbol = Parser.source [ Parser.index ];
  			Parser.index += 1;
  			if ( isQ3WhiteSpace ( symbol ) )
  			{
  				Parser.tokenresult = Q3_TOKEN_MATH_DIVIDE;
  				return;
  			}
  			else
  			if ( symbol == '*' )
  			{
  				// C-style comment in quake?
  			}
  			else
  			if ( symbol == '/' )
  			{
  				// skip to eol
  				do
  				{
  					if ( Parser.index >= Parser.sourcesize )
  					{
  						Parser.tokenresult = Q3_TOKEN_EOF;
  						return;
  					}
  					symbol = Parser.source [ Parser.index ];
  					Parser.index += 1;
  				} while ( symbol != '\n' );
  				Parser.tokenresult = Q3_TOKEN_COMMENT;
  				return;
  			}
  			// take /[name] as valid token..?!?!?. mhmm, maybe
  			break;
  			
  		case '\n':
  			Parser.tokenresult = Q3_TOKEN_EOL;
  			return;
  		case '{':
  			Parser.tokenresult = Q3_TOKEN_START_LIST;
  			return;
  		case '}':
  			Parser.tokenresult = Q3_TOKEN_END_LIST;
  			return;
  
  		case '"':
  			// string literal
  			do
  			{
  				if ( Parser.index >= Parser.sourcesize )
  				{
  					Parser.tokenresult = Q3_TOKEN_EOF;
  					return;
  				}
  				symbol = Parser.source [ Parser.index ];
  				Parser.index += 1;
  				if ( symbol != '"' )
  					Parser.token.append ( symbol );
  			} while ( symbol != '"' );
  			Parser.tokenresult = Q3_TOKEN_ENTITY;
  			return;
  	}
  
  
  	// user identity
  	Parser.token.append ( symbol );
  
  	// continue till whitespace
  	bool notisWhite = true;
  	do
  	{
  		if ( Parser.index >= Parser.sourcesize )
  		{
  			Parser.tokenresult = Q3_TOKEN_EOF;
  			return;
  		}
  		symbol = Parser.source [ Parser.index ];
  
  		notisWhite = ! isQ3WhiteSpace ( symbol );
  		if ( notisWhite )
  		{
  			Parser.token.append ( symbol );
  		}
  
  		Parser.index += 1;
  
  	} while ( notisWhite );
  
  	Parser.tokenresult = Q3_TOKEN_TOKEN;
  	return;
  }
  
  /*
  	parse entity & shader
  	calls callback on content in {}
  */
  void CQ3LevelMesh::parser_parse ( const void * data, const u32 size, CQ3LevelMesh::tParserCallback callback )
  {
  	Parser.source = (const c8*) data;
  	Parser.sourcesize = size;
  	Parser.index = 0;
  
  	quake3::SVarGroupList *groupList;
  
  	s32 active;
  	s32 last;
  
  	quake3::SVariable entity;
  
  	groupList = new quake3::SVarGroupList ();
  
  	groupList->VariableGroup.push_back ( quake3::SVarGroup () );
  	active = last = 0;
  
  	do
  	{
  		parser_nextToken ();
  
  		switch ( Parser.tokenresult )
  		{
  			case Q3_TOKEN_START_LIST:
  			{
  				//stack = core::min_ ( stack + 1, 7 );
  
  				groupList->VariableGroup.push_back ( quake3::SVarGroup () );
  				last = active;
  				active = groupList->VariableGroup.size() - 1;
  				entity.clear ();
  			}  break;
  
  			// a unregisterd variable is finished
  			case Q3_TOKEN_EOL:
  			{
  				if ( entity.isValid() )
  				{
  					groupList->VariableGroup[active].Variable.push_back ( entity );
  					entity.clear ();
  				}
  			} break;
  
  			case Q3_TOKEN_TOKEN:
  			case Q3_TOKEN_ENTITY:
  			{
  				Parser.token.make_lower();
  
  				// store content based on line-delemiter
  				if ( 0 == entity.isValid() )
  				{
  					entity.name = Parser.token;
  					entity.content = "";
  
  				}
  				else
  				{
  					if ( entity.content.size() )
  					{
  						entity.content += " ";
  					}
  					entity.content += Parser.token;
  				}
  			} break;
  
  			case Q3_TOKEN_END_LIST:
  			{
  				//stack = core::max_ ( stack - 1, 0 );
  
  				// close tag for first
  				if ( active == 1 )
  				{
  					(this->*callback) ( groupList );
  
  					// new group
  					groupList->drop ();
  					groupList = new quake3::SVarGroupList ();
  					groupList->VariableGroup.push_back ( quake3::SVarGroup () );
  					last = 0;
  				}
  
  				active = last;
  				entity.clear();
  
  			} break;
  
  		}
  
  	} while ( Parser.tokenresult != Q3_TOKEN_EOF );
  
  	groupList->drop ();
  }
  
  
  
  /*
  	this loader applies only textures for stage 1 & 2
  */
  s32 CQ3LevelMesh::setShaderMaterial ( video::SMaterial &material, const tBSPFace * face ) const
  {
  	material.MaterialType = video::EMT_SOLID;
  	material.Wireframe = false;
  	material.Lighting = false;
  	material.BilinearFilter = true;
  	material.BackfaceCulling = true;
  	material.Textures[0] = 0;
  	material.Textures[1] = 0;
  	material.Textures[2] = 0;
  	material.Textures[3] = 0;
  	material.ZBuffer = true;
  	material.ZWriteEnable = true;
  	material.MaterialTypeParam = 0.f;
  
  	s32 shaderState = -1;
  
  	if ( face->textureID >= 0 )
  	{
  		material.Textures[0] = Tex [ face->textureID ].Texture;
  		shaderState = Tex [ face->textureID ].ShaderID;
  	}
  
  	if ( face->lightmapID >= 0 )
  	{
  		material.Textures[1] = Lightmap [ face->lightmapID ];
  		material.MaterialType = quake3::defaultLightMap;
  	}
  
  	// store shader ID
  	material.MaterialTypeParam2 = (f32) shaderState;
  
  	const quake3::SShader *shader = getShader ( shaderState );
  	if ( 0 == shader )
  		return shaderState;
  
  
  	const quake3::SVarGroup *group;
  
  	s32 index;
  
  	// generic
  	group = shader->getGroup ( 1 );
  	if ( group )
  	{
  		material.BackfaceCulling = quake3::getBackfaceCulling ( group->get ( "cull" ) );
  
  		if ( group->isDefined ( "surfaceparm", "nolightmap" ) )
  		{
  			material.MaterialType = video::EMT_SOLID;
  			material.Textures[1] = 0;
  		}
  
  	}
  
  	// try to get the best of the 8 texture stages..
  
  	// texture 1, texture 2
  	u32 startPos;
  	for ( s32 g = 2; g <= 3; ++g )
  	{
  		group = shader->getGroup ( g );
  		if ( 0 == group )
  			continue;
  
  		startPos = 0;
  
  
  		index = group->getIndex ( "depthwrite" );
  		if ( index >= 0 )
  		{
  			material.ZBuffer = true;
  		}
  
  		quake3::SBlendFunc blendfunc;
  		quake3::getBlendFunc ( group->get ( "blendfunc" ), blendfunc );
  		quake3::getBlendFunc ( group->get ( "alphafunc" ), blendfunc );
  
  		material.MaterialType = blendfunc.type;
  		material.MaterialTypeParam = blendfunc.param;
  
  		// try if we can match better
  		shaderState |= (material.MaterialType == video::EMT_SOLID ) ? 0x00020000 : 0;
  
  	}
  
  
  	//material.BackfaceCulling = false;
  
  	if ( shader->VarGroup->VariableGroup.size () <= 4 )
  	{
  		shaderState |= 0x00010000;
  	}
  
  	material.MaterialTypeParam2 = (f32) shaderState;
  	return shaderState;
  }
  
  //! constructs a mesh from the quake 3 level file.
  void CQ3LevelMesh::constructMesh2()
  {
  	s32 i, j, k;
  
  	s32 *index;
  
  	video::S3DVertex2TCoords temp[3];
  
  	video::SMaterial material;
  
  	SToBuffer item;
  
  	core::array < SToBuffer > toBuffer;
  
  	const s32 mesh0size = (NumTextures+1) * (NumLightMaps+1);
  	for ( i=0; i < mesh0size; ++i)
  	{
  		scene::SMeshBufferLightMap* buffer = new scene::SMeshBufferLightMap();
  
  		Mesh[quake3::E_Q3_MESH_GEOMETRY]->addMeshBuffer(buffer);
  		buffer->drop();
  	}
  
  	for ( i=0; i<NumFaces; ++i)
  	{
  		const tBSPFace * face = &Faces [i];
  
  		s32 shaderState = setShaderMaterial ( material, face );
  		toBuffer.clear ();
  
  		const quake3::SShader *shader = getShader ( shaderState );
  
  		switch( Faces[i].type )
  		{
  			case 1: // normal polygons
  			case 2: // patches
  			case 3: // meshes
  				{
  					if ( 0 == shader )
  					{
  						item.takeVertexColor = material.Textures[0] == 0 || material.Textures[1] == 0;
  						item.index = quake3::E_Q3_MESH_GEOMETRY;
  						toBuffer.push_back ( item );
  					}
  					else
  					{
  						item.takeVertexColor = 1;
  						item.index = quake3::E_Q3_MESH_ITEMS;
  						toBuffer.push_back ( item );
  					}
  
  				} break;
  
  
  /*
  			case 1: // normal polygons
  			case 2: // patches
  				if ( material.Textures[0] && 0 == shader )
  				{
  					item.takeVertexColor = material.Textures[1] == 0;
  					item.index = quake3::E_Q3_MESH_GEOMETRY;
  					toBuffer.push_back ( item );
  				}
  				else
  				if ( material.Textures[0] )
  				{
  					item.takeVertexColor = material.Textures[1] == 0;
  					item.index = quake3::E_Q3_MESH_GEOMETRY;
  					toBuffer.push_back ( item );
  					if ( 0 == (shaderState & 0xFFFF0000 ) )
  					{
  						item.takeVertexColor = 1;
  						item.index = quake3::E_Q3_MESH_ITEMS;
  						toBuffer.push_back ( item );
  					}
  				}
  				else
  				{
  					item.takeVertexColor = 1;
  					item.index = quake3::E_Q3_MESH_ITEMS;
  					toBuffer.push_back ( item );
  				}
  				break;
  
  			case 3: // mesh vertices
  				if ( material.Textures[0] && ( shaderState & 0xFFFF0000 ) == 0x00030000 )
  				{
  					item.takeVertexColor = material.Textures[1] == 0;
  					item.index = quake3::E_Q3_MESH_GEOMETRY;
  					toBuffer.push_back ( item );
  				}
  				else
  				{
  					item.takeVertexColor = 1;
  					item.index = quake3::E_Q3_MESH_ITEMS;
  					toBuffer.push_back ( item );
  				}
  				break;
  */
  			case 4: // billboards
  				item.takeVertexColor = 1;
  				item.index = quake3::E_Q3_MESH_ITEMS;
  				toBuffer.push_back ( item );
  				break;
  		}
  
  		for ( u32 g = 0; g!= toBuffer.size(); ++g )
  		{
  			scene::SMeshBufferLightMap* buffer;
  
  			if ( toBuffer[g].index == quake3::E_Q3_MESH_GEOMETRY )
  			{
  				if ( 0 == toBuffer[g].takeVertexColor )
  				{
  					toBuffer[g].takeVertexColor = material.Textures[0] == 0 || material.Textures[1];
  				}
  				if (Faces[i].lightmapID < -1 || Faces[i].lightmapID > NumLightMaps-1)
  				{
  					Faces[i].lightmapID = -1;
  				}
  
  				// there are lightmapsids and textureid with -1
  				s32 index = ((Faces[i].lightmapID+1) * (NumTextures+1)) + (Faces[i].textureID+1);
  				buffer = (SMeshBufferLightMap*) Mesh[quake3::E_Q3_MESH_GEOMETRY]->getMeshBuffer(index);
  				buffer->getMaterial() = material;
  			}
  			else
  			{
  				buffer = (SMeshBufferLightMap*) Mesh[ toBuffer[g].index ]->getMeshBuffer ( material );
  				//buffer = 0;
  				if ( 0 == buffer )
  				{
  					buffer = new scene::SMeshBufferLightMap();
  					Mesh[ toBuffer[g].index ]->addMeshBuffer ( buffer );
  					buffer->drop ();
  					buffer->getMaterial() = material;
  				}
  			}
  
  
  			switch(Faces[i].type)
  			{
  				case 4: // billboards
  					break;
  				case 2: // patches
  					createCurvedSurface2(buffer, i, PatchTesselation,toBuffer[g].takeVertexColor);
  					break;
  
  				case 1: // normal polygons
  				case 3: // mesh vertices
  
  					index = MeshVerts + face->meshVertIndex;
  					k = buffer->getVertexCount();
  
  					for ( j = 0; j < face->numMeshVerts; j += 1 )
  					{
  						buffer->Indices.push_back( k + index [j] );
  					}
  
  					for ( j = 0; j != face->numOfVerts; ++j )
  					{
  						copy ( &temp[0], &Vertices[ j + face->vertexIndex ], toBuffer[g].takeVertexColor );
  						buffer->Vertices.push_back( temp[0] );
  					}
  					break;
  
  			} // end switch
  		}
  	}
  	
  
  }
  
  //! constructs a mesh from the quake 3 level file.
  void CQ3LevelMesh::constructMesh()
  {
  	// reserve buffer. 
  	s32 i; // new ISO for scoping problem with some compilers
  
  	for (i=0; i<(NumTextures+1) * (NumLightMaps+1); ++i)
  	{
  		scene::SMeshBufferLightMap* buffer = new scene::SMeshBufferLightMap();
  
  		buffer->Material.MaterialType = video::EMT_LIGHTMAP_M4;
  		buffer->Material.Wireframe = false;
  		buffer->Material.Lighting = false;
  		buffer->Material.BilinearFilter = true;
  
  		Mesh[0]->addMeshBuffer(buffer);
  
  		buffer->drop();
  	}
  
  	// go through all faces and add them to the buffer.
  
  	video::S3DVertex2TCoords temp[3];
  
  	for (i=0; i<NumFaces; ++i)
  	{
  		if (Faces[i].lightmapID < -1)
  			Faces[i].lightmapID = -1;
  
  		if (Faces[i].lightmapID > NumLightMaps-1)
  			Faces[i].lightmapID = -1;
  
  		// there are lightmapsids and textureid with -1
  		s32 meshBufferIndex = ((Faces[i].lightmapID+1) * (NumTextures+1)) + (Faces[i].textureID+1);
  		SMeshBufferLightMap* meshBuffer = ((SMeshBufferLightMap*)Mesh[0]->getMeshBuffer(meshBufferIndex));
  
  		switch(Faces[i].type)
  		{
  			//case 3: // mesh vertices
  			case 1: // normal polygons
  				{
  					for (s32 tf=0; tf<Faces[i].numMeshVerts; tf+=3)
  					{
  						s32 idx = meshBuffer->getVertexCount();
  						s32 vidxes[3];
  
  						vidxes[0] = MeshVerts[Faces[i].meshVertIndex + tf +0] 
  							+ Faces[i].vertexIndex;
  						vidxes[1] = MeshVerts[Faces[i].meshVertIndex + tf +1] 
  							+ Faces[i].vertexIndex;
  						vidxes[2] = MeshVerts[Faces[i].meshVertIndex + tf +2]
  							+ Faces[i].vertexIndex;
  
  						// add all three vertices
  						copy ( &temp[0], &Vertices[ vidxes[0] ], 0 );
  						copy ( &temp[1], &Vertices[ vidxes[1] ], 0 );
  						copy ( &temp[2], &Vertices[ vidxes[2] ], 0 );
  
  						meshBuffer->Vertices.push_back( temp[0] );
  						meshBuffer->Vertices.push_back( temp[1] );
  						meshBuffer->Vertices.push_back( temp[2] );
  
  						// add indexes
  
  						meshBuffer->Indices.push_back(idx);
  						meshBuffer->Indices.push_back(idx+1);
  						meshBuffer->Indices.push_back(idx+2);
  					}
  				}
  				break;
  			case 2: // curved surfaces
  				createCurvedSurface(meshBuffer, i);
  				break;
  
  			case 4: // billboards
  				break;
  		} // end switch
  	}
  	
  }
  
  // helper method for creating curved surfaces, sent in by Dean P. Macri.
  inline f32 CQ3LevelMesh::Blend( const f64 s[3], const f64 t[3], const tBSPVertex *v[9], int offset)
  {
  	f64 res = 0.0;
  	f32 *ptr;
  
  	for( int i=0; i<3; i++ )
  		for( int j=0; j<3; j++ )
  		{
  			ptr = (f32 *)( (char*)v[i*3+j] + offset );
  			res += s[i] * t[j] *  (*ptr);
  		}
  
  	return (f32) res;
  }
  
  //!helper function 
  inline s32 s32_min ( s32 a, s32 b)
  {
  	s32 mask = (a - b) >> 31;
  	return (a & mask) | (b & ~mask);
  }
  
  void CQ3LevelMesh::S3DVertex2TCoords_64::copyto ( video::S3DVertex2TCoords &dest ) const
  {
  	dest.Pos.X = core::round ( (f32) Pos.X );
  	dest.Pos.Y = core::round ( (f32) Pos.Y );
  	dest.Pos.Z = core::round ( (f32) Pos.Z );
  	//dest.Pos.X = (f32) Pos.X;
  	//dest.Pos.Y = (f32) Pos.Y;
  	//dest.Pos.Z = (f32) Pos.Z;
  
  	dest.Normal.X = (f32) Normal.X;
  	dest.Normal.Y = (f32) Normal.Y;
  	dest.Normal.Z = (f32) Normal.Z;
  	dest.Normal.normalize();
  
  	dest.Color = Color.toSColor();
  
  	dest.TCoords.X = (f32) TCoords.X;
  	dest.TCoords.Y = (f32) TCoords.Y;
  
  	dest.TCoords2.X = (f32) TCoords2.X;
  	dest.TCoords2.Y = (f32) TCoords2.Y;
  }
  
  
  void CQ3LevelMesh::copy ( S3DVertex2TCoords_64 * dest, const tBSPVertex * source, s32 vertexcolor ) const
  {
  	//dest->Pos.X = core::round ( source->vPosition[0] );
  	//dest->Pos.Y = core::round ( source->vPosition[2] );
  	//dest->Pos.Z = core::round ( source->vPosition[1] );
  	dest->Pos.X = source->vPosition[0];
  	dest->Pos.Y = source->vPosition[2];
  	dest->Pos.Z = source->vPosition[1];
  
  	dest->Normal.X = source->vNormal[0];
  	dest->Normal.Y = source->vNormal[2];
  	dest->Normal.Z = source->vNormal[1];
  	dest->Normal.normalize ();
  
  	dest->TCoords.X = source->vTextureCoord[0];
  	dest->TCoords.Y = source->vTextureCoord[1];
  	dest->TCoords2.X = source->vLightmapCoord[0];
  	dest->TCoords2.Y = source->vLightmapCoord[1];
  
  	if ( vertexcolor )
  	{
  		u32 a = s32_min ( source->color[3] * quake3::defaultModulate, 255 );
  		u32 r = s32_min ( source->color[0] * quake3::defaultModulate, 255 );
  		u32 g = s32_min ( source->color[1] * quake3::defaultModulate, 255 );
  		u32 b = s32_min ( source->color[2] * quake3::defaultModulate, 255 );
  
  		dest->Color.set (	a * 1.f/255.f,
  							r * 1.f/255.f,
  							g * 1.f/255.f,
  							b * 1.f/255.f
  						);
  	}
  	else
  	{
  		dest->Color.set ( 1.f, 1.f, 1.f, 1.f );
  	}
  
  }
  
  
  inline void CQ3LevelMesh::copy ( video::S3DVertex2TCoords * dest, const tBSPVertex * source, s32 vertexcolor ) const
  {
  	dest->Pos.X = core::round ( source->vPosition[0] );
  	dest->Pos.Y = core::round ( source->vPosition[2] );
  	dest->Pos.Z = core::round ( source->vPosition[1] );
  
  	//dest->Pos.X = source->vPosition[0];
  	//dest->Pos.Y = source->vPosition[2];
  	//dest->Pos.Z = source->vPosition[1];
  
  	dest->Normal.X = source->vNormal[0];
  	dest->Normal.Y = source->vNormal[2];
  	dest->Normal.Z = source->vNormal[1];
  	dest->Normal.normalize();
  
  	dest->TCoords.X = source->vTextureCoord[0];
  	dest->TCoords.Y = source->vTextureCoord[1];
  	dest->TCoords2.X = source->vLightmapCoord[0];
  	dest->TCoords2.Y = source->vLightmapCoord[1];
  
  	if ( vertexcolor )
  	{
  		u32 a = s32_min ( source->color[3] * quake3::defaultModulate, 255 );
  		u32 r = s32_min ( source->color[0] * quake3::defaultModulate, 255 );
  		u32 g = s32_min ( source->color[1] * quake3::defaultModulate, 255 );
  		u32 b = s32_min ( source->color[2] * quake3::defaultModulate, 255 );
  
  		dest->Color.color = a << 24 | r << 16 | g << 8 | b;
  	}
  	else
  	{
  		dest->Color.color = 0xFFFFFFFF;
  	}
  }
  
  void CQ3LevelMesh::SBezier::tesselate ( s32 level )
  {
  	//Calculate how many vertices across/down there are
  	s32 j, k;
  
  	u32 idx = Patch->Vertices.size();
  
  	column[0].set_used ( level + 1 );
  	column[1].set_used ( level + 1 );
  	column[2].set_used ( level + 1 );
  
  	const f64 w = 0.0 + core::reciprocal ( (f32) level );
  
  	//Tesselate along the columns
  	for( j = 0; j <= level; ++j)
  	{
  		const f64 f = w * (f64) j;
  
  		column[0][j] = control[0].getInterpolated_quadratic(control[3], control[6], f );
  		column[1][j] = control[1].getInterpolated_quadratic(control[4], control[7], f );
  		column[2][j] = control[2].getInterpolated_quadratic(control[5], control[8], f );
  	}
  
  	//Tesselate across the rows to get final vertices
  	video::S3DVertex2TCoords v;
  	S3DVertex2TCoords_64 f;
  	for( j = 0; j <= level; ++j)
  	{
  		for( k = 0; k <= level; ++k)
  		{
  			f = column[0][j].getInterpolated_quadratic(	column[1][j], column[2][j],	w * (f64) k );
  			f.copyto ( v );
  			Patch->Vertices.push_back ( v );
  		}
  	}
  
  	// connect
  	for( j = 0; j < level; ++j)
  	{
  		for( k = 0; k < level; ++k)
  		{
  			const s32 inx = idx + ( k * ( level + 1 ) ) + j;
  
  			Patch->Indices.push_back ( inx + 0 );
  			Patch->Indices.push_back ( inx + (level + 1 ) + 0 );
  			Patch->Indices.push_back ( inx + (level + 1 ) + 1 );
  
  			Patch->Indices.push_back ( inx + 0 );
  			Patch->Indices.push_back ( inx + (level + 1 ) + 1 );
  			Patch->Indices.push_back ( inx + 1 );
  		}
  	}
  }
  
  
  /*!
  	no subdivision
  */
  void CQ3LevelMesh::createCurvedSurface3 (	SMeshBufferLightMap* meshBuffer,
  											s32 faceIndex,
  											s32 patchTesselation,
  											s32 storevertexcolor
  										)
  {
  	tBSPFace * face = &Faces[faceIndex];
  	u32 j,k,m;
  
  	// number of control points across & up
  	const u32 controlWidth = face->size[0];
  	const u32 controlHeight = face->size[1];
  
  	video::S3DVertex2TCoords v;
  
  	m = meshBuffer->Vertices.size ();
  	for ( j = 0; j!= controlHeight * controlWidth; ++j )
  	{
  		copy ( &v, &Vertices [ face->vertexIndex + j ], storevertexcolor );
  		meshBuffer->Vertices.push_back ( v );
  	}
  
  	for ( j = 0; j!= controlHeight - 1; ++j )
  	{
  		for ( k = 0; k!= controlWidth - 1; ++k )
  		{
  			meshBuffer->Indices.push_back ( m + k + 0 );
  			meshBuffer->Indices.push_back ( m + k + controlWidth + 0 );
  			meshBuffer->Indices.push_back ( m + k + controlWidth + 1 );
  
  			meshBuffer->Indices.push_back ( m + k + 0 );
  			meshBuffer->Indices.push_back ( m + k + controlWidth + 1 );
  			meshBuffer->Indices.push_back ( m + k + 1 );
  		}
  		m += controlWidth;
  	}
  }
  
  /*!
  */
  void CQ3LevelMesh::createCurvedSurface2 (	SMeshBufferLightMap* meshBuffer,
  											s32 faceIndex,
  											s32 patchTesselation,
  											s32 storevertexcolor
  										)
  {
  	tBSPFace * face = &Faces[faceIndex];
  	u32 j,k;
  
  	// number of control points across & up
  	const u32 controlWidth = face->size[0];
  	const u32 controlHeight = face->size[1];
  
  
  	// number of biquadratic patches
  	const u32 biquadWidth =  (controlWidth - 1)/2;
  	const u32 biquadHeight = (controlHeight -1)/2;
  
  
  	// Create space for a temporary array of the patch's control points
  	core::array<S3DVertex2TCoords_64> controlPoint;
  	controlPoint.set_used ( controlWidth * controlHeight );
  
  	for( j = 0; j < controlPoint.size(); ++j)
  	{
  		copy ( &controlPoint[j], &Vertices [ face->vertexIndex + j ], storevertexcolor );
  	}
  
  	// create a temporary patch
  	Bezier.Patch = new scene::SMeshBufferLightMap();
  
  	//Loop through the biquadratic patches
  	for( j = 0; j < biquadHeight; ++j)
  	{
  		for( k = 0; k < biquadWidth; ++k)
  		{
  			// set up this patch
  			const s32 inx = j*controlWidth*2 + k*2;
  
  			// setup bezier control points for this patch
  			Bezier.control[0] = controlPoint[ inx + 0];
  			Bezier.control[1] = controlPoint[ inx + 1];
  			Bezier.control[2] = controlPoint[ inx + 2];
  			Bezier.control[3] = controlPoint[ inx + controlWidth + 0 ];
  			Bezier.control[4] = controlPoint[ inx + controlWidth + 1 ];
  			Bezier.control[5] = controlPoint[ inx + controlWidth + 2 ];
  			Bezier.control[6] = controlPoint[ inx + controlWidth * 2 + 0];
  			Bezier.control[7] = controlPoint[ inx + controlWidth * 2 + 1];
  			Bezier.control[8] = controlPoint[ inx + controlWidth * 2 + 2];
  
  			Bezier.tesselate ( patchTesselation );
  		}
  	}
  
  
  	// stitch together with existing geometry
  	// TODO: only border needs to be checked
  	const u32 bsize = Bezier.Patch->getVertexCount();
  	const u32 msize = meshBuffer->getVertexCount();
  /*
  	for ( j = 0; j!= bsize; ++j )
  	{
  		const core::vector3df &v = Bezier.Patch->Vertices[j].Pos;
  
  		for ( k = 0; k!= msize; ++k )
  		{
  			const core::vector3df &m = meshBuffer->Vertices[k].Pos;
  
  			if ( !v.equals ( m, tolerance ) )
  				continue;
  
  			meshBuffer->Vertices[k].Pos = v; 
  			//Bezier.Patch->Vertices[j].Pos = m;
  		}
  	}
  */
  
  
  	// add Patch to meshbuffer
  	for ( j = 0; j!= bsize; ++j )
  	{
  		meshBuffer->Vertices.push_back ( Bezier.Patch->Vertices[j] );
  	}
  
  	// add indices to meshbuffer
  	for ( j = 0; j!= Bezier.Patch->getIndexCount(); ++j )
  	{
  		meshBuffer->Indices.push_back ( msize + Bezier.Patch->Indices[j] );
  	}
  
  	delete Bezier.Patch;
  
  }
  
  void CQ3LevelMesh::createCurvedSurface(SMeshBufferLightMap* meshBuffer, s32 i)
  {
  	// this implementation for loading curved surfaces was
  	// sent in by Dean P. Macri. It was a little bit modified
  	// by me afterwards.
  	s32 idx;
  	s32 cpidx[9];
  
  	const tBSPVertex *v[9];
  	video::S3DVertex2TCoords currentVertex[4];
  
  	for( s32 row=0; row<Faces[i].size[1]-2; row+=2 )
  	{
  		for( s32 col=0; col<Faces[i].size[0]-2; col+=2 )
  		{
  			cpidx[0] = (row * Faces[i].size[0] + col) + Faces[i].vertexIndex;
  			cpidx[1] = (row * Faces[i].size[0] + col + 1) + Faces[i].vertexIndex;
  			cpidx[2] = (row * Faces[i].size[0] + col + 2) + Faces[i].vertexIndex;
  			cpidx[3] = ((row+1) * Faces[i].size[0] + col) + Faces[i].vertexIndex;
  			cpidx[4] = ((row+1) * Faces[i].size[0] + col + 1) + Faces[i].vertexIndex;
  			cpidx[5] = ((row+1) * Faces[i].size[0] + col + 2) + Faces[i].vertexIndex;
  			cpidx[6] = ((row+2) * Faces[i].size[0] + col) + Faces[i].vertexIndex;
  			cpidx[7] = ((row+2) * Faces[i].size[0] + col + 1) + Faces[i].vertexIndex;
  			cpidx[8] = ((row+2) * Faces[i].size[0] + col + 2) + Faces[i].vertexIndex;
  
  			// For some reason if we tesselate more than this (3x3),
  			// some of the patches don't show up!
  
  			f64 s;
  			f64 t;
  			f64 cs[3], ct[3], nxs[3], nxt[3];
  
  			s32 srun = 3;
  			s32 trun = 3;
  			const f64 sstep = 1.0 / (f64) srun;
  			const f64 tstep = 1.0 / (f64) trun;
  
  			v[0] = &Vertices[cpidx[0]];
  			v[1] = 	&Vertices[cpidx[1]];
  			v[2] = 	&Vertices[cpidx[2]];
  			v[3] = 	&Vertices[cpidx[3]];
  			v[4] = 	&Vertices[cpidx[4]];
  			v[5] = 	&Vertices[cpidx[5]];
  			v[6] = 	&Vertices[cpidx[6]];
  			v[7] = 	&Vertices[cpidx[7]];
  			v[8] = 	&Vertices[cpidx[8]];
  
  
  			s32 dos;
  			s32 dot;
  			for( dos = 0, s = 0; dos != srun; dos +=1,  s+= sstep )
  			{
  				cs[0] = (1.0-s)*(1.0-s);
  				cs[1] = 2.0 * (1.0 - s) * s;
  				cs[2] = s * s;
  				nxs[0] = (1.0-s-sstep)*(1.0-s-sstep);
  				nxs[1] = 2.0 * (1.0 - s -sstep) * (s+sstep);
  				nxs[2] = (s+sstep) * (s+sstep);
  
  				for( dot = 0, t = 0; dot != trun; dot += 1, t += tstep )
  				{
  					idx = meshBuffer->getVertexCount();
  					ct[0] = (1.0-t)*(1.0-t);
  					ct[1] = 2.0 * (1.0 - t) * t;
  					ct[2] = t * t;
  					nxt[0] = (1.0-t-tstep)*(1.0-t-tstep);
  					nxt[1] = 2.0 * (1.0 - t - tstep) * (t+tstep);
  					nxt[2] = (t+tstep) * (t+tstep);
  
  					// Vert 1
  					currentVertex[0].Color.set(255,255,255,255);
  					currentVertex[0].Pos.X = floorf( Blend( cs, ct, v, (char*)&v[0]->vPosition[0] - (char*)v[0])+ 0.5f);
  					currentVertex[0].Pos.Y = floorf( Blend( cs, ct, v, (char*)&v[0]->vPosition[2] - (char*)v[0])+ 0.5f);
  					currentVertex[0].Pos.Z = floorf( Blend( cs, ct, v, (char*)&v[0]->vPosition[1] - (char*)v[0])+ 0.5f);
  					currentVertex[0].Normal.X = Blend( cs, ct, v, (char*)&v[0]->vNormal[0] - (char*)v[0]);
  					currentVertex[0].Normal.Y = Blend( cs, ct, v, (char*)&v[0]->vNormal[2] - (char*)v[0]);
  					currentVertex[0].Normal.Z = Blend( cs, ct, v, (char*)&v[0]->vNormal[1] - (char*)v[0]);
  					currentVertex[0].TCoords.X = Blend( cs, ct, v, (char*)&v[0]->vTextureCoord[0] - (char*)v[0]);
  					currentVertex[0].TCoords.Y = Blend( cs, ct, v, (char*)&v[0]->vTextureCoord[1] - (char*)v[0]);
  					currentVertex[0].TCoords2.X = Blend( cs, ct, v, (char*)&v[0]->vLightmapCoord[0] - (char*)v[0]);
  					currentVertex[0].TCoords2.Y = Blend( cs, ct, v, (char*)&v[0]->vLightmapCoord[1] - (char*)v[0]);
  					// Vert 2
  					currentVertex[1].Color.set(255,255,255,255);
  					currentVertex[1].Pos.X = floorf( Blend( cs, nxt, v, (char*)&v[0]->vPosition[0] - (char*)v[0])+ 0.5f);
  					currentVertex[1].Pos.Y = floorf( Blend( cs, nxt, v, (char*)&v[0]->vPosition[2] - (char*)v[0])+ 0.5f);
  					currentVertex[1].Pos.Z = floorf( Blend( cs, nxt, v, (char*)&v[0]->vPosition[1] - (char*)v[0])+ 0.5f);
  					currentVertex[1].Normal.X = Blend( cs, nxt, v, (char*)&v[0]->vNormal[0] - (char*)v[0]);
  					currentVertex[1].Normal.Y = Blend( cs, nxt, v, (char*)&v[0]->vNormal[2] - (char*)v[0]);
  					currentVertex[1].Normal.Z = Blend( cs, nxt, v, (char*)&v[0]->vNormal[1] - (char*)v[0]);
  					currentVertex[1].TCoords.X = Blend( cs, nxt, v, (char*)&v[0]->vTextureCoord[0] - (char*)v[0]);
  					currentVertex[1].TCoords.Y = Blend( cs, nxt, v, (char*)&v[0]->vTextureCoord[1] - (char*)v[0]);
  					currentVertex[1].TCoords2.X = Blend( cs, nxt, v, (char*)&v[0]->vLightmapCoord[0] - (char*)v[0]);
  					currentVertex[1].TCoords2.Y = Blend( cs, nxt, v, (char*)&v[0]->vLightmapCoord[1] - (char*)v[0]);
  					// Vert 3
  					currentVertex[2].Color.set(255,255,255,255);
  					currentVertex[2].Pos.X = floorf( Blend( nxs, ct, v, (char*)&v[0]->vPosition[0] - (char*)v[0])+ 0.5f);
  					currentVertex[2].Pos.Y = floorf( Blend( nxs, ct, v, (char*)&v[0]->vPosition[2] - (char*)v[0])+ 0.5f);
  					currentVertex[2].Pos.Z = floorf( Blend( nxs, ct, v, (char*)&v[0]->vPosition[1] - (char*)v[0])+ 0.5f);
  					currentVertex[2].Normal.X = Blend( nxs, ct, v, (char*)&v[0]->vNormal[0] - (char*)v[0]);
  					currentVertex[2].Normal.Y = Blend( nxs, ct, v, (char*)&v[0]->vNormal[2] - (char*)v[0]);
  					currentVertex[2].Normal.Z = Blend( nxs, ct, v, (char*)&v[0]->vNormal[1] - (char*)v[0]);
  					currentVertex[2].TCoords.X = Blend( nxs, ct, v, (char*)&v[0]->vTextureCoord[0] - (char*)v[0]);
  					currentVertex[2].TCoords.Y = Blend( nxs, ct, v, (char*)&v[0]->vTextureCoord[1] - (char*)v[0]);
  					currentVertex[2].TCoords2.X = Blend( nxs, ct, v, (char*)&v[0]->vLightmapCoord[0] - (char*)v[0]);
  					currentVertex[2].TCoords2.Y = Blend( nxs, ct, v, (char*)&v[0]->vLightmapCoord[1] - (char*)v[0]);
  					// Vert 4
  					currentVertex[3].Color.set(255,255,255,255);
  					currentVertex[3].Pos.X = floorf(Blend( nxs, nxt, v, (char*)&v[0]->vPosition[0] - (char*)v[0])+ 0.5f);
  					currentVertex[3].Pos.Y = floorf(Blend( nxs, nxt, v, (char*)&v[0]->vPosition[2] - (char*)v[0])+ 0.5f);
  					currentVertex[3].Pos.Z = floorf(Blend( nxs, nxt, v, (char*)&v[0]->vPosition[1] - (char*)v[0])+ 0.5f);
  					currentVertex[3].Normal.X = Blend( nxs, nxt, v, (char*)&v[0]->vNormal[0] - (char*)v[0]);
  					currentVertex[3].Normal.Y = Blend( nxs, nxt, v, (char*)&v[0]->vNormal[2] - (char*)v[0]);
  					currentVertex[3].Normal.Z = Blend( nxs, nxt, v, (char*)&v[0]->vNormal[1] - (char*)v[0]);
  					currentVertex[3].TCoords.X = Blend( nxs, nxt, v, (char*)&v[0]->vTextureCoord[0] - (char*)v[0]);
  					currentVertex[3].TCoords.Y = Blend( nxs, nxt, v, (char*)&v[0]->vTextureCoord[1] - (char*)v[0]);
  					currentVertex[3].TCoords2.X = Blend( nxs, nxt, v, (char*)&v[0]->vLightmapCoord[0] - (char*)v[0]);
  					currentVertex[3].TCoords2.Y = Blend( nxs, nxt, v, (char*)&v[0]->vLightmapCoord[1] - (char*)v[0]);
  					// Put the vertices in the mesh buffer
  					meshBuffer->Vertices.push_back(currentVertex[0]);
  					meshBuffer->Vertices.push_back(currentVertex[2]);
  					meshBuffer->Vertices.push_back(currentVertex[1]);
  
  					meshBuffer->Vertices.push_back(currentVertex[1]);
  					meshBuffer->Vertices.push_back(currentVertex[2]);
  					meshBuffer->Vertices.push_back(currentVertex[3]);
  
  					// add indexes
  					meshBuffer->Indices.push_back(idx);
  					meshBuffer->Indices.push_back(idx+1);
  					meshBuffer->Indices.push_back(idx+2);
  					// add indexes
  					meshBuffer->Indices.push_back(idx+3);
  					meshBuffer->Indices.push_back(idx+4);
  					meshBuffer->Indices.push_back(idx+5);
  				}
  			}
  		}
  	}
  }
  
  //! get's an interface to the entities
  const quake3::tQ3EntityList & CQ3LevelMesh::getEntityList ()
  {
  	Entity.sort();
  	return Entity;
  }
  
  
  /*!
  */
  const quake3::SShader * CQ3LevelMesh::getShader ( u32 index  ) const
  {
  	index &= 0xFFFF;
  
  	if ( index < Shader.size () )
  	{
  		return &Shader[index];
  	}
  
  	return 0;
  }
  
  //! loads the shader definition
  //  either from file ( we assume /scripts on fileNameIsValid == 0 )
  const quake3::SShader * CQ3LevelMesh::getShader ( const c8 * filename, s32 fileNameIsValid )
  {
  	quake3::SShader search;
  	search.name = filename;
  
  	s32 index;
  	
  	//! is Shader already in cache?
  	index = Shader.linear_search ( search );
  	if ( index >= 0 )
  	{
  		return &Shader[index];
  	}
  
  	core::stringc loadFile;
  
  	if ( 0 == fileNameIsValid )
  	{
  		// extract the shader name from the last path component in filename
  		// "scripts/[name].shader"
  		core::stringc cut ( filename );
  
  		s32 end = cut.findLast ( '/' );
  		s32 start = cut.findLast ( '/', end - 1 );
  
  		loadFile = "scripts";
  		loadFile.append ( cut.subString ( start, end - start ) );
  		loadFile.append ( ".shader" );
  	}
  	else
  	{
  		loadFile = filename;
  	}
  
  	// already loaded the file ?
  	index = ShaderFile.binary_search ( loadFile );
  	if ( index >= 0 )
  		return 0;
  
  #if 0
  	core::stringc message;
  	message = loadFile + " for " + core::stringc ( filename );
  	os::Printer::log("Q3: Loading shader file ", message.c_str(), ELL_INFORMATION);
  	io::IReadFile *file = FileSystem->createAndOpenFile ( loadFile.c_str () );
  	if ( 0 == file )
  	{
  		os::Printer::log("Q3: could not load shader ", loadFile.c_str(), ELL_INFORMATION);
  		return 0;
  	}
  #endif
  
  	if ( !FileSystem->existFile ( loadFile.c_str () ) )
  		return 0;
  
  	io::IReadFile *file = FileSystem->createAndOpenFile ( loadFile.c_str () );
  	if ( 0 == file )
  		return 0;
  
  	core::stringc message;
  	message = loadFile + " for " + core::stringc ( filename );
  	os::Printer::log("Loaded shader", message.c_str(), ELL_INFORMATION);
  
  	// add file to loaded files
  	ShaderFile.push_back ( loadFile );
  
  	// load script
  	core::array<u8> script;
  	u32 len = file->getSize ();
  
  	script.set_used ( len + 2 );
  	script[ len + 1 ] = 0;
  
  	file->seek( 0 );
  	file->read ( script.pointer(), len );
  	file->drop ();
  
  	// start a parser instance
  	parser_parse ( script.pointer(), len, &CQ3LevelMesh::scriptcallback_shader );
  
  	// search again
  	index = Shader.linear_search ( search );
  	if ( index >= 0 )
  		return &Shader[index];
  
  	return 0;
  }
  
  //! adding default shaders
  void CQ3LevelMesh::InitShader ()
  {
  	ReleaseShader ();
  
  	quake3::SShader element;
  
  	quake3::SVarGroup group;
  	quake3::SVariable variable;
  
  	variable.name = "noshader";
  	group.Variable.push_back ( variable );
  
  	element.VarGroup = new quake3::SVarGroupList ();
  	element.VarGroup->VariableGroup.push_back ( group );
  	element.name = element.VarGroup->VariableGroup[0].Variable[0].name.c_str ();
  	Shader.push_back ( element );
  
  	// load common named shader
  	getShader ( "scripts/common.shader", 1 );
  }
  
  //!. script callback for shaders
  //! i'm having troubles with the reference counting, during callback.. resorting..
  void CQ3LevelMesh::ReleaseShader ()
  {
  	for ( u32 i = 0; i!= Shader.size(); ++i )
  	{
  		Shader[i].VarGroup->drop ();
  	}
  	Shader.clear ();
  	ShaderFile.clear();
  }
  
  void CQ3LevelMesh::ReleaseEntity ()
  {
  	for ( u32 i = 0; i!= Entity.size(); ++i )
  	{
  		Entity[i].VarGroup->drop ();
  	}
  	Entity.clear ();
  
  }
  
  
  // entity only has only one valid level.. and no assoziative name..
  void CQ3LevelMesh::scriptcallback_entity ( quake3::SVarGroupList *& grouplist )
  {
  	quake3::SEntity element;
  
  	if ( grouplist->VariableGroup.size () != 2 )
  		return;
  
  
  	element.name = grouplist->VariableGroup[1].get ( "classname" );
  
  	grouplist->grab ();
  	element.VarGroup = grouplist;
  	element.id = Shader.size();
  
  	Entity.push_back ( element );
  }
  
  
  //!. script callback for shaders
  void CQ3LevelMesh::scriptcallback_shader ( quake3::SVarGroupList *& grouplist )
  {
  	quake3::SShader element;
  
  	// TODO: There might be something wrong with this fix, but it avoids a core dump...
  	if (grouplist->VariableGroup[0].Variable.size()==0)
  		return;
  	// end fix
  
  	grouplist->grab ();
  
  	element.VarGroup = grouplist;
  	element.name = element.VarGroup->VariableGroup[0].Variable[0].name.c_str ();
  	element.id = Shader.size();
  
  	Shader.push_back ( element );
  }
  
  
  //! loads the textures
  void CQ3LevelMesh::loadTextures()
  {
  	if (!Driver)
  		return;
  
  	core::stringc s;
  	core::stringc extensions[2];
  	extensions[0] = ".jpg";
  	extensions[1] = ".tga";
  
  	// load textures
  
  	core::array<video::ITexture*> tex;
  	tex.set_used(NumTextures+1);
  
  	tex[0] = 0;
  	
  	s32 t;// new ISO for scoping problem with some compilers
  
  	for (t=1; t<(NumTextures+1); ++t)
  	{
  		tex[t] = 0;
  
  		if ( !tex[t] )
  		{
  			for (s32 e=0; e<2; ++e)
  			{
  				s = Textures[t-1].strName;
  				s.append(extensions[e]);
  				if (FileSystem->existFile(s.c_str()))
  				{
  					tex[t] = Driver->getTexture(s.c_str());
  					break;
  				}
  			}
  		}
  		if (!tex[t])
  		{
  			os::Printer::log("Q3: no texmap for texturename ", Textures[t-1].strName, ELL_WARNING);
  		}
  	}
  
  	// load lightmaps.
  	core::array<video::ITexture*> lig;
  	lig.set_used(NumLightMaps+1);
  
  	lig[0] = 0;
  	c8 lightmapname[255];
  	core::dimension2d<s32> lmapsize(128,128);
  
  	//bool oldMipMapState = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS);
  	//Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
  
  	video::IImage* lmapImg;
  	for (t=1; t<(NumLightMaps+1); ++t)
  	{
  		sprintf(lightmapname, "%s.lightmap.%d", LevelName.c_str(), t);
  
  		// lightmap is a CTexture::R8G8B8 format
  		lmapImg = Driver->createImageFromData(
  			video::ECF_R8G8B8, 
  			lmapsize,
  			LightMaps[t-1].imageBits, true, false );
  
  		lig[t] = Driver->addTexture ( lightmapname, lmapImg );
  		lmapImg->drop ();
  
  	}
  	//Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, oldMipMapState);
  
  
  	// attach textures to materials.
  	for (s32 l=0; l<NumLightMaps+1; ++l)
  		for (t=0; t<NumTextures+1; ++t)
  		{
  			SMeshBufferLightMap* b = (SMeshBufferLightMap*)Mesh[0]->getMeshBuffer(l*(NumTextures+1) + t);
  			b->Material.Textures[1] = lig[l];
  			b->Material.Textures[0] = tex[t];
  
  			if (!b->Material.Textures[1])
  				b->Material.MaterialType = video::EMT_SOLID;
  
  			if ( !b->Material.Textures[0] )
   				b->Material.MaterialType = video::EMT_SOLID;
  
  		}
  
  }
  
  // delete all buffers without geometry in it.
  void CQ3LevelMesh::cleanMeshes ()
  {
  	// delete all buffers without geometry in it.
  	for ( u32 g = 0; g < quake3::E_Q3_MESH_SIZE; ++g )
  	{
  		u32 i = 0;
  		bool texture0important = ( g == 0 );
  		while(i < Mesh[g]->MeshBuffers.size())
  		{
  			if (Mesh[g]->MeshBuffers[i]->getVertexCount() == 0 ||
  				Mesh[g]->MeshBuffers[i]->getIndexCount() == 0 ||
  				( texture0important && Mesh[g]->MeshBuffers[i]->getMaterial().Textures[0] == 0 )
  				)
  			{
  				// delete Meshbuffer
  				Mesh[g]->MeshBuffers[i]->drop();
  				Mesh[g]->MeshBuffers.erase(i);		
  			}
  			else
  				++i;
  		}
  	}
  
  }
  
  // recalculate bounding boxes
  void CQ3LevelMesh::calcBoundingBoxes ()
  {
  	// create bounding box
  	for ( u32 g = 0; g != quake3::E_Q3_MESH_SIZE; ++g )
  	{
  		for ( u32 j=0; j < Mesh[g]->MeshBuffers.size(); ++j)
  		{
  			((SMeshBufferLightMap*)Mesh[g]->MeshBuffers[j])->recalculateBoundingBox();
  			
  		}
  
  		Mesh[g]->recalculateBoundingBox();
  	}
  
  }
  
  /*
  //! loads a texture
  video::ITexture* CQ3LevelMesh::loadTexture ( const tStringList &stringList )
  {
  	static const char * extension[2] = 
  	{
  		".jpg",
  		".tga"
  	};
  
  	core::stringc loadFile;
  	for ( u32 i = 0; i!= stringList.size (); ++i )
  	{
  		for ( u32 g = 0; g != 2 ; ++g )
  		{
  			cutFilenameExtension ( loadFile, stringList[i] ).append ( extension[g] );
  
  			if ( FileSystem->existFile ( loadFile.c_str() ) )
  			{
  				video::ITexture* t = Driver->getTexture( loadFile.c_str() );
  				if ( t )
  					return t;
  			}
  		}
  	}
  	return 0;
  }
  */
  
  //! loads the textures
  void CQ3LevelMesh::loadTextures2()
  {
  	if (!Driver)
  		return;
  
  	s32 t;
  
  	// load lightmaps.
  	Lightmap.set_used(NumLightMaps+1);
  
  	c8 lightmapname[255];
  	core::dimension2d<s32> lmapsize(128,128);
  
  	video::IImage* lmapImg;
  	for ( t = 0; t < NumLightMaps ; ++t)
  	{
  		sprintf(lightmapname, "%s.lightmap.%d", LevelName.c_str(), t);
  
  		// lightmap is a CTexture::R8G8B8 format
  		lmapImg = Driver->createImageFromData(
  			video::ECF_R8G8B8, 
  			lmapsize,
  			LightMaps[t].imageBits, true, false );
  
  		Lightmap[t] = Driver->addTexture ( lightmapname, lmapImg );
  		lmapImg->drop ();
  
  	}
  
  	// load textures
  	Tex.set_used( NumTextures+1 );
  	
  	const quake3::SShader * shader;
  
  	core::stringc list;
  	core::stringc check;
  	quake3::tTexArray textureArray;
  
  	for ( t=0; t< NumTextures; ++t)
  	{
  		Tex[t].ShaderID = -1;
  		Tex[t].Texture = 0;
  
  		list = "";
  
  		// get a shader ( if one exists )
  		shader = getShader ( Textures[t].strName, 0 );
  		if ( shader )
  		{
  			Tex[t].ShaderID = shader->id;
  
  			// if texture name == stage1 Texture map
  			const quake3::SVarGroup * group;
  
  			group = shader->getGroup ( 2 );
  			if ( group )
  			{
  				if ( core::cutFilenameExtension ( check, group->get ( "map" ) ) == Textures[t].strName )
  				{
  					list += check;
  				}
  				else
  				if ( check == "$lightmap" )
  				{
  					// we check if lightmap is in stage 1 and texture in stage 2
  					group = shader->getGroup ( 3 );
  					if ( group )
  						list += group->get ( "map" );
  				}
  			}
  		}
  		else
  		{
  			// no shader, take it
  			list += Textures[t].strName;
  		}
  
  		u32 pos = 0;
  		quake3::getTextures ( textureArray, list, pos, FileSystem, Driver );
  
  		Tex[t].Texture = textureArray[0];
  	}
  }
  
  
  //! Returns an axis aligned bounding box of the mesh.
  //! \return A bounding box of this mesh is returned.
  const core::aabbox3d<f32>& CQ3LevelMesh::getBoundingBox() const
  {
  	return Mesh[0]->getBoundingBox();
  }
  
  
  //! Returns the type of the animated mesh.
  E_ANIMATED_MESH_TYPE CQ3LevelMesh::getMeshType() const
  {
  	return scene::EAMT_BSP;
  }
  
  } // end namespace scene
  } // end namespace irr
  
  