Vous êtes sur la page 1sur 54

Manual de OpenGL

Juan Manuel Huescar Juan Carlos Serra Antonio Fco. Bennasar

Informatica Gráfica II

1999/2000

GESTIÓN DE VENTANAS..................................................................................................................3

WINMAIN()............................................................................................................................................3

WNDPROC()............................................................................................................................................9

EJEMPLO...............................................................................................................................................10

RENDERIZAR OPENGL EN UNA VENTANA WIN32......................................................................................12 GESTION DE EVENTOS DEL RATON CON WIN32..........................................................................................15

UTILIZACIÓN DEL TALLER DE RECURSOS DE BORLAND Y EL COMPILADOR DE RECURSOS ..........................................................................................................................................17

RECURSOS ............................................................................................................................................17 Tipos de recursos..........................................................................................................................17 USO DEL TALLER DE RECURSOS DE BORLAND..............................................................................................18 Creación de un proyecto de recursos:..........................................................................................18 CREACIÓN DE UN CUADRO DE DIALOGO......................................................................................................18 Incluir un menú en un cuadro de diálogo.....................................................................................19 Cambiar las propiedades de las ventanas....................................................................................19 Añadir controles a un cuadro de diálogo.....................................................................................20 Programación de un cuadro de diálgo con el API de Windows ...................................................20 CREACIÓN DE UN MENÚ...........................................................................................................................21 Añadir elementos de menú y separadores.....................................................................................21 Añadir aceleradores a menús.......................................................................................................23 PROGRAMACIÓN DE MENÚS Y ACELERADORES CON EL API DE WINDOWS........................................................23

IDENTIFICADORES....................................................................................................................................23

Creacion de un fichero de identificadores....................................................................................24 Añadir identificadores ..................................................................................................................24

DIBUJO GEOMÉTRICO DE OBJETOS.........................................................................................25 COLOR DE FONDO...................................................................................................................................25 PRIMITIVAS DE DIBUJO.............................................................................................................................26

Puntos............................................................................................................................................27

Líneas............................................................................................................................................27

Poligonos......................................................................................................................................29

TRANSFORMACIONES DE VISUALIZACIÓN............................................................................31 MATRIZ DE VISUALIZACIÓN......................................................................................................................31

VISTAS.................................................................................................................................................32

gluLookAt()...................................................................................................................................32

glOrtho().......................................................................................................................................33

glFrustum()...................................................................................................................................34

gluPerpespective()........................................................................................................................34

ASPECTOS AVANZADOS................................................................................................................36

LISTAS..................................................................................................................................................36

ILUMINACIÓN.........................................................................................................................................38

TEXTURAS.............................................................................................................................................41

SELECCION DE OBJETOS EN OPENGL.........................................................................................................47

Informatica Gráfica II

1999/2000

Gestión de Ventanas

La creación de una aplicación fundamentada en el entorno Windows es una tarea de por sí bastante complicada. En este apartado se indicarán los pasos a seguir para poder crear un programa de ventanas sobre un entorno Windows.

La creación de una aplicación sobre el entorno Windows obliga a realizar una gestión sobre las típicas ventanas de Windows, para ello el programador deberá tener en cuenta una serie de características y propiedades que pueden tener dichas ventanas.

Las etapas necesarias para desarrollar una ventana se dividirán en varias partes:

Descripción de la ventana WinMain().

Registro de las clases de Windows.

Creación de la ventana.

Gestión y control de la ventana WndProc().

El código para crear y gestionar una ventana se fundamentará en dos funciones.

WinMain()

La función WinMain() es la primera función que se ejecuta en una aplicación Windows. Esta función es la equivalente a la función Main() de un programa en C.

El prototipo de la función es el siguiente:

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow);

Esta declaración implica que Windows espera la misma convención de llamada que utiliza en otras funciones Windows. Una vez compilado y enlazado, Windows no sabe en que lenguaje a estado escrito el programa, por consiguiente no puede llamar a esta función según las convenciones de llamada de C++, por lo tanto Windows define una estructura de llamada única que deben utilizar todos los lenguajes.

La función WinMain() tiene como argumentos de entrada:

hInstance: Es el handle del programa en uso, este argumento puede ser utilizado por una aplicación Windows para acceder a información acerca del estado del programa.

hPrevInstance: Siempre tiene un valor 0, en el caso de Windows 95 este valor se ignora. Este valor es utilizado para tener un control sobre el número de veces que se activa el mismo programa. En el caso de Windows 95, este almacena la información de cada ejecución del programa independientemente, por lo tanto este valor es ignorado.

lpszCmdLIne: Puntero a una cadena terminada con el valor 0. Señala todos aquellos parámetros de la línea de comandos que pasan por el programa. Es

Informatica Gráfica II

1999/2000

decir nos permite iniciar nuestra aplicación Windows introduciendo un comando en la línea de comandos tradicional.

nCmdShow: Indica a la aplicación como debe mostrar la ventana inicial, es decir podremos iniciar la aplicación mostrando una ventana normal, o bien maximizada, minimizada (forma de icono).

En este punto debemos describir que se entiende por un Handle. Los handles se utilizan en las librerías estándar del lenguaje C para realizar las entradas y salidas de archivos I/O. Así por ejemplo si abrimos un fichero utilizando la llamada open(), esta retorna un handle, que es utilizado por el resto de instrucciones, tales como read(). Hay que tener en cuenta que a pesar de que podamos creer que los handler funcionan como punteros, realmente no lo son, ya que no podemos manipular directamente el handler para acceder a información del archivo.

Dentro de la función WinMain() se realiza el registro de la clase de ventana de Windows. La clase de ventana indica a Windows ciertas propiedades importantes acerca del funcionamiento de la ventana. A partir de una única clase de ventana se puede crear más de una ventana.

La función WinMain() registra la ventana rellenando la estructura WINDCLASS, que se define a continuación:

Typedef struct_WNDCLASSW{

UINT

style;

WNDPROC lpfnWndProc;

int

cbClsExtra;

int

cbWndExtra;

HISTANCE hInstance;

HICON

hIcon;

HCURSOR hCursor;

HBRUSH

hbrBackground;

LPCWSTR lpszMenuName; LPCWSTR lpszClassName; } WNDCLASS

Esta estructura almacena las características de una ventana del entorno Windows. A continuación describiremos cada uno de los campos de la clase WNDCLASS.

Style

Controla las características de redibujo de la ventana. Así por ejemplo si toma el valor CS_NOCLOSE, estamos indicando que la ventana no tendrá la opción de Cerrar en el marco de la ventana. Los valores con prefijo CS_ están definidos en la librería Windows.h.

Es posible combinar diferentes valores en el valor style, para ello cada constante CS_ se define como un único bit. La siguiente expresión combina varios estilos:

wc.style = CS_NOCLOSE | CS_DBCLIK;

Esta asignación indica que la ventana no tendrá la opción de Cerrar en el marco, además que enviará mensajes de doble clic a la ventana.

En la siguiente tabla se puede ver los diferentes valores que puede coger el valor style. Estas constantes están definidas en la librería Windows.h.

Informatica Gráfica II

1999/2000

Constante

Mascara

CS_VREDRAW

0x0001

CS_HREDRAW

0x0002

CS_OWNDC

0x0020

CS_CLASSDC

0x0040

CS_PARENTDC

0x0080

CS_SAVEBITS

0x0800

CS_DBLCLKS

0x0008

CS_BYTEALIGNCLIENT

0x1000

CS_BYTEALIGNWINDOW

0x2000

CS_NOCLOSE

0x0200

CS_KEYCVTWINDOW

0x0004

CS_NOKEYCVT

0x0100

CS_GLOBALCLASS

0x4000

lpfnWndProc

Este valor es un puntero a una función que esta relacionada con cualquier ventana creada con la clase de ventana.

cbClsExtra y cbWndExtra

Estos dos valores definen la clase y las extensiones de la ventana. Estos valores pueden ser utilizados para añadir miembros de datos al final de la clase de estructura ventana. C++ gestiona mucho mejor estos elementos y por lo tanto se recomienda que se le asigne el valor

0.

hInstance

Este parámetro esta definido para el manejo de la aplicación en uso. Este elemento permite asociar la clase de ventana al programa asociado.

hIcon

Este valor nos permite manipular el icono asociado a la aplicación. Windows asigna un valor por defecto, normalmente asigna el icono IDI_APPLICATION, existen otros tipos de iconos por defecto como son: IDI_ASTERISK, IDI_EXCLAMATION, IDI_HAND, IDI_QUESTION.

Cabe la posibilidad de que el programador asigne un icono propio para ello deberá utilizar la siguiente sentencia:

Informatica Gráfica II

1999/2000

wc.hIcon = LoadIcon(NULL,“Nombre del Icono”);

El icono deberá encontrarse en el mismo directorio que se encuentra la aplicación, sino deberemos detallar el camino completo del icono.

hCursor

Este valor se utilizará para definir el handle que se utilizará para el cursor. De igual forma que pasa el caso del icono, Windows utiliza la flecha que es el cursor por defecto, ahora bien se puede modificar y adaptar al gusto del programador. Algunos de los cursores que tiene Windows por defecto son: IDC_CROSS, IDC_IBEAM, IDC_NO, IDC_WAIT.

De la misma forma que ocurría con el icono el programador puede asignar el tipo de cursor que desee, para ello deberá utilizar la sentencia:

wc.hCursor = LoadCursor(NULL,“Nombre cursor”);

El tipo de cursor deberá encontrarse en el mismo directorio que la aplicación, si no es así se deberá especificar la ruta donde se encuentra el icono.

hbrBackground

Se utiliza para determinar el pincel (paint-brush) de colores que se utilizara para el fondo de la ventana de aplicación. Al ser los entornos de ventanas totalmente gráficos es necesario utilizar un pincel de color, así si deseamos pintar el fondo de color amarillo deberemos coger un pincel amarilla. Los pinceles de Windows no solo permite pintar con un color uniforme sino que también pueden utilizar patrones.

La asignación siguiente define como color de fondo el color blanco, si deseamos cambiar el color de fondo, es suficiente con modificar el índice de la asignación.

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

Los colores posibles que podemos utilizar para el fondo del área de trabajo de la aplicación serán:

Informatica Gráfica II

1999/2000

Color

Índice

Rojo Oscuro

  • 1

   

Verde

  • 2

 

Mostaza

  • 3

 

Azul Marino

  • 4

 

Violeta

  • 5

 

Cían

  • 6

   

Gris 25%

  • 7

   

Verde claro

  • 8

 

Azul Claro

  • 9

 

Marrón

  • 10

   

Gris 40%

  • 11

 

Gris 50%

  • 12

 

Rojo Brillante

  • 13

 

Verde Brillante

  • 14

 

Amarillo

  • 15

 

Azul Brillante

  • 16

   

Violeta Brillante

  • 17

 

Cían Brillante

  • 18

 

Blanco

  • 19

   

Negro

  • 20

 

Debemos tener en cuenta que los colores dependerán de la paleta de colores del monitor. Así tenemos que si la configuración es a 256 colores no se distinguirá diferencia entre los colores brillantes y los oscuros, por lo tanto se recomienda que el monitor tenga la configuración a Color de Alta densidad.

lpszMenuName

Señala el nombre de la clase de menú que se va a utilizar con la ventana, en el caso que no se tenga menú deberemos asignarle el valor NULL, si tenemos menú deberemos asignarle el nombre que se ha definido para él.

lpszClassName

Con este valor podemos asignar un nombre a la ventana, normalmente se utiliza el mismo nombre para la ventana y para la clase, pero no tiene porque ser así.

Una vez que hemos inicializado estos valores es el momento de registrar la ventana para ello utilizaremos la sentencia RegisterClass(&wc), donde wc es una variable de tipo

Informatica Gráfica II

1999/2000

WNDCLASS. Si esta sentencia da como resultado un cero, indicará que no se pude continuar, la aplicación se finalizará.

Hasta este momento lo que hemos realizado es el registro de clases de Windows, ahora debemos crear la ventana de la aplicación.

La creación de la ventana se realiza despues de registrar la clase de ventana, la estructura de la llamada para crear la ventana es la siguiente:

hWnd CreateWindow( LPCTSTR lpszClassName, LPCTSTR lpzWindowName,

DWORD

dwStyle,

int nX, int nY, int nWidth, int nHeight, HWND hwndParent, HMENU hmenu, HANDLE hinst, LPVOID lpvParam

);

En el caso del programa que se muestra a final de la sección tendremos el siguiente código para la creación de la ventana, que difiere en gran parte de la estructura CreateWindow():

hWnd = CreateWindow( szAppName, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WIDTH, HEIGHT, NULL, NULL, hInstance, NULL

);

szAppName: Será el nombre que se utilizará para registrar la clase de ventana de Windows, este mismo nombre es el que hemos utilizado anteriormente cuando se registraba la clase de ventana.

szTitle: Es él titulo de la ventana que creamos.

WS_OVERLAPPEWINDOW: Este valor especifica ciertos detalles de la ventana. Normalmente se le asigna el valor WS_OVERLAPPEWINDOW, ya que define la ventana como una ventana estandar del Windows, existen otras opciones como es WS_SCROLL,WS_MINIMIZE, WS_MAXIMIZE. La primera define que la ventana tendrá una barra de scroll horizontal, el segundo la posibilidad de minimizar la ventana, y finalmente la posibilidad de maximizar. Todas estas descripciones las podemos encontrar en el include Windows.h junto a un número elevado valores para la ventana.

Los dos siguientes argumentos descritos por, CW_USEDEFAULT, definen la posición inicial de la ventana, es decir las coordenadas x, y de la ventana. Este valor en nuestro caso es el propio Windows que especifica un valor por defecto.

Informatica Gráfica II

1999/2000

Los siguientes dos argumentos, WIDTH, HEIGHT, especifican las dimensiones de la ventana. Es posible que asignemos a estos valores CW_USEDEFAULT, de esta forma será Windows quien especifique las dimensiones.

Los siguientes dos valores son los handler de la ventana padre y del menú, el siguiente el handler de la aplicación en uso, y finalmente el último se define siempre a un valor NULL.

WndProc()

Esta función es la encargada de gestionar los diferentes eventos que suceden en el programa, en esta función es donde el programador debe especificar que ocurrirá en su aplicación según el tipo de evento que suceda.

El prototipo de la función es:

LONG WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

En el ejemplo que detallaremos al final de la sección podremos ver como nuestra aplicación no responde a muchos eventos.

Informatica Gráfica II

1999/2000

Ejemplo

En este punto veremos el código necesario para crear una ventana con fondo blanco, la cual pueda redimensionarse, y que tenga todas las posibilidades de una ventana Windows.

El resultado final del código que viene a continuación es:

Barra de menú del sistema
Barra
de
menú del
sistema
Minimizar
Minimizar

Barra de Título

Maximizar

Informatica Gráfica II 1999/2000 Ejemplo En este punto veremos el código necesario para crear una ventana
Nombre ventana Cerrar
Nombre
ventana
Cerrar

Área de trabajo

Una vez creada la ventana podemos ver que tiene todas las funciones típicas de una ventana Windows. Además tenemos la posibilidad de redimensionar la ventana como cualquier aplicación Windows.

El siguiente código fuente crea la ventana anterior.

//============================================== // Nombre del programa: Proyecto1 // // Descripción: Crea y gestiona una ventana del // entorno Windows //==============================================

#include <windows.h> #include <windowsx.h>

#define WIDTH

400

#define HEIGHT

300

HDC

ghDC;

HGLRC ghRC;

 

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow);

LONG WINAPI WndProc (HWND hWnd,UINT msg, WPARAM wParam,LPARAM lParam)

Informatica Gráfica II

1999/2000

HINSTANCE hInst;

// ==================================================

//

Nombre de la funcion: winMain

// ==================================================

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)

{

 

static char szAppName[] = "P1"; static char szTitle[]="Proyecto 1"; WNDCLASS wc; MSG msg; HWND hWnd; wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon (hInstance,IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = NULL; wc.lpszClassName = szAppName; if (!RegisterClass(&wc )) return (FALSE); hWnd = CreateWindow( szAppName, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WIDTH, HEIGHT, NULL, NULL, hInstance, NULL

);

if (!hWnd) return(FALSE); ShowWindow( hWnd, nCmdShow ); UpdateWindow( hWnd ); while (GetMessage(&msg,NULL,0,0)){ TranslateMessage( &msg ); DispatchMessage( &msg );

} return( msg.wParam );

}

// ==================================================

//

Nombre de la funcion:

WndProc

// ==================================================

LONG WINAPI WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {

LONG lRet = 1; RECT rect; PAINTSTRUCT ps; HDC hdc; static HINSTANCE hInstance;

switch (msg){

Informatica Gráfica II

1999/2000

case WM_PAINT:

BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); break;

case WM_DESTROY:

if (ghRC) wglDeleteContext(ghRC); if (ghDC) ReleaseDC(hWnd, ghDC); PostQuitMessage (0);

 

break;

default:

lRet=DefWindowProc(hWnd,msg,wParam,lParam);

break;

}

return lRet;

}

Renderizar OpenGL en una ventana WIN32

A continuación vamos a explicar como crear una ventana en la que renderizar una escena con OpenGL. Vamos a verlo con un ejemplo :

#include "stdafx.h"

#include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h>

HGLRC SetUpOpenGL( HWND hWnd ) // Inicializa el formato de pixel y devuelve el contexto de rendering {

static PIXELFORMATDESCRIPTOR pfd = {

sizeof (PIXELFORMATDESCRIPTOR), // tamaño de la estructura

1,

PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA,

// nº version // Flags de dibujo en la ventana

// -> Doble buffering! Utilizar SwapBuffers!!

24,

// 24 bits de color

0, 0, 0, 0, 0, 0,

// RGB bits // No preocuparse de esto

0, 0, 0, 0, 0, 0, 0,

// No buffer alpha // No buffer de acumulacion

32,

// 32 bits de buffer de profundidad

0,

// No buffer de 'stencil'

0,

// No buffers auxiliares

PFD_MAIN_PLANE,

// Tipo de capas

0,

// Reservado

0,

// No mascara de capas

0,

// No mascara de visibilidad

0

// No 'damage mask'

};

int nMyPixelFormatID; HDC hDC; HGLRC hRC;

// Elegimos el formato de pixel hDC = GetDC( hWnd ); nMyPixelFormatID = ChoosePixelFormat( hDC, &pfd );

// Obtenemos el handler del contexto SetPixelFormat( hDC, nMyPixelFormatID, &pfd );

// Obtenemos el handler del contexto de rendering hRC = wglCreateContext( hDC );

Informatica Gráfica II

1999/2000

ReleaseDC( hWnd, hDC );

// Devolvemos el contexto de rendering return hRC;

}

LONG WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) // Funcion que procesa los mensajes de la ventana {

HDC hDC; PAINTSTRUCT ps; GLsizei glnWidth, glnHeight; static HGLRC hRC;

switch (msg)

{

case WM_CREATE:

// Mensaje de creacion de la ventana // Seleccionamos el formato de pixel y entonces // creamos el contexto de rendering para este. hRC = SetUpOpenGL( hWnd ); return 0;

case WM_SIZE:

// Se ha cambiado el tamaño de la ventana // Redefinimos la proyeccion y el viewport hDC = GetDC(hWnd); wglMakeCurrent(hDC,hRC);

// Obtenemos el nuevo tamaño de la ventana // y redefinimos el viewport glnWidth = (GLsizei) LOWORD (lParam); glnHeight = (GLsizei) HIWORD (lParam);

glViewport(0,0,glnWidth,glnHeight);

// Redefinimos la proyeccion glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0, (GLfloat)glnWidth / (GLfloat)glnHeight, 1.0, 200.0); glMatrixMode(GL_MODELVIEW);

wglMakeCurrent( hDC, NULL ); ReleaseDC( hWnd, hDC ); return 0;

case WM_PAINT:

// Se redibuja la ventana

// Obtenemos el DC y le asociamos el RC hDC = BeginPaint( hWnd, &ps ); wglMakeCurrent (hDC, hRC);

// Dibujamos la escena glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

glColor3f(1.0,1.0,1.0);

glPushMatrix();

glTranslatef(0.0,0.0,-5.0);

auxWireTeapot(1.0);

glPopMatrix();

SwapBuffers(hDC);

// Deseleccionamos el RC wglMakeCurrent( hDC, NULL );

EndPaint( hWnd, &ps ); return 0;

case WM_DESTROY :

wglDeleteContext(hRC);

PostQuitMessage(0);

Informatica Gráfica II

1999/2000

return 0; } // Los demas mensajes no los procesamos return DefWindowProc( hWnd, msg, wParam, lParam );

}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,

int

nCmdShow)

// Funcion de entrada a la aplicacion {

static char szAppName[] = "OpenGL"; static char Titulo[]="Una ventana OpenGL"; WNDCLASS wc;

MSG

msg;

HWND

hWnd;

int i;

// Definimos la clase de ventana

wc.style wc.lpfnWndProc // mensajes

= CS_HREDRAW | CS_VREDRAW ;// Estilo de ventana.

= (WNDPROC)WndProc;

// Procedimiento de proceso de

wc.cbClsExtra

= 0;

// No hay datos extra por clase

wc.cbWndExtra

= 0;

// No hay datos extra por ventana

wc.hInstance

= hInstance;

// Instancia propietaria de la ventana

wc.hIcon

= NULL;

// Icono de la aplicacion

wc.hCursor

= LoadCursor(NULL, IDC_ARROW);

// Cursor

wc.hbrBackground = NULL;

wc.lpszMenuName = NULL;

// Con esto indicamos que el fondo de la // ventana no debe redibujarse // Esto es IMPORTANTE, porque si no, // la ventana parpadea cuando se redibuja

// Menu de la ventana

wc.lpszClassName = szAppName; // Nombre de registro de la ventana

// Registramos la clase RegisterClass( &wc );

// Creamos las ventanas de vista

hWnd = CreateWindow( szAppName, // Nombre de la aplicacion

| WS_CLIPSIBLINGS,

Titulo,

// Titulo de la ventana

WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN

// Estilo de la ventana

CW_USEDEFAULT, CW_USEDEFAULT, // Posicion

400, 400,

// Tamaño

NULL,

// Ventana padre

NULL,

// Usar el menu de la clase

hInstance,

// Instancia propietaria de la ventana

NULL

// No datos extra para la ventana

);

// Miramos si se ha podido crear la ventana if ( !hWnd ) return( 0 );

// Mostramos la ventana ShowWindow( hWnd, nCmdShow ); UpdateWindow( hWnd );

// BUCLE PRINCIPAL DE LA APLICACION // Procesamos los mensajes que van llegando a la aplicacion

while(GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam;

}

Informatica Gráfica II

1999/2000

Una ventana WIN32 donde se vaya a renderizar una escena con OpenGL debe, como minimo, gestionar tres eventos:

Creación de la ventana

En este momento se debe crear un contexto de rendering. Un contexto de rendering guarda toda la información que se necesita para renderizar una escena : El z-buffer, el buffer de color, etc. Cada contexto de rendering se identifica con su handler : el tipo HGLRC.

Se debe crear un contexto de rendering para cada ventana, y cuando se vaya realizar cualquier operacion con OpenGL, previamente hay que indicar con que contexto de rendering se va a trabajar. Para crear un contexto de rendering, en el ejemplo utilizamos la función SetUpOpenGL. Esta función obtiene como parametro el handler de la ventana para la cual se va a crear el contexto, y devuelve el handler del contexto para esa ventana.

Para indicar cual es el contexto que estamos utilizando actualmente se utiliza la función wglMakeCurrent, a la cual se le pasa el handler device context de la ventana donde vamos a renderizar, y el handler del contexto de rendering que hemos obtenido cuando creamos la ventana. En el ejemplo sólo se ha creado una sola ventana, y por tanto sólo necesitamos un HGLRC. Si se fuesen a crear mas de una ventana, entonces necesitariamos un HGLRC para cada una.

Cambio de tamaño de la ventana

En caso de que ocurra este evento hay que redefinir el tamaño del viewport y el aspecto de la proyección.

Redibujado de la ventana

Este evento se genera cuando hay que redibujar una parte de la ventana. En este momento es en el que hay que llamar a la rutina que dibuja la escena. Si estamos utilizando doble buffering, para que la pantalla no parpadee cuando estamos dibujando, es importante no olvidarse de llamar a la función SwapBuffers, que es la encargada de intercambiar el buffer de dibujo con el buffer que se esta visualizando. Si no llamamos a esta función, no se a va a dibujar absolutamente nada en pantalla.

Estos eran los minimos eventos que una ventana OpenGL debe gestionar. Obviamente hay muchos mas que seria deseable gestionar. Es muy probable que nuestra aplicación vaya a tener menus, y quizas también debamos gestionar eventos de raton. En caso que, por motivo de alguno de ellos vayamos a utilizar codigo OpenGL, entonces no debemos olvidar indicar cual es el contexto de rendering para el que se va a ejecutar el codigo OpenGL.

Gestion de eventos del raton con WIN32

Cuando se produce un evento de ratón sobre una ventana, se manda un mensaje a ésta, con parametros tales como en que posición de la ventana ha ocurrido, si la tecla de Ctrl o Shift estaban pulsadas, etc. Para que nuestra aplicación gestione uno de estos eventos, unicamente hay que añadir un nuevo case en el procedimiento de gestión de eventos de la ventana.

Informatica Gráfica II

1999/2000

A continuación tenemos una lista de algunos de los mensajes generados por un evento de ratón que puede recibir una ventana:

WM_LBUTTONDOWN

   

WM_RBUTTONDOWN

El botón izquierdo se ha pulsado sobre la ventana. El botón derecho se ha pulsado sobre la ventana.

 

WM_LBUTTONUP

El

botón

izquierdo

estaba pulsado

y

se

ha

soltado sobre la

ventana.

 

WM_RBUTTONUP

El botón derecho estaba pulsado y se ha soltado sobre la ventana.

WM_LBUTTONDBLCLK

Se ha hecho una doble pulsación rapida sobre la ventana con el botón izquierdo.

WM_RBUTTONDBLCLK

Se ha hecho una doble pulsación rapida sobre la ventana con el botón derecho.

Con todos estos mensajes se mandan unos parametros que son comunes a todos ellos :

int fwKeys = wParam; // flags de teclado GLint xPos = LOWORD(lParam); // posicion horizontal del cursor GLint yPos = HIWORD(lParam); // posicion vertical del cursor

El valor de fwKeys indica el estado de botones a tener en cuenta en un evento del ratón. Este valor viene dado por una OR bit a bit de los siguientes valores :

MK_CONTROL

Se estaba pulsando la tecla Ctrl. Normalmente se utiliza para indicar

selecciones multiples.

MK_LBUTTON

El botón izquierdo del ratón estaba pulsado durante el evento.

MK_MBUTTON

El botón de enmedio del raton estaba pulsado durante el evento.

MK_RBUTTON

 

MK_SHIFT

El botón derecho del ratón estaba pulsado durante el evento. La tecla de Shift (Mayusculas) esta pulsada durante el evento. Normalmente se utiliza para indicar selecciones multiples.

xPos y yPos dan la posición del cursor sobre la región cliente durante el evento. La región cliente es la zona de la ventana sobre la que la aplicación dibuja : No incluye ni el menu, ni las barras de scroll, la barra de estado ni los bordes de la ventana.

Hay que tener en cuenta que tener en cuenta que los ejes de coordenadas en que se dan xPos e yPos son diferente con los que trabaja OpenGL. El origen de coordenadas de la ventana de Windows esta en la esquina superior izquierda, el sentido positivo de la X va hacia la derecha, y el eje positivo de la Y va hacia abajo. OpenGL trabaja con el sentido positivo de las X hacia la derecha, y el eje positivo de las Y hacia arriba. Así, si se intenta hacer una correspondencia entre las coordenadas de la ventana Windows y coordenadas sobre el viewport de OpenGL hay que cambiar el valor de la coordenada Y :

Coord. X del Viewport = Coord. X de la ventana Windows

Informatica Gráfica II

1999/2000

Coord Y del Viewport = Altura de la ventana - Coord. Y de la ventana Windows

Utilización del taller de compilador de recursos.

recursos de Borland y

el

Personalizar una aplicación Windows con nuestros propios menús, iconos, punteros y mapas de bits resulta una tarea sencilla si utilizamos el Taller de recursos de Borland o BRW (Borland Resource Workshop). En este capítulo aprenderemos a utilizar este taller para crear nuestros propios iconos, cursores, menús y cuadros de diálogo. El taller también puede ayudarnos a manejar mapas de bits, teclas rápidas y cadenas. Los menús, cursores, iconos y cuadros de diálogo creados por separado en este capítulo, se ensamblarán para dar una representación de calidad a un programa, en posteriores capítulos.

Recursos

Los archivos de recursos permiten añadir a nuestro programa componentes interactivos con el usuario como los menús, teclas rápidas y cuadros de diálogo.

El código de recursos se almacena aparte del programa principal en C o C++. Esto asegura que la información de recursos pueda cargarse en el programa cuando sea necesario, permitiendo que la usen varias aplicaciones y que se pueda cambiar la representación de una aplicación sin necesidad de modificar el código original de la misma. BRW nos permite crear, editar y compilar estos recursos.

Tipos de recursos

Los recursos de aplicación se encuadran en diferentes grupos. Entre los más importantes podemos citar las teclas rápidas, mapas de bits, cursores, iconos, menús y cuadros de diálogo.

Teclas rápidas: Una tecla rápida es una tecla o combinación de teclas que podemos pulsar como alternativa a seleccionar con el ratón una opción del menú. Si se añade una tecla rápida, las selecciones se pueden efectuar sin usar los menús desplegables.

Mapas de bits: Un mapa de bits es un conjunto de datos utilizados por una aplicación para mostrar información gráfica en la pantalla. Los mapas de bits se pueden usar para mostrar imágenes gráficas pequeñas o pantallas completas.

Cursores: Un cursor de pantalla se utiliza para seleccionar elementos en la misma o para establecer el punto de inserción correspondiente a una entrada de datos.

Iconos: Un icono es un pequeño mapa de bits,

es una forma gráfica de representar las

aplicaciones que tenemos disponibles. Pulsando con el ratón en un icono se puede activar una aplicación.

Menús: Los menús de aplicación se visualizan en la parte superior de la ventana. Los menús listan las opciones del programa que se pueden activar con el ratón o mediante combinación de teclas clave. Las opciones del menú pueden utilizarse para abrir otros menús o cuadros de diálogo, así como para realizar acciones específicas. Los menús permiten al usuario la interacción a bajo nivel con el programa.

Cuadros de diálogo: Los cuadros de diálogo son los métodos principales para obtener entradas interactivas con el usuario. Generalmente, las opciones del cuadro de diálogo se

Informatica Gráfica II

1999/2000

eligen desde un menú y contiene diversos elementos llamados controles. Entre los controles del cuadro de diálogo podemos citar los recuadros de listas, barras de desplazamiento y los campos de entrada de datos.

Uso del taller de recursos de Borland

Cada editor de recursos es parte del paquete global del Taller de recursos de Borland. Para entrar en él, lo tendremos que hacer a traves de la opcion :

Informatica Gráfica II 1999/2000 eligen desde un menú y contiene diversos elementos llamados controles. Entre los

Creación de un proyecto de recursos:

Para crear un proyecto de recursos,

  • 1. Elegir File|New|Resource Project

  • 2. Seleccionar el tipo de recurso en el que se va a basar el proyecto. Generalmente será un

fichero de guión de recursos (.RC)

  • 3. Cuando se

termine, pulsar OK; después volver a pulsar OK para cerrar el cuadro de

diálogo. El nombre del proyecto se pondrá cuando se guarde.

Creación de un cuadro de dialogo

Para crear un cuadro de diálogo:

  • 1. Elegir File|New|Resource Project para crear un nuevo proyecto de recursos o File| Open para cargar un proyecto de recursos ya existente.

  • 2. Elegir Resource|New. El taller de recursos muestra el Resource.

cuadro de diálogo New

  • 3. En la lista Resource Type, seleccionar DIALOG o DIALOGEX. Elegir DIALOGEX para especificar información ExStyle en los diálogos cuyo destino sea Windows’95 o NT.

Informatica Gráfica II 1999/2000 eligen desde un menú y contiene diversos elementos llamados controles. Entre los

Informatica Gráfica II

1999/2000

  • 4. Presionar el botón Options para especificar el nombre y tipo del cuadro diálogo, y para establecer otras opciones del nuevo cuadro de diálogo, incluyendo la plantilla del diálogo.

  • 5. Seleccionar OK en el diálogo Options para aceptar los valores de las opciones. Seleccionar OK de nuevo en el diálogo New Resource para crear el nuevo diálogo.

Una vez hecho esto entraremos en el editor de diálogo, donde se puede personalizar el cuadro de diálogo.

Incluir un menú en un cuadro de diálogo

Como un cuadro de diálogo es en sí una ventana, también puede incluir un menú. Para incluir un menú en un cuadro de diálogo:

  • 1. Definir el menú como un recurso separado y añadirlo al proyecto

  • 2. Abrir el cuadro de diálogo al que se quiere añadir el menú

  • 3. Hacer click con el botón derecho del ratón en un área vacia del cuadro de diálogo y elegir Properties para abrir el inspector de propiedades (Property Inspector).

  • 4. Hacer click en la página Window. Introducir el nombre del menú recurso en el campo Menú.

Cambiar las propiedades de las ventanas

El tipo de ventana, estilo de marco, tamaño, posición y las fuentes del cuadro de diálogo se eligen en el inspector de propiedades.

Para abrir el inspector de propiedades, hacer click con el botón derecho del ratón en un área vacía del cuadro de diálogo y elegir Properties.

Para cambiar el tipo de ventana para el cuadro de diálogo, seleccionar la página Window y elegir un tipo de las opciones de Window Type

El marco se selecciona desde Frame Type en la página Frame. En esta misma página se eligen también los estilos del marco del cuadro de diálogo en Frame Attributes. Los atributos determinan la apariencia del cuadro de diálogo.

Para especificar cómo se visualiza el texto en un cuadro de diálogo seleccionaremos la página Fonts, y en ella indicaremos el tipo, tamaño y estilo del texto.

Para fijar el tamaño de un cuadro de diálogo modificaremos los valores Width y Heigh de la página general. Y para fijar la posición inicial del cuadro de diálogo modificaremos los valores de Top (superior) y Left (izquierda) de esta misma página.

Informatica Gráfica II 1999/2000 4. Presionar el botón Options para especificar el nombre y tipo del

Una vez que se ha definido un cuadro de diálogo, se pueden crear y manipular sus controles. En el editor de diálogos, el menú local Dialog, la paleta Control, y la paleta Tool, facilitan el trabajo con los controles.

Informatica Gráfica II

1999/2000

Añadir controles a un cuadro de diálogo

Para añadir un nuevo control a un cuadro de diálogo:

  • 1. Hacer click en la página y control deseado en la paleta Control

Informatica Gráfica II 1999/2000 Añadir controles a un cuadro de diálogo Para añadir un nuevo control
  • 2. El cursor cambia a forma de cruz cuando se mueve sobre el cuadro diálogo

que se está diseñando.

  • 3. Para situar el control y darle un tamaño al mismo tiempo, hacer click en el

diálogo donde se quiera que esté la esquina superior izquierda del control. Arrastrar el ratón hacia la esquina superior derecha hasta que el control tenga el tamaño deseado. También se puede utilizar el menú Dialog para añadir controles a un cuadro de diálogo.

Una vez que se haya añadido un control a un control de diálogo, se puede modificar fácilmente haciendo click con el botón derecho del ratón sobre él y eligiendo Properties. Se visualizará el inspector de propiedades, con opciones que pueden modificar la apariencia y comportamiento del control.

Programación de un cuadro de diálgo con el API de Windows.

Para programar un cuadro de diálogo con el API de Windows, se debe usar la función DialogBox.

Por ejemplo, el siguiente código muestra como crear y mostrar un cuadro de diálogo:

HINSTANCE hInst; HWND hwndParent; DLGPROC dlgProc; DlgProc = (DLGPROC) MakeProcInstance (ResModeDlgProc, Hinst); DialogBox (hInst, MAKEINTROSOURCE(IDD_DIALOGO), hwndParent, dlgProc); FreeProcInstance( (FARPROC) dlgProc);

Donde:

ResModeDlgProc :

los controles.

La función de diálogo que maneja los mensajes que llegan de

HInst : El manejador HINSTANCE del módulo Windows (.exe o .dll) que controla el recurso del diálogo.

IDD_DIALOGO : se reemplazará por el identificador del recurso del diálogo. HwndParent : La ventana padre del diálogo.

Informatica Gráfica II

1999/2000

Creación de un menú

El editor de menús facilita la creación y edición de menús. El trabajo con menús implica cuatro pasos básicos:

1.

Crear

el

menú o editar uno existente. El editor de menús se abre

automáticamente.

2.

Hacer cambios en el menú.

3.

Probar el menú.

 

4.

Guardar el menú.

El editor de menús proporciona diferentes vistas del menú que se está editando:

La ventana de edición muestra la estructura del menú que se está editando. También tiene un modo de prueba donde se puede ver el menú tal como aparecerá en la aplicación.

El Inspector de Propiedades es el lugar donde se personaliza el elemento seleccionado actualmente en el menú

Para crear un nuevo menú :

 

1.

Abrir el proyecto de recursos al que se quiera añadir el menú.

2.

Elegir Resource|New para crear un nuevo recurso para el proyecto. El Taller

de Recursos muestra el cuadro de diálogo New Resource.

3.

En la lista Resource Type, seleccionar MENU (o MENUEX para proyectos

de 32 bits).

 

4.

Hacer click en Options para especificar un tipo de menú distinto al tipo por

defecto.

5.

Hacer click en OK para situar el nuevo menú en el proyecto de recursos

actualmente abierto.

 

Añadir elementos de menú y separadores

Los elementos siempre se añaden antes del elemento seleccionado. Para añadir elementos al final, se debe seleccionar el área vacia situada al final e insertar el nuevo elemento.

Para añadir un elemento de menú, menú desplegable, o separador a un menú,

1.

Decidir donde se quiere el elemento. Hacer click, en la ventana de edición,

en el elemento encima del que se quiera que se añada la sentencia.

2.

Seleccionar el nuevo elemento que se quiere insertar antes del elemento

seleccionado.

Para insertar un nuevo elemento de menú, hacer click con el botón derecho del ratón y elegir New Menuitem o elegir Menu|New Menuitem.

Para insertar un menú desplegable, hacer click con el botón derecho del ratón y elegir New Popup o elegir Menu|New Popup.

Para insertar un nuevo separador, hacer click con el botón derecho del ratón

y elegir New Separato o elegir Menu|New Separator.

Ahora se puede editar la cadena de texto, el identificador de menú, mensaje de ayuda, y aceleradores.

Informatica Gráfica II

1999/2000

Un elemento de menú que se acaba de añadir, tiene el nombre genérico “Item”. Para que el elemento sea útil, es necesario editarlo. Se puede editar el texto directamente en el menú, o con el inspector de propiedades. Con el inspector de propiedades, también se puede cambiar el identificador del menú y poner una marca de verificación junto al menú si va a ser un interruptor.

Informatica Gráfica II

1999/2000

Añadir aceleradores a menús

Los elementos de la página Accelerator del inspector de propiedades del editor de menús, crean una asociación entre un recurso MENU y un recurso ACCELERATOR.

Informatica Gráfica II 1999/2000 Añadir aceleradores a menús Los elementos de la página Accelerator del inspector

Un recurso ACCELERATOR se considera asociado a un recurso MENU si tienene el mismo nombre de recurso (valor númerico o string). Un acelerador individual está asociado con un elemento de menú si ambos tienen el mismo identificador.

Programación

Windows

de

menús

y

aceleradores

con

el

API

de

Para programar menús y aceleradores con el API de Windows, se deben usar las funciones LoadMenu y LoadAccelerators.

Por ejemplo, el siguiente código muestra cómo asociar un ménu y aceleradores con la ventana principal:

HMENU hMenu; HMenu = LoadMenu(hInst, MAKEINTRESOURCE (IDENTIF_MENU1); LoadAccelerators (hInst, MAKEINTRESOURCE (IDENTIF_ACELER1);

Donde IDENTIF_MENU1 y IDENTIF_ACELER1 son los identificadores del menú y de la tabla de aceleradores respectivamente.

Identificadores

Windows requiere que cada tipo de recurso y recurso definido por el usuario estén asociados con un nombre o número entero único (llamado identificador de recurso). Por defecto el taller de recursos asigna un nombre a cada nuevo recurso.

Un identificador está formado por dos partes: un literal de texto (nombre del identificador) y un valor (normalmente un entero). Los identificadores deben ser únicos dentro de un tipo de recurso. Sólo son significativos los primeros 31 caracteres.

Cuando se crea un nuevo proyecto, lo primero que se debería hacer es especificar un fichero en el que almacenar los identificadores. Se deben almacenar los identificadores en uno o más ficheros cabecera de recursos (.RH) que usen #defines para asignar valores a nombres de identificadores.

Informatica Gráfica II

1999/2000

Creacion de un fichero de identificadores

Para añadir un identificador después de crear un nuevo proyecto.

1.

Hacer click con el botón derecho del ratón y elegir Add to Project. Se verá el

cuadro de diálogo Add to Project.

 

2.

Hacer click en la flecha de List Files of Type. Elegir: C/C++ header (*.h,

*.rh)

3.

Introducir un nombre para el fichero de identificadores en el cuadro File

Name.

4.

Presionar OK. El Taller de recursos crea un fichero de identificadores.

Añadir identificadores

 

Se puede añadir un identificador al fichero de identificadores antes de crear el recurso con el que estará asociado. Para añadir un identificador,

1.

Elegir Resource|Identifiers para visualizar el cuadro de diálogo Identifiers.

2.

Hacer click con el botón derecho del ratón y elegir Create Identifier. Aparece

el cuadro de diálogo New Change Identifiers.

 

3.

En el cuadro Name, introducir el nombre del identificador.

4.

En el cuadro Value, introducir el valor del identificador.

5.

En el cuadro File, introducir

el

nombre del fichero en el que se va a

almacenar el identificador.

 

6.

Presionar OK.

Informatica Gráfica II

1999/2000

Dibujo geométrico de objetos

Una vez que tenemos la ventana y el menú de la aplicación, debemos definir la ventana de visualización donde dibujaremos los objetos geométricos. Antes de definir las diferentes primitivas para la creación de objetos analizaremos una serie de puntos para conseguir un entorno agradable al usuario.

Informatica Gráfica II 1999/2000 Dibujo geométrico de objetos Una vez que tenemos la ventana y el
Informatica Gráfica II 1999/2000 Dibujo geométrico de objetos Una vez que tenemos la ventana y el

Área de visualización

Color de fondo

Frecuentemente, según el tipo de aplicación que deseemos crear deberemos modificar el color de fondo de la ventana. La paleta de colores de fondo o background, corresponde a la misma que se ha descrito en los temas anteriores. Hay que tener en cuenta, que la paleta dependerá de la configuración del monitor, ya que si está esta configurado a 256 colores, no existirá diferencia entre los colores brillantes y los oscuros.

Antes de modificar el color de fondo de la ventana, debemos tener en mente como almacena la información de pantalla los dispositivos gráficos, normalmente la intensidad de un pixel corresponde a la suma de diferentes buffers, cada uno de ellos almacena información referente al pixel en cuestión, OpenGL utiliza estos buffers para determinar el color y alguna información adicional de cada pixel.

Los buffers que utiliza son:

Buffer Referencia
Buffer
Referencia

Buffer de color

GL_COLOR_BUFFER_BIT

Buffer de profundidad

GL_DEPTH_BUFFER_BIT

Buffer de acumulación

GL_ACCUM_BUFFER_BIT

Buffer de plantilla

GL_STENCIL_BUFFER_BIT

Para modificar el color de fondo de la ventana podemos utilizar los siguientes comandos OpenGL:

glClearColor (Rojo, Verde, Azul, Alpha);

Informatica Gráfica II

1999/2000

glClearDepth( 1.0 );

Esta función define el color de fondo que se aplicará, utiliza los valores de RGB, por lo tanto cada argumento corresponde a la intensidad del color, Rojo, Verde y Azul, el rango de estos valores debe ir comprendido entre el valor 0.0, menor intensidad, 1.0 máxima intensidad. El cuarto parámetro que utiliza la función glClearColor, corresponde al valor alpha del color que se utiliza para determinar información referente a la transparencia del color, de igual forma que los tres argumentos anteriores, esté también toma valores comprendidos entre 0.0 opaco, y 1.0 totalmente transparente. La función glClearDepth, se utiliza par definir especificar información sobre la profundidad.

Una vez que sé a definido el color de fondo debemos y se ha definido la profundidad podemos utilizar la función glClear para borrara la pantalla con el color que habíamos definido, el argumento de esta función podrá ser simple o compuesto, ya que podremos combinar la información de varios buffers tal como:

glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

En este caso el color de fondo depende de la suma de los valores de los buffers de color y de profundidad. Como vemos esta función nos permite borrar el contenido de todos los buffers, ahora bien es muy frecuente que solo se desee borrar alguno de estos por lo tanto deberemos utilizar instrucciones tales como:

glClearColor() //borra el buffer de color. glClearDepth() //Buffer de profundidad glClearAccum() //Buffer acumulativo glClearStencil() //Buffer de plantilla

Existe una posibilidad de definir color mediante la instrucción glClearIndex, esta función define el color según un entero. Este entero debe estar comprendido entre 1 y el 20. Para más información ver tema gestión de ventanas donde esta detallada cada color con su índice.

Primitivas de dibujo

En esta sección se describirán los diferentes elementos necesarios para crear una imagen sintética. Antes de definir los diferentes elementos que podemos generar especificamos el formato que sigue OpenGL:

glBegin(MODO);

glVertex(x,y,z);

glVertex(x,y,z);

...

glEnd();

Donde MODO es el identificador del tipo que deseamos dibujar en la pantalla y glVertex es la descripxión de un punto de la pantalla.

Como veremos a continuación los objetos que definimos los podemos describir como un conjunto de líneas, este mecanimso es el que utiliza para realizar objetos OpenGL, el problema surge cuando se desea generar objetos curvos, en este caso debemos generar rutinas para generarlos.

Informatica Gráfica II

1999/2000

OpenGL, tiene definidos un conjunto de objetos curvos en las librerias AUX, por lo tanto incorporando estos elementos podemos reducir el coste de creación de los objetos.

Para definir el color de los objetos se empleará la instrucción glIndexi(), descrita en el apartado anterior.

Puntos

Un punto se define mediante la función glVertex, esta función especifica las coordenadas del punto dentro de la ventana de visualización. Con esta función podremos definir puntos en dos y tres dimensiones, dependiendo del número de coordenadas que se detallan. OpenGL trabaja normalmente en coordenadas homogéneas representadas por cuatro componentes, (x, y, z, h), por lo tanto cuando estamos definiendo puntos en dos dimensiones el valor z coge el valor cero y h el valor 1, en tres dimensiones h coge el valor 1.

El tipo especificado de coordenadas viene determinado a partir según los sufijos que siguen a la función glvertex. Los sufijos que pueden seguir a la función serán, d (double), indica que las coordenadas deben especificarse en valores double, f (float), i (integer) y finalmente s (short), por lo tanto las coordenadas deberán indicarse con valores que correspondan al sufijo. Existe la posibilidad de definir un punto mediante un vector que contenga las coordenadas, para ello deberemos utilizar el sufijo v indicando que es un vector de coordenadas.

Gldouble v[3]= {4.5, 6.7, 23.8} glVertex(v);

Para definir un punto o conjuntos de puntos debemos especificar las siguientes cláusulas glBegin(GL_POINTS) y la cláusula glEnd(), detallando entre ambas las posiciones de cada punto, así tenemos por ejemplo:

glBegin(GL_POINTS) glVertex2f(50.4,34.6); //Punto 2D,en punto flotante glVertex3i(10,20 34); //Punto 3D, en enteros glEnd();

Por lo tanto para definir un conjunto de punto sobre la ventana de visualización podemos emplear la estructura anterior.

Una vez definido los puntos podemos modificar su tamaño empleando la sentencia glPointSize(valor), donde valor representa el tamaño con el que deseamos dibujar el punto.

Líneas

De la misma forma que definimos puntos podemos definir líneas. Para definir una línea se precisan dos puntos, estos dos puntos se definen de la misma forma que al definir un punto de la pantalla, es decir con la función glVertex.

Existen diversas formas de definir líneas dependiendo del modo en que se describa en la cláusula glBegin(modo), modo representará el tipo de línea que deseamos.

Informatica Gráfica II

1999/2000

Modo Descripción
Modo
Descripción

GL_LINES

Genera una serie de líneas que no se conectan entre sí. Las líneas se definen mediante los pares de puntos sucesivos, por lo tanto el número de vértices debe ser par, en el caso de que fuera impar se ignoraría

GL_LINE_STRIP

Genera una serie de líneas pero que se conectan entre sí, es decir el punto final de una línea es el punto inicial de la siguiente. Con este modo se pueden generar figuras cerradas si el punto inicial coincide con el final.

GL_LINE_LOOP

Genera una serie de líneas conectadas entre sí, es parecido al modo anterior pero este modo conecta automáticamente el punto inicial con el punto final.

Podemos ver el efecto de los diferentes modos en el siguiente código:

glBegin (MODO); glVertex2f(0.0, 0.0); glVertex2f(1.0, 0.0); glVertex2f(1.0, 1.0); glVertex2f(0.0, 1.0); glEnd;

Gráficamente quedará de la siguiente forma, según el valor de MODO.

GL_LINES

GL_LINE_STRIP

GL_LINE_LOOP

(0,1)
(0,1)
(0,0)
(0,0)
  • (1,1)

  • (1,0)

    • (0,1)

(1,1) (1,0) (0,1) (0,0)
  • (0,0)

(0,1) (0,0) (1,1) (1,0) (0,1) (0,0) (1,1) (1,0)
(1,1) (1,0)
(1,1)
(1,0)

El aspecto de las líneas también pueden modificarse, pudiendo crear líneas más gruesas y con formato punteado, para ello se utilizará los comandos:

glEnable(GL_LINE_STIPPLE); glLineStipple( factor, mascara); glDisable(GL_LINE_STIPPLE);

Con estos comandos podemos conseguir líneas punteadas, la primera instrucción activa el modo de línea punteada, mientras que el segundo define el estilo de la línea, donde factor es un valor que esta comprendido entre 1 y 255, este valor define la separación entre los trozos de la línea, mientras que máscara, es un valor de 16 bits que se describe en hexadecimal, cada máscara define un formato de línea, los valores van comprendidos entre 0x0000 hasta

0xAAAA.

Otra posibilidad que tenemos es modificar el grosor de la línea, para ello utilizaremos la siguiente instrucción:

Informatica Gráfica II

1999/2000

glLineWidth(tamaño);

Por defecto OpenGL define las líneas con tamaño 1.0, pero podemos modificarlo mediante la sentencia anterior. Hay que tener en cuenta que una vez activado el tamaño hay que volver a establecer si deseamos líneas con el tamaño por defecto.

Poligonos

OpenGL define los polígonos como secuencia de aristas, por lo tanto sigue con el mismo formato especificado en los puntos anteriores.

Para generar polígonos tenemos los siguientes modos para la sentencia estos son:

glBegin(modo),

Modo Descripción
Modo
Descripción

Genera un simple poligono relleno con los vertices especificados. Para generar un poligono debemos tener en cuenta que se debe garantizar tres cosas:

GL_POLYGON

Como mínimo se precisan 3 vertices. Las líneas no deben cruzarse.

Los vertices deben formar un poligono convexo, en caso contrario OpenGL ignorará el vertice.

Genera una serie de triangulos rellenos que no se conectan entre sí. El número de vertices debe ser multiplo de 3, si el total de vertices es menor de tres, OpenGL ignora los vertices que no forma un triángulo.

GL_TRIANGLES

Genera una serie de triángulos rellenos conectados entre sí, es decir dos de los vertices de un triángulo son los vertices del siguiente triángulo. Debemos saber que con N vertices se pueden crear N-2 triángulos. De igual forma que anteriormente el número de vertices debe ser multiplo de tres, si no lo es se ignora aquellos que sobran.

GL_TRIANGLE_STRIP

Genera un conjunto de triángulos rellenos conectados entre sí, con la caracteristica de que todos los triángulos tiene un vertice en común. El primer triángulo defien el vertice comun a todos los triángulos. De igual forma que los anteriores el número de vertices debe ser múltiplo de 3, si no lo es se ignora aquellos vertices que sobran.

GL_TRIANGLE_FAN

GL_QUADS

Genera un conjunto de cuadrilateros rellenos sin conectar entre ellos. El número de vertices que se requiere es multiplo de cuatro, si no se verifica esto entonces OpenGL ignora los vertices que sobran. Cada cuatro veretices se describe un cuadrilatero.

Genera un conjunto de cuadrilateros rellenos que se conectan entre sí, es decir dos vertices de un cuadrado se utilizan para generar el siguiente cuadrilatero. Hay que tener en cuenta que si tenemos un total de N vertices podremos obtener un total de N/2-1 cuadrados. El número de vertices debe ser multiplo de cautro, si no se verifica entonces los vertices que sobran son ignorados.

GL_QUAD_STRIP

Informatica Gráfica II

1999/2000

Gráficamente podemos ver las diferencias entre los modos para genera polígonos:

v2 v1 v5 vo v4
v2
v1
v5
vo
v4
GL_POLYGON v1 v2 v0 v3 v4
GL_POLYGON
v1
v2
v0
v3
v4

GL_TRIANGLE_FAN

v0 v1 v4 v2 v5 v0 v3 GL_TRIANGLES v1 v0 v2 v3 v4 v7 v5 v6
v0
v1
v4
v2
v5
v0
v3
GL_TRIANGLES
v1
v0
v2
v3
v4
v7
v5
v6

GL_QUADS

v1 v3 v2 GL_TRIANGLE_STRIP
v1
v3
v2
GL_TRIANGLE_STRIP
v1 v0 v2 v3 v5 v4 GL_QUAD_STRIP
v1
v0
v2
v3
v5
v4
GL_QUAD_STRIP

Informatica Gráfica II

1999/2000

Transformaciones de visualización

Una vez que tenemos definida la ventana donde realizaremos la escena debemos definir como va a ser la visualización de esta escena, es decir, deberemos definir aspectos tales como, posición de la cámara, a que punto mirara la cámara, que tipo de proyección

utilizará,

...

etc.

En este apartado se mostrará como podemos manipular la cámara virtual, es decir el punto desde donde vemos la imagen, junto con las transformaciones de visualización. Estos dos puntos nos permitirán definir una visión de los objetos más realista. Otro de los apartados de esta sección será manipular las transformaciones de proyección, y poder ver los efectos sobre la imagen.

Matriz de visualización

Para poder manipular los objetos de la escena debemos tener definida una matriz de visualización, esta matriz será de cuatro por cuatro, es decir coordenadas normalizadas. Esta matriz se utiliza tanto en dos como en tres dimensiones, la diferencia esta en la coordenada z, en el caso de dos dimensiones z siempre tomará el valor cero, mientras que en tres dimensiones podrá tomar diferentes valores.

OpenGl, facilita maneras para poder manipular la matriz de visualización. Antes de manipular la matriz directamente debemos inicializar el modo para la matriz de operaciones, esto se consigue con la función:

glMatrixMode(GL_MODELVIEW);

Una vez definido el funciones:

modo de la matriz podemos asignarle un valor con las siguientes

glLoadIdentity()

Inicializada la matriz de transformaciones con la identidad.

glLoadMatrix(M)

Esta sentencia nos permite inicializar la matriz con un valor dado, especificado por el valor de M. M puede ser definido como una matriz de cuatro por cuatro o bien por un vector de

dieciséis elementos, (m1, m2, m3,

...

).

Hay que tener en cuenta que los valores de la matriz se

especifica por columnas, es decir la primera columna corresponde a los valores del vector m1, m2, m3, m4, la segunda columna corresponderá a los valores m5, m6, m7, m8, y así sucesivamente.

Como vemos estas dos sentencias nos permiten inicializar la matriz de transformaciones, para poder realizar operaciones precisamos la siguiente instrucción:

glMultMatrix(M)

Esta sentencia nos permitirá multiplicar matrices, teniendo en cuenta que multiplicará la matriz definida con una de las sentencias anteriores por la matriz M que definimos en este punto.

Informatica Gráfica II

1999/2000

Vistas

OpenGl permite utilizar diferentes expresiones para definir la posición de la cámara y hacía donde mira dicha cámara. En OpenGl la representación de los ejes e coordenadas se describe en el siguiente gráfico.

y x z
y
x
z

Existe la posibilidad de manipular estos ejes cambiando la posición relativa de cada uno de ellos, para ello OpenGl utiliza una serie de sentencias que nos definen como vamos a ver los ejes de coordenadas y en consecuencia como se visualizará el objeto tridimensional.

gluLookAt()

Esta sentencia permite definir de forma especifica donde se situará la cámara, hacía donde mirara está y cual va a ser el orden de los ejes de coordenadas.

gluLookAt(x0,y0,z0,xc,yc,zc,Vx,Vy,Vz);

Esta sentencia tiene nueve argumentos que describen tres puntos, los valores de x0, y0, z0, representa el punto hacia donde mira la cámara virtual, este punto normalmente se identifica en el origen de coordenadas (0,0,0), ahora bien podemos definir nosotros el punto que más propicio sea para nuestra escena.

Los siguientes tres argumentos representan el punto donde se situará la cámara de visualización, estas coordenadas no deben coincidir con el punto al cual miramos.

Las últimas tres coordenadas representa el vector de vista hacía arriba, es decir indica cual será el vector cuya dirección será hacia arriba, “apuntará hacia la parte superior del monitor”.

Para entender este caso podemos ver una serie de ejemplos donde se especifica cual es el vector y como que da definido los ejes de coordenadas:

Informatica Gráfica II

1999/2000

y y x x
y
y
x
x
  • z (Vx,Vy,Vz) = (1,1,0)

z
z

(Vx,Vy,Vz) = (0,1,0)

Como podemos ver según sea el valor del vector de vista hacia arriba el objeto que visualizaremos tendrá un aspecto u otro. Normalmente para simplificar la visualización el vector de vista hacia arriba se hace coincidir con uno de los ejes de coordenadas, como es el caso del primer gráfico.

glOrtho()

Se utiliza para especificar una proyección ortográfica. Este tipo de proyección define un volumen de vista rectangular, concretamente define un paralelepípedo de tamaño infinito, este hecho nos lleva a definir una serie de planos de corte para detallar con exactitud el volumen de vista.

OpenGl define la sentencia como:

glOrtho(xwmin, xwmax, ywmin, ywmax, pcerca, plejos);

Estos seis argumentos definen la ventana de visualización y los planos de corte tanto cercano como lejano.

Para definir la ventana de visualización es suficiente definir las coordenadas de dos esquinas de la ventana, con estos dos valores queda totalmente definida.

Los valores de pcerca y plejos representan el plano cercano y el plano lejano. Hay que tener en cuenta que el objeto a visualizar debe encontrarse dentro de ambos planos, si sobrepasan estos dos planos el objeto se recortará automáticamente.

plejos pcerca
plejos
pcerca
Informatica Gráfica II 1999/2000 y y x x z (Vx,Vy,Vz) = (1,1,0) z (Vx,Vy,Vz) = (0,1,0)

Plano de corte lejano

Informatica Gráfica II 1999/2000 y y x x z (Vx,Vy,Vz) = (1,1,0) z (Vx,Vy,Vz) = (0,1,0)
(xwmin, ywmin)
(xwmin, ywmin)

Plano de corte cercano

(xwmax, ywmax)

Informatica Gráfica II 1999/2000 y y x x z (Vx,Vy,Vz) = (1,1,0) z (Vx,Vy,Vz) = (0,1,0)

J.M.Huescar J.C.Serra A.F. Bennasar

33

Informatica Gráfica II

1999/2000

El objeto se visualizará entre los dos planos de recorte, en el caso que sobrepase estos planos se recortará, y si el objeto es tan grande que la ventana de visualización esta dentro de él, no se visualizará nada quedando la pantalla en negro.

Hay que tener en cuenta que si el plano de corte es negativo esté se encontrará detrás de la ventana de visualización.

glFrustum()

Este comando se utiliza para definir una proyección perspectiva, se define de forma similar a la proyección ortográfica pero con la diferencia que la proyección perspectiva define como volumen de vista una pirámide, en consecuencia el objeto a visualizar tiene un aspecto mucho más realista.

La sentencia que utiliza OpenGl es:

glFrustum(xwmin, xwmax, ywmin, ywmax, pcerca,plejos);

Como vemos esta sentencia se define de forma similar a la utilizada para definir proyecciones paralelas, de igual forma que anteriormente definimos planos de corte para limitar el volumen de vista, que en este caso al ser una proyección perspectiva definirá un tronco piramidal.

Plano de corte lejano

Plano de corte cercano

plejos (xwmax, ywmax) pcerca (xwmin, ywmin) Punto de vista
plejos
(xwmax, ywmax)
pcerca
(xwmin, ywmin)
Punto de vista

Como vemos ambas sentencias se define de forma similar pero determina vistas muy diferentes entre sí.

gluPerpespective()

Esta sentencia es una alternativa a la función glFrustum, la diferencia entre ambas está en la forma de definir la ventana de visualización. Si en la sentencia glFrustum definimos los dos vértices necesarios de la ventana, en la sentencia glPerpestive solamente definiremos el ángulo de apertura de la cámara y la relación entre el largo y ancho del plano cercano de corte.

Informatica Gráfica II

1999/2000

La sentencia en OpenGl será pues de la forma:

gluPerpective(apertura, aspect, pcerca, plejos);

Apertura corresponde al ángulo de apertura de la cámara virtual, este ángulo puede tomar valores comprendidos entre 0º y 180º. El valor de aspect, vendrá dado por la relación entre el alto y ancho del plano de corte, por lo tanto aspect toma el valor de alto plano dividido entre largo plano.

Los valores de pcerca y plejos corresponden a los plano de corte cercano y lejano, de forma similar que en los casos anteriores.

plejos
plejos

Plano de corte lejano

Plano de corte cercano

pcerca
pcerca

Apertura

Punto de vista

Informatica Gráfica II

1999/2000

Aspectos avanzados

Listas

Una lista de OpenGL es un grupo de comandos de OpenGL que han sido guardados para una posterior ejecución. Cuando una lista es invocada, los comandos en ella son ejecutados en el orden el los cuales fueron introducidos en ella.

Utilizar listas tiene dos ventajas. La primera es la eficiencia y la segunda la claridad. Imaginemos que tenemos que dibujar tres circulos en pantalla, y el codigo que tenemos es el siguiente :

void DrawCircle(void) {

GLint i; GLfloat cosine, sine;

glBegin(GL_POLYGON);

for(i=0;i<100;i++)

 

{

 

cosine = cos(i*2*PI/100.0); sine = sin(i*2*PI/100.0);

glVertex2f(cosine,sine);

 

}

glEnd();

}

void DrawScene(void) { glPushMatrix(); DrawCircle();

glTranslatef(2.0,0.0,0.0);

DrawCircle();

glTranslatef(2.0,0.0,0.0);

DrawCircle();

glPopMatrix();

}

En este caso, para dibujar los tres circulos, hemos tenido que calcular 300 veces los valores de un seno y un coseno. Hacer este tipo de calculos es muy costoso. Si utilizasemos listas, entonces solo necesitariamos calcular una vez los puntos del circulo, con lo que sólo calculariamos 100 senos y cosenos, y despues llamariamos a la lista tres veces, una vez por cada dibujo.

Por ejemplo, podriamos crear la lista cuando se inicializa la aplicación, con una funcion como la siguiente :

define LISTA_CIRCULO 0

void CreateLists(void) {

GLint i; GLfloat cosine, sine;

glNewList(LISTA_CIRCULO,GL_COMPILE); glBegin(GL_POLYGON);

for(i=0;i<100;i++)

{

cosine = cos(i*2*PI/100.0); sine = sin(i*2*PI/100.0);

glVertex2f(cosine,sine);

}

Informatica Gráfica II

1999/2000

glEnd();

glEndList();

}

Y despues, cuando necesitasemos dibujar los tres circulos, hariamos lo siguiente :

void DrawScene(void) { glPushMatrix(); glCallList(LISTA_CIRCULO);

glTranslatef(2.0,0.0,0.0);

glCallList(LISTA_CIRCULO);

glTranslatef(2.0,0.0,0.0);

glCallList(LISTA_CIRCULO);

glPopMatrix();

}

Hay que tener en cuenta que se pueden crear listas jerarquicas. Asi por ejemplo, la función DrawScene anterior se podria ejecutar con un solo glCallList, si antes hubiesemos definido una lista del tipo siguiente :

define LISTA_ESCENA 99 void DefinirListaEscena(void) { glNewList(LISTA_ESCENA,GL_EXECUTE); glPushMatrix();

glCallList(LISTA_CIRCULO);

glTranslatef(2.0,0.0,0.0);

glCallList(LISTA_CIRCULO);

glTranslatef(2.0,0.0,0.0);

glCallList(LISTA_CIRCULO);

glPopMatrix();

glEndList();

}

Cuando se crea una lista, OpenGL se encarga de guardar cuales son las operaciones que realizamos y posteriormente se pueden ejecutar con un coste en calculo mucho menor. Esta mejora en el tiempo de ejecución depende mucho de la implementación de OpenGL que estemos utilizando.

Tambien tienen sus desventajas. Normalmente requieren mucho espacio en memoria. Por ejemplo, para la lista del circulo, se necesita espacio al menos para 200 puntos en coma flotante. Ademas, las listas una vez hechas no se pueden cambiar, solo se pueden ejecutar.

Las listas pueden guardar las siguientes operaciones :

Operaciones con matrices

Rasterización de bitmaps y imagenes

Luces, propiedades de materiales y modelos de luces.

Texturas

Primitivas de dibujo : poligonos, mallas de triangulos, lineas, ...

Las operaciones basicas con listas son las siguientes :

Informatica Gráfica II

1999/2000

void glNewList( GLuint list, GLenum mode );

Crea o reemplaza una lista. list es el identificador de la lista. mode puede valer GL_COMPILE. En este caso, todos los comandos que se encuentren en la lista se guardan el la lista, pero no se ejecutan. En caso de valer GL_COMPILE_AND_EXECUTE se guardan en la lista y se ejecutan.

void glEndList( void );

Indica el final de los comandos de la lista.

void glCallList( GLuint list );

Ejecuta la lista indicada.

GLboolean glIsList( GLuint list );

Devuelve TRUE si existe alguna lista con el identificador dado.

Iluminación

Con OpenGL se pueden crear muchos efectos de luz. Se pueden crear luces puntuales, direccionales y focos.

Una luz puntual es como si fuese una bombilla. La luz surge de un punto, y se dispersa en todas direcciones. La intensidad de la luz en un punto del espacio depende de la distancia de este punto al origen de la luz.

Una luz direccional es una luz parecida a la luz del sol. La luz se dirige en una posición, y no parece que venga de un punto en concreto.

Nosotros veremos como crear luces de estos dos tipos, y dejaremos fuera de aqui el como crear luces de foco.

Para especificar la iluminación de una escena hay que decidir como seran las luces, y cuales seran los materiales de los objetos de las escenas. Por defecto la iluminación esta deshabilitada. Lo primero que hay que hacer si se desea utilizar luces es habilitarlas. Esto se hace con el siguiente codigo :

glEnable(GL_LIGHTING);

Despues hay que decidir cuantas luces se van a insertar en la escena. Cada implementación de OpenGL tiene un número maximo de luces que se pueden manejar. Este numero viene dado por la constante GL_MAX_LIGHTS, definida en el archivo gl.h. Para habilitar cada una de las luces se ejecuta lo siguiente :

glEnable(GL_LIGHTi);

donde 0<=i<=GL_MAX_LIGHTS. Este identificador GL_LIGHTi es el que habitualmente se utiliza para referenciar a una luz.

Informatica Gráfica II

1999/2000

Una vez habilitadas las luces, hay que indicar los parametros de estas. Para hacer esto utilizaremos las siguientes funciones : glLightf, glLighti, glLightfv, glLightiv. A continuación veremos algunos de los parametros mas importantes de las luces, y como modificarlos :

Posición y tipo de luz

Obviamente, si hay una luz, hay que indicar en que posicion de la escena esta. Esto se consigue llamando a la siguiente función :

void glLightfv( GLenum light, GLenum pname, const GLfloat *params );

donde light es el identificador de la luz (GL_LIGHTi) pname = GL_POSITION params es un vector del tipo GLfloat position[4];

Con esta llamada indicamos dos cosas.

Si params[3] tiene un valor igual a 0.0, entonces indicamos que es una luz direccional. En este caso el vector (params[0], params[1], params[2]), da el vector de la dirección de la luz.

Si params[3] tiene un valor igual

a 1.0, entonces indicamos que es una luz

puntual. El vector (params[0], params[1], params[2]) indica la posición de la luz

Color de la luz

Los colores de las luces en OpenGL tienen tres componentes :

Ambiente Esta componente afecta a todos los objetos de la, independientemente de la posición o orientación de estos. Viene a emular la luz que en el mundo real viene dada por la reflexión difusa en las paredes, la luz solar indirecta, etc.

Difusa Se puede pensar en ella como en el verdadero color de la luz. Su influencia sobre una superficie de un objeto depende de su orientación y su distancia.

Especular Influye en el brillo que va a tener el objeto.

Estas componentes se pueden modificar con la siguiente función :

void glLightfv( GLenum light, GLenum pname, const GLfloat *params );

donde light es el identificador de la luz (GL_LIGHTi)

pname = GL_AMBIENT, GL_DIFFUSE o GL_SPECULAR, dependiendo de que componente se quiere modificar.

params es un vector del tipo GLfloat color[4];

Informatica Gráfica II

1999/2000

El formato de params debe ser el siguiente :

color[3] debe valer 1.0 (color[0], color[1], color[2]) da el vector RGB del color.

Los materiales de los objetos tienen las mismas componentes que la los colores de la luz. Vienen a indicar la reflectancia del material a cada una de las componentes de la luz. Para indicar el material de los objetos que se van a renderizar a continuación se debe utilizar la siguiente función :

void glMaterialfv( GLenum face, GLenum pname, const GLfloat *params );

Donde :

face

indica a

que caras

se

van a modificar.

Puede valer GL_FRONT,

GL_BACK o GL_FRONT_AND_BACK, pero casi siempre trabajaremos con GL_FRONT.

pname indica que componente se va a modificar. Puede valer GL_AMBIENT, GL_DIFFUSE o GL_SPECULAR.

params es un vector de cuatro componentes que indica el nuevo valor del color. (params [0], params [1], params [2]) da el vector RGB del color. params [3] debe valer 1.0.

A continuación vamos a ver un ejemplo, donde se tiene una bola roja con una luz direccional :

// Si no se esta compilando con WIN32 quitar estas dos lineas #include "stdafx.h" #include <windows.h>

#include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h>

void Init(void) // Inicializacion de los parametros de OpenGL {

GLfloat mat_difuso[] = {1.0,0.0,0.0,1.0}; GLfloat posicion_luz[] = {1.0,1.0,1.0,0.0}; // Luz direccional! // Provar despues con una luz puntual del tipo siguiente. // Se nota un poco que la luz se va degradando cuanto mayor // es la distancia entre la luz y el objeto // GLfloat posicion_luz[] = {5.0,5.0,5.0,1.0};

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);

glDepthFunc(GL_LEQUAL);

glEnable(GL_DEPTH_TEST);

glMaterialfv(GL_FRONT,GL_DIFFUSE ,mat_difuso);

glLightfv(GL_LIGHT0,GL_POSITION,posicion_luz);

}

void DrawScene(GLenum RenderType)

Informatica Gráfica II

1999/2000

{

auxSolidSphere(1.0);

 

}

void __

stdcall

OnResizeWindow(GLsizei w, GLsizei h)

{

glViewport(0,0,w,h);

 

glMatrixMode(GL_PROJECTION); glLoadIdentity();

gluPerspective(60.0,1.0*(GLfloat)w/(GLfloat)h,1.0,200.0);

gluLookAt(0.0,0.0,5.0,0.0,0.0,0.0,0.0,1.0,0.0);

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

 

}

void __

stdcall

OnRedrawWindow(void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); DrawScene(GL_RENDER); glFlush(); auxSwapBuffers();

}

int main(int argc, char* argv[]) { auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);

auxInitPosition(0,0,400,400);

auxInitWindow(argv[0]);

Init();

auxReshapeFunc(OnResizeWindow);

auxMainLoop(OnRedrawWindow);

return 0;

}

Texturas

Poner texturas a un objeto es como poner papel pintado en una pared. Cuando se esta dibujando un polígono, es posible indicar que se dibuje este poligono con una imagen, indicandole para cada vertice del polígono, que posición de la imagen le corresponde. Es posible mapear texturas en una, dos y tres dimensiones. Aqui sólo trataremos las texturas

2D.

Para dibujar un objeto con texturas hay que seguir los siguientes pasos :

Habilitar el mapeado de texturas Esto se hace ejecutando la siguiente instrucción :

glEnable(GL_TEXTURE_2D);

Especificar que imagen va a ser utilizada como textura Para ello se utiliza la siguiente función :

void glTexImage2D( GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels );

Informatica Gráfica II

1999/2000

Donde :

target debe valer GL_TEXTURE_2D

 

level

indica el nivel

de

detalle

de

la

textura. Esto no se explica aqui,

y

habitualmente tiene un valor 0. components indica el nº de componentes del color. Usualmente se usan componentes RGB, y especificaremos 3. Pero también se pueden hacer texturas semitransparentes, con lo que se utiliza un formato RGBA (4 componentes). En ese caso indicariamos un valor de 4. width indica el ancho de la imagen de la textura. Debe ser una potencia de 2.

height indica el alto de la imagen de la textura. Debe ser una potencia de 2.

border indica si se utiliza un borde en la textura (1) o no (0). Usualmente es

0.

format indica el formato del valor de cada pixel. Normalmente se utiliza

GL_RGB. type indica el tipo de datos usado para cada componente del valor de un pixel. Puede ser uno de los siguientes valores: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT o GL_FLOAT. pixels es un puntero al mapa de valores de los pixels. Es la imagen en si.

La imagen se puede obtener de dos formas. Una, generandola con codigo del propio programa. Esto es facil si la textura es sencilla, como puede ser un tablero de ajedrez. Si la imagen es mas complicada, hay que cargarla de un archivo. En el ejemplo que damos aqui, se muestra una función para cargar una textura a partir de un archivo BMP de Windows.

Mapear la textura

Cuando se esta dibujando el objeto, hay que indicar, para cada vertice de este, que posición de la textura le corresponde. Esto se hace mediante la siguiente función :

void glTexCoord2f( GLfloat s, GLfloat t);

Donde (s,t) indica una posicíon sobre el mapa de la imagen.

Lo que se hace es indicar la coordenada de la textura antes de indicar el vertice del polígono. A continuación vamos a ver dos funciones, donde se dibujan un cuadrado y un triangulo, indicando las posiciones de la textura :

void Cuadrado(void) { glBegin(GL_QUADS);

glTexCoord2f(0.0,1.0);glVertex3f(-1.0,1.0,0.0);

glTexCoord2f(1.0,1.0);glVertex3f(1.0,1.0,0.0);

glTexCoord2f(1.0,0.0);glVertex3f(1.0,-1.0,0.0);

glTexCoord2f(0.0,0.0);glVertex3f(-1.0,-1.0,0.0);

glEnd();

}

Informatica Gráfica II

1999/2000

void Triangulo(void) { glBegin(GL_QUADS);

glTexCoord2f(0.0,1.0);glVertex3f(-1.0,1.0,0.0);

glTexCoord2f(1.0,0.0);glVertex3f(1.0,-1.0,0.0);

glTexCoord2f(0.0,0.0);glVertex3f(-1.0,-1.0,0.0);

glEnd();

}

Indicar como la textura va a ser aplicada a cada pixel

Aqui hay varios puntos que indicar. El primero de ellos es indicar que ocurre con el tamaño de las texturas. Cuando uno referencia la coordenadas de las texturas, se indican valores entre 0 y 1, que dan los limites de las texturas. Cuando uno referencia un valor mayor que 1 o menor que 0, se esta fuera del mapa de la imagen. ¿Que hacer en estos casos?. Hay dos posibilidades. La primera es repetir los pixels de los bordes de la textura cuando se referencie fuera de ella, lo cual no parece que tenga mucha utilidad. La otra posibilidad es la de repetir la textura. Esto es, en lugar de tener un mapa con solo una imagen, se tiene un mapa donde la imagen de la textura esta repetida infinitas veces, unas contiguas a las otras.

Imaginemos que tenemos que la imagen de la textura es la imagen de una baldosa, y queremos dibujar un suelo que vaya a contener 20x20 baldosas. Entonces, para dibujar este suelo solo tendriamos que poner un codigo tal que asi :

glBegin(GL_QUADS);

glTexCoord2f(0.0,20.0);glVertex3f(-1.0,1.0,0.0);

glTexCoord2f(20.0,20.0);glVertex3f(1.0,1.0,0.0);

glTexCoord2f(20.0,0.0);glVertex3f(1.0,-1.0,0.0);

glTexCoord2f(0.0,0.0);glVertex3f(-1.0,-1.0,0.0);

glEnd();

Para indicar si se quiere repetir el borde de la textura, o se quiere repetir la textura completa se utiliza la siguiente función :

void glTexParameterf( GLenum target, GLenum pname, GLfloat param );

Donde :

target debe valer GL_TEXTURE_2D.

pname puede valer GL_TEXTURE_WRAP_S o GL_TEXTURE_WRAP_T, donde el primero indica las coordenadas X de la textura, y el segundo las coordenadas Y.

param indica si queremos que se repita el borde de la textura (GL_CLAMP) o si queremos que se repita la textura completa (GL_REPEAT).

Otro de los parametros a tener en cuenta es el filtrado de las texturas. Cuando la camara esta muy cerca de un objeto con texturas, debido al efecto del mapeado se pueden notar con mucha claridad la diferencia entre los pixels contiguos de la textura, que se ven como unos cuadrados mas grandes cuanto mas cerca se esta del objeto.

Un efecto desagradable aparece tambien cuando se esta lejos de las texturas. Si se tiene objeto lejano con una textura del tipo de un tablero de ajedrez, debido a que solo se dibujan algunos de los pixels de la textura, pueden aparecer formas muy extrañas en la textura.

Informatica Gráfica II

1999/2000

Si se quiere evitar de forma parcial este efecto, existe la posibilidad de filtrar las texturas. Esto se hace con la siguiente llamada :

void glTexParameterf( GLenum target, GLenum pname, GLfloat param );

Donde :

target debe valer GL_TEXTURE_2D.

pname puede valer GL_TEXTURE_MIN_FILTER o GL_TEXTURE_MAG_FILTER, segun se este especificando un filtro para cuando la textura este lejos o cerca.

param indica el tipo de filtro a aplicar. Puede valer GL_NEAREST o GL_LINEAR. El primero indica que no se filtran las texturas. El segundo indica que se va ha hacer un filtrado lineal de las texturas. Hay que tener en cuenta que aplicar un filtrado a las texturas es muy costoso en tiempo.

A continuación vamos a ver un ejemplo que dibuja un cuadrado girado 45º horizontalmente. En el ejemplo se utiliza una clase de C++ que se corresponde con la información de la textura. Esta clase incluye una función para cargar una textura a partir de un archivo BMP. Hay que tener en cuenta que solo se pueden cargar archivos de 24 bits de color, y en ancho y alto de la imagen deben ser potencias de 2.

// Si no se esta compilando con WIN32 quitar estas dos lineas #include "stdafx.h" #include <windows.h>

#include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h>

/////////////////////////////////// // DECLARACION DE LA CLASE CTextura ///////////////////////////////////

#include <stdio.h>

class CTextura { public :

// Constructor

CTextura(void);

// Habilita las texturas static void HabilitarTexturas(void);

// Leer la textura desde un archivo BMP de Windows de 24 bits // Devuelve 1 si se ha leido la textura // Devuelve 0 en otro caso int LeerTextura(const char *Archivo);

// Pone la textura como la activa de OpenGL void PonerActiva(void);

// Destructor

~CTextura();

// Puntero a la imagen de la textura unsigned char *Imagen;

// Tamanyo de la textura int TamX,TamY;

Informatica Gráfica II

1999/2000

};

/////////////////////////////////// // IMPLEMENTACION DE LA CLASE CTextura ///////////////////////////////////

CTextura :: CTextura(void) {

Imagen = NULL;

}

int CTextura :: LeerTextura(const char *Textura) { BOOL b = FALSE;

int namelen = strlen(Textura); if ( namelen > 4 ) {

char suffix[5]; strncpy(suffix, Textura + namelen - 4, 4); suffix[4] = 0; _strupr(suffix);

// is it a BMP? if ( !strcmp(suffix, ".BMP") ) {

FILE* file = fopen(Textura, "rb"); if ( file != NULL ) {

BITMAPFILEHEADER fileheader; BITMAPINFOHEADER infoheader;

if ( fread(&fileheader, sizeof(BITMAPFILEHEADER), 1,

file) == 1

&& (char)fileheader.bfType == 'B' && *(((char*)&fileheader.bfType) + 1) == 'M' && fread(&infoheader, sizeof(BITMAPINFOHEADER),

1, file) == 1

&& infoheader.biCompression == BI_RGB ) {

* abs(( int ) infoheader.biHeight) * 3; unsigned char* imagebuf = new unsigned char[bufsize];

&& infoheader.biBitCount == 24

rewind( file ); long offset = fileheader.bfOffBits; fseek(file, offset, SEEK_SET);

int bufsize = infoheader.biWidth

 

if (

(int)fread(imagebuf, 1, bufsize, file) ==

bufsize )

 

{

 

// Flip the RGB components for ( int i = 0; i < bufsize / 3; ++i ) { unsigned char c = imagebuf[ i * 3 ]; imagebuf[ i * 3 ] = imagebuf[ ( i * 3 ) +

2 ];

 

imagebuf[ ( i * 3 ) + 2 ] = c; }

TamX = infoheader.biWidth; TamY = infoheader.biHeight; //image.setValue(size, 3, imagebuf); b = TRUE;

 

}

Imagen = imagebuf;

}

Informatica Gráfica II

1999/2000

}

}

}

}

return b;

fclose(file);

void CTextura :: PonerActiva(void) { if(Imagen)

glTexImage2D(GL_TEXTURE_2D,0,3,TamX,

TamY,0,GL_RGB,GL_UNSIGNED_BYTE, Imagen); }

void CTextura :: HabilitarTexturas(void) {

glPixelStorei(GL_UNPACK_ALIGNMENT,1);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

glEnable(GL_TEXTURE_2D);

}

CTextura :: ~CTextura() { if(Imagen) delete Imagen;

}

///////////////////////////////////////////////// // Variables globales :

CTextura *Textura;

void Init(void) // Inicializacion de los parametros de OpenGL {

glClearColor(0.0,0.0,0.0,0.0);

glDepthFunc(GL_LEQUAL);

glEnable(GL_DEPTH_TEST);

glShadeModel(GL_FLAT);

// Creamos y cargamos la textura Textura = new CTextura; if(!Textura->LeerTextura("amacdowell.bmp")) {

printf("No se pudo leer el archivo bitmap\n");

exit(-1);

} CTextura :: HabilitarTexturas(); Textura->PonerActiva();

}

void DrawScene(GLenum RenderType) {

// La imagen va a salir rojiza. // Notese que el color tambien influye el la textura

glColor3f(1.0,0.5,0.5);

glPushMatrix();

glRotatef(45.0,0.0,1.0,0.0);

glBegin(GL_QUADS); glTexCoord2f(1.0,1.0); glVertex3f(1.0,1.0,0.0); glTexCoord2f(1.0,0.0); glVertex3f(1.0,-1.0,0.0); glTexCoord2f(0.0,0.0); glVertex3f(-1.0,-1.0,0.0); glTexCoord2f(0.0,1.0); glVertex3f(-1.0,1.0,0.0); glEnd(); glPopMatrix();

}

Informatica Gráfica II

1999/2000

void __

stdcall

OnResizeWindow(GLsizei w, GLsizei h)

{

glViewport(0,0,w,h);

 

glMatrixMode(GL_PROJECTION); glLoadIdentity();

gluPerspective(60.0,1.0*(GLfloat)w/(GLfloat)h,1.0,200.0);

gluLookAt(0.0,0.0,4.0,0.0,0.0,0.0,0.0,1.0,0.0);

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

 

}

void __

stdcall

OnRedrawWindow(void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); DrawScene(GL_RENDER); glFlush(); auxSwapBuffers();

}

int main(int argc, char* argv[]) { auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);

auxInitPosition(0,0,400,400);

auxInitWindow(argv[0]);

Init();

auxReshapeFunc(OnResizeWindow);

auxMainLoop(OnRedrawWindow);

return 0;

}

Seleccion de objetos en OpenGL

Una función tipica de muchos editores es la posibilidad de seleccionar objetos con el ratón. Esta función en un principio es compleja. Imaginese que se tiene una escena renderizada con una vista perspectiva, y con una posicion de la camara arbitraria. La idea seria trazar un rayo que partiese del viewport, y averiguar que objetos atraviesa, y los calculos no son triviales. Afortunadamente, OpenGL incluye varias funciones que permiten facilitar la selección de objetos.

El funcionamiento del mecanismo de seleccion de OpenGL es sencillo : volver a redibujar la escena en un modo especial, dando un identificador (un número entero) al objeto que se está renderizando en cada momento. OpenGL testea que objetos estan, parcial o totalmente, dentro del volumen de visualización. Si asi ocurre, entonces se guarda el identificador del objeto dentro de una lista. Una vez finalizado el ‘dibujo’ de la escena, podemos consultar esta lista y decidir que objetos han sido seleccionados. Un ejemplo de como se seleccionan los objetos está a continuación :

Informatica Gráfica II

1999/2000

Informatica Gráfica II 1999/2000 Para seleccionar con el raton debemos afinar un poco mas este mecanismo.

Para seleccionar con el raton debemos afinar un poco mas este mecanismo. Lo que tenemos que hacer es indicar un volumen de visualizacion que sea similar a un rayo que parta del punto en que se ha pinchado el raton sobre el viewport, con un cierto tamaño que se pueden considerar como la sensibilidad de la selección, y una cierta profundidad que dependerá del tamaño de la escena. Afortunadamente, OpenGL también incluye mecanismos para indicar esta volumen.

A continuación haremos una referencia de las funciones de OpenGL que se utilizan en el mecanismo de selección, y despues veremos un pequeño ejemplo que muestra como funcionan.

GLint glRenderMode( GLenum mode );

Indica el modo que se va a utilizar para renderizar. Para el dibujo normal se debe indicar GL_RENDER. Para el modo de seleccion se debe indicar GL_SELECT. En caso de que se llame a esta función y estuviésemos en modo de selección, devuelve en numero de objetos que se han seleccionado mientras se estaba en ese modo.

void glSelectBuffer( GLsizei size, GLuint *buffer );

Indica el buffer donde se van a guardar los identificadores de los objetos que se seleccionen. El parámetro size indica el tamaño máximo del buffer. Esta función se debe llamar antes de entrar en el modo de selección. El formato del buffer de seleccion, una vez se ha hecho la busqueda de las selecciones es el siguiente :

0

1

2

3

n

n+1

n+2

n+3

N+4

Nº de id.

Zmin

Zmax

Id.1

Id.n

Nº de id

Zmin

Zmax

Id.1

...

...

Selección 1

Selección 2

Donde Nº de id. indica la lista de identificadores que corresponden con la selección. Es el nº de identificadores que habia el la pila de nombres cuando se vio que el objeto estaba seleccionado.

Informatica Gráfica II

1999/2000

Cuando se selecciona lo que se hace es trazar un rayo que, si intersecta al objeto, esta esta seleccionado. Normalmente el rayo intersecta en dos puntos al objeto : uno es el punto donde el rayo entra en el objeto, y otro es por donde sale (la parte de atras del objeto). Pues Zmax y Zmin dan un nº entero que da una medida del valor de Z en estos dos puntos. Con esto, si hay dos objetos que esta seleccionados, podremos saber cual es el que esta mas cerca de nosotros.

Id. 1, Id.2, seleccionado.

...

Id. n indican los identificadores que habia en la pila de nombres para el objeto

void gluPickMatrix( GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint viewport[4] );

Esta función define un volumen de seleccion perpendicular al viewport y centrado en un punto de este. El par (x,y) indica el centro del volumen y (width,height) indica el tamaño del volumen de seleccion. El array viewport indica las coordenadas del viewport actual, y tiene un formato (left,top,right,down). Esta funcion normalmente se utiliza cuando se pincha el raton sobre la pantalla y se quieren ver que es lo que se ha seleccionado. El (x,y) seria el punto donde se ha pinchado en la pantalla, y (width,height) seria la ‘sensibilidad’ del raton.

Las funciones que vienen a continuación se utilizan para indicar el identificador del objeto que se esta renderizando. En realidad no se indica sencillamente el nombre del objeto que se esta renderizando, sino que los nombres se organizan en forma de pila. Esto es útil cuando se tiene la escena organizada en forma de árbol. Imaginemos que tenemos un editor, con la posibilidad de agrupar objetos. Si pinchamos sobre un objeto que forma parte de un grupo, quizás nos interesa que nos indiquen, no solo el identificador del objeto que nos han pinchado, sino también el identificador del grupo al que pertenecía el objeto. Esto se consigue utilizando la pila de nombres (identificadores). Mas adelante se muestra un ejemplo de como funciona la pila de nombres.

void glInitNames( void );

Esta función inicializa la pila de nombres. La deja vacia.

void glPushName( GLuint name );

Inserta un identificador en la cima de la pila. Este es el identificador que se insertará en el buffer de selección si se selecciona el objeto.

void glPopName( void );

Saca el identificador que esta en la cima de la pila.

void glLoadName( GLuint name );

Reemplaza el identificador que esta en la cima de la pila por el dado. La pila debe no estar vacía.

Ahora vamos a mostrar un sencillo ejemplo que dibuja dos esferas, y muestra un mensaje cuando se pincha sobre una de las dos esferas. Si se compila para la plataforma windows, no olvidar indicar que es una aplicación de consola.

Informatica Gráfica II

1999/2000

// Si no se esta compilando con WIN32 quitar estas dos lineas #include "stdafx.h" #include <windows.h>

#include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> #include <stdio.h>

#define LENGTH_MAX_PATH 256

// La estructura de una seleccion typedef struct {

GLuint Identifiers[LENGTH_MAX_PATH]; int NIdentifiers; GLuint Zmin,Zmax; } Path;

void Init(void) // Inicializacion de los parametros de OpenGL {

glClearColor(0.0,0.0,0.0,0.0);

glDepthFunc(GL_LEQUAL);

glEnable(GL_DEPTH_TEST);

glShadeModel(GL_FLAT);

}

void DrawSphere(int Identifier, GLenum RenderType) // Dibuja una esfera // Identifier indica el identificador que hay que dar al objeto, si // estamos en modo de seleccion // RenderType indica si vamos a buscar una seleccion (==GL_SELECT) // o vamos a dibujar el objeto (==GL_RENDER) {

GLUquadricObj *Sphere = gluNewQuadric();

// Si estamos en modo seleccion, indicamos el identificador // de la esfera if(RenderType == GL_SELECT) glPushName(Identifier);

glPushMatrix(); if(Identifier == 1) {

glTranslatef(2.0,0.0,-5.0);

glColor3f(1.0,0.0,0.0);

}

else

{

 

glTranslatef(-1.0,0.0,-5.0);

glColor3f(0.0,0.0,1.0);

}

// Dibujamos una esfera de radio 2.5 con 10 paralelos //y 10 meridianos

gluSphere(Sphere,2.5,10,10);

// Sacamos el idenficador de la pila if(RenderType == GL_SELECT) glPopName();

glPopMatrix();

gluDeleteQuadric(Sphere);

}

void DrawScene(GLenum RenderType)

Informatica Gráfica II

1999/2000

// Dibuja la escena // RenderType indica si vamos a buscar una seleccion (==GL_SELECT) // o vamos a dibujar el objeto (==GL_RENDER) {

DrawSphere(0,RenderType);

DrawSphere(1,RenderType);

}

int GetSelection(int x, int y, Path *Selected) // Devuelve 0 si no se ha encontrado ninguna seleccion // Devuelve 1 si se ha encontrado alguna, y mete su valor en Selected {

GLuint SelectionBuffer[LENGTH_MAX_PATH]; GLint ViewportCoordinates[4]; int NObjectsSelected; Path SelectedPath, CurrentPath; GLuint z1,z2,Identifier; int IndexBuffer;

// Inicializamos el modo de seleccion glSelectBuffer(LENGTH_MAX_PATH,SelectionBuffer); glRenderMode(GL_SELECT); glInitNames();

// Obtenemos las coordenadas actuales del Viewport glGetIntegerv(GL_VIEWPORT,ViewportCoordinates);

glMatrixMode(GL_PROJECTION); glPushMatrix(); // Creamos un volumen de seleccion, centrado en el punto donde // se pincho el raton (x,y) y con una sensibilidad de 5x5

pixels

glLoadIdentity(); gluPickMatrix( (GLdouble)x, (GLdouble) (ViewportCoordinates[3]

- y), 5.0, 5.0, ViewportCoordinates);

glOrtho(-8.0,8.0,-8.0,8.0,0.0,20.0);

// Redibujamos la escena DrawScene(GL_SELECT); glPopMatrix(); glFlush();

// Ahora procesamos las selecciones detectadas NObjectsSelected = glRenderMode(GL_RENDER); printf("\nNumero de objetos selecionados : %i\n",NObjectsSelected);

IndexBuffer = 0; SelectedPath.Zmax = 0; for(int i=0;i<NObjectsSelected; i++) {

// Obtenemos los valores de la seleccion que vamos a estudiar CurrentPath.NIdentifiers = SelectionBuffer[IndexBuffer++]; CurrentPath.Zmin = SelectionBuffer[IndexBuffer++]; CurrentPath.Zmax = SelectionBuffer[IndexBuffer++]; for(int j=0;j<CurrentPath.NIdentifiers;j++) CurrentPath.Identifiers[j] = SelectionBuffer[IndexBuffer++];

// los zi dan una medida de lo cerca o lejos que estan los

puntos // pinchados del objeto // z2 es el punto mas cercano a la pantalla. // z1 el mas lejano. // Si hay dos objetos superpuestos, con z2 podemos decidir cual

de

// ellos es el realmente seleccionado. if(CurrentPath.Zmax > SelectedPath.Zmax) SelectedPath = CurrentPath;

}

if(NObjectsSelected>0)

{

*Selected = SelectedPath; return 1;

Informatica Gráfica II

1999/2000

}

else

 

return 0;

}

void __

stdcall

OnLeftMouseDown(AUX_EVENTREC *event)

{

int x,y; Path SelectedPath; x = event->data[AUX_MOUSEX]; y = event->data[AUX_MOUSEY]; int Seleccionado;

Seleccionado = GetSelection(x,y,&SelectedPath); if(Seleccionado) {

if(SelectedPath.Identifiers[SelectedPath.NIdentifiers-1] == 0) printf("Se ha seleccionado la bola azul\n"); else if(SelectedPath.Identifiers[SelectedPath.NIdentifiers-1]

== 1)

 

printf("Se ha seleccionado la bola roja\n");

}

}

void __

stdcall

OnResizeWindow(GLsizei w, GLsizei h)

{

glViewport(0,0,w,h);

 

glMatrixMode(GL_PROJECTION); glLoadIdentity();

glOrtho(-8.0,8.0,-8.0,8.0,0.0,20.0);

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

 

}

void __

stdcall

OnRedrawWindow(void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); DrawScene(GL_RENDER); glFlush(); auxSwapBuffers();

}

int main(int argc, char* argv[]) { auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);

auxInitPosition(0,0,400,400);

auxInitWindow(argv[0]);

Init();

auxMouseFunc(AUX_LEFTBUTTON,AUX_MOUSEDOWN,OnLeftMouseDown);

auxReshapeFunc(OnResizeWindow);

auxMainLoop(OnRedrawWindow);

return 0;

}

Vamos a ver ahora cual es la utilidad de tener una pila de nombres. Imaginemos que tenemos que queremos tener en pantalla dos moleculas : una de oxigeno (O2) y otra de agua (H2O), y queremos que cuando se pinche el raton, nos indique que atomo y que molecula se ha seleccionado. Hay varias formas de hacer esto, pero una de las mas comodas es utilizando la pila de nombres.

Tendremos la escena estructurada en forma de un arbol como este :

Informatica Gráfica II

1999/2000

Informatica Gráfica II 1999/2000 Cada uno de los nodos se corresponderia con una funcion que dibujaria

Cada uno de los nodos se corresponderia con una funcion que dibujaria cada uno de los elementos. Obviamente, no todos los nodos son diferentes. Es decir, la función que se llama para dibujar un atomo de oxigeno es igual para cada uno de los cuatro atomos.

Lo que haremos es en cada función meter en la pila de nombres un identificador que indica que nodo estamos renderizando : si es un atomo de agua o de oxigeno, o si es un atomo de oxigeno o hidrogeno. Entonces, cuando se seleccione, obtendremos un ‘camino’ dentro del arbol, que indica sobre que molecula y que atomo hemos pinchado. En este caso, todos los caminos tendran una longitud de dos identificadores, ya que todos los caminos dentro del arbol tiene una longitud dos.

A continuación se puede ver el código correspondiente a este ejemplo. Se basa en el codigo anterior, modificando las funciones DrawScene y OnLeftMouseDown :

#define AT_OXIGENO

0

#define AT_HIDROGENO

1

#define MOL_OXIGENO

0

#define MOL_AGUA

1

void DibujarAtomoOxigeno(GLenum RenderType) {

GLUquadricObj *Sphere = gluNewQuadric(); if(RenderType == GL_SELECT) glPushName(AT_OXIGENO);

glColor3f(1.0,0.0,0.0);

gluSphere(Sphere,2.0,20,20);

gluDeleteQuadric(Sphere); if(RenderType == GL_SELECT) glPopName();

}

void DibujarAtomoHidrogeno(GLenum RenderType) {

GLUquadricObj *Sphere = gluNewQuadric(); if(RenderType == GL_SELECT) glPushName(AT_HIDROGENO);

glColor3f(0.0,0.0,1.0);

gluSphere(Sphere,1.5,20,20);

gluDeleteQuadric(Sphere); if(RenderType == GL_SELECT) glPopName();

}

void DibujarMoleculaAgua(GLenum RenderType) {

if(RenderType == GL_SELECT) glPushName(MOL_AGUA);

Informatica Gráfica II

1999/2000

DibujarAtomoOxigeno(RenderType); glPushMatrix();

glTranslatef(1.5,1.5,0.0);

DibujarAtomoHidrogeno(RenderType);

glTranslatef(-3.0,0.0,0.0);

DibujarAtomoHidrogeno(RenderType);

glPopMatrix();

if(RenderType == GL_SELECT) glPopName();

}

void DibujarMoleculaOxigeno(GLenum RenderType) {

if(RenderType == GL_SELECT) glPushName(MOL_OXIGENO);

DibujarAtomoOxigeno(RenderType); glPushMatrix();

glTranslatef(1.5,0.0,0.0);

DibujarAtomoOxigeno(RenderType);

glPopMatrix();

if(RenderType == GL_SELECT) glPopName();

}

void DrawScene(GLenum RenderType) // Dibuja la escena // RenderType indica si vamos a buscar una seleccion (==GL_SELECT) // o vamos a dibujar el objeto (==GL_RENDER) {

glPushMatrix();

glTranslatef(0.0,3.0,-5.0);

DibujarMoleculaAgua(RenderType);

glTranslatef(0.0,-6.0,0.0);

DibujarMoleculaOxigeno(RenderType);

glPopMatrix();

}

void __

stdcall

OnLeftMouseDown(AUX_EVENTREC *event)

{

int x,y; Path SelectedPath; x = event->data[AUX_MOUSEX]; y = event->data[AUX_MOUSEY]; int Seleccionado;

Seleccionado = GetSelection(x,y,&SelectedPath); if(Seleccionado) {

 

if(SelectedPath.Identifiers[0] == MOL_OXIGENO) printf("Se ha seleccionado la molecula de oxigeno\n"); else if(SelectedPath.Identifiers[0] == MOL_AGUA) printf("Se ha seleccionado la modelcula de agua\n"); if(SelectedPath.Identifiers[1] == AT_HIDROGENO) printf("Se ha seleccionado un atomo de hidrogeno\n"); else if(SelectedPath.Identifiers[1] == AT_OXIGENO) printf("Se ha seleccionado un atomo de oxigeno\n");

}

}