MokaByte Numero 12 - Ottobre 1997 |
|||
|
Java e VRML |
||
di |
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).
#setto la posizione iniziale dell’utente
Viewpoint {
description “posizione iniziale”
position 0 1.7 10
}
# questo nodo consente di specificare alcuni settaggi
iniziali
NavigationInfo {
headlight FALSE
}
#introduco una fonte luminosa direzionale
DirectionalLight {
direction 0 -1 -1
}
# prima definisco il piano : uso una primitiva
geometrica non ancora introdotta
Shape {
appearance Appearance {
material Material { diffuseColor
0 1 0 }
}
geometry IndexedFaceSet {
coord Coordinate {
point [-100 0
100, 100 0 100, 100 0 -100, -100 0 -100]
}
coordIndex [0,1,2,3,-1]
}
}
DEF Sfera Transform {
translation 0 1 0
children [
Shape {
appearance Appearance
{
material
Material { diffuseColor 1 0 0 }
}
geometry Sphere
{ radius 1 }
}
]
}
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.
Viewpoint
{
position 0 0 1000
description “entry view”
}
DEF
Terra Transform {
rotation 0 1 0 0 # così come è adesso non
fa nulla
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 .5 .5 }
texture ImageTexture { url “terra.jpg” }
}
geometry Sphere { radius 10 }
}
]
}
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
File HTML :
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;
}
}
<HTML>
<HEAD> <TITLE> Terra </TITLE> </HEAD>
<CENTER>
<EMBED SRC=”Terra.wrl” height = 300 width = 500> <P>
<APPLET CODE=”RuotaTerra.class” mayscript WIDTH=200 HEIGHT=60>
</APPLET>
</CENTER>
</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 |
||
|