MokaByte
Numero 16 - Febbraio 1998
|
|||
|
|
||
Ignazio Locatelli |
|
||
Le lezioni
precedenti
1.
2.
3.
4.
5.
Interfaccia esterna EAI e alternative
Sino ad
ora è stata analizzata l'interfaccia esterna EAI. Abbiamo visto
come questa soluzione rappresenti un ottimo approccio al problema di controllare
un mondo vrml da Java.
In particolare,
la external authoring interface è l'unica a consentire un controllo
direttamente da una applet.
Esistono tecniche alternative che consentono di programmare comportamenti all'interno di un mondo vrml. Nella prima lezione di questo corso (numero di settembre) sono state analizzate le differenze tra la EAI e l'utilizzo di Java all'interno di un nodo Script. Non intendo qui ripercorrere in dettaglio le differenze tra questi due approcci; basti dire che entrambi forniscono le stesse potenzialità a livello di controllo della scena virtuale.
Una ulteriore
possibilità a livello della programmazione dei comportamenti all'interno
di un mondo virtuale è rappresentata dall'utilizzo di JavaScript.
Quest'ultima alternativa è particolarmente semplice da utilizzare
e esente da particolari problemi di configurazione. Non è però
in grado di offrire la potenza di elaborazione propria di Java e pertanto
si presta maggiormente per la realizzazione di semplici scripts.
Utilizzo di Java dal nodo Script
Le specifiche vrml 2.0 prevedono soltanto questa tecnica di interfacciamento tra Java e Vrml (l'interfaccia EAI rappresenta una proposta di aggiunta allo standard).
Le modalità di interazione con il mondo vrml sono molto simili a quelle analizzate sino ad ora e quindi verranno spiegate velocemente in questo articolo.
Come visto nella
prima lezione di questo corso, l'approccio dal nodo Script prevede che
all'interno del sorgente vrml esista un nodo che indica quali siano gli
elementi che la classe Java dovrà sottoporre a controllo.
La specifica
del nodo Script è la seguente:
Il nodo Script prevede un campo url che contiene la reference alla classe Java controllante il mondo vrml. Tralasciando i campi directOutput e mustEvaluate, il nodo può contenere un qualsiasi numero di campi (field), eventi in ingresso (eventIn) e eventi in uscita (eventOut).
Script {
exposedField MFString url []
field SFBool directOutput FALSE
field SFBool mustEvaluate FALSE
# and any number of:
eventIn eventTypeName eventName
eventOut eventTypeName eventName
field fieldTypeName fieldName initialValue
}
Si deve poi fare attenzione ad un particolare. La classe Java può generare eventi in uscita (quelli indicati dal nodo Script con tipo di interfaccia eventOut), ma questi poi vanno rediretti all'opportuno campo di destinazione mediante un comando di ROUTE (vedere terza lezione).
Vediamo un semplice esempio per chiarire le idee.
L'evento isActive sul sensore di tempo viene inviato al nodo Script. Questo evento potrà essere recepito dalla classe Java in quanto è stato rediretto al campo clicked che è indicato con un tipo di interfaccia eventIn. La classe Java effettuerà delle elaborazioni e produce in uscita un assegnamento al campo new_position (l'assegnamento avviene in modo del tutto analogo al caso della EAI). L'assegnamento ad un campo di tipo eventOut produce un evento in uscita dal nodo Script. Questo viene rediretto dal comando ROUTE al campo translation del cubo modificandone la posizione.
DEF Cubo Transform {
translation 10 1 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box { size 2 2 2 }
}
DEF Touch TouchSensor {}
]
}
DEF cambia_posizione Script {
eventIn SFBool clicked
eventOut SFVec3f new_position
url "CambiaPosizione.class"
}
ROUTE Touch.isActive TO cambia_posizione.clicked
ROUTE cambia_posizione.new_position TO Cubo.set_translation
Vediamo ora come deve essere strutturata la classe Java per poter interagire con il mondo Vrml.
Utilizzando Java dal nodo Script non è possibile definire la classe come estensione di Applet. La si deve invece definire come estensione della classe Script.
Un opportuno metodo initialize() viene invocato prima della generazione di qualsiasi evento. Si tratta di un metodo nel quale effettuare delle operazioni preliminari. Tipicamente viene utilizzato per acquisire le references ai campi elencati nel nodo Script o per inizializzare opportuni elementi di interfaccia (per esempio sfruttando la AWT).
L'acquisizione
delle references ai campi avviene in un modo molto simile a quanto visto
per l'interfaccia esterna.
Dapprima si
devono definire delle variabili adatte a contenere tali references. Per
esempio le seguenti dichiarazioni potrebbero essere impiegate per la classe
CambiaPosizione.java:
import vrml.*;Si può notare come sia molto semplice acquisire una reference ad un campo elencato nel nodo Script. Dato che i campi sono relativi al solo nodo Script, non vi è alcuna necessità di acquisire prima delle references a nodi.
import vrml.field.*;
import vrml.node.*;
class CambiaPosizione extends Script {
private SFBool clicked;
private SFVec3f position;
initialize() {
clicked = (SFBool) getEventIn("clicked");
position = (SFVec3f) getEventOut("new_position");
}
...
...
}
Il settaggio di un evento in uscita avviene semplicemente invocando il metodo setValue, come per esempio:
position.setValue(array_di_3_float);Se si vogliono recepire eventi che si verificano all'interno del mondo vrml esistono due metodi progettati appositamente: ProcessEvent e ProcessEvents.
Il metodo ProcessEvent viene invocato ogni volta che il nodo Script riceve un evento in ingresso. Viceversa ProcessEvents viene invocato nel momento in cui siano sopraggiunti più eventi contemporaneamente. Solitamente tale metodo non fa altro che eseguire un ciclo nel quale si scandiscono i vari eventi e li si inviano singolarmente al metodo ProcessEvent. Per esempio:
public void processEvents(int count, Event events[]) {Passiamo dunque ad analizzare il funzionamento del metodo processEvent.
for (int i=0; i < count; i++)
processEvent(events[i]);
}
Il tipo di dato Event ha la seguente struttura:
class Event {Passando a processEvent un oggetto di tipo Event è quindi possibile risalire al campo sul quale l'evento si è generato, al suo valore e al relativo timestamp.
public String getName();
public ConstField getValue();
public double getTimeStamp();
}
public void processEvent(Event evt) {Esistono altri metodi che possono risultare utili in fase di controllo di un mondo vrml (per es. i metodi shutdown() e eventsProcessed()), ma quelli sino ad ora spiegati sono i più importanti. Per ulteriori approfondimenti vi rimando direttamente alle specifiche vrml 2.0 (o vrml97).
if (evt.getName().equals("clicked))
position.setValue(new_pos);
}
Chiudo qui la trattazione del controllo di un mondo vrml mediante una classe Java instanziata dal nodo Script. Non riporto un esempio esplicito a causa del già citato problema relativo a Cosmo Player 1.0. In realtà è sembrato molto strano che la Silicon Graphics abbia rilasciato la prima versione (la finale, non una beta!) del proprio browser vrml supportando la EAI (non standard) e tralasciando l'approccio previsto dalle specifiche (peraltro supportato benissimo dalle beta precedenti di Cosmo Player). Questa mancanza, unitamente a molti bug, fanno di Cosmo Player 1.0 un browser non molto apprezzato. Perchè allora usarlo in questo corso? semplice: a settembre la versione 2.0 di Cosmo Player ancora non era uscita e CP 1.0 sembrava il browser vrml più diffuso (e forse lo è tuttora).
Sarebbe però
bene che piano piano ognuno di noi cerchi di aggiornarsi alla nuova versione
(la 2.0): il pacchetto è scaricabile direttamente dal sito SGI (http://vrml.sgi.com).
Il passaggio non sarà certo indolore, specie se si è cercato
di programmare mondi abbastanza complessi (un paio di esempi di questo
corso presentano alcuni problemi con CP2.0). Se viceversa state utilizzando
con soddisfazione altri browsers, allora non avrete particolari necessità
di passare a Cosmo Player.
Javascript
Abbiamo più volte notato come sia necessario disporre di strumenti programmativi per procedere alla creazione di comportamenti complessi. Java rappresenta la soluzione più ricca e potente a questo problema, consentendo di espandere notevolmente il numero di applicazioni nelle quali vrml può essere utilizzato con successo.
In molti casi è però possibile evitare il ricorso a Java, soprattutto quando ci si ritrova a dover gestire elaborazioni non molto complese o che comunque non richiedono particolari funzionalità a livello di interfaccia o comunicazione. Le specifiche vrml 2.0 prevedono l'interfacciamento verso Javascript, inserendo direttamente il sorgente all'interno del nodo Script oppure indicando il file sorgente all'interno del campo url.
Le prime versioni dei browser vrml 2.0 prevedevano l'utilizzo di un linguaggio di scripting denominato vrmlscript. Si trattava di un sottoinsieme di javascript, ormai non più utilizzato.
L'utilizzo di uno script in Javascript si presta in particolar modo per svolgere semplici elaborazioni, evitando quindi di far ricorso ad un vero e proprio linguaggio di programmazione. Questo approccio potrebbe risultare quindi molto più semplice per persone a digiuno di esperienza nel campo della programmazione.
Il funzionamento
dell'interfaccia verso Javascript segue di pari passo quanto visto nella
prima parte di questo articolo. Il nodo Script elenca tutti i campi in
ingresso e in uscita al nodo. Le funzioni Javascript possono settare il
valore di un campo in uscita (generando così un eventOut) e recepire
eventuali eventi in ingresso.
Consideriamo
brevemente lo stesso esempio analizzato per l'utilizzo di Java dal nodo
Script.
DEF cambia_posizione Script {Analizzando il sorgente precedente, non si può fare a meno di notare come l'utilizzo di javascript per piccoli calcoli risulti molto più veloce e immediato della corrispondente soluzione Java.
eventIn SFBool clicked
eventOut SFVec3f posizione
field SFVec3f new_posizione 100 0 0
url "javascript:
function clicked(value,ts)
{
if (value == 0)
posizione = new_posizione;
}
"
}
Non voglio procedere con una descrizione dettagliata dell'utilizzo di Javascript. Ci si dovrebbe infatti occupare dei vari metodi esportati dall'oggetto Browser (come per esempio createVRMLFromString) e delle modalità tramite le quali gestire i vari tipi di dati (per esempio come definire array). Per approfondimenti di questo tipo vi rimando direttamente alle specifiche vrml.
Per chiudere questo articolo, riporto ora un esempio che fa ampio utilizzo delle tecniche di javascript.
Si tratta di un semplice puzzle, simile a quelli che usualmente utilizzano i bambini (in questo caso però il soggetto è più adatto ai grandi che ai piccini ;) ). L'utente ha a disposizione una serie di blocchi, ognuno dei quali riportante un pezzo dell'immagine da costruire. Effettuando delle operazioni di drag and drop con il mouse è possibile accostare i blocchi in modo da ricostruire l'immagine.
L'idea di costruire questo semplice esempio mi è venuta vedendo un mondo sul sito della Silicon Graphics che utilizzava la stessa tecnica. Mi sono chiesto come si potesse implementare: la soluzione seguente è la prima che mi è venuta in mente.
Per prima cosa mi sono cercato un ottimo soggetto da raffigurare. Una foto della Schiffer mi sembrava più che indicata. Ho tagliato l'immagine in più parti; ognuna di queste textures viene poi applicata ad un blocco all'interno del mondo vrml.
Il sorgente vrml (che qui non riporto per esteso) consiste di una sequenza di blocchi disposti su una griglia. Ad ogni blocco viene associato un pezzo dell'immagine di partenza. Inoltre, ho provveduto ad inserire un PlaneSensor per ogni blocco.
Il funzionamento
del PlaneSensor è molto semplice (lo abbiamo già visto la
lezione scorsa). Quando l'utente clicca con il mouse sull'oggetto a cui
è associato, emette in uscita degli eventi che consentono di recepire
lo spostamento del cursore a tasto premuto (è in grado cioè
di rilevare l'operazione di drag). Questo evento viene utilizzato per spostare
il blocco in conseguenza dell'azione dell'utente.
E' immediato
capire che serve un PlaneSensor per ogni blocco da spostare, onde evitare
conflitti nella gestione degli spostamenti.
Qui sotto riporto il codice vrml relativo ad un solo blocco. Gli altri blocchi saranno trattati in maniera analoga.
DEF B1 Transform {Il PlaneSensor P1 opera su tutti gli oggetti contenuti nello stesso campo children nel quale si trova: quindi sul cubo a cui è associato.
translation -2 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor .5 .5 1 }
texture ImageTexture { url "img12.jpg" }
}
geometry Box { size 1 1 1 }
}
DEF P1 PlaneSensor {}
]
}
Nell'esempio implementato ho fatto ricorso ad una griglia di 6 righe e 5 colonne (inizialmente avevo optato per una 5x5, ma poi mi sono accorto che avrei dovuto tagliare un pezzo dell'immagine che senz'altro attira molto ;) ). Di conseguenza si dovranno inserire 30 blocchi, con i relativi sensori.
L'evento translation_changed
in uscita dal nodo PlaneSensor viene intercettato da un nodo Script, il
quale provvede ad elaborarlo e a muovere il blocchetto corrispondente.
Il nodo Script
sfrutta Javascript per implementare le semplici elaborazioni richieste.
Per questo tipo di applicazione sarebbe del tutto inutile passare a Java.
Il nodo Script
riceve in ingresso gli eventi translation_changed. Vi saranno quindi 30
campi di tipo eventIn. Dato che deve poter modificare la posizione di ciascun
blocco, dovranno essere presenti anche 30 campi di tipo eventOut. Infine,
vengono inseriti anche dei campi di tipo field, utilizzati per effettuare
un corretto calcolo degli spostamenti.
Per capire a
fondo il funzionamento dei campi di tipo field, si deve porre particolare
attenzione al funzionamento del nodo PlaneSensor. Questo nodo emette eventi
translation_changed quando il mouse viene spostato (a pulsante premuto)
sopra l'oggetto a cui è associato. Gli eventi translation_changed
indicano lo spostamento rispetto al punto iniziale di pressione (quindi
in ogni momento ritorna la distanza rispetto al punto di pressione iniziale).
Si può ben capire come sia dunque necessario ricordarsi della precedente
posizione del blocco, in modo da poterla sottrarre dal valore ritornato
dall'evento translation_changed. Il risultato della sottrazione indica
di quanto si è spostato il mouse dall'ultima operazione di spostamento.
A questo punto basterà sommare tale step (che in realtà è
un array di 3 elementi x,y,z) al valore di posizione precedente.
Come si può notare le elaborazioni svolte da javascript sono molto semplici. Un utilizzo di Java in questo caso sarebbe stato del tutto fuori luogo.
Qui sotto riporto alcuni pezzi del nodo Script:
DEF muovi_puzzle Script {
eventIn SFVec3f new_pos1
eventIn SFVec3f new_pos2
...
eventIn SFVec3f new_pos30
# campi di appoggio utilizzati per ricordarsi della posizione
# attuale del blocco
field SFVec3f pos_1 -2 0 0
field SFVec3f pos_2 -1 0 0
...
field SFVec3f pos_30 2 -1 0
eventOut SFVec3f out_pos1
eventOut SFVec3f out_pos2
...
eventOut SFVec3f out_pos30
field SFVec3f stepx 0
field SFVec3f stepy 0
url "javascript:
function new_pos1(value) {
stepx = value[0] - pos_1[0];
stepy = value[1] - pos_1[1];
pos_1[0] += (stepx - 2);
pos_1[1] += stepy;
out_pos1 = pos_1;
}
...
function new_pos30(value) {
...
}
"
}
#sequenza di ROUTE
ROUTE P1.translation_changed TO muovi_puzzle.new_pos1
ROUTE P2.translation_changed TO muovi_puzzle.new_pos2
...
ROUTE P30.translation_changed TO muovi_puzzle.new_pos30
ROUTE muovi_puzzle.out_pos1 TO B1.set_translation
ROUTE muovi_puzzle.out_pos2 TO B2.set_translation
...
ROUTE muovi_puzzle.out_pos30 TO B30.set_translation
L'esempio è abbastanza semplice. Penso comunque che sia più che adatto per comprendere il funzionamento dell'interfaccia verso Javascript.
Il prossimo articolo
sarà dedicato all'analisi di un progetto abbastanza complesso: come
realizzare animazione umana in vrml. Questo argomento sta assumendo sempre
maggiore importanza sulla scena vrml. Cercherò di restare sul semplice,
analizzando i meccanismi fondamentali e fornendo un esempio pratico (molto
semplificato). Una applicazione molto più complessa in quest'ambito
può essere trovata presso il gruppo VRML DREAMERS (http://www.lucia.it/vrml),
sezione progetti - Frank (e costituisce anche la base della tesi che dovrò
dare al Politecnico di Milano il 19 febbraio).
L'analisi di
questo progetto ci fornirà comunque l'occasione per approfondire
le tecniche di programmazione vrml e dell'interfaccia verso Java (torneremo
ad utilizzare l'interfaccia EAI).
Giunti a questo
punto, con alle spalle tutta la trattazione sulla EAI e sul nodo Script,
sarebbe per me molto importante sapere se gli esempi che via via fornisco
sono pienamente accessibili e chiari. Se pertanto qualcuno reputa che siano
troppo complicati o duri da digerire, fatemelo sapere, in modo che mi possa
regolare. Come avrete notato, gli esempi sono andati via via complicandosi
con il passare delle lezioni (ma questo è naturale). Il mio indirizzo
email è: ignazio@logicom.it.
|
||
MokaByte ricerca
nuovi collaboratori.
|
||
|