|
|
|
cignoni@iei.pi.cnr.it |
|
http://vcg.iei.pi.cnr.it/~cignoni |
|
|
|
|
|
|
Oggi cercheremo di costruire da zero gli
elementi di base di una gui, in particolare uno slider, cercando di capire
quali siano le problematiche che ci sono dietro. |
|
Lo scopo è costruire, rimanendo in glut, un
programmino che permetta di modificare interattivamente alcuni parametri di
rendering di opengl |
|
|
|
|
|
|
|
|
|
|
#include<stdio.h> |
|
#include<GL/glut.h> |
|
|
|
void myRedrawFunc() |
|
{ |
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
|
glutSwapBuffers(); |
|
} |
|
void myIdle() { glutPostRedisplay();} |
|
int main(int argc, char *argv[]) |
|
{ |
|
glutInit(&argc, argv); |
|
glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH |
GLUT_DOUBLE); |
|
glutIdleFunc(myIdle); |
|
glutCreateWindow("Colors"); |
|
glutDisplayFunc(myRedrawFunc); |
|
glutMainLoop(); //Passare al SO il
controllo. |
|
return 0; |
|
} |
|
|
|
|
|
|
|
|
void myInitGL() |
|
{ |
|
// Abilita la possibilita' di cambiare il
alcune componenti colore del |
|
// materiale tramite un semplice glcolor,
anzichè fare glMaterial(...) |
|
glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE); |
|
glEnable(GL_COLOR_MATERIAL); |
|
|
|
// Abilita il calcolo dell'illuminazione |
|
glEnable(GL_LIGHTING); |
|
glEnable(GL_LIGHT0); |
|
|
|
// anche le normali attraversano la
pipeline, quindi devono essere |
|
// rinormalizzate, prima del calcolo
dell'illuminazione, altrimenti |
|
// il calcolo dell'illuminazione viene
sbagliato. |
|
glEnable(GL_NORMALIZE); |
|
|
|
// Abilita il test sullo zbuffer e quindi
l'eliminazione delle |
|
// superfici nascoste |
|
glEnable(GL_DEPTH_TEST); |
|
} |
|
|
|
|
|
|
|
Provare interattivamente le varie proprietà dei
materiali di opengl |
|
Il che implica: |
|
utilizzare qualche meccanismo di interazione
diretto tra l’applicazione e l’utente; |
|
Usare il mouse |
|
Implementare uno slider |
|
|
|
|
|
Al solito Callbacks |
|
glutMouseFunc(button,state,x,y); |
|
Chiamata quando cambia lo stato di uno dei
bottoni del mouse |
|
glutMotionFunc(button,state,x,y);
glutPassiveMotionFunc(button,state,x,y) |
|
Chiamate quando il mouse si muove con o senza I
bottoni premuti. |
|
|
|
|
|
Non è l’unica interfaccia possibile |
|
Windows struttura I messaggi in maniera
differente da glut |
|
|
|
|
Vogliamo mediare in maniera un po’pulita l’input
dell’utente, tramite il mouse, |
|
Costruiremo da zero uno slider che serva a
mappare il movimento del mouse in una certa regione della finestra nel
cambiamento di qualche proprieta’ della nostra applicazione |
|
|
|
|
|
|
Prima cosa ragioniamo su come è fatto uno slider
e cosa serve per memorizzarlo, disegnarlo e capire dove sto cliccando. |
|
|
|
|
|
|
Stato dello slider |
|
Dove:
Posizione e dimensione sulla finestra: |
|
In che sistema di riferimento? |
|
Scegliamo quello opengl (0,0 in basso a sx) |
|
Come:
range di valori su cui deve funzionare |
|
Valore corrente |
|
Altro (e.g. se stiamo draggando ecc.) |
|
|
|
|
|
Funzioni di modifica diretta dello stato dello
slider |
|
SetPos(); // pos e dim nella finestra |
|
SetRange(); |
|
SetVal(); |
|
Interrogazione |
|
GetVal(); |
|
|
|
|
|
Gestione eventi mouse, primo abbozzo |
|
MouseDown(); |
|
MouseUp(); |
|
MouseMove(); |
|
|
|
|
class glSlider |
|
{ |
|
public: |
|
glSlider(void); |
|
~glSlider(void); |
|
void SetPos(int posx, int posy, int sizex,
int sizey); |
|
void Draw(); |
|
|
|
void SetRange(float minval, float maxval); |
|
float GetVal(); |
|
void SetVal(int val); |
|
|
|
void MouseDown(int mx, int my); |
|
void MouseMove(int mx, int my); |
|
void MouseUp(int mx, int my); |
|
private: |
|
// Size and position |
|
int px,py,sx,sy; |
|
|
|
float minv,maxv; // range |
|
float curv; // current position |
|
}; |
|
|
|
|
Scriviamo subito le funzioni facili: |
|
|
|
|
|
|
|
L’implementazione fatta finora contiene alcune
parti palesemente sbagliate/arrangiate non generali (la draw!). |
|
Pero’ serve per testare il resto. |
|
L’importante è ricordarselo. |
|
|
|
|
Aggiungiamo una variabile globale di tipo
glSlider al nostro prog, un po’ di init nel main |
|
// Variabili Globali |
|
glSlider tt; // Lo Slider |
|
… |
|
int main(int argc, char *argv[]){ |
|
… |
|
tt.SetPos(5,5,200,50); |
|
tt.SetRange(0,100); |
|
tt.SetVal(50); |
|
… |
|
} |
|
E proviamo a disegnarlo |
|
void myRedrawFunc() |
|
{ |
|
glClear(GL_COLOR_BUFFER_BIT); |
|
tt.Draw(); |
|
glutSwapBuffers(); |
|
} |
|
|
|
|
|
|
|
La draw non era fatta bene |
|
Assunzione sulle matrici di proj che troviamo: |
|
glOrto
con coord == alla dim della finestra in pixel |
|
Aggiungiamo la gestione della resize, e
memorizziamoci le dim della finestra in due var globali |
|
// Stato della finestra corrente |
|
int WinW,WinH; |
|
|
|
void myReshapeFunc(int w, int h){ |
|
WinW=w; |
|
WinH=h; |
|
glViewport(0,0,w,h); |
|
} |
|
int main(int argc, char *argv[]){ |
|
… |
|
glutReshapeFunc(myReshapeFunc); |
|
… |
|
} |
|
|
|
|
|
|
void myRedrawFunc() |
|
{ |
|
glClear(GL_COLOR_BUFFER_BIT); |
|
glMatrixMode(GL_PROJECTION); |
|
glLoadIdentity(); |
|
glOrtho(0,WinW,0,WinH,-1,1); |
|
glMatrixMode(GL_MODELVIEW); |
|
glLoadIdentity(); |
|
|
|
tt.Draw(); |
|
glutSwapBuffers(); |
|
} |
|
|
|
void glSlider::Draw() |
|
{ |
|
int
curp=CurPos(); |
|
|
|
glDisable(GL_LIGHTING); |
|
… |
|
Come prima ma senza l’inizializzazione delle
matrici |
|
… |
|
} |
|
|
|
|
|
|
Di sicuro ci serve un test per sapere se ho
colpito con il mouse il nostro slider |
|
|
|
|
|
Aggiungiamo le callback di glut per il mouse, |
|
Notare l’inversione della y dovuta ai differenti
sistemi di riferimento |
|
Mouse ha lo 0 in alto a sx |
|
Opengl ha lo 0 in basso a sx |
|
main() |
|
… |
|
glutMouseFunc(myMouseFunc); |
|
glutMotionFunc(myMotionFunc); |
|
… |
|
|
|
void myMouseFunc(int button, int state, int x,
int y){ |
|
if(button==GLUT_LEFT_BUTTON && state
== GLUT_DOWN) |
|
{ |
|
if(tt.Hit(x,WinH-y))
tt.MouseDown(x,WinH-y); |
|
} |
|
} |
|
void myMotionFunc(int x, int y){} |
|
|
|
|
|
|
|
|
A questo punto conviene fermarsi e prima di
continuare l’implementazione farsi alcune domande: |
|
Cosa succede se voglio avere più slider? |
|
Chi distribuisce gli eventi tra i vari slider
della finestra? |
|
Ogni slider ha diritto a ricevere eventi anche
quando il mouse non e’ sopra di lui? |
|
Occorre una classe che distribuisca gli eventi
bene… |
|
|
|
|
|
|
|
Aggiungiamo a questo scopo un’altra classe
conosca tutti i widget nella finestra |
|
Ha una lista di puntatori a glSlider |
|
Distribuisce bene gli eventi del mouse |
|
Il nostro programma passa gli eventi del mouse
al manager che fa tutto lui |
|
Si occupa di disegnare tutti i widget |
|
|
|
|
|
|
|
|
class glDialogManager |
|
{ |
|
public: |
|
glDialogManager(void); |
|
~glDialogManager(void); |
|
void AddSlider(glSlider *sl); |
|
void Draw(); |
|
|
|
void MouseDown(int mx, int my); |
|
void MouseMove(int mx, int my); |
|
void MouseUp(int mx, int my); |
|
|
|
private: |
|
list<glSlider *> L; |
|
}; |
|
|
|
|
|
|
Il dialog manager usa una lista stl per tenere i
puntatori a tutti gli slider che deve gestire |
|
Le liste stl sono contenitori generici
templatati su un tipo scelto dall’utente |
|
push_back per aggiungere un elemento ad un
container. |
|
Iteratori (e.g. astrazione di puntatori) per
accedere agli elementi di un contenitore generico. |
|
|
|
|
Per scandire gli elementi di una lista occorre
un iteratore a quel tipo di lista e poi basta un semplice for. |
|
|
|
list<glSlider *>::iterator li; |
|
for(li=L.begin();li!=L.end();++li) |
|
DoSomethingWith(*li) |
|
|
|
|
Gestita dal DialogManager |
|
Semplicemente un for: |
|
|
|
void glDialogManager::Draw() |
|
{ |
|
list<glSlider *>::iterator li; |
|
for(li=L.begin();li!=L.end();++li) |
|
(*li)->Draw(); |
|
} |
|
|
|
|
|
|
|
Cosa succede quando si clicca su uno slider |
|
Lo slider colpito diventa “attivo” |
|
Lo slider attivo (ha il focus) finché il mouse è
premuto riceve tutti gli eventi del mouse (anche se il mouse è fuori
dall’area dello slider |
|
Quando si rilascia il bottone del mouse si
smette di aggiornare lo slider |
|
|
|
|
Si aggiunge un campo Focus al DialogManager, che
contiene un puntatore allo slider che ha correntemente il focus. |
|
Il campo Focus viene inizializzato dal
costruttore, e modificato dalla MouseDown e dalla MouseUp |
|
|
|
class glDialogManager |
|
{ |
|
… |
|
private: |
|
… |
|
glSlider *Focus; |
|
}; |
|
|
|
|
|
|
glDialogManager::glDialogManager(void){ Focus=0;
} |
|
|
|
void glDialogManager::MouseDown(int mx,int my){ |
|
list<glSlider *>::iterator li; |
|
bool found=false; |
|
for(li=L.begin();li!=L.end() &&
!found;++li) |
|
{ |
|
if((*li)->Hit(mx,my)) |
|
{ |
|
Focus=*li; |
|
Focus->MouseDown(mx,my); |
|
} |
|
} |
|
} |
|
|
|
void glDialogManager::MouseMove(int mx,int my){ |
|
if(!Focus) return; |
|
else Focus->MouseMove(mx,my); |
|
} |
|
void glDialogManager::MouseUp(int mx,int my) {
Focus=0; } |
|
|
|
|
|
|
|
|
Cambio di sistema di riferimento tra spazio del
mouse nella finestra e valori dello slider |
|
Conviene aggiungere due funzioni che facciano la
conversione di frame |
|
|
|
class glSlider |
|
{ |
|
private: |
|
… |
|
int Val2Pos(float val); |
|
float Pos2Val(int pos); |
|
… |
|
} |
|
|
|
|
int glSlider::Val2Pos(float val) |
|
{ |
|
int minp=px+sy/2; |
|
int maxp=px+sx-sy/2; |
|
if(val<minv) return minp; |
|
if(val>maxv) return maxp; |
|
return
(minp+((val-minv)/(maxv-minv))*float(maxp-minp)); |
|
} |
|
|
|
float glSlider::Pos2Val(int pos) |
|
{ |
|
int minp=px+sy/2; |
|
int maxp=px+sx-sy/2; |
|
if(pos<minp) return minv; |
|
if(pos>maxp) return maxv; |
|
return minv+(float(pos-minp)/(maxp-minp))*
(maxv-minv); |
|
} |
|
|
|
|
|
|
MouseDown e MouseUp in pratica non fanno nulla
(avrebbero un senso se si volesse ad esempio cambiare il modo in cui si
disegna lo slider mentre si dragga) |
|
MouseMove semplicemente converte da spazio di
mouse a spazio di valori dello slider |
|
|
|
|
void glSlider::MouseDown(int mx,int my) |
|
{ |
|
} |
|
|
|
void glSlider::MouseMove(int mx,int my) |
|
{ |
|
float newval=Pos2Val(mx); |
|
SetVal(newval); |
|
} |
|
|
|
void glSlider::MouseUp(int mx,int my) |
|
{ |
|
} |
|
|
|
|
|
|
Per gestire in maniera consistente gli slider un
modo è quello di copiare glut e affidare ad ogni slider una callback da
chiamare quando lo slider cambia valore; |
|
Una callback si memorizza con un puntatore a
funzione… |
|
|
|
|
|
|
|
Caso semplice |
|
void (*funcPtr)(); |
|
Puntatore ad una funzione che non ha argomenti e
ritorna void |
|
Trucco: Si leggono da dentro verso fuori,
destra-sinistra-destra |
|
funcPtr funcPtr… |
|
(*funcPtr) … è un puntatore … |
|
(*funcPtr)(); … a funzione senza argomenti … |
|
void (*funcPtr)(); … che ritorna void |
|
|
|
|
|
|
|
|
bool (*cbfunc)(float,float); |
|
puntatore a funzione che prende in ingresso due
float e ritorna un booleano |
|
|
|
float (*(*fp2)(int,int,float))(int); |
|
Puntatore a funzione che prende in ingresso due
interi e un float e ritorna un puntatore ad una funzione che prende in
ingresso un intero e ritorna un float |
|
|
|
|
Aggiungiamo quindi alla nostra classe |
|
class glSlider |
|
{ |
|
public: |
|
… |
|
void SetCallback(void (*func)(float)); |
|
… |
|
private: |
|
void (*cbfunc)(float); |
|
… |
|
} |
|
|
|
|
glSlider::glSlider(void) |
|
{ |
|
cbfunc=0; |
|
} |
|
… |
|
void glSlider::SetCallback(void (*func)(float )) |
|
{ |
|
cbfunc=func; |
|
} |
|
… |
|
void glSlider::MouseMove(int mx,int my) |
|
{ |
|
float newval=Pos2Val(mx); |
|
SetVal(newval); |
|
if(cbfunc) cbfunc(newval); |
|
} |
|
|
|
|
Usiamo gli slider per costruire un’app. in cui
si possa modificare interattivamente, componente diffusa, speculare e
shininess di un oggetto. |
|
Tre slider globali, ognuno con una sua callback
che modifica una variabile globale usata nella funzione di redraw per
definire le proprietà del materiale |
|
|
|
|
// Gli Slider |
|
glSlider tt1,tt2,tt3; |
|
|
|
// il Dialog Manager |
|
glDialogManager DM; |
|
|
|
// variabili gestite dagli slider |
|
float Shininess=1; |
|
float GraySpec=.5f; |
|
float GrayDiff=.5f; |
|
|
|
void SetShinyCallback(float val)
{Shininess=val;} |
|
void SetSpecCallback(float val) {GraySpec=val; } |
|
void SetDiffCallback(float val) {GrayDiff=val; } |
|
|
|
|
|
|
|
|
tt1.SetPos(10,10,200,30); |
|
tt1.SetRange(0,255); |
|
tt1.SetVal(50); |
|
tt1.SetCallback(SetShinyCallback); |
|
|
|
tt2.SetPos(10,50,200,30); |
|
tt2.SetRange(0,1); |
|
tt2.SetVal(.5f); |
|
tt2.SetCallback(SetSpecCallback); |
|
|
|
tt3.SetPos(10,90,200,30); |
|
tt3.SetRange(0,1); |
|
tt3.SetVal(.5f); |
|
tt3.SetCallback(SetDiffCallback); |
|
|
|
DM.AddSlider(&tt1); |
|
DM.AddSlider(&tt2); |
|
DM.AddSlider(&tt3); |
|
|
|
|
|
|
|
|
void myRedrawFunc() |
|
{ |
|
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT ); |
|
|
|
glMatrixMode(GL_PROJECTION); |
|
glLoadIdentity(); |
|
glOrtho(0,WinW,0,WinH,-1,1); |
|
glMatrixMode(GL_MODELVIEW); |
|
glLoadIdentity(); |
|
DM.Draw();
// Disegna tutti gli Slider |
|
myInitGL(); // Setta lo stato Opengl |
|
glMatrixMode(GL_PROJECTION); |
|
glLoadIdentity(); |
|
gluPerspective(45,1,1,10); |
|
glMatrixMode(GL_MODELVIEW); |
|
glLoadIdentity(); |
|
gluLookAt(0,0,5,0,0,0,0,1,0); |
|
glRotatef(glutGet(GLUT_ELAPSED_TIME)/50,1,1,0); |
|
// glutSolidTeapot(1); |
|
glutSolidTorus(.7,1,60,120); |
|
glutSwapBuffers(); |
|
} |
|
|
|
|
|
|
void myInitGL() |
|
{ |
|
// Settaggio materiale |
|
float
spec[4]={GraySpec,GraySpec,GraySpec,1}; |
|
glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR ,
spec); |
|
float
diff[4]={GrayDiff,GrayDiff,GrayDiff,1}; |
|
glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE ,
diff); |
|
glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,Shininess); |
|
|
|
// Abilita il calcolo dell'illuminazione |
|
glEnable(GL_LIGHTING); |
|
glEnable(GL_LIGHT0); |
|
|
|
// anche le normali attraversano la
pipeline, quindi devono essere |
|
// rinormalizzate, prima del calcolo
dell'illuminazione, altrimenti |
|
// il calcolo dell'illuminazione viene
sbagliato. |
|
glEnable(GL_NORMALIZE); |
|
|
|
// Abilita il test sullo zbuffer e quindi
l'eliminazione delle |
|
// superfici nascoste |
|
glEnable(GL_DEPTH_TEST); |
|
} |
|
|
|