15. Carga de modelos y uso de "Vertex arrays" Imprimir
Videojuegos - Curso de Programación de juegos
Escrito por Vicengetorix   
Llegamos por fin, a uno de los temas que mas interesan al ser ya, muy vistoso el resultado; la carga de modelos hechos con un programa de diseño 3D.
El tema no es facil. Tiene la complicacion inicial de conocer el formato del fichero donde esta el modelo. Luego la carga puede ser mas o menos laboriosa. Al final tendremos que tener el modelo cargado de forma que lo podamos pintar lo mas eficientemente posible.
En nuestro caso usaremos el formato de modelos propio del programa Caligari truSpace, cuyos ficheros tienen extension ".COB". Lo haremos en su forma ASCII, los que se graban como fichero de texto y podemos editar con un sencillo editor. Esto pemite saber exactamente lo que hay dentro del fichero y no depender de unas especificaciones parciales y en ingles.
Luego pintaremos el modelo usando el metodo de "vertex arrays".

El primer aviso es para los que no tengan todavia el programa Caligari truSpace:
Si no tienes el programa, bajatelo, pero ya. La ultima version es gratis porque la empresa ha sido comprada por Microsoft y no se sabe que va a ser de ella (que den el programa gatis es un mal presagio).
El programa es totalmente funcional, con posibilidad de cargar modelos de varios formatos habituales, y grabarlos tambien a otros. Permite, ademas de modelar, hacer animaciones por huesos (esqueletales suena mal) y tambien permite convertirlas a animaciones por vertice. Permite triangular los modelos y no se que mas cosas. Un programa en condiciones, vaya, y totalmente gratis.
Ademas el formato .COB en ASCII nos dara la facilidad de entender bien que es cada cosa.
Veremos primero como es este formato para luego ver como lo cargamos.
Lo primero sera abrir un fichero .COB que se haya guardado en truSpace con la pestaña de ASCII en un editor de texto y ver como esta estructurado.

Lo primero en un fichero de estos es algo asi:

1
2
3
4
5
6
7
Caligari V00.01ALH             
BitM V0.01 Id 0 Parent 0 Size 00007756
ThumbNailHdrSize 40
ThumbHeader:28 00 00 00 5e 00 00 00 5e 00 00 00 01 00 18 00 00 00 00 ...
ColorBufSize 26696
ColorBufZipSize 2515
ZippedThumbnail:78 9c ed 99 d9 53 93 59 1a 87 fd 0f c4 6e ad 52 ab 0d ...

Hay que decir que en este tipo de ficheros hay mucha morralla. De esto, pasaremos de todo.
Hay dos lineas que empiezan con ThumbHeader y ZippedThumbnail que yo suelo borrar a mano porque acupan mucho y solo son un pequeño bitmap codificado para que se vea en el truSpace al meterlo en un repositorio (o algo asi).

Luego puede haber la definicion de un grupo, de la que paso tambien. Al final iremos a las zonas que nos interesan.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PolH V0.08 Id 83869972 Parent 83869708 Size 00028088
Name Sphere
center 0.614419 0.20392 1.0758
x axis 0.999921 -0.0125365 0
y axis 0.0125365 0.999921 0
z axis 0 0 1
Transform
0.0345737 0.000204491 0 0.614419
-0.000433467 0.0163104 0 0.20392
0 0 0.0351248 1.0758
0 0 0 1
World Vertices 201
0.260287 0.109467 0.959493
0.199215 0.200480 0.959493
0.107814 0.260971 0.959493
-0.000000 0.281733 0.959493
-0.107815 0.259603 0.959493
-0.199215 0.197951 0.959493

PolH indica una malla de puntos. En nuestro caso, si el modelo esta formado por mas de una las uniremos y tendremos cargado una sola malla con el modelo completo.
Luego estan el nombre y los datos de los ejes para esta malla que no vamos a usar (nunca me ha hecho falta).
Despues tras la linea que pone Transform si que hay algo importante. Es una matriz de 4 x 4 que modifica cada uno de los vertices de esta malla, asi que tendremos que leerla para multiplicar cada vertice por esta matriz y asi obtener la posicion final.
Despues lo mas importante. Pone World Vertices, los vertices de la malla, y luego el numero de vertices de esta malla. Luego la lista de coordenadas de los vertices.

Tras los vertices, las coordenadas de textura.

1
2
3
4
5
6
7
8
9
0.374136 0.905620 0.183717
-0.084495 0.892289 0.415415
0.084495 0.893361 0.415415
Texture Vertices 249
0.062500 0.909091
0.125000 0.909091
0.187500 0.909091
0.250000 0.909091
0.035396 0.909091

Luego las caras de la malla, cada una con los indices de las coordenadas de vertice y de textura de cada vertice de la cara

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0.903127 0.597715
0.906458 0.399723
0.905948 0.568487
Faces 326
Face verts 3 flags 0 mat 0
<97,4> <98,88> <0,0>
Face verts 3 flags 0 mat 0
<1,1> <0,0> <98,141>
Face verts 3 flags 0 mat 0
<1,1> <98,141> <96,89>
Face verts 3 flags 0 mat 0
<2,2> <1,1> <96,139>
Face verts 3 flags 0 mat 0
<2,2> <96,139> <95,90>

Cada cara indica los vertices que tiene. En nuestro caso deberan ser siempre 3, triangulos (no siempre es asi si no tenemos cuidado de triangular el modelo antes de guardarlo). Tambien el indice material con que pintarla (cada malla tiene sus materiales).
El formato de cada vertice es "<indice_de_vertice,indice_de_textura>", 3 por cara.

Cuando termina con las caras hay una serie de lineas que no usaremos. Luego encontraremos uno o mas materiales por cada malla. Un ejemplo de material sin textura.

1
2
3
4
5
6
7
8
9
10
11
12
13
Mat1 V0.07 Id 83764900 Parent 83869972 Size 00000106
mat# 0
shader: phong facet: auto32
rgb 0.258824,0.164706,0.141176
alpha 1 ka 0.1 ks 0.1 exp 0 ior 1
ShBx V0.04 Id 83764901 Parent 83764900 Size 00000607
Shader class: color
Shader name: "plain color" (plain)
Number of parameters: 1
colour: color (66, 42, 36)
Flags: 3
Shader class: transparency
...

La definicion de material es mas larga pero solo usaremos las primeras lineas.
La que pone mat# 0 es que es el material 0 (lo que ponia en cada cara).
La que empieza en rgb es el color del material.
la que empieza por alpha tiene factores que modificaran cada componente del material.
Ya no vamos a usar mas datos si no hay textura. Si la hubiera apareceria asi.

1
2
3
4
5
6
7
8
9
Mat1 V0.06 Id 85040684 Parent 85012492 Size 00000189
mat# 0
shader: phong facet: auto87
rgb 0.784314,0.784314,0.784314
alpha 1 ka 0.3 ks 0.3 exp 0.2 ior 1
texture: 35C:\Ficheros\3dmods\animal\RHINO.jpg
offset 0,0 repeats 1,1 flags 2
ShBx V0.02 Id 85040685 Parent 85040684 Size 00000715
Shader class: color

Vemos que aparece una linea que empieza por texture que no aparecia antes y contiene el path completo del fichero de la textura. Nosotros, en nuestro programa despreciaremos el path y nos quedaremos solo con el nombre de fichero para colocarlo luego donde nos de la gana. Las texturas no son siempre del tamaño que le gusta a OpenGL. Si no lo es, basta con cambiar el tamaño a 128x256, 512x64, ...
Tras esta linea, otra con los datos de offset y de repeticion de la textura. Esto tambien lo usaremos por si la textura fuera repetida y movida en el modelo.

Eso es todo lo que vamos a usar para cargar un modelo. Las listas del vertices, coordenadas de textura y de caras pueden ser muy largas, pero es lo que realmente necesitamos.
El que quiera investigar y usar mas cosas, adelante.

Hay una cosa que no se si echareis de menos, las normales. Otros formatos las incluyen, este no. Tranquilidad, lo que haremos sera generarlas nosotros a partir de los vertices de las caras.

Este es un modelo de ejemplo, el que usaremos en el programa del capitulo, sin texturas: triciclo.cob

y este, otro con textura para probar: rino.cob
y su textura: RHINO.jpg



Tras ver el formato, solo queda cargarlo.

Aviso que, aunque en este capitulo no haya puesto la carga y gestion del modelo en una clase, lo logico es si hacerlo. Ya he recomedado otras veces muy seriamente usar la orientacion a objetos. Aqui no lo haremos por claridad para quien no la conoce. El que no la conozca que se ponga al tema.

Para la carga, tendremos que aceder al fichero al estilo C++ e ir leyendo las lineas y troceandolas en los campos que las componen.
Los datos del modelo seran guardados en bufferes que reservaremos tras una primera pasada para saber cuanto espacio nos hara falta (se da por supuesto lo de convertir los datos de cadena de caracteres a numeros con las funciones atoi() o atof() ).
Tras guardar todos los datos del modelo, tendremos que tratarlos para que esten ordenados y para generar las normales.
Ademas los vertices del fichero no se repiten. aunque el mismo vertice pertenezca a 8 caras a la vez solo tiene una entrada en el fichero. Nosotros lo tendremos que tener guardado las 8 veces repetido para pintarlo luego con la funcion glDrawArrays(...) ( ocupa mas memoria pero es mas rapido, creo), y tener todos los datos de cada vertice ordenados para pintar muuuuuchos de una sola llamada a la funcion. Los tendremos ordenados por material para minimizar las llamadas a la funcion glDrawArrays(...), una por material. Entre cada llamada cambiaremos el material y la textura si la hubiese. 

Tras el resumen, al lio.
Partimos del ultimo codigo del capitulo anterior. Quitaremos la parte de imprimir en pantalla las resoluciones de la tarjeta grafica. Ya no sera util.

Empezamos por las definiciones globales. En este capitulo no estaran todas al principio, estan mezcladas entre funciones auxiliares y alguna clase (¡ooooh! C++). Yo ire por el orden en que aparecen en el codigo, de momento.

Incluimos las cabeceras para usar funciones matematicas y para aceso a ficheros.

1
2
#include <math.h>	// Para funciones matematicas.
#include <fstream> // Para aceso a ficheros.

Incluimos un indicador para poder decir al programa que no pinte.

1
2
3
4
5
6
// Indicador de si el programa esta parado o no.
// Lo usaremos para que no intente pintar cuando
// esta destruyendo la ventana y borrando buferes.
// Tambien se puede usar cuando la ventana no esta
// activa.
bool ProgramaParado = false;

En este programa quitaremos todo el codigo que habiamos usado para imprimir las resoluciones de la tarjeta grafica en pantalla (no lo veo ya muy util).

Vamos a poner la definicion de la funcion CargaTextura porque la vamos a usar antes de codificarla, mas abajo. La cambiaremos un poco para que retorne true si termina bien o false si no puede cargar el fichero, y para aceptar un parametro segun el cual dejara la textura normal o la reflejara respecto al eje X (le dara la vuelta).

1
2
3
// Esta definicion esta aqui porque usaremos esta funcion 
// antes de codificarla, mas abajo.
bool CargaTextura(GLuint *textura, const char *fichero, bool arriba=true);

Ahora, y por primera vez en este curso, tutorial o como se llame esto, vamos a crearnos una clase. Sera una simple clase que nos permitira leer de un fichero de texto linea a linea y que cada vez que lea una linea, la troceara en los 10 primeros campos separados por espacio o tabulador. De esta manera solo tendremos luego, que leer una linea y ver que hay en cada campo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// Clase para leer de disco un fichero .COB
// en ASCII (no en binario)
class fichero
{
private:
std::fstream cob;
public:
// Indicador de si ha terminado
// el fichero (EOF)
bool fin;
// Indicador de error en la ultima
// operacion.
bool fallo;
// Aqui dejaremos la linea al leerla.
char cad[200];
// Aqui dejaremos troceada, la linea,
// en campos separados por espacio o
// tabulador.
char campo[10][200];
// Constructor. El prametro en el fichero
// en disco.
fichero(char *f)
{
fin=false;
fallo=false;
cob.open(f,std::ios::in );
if( cob.fail() ) fallo=true;
}
// Destructor.
~fichero() {cob.close();}
// Lee una linea del fichero en "cad[]"
// y la trocea dejando los trozos en "campo[][]"
void leelinea(int n);
// Regresa al principio del fichero.
void principio()
{
fin=false;
cob.clear();
cob.seekg(0L, std::ios::beg);
}
};
// Lee una linea del fichero en "cad[]"
// y la trocea dejando los trozos en "campo[][]"
void fichero::leelinea(int n=1)
{
int i=0;
int i2=0;
int c=0;
for(int i3=0;i3<n;i3++)
{
cob.clear();
cob.getline(cad,200);
}
while( cad[i] != '\0' && (cad[i] == ' ' || cad[i] == '\t') ) i++;
while( cad[i] != '\0' && c < 10 )
{
if(cad[i]!=' ' && cad[i]!='\t')
{
campo[c][i2]=cad[i];
i2++;
}
else
if(cad[i-1]!=' ' && cad[i-1]!='\t')
{ campo[c][i2]= '\0';
c++;
i2=0;
}
i++;
}
campo[c][i2]='\0';
if(cob.eof()) fin=true;
else fin=false;
} // Fin class fichero.

No tiene muchos comentarios pero ya he dejado claro lo que hace. Seguro que si perdeis 2 minutos en esto mejorais este codigo.

Ahora dos definiciones para que el codigo luego sea mas claro. Son para comparar cadenas de caracteres.

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

Las funciones siguientes seran utiles para los calculos con vectores. Son el producto escalar, producto vectorial y la normalizacion de vectores.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Producto escalar de dos vectores
//(el cos del angulo que forman)
GLfloat ProdEscalar(GLfloat* u, GLfloat* v)
{
return u[0]*v[0] + u[1]*v[1] + u[2]*v[2];
}
 
// Producto vectorial. El resultado en n es un
// vector perpendicular a los dos dados.
void ProdVectorial(GLfloat* u, GLfloat* v, GLfloat* n)
{
n[0] = u[1]*v[2] - u[2]*v[1];
n[1] = u[2]*v[0] - u[0]*v[2];
n[2] = u[0]*v[1] - u[1]*v[0];
}
// Funcion que normaliza el vector de v
// (lo hace de longitud 1).
void Normaliza(GLfloat* v)
{
GLfloat l;
l = (GLfloat)sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
v[0] /= l;
v[1] /= l;
v[2] /= l;
}

Una funcion para simplificar luego, en el codigo desentrañar los indices de caras que vendran en una cadena de caracteres como esta: "<35,206>". Esta funcion retornara el primer valor o el segundo segun el ultimo parametro que acepta.

1
2
3
4
5
6
7
8
9
10
11
12
// 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)
{
char *prt;
char aux[100];
strcpy(aux,cad);
prt=strtok(aux+1,",><");
if(p==1) return atoi(prt);
else return atoi(strtok(NULL,",><") );
}

La estructura que usaremos para guardar los datos de las caras (triangulos).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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;
 
};

Una funcion que usaremos para comparar triangulos y ordenarlos con la funcion de la libreria C qsort(...).

1
2
3
4
5
6
7
8
9
10
11
12
// 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);
}

Estructura para usada para guardar los datos de los materiales.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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;
};

Variables auxiliares para las operaciones con vectores y un array para guardar las matrices que modifican los puntos de una malla.

1
2
3
4
5
6
7
8
 // 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];

Por ultimo antes de cargar el modelo, los punteros que usaremos para reservar memoria para guardar los datos del modelo y las variables para guardar la cantidad definitiva de cada cosa.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Numero de vertices
GLuint NVertices=0;
// Puntero para guardar vertices
GLfloat (*Vertices)[3];
// Puntero para guardar los vertices
// definitivos.
GLfloat (*Puntos)[3];
// Numero de coordenadas de textura.
GLuint NCorTex=0;
// Puntero para guardar coordenadas
// de textura
GLfloat (*CorTex)[2];
// Puntero para guardar coordenadas
// de textura definitivas.
GLfloat (*uvTex)[2];
// Numero de normales.
GLuint NNormales=0;
// Puntero para normales por vertice.
GLfloat (*Normales2)[3];
// Puntero para normales definitivas
// por vertice.
GLfloat (*Normales)[3];
// Numero de normales por cara.
GLuint NFNormales=0;
// Puntero para las normales
// por cara.
GLfloat (*FNormales)[3];
// Numero de caras (triangulos)
// del modelo.
GLuint NCaras=0;
// Puntero para guardar las caras.
Triangulo *Caras;
// Numero de mateiales diferentes
// en este objeto
GLuint NMateriales=0;
// Puntero para guardar los materiales.
Material *Materiales;
 

Por fin llegamos a la funcion para cargar el modelo, CargaModelo(...).

1
2
3
4
5
// Funcion para cargar modelo de fichero .COB (trueSpace) en
// ASCII y triangulado.
//
bool CargaModelo(char *fich)
{

Primero defino unos indices auxiliares, pongo el puntero Puntos a NULL para luego saber si he cargado el modelo y creo un objeto de tipo fichero (la clase que hemos creado) para abrir el fichero .cob. Si no lo encuentra termina la funcion.

1
2
3
4
5
6
7
8
9
10
// 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;

Empiezo recorriendo el fichero y buscando el numero total de vertices, coordenadas de textura, caras (triangulos) y materiales para reservar espacio para todo. Luego voy al principio del fichero para la pasada definitiva.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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();

Antes de empezar a leer, reservo la memoria necesaria, que ya la se.

1
2
3
4
5
6
// 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];

Defino unas variables auxiliares para los datos parciales de cada cosa y para guardar el color de los materiales.

1
2
3
4
// 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];

Ahora, en un bucle while voy leyendo linea a linea hasta fin de fichero.

1
2
3
4
5
6
// Mientras no termine el fichero.
// Recorro definitivamente el fichero.
while(!cob.fin)
{
// Lee una linea.
cob.leelinea();

Si encontramos una nueva malla cargo la matriz para transformar los proximos vertices.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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();
}
}

Luego es de suponer que encontremos vertices. Aqui habra que implementar una multiplicacion de cada verice por la matriz de antes cada vez que leemos uno y antes de guardarlo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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;
}
cob.leelinea();
}

Los vertices, al final, son guardados cambiandolos. No es un error. Si no, aparecen los modelos tumbados.

Despues cargo coordenadas de textura si llego a la linea adecuada.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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();
}

Lo mismo con la caras, si llegamos leyendo el fichero a la palabra "Faces".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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);
}
}

Observad que usamos dos variables auxiliares (V2 y T2) para guardar los contadores parciales de cada cosa. En caso de que solo hubiera una malla no haria falta (de hecho en la primera su valor sera 0). En las siguientes mallas si no estuvieran, se repetirian los indices y al final solo usariamos los primeros vertices. El modelo seria un desastre.

Por ultimo cargo los materiales en caso de encontrar "mat#" al principio de linea.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 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.
// Usare este campo luego para saber si el material
// tiene textura o no.
Materiales[M].textura[0] = '\0';
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,'\\') );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	// 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++;
}

Como veis, nosotros solo usaremos el nombre del fichero sin path para poder poner las texturas donde queramos del disco y cargarlas sin problemas.

Con esto hemos teminado de recorrer el fichero y cargar datos.
Cerramos el bucle while(!cob.fin).

1
2
}
// Ya se han cargado todos los datos.

Ahora entramos en la parte en que ordenamos y preparamos todo con los datos que hemos cargado.

Primero ordenamos las caras (triangulos) por orden de material. Esta ordenacion es la que marcara como quedaran finalmente los datos. Lo hacemos con la funcion de la libreria C para ordenacion rapida. Al principio definimos la funcion comparar a la que se llamara desde la funcion qsort.

1
2
// Ordenamos por material la matriz de caras.
qsort(Caras,NCaras,sizeof(Triangulo),comparar);

Luego cuento el numero de caras de cada material y lo pongo en el campo para eso de cada material. Usaremos este dato para luego pintar los triangulos del mismo material de una soma llamada a glDrawArrays(...).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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;

Llega el calculo de las normales. Primero las normales por cara, que haran que el modelo se pueda ver facetado. Sera la misma normal para cada vertice del triangulo, aunque se guardar por triplicado y ya en orden cuando pintemos el modelo. Primero reservaremos el espacio para guardarlas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 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]) );
}

A partir de las normales facetadas, calculamos las normales suavizadas.
Reservaremos espacio, averiguamos a que caras vertenece cada vertice y luego sumaremos las normales facetadas de esas caras (excepto las que superen cierto angulo) y normalizaremos el vector resultado. Guardaremos el indice de la normal en el campo adecuado de la cara y el nuevo verctor normal en el array reservado para ello.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// 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++;
}
}

Como se explica en los comentarios, si el angulo entre caras es mayor de un valor que pondremos nosotros, el programa no suaviza esta arista. Imaginemos un objeto formado por un cubo y una esfera entrelazados. la parte de la esfera, es deseable que se suavize pero las aristas del cubo no. Este es un caso claro para ver el asunto..

Ya tengo todos los datos necesarios. Solo tengo que ordenar los vertices, coordenadas de textura y las normales suavizadas (las facetadas ya las habiamos dejado bien al crearlas.).
Cada uno de estos datos debe estar en un buffer en el mismo orden en el que estan las caras.
En la primera posicion de buffer de caras los tres vertices de la primera cara, en la primera posicion del buffer de coordenadas de textura, las de los vertices de la primera cara, en la primera posicion del buffer de normales, las normales de los vertices de a primera cara, en la segunda posicion del buffer de vertices, los de la segunda cara, ... y asi hasta terminar las caras, que ya habiamos ordenado por material.

Para esto nos crearemos nuevos bufferes (definitivos) donde dejar los datos de los vertices en este formato y borraremos luego los antiguos.

Reservo espacio para los nuevos bufferes y defino unos punteros auxiliares con los que recorrer los arrays.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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;

Luego en un bucle voy rellenando los bufferes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 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);
}
}

Libero la memoria que no me hace falta, incluida la del buffer de caras.

1
2
3
4
5
6
7
8
9
// 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, tenemos el modelo en memoria como queremos para poderlo pintar.

Solo queda cerrar la funcion.

1
2
3
4
5
// Llegado a este punto ya hemos cargado y preparado todo
// el modelo. Solo queda pintarlo cuando queramos.
return true;
 
} // Fin CargaModelo

Aunque me adelante al codigo, veremos ahora la funcion BorraModelo(), en la que liberaremos la memoria que ocua nuestro modelo antes de abandonar el programa.
Liberaremos primero la memoria de las texturas que hubieramos cargado y luego la de los bufferes de los datos de los vertices.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Funcion para borrar el modelo
void BorraModelo()
{
// 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

Toca pintar el modelo con glVertexArrays(...).



En la ultima parte del capitulo veremos como pintar el modelo cargado desde disco como vertex arrays. Al cargarlo ya lo tuvimos en mente y ahora sera facil hacerlo.
Luego veremos los cambios en el resto del programa, que seran pocos.

En este modo de pintar no usaremos glBegin ni glEnd, simplemente llamaremos a la funcion glVertexArray(...) con los siguientes parametros:

  • El primero, el tipo de primitivas a pintar (como en glBegin). En nuentro caso sera GL_TRIANGLES.
  • El segundo, el primer indice a partir del cual pintar. el 0 el primer vertice.
  • El tercero, el munero de vertices a pintar.

Todo esto esta muy bien pero ¿como sabe OpenGL donde estan los datos?.
Primero le decimos que vamos a usar vertex arrays para cada tipo de dato (podriamos no necesitar, por ejemplo, las coordenadas de textrura). Esto lo haremos con la funcion glEnableClientState(...) con el tipo de dato como parametro (GL_VERTEX_ARRAY, GL_NORMAL_ARRAY y GL_TEXTURE_COORD_ARRAY).
Luego indicamos el puntero donde empiezan los datos con lasfunciones glVertexPointer, glNormalPointer y glTexCoordPointer.

En nuestro programa tenemos los vertices ordenados por material, con lo que pintaremos el modelo de tantas veces como materiales tenga; si tiene un material, de una sola vez.
Nos encontramos que el truSpace nos da los vertices en el sentido de las agujas del reloj, asi que cambiaremos eso con la funcion glFrontFace(GL_CW). Al terminar lo dejaremos como estaba.

Esta es la funcion PintaModelo() el nuestro programa.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// Funcion para pintar el modelo.
// Usaremos el metodo de "Vertex Arrays"
//
void 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);
// Le digo donde estan guardados los vertices,
// que son 3 coordenadas por vertice y que son
// tipo float. El 0 es un offset por si tenemos
// todo en un buffer y entre cada vertice hubiera
// un espacio (no es nuestro caso).
glVertexPointer(3, GL_FLOAT, 0, Puntos);
// Le digo donde guardo las normales y que son
// floats. el 0 es el offset de antes.
glNormalPointer(GL_FLOAT, 0, Normales);
// 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);
// Le indico que las coordenadas de textura
// tambien se pintan con "glDrawArrays".
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
// Le digo donde estan guardadas, cada vertice
// son 2 y son tipo float. El 0 como antes.
glTexCoordPointer(2, GL_FLOAT, 0, uvTex);
// 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 );
// Premaro nv para la proxima pasada.
nv+=((Materiales[i].NCaras)*3);
// Desabilito texturas por si el proximo
// material no tiene.
glDisable(GL_TEXTURE_2D);
}
// 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, mat1_ambiente);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat1_difusa);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat1_especular);
glMaterialf(GL_FRONT, GL_SHININESS, 20);
 
} // Fin PintaModelo
 

Tras esto vemos como cambia el resto del programa.

En la funcion CargaTextura(...) ya vimos como cambiaba la definicion.

1
bool CargaTextura(GLuint *textura, const char *fichero, bool arriba)

Ademas de incluir los return correspondientes, incluimos esto para voltear la textura si hiciera falta.

1
2
3
// Si no es hacia arriba la volteo por el eje X (para las cargadas 
// desde un fichero .COB)
if(!arriba) imagen = corona::FlipImage(imagen, corona::CA_X);

Al final de la funcion IniciaGL() cargamos el fichero.

1
2
// Cargamos el modelo.
CargaModelo("dibus//triciclo.cob");

Tambien podemos añadir mas luces o por lo menos cambiar la que hay para que de mas luz y desde mejor posicion. Eso os lo dejo a vosotros.

y en la funcion ProcesaMensajes(...), en el evento WM_DESTROY (destruccion de la ventana), ponemos a true el indicador de parada de programa.

1
2
// Indicador de que paro el programa.
ProgramaParado=true;

Esto es por que la funcion Pinta() no esta asociada a la ventana y podria ocurrir que intentase pintar la escena y estuviese mientras borrando la ventana, donde nosotros liberamos la memoria reservada. Esto provocaria un error de memoria al salir del programa. Con esto lo evitamos.

Tambien borramos el modelo aqui.

1
2
// Borramos el modelo.
BorraModelo();

En la funcion WinMain(...), simplemente supeditamos la funcion Pinta() al indicador de programa parado.

1
2
3
// Pintamos nuestra escena.
// Si el programa no esta parado.
if(!ProgramaParado) Pinta();

Hemos dejado para el final la funcion Pinta(), como siempre.

Recordad que aqui hemos quitado el codigo para pintar en pantalla las resoluciones de la tarjeta grafica.
El unico cambio aqui sera, en este capitulo, mover mas atras los cubos para dejar espacio al modelo y cambiar el primer cubo por el modelo cargado.

Tambien pongo mas atras la camara, cambiando un poco esta linea de codigo.

1
2
// Me alejo o acerco de la escena.
glTranslatef(0,0,-3-dist);

En lugar del primer cubo pinto el modelo.

1
2
3
// Pinto el objeto que sea.
//glCallList(Cubo);
PintaModelo();

Tambien al estar seleccionado y pintarlo en malla.

1
2
3
4
// Pinto el objeto de nuevo pero esta
// vez se vera en "wireframe"
//glCallList(Cubo);
PintaModelo();

Por ultimo pinto los otros cubos un poco mas lejos para que no agobien.

1
2
// Pinto otro cubo un poco mas lejos
glTranslatef(0,0,-5.0f);

Si se quiere ver mejor la escena, se puede ajustar los valores de nuestra unica luz o poner otras en la funcion IniciaGL() como ya dije antes.

Al final el resultado debe ser algo asi:



o asi con con un modelo con textura pero muy pocos triangulos:



Y el codigo completo este: usw15.cpp


Tras el esfuerzo, y siendo verano, me tomare unas cortas vacaciones, que ya cuento los FPS's cuando veo la tele y sueño en C++.




¡Sólo los usuarios registrados pueden escribir comentarios!
+/- Comentarios
Buscar
cristiaan3003  - pregunta   |190.193.108.xxx |12-11-2009 07:24:44
hola queria saber como hago para triangular el modelo antes de guardarlo al
archivo .cob en el truespace, ya estube mirando el programa pero no encuentro la
opcion.
cristiaan3003@gmail.com, argentina , santa fe
Vicengetorix   |SAdministrator |12-11-2009 19:52:41
Esta respondido en el tema del foro que has puesto tambien.
lavz24  - Duda   |159.90.10.xxx |09-04-2010 15:59:20
Hola queria saber que otros formatos, soporta el opengl, por ejemplo puedo
cargar un .obj? o un .3ds?
Vicengetorix   |194.179.126.xxx |09-04-2010 19:40:07
El OpenGL no soporta ningun formato.
Es el programador el que debe trabajarse el
codigo para leer los vertices y pasarselos a OpenGL.
En este capitulo se da un
ejemplo con ficheros .COB de como hacer esto pero conociendo el formato de .OBJ
o .3DS o cualquier otro se puede hacer lo mismo.

Pregunta mejor en el foro, me
es mas comodo.

3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved."