MokaByte Numero 14 - Dicembre 1997 |
|||
|
INTERAZIONE TRA VRML E JAVA |
||
di |
Creazione
di oggetti vrml a run-time attraverso l'interfaccia Java-Vrml quarta parte |
||
Riassunto dell'articolo precedente
Il
precedente articolo è stato decisamente più pesante del solito.
Procedo dunque a riassumere i concetti fondamentali che erano stati introdotti.
L'animazione
di oggetti in vrml può essere realizzata banalmente attraverso i
meccanismi built-in offerti dalle specifiche vrml 2.0. Le entità
che più ci interessano in fase di realizzazione di una animazione
sono:
Sebbene
questi meccanismi consentano di ottenre semplicemente delle animazioni,
nella maggior parte dei casi non sono sufficienti. Già nell'esempio
dell'articolo n.3 abbiamo notato come l'implementazione di un interruttore
non può essere realizzata senza fare ricorso ad un linguaggio di
programmazione (o di scripting).
La
gestione da Java dei nodi vrml che controllano le animazioni non pone ulteriori
problemi rispetto ai soliti nodi vrml. E' possibile acquisire una reference
ad un campo di tipo eventIn o exposedField e quindi procedere alla sua
modifica attraverso il metodo setValue(). Ma a differenza di altri nodi
vrml, nodi come TimeSensor e PositionInterpolator possono anche emettere
eventi e sono quindi dotati di campi con un tipo di interfaccia eventOut.
L'acquisizione di una reference verso questi campi è molto simile
a quella per un evento in ingresso (utilizzando il metodo getEventOut("nome_campo")).
Non è però possibile procedere alla scrittura del campo,
ma solo alla sua lettura (trattandosi appunto di un evento in uscita da
un nodo).
Inoltre
sorge un altro problema. L'emissione di eventi in uscita è del tutto
asincrona rispetto all'esecuzione dell'applet Java e potrebbe rendere difficoltoso
recepire il verificarsi di eventi all'interno del mondo vrml.
Il
problema viene risolto dalla implementazione dell'interfaccia eventOutObserver,
definendo il metodo callback. Tale metodo viene invocato ogni volta che
all'interno del mondo vrml si scatena un evento sul quale ci si era posti
in ascolto.
Per
segnalare la necessità di essere informati del verificarsi di particolari
eventi viene utilizzato il metodo advise().
All'interno
del metodo callback è possibile identificare l'evento che si è
verificato e procedere dunque alla sua gestione.
L'esempio
a conclusione del precedente articolo ha mostrato come implementare la
gestione di un ascensore attraverso appositi pulsanti di chiamata.
Torneremo
brevemente su questo esempio in conclusione del presente articolo, per
cercare di risolvere quel problema puntualizzato la volta scorsa. Con Cosmo
Player 1.0 infatti, la fase di salita da pianto terra all'ultimo piano
presenta tremolii e imperfezioni.
Ho
reputato importante ripercorrere velocemente il precedente articolo, in
quanto risulta fondamentale per la comprensione dell'interazione tra Java
e Vrml attraverso la EAI.
Creazione dinamica di oggetti vrml
Questo
articolo considera un altro aspetto molto interessante nell'interazione
tra Java e Vrml: creazione dinamica di oggetti.
Per
la precisione, l'aggiunta a run-time di oggetti vrml non è ottenibile
solo attraverso Java. In particolare è possibile creare vrml dinamicamente
attraverso i seguenti modi:
Abbiamo
visto come risulti necessario acquisire una reference al browser html in
fase di inizializzazione. Questo consente di accedere a nodi e campi vrml
da applet Java.
L'oggetto
Browser esporta diversi metodi, uno dei quali, getNode(String), è
stato analizzato in precedenza. Un altro metodo molto utile è createVrmlFromString(String),
tramite il quale è possibile creare un nuovo nodo a run-time.
Il
parametro di tipo String contiene il sorgente Vrml che descrive il nodo
da creare. L'oggetto ritornato da questo metodo va assegnato ad un oggetto
di tipo Node[] (quindi in realtà si può creare una collezione
di nodi).
Per
esempio, il seguente frammento di codice crea un nuovo nodo vrml e lo assegna
all'oggetto Node[].
public Node[] newNode;
public String stringa;
...
stringa = "Shape { appearance Appearance { material Material { ";
stringa += "diffuseColor 1 0 0 } } geometry Sphere {} }";
newNode = browser.createVrmlFromString(stringa);
Il nodo creato è un semplice nodo Shape contenente la descrizione di una sfera.
La
creazione di un nodo attraverso il metodo createVrmlFromString(String)
non produce alcun risultato diretto sul mondo vrml. Per poter aggiungere
il nodo appena creato alla scena virtuale si deve provvedere a spedirlo
come evento in ingresso ad un grouping node.
Un
grouping node è un nodo che può contenere al suo interno
più nodi, racchiusi all'interno di un campo children. Tale è
il caso per esempio del nodo Transform. Tale nodo esporta diversi campi,
alcuni dei quali già analizzati in precedenza, come translation,
rotation, scale. Per procedere all'aggiunta dinamica di nodi, sono previsti
altri due campi:
Il seguente esempio mostra più chiaramente il procedimento.
Supponiamo di avere un file vrml contenente un nodo Transform di questo tipo:
DEF
Root Transform {}
Vogliamo
creare a run-time (per qualche motivo) una sfera da aggiungere al suo campo
children. Per prima cosa dobbiamo acquisire una reference al campo addChildren
del nodo Root. Solo in questo modo potremo procedere all'aggiunta della
sfera al suo campo children (che per il momento è completamente
vuoto).
...
Node Root;
EventInMFNode addChildren_root = null;
...
Node newNode[];
...
...
// acquisisco una reference al nodo Root
Root = browser.getNode("Root");
// acquisisco una reference al campo addChildren
addChildren_root = (EventInMFNode) Root.getEventIn("addChildren");
...
stringa = "Shape { appearance Appearance { material Material { ";
stringa += "diffuseColor 1 0 0 } } geometry Sphere {} }";
newNode = browser.createVrmlFromString(stringa);
addChildren_root.setValue(newNode);
...
Come
si può vedere l'aggiunta a run-time di nodi vrml risulta molto semplice.
Basta creare un nodo vrml con createVrmlFromString e mandarlo come evento
in ingresso al campo addChildren di un grouping node.
In
alcune applicazioni potrebbe però nascere la necessità di
voler controllare successivvamente i nodi appena creati a run-time.
Per
esempio, per la realizzazione di un semplice tool di creazione di scene
vrml (che potete trovare presso il sito Vrml Dreamers: http://www.lucia.it/vrml
nella sezione Progetti-Builder), oltre alla possibilità di introdurre
nuovi oggetti, risulta necessario poterli spostare, ruotare, scalare o
cambiare di colore. In tali casi deve essere possibile acquisire dei riferimenti
ai campi translation, rotation, scale, ecc. del nodo appena aggiunto.
Questa
operazione non presenta particolari problemi, in quanto, dopo la creazione
del nuovo nodo, abbiamo a disposizione una reference ad esso. Basta dunque
invocare i metodi getEventIn(String) o getEventOut(String) come per un
nodo qualsiasi.
C'è
un solo particolare su cui fare attenzione. Ricordo infatti che atraverso
il metodo createVrmlFromString non creiamo un solo nodo, ma una collezione
di nodi (Node[]). Il primo nodo contenuto nella stringa sarà dunque
referenziato da newNode[0], il secondo da newNode[1] e così via.
Supponiamo
per esempio di avere creato un nuovo nodo Transform e di volere poi traslare
il suo contenuto. Per es.
...
stringa = "Transform { children [ Shape { appearance Appearance {";
stringa += " material Material { diffuseColor 1 0 0 } } ";
stringa += "geometry Sphere {} ] }";
newTransf = browser.createVrmlFromString(stringa);
traslazione = (EventInSFVec3f) newTransf[0].getEventIn("set_translation");
rotazione = (EventInSFRotation) newTransf[0].getEventIn("set_rotation");
scala = (EventInSFVec3f) newTransf[0].getEventIn("set_scale");
addChildren_transf = (EventInMFNode) newTransf[0].getEventIn("addChildren");
...
Nel
frammento di codice precedente si è provveduto a creare un nuovo
nodo Transform, contenente una sfera di colore rosso; successivamente si
sono acquisiti i riferimenti ad alcuni campi. Attraverso di essi sarà
dunque possibile modificare alcune proprietà del nodo aggiunto.
Si
ricorda che il nodo newTransf dovrà essere mandato in ingresso ad
un campo addChildren per ottenere l'effettiva aggiunta al mondo vrml.
Si
noti infine come sia possibile acquisire riferimenti a campi addChildren
di nodi creati a run-time, fornendo la possibilità di aggiungere
altri nodi all'interno di quelli creati.
L'aggiunta di oggetti vrml a tempo di esecuzione rappresenta uno strumento molto potente e utile nell'ambito della realizzazione di mondi virtuali sempre più interessanti (anche se si vedono pochi mondi in circolazione che mostrano sfoggio di queste funzionalità... ma questo probabilmente deriva dal fatto che queste operazioni sono scarsamente automatizzabili attraverso tool di authoring appositi; suppongo che la situazione cambierà presto).
Per
concludere questo articolo, ci dedichiamo allo sviluppo di una semplice
demo di visualizzazione dati. L'utilità di vrml in questo ambito
è ancora tutta da scoprire e sperimentare.
La
presente applicazione non vuole certo rappresentarne un esempio, ma solo
uno spunto per dimostrare come vrml si presti anche ad applicazioni di
questo tipo.
Supponiamo
di volere raccogliere i dati di vendita di quattro aziende in quattro diversi
mesi di attività. Vogliamo realizzare un grafico a barre 3D, ponendo
sull'asse x le 4 aziende, sull'asse y il fatturato e sull'asse z i 4 mesi.
Il
grafico complessivo sarà dunque costituito da 16 barre 3D, ognuna
rappresentante le vendite realizzate da una azienda in un certo mese.
L'applet
Java deve gestire tutte le operazioni di input dei dati. Per semplicità,
esprimiamo gli importi come numeri interi, assumendo che si tratti di miliardi
di lire.
Utilizzeremo
una griglia di 16 celle, ognuna delle quali contiene il fatturato in miliardi
di una azienda in un certo mese.
Un
apposito pulsante sottopone al mondo vrml i dati introdotti e si procede
alla creazione a run-time del grafico.
Tralasciando
di considerare la parte di creazione del layout dell'applet (nella quale
suppongo sarete più esperti del sottoscritto), concentriamoci sugli
aspetti di creazione del grafico.
Dobbiamo
poter aggiungere a run-time delle barre 3D. Le barre del grafico possono
essere realizzate semplicemente attraverso delle Box, la cui altezza è
proporzionata al fatturato. La diversa altezza delle Box viene imposta
in fase di creazione, adattando il campo size relativo alla dimensione
in y; in pratica non si fa altro che agire sulla stringa da sottoporre
a createVrmlFromString. Dato poi che la Box viene posizionata con il suo
centro nell'origine del sistema di coordinate correnti, si deve provvedere
ad incapsulare il nodo Shape contenente la barra 3D all'interno di un nodo
Transform. In questo modo possiamo posizionare la barra nella cella opportuna
e spostarne il centro in alto o in basso in maniera tale che l'estremo
inferiore corrisponda ad una quota pari a 0 miliardi.
I dati introdotti devono poter essere modificati. Questo comporta la necessità di poter cancellare il grafico per ricostruirlo in base ai nuovi dati. La cancellazione di un nodo dal mondo vrml avviene seguendo le stesse modalità previste per la fase di aggiunta. Semplicemente si utilizza il nodo removeChildren anzichè addChildren.
Passiamo
ora a considerare i sorgenti. Il mondo vrml è semplicissimo, in
quanto è vuoto, tranne che per la definizione degli assi e del nodo
Root che verrà utilizzato per procedere all'aggiunta delle barre.
Si
faccia attenzione al fatto che è INDISPENSABILE avere un tale nodo.
Possiamo infatti aggiungere nodi a run-time solo mandando eventi ad un
campo addChildren. Si noti che per acquisire una reference a tale nodo
siamo obbligati a fornirgli un nome attraverso il comando DEF.
Qui di seguito riporto invece il sorgente dell'applet Java controllante. Alla luce di quanto spiegato in precedenza non dovrebbero sussistere particolari problemi di comprensione.#VRML V2.0 utf8 Viewpoint { description "vista di default" position 15 7 30 } DEF Root Transform {} DEF asse_x Transform { translation 15 0 0 rotation 0 0 1 -1.57 children [ DEF asse Transform { children [ Shape { appearance Appearance { material Material { diffuseColor 1 0 0 } } geometry Cylinder { radius .1 height 30 } } Transform { translation 0 15.5 0 children [ Shape { appearance Appearance { material Material { diffuseColor 0 1 0 } } geometry Cone { bottomRadius .3 height 1 } } ] } ] } ] } DEF asse_y Transform { translation 0 10 0 scale 1 .667 1 children [ USE asse ] } DEF asse_z Transform { translation 0 0 -15 rotation 1 0 0 -1.57 children [ USE asse ] }
Il file html relativo è il seguente:import java.awt.*; import java.applet.*; import vrml.external.field.*; import vrml.external.Node; import vrml.external.Browser; import vrml.external.exception.*; import netscape.javascript.JSObject; public class CreaGraf extends Applet { Browser browser; Node Root; EventInMFNode addChildren_root; EventInMFNode removeChildren_root; Node barre[][][] = new Node[4][][]; TextField data[][] = new TextField[4][]; boolean error = false; public void start() { // reference al browser vrml JSObject win = JSObject.getWindow(this); JSObject doc = (JSObject) win.getMember("document"); JSObject embeds = (JSObject) doc.getMember("embeds"); browser = (Browser) embeds.getSlot(0); // piccola attesa prima di accedere ai nodi vrml // aspetto che il mondo vrml sia definitivamente caricato // in modo da poter accedere ai nodi in esso contenuti try { Thread.sleep(2000); } catch(InterruptedException e) { showStatus("thread interrupted!"); error = true; } try { Root = browser.getNode("Root"); addChildren_root = (EventInMFNode) Root.getEventIn("addChildren"); removeChildren_root = (EventInMFNode) Root.getEventIn("removeChildren"); } catch (InvalidNodeException ne) { error = true; showStatus("Errore in fase di reference al nodo "" + ne); } catch (InvalidEventInException ee) { error = true; showStatus("Errore in fase di reference all'evento "" + ee); } catch (InvalidEventOutException ee) { error = true; showStatus("Errore in fase di reference all'evento "" + ee); } barre[0] = new Node[4][]; barre[1] = new Node[4][]; barre[2] = new Node[4][]; barre[3] = new Node[4][]; data[0] = new TextField[4]; data[1] = new TextField[4]; data[2] = new TextField[4]; data[3] = new TextField[4]; // layout dell'applet setLayout(new GridLayout(6,5,2,2)); add(new Label("")); add(new Label("mese 1")); add(new Label("mese 2")); add(new Label("mese 3")); add(new Label("mese 4")); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) data[i][j] = new TextField("0",2); add(new Label("ditta 1")); add(data[0][0]); add(data[0][1]); add(data[0][2]); add(data[0][3]); add(new Label("ditta 2")); add(data[1][0]); add(data[1][1]); add(data[1][2]); add(data[1][3]); add(new Label("ditta 3")); add(data[2][0]); add(data[2][1]); add(data[2][2]); add(data[2][3]); add(new Label("ditta 4")); add(data[3][0]); add(data[3][1]); add(data[3][2]); add(data[3][3]); add(new Label("")); add(new Label("")); add(new Label("")); add(new Label("")); add(new Button("Visualizza")); visualizza(); } public boolean action(Event evt, Object arg) { if (evt.target instanceof Button) { String label = (String) arg; if (label.equals("Visualizza")) { pulisci(); visualizza(); } return true; } else return false; } private void visualizza() { int i,j; String stringa = new String(); float center_x, center_y, center_z; float altezza; center_x = (float) 4; center_z = (float) -4; for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { altezza = getValue(i,j); if (altezza > 30) altezza = 30; center_y = altezza/2; stringa = "Transform { translation "" + center_x + " ""; stringa += center_y + " "" + center_z + " children [ ""; stringa += "Shape { appearance Appearance { material ""; stringa += "Material { diffuseColor ""; switch(i) { case 0: stringa += "1 0 0"; break; case 1: stringa += "0 1 0"; break; case 2: stringa += ".5 .5 1"; break; case 3: stringa += ".8 .8 .8"; break; } stringa += " } } geometry Box { size 5 "" + altezza + " 5 }""; stringa += " } ] }""; barre[i][j] = browser.createVrmlFromString(stringa); addChildren_root.setValue(barre[i][j]); center_x += 8; } center_x = (float) 4; center_z -= 8; } } private void pulisci() { int i,j; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) removeChildren_root.setValue(barre[i][j]); } private float getValue(int i, int j) { Float num_read = new Float(0); String str = new String(); str = data[i][j].getText(); num_read = num_read.valueOf(str); return num_read.floatValue(); } }
<HTML> <HEAD><TITLE>Visualizzazione dati</TITLE></HEAD> <CENTER> <EMBED SRC="grafico.wrl" height = 200 width = 400> <P> Massimo fatturato = 30 <P> <APPLET CODE="CreaGraf.class" HEIGHT=150 WIDTH=600 mayscript> </APPLET> </HTML>
Ricordo che per la visualizzazione dei dati si deve procedere alla pressione del bottone 'Visualizza'; solo allora le modifiche nei campi di input verranno applicate anche al grafico.
L'esempio
analizzato nel presente articolo è decisamente esemplificativo delle
potenzialità raggiungibili da vrml anche in campi un po' più
'professionali'... in ogni caso è un ottimo esempio di come creare
oggetti vrml a run-time.
Riflessioni sull'esempio dell'articolo precedente
In conclusione di articolo torniamo brevemente all'esempio visto la volta scorsa. Avevamo implementato un ascensore virtuale, dotato dei relativi bottoni di chiamata a piano terra e all'ultimo piano. Altri due bottoni sull'ascensore consentivano di controllare l'ascensore dall'interno.
Ci eravamo inbattuti nel problema di spostare l'utente quando questo è posizionato all'interno dell'ascensore in movimento. Lo avevamo risolto affidandoci al meccanismo automatico di collision detection implementato da Cosmo Player (come del resto dalla maggior parte di tutti i browser vrml 2.0). Questa soluzione porta però a risultati accettabili ma non certo esaltanti. Con Cosmo Player 1.0 si ravvisano fastidiosi tremolii e discontinuità nello spostamento.
Una soluzione alternativa potrebbe essere la seguente (anche se in verità presenta una piccola controindicazione)
Nei precedenti articoli abbiamo incontrato più volte il nodo Viewpoint. Tale nodo consente di definire la posizione dell'utente all'interno del mondo virtuale. Il primo nodo Viewpoint definito nel sorgente vrml specifica la posizione iniziale dell'utente. Successive viste predefinite con Viewpoint devono essere attivate mediante i meccanismi forniti dal browser (per esempio, con Cosmo Player 1.0 si clicchi con il pulsante destro del mouse e si selezioni viewpoints).
L'attivazione di una vista predefinita attraverso un nodo Viewpoint può essere effettuata anche da Java (ma anche da vrml direttamente). Il nodo Viewpoint prevede infatti un campo in ingresso di tipo SFBool chiamato set_bind. Mandando un evento TRUE in ingresso a questo campo, si ottiene l'attivazione della vista.
L'idea
è dunque la seguente. Definiamo una vista all'interno del campo
children nel quale è definita anche la base dell'ascensore. Più
precisamente, la vista è sottoposta ad una ulteriore traslazione
(mediante un nesting di nodi Transform), in modo che l'utente sia piazzato
circa un metro sopra il pavimento dell'ascensore.
Il
nodo Viewpoint inoltre è innestato all'interno di un grouping node
che contiene anche il pavimento dell'ascensore. Quando il pavimento viene
mosso (agendo sul campo translation del nodo Transform che lo contiene),
anche la Viewpoint risentirà di un movimento identico. Risultato:
se l'utente viene assegnato a questa Viewpoint, il suo posizionamento dovrebbe
seguire di pari passo lo spostamento dell'ascensore. E il tutto senza tremolii
e imperfezioni.
Attenzione al condizionale... dico dovrebbe in quanto non basta attivare la Viewpoint e poi far partire l'animazione. Infatti, l'utente non risente delle variazioni imposte ad una Viewpoint, se non quando un set_bind viene eseguito. Ciò comporta che si deve procedere a mandare in continuazione eventi set_bind di valore TRUE. In questo modo si realizza uno spostamento fluido e lineare dell'utente.
Il lato negativo? beh... è facile immaginarlo. Dato che continuiamo a mandare eventi set_bind, durante una salita l'utente non potrà muovere un dito! Non potrà scendere dall'ascensore, non potrà voltarsi, guardarsi attorno, ecc. Questo per il semplice fatto che la Viewpoint specifica completamente posizione e orientamento dell'utente.
Non
riporto l'esempio modificato dell'ascensore, forse anche perchè,
tutto sommato, questa perdita di libertà in fase di movimento dell'ascensore
non mi piace poi molto (la sensazione di buttarsi dall'ascensore va pure
mantenuta no?).
Ho
voluto comunque riportare queste considerazioni in quanto l'animazione
della telecamera può risultare molto utile anche in altri contesti.
Prima di chiudere vorrei fare un paio di considerazioni:
|
||
|
||
MokaByte
rivista web su Java |
||
|