Jerarquia de objetos en OpenGL

Una vez conocida la estructura y comandos básicos de OpenGl y Glut, es hora de adentrarnos más y aplicar algunas nuevas ideas. Intentaremos crear un modelo que simule el movimiento de piernas de una persona andando.
Previamente necesitamos haber aprendido como se realizan las transformaciones de modelado y la proyección que serán usadas en la creación de este modelo. Si aun no has leído estos temas te recomendamos lo hagas antes de empezar.

Para explicar la construcción del citado modelo con movimiento de una manera sencilla, en lugar de crearlo completamente, vamos a construir solo una parte que corresponde a la parte baja del tronco (que llamaremos base), las piernas y los pies. Luego animaremos este modelo incompleto. Crearemos con este propósito una función 'caminar'. Cuando este ejemplo este terminado, comprendido y suficientemente trabajado podríamos seguir desarrollando el modelo y hasta hacer un cuerpo completo.
Comenzaremos esta sección describiendo la estructura y los conceptos para crear modelos "jerárquicos".


Conceptos basicos de jerarquía

Imaginemos que queremos construir un coche para realizar una cierta simulación. El coche estará compuesto por el cuerpo, cuatro ruedas con sus respectivos 5 tornillos, y seis ventanas (ventana delantera, ventana trasera y dos ventanas más a cada lado del coche que serán simétricas (ver figura 1). Para este ejemplo, utilizaremos solo el lado derecho del coche (visible en la figura). Por lo tanto usaremos el cuerpo, dos ruedas con sus diez tornillos y dos ventanas laterales. Para no tener que repetir código sería deseable construir el coche de una forma "jerárquica", significa esto que cuando los cinco tornillos estén correctamente colocados en la rueda, estos deberían moverse en concordancia con la misma sin ningún tipo de ayuda exterior. También seria deseable que cuando el cuerpo del coche se mueva, el resto de las partes sigan este movimiento y no permanezcan estáticas. Para realizar esta construcción "jerárquica" comenzaremos por poner el cuerpo de nuestro coche como el elemento principal y a partir de él colocaremos las ventanas y las ruedas con los tornillos enlazados a ellas. Con esta parte jerárquica lo que conseguimos es que cuando el cuerpo del coche se está moviendo, las ventanas y las ruedas se mueven con él de forma coordinada. Y además cuando las ruedas giran los tornillos lo hagan con ella. OpenGl nos permite realizar la construcción "jerárquica" de modelos con las funciones glPushMatrix y glPopMatrix. Si asumimos que con estas funciones podemos crear las partes separadas del coche (tornillos, ruedas, ventanas y cuerpo) entonces la figura 2 nos demuestra la necesidad de la "jerarquía" y el ejemplo 1 el pseudo-código para la realización del mismo.



Ejemplo 1. Pseudo-código que demuestra la "jerarquía" del coche

functión dibujar_coche{
glPushMatrix
dibujar_cuerpo_coche
glPushMatrix
ir_posición_ventana_delantera
dibujar_ventana_delantera
ir_posición_ventana_trasera
invertir_ejes
dibujar_ventana_trasera
invertir_ejes
ir_posición_rueda_delantera
dibujar_rueda_y_tornillos
ir_posición_rueda_trasera
dibujar_rueda_y_tornillos
glPopMatrix
glPopMatrix
}
functión dibujar_rueda_y_tornillos{
glPushMatrix
dibuja_rueda
glPushMatrix
for counter = 1 up to 5 do
{
ir_posición_tornillo
dibujar_tornillo
}
glPopMatrix
glPopMatrix
}






En el ejemplo 1 la función 'ir_' se usa para trasladarnos al punto donde debemos dibujar. La función 'invertir_ejes' se usa para invertir el eje x y utilizar el función 'dibujar_ventana' para dibujar la ventana trasera a partir del dibujo de la ventana delantera. Si ahora ultimamos la función glTranslate justo antes de la función 'dibujar_coche', podremos ver como se mueve todo el coche incluidas las ventanas y las ruedas. Del mismo modo que las ruedas se mueven con el coche conseguiremos que los tornillos se muevan en la misma relación con las ruedas. Podemos utilizar los comandos glPushMatrix y glPopMatrix para guardar las distintas posiciones en la escena. Por ejemplo si tenemos que el cuerpo del coche mide 100 unidades y sabemos que las coordenadas del sistema están en el centro del coche entonces también sabemos que el coche está dibujado entre las coordenadas 50 y -50. Con estas funciones sabemos pues que tenemos que posicionar las ruedas y los tornillos 10 unidades después que los límites del coche.

Esto se puede hacer de dos formas:

  - Sin usar la pila de matriz, con glTranslate(40,0,0) podemos mover el centro de nuestro sistema de coordenadas 40 unidades en el eje x, después podemos dibujar la rueda usando la función 'dibujar_rueda_y_tornillos'. Y después podemos mover el centro del sistema de coordenadas ochenta unidades hacia atrás para situarnos en la posición de la segunda rueda usando la función glTranslate(-80,0,0).


  - Si usamos la pila de matriz y los comandos citados (glPushMatrix y glPopMatrix) podemos hacer lo mismo de una manera más eficaz. Primero llamaremos a la función glPushMatrix para guardar la matriz actual y seguidamente llamaremos a glTranslate(40,0,0) y a 'dibujar_rueda_y_tornillos' con lo que tendremos dibujada la primera rueda en la posición correcta. Después llamando a glPopMatrix recuperaremos la matriz inicial. Luego llamaremos a glTranslate(-40,0,0) y a 'dibujar_rueda_y_tornillos' con lo que ya tendremos las dos ruedas en su posición correcta.

En el ejemplo podremos ver como pulsando las teclas 'o' y 'p' hacemos que el coche se mueva, girando las ruedas y los tornillos que hay dentro.
Codigo fuente del ejemplo: coche.c



Creando un esqueleto "andante"

Ahora que ya hemos adquirido ciertos conocimiento del modelo "jerárquico" y de la pila de matriz, ya podemos comenzar el diseño del modelo de las piernas. El dibujo 3 nos muestra las distintas partes del primer modelo básico y su relación de "jerarquía". Como vemos en ese dibujo, en la parte de arriba de la jerarquía se encuentra la base (parte inferior del tronco), seguido por la articulación de la parte superior de las piernas, luego la parte superior de las piernas, la articulación de la parte inferior de las piernas, articulación del pie y luego el pie. Las articulaciones están hechas con esferas y el resto de las partes con rectángulos.
Con esta jerarquia si aplicamos una rotación a la base, todas las partes rotarán también. Sin embargo si la rotación se la aplicamos a la articulación de la parte baja de la pierna, solo la articulación y los elementos por debajo de ella se verán afectados por citada rotación. (Véase figura 4).

A llegado el momento de empezar a construir el código para dibujar y posteriormente animar el modelo. El proyecto llegados a este punto lo dividiremos en tres ficheros. Podemos llamar principal.c al programa principal. Otro fichero se puede llamar modelo.c que contendrá las funciones que serán usadas para posicionar, dibujar y animar el modelo. El fichero modelo.h contendrá las definiciones de las funciones de modelo.c. Para dibujar el modelo básico, hay que tener en cuenta ciertos valores para aproximar el movimiento del modelo al movimiento del cuerpo humano. Como el objetivo de este ejemplo no es la exactitud sino una demostración de como se crea un modelo jerárquico, las alturas relativas y anchuras de las partes de cuerpo se basarán en un esbozo echo a mano de un hombre.
El ejemplo 2 muestra parte del contenido del fichero modelo.h, donde se pueden encontrar las medidas relativas del cuerpo.



Ejemplo 2. Definición del tamaño de las partes del modelo

#define TAMANHO_ARTICULACION_PIE TAMANHO_ARTICULACION_CABEZA
#define LARGO_PIE TAMANHO_ARTICULACION_PIE * 2.0
#define ANCHO_PIE ANCHO_BRAZO_LO
#define PIE ANCHURA_PIE * 2.0
#define ALTURA_BRAZO_SUPERIOR ALTURA_TORSO * 0.625
#define ANCHO_BRAZO_SUPERIOR ANCHURA_TORSO/4.0
#define TAMANHO_ARTICULACION_BRAZO_SUPERIOR TAMANHO_ARTICULACION_CABEZA * 2.0
#define ALTURA_BRAZO_INFERIOR ALTURA_TORSO * 0.5
#define ANCHO_BRAZO_INFERIOR ANCHO_BRAZO_SUPERIOR
#define TAMANHO_ARTICULACION_BRAZO_INFERIOR TAMANHO_ARTICULACION_BRAZO_SUPERIOR * 0.75
#define ALTURA_MANO ALTURA_BRAZO_INFERIOR / 2.0
#define ANCHO_MANO ANCHO_BRAZO_INFERIOR
#define MANO ANCHO_BRAZO_INFERIOR / 2.0
#define ANCHO_TORSO ALTURA_TORSO * 0.75
#define ALTURA_TORSO 0.8
#define TORSO ANCHO_TORSO / 3.0
#define ANCHO_CABEZA ALTO_CABEZA * 0.93
#define ALTO_CABEZA ALTURA_TORSO * 0.375
#define TAMANHO_ARTICULACION_CABEZA ALTO_CABEZA/6
#define ANCHO_BASE ANCHO_TORSO
#define ALTURA_BASE ALTURA_TORSO / 4.0
#define ALTURA_PIERNA_SUPERIOR ALTURA_BRAZO_INFERIOR
#define TAMANHO_ARTICULACION_PIERNA_SUPERIOR TAMANHO_ARTICULACION_BRAZO_SUPERIOR
#define ANCHO_PIERNA_SUPERIOR TAMANHO_ARTICULACION_PIERNA_SUPERIOR * 2.0
#define ALTURA_PIERNA_INFERIOR ALTURA_PIERNA_SUPERIOR
#define ANCHO_PIERNA_INFERIOR ANCHO_PIERNA_SUPERIOR
#define TAMANHO_ARTICULACION_PIERNA_INFERIOR TAMANHO_ARTICULACION_BRAZO_SUPERIOR
#define ALTURA_PIERNA ALTURA_SUPERIOR_BRAZO + ALTURA_BRAZO_INFERIOR + ALTURA_PIE + 2* (TAMANHO_ARTICULACION_PIE+ TAMANHO_ARTICULACION_BRAZO_SUPERIOR+ TAMANHO_ARTICULACION_BRAZO_INFERIOR)




Como vemos en el ejemplo solo se define el largo del torso y el resto de los elementos del cuerpo son relativos a este largo, por ejemplo el ancho del torso es tres cuartas partes del largo del torso... Esto está hecho teniendo en mente que podemos necesitar variar las medidas de nuestro modelo, con lo que solo tendremos que modificar la medida del largo del torso. En este ejemplo no vamos a necesitar todas las medidas aquí descritas pero en el caso de construir el cuerpo completo estas serían todas las medidas a utilizar. Ahora que ya tenemos definido el tamaño de las partes del modelo, es el momento de empezar a construirlo. Dividiremos el modelo en este punto en tres partes, la base (parte baja del torso) y las dos piernas.
El ejemplo 3 muestra el código que crea la base.



Ejemplo 3. Función que dibuja la base del modelo básico

void Dibuja_Base(int frame){
glPushMatrix() ;
glScalef(LARGO_BASE, ANCHO_BASE, TORSO) ;
glColor3f(0.0f,1.0f,1.0f) ;
if (frame == WIRE)
glutWireCube(1.0f) ;
else
glutSolidCube(1.0f) ;
glPopMatrix() ;
}




La función 'Dibuja_Base' acepta un argumento. Este argumento se usará para dibujar una base sólida (pasándole el valor SOLID) o de regilla (pasándole el valor WIRE) aunque luego utilizaremos la misma función, cuando utilizando luz, construyamos una base sólida. El cuerpo de la función comienza llamando a la función glPushMatrix para guardar la matriz actual y "después" realizar algunas modificaciones sobre la misma. Luego le sigue una llamada a la función glScale con el valor ANCHO_BASE,LARGO_BASE y TORSO. El resultado de esta llamada es el escalado de los ejes con estos nuevos valores (de izquierda a derecha, los ejes x, y, z). Luego se llama a la función glutWireCube(1.0f) o glutSolidCube(1.0f) y el resultado no es un cubo sino un rectángulo como se muestra el los dibujos 3 y 4. Esta función es muy sencilla de comprender ya que en ella no hay jerarquía de objetos ni rotaciones. La función 'Dibujar_pierna' es un poco mas complicada. Contiene tres partes, la parte superior de la pierna, la parte inferior de la pierna y el pie. Estas tres partes se construyen como tres funciones diferentes.
Son todas similares a la que vamos a exponer en el ejemplo 4 que dibuja la parte superior de la pierna y que se llama ´Dibujar_Pierna_Superior'.

Ejemplo 4. Función que dibuja la parte superior de la pierna del modelo básico

void Dibujar_Pierna_Superior(int frame){
glPushMatrix();
glScalef(TAMANHO_ARTICULACION_BRAZO_SUPERIOR, TAMANHO_ARTICULACION_BRAZO_SUPERIOR, TAMANHO_ARTICULACION_BRAZO_SUPERIOR) ;
glColor3f(0.0f,1.0f,0.0f) ;
if (frame == WIRE)
glutWireSphere(1.0f,8,8) ;
else
glutSolidSphere(1.0f,8,8) ;
glPopMatrix() ;
glTranslatef(0.0f,- ALTURA_SUPERIOR_BRAZO * 0.75f, 0.0f) ;
glPushMatrix() ;
glScalef(ANHCHO_BRAZO_SUPERIOR,ALTURA_SUPERIOR_BRAZO,ANHCHO_BRAZO_SUPERIOR) ;
glColor3f(0.0f,0.0f,1.0f) ;
if (frame == WIRE) glutWireCube(1.0f) ;
else
glutSolidCube(1.0f) ;
glPopMatrix() ;
}




En el cuerpo de esta función, la rutina glPushMatrix se usa para guardar la matriz actual antes de escalarla. Después de guardarla se usa la función glScale para escalar los ejes con las dimensiones apropiadas para dibujar la articulación de la parte superior de la pierna. Luego de hacer esto, se pone el color a verde y se dibuja la articulación (nuevamente sólida o de rejilla dependiendo del valor pasado a la función) y luego recargamos la matriz llamando a la función glPopMatrix. Con esto lo que conseguimos es poner los ejes con su relación inicial uno-a-uno. Luego llamamos a la función glTranslate para mover el centro de los ejes a la posición requerida para dibujar la parte superior de la pierna. Nuevamente volvemos a guardar la matriz actual, escalar los ejes, escoger el color(azul) y finalmente pintar la parte superior de la pierna. En este momento la función Dibujar_Pierna_Superior (la función que dibuja la parte superior de la pierna y su articulación) está lista. Las funciones Dibujar_Pierna_Inferior y Dibujar_Pie son similares a esta por lo que no las explicaremos.

Ahora es el momento de echar una mirada a la función Dibuja_Pierna. Esta función combina las anteriores funciones que construyen la piernas con sus respectivas funciones de rotación, que serán necesarias para animar el modelo.
El ejemplo 5 contiene el código para esta función.



Ejemplo 5. La función que crea la pierna

void Dibuja_Pierna(int side, int frame){
glPushMatrix() ;
glRotatef(angulos_caminado[side][3],1.0f,0.0f,0.0f) ;
Dibujar_Pierna_Superior(frame) ;
glTranslatef(0.0,- ALTURA_SUPERIOR_BRAZO * 0.75f,0.0f) ;
glRotatef(angulos_caminado[side][4],1.0f,0.0f,0.0f) ;
Dibujar_Pierna_Inferior(frame) ;
glTranslatef(0.0f,- ALTURA_BRAZO_INFERIOR * 0.625f, 0.0f) ;
glRotatef(angulos_caminado[side][5],1.0f,0.0f,0.0f) ;
Dibujar_pie(frame) ;
glPopMatrix() ;
}



Como las otras, la función comienza guardando la matriz actual. Luego se llama a la función glRotate. Los valores pasados a esta rutina muestran que el objeto que se dibuja luego de llamar a la función, solo puede ser rotado en el eje x ( el segundo parámetro es 1.0f y el tercero y el cuarto son 0). El primer parámetro son los grados de rotación en el eje x. Este valor estará contenido en el array 'angulos_caminado'. Este es un array de dos dimensiones que tendrá un tamaño entre dos y seis, que será declarado en principal.c (y lo podremos usar aquí declarándolo como #extern) y contiene todos los ángulos para la animación del caminado. Este array puede mostrarnos seis ángulos de rotación (parte superior del brazo, parte inferior del brazo, parte superior de la pierna, parte inferior de la pierna y el pie) para todos los movimientos (izquierda y derecha de brazos y piernas).

Siguiendo con la explicación, luego se llama a la función 'Dibuja_pierna_superior' para dibujar la parte superior de la pierna. Después movemos el centro de los ejes a la una nueva posición llamando a la función glTranslate y el resto de la función continua de una forma parecida a lo que acabamos de explicar (rotar ejes, dibujar otra parte y mover el centro de los ejes a una nueva posición). Cuando la pierna está dibujada (incluyendo parte superior, parte inferior y pie) usamos la función glPopMatrix para recargar la matriz inicial.

Ahora que estas dos funciones están listas, una que dibuja la parte inferior del tronco y otra las piernas, debemos volver atrás y pensar que es lo que necesitamos para continuar la construcción de nuestro modelo.
El ejemplo 6 muestra el código necesario para finalizar la construcción de nuestro modelo básico.



Ejemplo 6. Función que crea el modelo básico

void Dibujar_Base_Piernas(void){
glPushMatrix() ;
glTranslatef(0.0,movimiento_base,0.0) ;
Dibuja_Base(WIRE) ;
glTranslatef(0.0,-(ALTURA_BASE),0.0) ;
glPushMatrix() ;
glTranslatef(ANCHO_TORSO * 0.33,0.0,0.0) ;
Dibuja_Pierna(LEFT, WIRE) ;
glPopMatrix() ;
glTranslatef(-ANCHO_TORSO * 0.33,0.0,0.0) ;
Dibuja_Pierna(RIGHT,WIRE) ;
glPopMatrix() ;
}




Como se muestra en el ejemplo 6, justo después de guardar la matriz actual, llamando a la función glPushMatrix, llamamos a la función glTranslate pasándole como uno de los parámetros 'movimiento_base'. Esta particular llamada la explicaremos en un momento. Seguidamente, dibujamos la base llamando a la función Dibujar_Base. Luego movemos abajo el centro de los ejes para dibujar las piernas. Guardamos la matriz y luego movemos los ejes a la derecha y dibujamos la pierna derecha. Finalmente llamamos a la función glPopMatrix para recargar la matriz inicial. Si compilamos estos ficheros y los ejecutamos, el resultado será algo como lo mostrado en la figura 5. En el ejemplo 6 la primera llamada a la función glTranslate la hemos dejado sin explicación. Lo principal de la explicación está en explicar el parámetro 'movimiento_base'. Este parámetro contiene el desplazamiento vertical del cuerpo, durante la animación de caminado del cuerpo humano. Cuando una persona camina, el torso no permanece a la misma altura sino que se sube y se baja según el ángulo de inclinación de las piernas (figura 6). El ejemplo 7 contiene la función que calcula el desplazamiento vertical.



Ejemplo 7. La función que calcula el desplazamiento vertical del cuerpo

double mostrar_movimiento_base(double lángulo_superior, double lángulo_inferior, double rángulo_superior, double rángulo_inferior){
double resultado1, resultado2, primer_resultado, segundo_resultado, radianes_superior, radianes_inferior ;
radianes_superior = (PI*lángulo_superior)/180.0 ;
radianes_inferior = (PI*lángulo_inferior-lángulo_superior)/180.0 ;
resultado1 = (ALTURA_SUPERIOR_BRAZO + 2*TAMANHO_ARTICULACION_BRAZO_SUPERIOR) * cos(radians_up) ;
resultado2 = (ALTURA_BRAZO_INFERIOR + 2 * (TAMANHO_ARTICULACION_BRAZO_INFERIOR + FOOT_JOINT_SIZE) + FOOT_HEIGHT) * cos(radians_lo) ;
primer_resultado = LEG_HEIGHT - (resultado1 + resultado2) ;
radianes_superior = (PI*rángulo_superior)/180.0 ;
radianes_inferior = (PI*rángulo_inferior-rángulo_superior)/180.0 ;
resultado1 = (ALTURA_SUPERIOR_BRAZO + 2*TAMANHO_ARTICULACION_BRAZO_SUPERIOR) * cos(radianes_superiror) ;
resultado2 = (ALTURA_BRAZO_INFERIOR + 2 * (TAMANHO_ARTICULACION_BRAZO_INFERIOR + FOOT_JOINT_SIZE) + FOOT_HEIGHT) * cos(radians_lo) ;
segundo_resultado = LEG_HEIGHT - (resultado1 + resultado2) ;
if (primer_resultado <= segundo_resultado)
return (- primer_resultado) ;
else
return (- segundo_resultado) ;
}




Como se puede ver en la figura 7, el desplazamiento vertical VD puede ser calculado restando los valores Vertical_pierna_superior y vertical_pierna_inferior con la longitud de la pierna, LL.

VD=LL-(vertical_pierna_superior+vertical_pierna_inferior) (1)


En este punto el desplazamiento vertical del pie no es tenido en cuenta.

Siguiendo con la función mostrar_movimiento_base, los ángulos son primero convertidos de grados a radianes ( Como la función de librería 'cos()' que se usa para encontrar el coseno de los ángulos y necesita que los ángulos estén en radianes). Luego usamos la función que definimos anteriormente (1) para calcular el desplazamiento vertical. En esta función, VD es representado como resultado_final, superior_pierna_vertical como resultado1 y inferior_pierna_vertical como resultado2. Para calcular resultado1 y resultado2 se usan las siguientes funciones (mirar figura 7):

resultado1=x*cos(r) (2)
resultado2=x*cos(f) (3)


Calculamos el desplazamiento vertical para ambas piernas y después miramos cual de las piernas es la que está tocando el suelo (la que tenga el desplazamiento vertical menor que la otra); este es el valor devuelto por la función. En este punto tenemos una función que dibuja el modelo básico; también tenemos una función que calcula el desplazamiento vertical del modelo. La función final que debemos construir es la que de vida a nuestro modelo, una función que anime el modelo y que lo haga caminar. En esta primera sección del capítulo, los ángulos de la animación pueden ser un duro trabajo, ya que el programa no los lee de un fichero sino que los calcula en el cuerpo de la función de animación. Una vez realizado el ejemplo completo, podemos calcular estos valores y hacer que el programa los lea de un fichero. Esta función se basará en la técnica de frame clave. Está técnica primero identifica el numero de los frames claves. Estos frames son aquellos donde ocurre algo importante para la animación. En estos frames claves todos los ángulos del cuerpo serán proporcionados al programa. Esto significa que el programador los calculará explícitamente y pasará los ángulos a la función. Luego la función podrá usar estos frames clave para calcular los ángulos de todas las partes del cuerpo para todos los frames de la animación. Esto será realizado tomando el ángulo entre dos frames clave y dividiéndolo entre el número de frames que hay en el medio. Por ejemplo, si queremos mover veinte grados la parte inferior de la pierna entre dos frames clave y lo queremos hacer en veinte frames, la función que calcula la parte inferior de la pierna la tiene que mover un grado en cada frame (veinte grados / veinte frames = un grado por frame), para realizar el citado movimiento.

La función de animación está basada en el libro de Tony Wight "Moving Pictures". En este libro la animación de caminado cíclico nos viene dada con ocho frames clave. Los primeros cuatro frames clave se usan para animar la primera parte de un paso y los otros cuatro para animar la segunda parte. Esta segunda parte de la animación es igual que la primera pero invertida. En la primera mitad de la animación la pierna que estaba delante al principio termina detrás y la que estaba detrás termina delante. En la segunda mitad, como dijimos, ocurre justo lo contrario para terminar el ciclo de un paso. A continuación volvemos a la primera mitad y así continuamente. La función que realiza la animación para el modelo que estamos construyendo, está basada en el desarrollo encontrado en este libro. Por eso su estructura sigue la estructura que describimos en le párrafo anterior. Los ángulos usados para los cálculos están desarrollados en el libro.
El ejemplo 8 contiene el código para esta función.



Ejemplo 8.Parte de la función de animación para caminar

Void animar_base(void){
static frames = FRAMES,zoom_fl = 0,flag = 1 ;
float l_upleg_dif ,r_upleg_dif ,l_upleg_add ,r_upleg_add ,l_loleg_dif ,r_loleg_dif ,l_loleg_add ,r_loleg_add ;
switch (flag){
case 1 :
l_upleg_dif = 15 ;
r_upleg_dif = 5 ;
l_loleg_dif = 15 ;
r_loleg_dif = 5 ;
l_upleg_add = l_upleg_dif / FRAMES ;
r_upleg_add = r_upleg_dif / FRAMES ;
l_loleg_add = l_loleg_dif / FRAMES ;
r_loleg_add = r_loleg_dif / FRAMES ;
angulos_caminado[0][3] += r_upleg_add ;
angulos_caminado[1][3] += l_upleg_add ;
angulos_caminado[0][4] += r_loleg_add ;
angulos_caminado[1][4] += l_loleg_add ;
langle_count -= l_upleg_add ;
langle_count2 -= l_loleg_add ;
rangle_count -= r_upleg_add ;
rangle_count2 -= r_loleg_add ;
movimiento_base = mostrar_movimiento_base ( langle_count, langle_count2, rangle_count, rangle_count2 ) ;
frames-- ;
if (frames == 0){
flag = 2 ;
frames = FRAMES ;
}
break ;
case 2 :
………………………………… repeat until case 8 then go to case 1………………
if (zoom_flag){
switch (zoom_fl){
case 0 :
zoom += 0.05 ;
if (zoom > 2.5) zoom_fl = 1 ;
break ;
case 1 :
zoom -= 0.05 ;
if (zoom < -2.5) zoom_fl = 0 ;
break ;
default :
break ;
}
}
if (rotate_flag){
rotate = (rotate + 1) % 360 ;
}
glutPostRedisplay() ;
}




Al principio de la función declaramos una serie de variables. Las variables frames, zoom_fl y flag son declaradas como static porque solo las tenemos que inicializar una vez(la primera vez que la función las llama). Luego de esta declaración tenemos una estructura swith. Este es el esqueleto de la función, y todas las operaciones necesarias para realizar la animación están incluidas en ella. Esta estructura switch depende de la variable flag, que está inicializada a 1. Esto significa que la primera parte de la estructura se ejecutará hasta que el valor de flag sea distinto de 1 (en este caso puede cambiar por ejemplo a 2). Dentro de esta parte de la estructura, se inicializan las variables l_upleg_dif, l_loleg_dif, r_up_leg_dif y r_loleg_dif. Sus valores son la diferencia entre los ángulos de las piernas izquierda y derecha superior entre los dos primeros frames clave. Después de esto se calculan la variables l_upleg_add, l_loleg_add, r_upleg_add y r_loleg_add dividiendo la diferencia inicial en el ángulo (entre los dos frames clave) por el numero de frames que son necesarios para hacer la animación. Estos pueden ser los valores de la rotación para un frame de la animación. El numero de frames necesarios entre dos frames clave es constante y se define es este caso como veinte en el fichero modelo.h. El siguiente paso es copiar los valores anteriormente calculados en las posiciones adecuadas en array angulos_caminado. Este array definido externamente fue descrito anteriormente en la función 'dibujar_base'. Los valores de las variables no son copiados directamente sino que son sumados a los valores previamente almacenados en el array, con lo que contendrá los ángulos para el siguiente frame. Se hace de esta manera porque la rotación no es incremental; por ejemplo la función glRotate(20,1.0,0,0) se usa para rotar la parte superior de la pierna veinte grados y en el siguiente paso esta parte superior tiene que ser rotada cinco grados más, la forma correcta de llamar a la función glRotate es glRotate(25, 1.0, 0.0, 0.0) y no glRotate(5, 1.0, 0.0, 0.0.). OpenGL sigue este esquema no incremental para eliminar los error acumulativos que pueden aparecer en el modelo incremental. Luego de hacer esto, estos valores son sustraídos de las variables langle_count, langle_count2, rangle_count y rangle_count2. Estas cuatro variables son inicializadas externamente, en el fichero principal.c y contienen el valor inicial de los ángulos de las partes del cuerpo. Estas variables se usarán en la función 'movimiento_base' para calcular el desplazamiento vertical de cuerpo. Haciendo esto, los valores de las variables r(*angle_count) y q(*angle_count2) de las piernas izquierda y derecha serán recobrados(volver a ver figura 7). Luego de calcular los nuevos ángulos con la técnica explicada en le párrafo anterior, los valores de estas variables se pasan a la función 'movimiento_base', para calcular el desplazamiento del cuerpo. Este es el final del primer ciclo (la transición del primer frame clave al segundo). La variable frame se decrementa y se mira si es igual a 0. Si es 0, esto significa que el segundo frame clave se ha alcanzado y que el valor de la variable flag debe se incrementada (con el fin de pasar al siguiente caso en el switch, el segundo ciclo). La variable frames también se reinicializa a FRAMES (constante numérica predefinida con el numero de frames entre dos frames clave). Esto continua hasta el último caso, el ocho, después la variable flag se pone a 1 para volver a comenzar el ciclo de caminado. Luego de la estructura switch hay más código en el ejemplo 8. Este código sirve para calcular el valor de las variables zoom y rotate. Estas variables son usadas externamente, en el programa principal para modificar el zoom (en el eje z) y rotar el modelo (en el eje y).

Ahora que todas las funciones principales están listas, solo falta una para terminar el programa. Esta es la función 'teclado' que es necesaria para interactuar entre el usuario y el programa. Esta función por ser sencilla no la vamos a introducir aunque en la tabla 1 tenemos las teclas que se usarán en el programa y sus operaciones.

En este punto el programa está casi finalizado, y solo quedan una serie de operaciones por hacer en el programa principal para terminar por completo el programa. En el programa principal todas las variables externas descritas anteriormente deben declararse. Cuando se haga esto, las variables langle_count, langle_count2, rangle_count y rangle_count2 se inicializarán a los valores 30,0,-30 y 0. Estos, como dijimos antes, son los valores iniciales de los ángulos izquierdo, derecho, superior e inferior de la pierna. Las variables zoom_flag y rotate_flag son inicializadas a GL_FALSE (al principio no daremos ningún zoom y realizaremos ninguna rotación) y las variables rotate y zoom se inicializaran a 0 (por la misma causa).Sin embargo algo nuevo aparece en la función 'init'.
El ejemplo 9 contiene el código de esta función.



Ejemplo 9. La función init que imprime la informacion general sobre la versión de OpenGL

void init(void){
const GLubyte* informacion ;
glClearColor(1.0, 1.0, 1.0, 0.0) ;
glShadeModel(GL_FLAT) ;
informacion = glGetString(GL_VENDOR) ;
printf("VENDOR : %s\n", informacion) ;
informacion = glGetString(GL_RENDERER) ;
printf("RENDERER : %s\n", informacion) ;
informacion = glGetString(GL_EXTENSIONS) ;
printf("EXTENSIONS : %s\n", informacion) ;
informacion = glGetString(GL_VERSION) ;
printf("VERSION : %s\n", informacion) ;
angulos_caminado[0][3] = langle_count ;
angulos_caminado[1][3] = rangle_count ;
angulos_caminado[0][4] = langle_count2 ;
angulos_caminado[1][4] = rangle_count2 ;
movimiento_base = find_base_move( langle_count, langle_count2, rangle_count, rangle_count) ;
}



En este ejemplo la variable informacion (de tipo puntero a Glubyte) se usa con la función de OpenGL glGetString para adquirir y después imprimir la informacion general sobre la versión de OpenGL, extensiones soportadas. En esta función, el array que se usa para guardar los ángulos también se inicializa. Se le da el valor inicial del desplazamiento vertical del modelo llamando a la función 'movimiento_base' pasándole los valores iniciales de los ángulos de las piernas. También se usa una función de callback. La función glutSpecialFunc que es similar a glutKeyboardFunc pero se usa para registrar una función de callback que permite utilizar las teclas que no generan código ASCII, como pueden ser las techas direccionales y las techas de función (F1 a F12). Después de registrar la función especial con la llamada glutSpecialFunc en la función principal el usuario puede usar las teclas direccionales arriba y abajo para modificar el zoom del modelo y las de izquierda y derecha para rotar el modelo en el eje y. Las llamadas a las funciones glTranslatef(0,0,zoom) y glRotatef(rotate,0,1,0) justo antes de dibujar el modelo en la función 'display' para modificar el zoom y la rotación. Después de compilar y ejecutar el programa el usuario podrá ver el modelo básico.
La figura 9 contiene alguno frames de como debería salir el modelo.
Alguna de las teclas que podemos utilizar para interactuar con el modelo son las siguientes:





Codigo fuente de este ejemplo: ejemplo2.tar



2003  Yago Castiñeira Lema (insycl00), Yago Corbal Ramón (inggcr01)