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:

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:

Selection mode

Il selection mode serve per selezionare oggetti dall'immagine visualizzata (es. l'oggetto sotto la posizione del cursore).

Passi da effettuare

  1. creare il selection buffer
  2. entrare in selection mode
  3. restringere l'area di selezione
  4. disegnare la scena assegnando nomi alle primitive
  5. uscire da selection mode
  6. interrogare il contenuto del selection buffer per leggere i nomi degli oggetti selezionati

Creare il selection buffer

glSelectBuffer(size,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.

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".

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.

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):

  1. numero di nomi della primitiva selezionata (quanti elementi erano nello stack quando la primitiva e' stata processata)
  2. 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)
  3. z massima, come sopra
  4. (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:

    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 */
    }