CANVAS Un canvas e' una sottofinestra di una form gestita da X direttamente. Abbiamo detto che XForms si frappone tra Xlib e il programma applicativo rendendo Xlib trasparente a quest'ultimo. Si puo' pensare la canvas come un "buco" in XForms attraverso il quale il programma applicativo puo' vedere Xlib direttamente. Una canvas e' una finestra X. Vi sono funzioni per settare le caratteristiche di questa finestra (colormap, profondita', ecc.). Una volta che la form contenente la canvas e' stata mostrata, posso ottenere l'identificatore della finestra X che realizza la canvas e usare questa come argomento di routine fornite da Xlib. Noi non gestiremo una canvas direttamente con Xlib, ma con OpenGL. Perche' Vi possa operare OpenGL, una canvas (ovvero la finestra X che la realizza) deve avere certe caratteristiche. XForms fornisce una routine apposita che aggiunge ad una form una canvas adatta ad essere gestita da OpenGL. Apertura di una canvas OpenGL: FL_OBJECT * fl_add_glcanvas(,x,y,w,h); = GL_NORMAL_CANVAS (in futuro anche GL_SCROLLED_CANVAS) Per default crea una canvas costituita da una finestra X in RGBA mode (colori specificati come terne di valori rosso/verde/blu), dotata di depth buffer (per eliminazione superfici nascoste) e double buffer (cioe' ha un front buffer = quello visualizzato sullo schermo, e un back buffer = in memoria ma non visualizzato; io disegno nel back buffer e poi con un'istruzione scambio i buffer in modo da visualizzare istantaneamente quanto disegnato; invece nelle finestre con single buffer disegno direttamente nel front buffer, e siccome disegnare richiede un certo tempo, si ha un effetto visivo sgradevole). Per cambiare gli attributi con cui la finestra viene creata, chiamare, prima di fl_add_glcanvas, la funzione fl_set_glcanvas_defaults. Una volta aperta una canvas OpenGL, vi si puo' disegnare dentro usando le funzioni di OpenGL. GRAFICA IN OPENGL - primitive grafiche - attributi - trasformazioni geometriche PRIMITIVE Primitive = oggetti geometrici specificati da lista di vertici e "tipo di primitiva", che stabilisce come tali vertici devono essere interpretati per formare l'oggetto Tipi di primitive: GL_POINTS --> l'oggetto e' un insieme di punti, ogni vertice e' preso singolarmente GL_LINES --> l'oggetto e' un insieme di segmenti, ogni due vertici ho un segmento GL_LINE_STRIP --> spezzata poligonale, i vertici della lista sono connessi in catena GL_LINE_LOOP --> spezzata poligonale chiusa, come sopra ma l'ultimo vertice e' connesso al primo GL_TRIANGLES --> insieme di triangoli, ogni tre vertici ho un triangolo GL_TRIANGLE_STRIP --> striscia di triangoli, i primi tre vertici formano il primo triangolo, ogni nuovo vertice forma un triangolo con i due vertici immediatamente precedenti GL_TRIANGLE_FAN --> ventaglio di triangoli, i primi tre vertici formano il primo triangolo, ogni nuovo vertice forma un triangolo con il vertice precedente ed il primo vertice della lista GL_QUADS --> insieme di quadrilateri, ogni 4 vertici un quadrilatero GL_QUAD_STRIP --> striscia di quadrilateri, i primi 4 vertici formano il primo quadrilatero, ogni due nuovi vertici formano un quadrilatero coi precedenti due GL_POLYGON --> poligono, la lista di vertici specifica il contorno del poligono Come si definisce una primitiva: glBegin(); glVertex(...) per ogni vertice che forma la primitiva glEnd(); La funzione glVertex ha varie forme per permettere di utilizzare argomenti di vario tipo, es: glvertex2f(); glvertex2fv(); per vertici specificati in 2D (implicitamente z=0), glvertex3f(); glvertex3fv(); per vertici specificati in 3D. ATTRIBUTI Attributi = parametri dello stato interno di OpenGL che influenzano il modo in cui le primitive sono rese (es. colore, spessore di linea...). Valori degli attributi fanno parte dello stato corrente. Assegnare il valore di un attributo influisce sul modo in cui sono disegnate tutte le primitive da quel momento in poi. Se voglio modificare il valore di un attributo momentaneamente per disegnare una certa primitiva, salvando il valore precedente per poi ripristinarlo, uso glPushAttrib e glPopAttrib. Esempi: glColor3f(1.0, 0.0, 0.0); /* assegna colore corrente = rosso */ glBegin(GL_TRIANGLES); glVertex2f(-1.0,0.0); glVertex2f(1.0,0.0); glVertex2f(0.0,1.0); glEnd(); ---> disegna un triangolo in rosso (anche tutto cio' che disegno in seguito sara' rosso, se non cambio colore nel frattempo) glPushAttrib(GL_CURRENT_BIT); glColor3f(1.0, 0.0, 0.0); /* assegna colore corrente = rosso */ glBegin(GL_TRIANGLES); glVertex2f(-1.0,0.0); glVertex2f(1.0,0.0); glVertex2f(0.0,1.0); glEnd(); glPopAttrib(); ---> disegna un triangolo in rosso e poi ripristina il colore precedente (cio' che disegno dopo sara' del colore che avevo prima di assegnare il rosso) Alcuni attributi sono "vertice per vertice" e possono essere cambiati all'interno di una coppia glBegin/glEnd. Uno di questi e' il colore. Se i vari vertici di una primitiva hanno colori diversi tra loro, il colore di un punto interno alla primitiva viene interpolato ed assume un valore ottenuto come media dei valori nei vertici, pesata rispetto alla distanza del punti dai vertici. Esempio: glBegin(GL_TRIANGLES); glColor3f(1.0, 0.0, 0.0); /* assegna colore corrente = rosso */ glVertex2f(-1.0,0.0); glColor3f(0.0, 1.0, 0.0); /* assegna colore corrente = verde */ glVertex2f(1.0,0.0); glColor3f(0.0, 0.0, 1.0); /* assegna colore corrente = blu */ glVertex2f(0.0,1.0); glEnd(); ---> disegna un triangolo con colore rosso, verde, blu nei tre vertici, e sfumato di conseguenza all'interno Altri attributi agiscono su una primitiva nel suo complesso, e devono essere cambiati fuori da glBegin/glEnd. Tra questi: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL oppure GL_LINE oppure GL_POINT); --> assegna il modo in cui sono disegnate le primitive con area (triangoli, strisce e ventagli di triangoli, poligoni): piene, solo i lati di contorno, o solo i vertici di contorno glPointSize(); --> assegna dimensione punti glLineWidth(); --> assegna spessore linee TRASFORMAZIONI Le primitive OpenGL sono specificate come oggetti solidi 3D immersi in un loro proprio sistema di coordinate 3D. Queste primitive saranno visualizzate come immagine 2D nel sistema di coordinate 2D della canvas. Il passaggio da 3D a 2D viene fatto da OpenGL attraverso una serie di trasformazioni di coordinate. 1) Trasformazioni di modellazione e di vista. Per comporre la scena (collocare i vari oggetti, che possono essere definiti in un sistema di riferimento locale, inserendoli in un sistema di riferimento comune), e per porre la scena davanti alla telecamera 2) Trasformazioni di proiezione. Definiscono il tipo di proiezione (parallela o prospettica) e il volume di vista (quanta parte di spazio viene inquadrata dalla telecamera) 3) Trasformazioni di viewport. Per mappare il tutto nella finestra 2D (= la glcanvas nel nostro caso), o in una sottoparte di essa. Per il momento consideriamo una situazione semplificata: - Come trasformazioni 2) e 3) usiamo quelle di default, ovvero: - proiezione e' parallela, punto di vista sta sull'asse z e guarda in giu' lungo l'asse z - volume di vista e' un cubo con x,y,z tra -1 e +1 - il tutto viene mappato nell'intera glcanvas (cio' implica una deformazione se la canvas non e' quadrata) - Come trasformazioni 1), usiamo una scena composta di oggetti gia' definiti in uno spazio di riferimento comune, e contenuti nel volume di vista di default (cioe' il cubo -1 +1) In tal modo non e' necessario definire trasformazioni. Vedremo le trasformazioni in seguito. DISEGNAMENTO DELLA SCENA 1) abilitare eliminazione superfici nascoste (non di default in OpenGL): glEnable(GL_DEPTH_TEST); 2) "pulire il foglio" prima di mettersi a disegnare: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 3) disegnare: definizioni di primitive tra glBegin/glEnd, assegnazioni di attributi (il disegnamento avviene nel back buffer mentre nel front buffer e' ancora visualizzata l'immagine precedente) 4) scambio dei due buffer: glXSwapBuffers(fl_display, fl_get_canvas_id()); a questo punto la nuova immagine appare sullo schermo INTERAZIONE TRA OPENGL E XFORMS (E XLIB) I comandi di disegno OpenGL vanno inviati ogni qual volta la canvas necessita di ridisegnamento (per esempio quando viene visualizzata per la prima volta, quando viene rivisualizzata dopo essere stata coperta da un'altra finestra, ecc.). Infatti, se XForms si occupa di ridisegnare i suoi oggetti in questi casi, non lo fa per le canvas (per definizione, una canvas e' una sottofinestra che "sfugge" alla gestione di XForms). Per ottenere il redisegmanento automatico del contenuto della canvas, devo mettere i comandi necessari all'interno di una funzione che assegno come "event-handler" della canvas, collegata all'evento "Expose". Un event-handler e' una funzione chiamata da XForms automaticamente quando si verifica un certo evento X all'interno dell' oggetto canvas. Expose e' un particolare tipo di evento X, che viene generato da X quando una finestra (in questo caso la canvas) diventa visibile. Differenza tra event-handler di una canvas e callback degli altri oggetti Xforms: - ogni tipo di oggetto XForms (non canvas) e' sensibile a un certo insieme predefinito di eventi per i quali XForms prevede una callback. Gli eventi X sono filtrati da XForms, e non devono essere gestiti dal programmatore. - per una canvas, e' il programmatore che deve stabilire quali eventi X vuole gestire, e per questi eventi definisce un handler. Un event handler e' una funzione del tipo: int f(FL_OBJECT *ob, Window win, int w,int h, XEvent *ev, void* user_data); dove gli argomenti (generati automaticamente da XForms quando chiama si verifica l'evento), sono: - ob = l'oggetto interessato (la canvas) - win = la finestra X che realizza l'oggetto (necessaria per agire su di essa con le funzioni Xlib, visto che la canvas e' gestita con Xlib) - w,h = dimensioni in pixel della finestra - ev = evento X (contenente parametri al suo interno) - user_data = argomento passato dal programma quando ha associato la funzione all'evento (ved. sotto) e si associa con: void fl_add_canvas_handler(, , f, user_data); In teoria, bisogna sapere programmare con Xlib per scrivere un event-handler (chiamare sulla finestra win opportune funzioni Xlib a seconda dei valori dei parametri portati dall'evento ev). In pratica, siccome noi stiamo usando una glcanvas, non usiamo Xlib ma OpenGL per scrivere l'event-handler. Il nostro event-handler per l'evento Expose conterra' le funzioni OpenGL necessarie a ridisegnare la scena in OpenGL: int canvas_expose(FL_OBJECT *ob, Window win, int width, int height, XEvent *xev, void *ud) { drawScene(ob); /* ob punta alla canvas */ return 0; } /* dove draw scene contiene i comandi OpenGL per disegnare la scena */ Inoltre e' utile definire anche un event handler per l'evento ConfigureNotify (evento generato quando le dimensioni della finestra che realizza la canvas vengono modificate). Se l'evento ConfigureNotify non e' gestito, OpenGL continua a utilizzare come dimensioni di viewport quelle precedenti, e la scena appare o in un piccolo sottopezzo della finestra attuale (se le dimensioni sono aumentate) o appare tagliata (se le dimensioni sono state ridotte). L'event-handler per ConfigureNotify riassegna le dimensioni della viewport di OpenGL uguali alle nuove dimensioni della canvas. int canvas_reshape(FL_OBJECT *ob, Window win, int width, int height, XEvent *xev, void *ud) { glViewport(0, 0, (GLint)width, (GLint)height); /* assegna viewport */ drawScene(ob); /* ridisegna scena */ return 0; }