ROBOTICA

video
immagini
papers
progettati
costruiti
toolbox
vrml
simulatori

links

 

Papers
meccanica
sistemi
documents

Capitolo 33° Vrml + Java Indice

E' giunto il momento di fare un salto di qualita' nella creazione di mondi vrml. Fino ad ora ci siamo occupati di analizzare i meccanismi resi disponibili dalle specifiche 2.0 per poter modellizzare il mondo e interagire con esso.

In precedenza ci eravamo gia' accorti che vrml da solo non puo' bastare per una vasta gamma di applicazioni. E avevamo preso in considerazione l'utilizzo di vrmlscript per creare comportamenti piu' complessi, non possibili con i meccanismi standard. Ma vrmlscript costituisce solo un piccolo passo verso la soluzione a questi problemi; infatti questo linguaggio di scripting, sottoinsieme di javascript (che viene usato in alcuni browser vrml 2.0 come ad esempio WorldView), non consente di avere accesso alla potenza che un vero linguaggio di programmazione puo' rendere disponibile. Puo' essere impiegato per compiere alcuni calcoli matematici, per gestire in modo piu' pulito alcuni eventi da indirizzare al mondo vrml, ma poco piu'.

Supponiamo di volere creare una interfaccia a finestre da cui controllare il mondo vrml. L'utente selezionando dai vari bottoni presenti sulla finestra puo' imporre che avvengano certe azioni nel mondo. Per esempio potrebbe manovrare un manichino virtuale.
Oppure ancora, vogliamo realizzare un semplice mondo vrml in multi-utenza, a cui si possono collegare diversi utenti di internet, ed in cui ognuno viene rappresentato con il suo avatar.

(questi riferimenti non sono campati in aria, e in effetti corrispondono a progetti in corso presso il gruppo 'Vrml Dreamers' dove questo tutorial viene reso disponibile).

In tutte queste situazioni vrml non puo' farcela da solo; e neppure vrmlscript o javascript possono essere di aiuto.

L'uso di Java combinato a vrml rappresenta un mezzo potentissimo per la gestione di mondi virtuali su internet. Come vedremo tra breve il suo impiego e' abbastanza semplice, anche se viene richiesta una conoscenza del linguaggio, senza la quale non e' possibile ottenere risultati apprezzabili (anzi... non e' possibile ottenere proprio alcun risultato!).

Java e' un linguaggio di programmazione a tutti gli effetti. Ha conosciuto grossa diffusione (a partire dal 1995) grazie ad una caratteristica fondamentale: la portabilita'. Infatti il codice Java viene compilato in una forma intermedia, nota come byte code. Il byte code viene poi interpretato dalla Java Virtual Machine che lo esegue. Questo meccanismo consente ad un programma scritto in java di essere eseguito su qualsiasi macchina, purche' questa abbia l'interprete del byte code.
Un uso molto interessante di java si ha con le applets, che consistono in programmi java che vengono scaricati da internet ed eseguiti in locale all'interno di pagine html.

Non voglio qui trattare le caratteristiche di java; questo e' un tutorial su vrml dopotutto. Se siete interessati ad usare vrml in modo avanzato, quello che posso consigliarvi e' di acquistarvi anche un buon libro di java o di recuperare in rete alcuni dei molti tutorials sull'argomento.

Detto questo, da qui in avanti assumo che conosciate a grandi linee come scrivere un programma java. Nei primi esempi cerchero' comunque di andare molto piano e di fornire qualche commento qua e la'.

Prima di iniziare, faccio presente che il codice presentato e' stato compilato utilizzando il JDK 1.0.2 (il JDK 1.1 va comunque benissimo). Potete trovarlo sul sito della Sun http://www.javasoft.com.

Un primo semplice esempio.

Java puo' essere impiegato in modi diversi all'interno di un mondo vrml. Qui di seguito viene presentata la modalita' standard, come prevista dalle specifiche vrml 2.0, lasciando l'uso dell'interfaccia esterna (EAI), non ancora standardizzata, ad una successiva lezione.

Java in un mondo vrml non gira come un applet; infatti la classe deve essere definita come estensione di una classe Script che viene fornita con il browser vrml. Assieme al browser infatti vengono distribuite tutte le classi che servono per interfacciare il mondo vrml con la classe.

Non analizzeremo ora in dettaglio la struttura delle classi rese disponibili. E' mio intendimento procedere tramite esempi, in modo da rendere piu' pratica la discussione. Per dettagli tecnici sulla gerarchia delle classi e sulle varie caratteristiche dei singoli tipi potete rivolgervi direttamente alle specifiche.

Supponiamo ora di volere implementare una classe java da collegare ad un mondo vrml che risolve il problema, per cui si era trovata una soluzione con vrmlscript, di far partire una animazione in conseguenza di un click su un certo oggetto (per il momento restiamo sul molto semplice; in seguito vedremo applicazioni piu' complesse, sino ad arrivare ad interfacce a finestra per controllare il mondo).

Ricapitolando velocemente, il seguente sorgente vrml presentava un problema (si dovrebbe far rolotare una sfera da un punto ad un altro quando si clicca su un certo sensore).

#VRML V2.0 utf8
#esempio errato
#ripiano su cui poggia la sfera
Shape {
 appearance Appearance {
   material Material { diffuseColor 0 1 0 }
 }
 geometry Box { size 12 1 2 }
}
DEF pos_sfera Transform {
  translation -5 1.5 0 
  children [
    Shape {
      appearance Appearance {
        material Material { diffuseColor 1 0 0 }
      }
      geometry Sphere {}
    }
    DEF touch TouchSensor {}
  ]
}
DEF tempo TimeSensor {
  cycleInterval 8
  enabled FALSE
  loop FALSE
  startTime 1
}
DEF posizioni_sfera PositionInterpolator {
  key [0 .5 1]
  keyValue [-5 1 0, 5 1 0, -5 1 0]
}
ROUTE touch.isActive TO tempo.enabled 
ROUTE touch.touchTime TO tempo.startTime 
ROUTE tempo.fraction_changed TO posizioni_sfera.set_fraction
ROUTE posizioni_sfera.value_changed TO pos_sfera.set_translation

Questo esempio presenta un comportamento scorretto. Come avevamo gia' visto a suo tempo, il campo isActive del sensore passa a TRUE quando viene cliccato e a FALSE quando viene rilasciato. Questo comporta la generazione di 2 eventi. Il primo fa partire l'animazione, mentre il secondo la deattiva. Ne segue che per far completare il viaggio della sfera l'utente deve tenere premuto in continuazione il sensore... che in questo caso e' rappresentato dalla sfera stessa; il massimo della scomodita'!

Avevamo visto che con vrmlscript si puo' risolvere ampiamente il problema. Vediamo ora come risolvere con Java.

Si deve inserire ancora una volta un nodo Script; la parte relativa agli eventi da importare, quelli da esportare e ai campi da definire rimane uguale.

Quindi anche in questo caso il nodo Script avra' la seguente forma:

DEF trigger Script {
  eventIn    SFBool     click 
  eventOut   SFBool     inizia
  eventOut   SFTime     tempo_partenza
  field      SFBool     premuto          FALSE
  url "nomeclasse.class"
}

Come si puo' notare si mantiene molto della struttura gia' vista; del resto l'interfaccia del nodo Script deve restare uguale. Deve cambiare solo il modo con il quale il lavoro viene svolto.

Nel campo url si specifica il nome della classe java (ottenuta per compilazione di un file .java).

Ricordo brevemente che i field vanno inizializzati (nel nostro caso abbiamo un field premuto che viene inizializzato a FALSE). I campi eventOut sono eventi in uscita dal nodo Script. Quando vengono generati un eventuale ROUTE puo' ridirigerli verso un altro nodo. In questo caso l'evento in uscita 'inizia' verra' indirizzato al campo enabled del sensore di tempo per farlo partire. Un campo eventIn riceve invece eventi dal mondo vrml e questo provoca l'esecuzione di alcune funzionalita' all'interno della classe java. Il programmatore non deve fare altro che programmare le opportune risposte agli eventi in ingresso; queste risposte vengono definite all'interno della classe java.

Passiamo ora a considerare la classe java che consente di risolvere il problema che ci siamo posti.

import vrml.*;
import vrml.field.*;
import vrml.node.*;

class trigger_sfera extends Script {
   private SFBool start_sfera;
   private SFTime tempo_start;
   private SFBool premuto;
   public void initialize() {
       start_sfera = (SFBool) getEventOut("inizia");
       tempo_start = (SFTime) getEventOut("tempo_partenza");
       premuto = (SFBool) getField("premuto");
       premuto.setValue(false);
        }
   public void processEvents(int count, Event events[]) {
     int i;
       for (i = 0; i < count; i++) 
         if (events[i].getName().equals("click"))
           decidi_partenza(events[i].getTimeStamp());
        }
        public void decidi_partenza(double tempo) {
                if (premuto.getValue() == false) {
                        premuto.setValue(true);
                        start_sfera.setValue(true);
                        tempo_start.setValue(tempo);
                }
                else
                        premuto.setValue(false);
        }
}

Cerchiamo ora di analizzare i punti fondamentali della classe.

Inizialmente si importano i packages che ci servono. Si tratta di una serie di classi che ci servono per gestire all'interno di una classe java i tipi vrml, nonche' l'arrivo di eventi dal mondo vrml. Nell'esempio qui riportato ho importato tutto cio' che era importabile; in teoria si potrebbe importare solo i tipi e le classi che vengono utilizzate.

Come dicevo precedentemente la classe viene derivata dalla classe Script. Non serve un metodo main nella classe per farla partire; viene lanciata direttamente dal browser vrml.

Seguono le dichiarazioni delle variabili della classe. In particolare qui definisco 3 variabili che rispecchiano i 3 campi del nodo Script, field e campi eventOut. Non mi servono variabili per i campi eventIn in quanto il loro valore viene impostato automaticamente quando viene sollevato l'evento.

Il metodo Initialize serve per effettuare delle operazioni di inizializzazione sulle variabili. Viene invocato quando la classe viene caricata per la prima volta. In particolare, in essa possiamo legare le variabili della classe ai corrispondenti campi nel nodo Script. Questo e' possibile tramite i metodi getEventOut e getField.
Su una variabile cosi' definita e' possibile effettuare una operazione di assegnamento, come per il caso del field premuto (premuto.setValue(false)), oppure operazioni di lettura (premuto.getValue()).

In particolare settando il valore di una variabile che rappresenta un eventOut genera un evento in uscita dal nodo Scritp, in maniera analoga a quanto gia' visto per vrmlscript.

Per gestire gli eventi in ingresso utilizziamo il metodo ProcessEvents. ProcessEvents viene invocato automaticamente quando il nodo Script riceve un evento in ingresso. I due parametri indicano rispettivamente il numero di eventi ricevuti e la lista di tali eventi. All'interno del corpo di questo metodo vengono analizzati tutti gli eventi ricevuti; testando il nome dell'evento (con un semplice if) si possono invocare i metodi opportuni che devono gestire le risposte agli eventi.
Su una variabile di tipo Event sono possibili le operazioni di:

Nel caso in esame abbiamo un solo evento e quindi il test risulta superfluo. Invochiamo il metodo decidi_partenza passando come parametro il tempo di pressione che viene mappato su un double (numero reale).

All'interno del metodo decidi_partenza mi domando se la variabile premuto ha valore false. In caso affermativo faccio partire l'animazione. Quando l'utente rilascera' il mouse, arrivera' un secondo evento il quale verra' processato dal metodo ProcessEvents. Ancora una volta verra' invocato il metodo decidi_partenza. Questa volta pero' il valore della variabile premuto e' a true; quindi non si fa altro che mettere premuto a false. Non si manda in particolare nessun evento false al campo enabled; l'animazione pertanto continua a girare.

Potrebbe sembrare molto complicato a prima vista ricorrere a java per un esempio di questo tipo. E infatti per questi casi l'uso di javascript o vrmlscript risulta piu' consono. Per compiti piu' complicati l'approccio java risultera' pero' l'unica soluzione plausbile.

Si tenga presente che il metodo Initialize e ProcessEvents ci saranno prati- camente sempre nella classe java. Il metodo decidi_partenza e' stato invece impiegato per programmare una risposta all'evento.

Infine, vale la pena considerare un aspetto un po' ambiguo presente all'interno del mondo appena realizzato. Secondo voi... cosa succede se l'utente dopo avere premuto e rilasciato una prima volta, riesce a cliccare sulla sfera in movimento?
La risposta non e' poi cosi' banale; diciamo pure che al momento ce la caviamo di lusso. La sfera continua la sua marcia come se niente fosse; cosa che del resto ci va bene. Giunge alla fine della sua corsa e poi potremo riattivarla. Questo funziona anche se la seconda pressione comporta la generazione di un evento su start_sfera e uno su tempo_start. Mentre il primo evento non cambia il valore TRUE del campo enabled del TimeSensor, il secondo cercherebbe di cambiargli il tempo di inizio, riportando la sfera di colpo all'inizio della animazione. Questo pero' non avviene in quanto il campo startTime puo' essere settato solo se il TimeSensor non sta gia' funzionando, se cioe' il campo enabled e' FALSE. E questo avverra' automaticamente solo quando l'animazione sara' finita (in quanto abbiamo imposto loop a false).

Qui di seguito riporto il sorgente complessivo, dove aggiungo quei pochi ROUTE che ancora servono, su cui penso non abbiate alcuna difficolta' di comprensione.

Per provare direttamente il sortgente: Movimento sfera con Java

Ricordo che questa non vuole essere una discussione esaustiva delle possibilita' di interfacciamento tra java e vrml, ma solo una semplice panoramica. Nella prossima lezione vedremo di approfondire ulteriormente.

#VRML V2.0 utf8
#esempio corretto
#ripiano su cui poggia la sfera
Shape {
 appearance Appearance {
   material Material { diffuseColor 0 1 0 }
 }
 geometry Box { size 12 1 2 }
}
DEF pos_sfera Transform {
  translation -5 1.5 0 
  children [
    Shape {
      appearance Appearance {
        material Material { diffuseColor 1 0 0 }
      }
      geometry Sphere {}
    }
    DEF touch TouchSensor {}
  ]
}
DEF tempo TimeSensor {
  cycleInterval 8
  enabled FALSE
  loop FALSE
  startTime 1
}
DEF posizioni_sfera PositionInterpolator {
  key [0 .5 1]
  keyValue [-5 1.5 0, 5 1.5 0, -5 1.5 0]
}
DEF trigger Script {
  eventIn    SFBool     click 
  eventOut   SFBool     inizia
  eventOut   SFTime     tempo_partenza
  field      SFBool     premuto          FALSE
  url "trigger_sfera.class"
}

ROUTE touch.isActive TO trigger.click
ROUTE trigger.inizia TO tempo.enabled 
ROUTE trigger.tempo_partenza TO tempo.set_startTime
ROUTE tempo.fraction_changed TO posizioni_sfera.set_fraction
ROUTE posizioni_sfera.value_changed TO pos_sfera.set_translation