//models.cpp
//3d model drawing code

#include <stdio.h>
#include <string.h>
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#include <math.h>
#include "fileio.h"
#include "gamemem.h"
#include "vectors.h"
#include "entities.h"
#include "textures.h"
#include "renderframe.h"
#include "environment.h"
#include "transparency.h"
#include "config.h"
#include "modeltypes.h"
#include "models.h"
#include "stencil.h"
#include "network.h"

//#define __USEDISPLAYLISTS
#ifndef __TARGET_TOOLAPP
#define __USEVERTEXARRAYS
#endif

typedef struct{
	void *next;
	tFileRef model;
} tModelList;

tModelList *gModelList=NULL;

#define kShadowZoom 0.99

//When a model is loaded it won't neccesaryly have the correct
//file reference IDs for the texture files, as file reference IDs 
//are redefined each time at application startup.
//this function inserts the correct file reference IDs for a model's
//textures based on file names.
void ModelInsertTextureRefs(tFileRef modelRef)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	if(modelData->modelFlags&kFlagMultiTextureFlag)
		materials=(tMaterial*)((tFaceDataMT*)faces+modelData->faceCount);
	for(int i=0;i<modelData->materialCount;i++)
		materials[i].m.texRef=FileGetReference(materials[i].texName);	
}

void StoreFaceVertices(tVector3 *vertices,tVector3 *normals,tVector2 *texels,tFaceData *faces,int i,tVertexArrayElement *store,int mt)
{
	if(mt)
		if(((tFaceDataMT*)faces)[i].vertices->normal!=-1)
			for(int vertex=0;vertex<3;vertex++){
				if(((tFaceDataMT*)faces)[i].vertices->texel!=-1)
					store[0][vertex].texel=texels[((tFaceDataMT*)faces)[i].vertices[vertex].texel];
				else
					store[0][vertex].texel=Vector(0,0);
				if(((tFaceDataMT*)faces)[i].vertices->texel2!=-1)
					store[0][vertex].texel2=texels[((tFaceDataMT*)faces)[i].vertices[vertex].texel2];
				else
					store[0][vertex].texel2=Vector(0,0);
				store[0][vertex].normal=normals[((tFaceDataMT*)faces)[i].vertices[vertex].normal];
				store[0][vertex].vertex=vertices[((tFaceDataMT*)faces)[i].vertices[vertex].vertex];
			}
		else{
			//otherwise, calculate a normal.
			tVector3 *a=vertices+(((tFaceDataMT*)faces)[i].vertices[0].vertex);
			tVector3 *b=vertices+(((tFaceDataMT*)faces)[i].vertices[1].vertex);
			tVector3 *c=vertices+(((tFaceDataMT*)faces)[i].vertices[2].vertex);
			tVector3 n=!((*b-*a)%(*c-*a));
			for(int vertex=0;vertex<3;vertex++)
			{
				if(((tFaceDataMT*)faces)[i].vertices->texel!=-1)
					store[0][vertex].texel=texels[((tFaceDataMT*)faces)[i].vertices[vertex].texel];
				else
					store[0][vertex].texel=Vector(0,0);
				if(((tFaceDataMT*)faces)[i].vertices->texel2!=-1)
					store[0][vertex].texel2=texels[((tFaceDataMT*)faces)[i].vertices[vertex].texel2];
				else
					store[0][vertex].texel2=Vector(0,0);
				store[0][vertex].normal=n;
				store[0][vertex].vertex=vertices[((tFaceDataMT*)faces)[i].vertices[vertex].vertex];
			}		
		}
	else if(faces[i].vertices->normal!=-1)
			for(int vertex=0;vertex<3;vertex++){
				if(faces[i].vertices->texel!=-1)
					store[0][vertex].texel=texels[faces[i].vertices[vertex].texel];
				else
					store[0][vertex].texel=Vector(0,0);
				store[0][vertex].normal=normals[faces[i].vertices[vertex].normal];
				store[0][vertex].vertex=vertices[faces[i].vertices[vertex].vertex];
			}
		else{
			//otherwise, calculate a normal.
			tVector3 *a=vertices+(faces[i].vertices[0].vertex);
			tVector3 *b=vertices+(faces[i].vertices[1].vertex);
			tVector3 *c=vertices+(faces[i].vertices[2].vertex);
			tVector3 n=!((*b-*a)%(*c-*a));
			for(int vertex=0;vertex<3;vertex++)
			{
				if(faces[i].vertices->texel!=-1)
					store[0][vertex].texel=texels[faces[i].vertices[vertex].texel];
				else
					store[0][vertex].texel=Vector(0,0);
				store[0][vertex].normal=n;
				store[0][vertex].vertex=vertices[faces[i].vertices[vertex].vertex];
			}		
		}
}

void ModelInitArrays(tFileRef modelRef)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	int mt=modelData->modelFlags&kFlagMultiTextureFlag;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	tFaceDataMT *facesMT=(tFaceDataMT*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	if(mt)
		materials=(tMaterial*)(facesMT+modelData->faceCount);
	int matCount=mt?modelData->materialCount/2:modelData->materialCount;
	
	((tModel*)gFileTable[modelRef].parsedData)->indices=(tMaterialArrayIndices*)MemoryAllocateZeroedBlock(sizeof(tMaterialArrayIndices)*matCount);
	tMaterialArrayIndices *indices=((tModel*)gFileTable[modelRef].parsedData)->indices;
	((tModel*)gFileTable[modelRef].parsedData)->array=(tVertexArrayElement*)MemoryAllocateBlock(sizeof(tVertexArrayElement)*modelData->faceCount);
	tVertexArrayElement *array=((tModel*)gFileTable[modelRef].parsedData)->array;
	
	//arrays are indexed starting with 1, so we shift all arrays
	//down by one index.
	vertices--;
	normals--;
	texels--;
	materials--;

	//for each face
	for(int i=0;i<modelData->faceCount;i++)
	{	
		int mat=mt?facesMT[i].material:faces[i].material;
		int faceMaterial=mat&kMaterialMask;	
		if(faceMaterial!=-1&&faceMaterial!=0)
			if((!(materials[faceMaterial].m.flags&kMaterialUseAlphaChannel))||mat&kDisableTransparencyFlag)
				indices[faceMaterial-1].num++;
			else
				indices[faceMaterial-1].numTransparent++;
	}
		
	int arrayVertexCount=0;
	for(int material=1;material<=matCount;material++)
	{
		indices[material-1].start=arrayVertexCount;
		for(int i=0;i<modelData->faceCount;i++)
		{	
			int mat=mt?facesMT[i].material:faces[i].material;
			int faceMaterial=mat&kMaterialMask;	
			if(material==faceMaterial)	
				if((!(materials[faceMaterial].m.flags&kMaterialUseAlphaChannel))||mat&kDisableTransparencyFlag)
					StoreFaceVertices(vertices,normals,texels,faces,i,array+arrayVertexCount++,mt);
		}
		for(int i=0;i<modelData->faceCount;i++)
		{	
			int mat=mt?facesMT[i].material:faces[i].material;
			int faceMaterial=mat&kMaterialMask;	
			if(material==faceMaterial)	
				if(materials[faceMaterial].m.flags&kMaterialUseAlphaChannel&&!(mat&kDisableTransparencyFlag))
					StoreFaceVertices(vertices,normals,texels,faces,i,array+arrayVertexCount++,mt);
		}
	}	
	
	glGenVertexArraysAPPLE(1,&(((tModel*)gFileTable[modelRef].parsedData)->arrayRef));
	glBindVertexArrayAPPLE(((tModel*)gFileTable[modelRef].parsedData)->arrayRef);
	glInterleavedArrays(GL_T2F_N3F_V3F,sizeof(tVertexArrayVertex),array);
	
	if(mt){
		glClientActiveTextureARB(GL_TEXTURE1_ARB); 
		glTexCoordPointer(2,GL_FLOAT,sizeof(tVertexArrayVertex),((char*)array)+sizeof(tVector2)+2*sizeof(tVector3));
	    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glClientActiveTextureARB(GL_TEXTURE0_ARB); 
	}
}

enum{
	kModeDrawNormal,
	kModeDrawEverythingSolid,
	kModeDrawOnlyTransparent,
	kModeDrawOnlyTransparentSolid,
	kModeDrawOnlySolid,
	kModeDrawEverythingGhosted
};


void ModelAddGhostedArrays(tFileRef modelRef,int textureSet)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	
	tMaterialArrayIndices *indices=((tModel*)gFileTable[modelRef].parsedData)->indices;
	tVertexArrayElement *array=((tModel*)gFileTable[modelRef].parsedData)->array;
	//arrays are indexed starting with 1, so we shift all arrays
	//down by one index.
	materials--;

	for(int i=0;i<modelData->materialCount;i++)
		for(int j=indices[i].start;j<indices[i].start+indices[i].num+indices[i].numTransparent;j++)
			if(tTransparentPoly *p=GetNextTransparentPoly(false))
			{
				p->mat=&(materials+i+1)->m;
				p->ghost=true;
				p->textureSet=textureSet;
				for(int v=0;v<3;v++)
				{
					p->v[v]=array[j][v];
					p->v[v].vertex=p->v[v].vertex*gTransformDir+gTransformPos;
				}
			}
}

void ModelAddTransparentArrays(tFileRef modelRef,int textureSet)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	
	tMaterialArrayIndices *indices=((tModel*)gFileTable[modelRef].parsedData)->indices;
	tVertexArrayElement *array=((tModel*)gFileTable[modelRef].parsedData)->array;
	//arrays are indexed starting with 1, so we shift all arrays
	//down by one index.
	materials--;

	for(int i=0;i<modelData->materialCount;i++)
		for(int j=indices[i].start+indices[i].num;j<indices[i].start+indices[i].num+indices[i].numTransparent;j++)
			if(tTransparentPoly *p=GetNextTransparentPoly(false))
			{
				p->mat=&(materials+i+1)->m;
				p->textureSet=textureSet;
				for(int v=0;v<3;v++)
				{
					p->v[v]=array[j][v];
					p->v[v].normal=p->v[v].normal*gTransformDir;
					p->v[v].vertex=p->v[v].vertex*gTransformDir+gTransformPos;
				}
			}
}

void ModelDrawArrays(tFileRef modelRef,int mode,int textureSet)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	int mt=modelData->modelFlags&kFlagMultiTextureFlag;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	tFaceDataMT *facesMT=(tFaceDataMT*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	if(mt)
		materials=(tMaterial*)(facesMT+modelData->faceCount);
	int matCount=mt?modelData->materialCount/2:modelData->materialCount;
	
	tMaterialArrayIndices *indices=((tModel*)gFileTable[modelRef].parsedData)->indices;

	//arrays are indexed starting with 1, so we shift all arrays
	//down by one index.
	materials--;
	
	glBindVertexArrayAPPLE(((tModel*)gFileTable[modelRef].parsedData)->arrayRef);
	//glInterleavedArrays(GL_T2F_N3F_V3F,0,((tModel*)gFileTable[modelRef].parsedData)->array);
		
	//glLockArraysEXT(0,modelData->faceCount*3);
	
/*		if(TexturesSelectTextureUnit(1))
	{
		glTexCoordPointer(2,GL_FLOAT,sizeof(tVertexArrayVertex),((char*)((tModel*)gFileTable[modelRef].parsedData)->array)+sizeof(tVector2)+2*sizeof(tVector3));
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
     	TexturesSelectTextureUnit(0);
	}
	glTexCoordPointer(2,GL_FLOAT,sizeof(tVertexArrayVertex),((char*)((tModel*)gFileTable[modelRef].parsedData)->array));
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glNormalPointer(GL_FLOAT,sizeof(tVertexArrayVertex),((char*)((tModel*)gFileTable[modelRef].parsedData)->array)+sizeof(tVector2));
	glEnableClientState(GL_NORMAL_ARRAY);
	glVertexPointer(3,GL_FLOAT,sizeof(tVertexArrayVertex),((char*)((tModel*)gFileTable[modelRef].parsedData)->array)+sizeof(tVector2)+sizeof(tVector3));
	glEnableClientState(GL_VERTEX_ARRAY);*/


	if(mode!=kModeDrawOnlyTransparent)
		for(int i=0;i<matCount;i++)
		{
			int start=indices[i].start;
			if(mode==kModeDrawOnlyTransparentSolid)
				start+=indices[i].num;
			int num;
			if(mode==kModeDrawNormal||mode==kModeDrawOnlySolid)
				num=indices[i].num;
			else if(mode==kModeDrawOnlyTransparentSolid)
				num=indices[i].numTransparent;
			else if(mode==kModeDrawEverythingSolid)
				num=indices[i].num+indices[i].numTransparent;
		
			if(mt)
				UseMaterial2((&(materials+i+1)->m),(&(materials+i+1+matCount)->m),textureSet);
			else
				UseMaterial((&(materials+i+1)->m),false,textureSet);
			glDrawArrays(GL_TRIANGLES,start*3,num*3);
		}	
	//glUnlockArraysEXT();
		
	if((mode==kModeDrawNormal||mode==kModeDrawOnlyTransparent)&&!mt)
		ModelAddTransparentArrays(modelRef,textureSet);
}

void ModelCompileDisplayList(tFileRef modelRef);

void ModelCalcClipInfo(tFileRef modelRef)
{
	tVector3 minv=Vector(INFINITY,INFINITY,INFINITY);
	tVector3 maxv=Vector(-INFINITY,-INFINITY,-INFINITY);
	
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	tVector3 *vertices=(tVector3*)&modelData->data;
	for(int i=0;i<modelData->vertexCount;i++)
	{
		if(vertices[i].x<minv.x)minv.x=vertices[i].x;
		if(vertices[i].y<minv.y)minv.y=vertices[i].y;
		if(vertices[i].z<minv.z)minv.z=vertices[i].z;
		if(vertices[i].x>maxv.x)maxv.x=vertices[i].x;
		if(vertices[i].y>maxv.y)maxv.y=vertices[i].y;
		if(vertices[i].z>maxv.z)maxv.z=vertices[i].z;
	}
	if(modelData->vertexCount)
	{
		((tModel*)gFileTable[modelRef].parsedData)->center=(minv+maxv)*0.5;
		((tModel*)gFileTable[modelRef].parsedData)->centerMaxExtends=~(minv-((tModel*)gFileTable[modelRef].parsedData)->center);
	}
	else
	{
		((tModel*)gFileTable[modelRef].parsedData)->center=Vector(0,0,0);
		((tModel*)gFileTable[modelRef].parsedData)->centerMaxExtends=0;
	}
}

void ModelSwitchEndianess(tFileRef modelRef)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	S32Swap(modelData->vertexCount);
	S32Swap(modelData->normalCount);
	S32Swap(modelData->faceCount);
	S32Swap(modelData->materialCount);
	S32Swap(modelData->modelFlags);
	S32Swap(modelData->texelCount);

	S32Swap(*((SInt32*)(&modelData->maxExtends)));
		
	int mt=modelData->modelFlags&kFlagMultiTextureFlag;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	tFaceDataMT *facesMT=(tFaceDataMT*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	if(mt)
		materials=(tMaterial*)(facesMT+modelData->faceCount);
	
	for(int i=0;i<modelData->vertexCount;i++)
	{
		S32Swap(*((SInt32*)(&(vertices[i].x))));
		S32Swap(*((SInt32*)(&(vertices[i].y))));
		S32Swap(*((SInt32*)(&(vertices[i].z))));
	}
	
	for(int i=0;i<modelData->normalCount;i++)
	{
		S32Swap(*((SInt32*)(&(normals[i].x))));
		S32Swap(*((SInt32*)(&(normals[i].y))));
		S32Swap(*((SInt32*)(&(normals[i].z))));
	}
	
	for(int i=0;i<modelData->texelCount;i++)
	{
		S32Swap(*((SInt32*)(&(texels[i].x))));
		S32Swap(*((SInt32*)(&(texels[i].y))));
	}
	

	if(mt)
		for(int i=0;i<modelData->faceCount;i++)
		{
			S32Swap(facesMT[i].material);
			for(int j=0;j<3;j++)
			{
				S32Swap(facesMT[i].vertices[j].vertex);
				S32Swap(facesMT[i].vertices[j].normal);
				S32Swap(facesMT[i].vertices[j].texel);
				S32Swap(facesMT[i].vertices[j].texel2);
				S32Swap(facesMT[i].vertices[j].neighbor);
			}
		}
	else
		for(int i=0;i<modelData->faceCount;i++)
		{
			S32Swap(faces[i].material);
			for(int j=0;j<3;j++)
			{
				S32Swap(faces[i].vertices[j].vertex);
				S32Swap(faces[i].vertices[j].normal);
				S32Swap(faces[i].vertices[j].texel);
				S32Swap(faces[i].vertices[j].neighbor);
			}
		}
	for(int i=0;i<modelData->materialCount;i++)
	{
		S32Swap(materials[i].m.flags);
		S32Swap(*((SInt32*)(&(materials[i].m.shininess))));
		S32Swap(*((SInt32*)(&(materials[i].m.specular.x))));
		S32Swap(*((SInt32*)(&(materials[i].m.specular.y))));
		S32Swap(*((SInt32*)(&(materials[i].m.specular.z))));
		S32Swap(*((SInt32*)(&(materials[i].m.ambient.x))));
		S32Swap(*((SInt32*)(&(materials[i].m.ambient.y))));
		S32Swap(*((SInt32*)(&(materials[i].m.ambient.z))));
		S32Swap(*((SInt32*)(&(materials[i].m.diffuse.x))));
		S32Swap(*((SInt32*)(&(materials[i].m.diffuse.y))));
		S32Swap(*((SInt32*)(&(materials[i].m.diffuse.z))));
	}
}

//Load a model from disk
void ModelLoad(tFileRef modelRef)
{
	gFileTable[modelRef].parsedData=MemoryAllocateBlock(sizeof(tModel));
	((tModel*)gFileTable[modelRef].parsedData)->data=(tModelData*)FileGetDataPtr(modelRef);

#if TARGET_RT_LITTLE_ENDIAN
	ModelSwitchEndianess(modelRef);
#endif

	ModelInsertTextureRefs(modelRef);
#ifdef __USEVERTEXARRAYS
	ModelInitArrays(modelRef);
#endif	
#ifdef __USEDISPLAYLISTS
	ModelCompileDisplayList(modelRef);
#endif
	gFileTable[modelRef].parsed=true;
	ModelCalcClipInfo(modelRef);
	
	tModelList *modelList=gModelList;
	gModelList=(tModelList*)MemoryAllocateBlock(sizeof(tModelList));
	gModelList->next=(void*)modelList;
	gModelList->model=modelRef;

}

tVector3 ModelGetCenter(tFileRef modelRef)
{
	if(!gFileTable[modelRef].parsed)
		ModelLoad(modelRef);
	return ((tModel*)gFileTable[modelRef].parsedData)->center;
}

float ModelGetCenterMaxExtends(tFileRef modelRef)
{
	if(!gFileTable[modelRef].parsed)
		ModelLoad(modelRef);
	return ((tModel*)gFileTable[modelRef].parsedData)->centerMaxExtends;
}

//Dispose all data structures associated with a model.
void ModelDispose(tFileRef modelRef)
{
	MemoryFreeBlock(((tModel*)gFileTable[modelRef].parsedData)->data);
	MemoryFreeBlock(((tModel*)gFileTable[modelRef].parsedData)->indices);
	MemoryFreeBlock(((tModel*)gFileTable[modelRef].parsedData)->array);
	if(gFileTable[modelRef].data!=((tModel*)gFileTable[modelRef].parsedData)->data)
		MemoryFreeBlock(gFileTable[modelRef].data);
		
#ifdef __USEDISPLAYLISTS
	glDeleteLists(((tModel*)gFileTable[modelRef].parsedData)->ref,1);
#endif
	glDeleteVertexArraysAPPLE(1,&((tModel*)gFileTable[modelRef].parsedData)->arrayRef);
	MemoryFreeBlock(((tModel*)gFileTable[modelRef].parsedData));
	
	gFileTable[modelRef].parsed=false;
	gFileTable[modelRef].loaded=false;
}

void ModelsUnloadAll()
{
	while(gModelList!=NULL)
	{
		tModelList *next=(tModelList*)gModelList->next;
		ModelDispose(gModelList->model);
		MemoryFreeBlock(gModelList);
		gModelList=next;
	}
}

//return the maximum extends of a model. (the distance from
//the furthest vertex from center).
float ModelGetMaxExtends(tFileRef modelRef)
{
	if(!gFileTable[modelRef].parsed)
		ModelLoad(modelRef);
	if(!gFileTable[modelRef].parsed)
		return 0;
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
		
	if(!modelData->maxExtends)
	{
		tVector3 *vertices=(tVector3*)&modelData->data;
		tVector3 maxExtends=Vector(0,0,0);
		for(int i=0;i<modelData->vertexCount;i++)
		{
			if(fabs(vertices[i].x)>maxExtends.x)maxExtends.x=fabs(vertices[i].x);
			if(fabs(vertices[i].y)>maxExtends.y)maxExtends.y=fabs(vertices[i].y);
			if(fabs(vertices[i].z)>maxExtends.z)maxExtends.z=fabs(vertices[i].z);
		}
		modelData->maxExtends=~maxExtends;
	}
	return modelData->maxExtends;
}

//Draw a model
void ModelDraw(tFileRef modelRef,int mode,int textureSet)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	
	//arrays are indexed starting with 1, so we shift all arrays
	//down by one index.
	vertices--;
	normals--;
	texels--;
	materials--;
	
	int material=-1;//currently used material
	int transparent;

	//for each face
	for(int i=0;i<modelData->faceCount;i++)
	{	
		int faceMaterial=faces[i].material&kMaterialMask;
		//check if the material is the same as we were using
		if(faceMaterial!=material){
			//if not, change material
			transparent=UseMaterial(&((materials+faceMaterial)->m),false,textureSet);
			if(mode==kModeDrawEverythingSolid)
				transparent=false;
			material=faceMaterial;
		}
		
		//if the face isn't transparent, draw it
		int drawTransparent=transparent&&!(faces[i].material&kDisableTransparencyFlag);
		if(!drawTransparent||(drawTransparent&&mode==kModeDrawOnlyTransparentSolid))
		{
			if(mode!=kModeDrawOnlyTransparent)
			{
				glBegin(GL_TRIANGLES);
				//do we have a normal?
				if(faces[i].vertices->normal!=-1)
					for(int vertex=0;vertex<3;vertex++){
						if(faces[i].vertices->texel!=-1)
							glTexCoord2fv(&texels[faces[i].vertices[vertex].texel].x);
						glNormal3fv(&normals[faces[i].vertices[vertex].normal].x);
						glVertex3fv(&vertices[faces[i].vertices[vertex].vertex].x);
					}
				else{
					//otherwise, calculate a normal.
					tVector3 *a=vertices+(faces[i].vertices[0].vertex);
					tVector3 *b=vertices+(faces[i].vertices[1].vertex);
					tVector3 *c=vertices+(faces[i].vertices[2].vertex);
					tVector3 n=!((*b-*a)%(*c-*a));
					glNormal3fv(&n.x);	
					for(int vertex=0;vertex<3;vertex++)
					{
						if(faces[i].vertices->texel!=-1)
							glTexCoord2fv(&texels[faces[i].vertices[vertex].texel].x);
						glVertex3fv(&vertices[faces[i].vertices[vertex].vertex].x);	
					}		
				}
				glEnd();
			}
		}
		//if the face is transparent, add it to the list of transparent faces
		else if(mode!=kModeDrawOnlySolid)
			if(tTransparentPoly *p=GetNextTransparentPoly(false)){
				if(faces[i].vertices->normal!=-1)
					for(int vertex=0;vertex<3;vertex++){
						if(faces[i].vertices->texel!=-1)
							p->v[vertex].texel=texels[faces[i].vertices[vertex].texel];
						else
							p->v[vertex].texel=Vector(0,0);
						p->v[vertex].normal=normals[faces[i].vertices[vertex].normal]*gTransformDir;
						p->v[vertex].vertex=vertices[faces[i].vertices[vertex].vertex]*gTransformDir+gTransformPos;
					}
				else{
					tVector3 *a=vertices+(faces[i].vertices[0].vertex);
					tVector3 *b=vertices+(faces[i].vertices[1].vertex);
					tVector3 *c=vertices+(faces[i].vertices[2].vertex);
					tVector3 n=!((*b-*a)%(*c-*a));
					for(int vertex=0;vertex<3;vertex++)
					{
						if(faces[vertex].vertices->texel!=-1)
							p->v[vertex].texel=texels[faces[i].vertices[vertex].texel];
						else
							p->v[vertex].texel=Vector(0,0);
						p->v[vertex].normal=n*gTransformDir;
						p->v[vertex].vertex=vertices[faces[i].vertices[vertex].vertex]*gTransformDir+gTransformPos;
					}		
				}
				p->mat=&(materials+faces[i].material)->m;
				p->textureSet=textureSet;
			}
	}	
}

void ModelDrawMultiTextured(tFileRef modelRef,int textureSet)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	tFaceDataMT *faces=(tFaceDataMT*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	
	//arrays are indexed starting with 1, so we shift all arrays
	//down by one index.
	vertices--;
	normals--;
	texels--;
	materials--;
	
	int material=-1;//currently used material

	//for each face
	for(int i=0;i<modelData->faceCount;i++)
	{	
		int faceMaterial=faces[i].material&kMaterialMask;
		//check if the material is the same as we were using
		if(faceMaterial!=material){
			//if not, change material
			UseMaterial2(&((materials+faceMaterial)->m),&((materials+faceMaterial+modelData->materialCount/2)->m),textureSet);
			material=faceMaterial;
		}
		
		glBegin(GL_TRIANGLES);
		//do we have a normal?
		if(faces[i].vertices->normal!=-1)
			for(int vertex=0;vertex<3;vertex++){
				if(faces[i].vertices->texel!=-1)
					glTexCoord2fv(&texels[faces[i].vertices[vertex].texel].x);
				if(faces[i].vertices->texel2!=-1)
					glMultiTexCoord2fv(GL_TEXTURE1,&texels[faces[i].vertices[vertex].texel2].x);
				glNormal3fv(&normals[faces[i].vertices[vertex].normal].x);
				glVertex3fv(&vertices[faces[i].vertices[vertex].vertex].x);
			}
		else{
			//otherwise, calculate a normal.
			tVector3 *a=vertices+(faces[i].vertices[0].vertex);
			tVector3 *b=vertices+(faces[i].vertices[1].vertex);
			tVector3 *c=vertices+(faces[i].vertices[2].vertex);
			tVector3 n=!((*b-*a)%(*c-*a));
			glNormal3fv(&n.x);	
			for(int vertex=0;vertex<3;vertex++)
			{
				if(faces[i].vertices->texel!=-1)
					glTexCoord2fv(&texels[faces[i].vertices[vertex].texel].x);
				if(faces[i].vertices->texel2!=-1)
					glMultiTexCoord2fv(GL_TEXTURE1,&texels[faces[i].vertices[vertex].texel2].x);
				glVertex3fv(&vertices[faces[i].vertices[vertex].vertex].x);	
			}		
		}
		glEnd();
	}	
}

void ModelLoadTextures(tFileRef modelRef)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	for(int i=0;i<modelData->materialCount;i++)
		UseMaterial(&((materials+i)->m),false,0);
}

void ModelCompileDisplayList(tFileRef modelRef)
{
	//create an OpenGL display list for a model
	((tModel*)gFileTable[modelRef].parsedData)->ref=glGenLists(1);
	ModelLoadTextures(modelRef);
	glNewList(((tModel*)gFileTable[modelRef].parsedData)->ref,GL_COMPILE);
#ifdef __USEVERTEXARRAYS
	ModelDrawArrays(modelRef,kModeDrawOnlySolid,0);
#else
	ModelDraw(modelRef,kModeDrawOnlySolid,0);
#endif	
	glEndList();
}

void ModelShadowPassZFail(tFileRef modelRef,tVector3 shadowVector,char *faceSide)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	vertices--;
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	materials--;
	tVector3 sw=0.05*!shadowVector;

	for(int i=0;i<modelData->faceCount;i++)
		if(!((materials+(faces[i].material&kMaterialMask))->m.flags&kMaterialDisableShadow))
			if(modelData->modelFlags&kFlagShadowFlag)
				{
					for(int n=0;n<3;n++)
						if(faces[i].vertices[n].neighbor<=-1)
							{
								glBegin(GL_TRIANGLE_STRIP);
								glVertex3fv(&(vertices[faces[i].vertices[n].vertex]*gStencilZoom-sw).x);
								glVertex3fv(&(vertices[faces[i].vertices[(n+1)%3].vertex]*gStencilZoom-sw).x);
								glVertex3fv(&(vertices[faces[i].vertices[n].vertex]*gStencilZoom-shadowVector).x);
								glVertex3fv(&(vertices[faces[i].vertices[(n+1)%3].vertex]*gStencilZoom-shadowVector).x);
								glEnd();
								#ifdef __POLYCOUNT
									gPolyCount+=2;
								#endif
							}
					
					if(faceSide[i]||1)
					{
						glBegin(GL_TRIANGLES);
						for(int j=2;j>=0;j--)
							glVertex3fv(&(vertices[faces[i].vertices[j].vertex]*gStencilZoom-sw).x);
	
						for(int j=0;j<=2;j++)
							glVertex3fv(&(vertices[faces[i].vertices[j].vertex]*gStencilZoom-shadowVector).x);
						glEnd();			
					}
					else
					{
						glBegin(GL_TRIANGLES);
						for(int j=2;j>=0;j--)
							glVertex3fv(&(vertices[faces[i].vertices[j].vertex]*gStencilZoom-shadowVector).x);

						for(int j=0;j<=2;j++)
							glVertex3fv(&(vertices[faces[i].vertices[j].vertex]*gStencilZoom-sw).x);
						glEnd();			
					}
				}
			else
				if(faceSide[i])
				{
					for(int n=0;n<3;n++)
						if(faces[i].vertices[n].neighbor!=-1)
							{
								if(faceSide[faces[i].vertices[n].neighbor]!=faceSide[i])
								{
									glBegin(GL_TRIANGLE_STRIP);
									glVertex3fv(&(vertices[faces[i].vertices[n].vertex]*(kShadowZoom*gStencilZoom)).x);
									glVertex3fv(&(vertices[faces[i].vertices[(n+1)%3].vertex]*(kShadowZoom*gStencilZoom)).x);
									glVertex3fv(&(vertices[faces[i].vertices[n].vertex]*(kShadowZoom*gStencilZoom)-shadowVector).x);
									glVertex3fv(&(vertices[faces[i].vertices[(n+1)%3].vertex]*(kShadowZoom*gStencilZoom)-shadowVector).x);
									glEnd();
									#ifdef __POLYCOUNT
										gPolyCount+=2;
									#endif
								}
							}
						
					glBegin(GL_TRIANGLES);
					for(int j=2;j>=0;j--)
						glVertex3fv(&(vertices[faces[i].vertices[j].vertex]*(kShadowZoom*gStencilZoom)).x);
					glEnd();			
				}
				else
				{
					glBegin(GL_TRIANGLES);
					for(int j=2;j>=0;j--)
						glVertex3fv(&(vertices[faces[i].vertices[j].vertex]*(kShadowZoom*gStencilZoom)-shadowVector).x);
					glEnd();			
				}
	#ifdef __POLYCOUNT
		gPolyCount+=modelData->faceCount;
	#endif
}


void ModelShadowPassZPass(tFileRef modelRef,tVector3 shadowVector,char *faceSide)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	vertices--;
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	tMaterial *materials=(tMaterial*)(faces+modelData->faceCount);
	materials--;

	if(modelData->modelFlags&kFlagShadowFlag)
	{
		tVector3 sw=0.05*!shadowVector;
		for(int i=0;i<modelData->faceCount;i++)
			if(!((materials+(faces[i].material&kMaterialMask))->m.flags&kMaterialDisableShadow))
			{
				for(int n=0;n<3;n++)
					if(faces[i].vertices[n].neighbor==-1)
					{
						glBegin(GL_TRIANGLE_STRIP);
						glVertex3fv(&(vertices[faces[i].vertices[n].vertex]*gStencilZoom-sw).x);
						glVertex3fv(&(vertices[faces[i].vertices[(n+1)%3].vertex]*gStencilZoom-sw).x);
						glVertex3fv(&(vertices[faces[i].vertices[n].vertex]*gStencilZoom-shadowVector).x);
						glVertex3fv(&(vertices[faces[i].vertices[(n+1)%3].vertex]*gStencilZoom-shadowVector).x);
						glEnd();
						#ifdef __POLYCOUNT
							gPolyCount+=2;
						#endif
					}
			}
	}
	else for(int i=0;i<modelData->faceCount;i++)
		if(!((materials+(faces[i].material&kMaterialMask))->m.flags&kMaterialDisableShadow))
			if(faceSide[i]||(modelData->modelFlags&kFlagShadowFlag))
				for(int n=0;n<3;n++)
					if(faces[i].vertices[n].neighbor!=-1)
						if(faceSide[faces[i].vertices[n].neighbor]!=faceSide[i])
						{
							glBegin(GL_TRIANGLE_STRIP);
							glVertex3fv(&(vertices[faces[i].vertices[n].vertex]*(kShadowZoom*gStencilZoom)).x);
							glVertex3fv(&(vertices[faces[i].vertices[(n+1)%3].vertex]*(kShadowZoom*gStencilZoom)).x);
							glVertex3fv(&(vertices[faces[i].vertices[n].vertex]*(kShadowZoom*gStencilZoom)-shadowVector).x);
							glVertex3fv(&(vertices[faces[i].vertices[(n+1)%3].vertex]*(kShadowZoom*gStencilZoom)-shadowVector).x);
							glEnd();
							#ifdef __POLYCOUNT
								gPolyCount+=2;
							#endif
						}
}

//for each face of a model, calculate whether it faces the light or not.
//return the results in the array faceSide
void CalcFaceSides(tFileRef modelRef,tVector3 lightDir,char **faceSide)
{
	tModelData *modelData=((tModel*)gFileTable[modelRef].parsedData)->data;
	tVector3 *vertices=(tVector3*)&modelData->data;
	tVector3 *normals=vertices+modelData->vertexCount;
	tVector2 *texels=(tVector2*)(normals+modelData->normalCount);
	vertices--;
	tFaceData *faces=(tFaceData*)(texels+modelData->texelCount);
	*faceSide=(char*)MemoryAllocateBlock(sizeof(char)*modelData->faceCount+10);
	
	for(int i=0;i<modelData->faceCount;i++){
		tVector3 *a=vertices+(faces[i].vertices[0].vertex);
		tVector3 *b=vertices+(faces[i].vertices[1].vertex);
		tVector3 *c=vertices+(faces[i].vertices[2].vertex);
		
		//calculate a normal to the face
		tVector3 n=((*b-*a)%(*c-*a));
		
		//use dot product to determine if the face faces the light
		(*faceSide)[i]=n*lightDir>0;
	}	
}

//Draw the shadow of a model
//Uses s stencil buffer-based shadow volume algorithm
void ModelCastShadow(tFileRef modelRef,float shadowLength)
{
	tVector3 lightDir=gEnvironment->lightDir;
	if(gMapEnv)
		if(!VectorZero(gMapEnv->lightDir))
			lightDir=gMapEnv->lightDir;
			
	tMatrix3 tr;
	MatrixTranspose(gTransformDir,tr);
	lightDir=lightDir*tr;
	
	char *faceSide;
	CalcFaceSides(modelRef,lightDir,&faceSide);

	//disable actual drawing
	glDisable(GL_LIGHTING);
	glDisable(GL_FOG);
	glDepthMask(GL_FALSE);
	glColorMask(0, 0, 0, 0);
	
	//enable stencil buffer drawing
	glEnable(GL_STENCIL_TEST);
	glStencilFunc(GL_ALWAYS, 0, 0xffffffff);

	tVector3 lightNormal1=!((gTransformPos-gCameraEntity->pos)%lightDir);
	tVector3 lightNormal2=lightNormal1%lightDir;
	tVector3 shadowVector=lightDir*shadowLength;
	//is the camera possibly in the shadow volume?
	if(-lightNormal2*(gTransformPos-gCameraEntity->pos)<ModelGetMaxExtends(modelRef)+shadowLength)
	{
		//if the camera is possibly inside the shadow volume,
		//we have to use zFail rendering, as normal zPass rendering
		//won't work if the camera is inside the shadow volume.
		glFrontFace(GL_CW);
		glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
		ModelShadowPassZFail(modelRef,shadowVector,faceSide);
		
		glFrontFace(GL_CCW);
		glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
		ModelShadowPassZFail(modelRef,shadowVector,faceSide);
	}
	else
	{//zPass Rendering
		glFrontFace(GL_CCW);
		glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
		ModelShadowPassZPass(modelRef,shadowVector,faceSide);
		
		glFrontFace(GL_CW);
		glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
		ModelShadowPassZPass(modelRef,shadowVector,faceSide);
	}
	MemoryFreeBlock(faceSide);
	glDepthMask(GL_TRUE);
	glColorMask(1, 1, 1, 1);
}

//draw a model
void DrawModel(tFileRef modelRef,int allowTransparency,int textureSet)
{	
	if(!gFileTable[modelRef].parsed)
		ModelLoad(modelRef);
	if(!gFileTable[modelRef].parsed)
		return;
		
#ifdef __USEDISPLAYLISTS
	glCallList(((tModel*)gFileTable[modelRef].parsedData)->ref);
#ifdef __USEVERTEXARRAYS
	ModelDrawArrays(modelRef,allowTransparency?kModeDrawOnlyTransparent:kModeDrawOnlyTransparentSolid,textureSet);
#else
	ModelDraw(modelRef,allowTransparency?kModeDrawOnlyTransparent:kModeDrawOnlyTransparentSolid,textureSet);
#endif
#else
#ifdef __USEVERTEXARRAYS
	if(allowTransparency==kTransparencyGhost)
		ModelAddGhostedArrays(modelRef,textureSet);
	else
		ModelDrawArrays(modelRef,allowTransparency?kModeDrawNormal:kModeDrawEverythingSolid,textureSet);
#else
	if((((tModel*)gFileTable[modelRef].parsedData)->data)->modelFlags&kFlagMultiTextureFlag)
		ModelDrawMultiTextured(modelRef,textureSet);
	else
		ModelDraw(modelRef,allowTransparency?kModeDrawNormal:kModeDrawEverythingSolid,textureSet);
#endif
#endif
#ifdef __POLYCOUNT
	gPolyCount+=((tModelData*)((tModel*)gFileTable[modelRef].parsedData)->data)->faceCount;
#endif
	if(TexturesSelectTextureUnit(1)){
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_TEXTURE_3D);
		glDisable(GL_TEXTURE_GEN_S);
		glDisable(GL_TEXTURE_GEN_T);
		TexturesSelectTextureUnit(0);
	}
}

//draw a model's shadow
void DrawModelShadow(tFileRef modelRef,float shadowLength)
{	
	if(!gFileTable[modelRef].parsed)
		ModelLoad(modelRef);
	if(!gFileTable[modelRef].parsed)
		return;
	if((((tModel*)gFileTable[modelRef].parsedData)->data)->modelFlags&kFlagMultiTextureFlag)
		return;
	if(shadowLength>0)
		ModelCastShadow(modelRef,shadowLength);
}

