MokaByte Numero 12 - Ottobre 1997


 

Java e VRML 

 

di

Ignazio Locatelli 

seconda parte

 

 
 

 

Nel precedente articolo abbiamo iniziato ad analizzare i primi concetti alla base di vrml, focalizzando l'attenzione soprattutto sugli aspetti di interfacciamento con Java. Per quanto riguarda quest'ultimo argomento, ribadisco che prenderemo in considerazione l'approccio EAI utilizzando Cosmo Player come browser vrml di riferimento.
Nel presente articolo verranno analizzati due esempi nei quali si cerca di chiarire le modalità tramite le quali implementare il controllo di un mondo vrml attraverso una applet. Il primo esempio è abbastanza banale. Rifacendoci a quanto visto la scorsa volta su come posizionare nel mondo un certo oggetto, ci proponiamo di controllare la posizione di una sfera sopra un certo piano attraverso un set di bottoni che consentono lo spostamento nelle direzioni lungo gli assi x e z. Non serve nulla di aggiuntivo rispetto a quanto già visto in precedenza. Si dovrà fare in modo che in risposta alla pressione di un bottone, la posizione della sfera venga modificata. Questo si ottiene semplicemente modificando il valore del campo translation del nodo Transform che contiene la sfera. Per poter modificare tale valore, occorre ottenere una reference al campo. Una volta ottenuta, si potrà impostare il valore voluto, semplicemente assegnando un valore alla variabile che contiene tale reference. Risulta dunque indispensabile identificare in maniera univoca un certo nodo all'interno di un mondo vrml. Infatti, in un file vrml generico potremmo avere una gran quantità di nodi Transform. A quale di questi dunque ci si riferisce quando si vuole settare il campo translation?
Dotando di un nome univoco il nodo che vogliamo modificare si risolve l'ambiguità. L'attribuzione di un nome ad un nodo si ottiene attraverso un opportuno comando vrml : DEF. La sintassi è molto semplice: DEF nome_nodo Transform { ... } A questo punto, dall'applet potremo richiedere espressamente una reference del nodo con un certo nome e successivamente una reference al relativo campo translation. Qui di seguito riporto il sorgente vrml e il codice formante l'applet di controllo (provvederò poi ad analizzare in dettaglio le varie parti di interesse).
 
 

 
 

Si noti come si è identificato in maniera univoca il solo nodo Transform relativo alla sfera. Infatti sarà l'unico che dovrà essere modificato dall'applet di controllo. Il nodo Viewpoint serve per settare la posizione dell'utente. Si possono inserire più nodi Viewpoint all'interno di un mondo vrml ; il primo rappresenta la posizione iniziale. Gli altri sono selezionabili mediante opzioni dal browser (le diverse viste vengono presentate per mezzo delle descrizioni inserite nel campo description).Utilizzare Viewpoint è molto utile per creare dei punti di vista preferenziali cui l'utente può fare riferimento. Il nodo DirectionalLight consente di introdurre una sorgente luminosa. Vrml prevede tre modalità tramite le quali inserire fonti luminose. In questo caso si procede all'inserimento di una sorgente di luce posta a distanza infinita e i cui raggi sono tutti paralleli. Il campo direction specifica la direzione del fascio di raggi. Le specifiche vrml 2.0 non considerano effetti di ombra dovuti alla presenza di un corpo sul tragitto della luce. Il nodo NavigationInfo serve invece per settare alcuni parametri all'interno del mondo. In questo caso viene usato per disattivare la headlight. La headlight è una luce direzionale che punta sempre nella direzione in cui l'utente sta guardando. Se non specificato in maniera diversa, il browser attiva la headlight di default. In questo esempio è stata disattivata in quanto avendo già inserito una luce direzionale si sarebbero ottenuti degli effetti di sovrailluminazione decisamente fastidiosi. Il nodo IndexedFaceSet fornisce invece la possibilità di specificare un oggetto di forma qualsiasi. Infatti non possiamo ridurci sempre ad utilizzare solo coni, cilindri, sfere o box. Con questo nodo è possibile enumerare le singole facce che compongono un certo oggetto. Il campo point (campo del nodo Coordinate) specifica i punti che formano l'oggetto. Il campo coordIndex descrive invece le facce, specificando per ognuna i punti che la compongono. In particolare si riportano gli indici dei punti. L'indice 0 si riferisce al primo punto del campo point, l'indice 1 al secondo e così via. L'indice -1 indica che la faccia deve essere terminata e che ne comincia un'altra. Attenzione all'ordinamento. Le facce visibili devono essere indicate ordinando i punti in senso antiorario. Questo consente al browser di fare back face culling, cioè di non visualizzare le facce interne dell'oggetto, ma solo quell esterne che sono appunto quelle visibili.
Se si volessero avere poligoni con entrambe le facce, non si deve far altro che inserire entrambe gli orientamenti.

Applet java di controllo



// Applet per il controllo della posizione della sfera



import java.applet.*;

import java.awt.* ;

import vrml.external.field.*;

import vrml.external.Node;

import vrml.external.Browser;

import vrml.external.exception.*;

import netscape.javascript.JSObject;



public class GuidaSfera extends Applet {



  Browser browser = null;

  Node sfera = null;

  EventInSFVec3f trasla = null;



  float current_position[] = new float[3];



  final float STEP = 1;



  boolean error = false;



// operazioni da fare una volta sola al primo caricamento dell’applet

  public void init()

  {

    current_position[0] = current_position[2] = 0;

    current_position[1] = 1;



    setLayout(new GridLayout(3,2,5,5));



    add(new Button("X +"));

    add(new Button("X -"));

    add(new Button("Z +"));

    add(new Button("Z -"));

  }



// start viene rieseguito ogni volta che l’applet viene ricaricato

// indispensabile dunque inserire qui l’acquisizione della reference del browser

  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);



    // e' meglio inserire un certo delay prima di accedere ai nodi del file

    // vrml; se il caricamento della scena e' lento, puo' accadere che si

    // cerchi di accedere al nodo ancor prima che il browser abbia caricato

    // il sorgente. Questo portera' ad un errore.



    try {

      Thread.sleep(2000);

    }

    catch(InterruptedException e) {

        showStatus("Errore!");

        error = true;

    }



    try {

      sfera = browser.getNode("Sfera");

      trasla = (EventInSFVec3f) sfera.getEventIn("set_translation");

    }



    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);

    }

  }





  public boolean action(Event event, Object what)

  {



    if (error)

      {

        showStatus("Problemi! Errori in fase di inizializzazione!");

        return true;  

      }

    if (event.target instanceof Button)

    {



        String report = new String();



       Button b = (Button) event.target;



        if (b.getLabel() == "X +") {

                current_position[0] += STEP;

                trasla.setValue(current_position);

        }

        if (b.getLabel() == "X -") {

                current_position[0] -= STEP;

                trasla.setValue(current_position);

        }

        if (b.getLabel() == "Z +") {

                current_position[2] += STEP;

                trasla.setValue(current_position);

        }

        if (b.getLabel() == "Z -") {

                current_position[2] -= STEP;

                trasla.setValue(current_position);

        }

        return true;

    }

    else

        return false;

  }



}

 

Analizziamo in dettaglio il sorgente che costituisce l'applet. Per prima cosa provvediamo ad importare i packages che consentono di accedere alle classi di utilità per la gestione di nodi e campi vrml. Quattro di questi packages vengono rilasciati con il browser, mentre uno è incluso nel pacchetto di Netscape Navigator.
Il tipo Browser serve per contenere una reference al browser vrml. Il tipo Node invece conterrà una reference ad un nodo presente nella scena. Prima di poterla ottenere, è indispensabile avere già acquisito quella relativa al browser ed invocare un opportuno metodo sull'oggetto di tipo Browser. Il metodo getNode consente di ottenere la reference al nodo e richiede come parametro il nome del nodo stesso. Fornendo il nome è dunque possibile identificare in maniera univoca un certo nodo. EventInSFVec3f rappresenta invece l'oggetto deputato a puntare ad un campo vrml. La relativa reference potrà essere acquisita solo dopo l'acquisizione di quella relativa al nodo a cui appartiene. Sull'oggetto di tipo Node è definito il metodo getEventIn il quale consente di ottenere questo risultato. Questo metodo richiede come parametro il nome del campo di cui si vuole ottenere la reference. Dato che i nomi dei campi all'interno di un nodo sono univoci, non ci sono problemi di ambiguità e quindi non sussiste la necessità di identificarli con dei nomi. Dall'analisi dei nodi vrml visti sino ad ora, è chiaro che esistono campi che possono contenere valori diversi. Per esempio un campo translation contiene una tripletta di valori, un campo radius un solo valore frazionario, un campo description di un nodo Viewpoint contiene invece una stringa. Nel caso in esame, EventInSFVec3f indica che si tratta di un campo che deve contenere una tripletta di valori frazionari (SFVec3f). EventIn sta ad indicare che otteniamo una reference all'evento di settaggio del valore del campo (come vedremo in futuro è anche possibile fare in modo che l'applet java venga notificato di particolari eventi che si scatenano all'interno del mondo vrml e quindi in tal caso saremo interessati ad un evento in uscita dal nodo). Campi che contengono una tripletta di valori frazionari saranno dunque referenziati da oggetti di tipo EventInSFVec3f. Viceversa, EventInSFFloat potrà puntare per esempio and un campo radius, EventInSFRotation ad un campo rotation di un nodo Transform (campo che vedremo tra breve) e così via. Oltre a queste variabili viene definita la costante STEP che indica di quanto si deve spostare la sfera per effetto della pressione di un pulsante, e il vettore current_position che contiene i valori correnti di posizione della sfera.
Il metodo init() imposta il layout dell'applet, mentre il metodo start() provvede ad ottenere tutte le references necessarie.
E' indispensabile inserire qui l'acquisizione delle references in quanto questa operazione deve esseree effettuata ogni volta che si procede a ricaricare l'applet (se avessimo incluso tali operazioni all'interno del metodo init(), una successiva operazione di reload avrebbe portato a dei malfunzionamenti, dovuti al fatto che i puntatori precedentemente acquisiti non sono più validi). Il tempo di attesa inserito può essere molto utile in presenza di una fase di caricamento del mondo molto lenta. In tal caso infatti è possibile che la reference al browser sia stata ottenuta, mentre ancora i nodi che formano la scena vrml non sono stati caricati. Si potrebbe dunque verificare un errore nel momento in cui si cerca di ottenere la reference al nodo indicato. Il metodo action(Event, Object) consente di catturare le azioni dell'utente, in particolare la pressione dei pulsanti. In risposta, l'applet deve spostare la sfera. Non si fa altro che aggiornare il vettore incrementando la variabile opportuna. Si provvede poi ad assegnare tale vettore all'oggetto di tipo EventInSFVec3f utilizzando il metodo setValue(float[3]). Tutto qui. L'esempio è molto semplice, ma è già indicativo di come java e vrml possano semplicemente cooperare. Da ultimo riporto la parte html che serve per unire applet e mondo vrml nella stessa pagina.

 

Il secondo esempio che passiamo a considerare è molto simile a quello appena visto. Ci sarà molto utile per consolidare i concetti visti in questo articolo e per vedere altri nodi e campi previsti dalle specifiche vrml 2.0. In particolare ci prefiggiamo di realizzare una rappresentazione del pianeta Terra, fornendo la possibilità di ruotarlo mediante due opportuni bottoni da una applet java (in questo caso si consentirà alla Terra di ruotare anche in senso contrario a quello usuale). Iniziamo a pensare a come si potrebbe realizzare il pianeta terrestre in vrml.
In primo luogo procediamo ad una approssimazione modellando il pianeta come una sfera. Ma come raggiungere un sufficiente grado di realismo ? L'unica soluzione in questo caso è quella di mappare una texture della superficie terrestre sopra la sfera. Ci procuriamo dunque una immagine completa della superficie terrestre e provvediamo poi ad effettuare il successivo mapping. L'utilizzo di textures può aggiungere molto in termini realistici ad un mondo vrml. E risulta indispensabile in casi come questi, dove è impensabile cercare di ottenere gli stessi risultati mediante poligoni.L'uso di textures è inoltre molto utile quando si vogliono aggiungere dettagli e rugosità ad una certa superficie (per esempio per modellizzare certi materiali). Si deve però fare attenzione a non abusare di questo strumento ; troppe textures possono portare ben presto a vistosi rallentamenti in fase di rendering, oltre che a maggiori tempi di download.
Il mapping di textures è un aspetto riguardante l'apparenza esterna di un certo oggetto. Di conseguenza la presenza di textures dovrà essere indicata all'interno del nodo Appearance. Abbiamo già visto che il nodo Appearance contiene il campo material. Oltre a questo, è presente un ulteriore campo che serve ai nostri scopi : texture. texture contiene al suo interno il nodo ImageTexture il quale a sua volta contiene un campo url. Quest'ultimo campo contiene il riferimento al file che definisce l'immagine da mappare. L'url da specificare può essere semplicemente il pathname del file oppure un vero e proprio indirizzo internet, nel caso in cui l'immagine risieda su un altro server.
In particolare, ci si potrebbe addirittura agganciare ad una immagine della Terra aggiornata ogni ora e riportante le diverse situazioni metereologiche (presente al sito da cui ho preso l'immagine che utilizzeremo); in tal caso avremmo una rappresentazione della Terra con una texture che rappresenta l'attuale situazione. Il sorgente del file vrml risulta dunque il seguente.
 

 

I formati supportati per le immagini sono GIF e JPEG. Per l'esempio in questione si utilizza la headlight come luce all'interno del mondo vrml (attivata di default). L'immagine che mapperemo sopra la sfera è riportata qui sotto.

 
 

Come avviene il mapping dell'immagine? Vrml prevede un modo predefinito per mappare le immagini sopra le primitive geometriche built-in (sfera, cono, box, cilindro). Nel caso della sfera, l'immagine viene avvolta attorno alla superficie, il che è anche quello che vogliamo ottenere in questo caso.
Per la Box, vrml provvede a mappare una copia dell'immagine su ogni faccia. Per il cilindro si mappano due immagini sulle due basi circolari e una terza immagine viene avvolta sulla superficie laterale. Infine, nel caso di mapping su cono, si mappa una immagine sulla superficie di base e una viene avvolta su quella laterale.
Diverso è il modo invece con il quale effettuare texture mapping sopra un oggetto definito attraverso un IndexedFaceSet, dove il procedimento risulta decisamente più complicato (si rimanda al tutorial presso il gruppo Vrml Dreamers per approfondimenti: http://www.lucia.it/vrml).

Nel sorgente sopra riportato, ho indicato un campo rotation all'interno del nodo Transform. Il nodo Transform infatti prevede diversi campi che consentono di modificare il sistema di riferimento relativo. In particolare, oltre al campo translation esiste il campo rotation il cui compito è quello di ruotare il sistema di riferimento. Il nodo Shape definito all'interno risentirà dunque della rotazione. Per specificare una rotazione si devono fornire quattro parametri. I primi tre specificano i parametri direttori dell'asse attorno a cui effettuare la rotazione. Il quarto parametro specifica invece di quanto ruotare ; la quantità è espressa in radianti.
Nell'esempio indicato si è specificata una rotazione attorno all'asse y
(1 0 0 = asse x ; 0 1 0 = asse y ; 0 0 1 = asse z).
Dato che il quarto valore è 0, non si effettua alcuna rotazione ; i valori di rotazione saranno modificati in seguito dall'applet di controllo.
Passiamo ora ad analizzare l'applet che procederà a controllare i valori di rotazione da assegnare alla sfera. Si noti che abbiamo identificato in modo univoco il nodo Terra all'interno del mondo vrml. Potremo dunque acquisire una reference ad esso. Non ci sono molte modifiche rispetto all'applet già considerato in precedenza, se non nella diversità del campo da sottoporre a controllo.

// Applet di controllo della rotazione del pianeta Terra



import java.applet.*;

import java.awt.* ;

import vrml.external.field.*;

import vrml.external.Node;

import vrml.external.Browser;

import vrml.external.exception.*;

import netscape.javascript.JSObject;



public class RuotaTerra extends Applet {



  Browser browser = null;

  Node terra = null;

  EventInSFRotation rot = null;



  float current_rotation[] = new float[4];



  float step_rotation = (float) 0.2;



  TextField rotazione ;



  public void init()

  {

    current_rotation[0] = current_rotation[2] = current_rotation[3] = 0 ;

    current_rotation[1] = 1;



    SetLayout(new GridLayout(2,2,5,5));



    add(new Button("Ruota Sinistra"));

    add(new Button("Ruota Destra"));

    add(new Label(“rotazione : “))

    rotazione =  new TextField(“0.2”);

    add(rotazione);

  }



  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);



    // e' meglio inserire un certo delay prima di accedere ai nodi del file

    // vrml; se il caricamento della scena e' lento, puo' accadere che si

    // cerchi di accedere al nodo ancor prima che il browser abbia caricato

    // il sorgente. Questo portera' ad un errore.



    try {

      Thread.sleep(2000);

    }

    catch(InterruptedException e) {

        showStatus("Errore!");

        error = true;

    }



    try {

      terra = browser.getNode("Terra");

      rot = (EventInSFRotation) terra.getEventIn("set_rotation");

    }



    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);

    }

  }



  public boolean action(Event event, Object what)

  {



    Float rot_read = new Float(0);

    String str = new String();

        

    if (error)

      {

        showStatus("Problemi! Errori in fase di inizializzazione!");

        return true;  

      }

    if (event.target instanceof Button)

    {

        String report = new String();



       Button b = (Button) event.target;



        if (b.getLabel() == "Ruota Sinistra") {

        str = rotazione.getText();

        rot_read = rot_read.valueOf(str);

        current_rotation[3] -= rot_read.floatValue();

        rot.setValue(current_rotation);

        }

        if (b.getLabel() == "Ruota Destra") {

        str = rotazione.getText();

        rot_read = rot_read.valueOf(str);

        current_rotation[3] += rot_read.floatValue();

        rot.setValue(current_rotation);

        }

        return true;

    } 

    else

        return false;

  }

}

File HTML :
 
 
 
 
 

Le modifiche rispetto all'applet precedente sono minime. Il campo da sottoporre a controllo è di tipo SFRotation. Il relativo metodo setValue prende come parametro un vettore di quattro elementi di tipo float. Il quarto elemento è quello che definisce la rotazione, mentre i primi tre definiscono l'asse attorno a cui effettuarla. Si è inoltre aggiunta la possibilità di impostare lo step di rotazione attraverso un textfield. Nei due esempi visti sino ad ora abbiamo considerato solo aspetti statici.
Non ci siamo occupati dell'inserimento di animazioni. Si noti come una animazione sia caratterizzata dal susseguirsi nel tempo di diversi stati associati ad un certo oggetto. Nel caso dell'applet che controlla la rotazione della Terra, una sequenza di rotazioni che si susseguono abbastanza rapidamente rappresenta una animazione. Qualsiasi animazione in vrml si basa sul concetto di evento, dove per evento si intende la variazione del valore associato ad un campo.
In questo senso, possiamo notare come le applet di controllo viste sino ad ora si comportano come generatori di eventi. Infatti, ogni volta che settiamo il valore di un campo, non facciamo altro che generare un evento all'interno del mondo vrml. Un programma Java che generi in rapida sequenza i diversi valori di rotazione potrà dunque realizzare una animazione della Terra che ruota attorno al proprio asse. Per animazioni così semplici non si ricorre però direttamente a java. Vrml infatti predispone un set di nodi che consentono di generare essi stessi degli eventi all'interno del mondo. Non solo; esistono nodi che possono monitorare le azioni che l'utente sta compiendo all'interno della scena tridimensionale (per esempio sta cliccando sopra un certo oggetto, oppure è entrato all'interno di una certa area) e generare opportuni eventi in risposta.
Quindi il mondo vrml è in grado di generare eventi, oltre che di riceverli dall'esterno. In tali casi è molto utile poter fare in modo che l'applet di controllo possa essere informata di eventuali eventi sollevati all'interno del mondo vrml, in modo che possa attivare le opportune operazioni di risposta (che consisteranno in altri eventi da mandare al mondo vrml in modo che reagisca in maniera corretta alle azioni dell'utente).

Nel prossimo articolo vedremo dunque di analizzare i meccanismi built-in di vrml per ottenere animazioni e interazione con l'utente. Inoltre ci occuperemo di come una applet java possa captare gli eventi generati dalla scena e quindi programmare le opportune risposte.
 
 
 

 

 

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it