Laboratorio di Grafica Interattiva A.A. 2002-3
Esercizio 7
Argomento
Ancora trasformazioni in OpenGL.
Ancora selezione interattiva di oggetti in OpenGL, modifica della
geometria della scena.
Files
Nessun nuovo file, si continua da quelli dell'esercitazione precedente.
Esercitazione guidata
Prima tappa
Gestite la rotazione del singolo solido nel seguente modo:
-
La rotazione si comanda solo dalle tre viste ortografiche e in
ciascuna di queste viste l'utente controlla la rotazione attorno all'asse
che appare perpendicolare al piano di vista (nella vista sul
piano xy controlla la rotazione attorno all'asse z ecc.).
-
Nella vista prospettica l'utente controlla solo la rotazione dell'intera
scena (tutti i solidi e gli assi insieme) - che poi e' la rotazione del
punto di vista attorno alla scena - gia' implementata le scorse volte.
-
In ciascuna vista ortografica, muovere il mouse a bottone premuto
verso l'alto o verso destra ruota il solido selezionato
attorno all'asse in senso antiorario,
muoverlo verso il basso o verso sinistra ruota in senso orario.
-
Le rotazioni si eseguono nell'ordine in cui sono state specificate,
per es. se l'utente immette prima una rotazione attorno all'asse y,
poi attorno all'asse x poi di nuovo attorno all'asse y, infine
attorno all'asse z, le rotazioni sono applicate esattamente
in quest'ordine.
Per poter eseguire la sequenza di rotazioni nell'ordine specificato,
non basta memorizzare un angolo per ogni asse.
Bisogna accumulare le rotazioni in una matrice
man mano che l'utente le immette, poi durante il disegno
usare questa matrice per eseguire in un colpo solo
tutte le rotazioni accumulate.
L'istruzione OpenGL glMultMatrix permette di concatenare alla
matrice corrente una matrice esplicitamente memorizzata in un array.
Vale la solita regola dell'ordine di esecuzione inverso:
OpenGL eseguira' PRIMA la trasformazione corrispondente a questa matrice
e POI le trasformazioni che gia' erano presenti nello stato.
-
glMultMatrixf(matrice); oppure glMultMatrixd(matrice);
dove matrice e' un array di 16 GLfloat oppure
GLdouble (a seconda della forma usata).
Abbiamo una matrice ausiliaria per ogni solido che memorizza le
sue rotazioni.
Bisogna gestire questa matrice:
-
Inizializzarla alla matrice identica quando il solido viene creato
-
Concanetare una rotazione ogni volta che il solido viene ruotato
(cioe' nella mouse motion callback)
Per gestire la matrice usiamo le funzioni messe a disposizione da OpenGL,
con un trucco.
OpenGL presume che si lavori sulla matrice corrente modelview o projection,
qui noi vogliamo lavorare su una matrice "nostra" memorizzata in
un array.
Il trucco usa il top dello stack delle matrici come area temporanea
di lavoro per costruire e modificare la nostra matrice.
Ogni volta che vogliamo agire sulla nostra matrice
-
facciamo push sullo stack di matrici, questo salva la matrice
precedente
-
usiamo la matrice corrente per fare le modifiche che vogliamo
-
salviamo la matrice corrente nel nostro array
-
facciamo pop dallo stack di matrici, questo ripristina la matrice
che c'era, cancellando le tracce del delitto
Quindi le due operazioni che occorrono:
-
Inizializzare il nostro array come matrice identica:
glMatrixMode(GL_MODELVIEW);
/* ma andrebbe bene anche GL_PROJECTION, tanto poi si ripristina */
glPushMatrix();
/* salva la matrice */
glLoadIdentity();
/* pulisce la matrice */
glGetFloatv(GL_MODELVIEW_MATRIX,matrice);
/* carica nel nostro array la matrice pulita */
glPopMatrix();
/* rispristina la matrice precedente */
-
Concatenare una rotazione alla matrice memorizzata nel nostro array,
in modo tale che la nuova rotazione sia eseguita dopo le rotazioni
che gia' ci sono:
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
/* pulisce */
glRotatef(...);
/* mette la rotazione da eseguire dopo */
glMultMatrixf(matrice);
/* mette le rotazioni da eseguire prima */
glGetFloatv(GL_MODELVIEW_MATRIX,matrice);
glPopMatrix();
Seconda tappa
Date all'utente la possibilita' di selezionare, oltre che un solido,
anche una faccia su quel solido.
La faccia e' quella su cui e' avvenuto il click.
Evidenziate la faccia cliccata in qualche modo.
Occorre usare una gerarchia di nomi a due livelli.
Ogni primitiva GL_POLYGON ha due nomi:
- nome di solido, tutte le facce di un solido hanno lo stesso nome
di solido
- nome di faccia, ciascuna faccia di un solido ha un nome diverso
Mentre nell'esercitazione precedente agivamo sullo stack dei nomi con
glLoadName (che equivale a pop seguito da push) qui forse
per chiarezza e' meglio usare pop e push espliciti:
-
Parto con stack dei nomi vuoto
-
Quando inizio un solido faccio push del nome di questo solido,
ora sullo stack c'e' solo il nome del solido
-
Quando inizio una faccia faccio push del nome della faccia
senza fare pop (altrimenti toglierei il nome del solido),
ora sullo stack ci sono entrambi i nomi
-
Quando finisco la faccia faccio pop (toglie il nome della faccia),
ora c'e' di nuovo solo il nome del solido
-
Quando finisco il solido faccio pop (toglie il nome di solido),
ora lo stack e' di nuovo vuoto
Non e' necessario che i nomi dei solidi e quelli delle facce
siano insiemi di valori disgiunti, per esempio possono essere
i nomi dei solidi da 0 a 4 (se abbiamo 5 solidi) e quelli delle
facce da 0 a N (se abbiamo N facce nel solido).
Quando, al ritorno in rendering mode, leggete lo hit record,
ci trovate due nomi: uno di solido e uno di faccia, memorizzati
nell'ordine dal fondo verso la cima dello stack.
Questo significa PRIMA il nome del solido e POI quello della faccia.
Compito
Permettete all'utente di modificare la geometria della scena con
la seguente operazione di editing:
- l'utente seleziona una faccia
- l'utente sposta la faccia lungo un vettore perpendicolare
alla faccia stessa
Questa non e' una semplice operazione di visualizzazione.
E' necessario cambiare le coordinate dei punti nella struttura dati
e rifare la display list del solido.
-
Per ogni vertice v della faccia
si ricalcolano le coordinate di v
sommandovi un vettore incremento pari al vettore normale
della faccia (eventualmente con un fattore moltiplicativo
opportuno)
-
Siccome i vertici stanno in una tabella globale a cui fanno riferimento
tutte le facce del solido, automaticamente cambiano, oltre alla
faccia in questione, tutte le facce ad essa adiacenti, che vengono
deformate per "venire dietro" alla faccia spostata.
Il vettore normale (nx,ny,nz) ad un triangolo di vertici
p1=(x1,y1,z1), p2=(x2,y2,z1), p3=(x3,y3,z3)
si calcola con la seguente formula:
nx = dy12*dz13 - dz12*dy13;
ny = dz12*dx13 - dx12*dz13;
nz = dx12*dy13 - dy12*dx13;
dove
dx12 = x1-x2; dy12 = y1-y2; dz12 = z1-z2;
dx13 = x1-x3; dy13 = y1-y3; dz13 = z1-z3;
e i valori nx,ny,nz vanno poi normalizzati dividendoli
per la lunghezza del vettore (nx,ny,nz).
Il vettore normale ad una faccia non triangolare e' uguale alla
normale al triangolo formato dai primi tre vertici non allineati
della faccia.