Interazione con la scena OpenGL
Note integrative al corso di Grafica Interattiva, corso di Laurea
in Informatica, nuovo ordinamento.
A cura di Paola Magillo, DISI, Universita' degli Studi di Genova.
Selezione di oggetti disegnati
Consideriamo il seguente problema:
-
la finestra grafica mostra l'immagine di una scena costituita
da un insieme di oggetti
-
l'utente usa il mouse per puntare un oggetto
all'interno della finestra
-
vogliamo capire quale oggetto e' stato puntato
L'immagine e' una collezione di pixel colorati, non contiene alcuna
informazione riguardo gli oggetti della scena originale.
Per risolvere il problema dobbiamo costringere OpenGL a
ricordare le primitive che hanno dato luogo al colore
di ogni pixel.
Modi di rendering
OpenGL ha tre modi di rendering:
-
Render mode (default): le primitive sono processate
per produrre l'immagine nella finestra (frame buffer)
-
Selection mode: nessuna immagine prodotta,
i "nomi" delle primitive che apparirebbero nell'immagine sono
memorizzati in un buffer speciale (selection buffer)
-
Feedback mode: non ce ne occupiamo
Selection mode
Il selection mode serve per selezionare oggetti
dall'immagine visualizzata (es. l'oggetto sotto la posizione del cursore).
-
Disegno in selection mode la stessa scena che ho disegnato
in rendering mode
-
Siccome voglio solo le primitive che stanno nei
pochi pixel attorno al cursore,
prima di disegnare la scena restringo l'area di selezione
-
Mentre disegno, assegno ad ogni primitiva uno o piu' nomi
-
Ogni primitiva che verrebbe disegnata
(anche senza apparire nell'immagine perche' coperta da altre)
e che sta nell'area di selezione causa uno "hit"
e i suoi nomi vengono memorizzati nel selection buffer
Passi da effettuare
- creare il selection buffer
- entrare in selection mode
- restringere l'area di selezione
- disegnare la scena assegnando nomi alle primitive
- uscire da selection mode
- interrogare il contenuto del selection buffer per leggere
i nomi degli oggetti selezionati
Creare il selection buffer
glSelectBuffer(size,buffer)
- buffer = array di interi, di tipo GLuint,
che fungera' da buffer, deve essere abbastanza grande
per contenere tutti gli hit che avverranno
- size = dimensione (numero di elementi) dell'array
buffer
Entrare in selection mode
glRenderMode(GL_SELECT);
Restringere il campo
gluPickMatrix(x,y,w,h,viewport)
Restringe l'area di selezione (area della finestra in cui le
primitive che sarebbero disegnate generano "hit").
Di default l'area di selezione sarebbe l'intera viewport.
Va chiamata subito dopo
glMatrixMode(GL_PROJECTION); glLoadIdentity(); prima di effettuare
eventuali altre trasformazioni sulla projection matrix.
-
x,y = centro dell'area ristretta in coordinate
della finestra (pixel), es: la posizione del cursore
-
w,h = larghezza e altezza dell'area ristretta (pochi pixel)
-
viewport = array di 4 interi che descrive la viewport corrente,
posso ottenerla con glGetIntegerv (istruzione gia' vista)
Attenzione:
Le coordinate della finestra hanno y=0 in alto, mentre OpenGL le pensa
con y=0 in basso.
Quindi se la posizione del cursore e' (wx,wy) abbiamo
x=wx e y=viewport[3]-wy.
Disegnare la scena
Usare le stesse trasformazioni ed attributi usati in rendering mode.
Dare ad ogni primitiva (o gruppo di primitive che compongono
un unico oggetto) un nome per distiguerla quando viene selezionata.
Tale nome sara' riportato nel corrispondente "hit record".
-
piu' primitive hanno lo stesso nome se fanno parte di un oggetto
pensato come unico
-
una primitiva ha piu' nomi se e' pensata come facente parte di oggetti
diversi a seconda del livello di astrazione considerato
(nel caso di oggetti formati aggregando altri oggetti)
Esempio:
un tavolo formato da un piano e quattro gambe, il piano e ciascuna gamba
sono formati da varie primitive GL_POLYGON.
Voglio poter selezionare l'intero tavolo oppure ciascuno dei
suoi cinque pezzi separatamente.
Ciascuna GL_POLYGON ha due nomi: "tavolo" e uno fra
"piano", "gamba1", "gamba2", "gamba3", "gamba4".
In OpenGL i nomi sono numeri interi.
Nello stato di OpenGL c'e' uno stack di nomi correnti.
Una primitiva ha tutti i nomi che sono
contenuti nello stack quando la primitiva viene processata.
-
glInitNames(); inizializza lo stack dei nomi a vuoto
-
glPushName(int nome);
aggiunge un nome in cima allo stack
-
glPopName(); toglie il nome in cima allo stack
-
glLoadName(int nome); sostituisce il nome in
cima allo stack con quello dato (equivale a pop seguito da push)
Di solito il nome alle primitive si assegna con glLoadName
che pero' provoca errore se lo stack e' vuoto.
Per evitare cio', dopo l'inizializzazione si carica un primo
nome fittizio con glPushName.
Esempio:
glInitNames();
glPushName(~0); /* nome fittizio, intero con tutti i bit a 1 */
for (i=0; i<numero_primitive)
{ glLoadName(i);
glBegin(...);
...
glEnd();
}
Uscire da selection mode
N = glRenderMode(GL_RENDER);
Torna in rendering mode e ritorna in N il numero di hit generati.
Ritorna -1 se il selection buffer era troppo piccolo per contenere
tutti gli hit.
Leggere il selection buffer
Gli hit si trovano tutti i fila nel selection buffer.
Ogni hit occupa un numero variabile di posti nell'array (almeno 4):
-
numero di nomi della primitiva selezionata (quanti elementi erano nello
stack quando la primitiva e' stata processata)
-
z minima della primitiva nello z-buffer della finestra (sarebbe un numero reale
tra 0 e 1, viene moltiplicato per (2^32 -1) ed arrotondato al piu' vicino
unsigned int)
-
z massima, come sopra
- (e seguenti) i nomi della primitiva,
in ordine partendo dall'elemento inferiore dello stack
+---------------+
0 | K1= num. nomi | \
+---------------+ \
1 | min z | \
+---------------+ \
2 | max z | primo hit
+---------------+ /
3 | primo nome | /
+---------------+ /
...... /
+---------------+
3+K1 | K2= num. nomi | \
+---------------+ \
...... secondo hit
Di solito quello che interessa sapere e' il nome (i nomi).
Supponiamo che:
-
N = numero di hit ritornato da glRenderMode(GL_RENDER)
-
buffer = l'array usato come selection buffer
-
tutte le primitive hanno un solo nome (ogni hit occupa 4 posti
nell'array)
Allora il nome dell'i-esimo oggetto selezionato
(con i=0..N-1) lo trovo in buffer[i*4+3].
Nel caso al punto cliccato ci siano due (o piu') primitive,
entrambe (tutte) generano hit, anche se una sola (quella che sta
sopra le altre e le copre) si vede nell'immagine.
Abbiamo quindi N hit con N>1 ed altrettanti record
nel selection buffer.
Come distinguere quello buono, cioe' quello corrispondente alla
primitiva che e' visibile nel punto di click?
Dobbiamo prendere lo hit record che ha minimo valore di z minima
(cioe' quello piu' davanti).
Dunque scorriamo il selection buffer (supponiamo che
sia mantenuto nella variabile buffer):
InizioHit = 0;
HitBuono = -1; /* hit che sto cercando */
ZetaBuona = 0xFFFFFFFF; /* la sua zeta */
for (i=0; i < N; i++)
{
zmin = buffer[InizioHit+1];
if (zmin < ZetaBuona)
{ ZetaBuona = zmin; HitBuono = i; }
InizioHit += (3 + buffer[InizioHit]);
/* inizio del prossimo hit record, bisogna tenere conto che
ogni hit record ha lunghezza dipendente dal numero di nomi */
}