Interazione utente- programma (dal punto di vista dell'OpenGL)

Post on 01-May-2015

215 views 3 download

Transcript of Interazione utente- programma (dal punto di vista dell'OpenGL)

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 ditipo indirizzo di procedura, e myKeyboard, myArrowkeys, myReshape, sono procedure (che scrivo io) da eseguirein 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

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)

PCMP

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

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

P2dx

dy dist

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

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:

s2s1 P

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

P1

P2

angolo

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) = pred 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!)

a

P1

P2

VP2

SVP1

VP1

VP2

pr

a

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)

y=b

x=a

P1(x1,y1)

P2(x2,y2)

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)

P1

P2

P

r

distanza

punto P --

retta P1-P2

punto P --

segmento P1-P2

P1

P2P

d

r

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)

P1

P2P v

dx

dy

v

r

r

t

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:

p2

p3

p1

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

BA

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;

p2

p3

p1 BA

r

dist=linedist( x,y, xa,ya,xb,yb );onsegm=vertonseg( x,y, xa,ya,xb,yb );if( (d<limit) && (onsegment) ) { allora..e'.vicino..}

double vlineproj (int x0, int y0, int x1, int y1, int x2, int y2) {/* calcola la proiezione del vettore P1-P0 sulla retta definita daidue 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

Pesterno

Pinterno

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 puntida 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

Q

AC

T

S

B Q

un altro metodo:

test di punto P dentro/fuoriregola dell'attraversamento orientato:si conta quante volte un seg-mento P-Q (Q lontano) vieneattraversato dai lati orientatidel poligono, contando +1 seil lato attraversa P-Q da desta sinistra, e -1 se viceversa;alla fine, se il conto e' non zero, allora P sta dentro altrimenti sta fuori;

Q

P

A

B

C

D

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(); glLoadIdentity();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 */ glutInitWindowPosition( ... ); glutInitWindowSize( ... ); 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 );

glutPopWindow(); break;

case '3': if (myWinID3==0) myMakeNewWin();

glutSetWindow( myWinID3 );

glutPopWindow(); break;

case 'k': if( myWinID3 !=0 )

{ glutDestroyWindow(myWinID3);

myWinID3 = 0; }

break;

} } // switch e myKeyboard ...

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

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

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

glMatrixMode(GL_PROJECTION); glLoadIdentity();

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

piu' viewport

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