OpenGL e' una libreria grafica. Fornisce strumenti per "disegnare" una scena in una finestra.
Assume di avere a disposizione una finestra in cui disegnare, ma non e' in grado di procurarsene una ne' di gestire l'interazione dell'utente con la finestra.
Glut e' una libreria creata per colmare questa lacuna. Crea finestre in cui OpenGL possa disegnare e gestisce l'interazione con l'utente su tali finestre.
Glut NON e' una libreria per interfacce grafiche. Non fornisce dispositivi di interazione sofisticati (bottoni, campi di testo, ecc.).
Una finestra creata da Glut e' "monolitica", non c'e' possibilita' di darle una struttura creando sottocomponenti/sottofinestre (pannelli, bottoni, ecc., come nelle librerie per interfacce grafiche).
Una finestra Glut prevede un certo numero di eventi che possono essere catturati associandovi delle funzioni callback. La funzione callback associata ad un certo tipo di evento scattera' automaticamente al verificarsi di quell'evento sulla finestra. Una callback e' associata a tutta la finestra, non c'e' possibilita' di associare callback diverse a zone diverse della finestra (appunto perche' la finestra e' monolitica).
Inoltre Glut prevede la possibilita' di associare un menu' pop-up
alla finestra, che appare quando l'utente preme il mouse
all'interno della finestra. Anche qui un solo menu' pop-up
e' associato a tutta la finestra, non c'e' possibilita'
di associare menu' diversi a zone diverse della finestra
(la finestra e' monolitica).
Il menu' puo' essere gerarchico, cioe' avere sotto-menu'.
Nota: in realta' si possono avere tanti menu' quanti i tasti del mouse.
Un programma OpenGL+Glut consiste in:
main contenente codice Glut per compiere:
un insieme di funzioni callback che contengono codice scritto dal programmatore (compreso il codice OpenGL) e sono registrate (nel main) per scattare in corrispondenza di eventi nelle finestre o all'attivazione dei menu' (e dei sottomenu').
glutInit(int * argc, char ** argv);
Chiamata con gli stessi argomenti con cui il programma e' invocato da command line.
Per ciascuna finestra che si vuole creare bisogna chiamare:
glutInitDisplayMode(unsigned int);
Stabilisce i requisiti della finestra da creare. I requisiti (in argomento) sono espressi da una lista di costanti intere messe in OR bit-a-bit. Tipiche richieste sono:
glutInitWindowSize(int dimX, int dimY);
(Opzionale) Stabilisce le dimensioni della finestra da creare (larghezza e altezza in pixel). E' solo un suggerimento, puo' essere ignorata dal window system.
glutInitWindowPosition(int x0, int y0);
(Opzionale) Stabilisce la posizione (riferita all'intero schermo) della finestra da creare. E' solo un suggerimento, puo' essere ignorata dal window system.
int glutCreateWindow(char * label);
Crea la finestra e ritorna un identificatore intero per tale finestra. La stringa label comparira' sulla barra del titolo della finestra (e' solo un suggerimento, puo' essere ignorata dal window system). La finestra creata non viene ancora resa visibile.
Per ciascuna finestra e' necessario registrare almeno la
display callback, che scatta quando la finestra diventa visibile
(la prima volta, oppure dopo essere stata parzialmente o totalmente
nascosta).
Tale callback contiene il codice OpenGL necessario a disegnare la scena.
Sulle callback torneremo dopo.
Per registrare la callback (nel main): glutDisplayFunc(f);
Il tipo della funzione f (definita a parte) deve essere: void f (void)
Su questo torneremo dopo.
A questo punto e' tutto predisposto, ma non appare ancora nulla su shermo.
glutMainLoop();
Rende visibile la finestra ed innesca il ciclo principale di gestione degli eventi. Questo e' un ciclo senza fine, interamente gestito da Glut, che ad ogni giro:
Il main loop non termina mai. Va chiamato come ultima istruzione del main, dopo avere registrato le callback. Il programma puo' terminare solo eseguendo exit all'interno di una funzione callback.
Eventi/callback previsti da Glut:
Le funzioni da usare come callback devono essere scritte dal programmatore prevedendo certi parametri come argomenti. Il numero, tipo e significato degli argomenti dipendono dal tipo di evento. Per esempio:
Sia XXXXX il nome di un evento Glut (vedere lista precedente):
Funzione senza argomenti, di tipo: void f(void)
Chiamata da Glut quando e' necessario ridisegnare la finestra. Contiene il codice OpenGL che ridisegna la scena.
Si puo' chiamare anche da programma mediante istruzione appostita, per ridisegnare la scena dopo aver cambiato qualcosa.
Per associare f alla finestra come display callback (nel main):
glutDisplayFunc(f);
Per chiamarla da programma (dopo averla associata come display callback):
glutPostRedisplay();
Di norma tutto il codice OpenGL trova posto nella display callback. Non mettere codice OpenGL dentro altre callback. Se una callback deve ridisegnare la scena (es. idle callback per animazione) lo deve fare chiamando glutPostRedisplay(); (vedere anche dopo).
Tipicamente il corpo della display callback contiene:
Abilitazioni delle funzionalita' OpenGL (glEnable)
Alcune funzioni OpenGL computazionalmente costose non sono abilitate, le abilita il programmatore solo se servono per la scena che vuole disegnare. Per esempio:
Pulizia del foglio (glClear)
Si cancella il contenuto dei buffer dove si andra' a disegnare. L'argomento e' una lista di costanti intere messe in OR bit-a-bit. Per esempio:
Trasformazione di proiezione
Stabilisce quale parte della scena viene inquadrata per produrre l'immagine (volume di vista). In OpenGL e' gestita da una matrice 4x4 chiamata projection matrix. La trasformazione di default, rappresentata dalla matrice identita', corrisponde ad una proiezione ortografica che ha come volume di vista il cubo con diagonale (-1,-1,-1)-(1,1,1).
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
Sulle trasformazioni di proiezione torneremo dopo.
Trasformazione di vista
Stabilisce la posizione del punto di vista da cui guardiamo la scena. In OpenGL e' gestita da una matrice 4x4 chiamata modelview. La trasformazione di default, rappresentata dalla matrice identita', mette il punto di vista in (0,0,0) e rivolto verso la direzione negativa dell'asse z.
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
Sulle trasformazioni di vista torneremo dopo.
Notare che, nel caso di trasformazioni di proiezione e di vista di default, il punto di vista sta al centro del volume di vista, cioe' una parte della scena inquadrata rimane dietro le spalle dell'osservatore. Questo e' permesso in proiezione ortografica: di fatto, non importa dove sta il punto di vista, la scena e' comunque proiettata con raggi visuali paralleli tra loro. Diverso sarebbe se la proiezione fosse prospettica. Ci torneremo.
Assegnazione degli attributi e chiamata delle primitive.
Prima di chiamare una primitiva occorre "preparare il terreno" assegnando il valore desiderato a tutti gli attributi rilevanti.
Sotto l'influsso degli attributi appena assegnati, si puo' chiamare la primitiva specificando il tipo e la lista dei vertici.
glBegin(int tipo_primitiva); ... glEnd();dove tra la coppia glBegin,glEnd trovano posto i vertici. Per specificare un vertice si usa glVertex che ha varie forme a seconda di come si vogliono passare i parametri (vedere il manuale).
Scambio di front e back buffer
Essendo la finestra double buffer, il disegno finora e' stato tracciato nel back buffer, ora lo portiamo nel front buffer perche' sia reso visibile sullo schermo.
glFlush();
glutSwapBuffers();
Funzione con due argomenti, di tipo: void f(int, int)
Chiamata da Glut quando la finestra cambia dimensioni. Gli argomenti sono generati automaticamente e sono le nuove dimensioni in pixel della finestra (larghezza, altezza).
Per associare f alla finestra come reshape callback (nel main):
glutReshapeFunc(f);
La parte di finestra in cui OpenGL disegna e' chiamata viewport.
Per default (cioe' se non viene registrata una reshape callback),
Glut mantiene sempre la viewport coincidente con tutta la finestra.
Questo significa che se sto disegnando un quadrato in una finestra quadrata
e l'utente deforma la finestra rendendola rettangolare, il mio quadrato
diventera' un rettangolo (si altera la "aspect ratio").
Il programmatore puo' registrare una reshape callback che eviti questo
problema imponendo a OpenGL di disegnare sempre in una sottoparte quadrata
della finestra (mantenimento della "aspect ratio").
Tipicamente il corpo della reshape callback contiene:
Assegnazione della viewport
glViewport(x0, y0, w, h);
Gli argomenti (interi, espressi in pixel) specificano
le coordinate (x0,y0) dell'angolo in basso a sinistra della viewport e
le sue dimensioni larghezza, altezza (w,h).
Glut intende le coordinate della finestra con l'origine in basso a
sinistra.
Ridisegnamento
Ottenuto chiamando glutPostRedisplay();
Le uniche funzioni callback che contengono codice OpenGL sono la display callback e la reshape callback (per il cambiamento della viewport). Non mettere codice OpenGL nelle altre funzioni callback.
Il corpo di funzione callback di solito contiene:
Cambio di stato del programma
A seconda di che cosa e' successo (leggibile nei parametri che
Glut passa alla callback) il programma cambia il valore di
alcune sue variabili interne.
In particolare puo' cambiare il valore di
variabili globali che sono usate nella display callback
(es: lo spessore di linea corrente).
Ridisegnamento
Se occorre (cioe' se sono cambiate delle variabili usate nella display callback) ridisegna la scena chiamando glutPostRedisplay();
Funzione con tre argomenti, di tipo: void f(unsigned char c, int x, int y)
Chiamata da Glut quando l'utente preme un carattere stampabile della tastiera. Gli argomenti, generati automaticamente, sono
Per associare f alla finestra come keyboard callback (nel main):
glutKeyboardFunc(f);
Funzione con tre argomenti, di tipo: void f(int k, int x, int y)
Chiamata da Glut quando l'utente preme un tasto speciale (non stampabile) della tastiera. Gli argomenti, generati automaticamente, sono
Per associare f alla finestra come special callback (nel main):
glutSpecialFunc(f);
Funzione con quattro argomenti, di tipo: void f(int b, int s, int x, int y)
Chiamata da Glut quando l'utente preme o rilascia un bottone del mouse. Gli argomenti, generati automaticamente, sono
Per associare f alla finestra come mouse function:
glutMouseFunc(f);
Funzioni a due argomenti, di tipo: void f(int x, int y)
Chiamate da Glut quando l'utente muove il mouse tenendo il bottone rispettivamente premuto o rilasciato. L'argomento e' la posizione (x,y) del mouse.
Per associare f alla finestra come motion o passive
motion callback:
glutMotionFunc(f); oppure glutPassiveMotionFunc(f);
Funzione a un solo argomento, di tipo: void f(int s)
Chiamata da Glut quando il mouse entra od esce dalla finestra. L'argomento e' lo stato attuale: GLUT_ENTERED o GLUT_LEFT.
Per associare f alla finestra come entry callback:
glutEntryFunc(f);
Funzione senza argomenti, di tipo: void f(void)
Chiamata da Glut "continuamente", cioe' ad ogni giro del main loop.
Per associare f alla finestra come idle callback:
glutIdleFunc(f);
Funzione a un solo argomento, di tipo: void f(int s)
Chiamata da Glut quando lo stato della finestra passa da visibile a invisibile o viceversa. L'argomento e' lo stato attuale: GLUT_VISIBLE o GLUT_NOT_VISIBLE.
Tipicamente usata per togliere la idle callback quando la finestra non e' visibile e rimetterla quando torna visibile. Per togliere la idle callback: glutIdleFunc(NULL);
Per associare f alla finestra come visibility callback:
glutVisibilityFunc(f);
Funzione a un solo argomento, di tipo: void f(int v)
Chiamata da Glut dopo un tempo stabilito a partire da quando la funzione viene registrata come timer callback. Anche il valore di v e' stabilito al momento della registrazione.
Per associare f alla finestra come timer callback:
glutTimerFunc(k,f,v);
Glut permette di regitrare un menu' pop-up gerarchico (anche detto "a cascata") associato ad bottone del mouse in una finestra.
La gerarchia di menu' e' rappresentata da un albero. La radice e' il menu' principale che appare cliccando con il mouse sulla finestra (con il bottone prestabilito). Se una voce di menu' chiama un sottomenu', il sottomenu' e' figlio del menu' che lo chiama (il quale puo' essere sottomenu' di un altro). Le foglie sono menu' che non ne chiamano altri.
La gerarchia si costruisce "dal basso" iniziando dalle foglie.
Per una finestra si possono avere fino a tre gerarchie di menu', ciascuna con la radice collegata a un diverso bottone del mouse.
int glutCreateMenu(f);
Crea un menu' (finora senza alcuna voce) avente la funzione f come callback. Ritorna un identificatore per il menu'. Il menu' appena creato diventa il menu' corrente, tutte le successive istruzioni di manipolazione di menu' si riferiscono al menu' corrente.
La funzione f e' incaricata di eseguire le azioni
corrispondenti alle varie voci.
E' una funzione a un argomento del tipo:
void f (int i)
Quando l'utente selezionera' la voce i-esima del menu', Glut chiamera'
automaticamente la funzione f con argomento i.
Tipicamente il corpo di f contiene uno switch a seconda del
valore di i.
Valgono le considerazioni fatte per le altre callback: non mettere
codice OpenGL nel corpo di f!
void glutAddMenuEntry(char * label, int v);
Aggiunge una voce al menu' corrente.
void glutAddSubMenu(char * label, int m);
Aggiunge al menu' corrente una voce che invoca un altro menu' (sottomenu'). Il sottomenu' apparira' quando l'utente scorrera' col mouse su quella voce.
void glutAttachMenu(int b);
Collega il menu' corrente al bottone del mouse passato in argomento, b e' GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, oppure GLUT_RIGHT_BUTTON. Il menu' pop-up apparira' premendo quel bottone.E' buona norma disattivare funzioni costose (es. animazione) mentre l'utente sta interagendo con un menu' pop-up. La Menu Status callback e' chiamata da Glut quando attiva o disattiva un menu' pop-up.
La menu' callback e' una funzione a tre argomenti, di tipo:
void f(int s, int x, int y)
Ne citiamo solo alcune: