Capitolo 4: Le API Java 3D __________________________________________________________________ Capitolo4 Le API Java 3D 4.1 Introduzione Lo slogan “Write once, run anywhere” è stato adattato dal marketing Sun di Java 3D a “Write once, view anywhere”, focalizzando l’attenzione sulla portabilità delle applicazioni e applet realizzate con Java 3D. Java 3D è una estensione standard di java 2 JDK. Tale interfaccia permette di creare applicazioni e applet basate su grafica tridimensionale. Le API prevedono una collezione di costrutti ad alto livello per creare e manipolare geometrie 3D, e strutture per renderizzare tali geometrie. In questo capitolo mostreremo il funzionamento generale dell’engine Java 3D, della sua caratteristica struttura ad albero, e parleremo senza entrare troppo nei dettagli di texture, luci, e collisioni. 62 Capitolo 4: Le API Java 3D __________________________________________________________________ 4.2 Le API Le API definiscono più di 100 classi presenti nel pacchetto javax.media.j3d. A tali classi ci si riferiscie come Java 3D core classes. In aggiunta al pacchetto java 3D core, esistono dei pacchetti supplementari usati per scrivere programmi Java 3D. Uno di questi è com.sun.j3d.utils al quale ci si riferisce generalmente come Javax.media.j3d Java 3D utility classes. Tali classi sono d’estrema utilità poiché le core classes includono solo le classi a basso livello strettamente necessarie Virtual universe Locele nella programmazione Java 3D. Le utility sono classi potenti e utili di supporto. View Phisical Body Questo pacchetto si divide in quattro PhisicalUniverse categorie: Screen3D Content loaders Scene graph construction aids Geometry classes Convenience utilities Convas3D ScreenGraphObject Node Le future funzionalità saranno Group aggiunte come utilità nel pacchetto java 3D. Leaf In aggiunta a queste classi ogni programma java 3D utilizza il pacchetto javax.vecmath nel quale NodeComponent Transform3D sono definiti vettori, matrici e altri oggetti matematici. Alpha Figura 4.1 – Gerarchia delle Classi 63 Capitolo 4: Le API Java 3D __________________________________________________________________ 4.3 Il Motore di Java 3D Java 3D, consente la produzione in tempo reale Applet o Applicazione di grafica 3D tramite un insieme di API JAVA 3D omogenee e multipiattaforma. Per riuscire ad avere un buon rendimento e delle buone prestazione è stata sacrificata JAVA 2 la portabilità delle classi. Java 3D, infatti, non si occupa del rendering a basso livello delle Native Graphics Calls OpenGL immagini: operazione delegata dal motore di rendering di java 3D a librerie native (OpenGL, Direct3D Future Apis Proprietary Hardware Direct3D). Le capacità, e le possibilità di java 3D sono, infatti, un insieme standard di caratteristiche riscontrabili in queste librerie Figura 4.2 – Relazione tra Hardware e software native: non è possibile ad esempio gestire le ombre e le texture animate. Queste non sono limitazioni di Java 3D, ma delle librerie native. Le operazioni citate sono, infatti, molto dispendiose in termini di risorse e non sono utilizzate in grafica 3D in tempo reale. E’ lecito aspettarsi che quando l’evoluzione tecnologica porterà gli standard 3D a supportare questo tipo di caratteristiche, anche Java 3D ne fornirà il supporto. Java 3D non è però solo un semplice wrapper su API esistenti: il suo motore si occupa anche di ottimizzazione e operazioni aggiuntive che non sono previste nelle API native. Il motore sceglie l’ordine di attraversamento dell’albero degli oggetti 3D e non è limitato ad una direzione di tipo sinistra-destra o alto-basso (eccetto per quanto riguarda attributi con limiti di spazio come le sorgenti di luce o l’effetto nebbia), quindi può ottimizzare la visualizzazione. Il motore è concettualmente un ciclo infinito che effettua una fase di render ogni ciclo rielaborando ogni volta eventuali modifiche apportate e passando ad una fase di idle al termine del ciclo. 64 Capitolo 4: Le API Java 3D __________________________________________________________________ While (true) { Process input If (request to exit) break Perform Behaviors Traverse the scene graph and render visible objects } Cleanup and exit Il sistema è inoltre aperto all’elaborazione parallela. 4.4 La Struttura Dati VirtualUniverse Locale BranchGroup Shape3D node Appearance TransformGroup Geometry Canvas3 D View Creen3D View Platform Phisical Body Phisical Envoronment Figura 4.3 - SimpleUniverse Java 3D permette di definire “SceneGraph” (ambienti in cui sono presenti oggetti 3D) ed organizza gli oggetti necessari a descrivere l’universo virtuale tramite un albero. Gli oggetti appartenenti all’albero non sono omogenei, ma sono di tipo diverso. La radice dell’albero è un oggetto di tipo VirtualUniverse e serve come punto di riferimento per gli altri oggetti. Da questo discende un oggetto di classe Locale che consente di definire la posizione all’interno dell’universo. 65 Capitolo 4: Le API Java 3D __________________________________________________________________ Teoricamente è possibile inserire più istanze di questo oggetto all’interno della stessa scena. Da Locale discendono oggetti di classe BranchGroup e TransformGroup. Come possiamo vedere dalla Figura 4.3 a questo punto l’albero si divide in due grosse rami che possiamo definire come albero dei contenuti a sinistra, e albero di input e output a destra, che prende il nome di View Branch. Questo ramo è piuttosto complesso e fornisce a Java 3D molta flessibilità: è grazie ad esso, infatti, che Java 3D è in grado di usare dispositivi di input come guanti, e dispositivi di visione come i caschi. Continuiamo con la descrizione del View Branch. La radice è un oggetto di tipo ViewPlatform che indica dove la scena deve essere vista (posizione e orientamento), a cui è collegato un View, che indica invece come la scena deve essere vista (tipo di proiezione, antialiasing), e infine troviamo Canvas3D che estende Canvas con il double buffering. Inoltre troviamo PhysicalBody, che contiene una serie d’informazioni sulla posizione dell’utente, e Physical Enviroment che contiene informazioni su tipi differenti di sensori di input e periferiche di output. Sul ramo sinistro dell’albero, Content Branch, sono presenti gli oggetti 3D, sottoclassi di Shape3D. La costruzione di un albero Java 3D non è banale, ma fortunatamente è presente un’utilità, la classe SimpleUniverse, che raccoglie tutto il View Branch e che consente di creare in automatico un “semplice universo”, in cui sono presenti i dispositivi standard disponibili quali mouse e monitor. L’universo base creato da SimpleUniverse è simile a quello che abbiamo descritto fino a questo momento, con la sola eccezione del ramo sinistro dell’albero, in cui l’insieme degli oggetti che dovranno essere presenti nella scena, devono essere creati manualmente o importati. L’unica classe presente in Java 3D che definisce una forma geometrica è Shape3D. Per definire forme specifiche è necessario fare riferimento alle classi utility presenti nel pacchetto. Alcune forme disponibili sono Box, Spere, Cylinder, Cone. La scelta di non comprendere nei core package forme specifiche è dettata dal fatto che le API di Java3D si intendono mantenere il più semplice possibile. Anche funzionalità future verranno molto probabilmente inserite nel pacchetto di utilità. 66 Capitolo 4: Le API Java 3D __________________________________________________________________ Per collegare gli oggetti 3D al ramo sinistro dell’oggetto Locale è necessario utilizzare oggetti di classe BranchGroup e TransformGroup. Oggetti di queste classi permettono di collegare le forme geometriche tra di loro (Branch) e di applicare trasformazioni (Transform), quali la geometria (Geometry), la posizione della luce, la vista. Per ottimizzare ulteriormente le prestazioni è possibile compilare tutta una parte dell’albero. Una volta compilato il ramo, però, non sarà più possibile effettuare variazioni strutturali su di esso. 4.4.1 Le Texture Un modo semplice per dare l'apparenza di un oggetto complesso è di applicarvi sulle facce una texture, cioè un’immagine. Si pensi ad esempio ad un muro composto di tante pietre irregolari: il modo migliore per visualizzarlo è avere un oggetto per ogni pietra, o per lo meno avere un unico oggetto più o meno a forma di parallelepipedo, con un lato irregolare a simulare le varie pietre. Questo metodo presenta due svantaggi: é lungo e tedioso da generare per il grafico e soprattutto richiede un grosso lavoro al calcolatore, con un’eccessiva perdita prestazionale. Generalmente, quindi, si ricopre un oggetto liscio con un’immagine. Questa tecnica permette di utilizzare molti meno poligoni con risultati paragonabili, almeno fino a quando non ci si avvicina troppo al soggetto. Per cercare di eliminare questo problema si utilizza una tecnica chiamata mip-mapping: in pratica, quando l'elaboratore deve "inventarsi" dei pixel perché la texture è troppo vicina, non si limiterà a considerare solo il pixel più vicino al punto di osservazione, ma anche quelli limitrofi. Oppure una tecnica avanzata che prevede texture molto sofisticate in cui i particolari appaiono avvicinandosi; in questo modo non si hanno bruschi cambiamenti di colore nella texture, anche se sembra esserci un po' di sfocatura. Vediamo come Java3D gestisce tutto questo: La classe TextureLoader, una classe d’utilità, carica un'immagine (da un file o da un URL) e restituisce una Texture. Per motivi tecnici le dimensioni delle texture 67 Capitolo 4: Le API Java 3D __________________________________________________________________ devono essere una potenza di due. Inoltre grazie ad opportuni metodi, la texture può essere ruotata, spostata e scalata; può essere piazzate sull'oggetto una volta (clamping) oppure ripetersi per coprire l'intera superficie (wrapping). Le texture possono fondenrsi in varia misura con il colore dell'oggetto; inoltre possono essere ancorate all'oggetto oppure al mondo: questo permette di utilizzare una tecnica chiamata reflection mapping, che permette dà l'effetto delle riflessioni su un oggetto senza troppi calcoli. 4.4.2 Le Luci Per aumentare il realismo delle scene è possibile aggiungere delle luci. Bisogna però stare attenti a non esagerare, perché le luci sono computazionalmente impegnative e tendono a rallentare la scena. Anche le ombre proiettate sono troppo dispendiose e normalmente non sono usate. I tipi di luce che si possono utilizzare sono quattro: Ambient: una luce diffusa che illumina uniformemente tutti gli oggetti. Di solito se ne utilizza una sola Directional: raggi paralleli che puntano in una direzione; possono simulare per esempio il Sole Point: i raggi sono emessi da un punto e si irradiano in tutte le direzioni; ad esempio una lampadina Spot: i raggi sono emessi da un punto e si irradiano dentro un cono. Tutte i tipi di luce hanno in comune metodi per attivarle o disattivarle e per cambiarne il colore. Inoltre un fattore estremamente importante da considerare è che ogni luce deve avere dei limiti ben fissati da un oggetto Bounds. I limiti servono a confinare le luci in una certa area, oltre che per motivi estetici anche per evitare un grosso carico computazionale. Se si hanno, ad esempio, due stanze non comunicanti entrambe con una luce all'interno e se non si limita l'area d'effetto 68 Capitolo 4: Le API Java 3D __________________________________________________________________ delle luci esse avranno effetto su entrambe le stanze, creando un effetto sbagliato oltre che rallentando inutilmente la scena. 4.4.3 I behaviour Fino ad ora il contenuto della nostra scena è statico; vediamo adesso come renderlo un po' più dinamico. Java3D fonda il suo meccanismo per gestire l'animazione, il movimento, l'interazione con l'utente e le collisioni sui Behavior (letteralmente "Comportamenti"). I behaviour sono delle classi utilizzate per gestire alcuni raffinati processi in una scena 3D. Tutti i behaviour devono essere definiti dentro una regione per motivi di performance e comodità. I metodi fondamentali sono initialize() e processStimulus(): il primo è chiamato solo una volta, all’inizio; di solito contiene le definizioni delle condizioni di WakeUp (svegliarsi) e la specifica del primo criterio per il quale svegliarsi. Invece processStimulus viene chiamato quando si verifica il criterio specificato da wakeupOn. I criteri per cui un behavior viene svegliato possono essere di vario tipo: eventi dell'AWT, collisioni, un certo tempo passato e molti altri ancora. Vediamo, quindi, i vari WakeUpCriterion: WakeupOnAWTEvent, quando avviene un evento dell'AWT. WakeupOnBehaviourPost, quando un behaviour specificato lancia un preciso evento. WakeupOnActivation e WakeupOnDeactivation, quando un behaviour entra o esce dai limiti di attivazione. WakeupOnElapsedFrames, dopo che sono stati disegnati un certo numero di frames. WakeupOnElapsedTime, dopo che è passato un certo tempo. WakeupOnSensorEntry e WakeupOnSensorExit, il centro di un Sensor entra o esce da una certa regione. WakeupOnViewplatformEntry e WakeupOnViewplatformExit, il centro di una ViewPlatform entra o esce da una certa regione. WakeupOnTransformChange, quando un certo nodo viene sottoposto a una trasformazione. 69 Capitolo 4: Le API Java 3D __________________________________________________________________ WakeupOnCollisionEntry, WakeupOnCollisionExit,WakeupOnCollisionMovement, per la gestione delle collisioni. I behaviour sono piuttosto flessibili e possono essere combinati con degli AND e OR logici, tramite quattro classi: WakeupAnd: una serie di criteri legati da un AND (WakeupCriterion && WakeupCriterion && ...) WakeupOr: una seria di criteri legati da un OR (WakeupCriterion || WakeupCriterion || ...) WakeupAndOfOrs: una serie di WakeupOr legati da un AND (WakeupOr && WakeupOr && ...) WakeupOrOfAnds: una serie di WakeupAnd legati da un OR (WakeupAnd || WakeupAnd || ...) Per far girare una sfera ad esempio dovremmo quindi definire un Behavior che abbastanza spesso si svegli, crei una trasformazione per orientare la sfera, la applichi e torni a dormire; invece di agire così, è consigliabile utilizzare una sottoclasse dei Behavior: Interpolator. Gli Intepolator servono ad esprimere semplici Behavior che cambiano un parametro da un valore iniziale ad un valore finale in un intervallo di tempo. Sono fornite delle sottoclassi di Interpolator per gli utilizzi più comuni, tra cui proprio una che serve per ruotare un gruppo di oggetti. E' necessario creare un oggetto Alpha che controlla il modo in cui l'interpolatore agisce. Facendo un po’ di test su varie porzione di codice si arriva però alla conclusione che la gestione delle collisioni è altamente imprecisa. Se si passa al behaviur lo shape dell’oggetto (quindi tutta la geometria dell’oggetto) si ottiene un effetto meno preciso rispetto a passare gli oggetti come Node, in modo da controllare solamente il centro, (ovviamente solo nei pochi casi in cui è possibile farlo). Probabilmente questo è dovuto ad una non corretta implementazione del metodo getShape. 70 Capitolo 4: Le API Java 3D __________________________________________________________________ 4.5 Java 3D negli Origami Entriamo ora nel dettaglio delle problematiche da affrontare per implementare la nostra applicazione. Lo scopo è di visualizzare un foglio di carta diviso in poligoni, in modo tale che ognuno di questi sia un oggetto indipendente (shape 3d) libero di ruotare intorno ad uno qualunque dei suoi lati. I problemi da affrontare sono quindi i seguenti: Creazione dello ScheneGraph Creazioni dei poligoni Rotazione dei poligoni 4.5.1 Creazione dello SceneGraph VirtualUniver se Local e TG - scala TG - luci BranchGrou p ORIGAMI TG TransformGroup TG TransformGroup TransformGroup View View Platform TG TG Phisical TransformGroupBody TransformGroup Canvas3 Creen3D Phisical Envoronment Figura 4.4 – Shene Graph 71 Capitolo 4: Le API Java 3D __________________________________________________________________ Come si vede dalla Figura 4.4 lo SheneGraph viene costruito seguendo le indicazioni che abbiamo dato nel paragrafo 4.4. Soffermiamoci un momento sulla porzione sinistra dell’albero. Il primo nodo è di tipo TrasfornGroup, utilizzato per settare il fattore di scala, tale metodo risulta molto comodo poiché dà la possibilità di variare facilmente le dimensioni degli oggetti della scena, senza dover riscrivere tutti i parametri che li descrivono. In cascata segue un altro nodo di tipo TrasforGroup dove viene creata l’illuminazione della scena, ponendo una luce d’ambiente, e una luce direzionale inclinata di 45°. In fine iniziano i nodi che descrivono l’origami: ogni nodo di tipo TrasformGroup descrive un poligono. Nella Figura 4.4 non viene rappresentato per ragioni di leggibilità, ma è chiaro che ognuno di questi nodi sarà composto a sua volta da un oggetto di tipo Geometry necessario a descrivere la geometria del poligono, a da un oggetto di tipo Apparence per settare i parametri che ne identificano appunto l’apparenza: il grado di riflessione, il colore, eventuali texture ecc. Come avviene la costruzione della porzione di albero che descrive l’origami sarà illustrato dettagliatamente nel capitolo 5, deve essere chiaro però che tale struttura dipenderà dal tipo di pattern che intendiamo simulare. 4.5.2 Creazione dei poligoni Immaginiamo di avere tutti i punti che descrivono il poligono, il modo per ricavarli sarà chiarito nel capitolo 5. Java 3D fornisce molti metodi differenti che permettono di creare degli oggetti. Nella figura 4.5 possiamo osservare la gerarchia della classe Geometry 72 Capitolo 4: Le API Java 3D __________________________________________________________________ Figura 4.5 – Geararchia della classe Geometry GeometryStripArray è una super classe di LineStripArray, TriangleStripArray e TriangleFanArray. La figura 4.6 mostra il comportamento dei tre metodi su un insieme di punti. Figura 4.6 – Sottoclasse GeometryStripArray Il metodo più idoneo al nostro caso è TriangleFanArray; guardiamone la sintassi. TriangleFanArray(int vtxCount, int vertexFormat, int stripVertexCounts[])) VtxCount è un intero che indica il numero di punti di cui è composto il poligono; vertexFormat indica il modo con cui sono passati i punti nell’array stripVertexCounts. 73 Capitolo 4: Le API Java 3D __________________________________________________________________ Per utilizzare tale metodo, però, è necessario ordinare i punti, poiché il primo dell’array ha la funzione di v0 nella Figura 4.6, ossia è l’unico vertice che appartiene a tutti i triangoli. Tale metodo deve essere invocato due volte, passandogli stripVertexCounts ordinato in manira prima crescente e poi decrescente, in questo modo java 3D disegnerà prima il semipiano frontale, e poi quello posteriore. 4.5.3 Rotazione dei Poligoni Per addentrarci nelle problematiche della rotazione dei poligoni è opportuno descrivere in modo un po’ più dettagliato il pacchetto javax.vecmath in cui sono definiti vettori, matrici e altri oggetti matematici, che risultano indispensabili per applicare trasformazioni sugli oggetti, quali rotazioni, traslazioni, deformazioni. Figura 4.7 – gerarchia del pacchetto javax.vecmath 74 Capitolo 4: Le API Java 3D __________________________________________________________________ Ogni vertice di un oggetto geometrico può essere specificato da quattro tipi di oggetti appartenenti a javax.vecmath, rappresentanti coordinate, colori, normali alla superficie, e coordinate della texture. Le classi sono Point* Color* Vector* TexCoord* L’asterisco rappresenta la variazione al nome della classe. Per esempio Tuple* sono tutte le classi: Tuple2f, Tuple2d, Tuple3b, Tuple3f, Tuple3d, Tuple4b, Tuple4f, e Tuple4d. In tutti i casi il numero indica la quantità di elementi che compongono il tuple, e variano da due a quattro, la lettera indica invece il tipo. ‘f’ per la precisione singola floating point, ‘d’ la doppia precisione e ‘b’ il bytes. Noi abbiamo bisogno di un metodo che ci permetta di ruotare il poligono intorno ad un vettore qualunque nello spazio di un determinato angolo. Tale metodo è AxisAngle4d , che ha appunto quattro argomenti double, tre dei quali (x,y,z,) rappresentano le componenti del vettore lungo le tre direzioni e il quarto rappresenta l’angolo di rotazione. E’ importante notare però che tale vettore non è centrato nell’origine degli assi. Per avere una rotazione corretta del poligono è necessario compiere, quindi, una composizione di trasformazioni: Traslazione - Rotazione - Traslazione. Di seguito è riportata parte del codice utilizzata per compiere questa operazione all’interno dell’applicazione 75 Capitolo 4: Le API Java 3D __________________________________________________________________ Transform3D trasla = new Transform3D(); trasla.setTranslation(new Vector3d((V.getCentro()).x,(V.getCentro()).y,0)); AxisAngle4d Ax = new AxisAngle4d( s ); Transform3D rot = new Transform3D(); rot.setRotation(Ax); trasla.mul(rot); Transform3D retrasla = new Transform3D(); retrasla.setTranslation(new Vector3d(-(V.getCentro()).x,-(V.getCentro()).y,0)); trasla.mul(retrasla); T.setTransform(trasla); 4.5 Conclusioni Java3D è un’API piuttosto estesa, flessibile e complessa, che consente con relativa semplicità di produrre applicazioni o applet dotate di grafica 3D, ed è adatta specialmente per progetti sofisticati, dove strumenti principalmente descrittivi come il VRML non sono sufficienti, e si desidera utilizzare un’API a più alto livello rispetto ad OpenGL e Direct3D. L’utilizzo di librerie native e una serie di strumenti come i limiti per i behaviour e per le luci, i capabilites bit,(che permettono ad esempio di specificare se un oggetto sarà modificato durante l’esecuzione) la possibilità di precompilare porzioni di albero, garantiscono prestazioni accettabili, anche se purtroppo è proprio sui grandi progetti che si incontra i problemi più grossi. L’interfaccia Java fornisce portabilità e una buona semplicità d’uso, anche se dà l’idea di essere stato sviluppato un po’ in disparte rispetto alle altre tecnologie Java. L’incompatibilità con Swing ne è un esempio (come la classe Locale che 76 Capitolo 4: Le API Java 3D __________________________________________________________________ trova un duplicato in java.util), che potrebbe frenare progetti ambiziosi su Java3D. Nonostante ciò Java3D completa l’offerta della piattaforma Java con un elemento chiave quale la grafica 3D fornendo un ulteriore e valido tassello alla tecnologia globale Java. 77