Interazione utenteprogramma
(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
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
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 )
P1
dy
dist
dx
se i due punti sono pensati come estremi
di un vettore allora
dist = lunghezza del vettore
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 */
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:
s1
P
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
P2
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
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)
P2
S
VP1
P1
a
VP2
se uno dei due (ad es.VP1) e' unitario, il prodotto scalare
e' | VP2 | * cos(a) = pr
VP2
ed e' la proiezione di VP2 su VP1
se entrambi sono unitari,
a
il prodotto scalare e' cos(a),
con a angolo tra i due vettori
pr
... da cui
cos(a) = (VP1.VP2)/(|VP1| * | VP2 |)
(useremo in seguito!)
VP1
retta: si definisce
in molti modi:
y=mx+b
(y-y1)=m(x-x1)
x/a + y/b = 1
(con m = -b/a)
y=b
x = ax * t,
y = ay * t (parametrico)
(x-x1)/(x2-x1)=(y-y1)/(y2-y1)
P1(x1,y1)
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
P
punto P -segmento P1-P2
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
P2
per una componente):
P
v
v = ( (y2-y1), -(x2-x1) )
dy
allora la distanza punto P e retta
t
r
e' data dal prodotto scalare del
P1
dx
vettore v normale alla retta e del
vettore t = P1-P che unisce P e P1,
((x1-x),(y1-y)), quindi dist = |v . r|
r
|(x1-x)*(y2-y1)- (y1-y)*(x2-x1)|
dist = ---------------------------------v
sqrt((x2-x1)^2+(y2-y1)^2)
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
p2
p1
B
A
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
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 :
dist=linedist(
x,y, xa,ya,xb,yb );
onsegm=vertonseg(
x,y, xa,ya,xb,yb );
if( (d<limit) &&
(onsegment) ) {
allora..e'.vicino..}
( p1-A ) . r < zero,
zero < ( p2-A) . r < lungh(A-B),
( p3-A) . r > lungh(A-B)
p3
p2
r
p1
e quindi p2 e' vicino
al lato A-B,
p1 e p3 non lo sono;
A
B
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:
S
Q
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 attraversa 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
A
C
B
T
Q
un altro metodo:
test di punto P dentro/fuori
regola dell'attraversamento
orientato:
si conta quante volte un segmento 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();
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 ripassare 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 */
...
finestre ...
void myOpen2Windows( int argc, char* argv[ ] ) piu'
{
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 ...
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 */
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 * /
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
Scarica

EGD15_INTERAZ2D