//-----------------------------------------------------// 
//
//  Codigo del curso de programacion de videojuegos                        
// 
// 	Clase para carga y manejo de modelos .COB de texto
//   (Caligari truSpace)
//
//  UnSitioWeb.com para programar juegos 
//  (cc) Reconocimiento-NoComercial-SinObrasDerivadas   
//                                                
//  Codigo probado en:
//   VC++ 2005 Express con SDK Microsoft instalado  
//
//-----------------------------------------------------//

// Aclaramos las comparaciones de cadenas.
#define Distintas(a,b) 0!=_stricmp(a,b)
#define Iguales(a,b) 0==_stricmp(a,b)

// Estructura de un triangulo (Cara) del modelo
struct Triangulo
{
	// Un indice de la posicion en el array
	// por cada vertice...
	GLuint vertices[3];
	// ...por cada coordenada de textura..
	GLuint texcor[3];
	// ...por cadanormal...
	GLuint normales[3];
	// ...indice de la normal por caras(facetado)
	GLuint normal;
	// Indice del material con que se pinta.
	GLuint material;

};

// Estructura de un material
struct Material
{
	// Fichero de la textura
	char textura[100];
	// Identificativo de la textura
	GLuint Tex;
	// Offset colocar la textura.
	GLfloat tOffset[2];
	// Repeticion de la textura
	GLfloat tRepite[2];
	// Componente difusa
	GLfloat difusa[4];  
	// Componente ambiente
	GLfloat ambiente[4];  
	// componente especular
	GLfloat especular[4];  
	// Componente emisiva
	GLfloat emisiva[4];  
	// Brillo
	GLfloat brillo;
	// Numero de caras (triangulos) con este
	// material en la malla de triangulos
	GLuint NCaras;
};

// Estructura para los datos de los 
// bounding volumes.
struct _bound {
	GLfloat maxX;
	GLfloat minX;
	GLfloat maxY;
	GLfloat minY;
	GLfloat maxZ;
	GLfloat minZ;
	GLfloat radio;

}; 

//--------------------------------------------------------------
// Funcion para comparar triangulos que se pasa
// como parametro a qsort(...), para ordenar los 
// triangulos por material.
int comparar(const void *a, const void *b)
{
	if( (*(Triangulo *)a).material < (*(Triangulo *)b).material )
		return(-1);
	else if( (*(Triangulo *)a).material > (*(Triangulo *)b).material)
		return(1);
	else 
		return(0);
}

//---------------------------------------------------------------------------
// Clase modeloCOB que encapsula la carga y 
// manejo de modelos truSpace de texto.
class modeloCOB
{
private:
	// Variables auxiliares para
	// guardar vectores, puntos, vertices o
	// lo que se nos antoje de tres coordenadas
	float v[3],v2[3];
	// Array para guardar la matriz que leeremos 
	// del fichero y deberemos aplicar a los vertices
	// de cada malla de las que se componga el modelo.
	float trans[4][4];
	// Numero de vertices
	GLuint	NVertices;
	// Puntero para guardar vertices
	GLfloat	(*Vertices)[3];
	// Puntero para guardar los vertices
	// definitivos.
	GLfloat (*Puntos)[3];
	// Numero de coordenadas de textura.
	GLuint	NCorTex;
	// Puntero para guardar coordenadas 
	// de textura
	GLfloat	(*CorTex)[2];
	// Puntero para guardar coordenadas 
	// de textura definitivas.
	GLfloat (*uvTex)[2];
	// Numero de normales.
	GLuint	NNormales;
	// Puntero para normales por vertice. 
	GLfloat	(*Normales2)[3];
	// Puntero para normales definitivas
	// por vertice.
	GLfloat	(*Normales)[3];
	// Numero de normales por cara.
	GLuint	NFNormales;
	// Puntero para las normales 
	// por cara.
	GLfloat	(*FNormales)[3];
	// Numero de caras (triangulos)
	// del modelo.
	GLuint	NCaras;
	// Puntero para guardar las caras. 
	Triangulo *Caras;
	// Numero de mateiales diferentes 
	// en este objeto
	GLuint	NMateriales;
	// Puntero para guardar los materiales.
	Material *Materiales;

	// Definimos tres variables para guardar el identificativo
	// de los VBOs que vamos a usar.
	GLuint vboVertices;
	GLuint vboNormales;
	// Funcion para separar datos de tipo "<5,45>"
	// de un fichero .cob. p=1 retorna el primer parametro,
	// p=2 el segundo.
	int IndiceCara(char *cad,int p=1);
	// Funcion para borrar el modelo
	void BorraModelo();
	
public:
	// Funcion para cargar modelo de fichero .COB (trueSpace) en
	// ASCII y triangulado.
	bool CargaModelo(char *fich);
	// Dibuja el modelo.
	void PintaModelo();
	// Constructor
	modeloCOB();
	// Constrructor con carga
	modeloCOB(char *fich);
	// Destructor
	~modeloCOB();
	// He dejado este VBO en la parte publica
	// para usarlo desde fuera de la clase en caso
	// de querer aplicar otra textura mas al modelo
	// con estas coordenadas.
	GLuint vboTextura;
	// Este estructura almacenara los datos 
	// de los "bounding volumes" del modelo
	_bound bound;
	
};
//--------------------------------------------------------------------
modeloCOB::modeloCOB()
{
	NVertices=0;
	NCorTex=0;
	NNormales=0;
	NFNormales=0;
	NCaras=0;
	NMateriales=0;
	
}
//--------------------------------------------------------------------
modeloCOB::modeloCOB(char *fich)
{
	NVertices=0;
	NCorTex=0;
	NNormales=0;
	NFNormales=0;
	NCaras=0;
	NMateriales=0;
	CargaModelo(fich);
}
//---------------------------------------------------------------------
modeloCOB::~modeloCOB()
{
	if(NVertices!=0) BorraModelo();
}
//---------------------------------------------------------------------
// Funcion para separar datos de tipo "<5,45>"
// de un fichero .cob. p=1 retorna el primer parametro,
// p=2 el segundo.
int modeloCOB::IndiceCara(char *cad,int p)
{
	char *prt;
	char aux[100];
	strcpy(aux,cad);
	prt=strtok(aux+1,",><");
	if(p==1) return atoi(prt);
	else return atoi(strtok(NULL,",><") );
}

//-----------------------------------------------------------------------
// Funcion para cargar modelo de fichero .COB (trueSpace) en
// ASCII y triangulado.
bool modeloCOB::CargaModelo(char *fich)
{
// Variable temporal para el calculo
// del radio. 
GLfloat r_temp;

// Variables para indices.
int i,i2;
// Puntero Puntos es NULL. Asi podre comprobar 
// si he cargado el modelo.
Puntos=NULL;
// Creo un objeto del tipo (clase)
// fichero, de nombre cob.
fichero cob(fich);
// Si falla ¿para que seguir?
if( cob.fallo ) return false;

// Recorro lo primero el fichero para contar los 
// vertices, coordenadas de textura, 
// caras (triangulos) y materiales, y asi
// luego reservar el espacio en memoria 
// necesario.
//int mallas=0;
// Mientras no termine el fichero.
while(!cob.fin)
{	cob.leelinea();
	//if(Iguales(cob.campo[0],"PolH")) mallas++;
	if(Iguales(cob.campo[0],"World")) NVertices+=atoi(cob.campo[2]);
	if(Iguales(cob.campo[0],"Texture")) NCorTex+=atoi(cob.campo[2]);
	if(Iguales(cob.campo[0],"Faces")) NCaras+=atoi(cob.campo[1]);
	if(Iguales(cob.campo[0],"mat#")) NMateriales++;
}  
// Una vez visto, me pongo al principio para
// la siguiente pasada.
cob.principio();
// Reservo memoria para lo que necesito 
// por ahora.
Vertices=new GLfloat[NVertices][3];
CorTex=new GLfloat[NCorTex][2];
Caras=new Triangulo[NCaras];
Materiales=new Material[NMateriales];



// Recorro el fichero cagando datos de cada cosa.

// Variables para valores parciales de cada cosa.
int V=0,V2=0,T=0,T2=0,F=0,M=0;
// Variable auxiliar para el color.
float rgb[3];

// Mientras no termine el fichero.
// Recorro definitivamente el fichero.
while(!cob.fin)
{
	// Lee una linea.
	cob.leelinea();
	
	// Si encuentra la palabra "PolH" al principio
	// de la linea es que hay una nueva malla.
	if(Iguales(cob.campo[0],"PolH") )
	{
		// Lee 7 lineas.
		cob.leelinea(7);
		// Cargamos la matriz de transformacion
		// de los vertices para esta malla.
		for(i=0;i<4;i++) 
		{			
			for(i2=0;i2<4;i2++) trans[i][i2]=atof(cob.campo[i2]);
			cob.leelinea();
		}
	}
	// Si encontramos en primer lugar de la linea
	// la palabra "World", hay que cargar vertices.
	if(Iguales(cob.campo[0],"World") )
	{
		// Pongo indices parciales.
		V2=i=V;
		// Sumo el numero de vertices de esta malla al
		// de vertices que llevamos.
		V+=atoi(cob.campo[2]);
		// Del ultimo vertice de la vez anterior hasta
		// la suma total.
		for(;i<V;i++)
		{
			// Voy leyendo lineas y aplicando la matriz que
			// cargamos antes a cada vertice (multiplicacion 
			// de matrices)
			cob.leelinea();
			v[0]=atof(cob.campo[0]); v[1]=atof(cob.campo[1]); v[2]=atof(cob.campo[2]); 
			// Multiplico
			v2[0]=(trans[0][0]*v[0])+(trans[0][1]*v[1])+(trans[0][2]*v[2])+trans[0][3];
			v2[1]=(trans[1][0]*v[0])+(trans[1][1]*v[1])+(trans[1][2]*v[2])+trans[1][3];
			v2[2]=(trans[2][0]*v[0])+(trans[2][1]*v[1])+(trans[2][2]*v[2])+trans[2][3];
			
			// Pongo el vertice convertido en el array.
			// Aqui, al cargar los vertices, podria modificarlos
			// para aumentar el modelo o disminuirlo con solo
			// multiplicar o dividir las coordenadas..
			Vertices[i][0] = v2[1]; //*2; //*0.5;
			Vertices[i][1] = v2[2]; //*2; //*0.5;
			Vertices[i][2] = v2[0]; //*2; //*0.5;
		
			// Calculamos la bounding box y bounding
			// sphere.
			// Primero, si es el primer vertice que
			// cargamos (i==0), cargamos el valor
			// como punto inicial.
			if(i==0) { 
				// Para la bounding box.
				bound.maxX = v2[1];
				bound.maxY = v2[2];
				bound.maxZ = v2[0];
				bound.minX = v2[1];
				bound.minY = v2[2];
				bound.minZ = v2[0];
				// Para la bounding sphere
				// la distancia del vertice al
				// centro de coordenadas.
				bound.radio=(GLfloat)sqrt(v2[1]*v2[1] + v2[2]*v2[2] + v2[0]*v2[0]);
			}
			// En los siguientes vertices
			// nos quedamos con las coordenadas 
			// maximas y minimas para cada eje
			// para la bounding box.
			else {
				if( v2[1] > bound.maxX ) bound.maxX=v2[1];
				if( v2[2] > bound.maxY ) bound.maxY=v2[2];
				if( v2[0] > bound.maxZ ) bound.maxZ=v2[0];
				if( v2[1] < bound.minX ) bound.minX=v2[1];
				if( v2[2] < bound.minY ) bound.minY=v2[2];
				if( v2[0] < bound.minZ ) bound.minZ=v2[0];  }
			
			// Para la bounding sphere nos quedamos con 
			// la mayor distancia de un vertice a la coordenada
			// 0,0,0.
			r_temp=(GLfloat)sqrt(v2[1]*v2[1] + v2[2]*v2[2] + v2[0]*v2[0]);
			if(bound.radio < r_temp) bound.radio = r_temp;
		
		}
		cob.leelinea();
	}
	// Lo mismo que el apartado anterior, pero
	// con las coordenadas de textura.
	// Aqui no hay que convertir.
	if(Iguales(cob.campo[0],"Texture") )
	{
		T2=i=T;
		T+=atoi(cob.campo[2]);
		for(;i<T;i++)
		{
			cob.leelinea();
			CorTex[i][0] = atof(cob.campo[0]);
			CorTex[i][1] = atof(cob.campo[1]);
		}
		cob.leelinea();
	}
	
	// Tres cuartos de lo mismo con los datos de
	// cada cara(triangulo). Usamos la funcion 
	// "IndiceCara(...)" para los datos de tipo:
	// "<34,270>".
	if(Iguales(cob.campo[0],"Faces") )
	{
		i=F;
		F+=atoi(cob.campo[1]);
		for(;i<F;i++)
		{
			cob.leelinea();
			Caras[i].material = M+atoi(cob.campo[6]);
			cob.leelinea();			
			Caras[i].vertices[0] = V2+IndiceCara(cob.campo[0]);
			Caras[i].vertices[1] = V2+IndiceCara(cob.campo[1]);
			Caras[i].vertices[2] = V2+IndiceCara(cob.campo[2]);	
			Caras[i].texcor[0] = T2+IndiceCara(cob.campo[0],2);
			Caras[i].texcor[1] = T2+IndiceCara(cob.campo[1],2);
			Caras[i].texcor[2] = T2+IndiceCara(cob.campo[2],2);
		}
	}
	
	// Si llegamos a una linea del fichero que empieza por 
	// "mat#" cargamos uno de los materiales de esta malla. 
	if(Iguales(cob.campo[0],"mat#") )
	{
		cob.leelinea(2);
		// Pimero leo el color.
		rgb[0]=atof( strtok(cob.campo[1],",") );
		rgb[1]=atof( strtok(NULL,",") );
		rgb[2]=atof( strtok(NULL,",") );
		
		// Pongo el color como componente difusa y 
		// ambiente. Especular lo voy a poner siempre
		// a 1.0
		for(i=0;i<3;i++) {
					Materiales[M].difusa[i]=rgb[i];
					Materiales[M].ambiente[i]=rgb[i];
					Materiales[M].especular[i]=1.0;
					}		
		Materiales[M].difusa[3]=1.0;
		Materiales[M].ambiente[3]=1.0;
		Materiales[M].especular[3]=1.0;
		cob.leelinea();
		// Multiplico ambiente y especular por sus
		// respectivos factores
		for(i=0;i<3;i++) Materiales[M].ambiente[i]*=atof(cob.campo[3]);
		for(i=0;i<3;i++) Materiales[M].especular[i]*=atof(cob.campo[5]);
		// Emisiva a 0
		for(i=0;i<3;i++) Materiales[M].emisiva[i]=0.0;
		Materiales[M].emisiva[3]=1.0; 
		// Cargo el brillo
		Materiales[M].brillo=100*atof(cob.campo[7]);
		
		// Pongo en textura una cadena vacia por defecto.
		// Luego lo usare para saber si el material
		// tiene textura o no.
		Materiales[M].textura[0] = '\0';
	
		// Pongo valores por defecto de offset y repite
		Materiales[M].tOffset[0]=0;
		Materiales[M].tOffset[1]=0;
		Materiales[M].tRepite[0]=1;
		Materiales[M].tRepite[1]=1;
		
		cob.leelinea();
		// Si esta linea empieza con "texture" es que 
		// este meterial tiene textura que cargar
		if(Iguales(cob.campo[0],"texture:") )
		{
			// Pongo primero el directorio con las texturas
			strcpy(Materiales[M].textura,"dibus\\");
			// Luego le añado el nombre de fichero sin path.
			strcat( Materiales[M].textura, strrchr(cob.cad,'\\') );
			// Cargo ahora la textura.
			// Si falla algo lo dejo como si no tuviera.
			if(!CargaTextura(&(Materiales[M].Tex), Materiales[M].textura, false))
				Materiales[M].textura[0] = '\0';
			cob.leelinea();
			// Cargo offset y repeticion de la textura.
			Materiales[M].tOffset[0]=atof( strtok(cob.campo[1],",") );
			Materiales[M].tOffset[1]=atof( strtok(NULL,",") );
			Materiales[M].tRepite[0]=atof( strtok(cob.campo[3],",") );
			Materiales[M].tRepite[1]=atof( strtok(NULL,",") );
		}
		// Aumento el indice pacial de texturas.
		M++;
	}
}
// Ya se han cargado todos los datos.

// Ordenamos por material la matriz de caras.
qsort(Caras,NCaras,sizeof(Triangulo),comparar);

// En cada material pongo en numero de caras 
// a que se aplica.
// Recorriendo el array de caras ya ordenado
// y voy contando.
M=Caras[0].material;
i2=0;
for(i=0;i<NCaras;i++)
{	
	if(	Caras[i].material == M ) i2++;
	else
	{
		Materiales[M].NCaras = i2;		
		i2=1; M++;
	}
}
Materiales[M].NCaras = i2;

// Calculamos ahora las normales facetadas,
// todos los vertices de una cara la misma
// normal.

// Variables auxiliares.
GLfloat v1[3];
GLfloat v2[3];
GLfloat r[3];

// Pongo el total y reservo espacio.
NFNormales = NCaras*3;
FNormales = new GLfloat[NFNormales][3];
  
// Recorro las caras ...
for (i = 0; i < NCaras; i++) 
{	//...Calculando las normales.
    Caras[i].normal = i*3;

	// Pongo en v1 y v2 dos vectores que pertenecen
	// a esta cara.
    v1[0] = Vertices[Caras[i].vertices[1]][0] -
        Vertices[Caras[i].vertices[0]][0];
    v1[1] = Vertices[Caras[i].vertices[1]][1] -
        Vertices[Caras[i].vertices[0]][1];
    v1[2] = Vertices[Caras[i].vertices[1]][2] -
        Vertices[Caras[i].vertices[0]][2];
    
    v2[0] = Vertices[Caras[i].vertices[2]][0] -
        Vertices[Caras[i].vertices[0]][0];
    v2[1] = Vertices[Caras[i].vertices[2]][1] -
        Vertices[Caras[i].vertices[0]][1];
    v2[2] = Vertices[Caras[i].vertices[2]][2] -
        Vertices[Caras[i].vertices[0]][2];
    // Calculo el producto vectorial
	ProdVectorial(v2, v1, r);
	// y lo normalizo
	Normaliza(r);
	// Cargo, ahora, el array con 3 veces la misma
	// normal, una vez por vertice.
	memcpy(FNormales[i*3],r,sizeof(GLfloat[3]) );
	memcpy(FNormales[(i*3)+1],r,sizeof(GLfloat[3]) );
	memcpy(FNormales[(i*3)+2],r,sizeof(GLfloat[3]) );      
}

// Calculamos las normales suavizadas a partir de las 
// facetadas de las caras a las que pertenece un vertice.
// Este angulo es el minimo para suavizar.
// Si el angulo de dos caras adyacentes es mayor, 
// no lo suavizara. 
// Se puede jugar con este dato segun el modelo.
GLfloat angulo = 50;
// Variables auxiliares.
GLuint  j,k,fr,indnor=0,nc=0;
GLuint	c[400];
GLfloat PEsc, cos_angulo;
GLfloat av[3],u[3],n[3];
// Calculo el coseno en bas al aggulo	
cos_angulo = cos(angulo * 3.14159265f / 180.0);
// Reservo espacio y pongo el total.
// El espacio es temporal, luego habra que
// pasarlo a otro ordenado por cara.
NNormales=NCaras*3;
Normales2 = new GLfloat[NNormales][3];
// Indice con el que voy guardando en el array.	
indnor=0;
// Tantas vueltas como vertices.
for(i=0;i < NVertices;i++)
{
	// Busco caras adyacentes al vertice y se almacenan los indices en c[nc]
	nc=0;
	for(j=0;j < NCaras;j++)
	{
		if(Caras[j].vertices[0]==i || Caras[j].vertices[1]==i 
			|| Caras[j].vertices[2]==i )
		{
			c[nc++]=j;
		}
	}
	// Genenero la normal al vertice recoriendo c[] de las caras adyacentes
	// y sumando las normales de cada cara a las que pertenece (si el
	// angulo no es mayor al puesto antes)
	for(j=0;j<nc;j++)
	{
		n[0]=FNormales[Caras[c[j]].normal][0];
		n[1]=FNormales[Caras[c[j]].normal][1];
		n[2]=FNormales[Caras[c[j]].normal][2];
		av[0]=n[0]; av[1]=n[1]; av[2]=n[2];
		for(k=0;k<nc;k++)
		{
			if(k!=j)
			{	// El Producto Escalar es el cos del angulo 
				// entre vectores normalizados.
				PEsc=ProdEscalar(n,FNormales[Caras[c[k]].normal]);
				// Si no excede el angulo.
				if(PEsc>cos_angulo)
				{
					av[0]+=FNormales[Caras[c[k]].normal][0];
					av[1]+=FNormales[Caras[c[k]].normal][1];
					av[2]+=FNormales[Caras[c[k]].normal][2];
				}
			}
		}
		// Normlizo el resultado.
		Normaliza(av);
		// Pongo los indices de las nuevas
		// normales en la cara.
		if(Caras[c[j]].vertices[0]==i)
			Caras[c[j]].normales[0]=indnor;
		if(Caras[c[j]].vertices[1]==i)
			Caras[c[j]].normales[1]=indnor;
		if(Caras[c[j]].vertices[2]==i)
			Caras[c[j]].normales[2]=indnor;
	
		// Y guardo la normal en el array
		Normales2[indnor][0]=av[0];
		Normales2[indnor][1]=av[1];
		Normales2[indnor][2]=av[2];
		indnor++;
	}
}
// Reservo espacio para los bufferes
// definitivos de vertices (puntos), 
// de coordenadas de textura (uv) y
// normales suavizadas (las de cara ya estan 
// ordenadas.
// Esto se hace por la forma de trabajar de 
// "glDrawArrays(...)". 

// Reservo espacio y declaro variables auxiliares.
GLfloat *xv, *xuv, *xnor, ou, ov, ru, rv;
Puntos=new GLfloat[NCaras*3][3];
uvTex=new GLfloat[NCaras*3][2];
Normales=new GLfloat[NCaras*3][3];
// Los punteros auxiliares iran recorriendo los
// arrays (espacios reservados).
xv=(GLfloat*)Puntos;
xuv=(GLfloat*)uvTex;
xnor=(GLfloat*)Normales;
// Porque no sea como el primer material
M=10000;
// Recorro los triangulos.
for(i2=0;i2<NCaras;i2++)
{	// Si hay cambio de material cargo
	// los nuevos valores a aplicar a 
	// las coordenadas de textura.
	if(M!=Caras[i2].material) 
	{
		M=Caras[i2].material;
		//Repite y offset de texturas
		ou=Materiales[Caras[i2].material].tOffset[0];
		ov=Materiales[Caras[i2].material].tOffset[1];
		ru=Materiales[Caras[i2].material].tRepite[0];
		rv=Materiales[Caras[i2].material].tRepite[1];
	}
	// Cada cara 3 vertices (¡que son triangulos siempre!).
	// Voy cargando por orden los arrays de cada cosa.
	for(i=0;i<3;i++)
	{	// Vertices.
		*xv++=Vertices[Caras[i2].vertices[i]][0];
		*xv++=Vertices[Caras[i2].vertices[i]][1];
		*xv++=Vertices[Caras[i2].vertices[i]][2];
		// Normales suavizadas.
		*xnor++=Normales2[Caras[i2].normales[i]][0];
		*xnor++=Normales2[Caras[i2].normales[i]][1];
		*xnor++=Normales2[Caras[i2].normales[i]][2];
		// Coordenadas de textura. Las sumo el offset
		// y las multiplico por el repite.
		*xuv++=ou+(CorTex[Caras[i2].texcor[i]][0]*ru);
		*xuv++=ov+(CorTex[Caras[i2].texcor[i]][1]*rv);
	}
}

// Vamos a crear los 3 VBOs y los vamos a cargar con los datos
// que antes teniamos simplemente en un array.
// A partir de aqui los datos estaran en mamoria de la tarjeta de video 
// o memoria AGP, o donde el driver OpenGL de nuestra tarjeta crea que 
// va a rendir mejor

// Generamos 3 VBOs para los vertices, normales y coordenadas
// de textura
glGenBuffersARB(1, &vboVertices);
glGenBuffersARB(1, &vboNormales);
glGenBuffersARB(1, &vboTextura);
// Indico a OpenGL que voy a hacer algo con el VBO que usare
// para los vertices, hago bind del VBO.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboVertices);
// Paso al VBO los datos de vertices, indico que los datos pasados son 
// coordenadas de vertices, normales o textura (no indices), el tamaño del 
// buffer, el puntero donde estan los datos a traspasar y por ultimo la 
// frecuencia con que se van a modificar los datos del VBO (en nuestro caso nunca) 
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat)*9*NCaras, Puntos, GL_STATIC_DRAW_ARB);
// Hago bind del VBO para normales.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboNormales);
// Indico el tamaño y le paso los datos de las normales
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat)*9*NCaras, Normales, GL_STATIC_DRAW_ARB);
// Hago bind del VBO para coordenadas de textura.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboTextura);
// Le paso los datos de coordenadas de textura.
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat)*6*NCaras, uvTex, GL_STATIC_DRAW_ARB);



// Borro el primer array de normales suavizadas que 
// no me sirve ya.
if(Normales2!=NULL) delete[] Normales2;
// Lo mismo con los primeros de vertices 
// y coordenadas de textura.
if(Vertices!=NULL) delete[] Vertices;
if(CorTex!=NULL) delete[] CorTex;
// Las caras tampoco hacen falta ya.
if(Caras!=NULL) delete[] Caras; Caras=NULL;
// Llegado a este punto ya hemos cargado y preparado todo
// el modelo. Solo queda pintarlo cuando queramos.

return true;

} // Fin CargaModelo

//---------------------------------------------------------------------
// Funcion para pintar el modelo.
// Usaremos el metodo de "Vertex Arrays"
// 
void modeloCOB::PintaModelo()
{
	// Si no hay array de puntos no hay nada que pintar.
	if(Puntos==NULL) return;
	// Los vertices los carga con las caras 
	// delanteras en el sentido se las agujas del reloj.
	// Decimos a OpenGL que cambie su forma de trabajar
	// y se acomode a esto.
	glFrontFace(GL_CW);
	// Varables indice.
	GLint i, nv=0;
	
	// Indico a OpenGL que vamos a pintar con
	// "vertex arrays" los vertices...
	glEnableClientState(GL_VERTEX_ARRAY);
	// ...y las normales.
	glEnableClientState(GL_NORMAL_ARRAY);

	// Hago bind del VBO para vertices, indico a 
	// OpenGL que lo use a partir de ahora. 
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboVertices);

	// Como uso un VBO, le paso 0 en vez de un puntero.
	// Eso quiere decir que use el VBO, que ya tiene los
	// datos de vertices.
	glVertexPointer(3, GL_FLOAT, sizeof(GLfloat)*3, 0); 

	// Hago bind del VBO para normales
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboNormales);

	// Le paso 0 en vez de un puntero para indicar que
	// use los datos de normales del VBO que esta activo y
	// donde ya estan los datos de las normales.
	glNormalPointer(GL_FLOAT, sizeof(GLfloat)*3, 0); 
	
	// Indico con cual textura voy a trabajar.
	// Usaremos la 0 como textura propia del modelo.
	// el "Client" indica que trabajamos con Vertex Arrays
	// para las coordenadas de textura.
	glActiveTexture(GL_TEXTURE0);
	glClientActiveTexture(GL_TEXTURE0);
	
	// Por defecto desabilito texturas.
	glDisable(GL_TEXTURE_2D);
	// Pinto una vez por cada material del modelo.
	// Para eso habiamos ordenado las caras por material.
	// Un modelo con 3 materiales se pintara de tres 
	// llamadas a "glDrawArrays(...)"
	for(i=0;i<NMateriales;i++)
	{
		// Pongo el material.
		glMaterialfv(GL_FRONT, GL_AMBIENT, Materiales[i].ambiente);
		glMaterialfv(GL_FRONT, GL_DIFFUSE, Materiales[i].difusa);
		glMaterialfv(GL_FRONT, GL_SPECULAR, Materiales[i].especular);
		glMaterialf(GL_FRONT, GL_SHININESS, Materiales[i].brillo);
		// Si tiene textura la pongo ahora.
		if(Materiales[i].textura[0]!= '\0' )
		{	
			// Si tiene textura habilito texturas
			glEnable(GL_TEXTURE_2D);

			// tambien se pintan con "glDrawArrays".
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);

			// Hago bind del VBO que tiene las coordenadas 
			// de textura
			glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboTextura);

			// Le indico que las coordenadas de textura estan 
			// en el VBO activo (el ultimo con el que hicimos bind)
			// pasando 0 o NULL en vez de un puntero.
			glTexCoordPointer(2, GL_FLOAT,sizeof(GLfloat)*2, 0);
			
			// Pongo la textura del material como activa.
			glBindTexture(GL_TEXTURE_2D, Materiales[i].Tex);
	
		}


		
			
		

		// Y pinto con triangulos, desde el indice que marca 
		// el segundo parametro (nv), el numero de verices que
		// marca el tercer parametro (Materiales[i].NCaras*3).
		// Los indices son para todos los arrays que uses
		// (vertices, normales y cord. de textura si hace falta),
		// Por eso los ordenamos y cargamos de forma paralela,
		// si no se corresponden los indices en todos los 
		// bufferes se pinta mal.
		glDrawArrays(GL_TRIANGLES, nv, Materiales[i].NCaras*3 );
		// Preparo nv para la proxima pasada.
		nv+=((Materiales[i].NCaras)*3);
		
		// Desabilito texturas por si el proximo
		// material no tiene.
		glDisable(GL_TEXTURE_2D);
	}

	// Al hacer bind de un VBO pasando 0 como 
	// parametro en vez de el id de un VBO le
	// estoy diciendo que deje de usar VBOs, 
	// que use vertex array normales.
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);


	// Antes de terminar de pintar el modelo 
	// dejo todo lo mas ordenado que pueda para 
	// lo siguiente que venga.
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glFrontFace(GL_CCW);
	
	// Dejo tambien el material base puesto
	glMaterialfv(GL_FRONT, GL_AMBIENT, base_ambiente);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, base_difusa);
	glMaterialfv(GL_FRONT, GL_SPECULAR, base_especular);
	glMaterialf(GL_FRONT, GL_SHININESS, 20);

} // Fin PintaModelo

//-------------------------------------------------------------------------
// Funcion para borrar el modelo
void modeloCOB::BorraModelo()
{
// Como siempre, borro lo que creo para no dejar basura
// (memoria asignada sin usar)
glDeleteBuffersARB(1, &vboVertices);
glDeleteBuffersARB(1, &vboNormales);
glDeleteBuffersARB(1, &vboTextura);


// Borramos las texturas que se hayan cargado,
// recorriendo los materiales.
for(int i=0;i<NMateriales;i++)
{	// Si tiene textura la borro.
	if(Materiales[i].textura[0]!= '\0')
		glDeleteTextures( 1, &Materiales[i].Tex );
}
// Libero la memoria que reserve.
if(Puntos!=NULL) delete[] Puntos;
// Lo hago NULL por si mientras borro
// el programa intenta pintar.
Puntos=NULL;
if(uvTex!=NULL) delete[] uvTex; 
if(Caras!=NULL) delete[] Caras;
if(Materiales!=NULL) delete[] Materiales;	
if(FNormales!=NULL) delete[] FNormales;	
if(Normales!=NULL) delete[] Normales;	
} // Fin BorraModelo

















