Laboratorio di Grafica Interattiva A.A. 2003-4

Esercizio 5

Argomento

Grafica tridimensionale in OpenGL.

Files

I file plotfun.h e plotfun.c definiscono la classe PlotFunction che implementa una funzione "graficabile":

La funzione viene graficata con riferimento ad una griglia che suddivide il dominio rettangolare in un certo numero di righe e colonne. Sono possibili tre modalita' di disegno:

Esempio per una griglia 2x2 (la modalita' di disegno a facce piene ammette piu' varianti tutte valide):

Il numero di righe e colonne della griglia determina la precisione del grafico e si sceglie con setRowColNum(numrighe,numcolonne).
La griglia avra' numrighe x numcolonne maglie rettangolari e (1+numrighe) x (1+numcolonne) vertici. Per default e' una riga e una colonna (4 vertici).

I limiti del dominio della funzione si assegnano con setDomain(x0,y0, x1,y1) dove (x0,y0) e (x1,y1) sono i due angoli in basso a sinistra e in alto a destra del rettangolo.

La classe PlotFun e' astratta, non implementa il calcolo del valore di f in un punto (funzioni vertexZ e pointZ).

I file mathfun.h e mathfun.c definiscono la sotto-classe MathFunction che implementa una funzione graficabile dotata di espressione analitica.

Occorre definire una funzione del tipo:
double myf(double x, double y) { .... }
e poi assegnarla all'oggetto di classe MathFunction con setFunction(myf).

Per esempio potete provare le funzioni:

Verranno poi date altre sottoclassi di PlotFunction.

Esercitazione guidata

Prima tappa

Prendete visione delle funzioni fornite nelle classi PlotFunction e MathFunction. Il file plotfun.h e mathfun.h contengono i commenti che spiegano che cosa fa ciascuna funzione.

Seconda tappa

Scrivete un programma che crea una funzione graficabile e ne visualizza il grafico. Per adesso realizzate le prime due modalita' di disegno (tralasciando quella a facce piene).

Per modularita' realizzate a parte una classe "disegnatrice" che contiene internamente un puntatore a un oggetto di classe PlotFunction. Il creatore della classe "disegnatrice" prendera' in argomento una funzione graficabile. La classe "disegnatrice" avra' un metodo incaricato di produrre le istruzioni OpenGL necessarie a disegnare la funzione graficabile associata (si puo' fare un metodo distinto per ogni modalita' di disegno). Chiamate poi questo metodo all'interno della display callback.

La classe "disegnatrice" potra' cosi' disegnare oggetti di qualunque sottoclasse di PlotFunction. Inoltre si potranno associare informazioni legate al disegno (ved. tappe successive).
NON definite invece sottoclassi di MathFunction con funzioni di disegno, perche' non potranno disegnare le sotto-classi sorelle (e dovrete ripetere il lavoro!).

Potete usare le funzioni vertexX, vertexY della classe PlotFunction per ottenere le coordinate dei vertici della griglia in base ai loro indici di riga e colonna, e la funzione vertexZ per ottenere il valore della funzione (coordinata z).

Poiche' disegnamo una scena 3D, e' necessario eliminare le parti nascoste degli oggetti. Bisogna aggiungere o ritoccare i parametri di alcune funzioni gia' usate:

Anche le trasformazioni vanno ritoccate perche' non e' detto che il grafico della nostra funzione cada all'interno del volume di vista di default di OpenGL, il cubo di diagonale (-1,-1,-1) (1,1,1).

Potete ricondurvi a questo caso scalando tutta la scena in modo che rientri nel cubo di default. Fate questo in coordinate di modellazione (matrice modelview). E' necessario:

nel codice le due operazioni saranno scritte in ordine inverso.

Potete usare le funzioni getMinX, getMaxX (ecc. per le y e z) per ottenere i limiti della parte di spazio che deve finire dentro al cubo di default. Si consiglia di scalare un po' di piu' per ottenere un volume di vista "abbondante" che lasci un po' di spazio vuoto intorno.

In questo modo senza eseguire altre trasformazioni riuscirete ad inquadrare tutta la scena. Tenete pero' presente che vedrete il grafico dall'alto (quindi vedrete una serie di punti o di quadrati apparentemente giacenti sul piano xy).

Terza tappa

Aggiungete un meccanismo per permettere al punto di vista di ruotare attorno alla scena usando due angoli:


Queste due rotazioni vanno eseguite esattamente in quest'ordine (quindi scritte in ordine inverso nel codice) e vanno applicate alla matrice MODELVIEW. Sono intese come trasformazioni di vista, cioe' devono intervenire per ultime sulla scena espressa in coordinate del mondo (nel codice vanno scritte per prime subito dopo il caricamento della matrice identica).

L'utente controlla questi angoli muovendo il mouse a bottone premuto:

Per riuscire a capire meglio che cosa succede ruotando, consigliamo di visualizzare anche il pavimento della scena (sul piano xy) come griglia quadrettata. E' sufficiente eseguire lo stesso disegno che si fa per la modalita' wireframe, ma con z=0 oppure z= la quota minima del grafico.

Quarta tappa

Realizzate la terza modalita' di disegno, quella a facce piene.

Poiche' non usiamo ancora l'illuminazione, e' necessario dare un colore diverso ad ogni faccia. Infatti senza illuminazione ogni faccia ha lo stesso colore indipendentemente dalla sua inclinazione, percio' tutto il grafico appare come una macchia in cui non si percepisce la tridimensionalita'.

Per colorare le facce si possono usare vari metodi:

Compito

In modo analogo a quanto fatto in 2D inserite un menu' con i comandi per:

Occorre aggiungere alla classe "disegnatrice" le variabili per tenere i valori correnti degli attributi.

Permettete all'utente di decidere la granularita' della griglia (il numero di righe e colonne), per esempio dal menu' scegliendo tra un insieme di opzioni prefissate.

Ripristinate da quanto fatto in 2D i comandi per zoom e pan (sia sul solo grafico che su tutta la scena fatta da grafico e assi cartesiani).