Interazione utente-programma (dal punto di vista dell'OpenGL)
elementi di interazione uomo->macchina: dal punto di vista astratto possiamo elencare: LOCATOR specifica un punto (x,y) su schermo (ad es click del mouse) STROKE specifica un insieme di punti (traccia del mouse, oppure una serie di click del mouse) STRING una stringa di caratteri ("edit object") VALUATOR un valore scalare (uno slider..) CHOICE scelta in un menu' PICK selezione di una componente di un'immagine strutturata...
i componenti elencati fanno spesso parte di un sistema grafico (es XWindows, la Borland Builder Visual Class Lib, Java..., ecc); dal punto di vista dell'esecuzione del programma, abbiamo varie situazioni: un programma puo' decidere quando ha bisogno di dati (request mode), e i dati devono essere forniti al momento della richiesta es. tipico in/out stile "console" unix o msdos : printf("inserisci un intero"); scanf("%d", &dato); oppure cout<<"inserisci dato"; cin>>dato; ... il programma su scanf oppure su cin>> si ferma e aspetta il dato; l'istruzione di lettura e' sincronizzata con l'immissione del dato;
schema a eventi: il programma esegue costantemente un ciclo di attesa di evento, ovvero il programma lavora indipendentemente dal dispositivo che genera i dati: quando il dato serve al programma esso lo preleva (sample mode, campionamento) da una memoria comune, che e' la coda degli eventi (di vario genere) se la coda eventi e' vuota e il programma e' in attesa ("idle") l'OpenGL prevede la possibilita' di far fare al programma qualcosa "se non c'e' null'altro da fare allora fai questo" ancora, "event mode": dall' ingresso (uno dei ..) viene generato un dato che va inserito in una coda di eventi; appena possibile, il progr.(sistema OpenGL) esamina la coda di eventi e in base al tipo di evento e al dato associato esegue quanto richiesto; l'OpenGL prevede questo come specifica normale di funzionamento:
int main (int argc, char **argv) { myOpenWindow( argc, argv ); /* GLUT init */ myInitProj( ); /* init projection matrix */ glutKeyboardFunc( myKeyboard ); /*<<<<<< */ glutSpecialFunc ( myArrowkeys ); /*<<<<<< */ glutReshapeFunc ( myReshape ); /*if reshape*/ glutDisplayFunc ( myDisplay ); /* chiamata da glutPostRedisplay() */ glutMainLoop( ); return (0); } /* main() */ /*le procedure glutEventFunct(myProc) hanno parametro di tipo indirizzo di procedura, e myKeyboard, myArrowkeys, myReshape, sono procedure (che scrivo io) da eseguire in risposta agli eventi specificati:
procedura da eseguire se si preme un tasto, notificata dal main al sistema OpenGL glut prima di eseguire il glutMainLoop(); con la glutKeyboardFunc( myKeyboard ): void myKeyboard( unsigned char key, int x, int y) { switch (key) { case 'b': case 'B': /* cambia stato */ blend_colors = ! blend_colors; break; case 27 : /*ESC key-default Unix exit*/ case 'q': case 'Q': exit(0); } glutPostRedisplay(); /*ridisegna appena possib*/ } /* myKeyboardPP() */
vediamo alcuni problemi associati all'ingresso dati
questo e' il caso piu' semplice: una situazione molto frequente si verifica quando abbiamo un dispositivo di puntamento (es. un mouse) che viene usato per selezionare un oggetto: abbiamo il problema della distanza tra il punto PCM "posizione corrente del mouse" (marcato sullo schermo da una freccia) e un altro punto P(di un oggetto da selezionare) questo e' il caso piu' semplice: test di "vicinanza" equivale al test di "distanza tra due punti minore di una soglia LIMITE" : vicino = | P - PCM | < LIMITE P PCM
dist = sqrt( dx*dx + dy*dy ) se i due punti sono pensati come estremi distanza tra due punti P1(x1,y1) P2(x2,y2) se dx=x2-x1 e dy = y2-y1 allora dist = sqrt( dx*dx + dy*dy ) se i due punti sono pensati come estremi di un vettore allora dist = lunghezza del vettore P1 dist dy dx P2
double dist ( double x1, double y1, double x2, double y2) { /* distanza tra due punti, buon vecchio Pitagora */ return sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ); } /* dist */
a) test di punto vicino / non vicino ad un lato oppure un altro esempio di test di vicinanza e' il test di un punto (posizione del mouse) vicino ad un poligono (es un rettangolo); in tal caso il test puo' essere visto in due modi: a) test di punto vicino / non vicino ad un lato oppure b) test di punto esterno / interno ad un poligono vediamo il problema di punto vicino ad un lato, ovvero il problema di punto vicino ad un segmento: P s1 s2
seguono alcuni richiami di geometria: punto, vettore, prodotto scalare, retta, proiezione di vettore su retta, distanza punto retta segue poi un cenno sul test di punto esterno / interno ad un poligono
vettore = definito da due punti: VP = P2 - P1 ha una lunghezza | VP | == | P2-P1 | == dist(P2,P1) == sqrt( dx*dx + dy*dy ) un orientamento rispetto il sistema di riferimento = angolo rispetto asse x P2 angolo P1
vettore = due punti = VP = P2 - P1 vettore=somma di due vettori S = VP1+VP2=(x1+x2,y1+y2) prodotto scalare: x = VP1 . VP2 = |VP1| * | VP2 | * cos(a) se uno dei due (ad es.VP1) e' unitario, il prodotto scalare e' | VP2 | * cos(a) = pr ed e' la proiezione di VP2 su VP1 se entrambi sono unitari, il prodotto scalare e' cos(a), con a angolo tra i due vettori ... da cui cos(a) = (VP1.VP2)/(|VP1| * | VP2 |) (useremo in seguito!) P2 S VP1 a P1 VP2 VP2 a pr VP1
(x-x1)/(x2-x1)=(y-y1)/(y2-y1) retta: si definisce in molti modi: y=mx+b (y-y1)=m(x-x1) x/a + y/b = 1 (con m = -b/a) x = ax * t, y = ay * t (parametrico) (x-x1)/(x2-x1)=(y-y1)/(y2-y1) P1(x1,y1) y=b P2(x2,y2) x=a
retta individuata con due punti, P1 e P2, equazione (r = P2-P1) P = (P2-P1)*k = r * k con (x-x1) (x2-x1) -------- = --------- (y-y1) (y2-y1) (y-y1) = (x-x1)/(x2-x1)*(y2-y1) y= m * x + dy dove m = (y2-y1)/(x2-x1) e dy = (y1*x2-x1*y2)/(x2-x1) P2 r P1 P
distanza punto P -- retta P1-P2 punto P -- segmento P1-P2 P P2 d r P1
DISTANZA PUNTO P - RETTA (P1,P2) ( retta data dai due punti P1,P2 = dal vettore r (dx,dy) ) un vettore perpendicolare alla linea e' (scambia dx,dy e cambia segno per una componente): v = ( (y2-y1), -(x2-x1) ) allora la distanza punto P e retta e' data dal prodotto scalare del vettore v normale alla retta e del vettore t = P1-P che unisce P e P1, ((x1-x),(y1-y)), quindi dist = |v . r| |(x1-x)*(y2-y1)- (y1-y)*(x2-x1)| dist = ---------------------------------- sqrt((x2-x1)^2+(y2-y1)^2) P2 P v dy t r P1 dx r v
double distanza_P0_retta12 (int x0, int y0, int x1, int y1, int x2, int y2) { /*distanza di un punto P0 da una retta def.da r=P1-P2, distanza calcolata come prodotto scalare del vettore P1-P0 e del vettore v normale alla retta (a r), definita da (x1,y1) a (x2, y2), il punto e'(x0,y0) */ double lung; double vx,vy; /* v */ /* vettore normalizzato (lung==1) perpendicolare */ lung = dist(x1,y1, x2,y2); if (lung==0.0) return -999.0; /* nulla da fare...*/ /* componenti del vettore perpendicolare a P1-P2 */ vx = -(y2-y1)/lung; vy = (x2-x1)/lung; /*proiezione di P0-P1 su v perpendicolare a r e norm*/ vldis = fabs( vx*(x0-x1) + vy*(y0-y1) ); return vldis; } /* distanza_P0_retta12 */
peraltro si noti che per stabilire la vicinanza di un punto ad un lato di un poligono non e' sufficiente stabilire che il punto e' vicino alla retta che passa per il lato: p3 tutti e tre i punti p1,p2,p3 sono vicini alla retta che passa per il lato A-B, nel senso che la distanza punto-retta e' piccola per p1,p2,p3; ma solo il punto p2 e' vicino al lato; si dovra' quindi tenere anche conto della posizione rispetto il segmento A-B p2 p1 B A
calcolo la proiezione con segno del vettore A-> pk , sul segm. vediamo meglio: calcolo la proiezione con segno del vettore A-> pk , sul segm. A-B, data dal prodotto scalare di r (unitario) (A-B)/|A-B| per A-> pk si avra' per i tre punti : ( p1-A ) . r < zero, zero < ( p2-A) . r < lungh(A-B), ( p3-A) . r > lungh(A-B) e quindi p2 e' vicino al lato A-B, p1 e p3 non lo sono; dist=linedist( x,y, xa,ya,xb,yb ); onsegm=vertonseg( if( (d<limit) && (onsegment) ) { allora..e'.vicino..} p3 p2 r p1 B A
double vlineproj (int x0, int y0, int x1, int y1, int x2, int y2) { /* calcola la proiezione del vettore P1-P0 sulla retta definita dai due punti P1 P2, con segno; usata poi in vertonseg qui sotto */ double vlpro, lung; double rx,ry; /* r = (P2- P1),unitario */ lung = dist(x1,y1,x2,y2); if (lung==0.0) return -999.0; rx=(x2-x1)/lung; ry=(y2-y1)/lung; vlpro=rx*(x0-x1)+ry*(y0-y1); return vlpro ; } /* vlineproj */ bool vertonseg(int x0, int y0, int x1, int y1, int x2, int y2) { /* vale true 1 se la proiezione di t sta sul segmento */ double prz, lung; lung = dist(x1,y1,x2,y2); if (lung==0.0) return -999.0; prz = vlineproj(x0,y0,x1,y1,x2,y2) ; if (prz >=0 && l<=lung) return 1; else return 0; } /* vertonseg */
nella figura a fianco il cursore (quadratino verde) e' posizionato vicino al segmento diagonale a destra, il programma riconosce (con un controllo di vicinanza del cursore a tutti i segmenti dell' immagine) quale segmento e' vicino al cursore, e questo appare evidenziato piu' chiaro,
cenno sul test di punto esterno / interno ad un poligono Pinterno Pesterno
appartenenza di un punto ad un poligono: test di punto P dentro/fuori di un poligono: esistono vari metodi per poligoni generali, es: regola dell' attraversamento pari/dispari : dato P, scelgo punto Q lontano dal poligono, poi conto quante volte si attra- versa un lato del poligono percorrendo tutti i punti da P a Q: P e' dentro se dispari, fuori se pari: in figura sopra, da A (oppure B) a Q sono due (o 4)attraversamenti -> A e B sono fuori, da C a S (oppure T) sono uno (o 3) attraversamenti -> C e' dentro S Q A T C B Q
un altro metodo: test di punto P dentro/fuori regola dell'attraversamento orientato: si conta quante volte un seg- mento P-Q (Q lontano) viene attraversato dai lati orientati del poligono, contando +1 se il lato attraversa P-Q da dest a sinistra, e -1 se viceversa; alla fine, se il conto e' non zero, allora P sta dentro altrimenti sta fuori; A D C P Q B
un altro problema da notare: test di vicinanza - ma in che coordinate? quando muovo il mouse, spesso il sistema fornisce al programma le coordinate schermo della posizione corrente del mouse; se si vuole ottenere le coordinate del "mondo utente" o "mondo oggetto" allora si deve fare a ritroso la trasformazione che normalmente viene fatta dalle coordinate oggetto alle coordinate schermo: model -> object world -> viewport (almeno 2 matrici di trasformazione, "MODEL_VIEW" e "PROJECTION", la seconda e' predisposta ad es. dalle procedure di inizializzazione...
... glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0.0, MyWidth, 0.0, MyHeight ); // <<<<<<< glViewport(0, 0, MyWidth,MyHeight); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glTranslatef(MyBariX,MyBariY,0.0); // <<<<<<< glRotatef(MyAlfa, 0.0, 0.0, 1.0); // <<<<<<< glTranslatef(-MyBariX,-MyBariY,0.0); glTranslatef(MyBariX,MyBariY,0.0); glScalef( MyScala, MyScala, 1.0); // <<<<<<< glTranslatef(-(MyBariX),-(MyBariY),0.0); drawMyObject(); le coordinate (x,y) dell'oggetto sono sempre trasformate attraverso la pipeline di visualizzazione in coordinate schermo... ad es. (0.2,0.01) in un mondo 0.0..1.0, oppure (5700.0,-4000.0) in un mondo (-10000.. +10000)
viceversa, se voglio poi nel programma eseguire un test di vicinanza, devo ri-passare da coordinate viewport del mouse a coordinate del mondo oggetto correnti: ... getRealXY ( MyPassMousePosX, MyPassMousePosY, X, Y ); if( dist(X,Y, MyBariX,MyBariY) < D_NEAR ) { we are near } /* coord.viewport myX,myY => coord.oggetto myXREAL,myYREAL */ void getRealXY(int myX,int myY, int& myXREAL,int& myYREAL) { GLint viewport[4]; GLdouble mvmatrix[16], projmatrix[16]; GLdouble wx,wy,wz; /* current window coordin. to current my object coord */ glGetIntegerv (GL_VIEWPORT, viewport); glGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix); glGetDoublev (GL_PROJECTION_MATRIX, projmatrix); gluUnProject ( (GLdouble) myX, (GLdouble) myY, 0.0, mvmatrix, projmatrix, viewport, &wx, &wy, &wz ); myXREAL = (GLint) wx; /* un-project== un-transform */ myYREAL = (GLint) wy; } /* getRealXY */
Riempimento (filling): un procedimento intuitivo e': void fillR4(int x,int y, tcol bordo, tcol fillcolore){ tcol corrente = getPixel(x,y); /* colore del pix corrente */ if( corrente != colorebordo && corrente != fillcolore ) { setPixel(x,y); /* allora setColore/fillcolore; poi vedi vicini: */ fillR4(x+1, y, bordo, fillcolore ); /* destra */ fillR4(x-1, y, bordo, fillcolore ); /* sinistra */ fillR4(x, y+1, bordo, fillcolore ); /* su */ fillR4(x, y-1, bordo, fillcolore ); /* giu' */ } /* if */ } /* fillR4 */ procedimento ricorsivo per visita dei 4 pixel adiacenti (ma costa molta memoria per lo stack); meglio: procedere per linee orizzontali verso sin e verso dest, e poi procedere alle due linee sopra/sotto, e ripetere separatamente per le due linee
esempio di uso di procedura di fill (a fianco un es.di esecuzione, partendo dai punti A e B) /*fillColor =1,interiorColor=0 */ void FloodFill4 (int x, int y) { if( x<0 || /* controlla */ y<0 || /* se out! */ x>=RasterSizeX || y>=RasterSizeY ) return; /*check se colore interno*/ if( getPixel( x,y ) == 0 ) { DrawMode = DRAW_OR; setPixel( x,y, 1 ); FloodFill4 ( x+1, y ); FloodFill4 ( x-1, y ); FloodFill4 ( x, y-1); FloodFill4 ( x, y+1); } /* if */ } /* FloodFill4 */ A B
A) PIU' FINESTRE: il sistema glut permette di avere piu' finestre; esse sono create con la glutCreateWindow, che fornisce in uscita l'identificatore (un numero intero da 1 in poi) ad ogni finestra sono associate delle proc di gestione evento: ... myWinID1=glutCreateWindow("OGL5E_WWW_111"); glutSetWindow(myWinID1); myCurrentWin = myWinID1; glutDisplayFunc ( myDisplay1 ); myWinID2 = glutCreateWindow("OGL5E_WWW_222"); glutSetWindow(myWinID2); myCurrentWin = myWinID2; glutDisplayFunc ( myDisplay2 ); ... poi posso attivare la prima finestra: myCurrentWin = myWinID1; /* NOTA !! glutSetWindow(myWinID1);
piu' finestre ... void myOpen2Windows( int argc, char* argv[ ] ) { glutInit(&argc, argv); /*GLUT initialization here comune :*/ glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); /* for the first window: */ glutInitWindowPosition( Xwp1, Ywp1 ); glutInitWindowSize( Xww1, Ywh1 ); myWinID1 = glutCreateWindow("EGD4_D uno"); /* NOTA IL N.RO FINESTRA myWinID1 */ glutSetWindow(myWinID1); myCurrentWin = myWinID1; glutDisplayFunc ( myDis1 ); ... glutReshapeFunc ( myRes1 ); glutKeyboardFunc( myKeyb1 ); /* now the second window: */ glutInitWindowPosition( Xwp2, Xwp2 ); glutInitWindowSize( Xww2, Ywh2 ); myWinID2 = glutCreateWindow("EGD_4D due "); glutSetWindow(myWinID2); myCurrentWin = myWinID2; glutDisplayFunc ( myDis2 ); glutReshapeFunc ( myRes2 ); glutKeyboardFunc ( myKeyb2 ); glutSetWindow(myWinID1); myCurrentWin = myWinID1; } /* myOpenWindow */
piu' finestre ... void myOpen2Windows( int argc, char* argv[ ] ) { glutInit(&argc, argv); /* parte comune */ glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); /* W1 */ glutInitWindowPosition( ... ); glutInitWindowSize( ... ); myWinID1=glutCreateWindow("Titolo"); glutSetWindow(myWinID1); myCurrentWin = myWinID1; glutDisplayFunc ( myD1 ); glutReshapeFunc ( myR1 ); glutKeyboardFunc ( myK ); /* W2 */ myWinID2 = glutCreateWindow("OGL5E_WWW_222"); glutSetWindow(myWinID2); myCurrentWin = myWinID2; glutDisplayFunc ( myD2 ); glutReshapeFunc ( myR2 ); glutKeyboardFunc ( myK ); /* NOTA : cambia finestra attiva: */ glutSetWindow(myWinID1); myCurrentWin = myWinID1; } /* myOpenWindow */
piu' finestre in figura lo schermo dopo la creazione di due finestre, la seconda finestra e' attiva
piu' finestre in figura lo schermo dopo la creazione di due finestre, qui e' attiva la prima finestra:
piu' finestre void myKeyboard(unsigned char key, int x, int y) { switch (key) { case '1': glutSetWindow( myWinID1 ); glutPopWindow(); break; case '2': glutSetWindow( myWinID2 ); case '3': if (myWinID3==0) myMakeNewWin(); glutSetWindow( myWinID3 ); case 'k': if( myWinID3 !=0 ) { glutDestroyWindow(myWinID3); myWinID3 = 0; } break; } } // switch e myKeyboard ...
piu' finestre la terza finestra e' creata dopo, procedura tipo: void myMakeNewWin() { glutInitWindowPosition( 50,300 ); glutInitWindowSize( 400,400 ); myWinID3 = glutCreateWindow("EGD4_D tre "); myCurrentWin = myWinID3; glutDisplayFunc ( myDisplay3 ); glutReshapeFunc ( myReshape3 ); glutKeyboardFunc ( myKeyboard ); glutSetWindow(myWinID3); glutPopWindow(); } // myMakeNewWin
piu' finestre in figura dopo la creazione della terza finestra
piu' finestre durante l'esecuzione si puo' cambiare posizione e dimensione della finestra; qui, con il tasto spazio, si agisce sulla win 2: void myKeyboard(unsigned char key, int x, int y) { switch (key) { .... case ' ': if( myWinID2 !=0 ) { /* se esiste la finestra 2 ...*/ Xmaxw2=300+rand()%200; Ymaxw2=200+rand()%200; Xpos_win2= rand()%500; Ypos_win2= rand()%400; glutSetWindow( myWinID2 ); glutPopWindow(); glutPositionWindow(Xpos_win2, Ypos_win2); glutReshapeWindow(Xmax_win2, Ymax_win2); glViewport( 0, 0, Xmax_win2, Ymax_win2 ); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D( ... ); /* y=0 on bottom */ } } } // myKeyboard
piu' viewport void myDisplay3(void) { float x1,y1,x2,y2,dx,dy,r,g,b; // in una finestra si possono int k; int wid, hi; // avere due viewport (o piu'): wid = glutGet(GLUT_WINDOW_WIDTH); hi = glutGet(GLUT_WINDOW_HEIGHT); myClear(); /* clear current window (all!) * / glViewport(0, 0, wid/2, hi); /* left half window !! * / glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D( 0.0, 1.0 /* x1,x2 */ , 0.0, 1.0 ); /* y=0 on bottom */ myTriFull(-0.1, 1.1, 1.1, 0.9, 0.5, -0.1, 0, 1, 1 ); /* cyano */ glViewport(wid/2, 0, wid, hi); /* right half window */ gluOrtho2D( 0.0, 1.0, 0.0, 1.0 ); /* y=0 on bottom * / myTriFull(-0.1, 0.1, 1.1, -0.1, 0.5, 1.2, 1, 0,1 ); /*magenta*/ glFlush(); } /* myDisplay3 * /
terza finestra con due viewport glViewport(0, 0, wid/2, hi); /* left half window !! * / disegna in viewport a sinistra ... glViewport(wid/2, 0, wid, hi); /* right half window */ disegna in viewport a destra ...
piu' finestre, piu' viewport, ...
FINE EGD15_interaz2D