==================================================================== A.A. 1997-98 ESEMPIO DI SCRITTO DI INTERFACCE GRAFICHE ==================================================================== T E S T O --------- SCOPO Progettare un'interfaccia per un'applicazione che visualizza insiemi di dati volumetrici mediante isosuperfici. L'interfaccia deve essere progettata usando il toolkit XForms, e per la parte grafica la libreria OpenGL. DESCRIZIONE DELL'APPLICAZIONE Un dataset volumetrico e' una funzione che associa ad ogni punto (x,y,z) di una certa area dello spazio un valore di campo v. Esempio: temperatura nel volume interno di un altoforno. Un'isosuperficie e' il luogo dei punti dove la la funzione assume lo stesso valore (es. luogo dei punti ad una data temperatura). Nel programma applicativo, ciascuna isosuperficie e' restituita sotto forma di un insieme di faccette triangolari. Si tratta quindi di visualizzare insiemi di triangoli. Dal punto di vista dell'interfaccia il programma applicativo e' una scatola nera che esporta le seguenti procedure (che possono essere invocate dall'utente tramite l'interfaccia, o che servono all'interfaccia per realizzare operazioni invocate dall'utente, come quelle di disegno): loadDataset(FILE * fd); legge il dataset volumetrico dal file indicato (e lo memorizza in qualche struttura dati interna del programma). A livello di interfaccia non interessa conoscere ne' il formato di file ne' la struttura dati interna che memorizza il dataset. void findIsosurf(float value); costruisce l'isosuperficie corrispondente al valore dato (e la memorizza in qualche struttura dati interna del programma, che all'interfaccia non interessa conoscere). void getBbox(float * minX, float *maxX, float *minY, float *maxY, float * minZ, float *maxZ); ritorna le coordinate estreme dell'isosuperficie costruita (errore se non e' ancora stata costruita una isosuperficie) int numTriangles(void); ritorna il numero di triangoli componenti l'isosuperficie costruita (zero se non e' stata ancora costruita nessuna isosuperficie) void theTriangle(int i, float *x1, float *y1, float *z1, float *x2, float *y2, float *z2, float *x3, float *y3, float *z3, float *nx, float *ny, float *nz); restituisce in (x1,y1,z1), (x2,y2,z2), (x3,y3,z3) i tre vertici dell'i-esimo triangolo, e in (nx,ny,nz) la sua normale (i deve essere in [0,numTriangles()-1], altrimenti errore) void unloadDataset(void); "scarica" il dataset letto, e l'eventuale isosuperficie costruita su di esso, ripulendo le strutture interne del programma applicativo e preparandolo al caricamento eventuale di un altro dataset. L'interfaccia deve fornire le seguenti funzionalita': - leggere un dataset da un file di cui l'utente fornisce il nome - acquisire un valore dall'utente e costruire l'isosuperficie corrispondente a tale valore - visualizzare tale superficie - assegnare la modalita' di visualizzazione della superficie tra wireframe e solid (flat shading) - ruotare la superficie visualizzata attorno ai tre assi coordinati - assegnare il colore con cui si visualizza la superficie - ritornare in condizioni iniziali scaricando il dataset e la superficie correnti (se esistono) CHE COSA SI CHIEDE - Organizzazione dell'interfaccia: disegno schematico, spiegazione dei vari elementi e della loro funzione - Comportamento dell'interfaccia: condizioni iniziali e passaggi di stato provocati dalle interazioni con l'utente (es. dispositivi prima disattivati che diventano attivi in seguito a certe azioni, ecc.) - Struttura generale del programma per punti o in pseudocodice da cui emergano: - gli elementi dell'interfaccia (definizione di forms e oggetti che compongono l'interfaccia) - le funzioni associate a tali elementi (definizione di callback/ event handler, e loro associazione agli elementi di interfaccia) - la gestione della parte grafica (primitive OpenGL e trasformazioni geometriche) Si consiglia di presentare un'interfaccia semplice e minimale, e alla fine (se avete il tempo) discutere eventuali raffinamenti. E S E M P I O D I S V O L G I M E N T O ------------------------------------------- **Descrizione generale dell'interfaccia** +------------------------------------------------------------------+ | Quit Load Isosurf | | | | +--------------------------------------------+ +------------+ | | | | | | | | | | +------------+ | | | | | | | | +--+ +--+ +--+ | | | | | | | | | | | | | | |[]| | | | | | | | | | | | | |[]| | | | AREA VISUALIZZAZIONE 3D | | | | | | | | | | | | | |[]| | | | | | | | | | | | | | | | | | | | | | | | | | | +--+ +--+ +--+ | | | | R G B | | | | | | +--------------------------------------------+ | | +------------------------------------------+ | | rot x | [] | | | +------------------------------------------+ | | +------------------------------------------+ | | rot y | [] | | | +------------------------------------------+ | | +------------------------------------------+ | | rot z | [] | | | +------------------------------------------+ | +------------------------------------------------------------------+ L'interfaccia e' costituita da un'unica finestra cosi' organizzata - in alto, i bottoni quit, load, isosurf, che consentono rispettivamente: l'uscita dal programma, il caricamento di un dataset, e il calcolo di un'isosuperficie. I primi due sono sempre attivi, il terzo e' attivo solo se e' stato caricato un dataset. - al centro, un'area di disegno (canvas) dove viene visualizzata l'eventuale isosuperficie presente in memoria. - sotto la canvas, tre slider consentono di ruotare l'isosuperficie visualizzata attorno ai tre assi. Questi sono attivi solo se vi e' una superficie visualizzata. - a destra in alto, un box che visualizza il valore sul quale e' stata costruita l'isosuperficie corrente (vuoto se non vi e' alcuna isosuperficie) - a destra, tre slider per l'assegnazione delle componenti RGB del colore della superficie visualizzata. Questi sono attivi solo se vi e' una superficie visualizzata. La pressione del bottone load invoca l'apparizione di una finestra ausiliaria, che richiede il nome del file da caricare. La pressione del bottone isosurf invoca l'apparizione di una finestra ausiliaria, che richiede il valore sul quale calcolare l'isosuperficie. Il caricamento di un dataset attiva il bottone isosurf. Il calcolo di un'isosuperficie attiva il disegnamento della stessa nel canvas, e gli slider preposti alle rotazioni e al cambio del colore. Non vi sono dispositivi per scaricare un dataset: un dataset (con la sua eventuale isosuperfcie) viene scaricato all'atto di caricarne un altro. Analogamente, un'isosuperficie viene cancallata solo all'atto di calcolarne un'altra. **Definizione degli elementi di interfaccia** Abbiamo due form a top level: la form principale e la form secondaria che richiede il valore dell'isosuperficie da calcolare. Non e' necessario trattare esplicitamente la form ausiliaria per la richiesta del nome file perche' questa viene realizzata mediante il dispositivo di file selector di XForms. Le seguenti variabili sono definite a top level: FL_FORM * main_form; /* form principale */ FL_FORM * value_form; /* form secondaria per acquisire il valore v */ FL_OBJECT * canvas; /* canvas per la visualizzazione OpenGL */ FL_OBJECT * isosurf_but; /* bottone per costruire isosuperficie */ FL_OBJECT * slider_X, * slider_Y, * slider_Z; /* slider per rotazioni */ FL_OBJECT * slider_R, * slider_G, * slider_B; /* slider per colore */ FL_OBJECT * box; /* box per visualizzare valore v corrente */ La form principale e' costruita cosi': void CreateMainForm(void) { FL_OBJECT * ob; main_form = fl_bgn_form(...) /* i tre bottoni in alto e loro callback */ ob = fl_add_button(FL_NORMAL_BUTTON,...,"quit"); fl_set_object_callback(ob, QuitCallback,0); ob = fl_add_button(FL_NORMAL_BUTTON,...,"load"); fl_set_object_callback(ob, LoadCallback,0); isosurf_but = fl_add_button(FL_NORMAL_BUTTON,...,"isosurf"); fl_set_object_callback(isosurf_but, IsosurfCallback,0); /* il canvas e relativi handler per la gestione degli eventi X di espozizione e ridiemensionamento della finestra */ canvas = fl_add_object(FL_NORMAL_CANVAS,...,""); fl_set_canvas_handler(canvas, Expose, RedrawCanvas); fl_set_canvas_handler(canvas, ConfigureNotify, ReshapeCanvas); /* i tre slider per le rotazioni, condividono la stessa callback con parametro 0,1,2 rispettivamente */ slider_X = fl_add_slider(FL_HOR_SLIDER,...,"rot x"); fl_set_object_callback(slider_X, RotCallback,0); fl_set_slider_bounds(slider_X, 0, 360); fl_set_slider_value(slider_X, 0); ... analogo per slider_Y e slider_Z ... /* il box che visualizza il valore corrente (inizialmente vuoto) */ box = fl_add_box(FL_NORMAL_BOX,...,""); /* i tre slider per il cambio di colore */ ... analogo ai precedenti, soltanto FL_VERT_SLIDER, e condividono la callback ColCallback con parametri 0,1,2 */ } La form secondaria contiene solo un input field per l'immissione del valore di campo, se ne omette la definizione. Il programma inizialmente visualizza solo la form principale, con tutti gli oggetti inattivi a parte i bottoni quit e load: void main(...) { fl_initialize (...); /* creazione delle form */ CreateMainForm(); CreateValueForm(); /* assegnazione stato iniziale degli oggetti inattivi */ fl_deactivate_object(isosurf_but); ... idem per i 6 slider ... /* visualizzazione della sola form principale */ fl_show_form(main_form); /* main loop */ fl_do_forms(); } **Schema delle callback ed event handler piu' rilevanti** Si omette la descrizione di QuitCallback. void LoadCallback (...) { acquisizione del nome file tramite invocazione di fl_file_selector; apertura del file, che restituisce il file descriptor fd; loadDataset(fd); fl_activate_object(isosurf_but); } void IsosurfCallback (...) { /* mostra la form secondaria e disattiva form principale in attesa dell'input di un valore v */ fl_show_form(value_form); fl_deactivate_form(main_form); } La costruzione dell'isosuperficie avviene in seguito alla selezione del valore nella form secondaria, nella callback associata al'input field: void ValueCallback (FL_OBJECT * ob, ...) { /* legge la stringa immessa */ s = fl_get_input(ob); v = conversione di s in float; findIsosurf(v); /* assegnazione del valore da visualizzare nel box */ fl_set_object_label(box,s); /* nascondimento form secondaria e riattivazione form principale */ fl_hide_form(value_form); fl_activate_form(main_form); /* inizializzazione display list OpenGL per l'isosuperficie */ if (display list gia' presente) glDeleteLists(...) list_index = glGenLists(1); glNewList(list_index, GL_COMPILE); glBegin(GL_TRIANGLES); for (i=0; i