MokaByte
Numero 15 - Gennaio 1998
|
|||
|
|
||
Ignazio Locatelli |
|
||
Riassunto delle lezioni precedenti
In questo articolo vedremo di consolidare le nozioni acquisite nei precedenti, cercando di introdurre semplici novità dal punto di vista dei nodi resi disponibili dalle specifiche vrml 2.0. Procederemo ad analizzare un paio di esempi: il primo consente di introdurre attrazione gravitazionale sugli oggetti all'interno del mondo; il secondo consiste invece in un semplice gioco vrml, incentrato sull'utilizzo della stessa legge gravitazionale spiegata nell'esempio precedente.
Prima però
vale la pena ripercorrere brevemente il percorso seguito sino a questo
punto.
Abbiamo notato
come sia banale modificare un mondo vrml da una applet Java. Attraverso
semplici metodi è infatti possibile acquisire un riferimento alla
scena virtuale e procedere alla modifica di nodi e campi vrml.
Abbiamo poi
visto come sia spesso indispensabile poter sentire il verificarsi di eventi
all'interno della scena virtuale: capire in pratica quando si verifica
un evento e di quale evento si tratta. Per conseguire questo risultato
si deve implementare l'interfaccia EventOutObserver e definire il metodo
callback. Sfruttando questa tecnica è pertanto possibile gestire
gli eventi generati all'interno del mondo vrml, consentendo un controllo
interattivo della scena.
Infine, la volta
scorsa abbiamo visto come sia relativamente semplice procedere ad una modifica
sostanziale dell'ambiente virtuale aggiungendo o rimuovendo nodi a tempo
di esecuzione.
Ricapitolando,
i passi fondamentali seguiti nell'esposizione dell'interfaccia Java-Vrml
sono stati i seguenti:
- modifica del
valore di campi e nodi vrml
- monitoraggio
di eventi e relativa gestione
- creazione
a run-time di oggetti vrml
Gli argomenti sino a questo punto trattati sono le basi portanti dell'interfaccia Java-Vrml (sia attraverso l'utilizzo della EAI che dal nodo Script).
Ritengo a questo
punto molto utile soffermarsi sull'esame di un paio di esempi, in modo
da rafforzare le conoscenze acquisite e chiarirne l'utilizzo. Nel prossimo
articolo si vedrà brevemente l'approccio all'interazione tra Java
e Vrml dal nodo Script, nonchè l'utilizzo di JavaScript all'interno
di mondi vrml. I successivi saranno invece incentrati su esempi più
complessi e, a mio avviso, più interessanti.
Introdurre attrazione gravitazionale sugli oggetti
Subito
è necessaria una precisazione. Vrml implementa già una forma
di attrazione gravitazionale (l'opzione gravity del browser). Questa agisce
nei confronti del visitatore, forzandolo a rimanere ancorato al terreno
(se esistente).
Non esiste però
un concetto analogo per quanto riguarda gli oggetti presenti all'interno
del mondo. Nulla vieta che esistano dei blocchi sospesi magicamente nel
vuoto. Non voglio dire che questo non debba accadere: per esempio potremmo
volutamente decidere di implementare un ambiente in assenza di gravità,
oppure anche un mondo nel quale le leggi fisiche sono completamente sovvertite.
Ma se l'obiettivo che ci poniamo è quello di ricreare un ambiente
il più possibile realistico dobbiamo preoccuparci della gestione
di questo aspetto.
Ma perchè si dovrebbero avere degli oggetti sospesi nel vuoto? A parte il caso banale di un errore nel posizionamento da parte del programmatore (contro il quale non si vuole ovviamente agire), si potrebbero avere oggetti non ancorati al suolo in quanto vogliamo fornire all'utente la possibilità di spostarli. In un mondo 'reale' è infatti possibile sollevare oggetti non troppo pesanti... così come poi è naturale aspettarsi che questi oggetti cadano al suolo in conseguenza del loro rilascio.
L'esempio che
procediamo ora ad analizzare considera appunto questi due fattori: possibilità
di sollevare oggetti e loro caduta in conseguenza dell'abbandono.
Il sollevamento
deve essere tale da risultare più difficoltoso per oggetti molto
pesanti, mentre molto più semplice per oggetti di peso inferiore.
La caduta deve seguire la nota legge di Newton, subendo una accelerazione
pari a g.
L'esempio riportato
introduce alcune semplificazioni:
- lo 'sforzo'
da effettuare per il sollevamento non dipende da g ma direttamente da un
coefficiente di peso
- lo spostamento
consentito sugli oggetti riguarda solo un moto traslatorio lungo l'asse
y
Entrambe queste limitazioni possono essere rimosse senza eccessiva fatica.
Sollevamento
dell'oggetto
Il modo più
naturale per consentire all'utente di sollevare un oggetto è quello
di utilizzare un meccanismo di drag and drop. Effettuando un drag verso
l'alto è possibile trascinare l'oggetto lungo la stessa direzione.
Ma come è possibile implementarlo dal punto di vista pratico?
Per prima cosa
si deve notare che si tratta di eventi vrml. La pressione e il trascinamento
del mouse avvengono all'interno della scena. Sarà dunque necessario
implementare l'interfaccia EventOutObserver per monitorare il verificarsi
di tali eventi.
Sino ad ora
abbiamo analizzato solo un nodo che genera eventi in conseguenza della
pressione del mouse: TouchSensor. Tale nodo non si presta però alla
risoluzione del nostro problema (sebbene contenga un campo la cui funzione
è quella di segnalare lo spostamento del mouse a pulsante premuto).
Utilizzeremo
un nodo PlaneSensor. Un PlaneSensor, come un nodo TouchSensor, deve
essere inserito all'interno del campo children nel quale l'oggetto è
definito. Esso definisce un piano immaginario che è in grado di
registrare i movimenti del mouse a pulsante premuto.
Il sensore manda eventi solo quando il mouse viene premuto con il cursore posizionato sopra l'oggetto definito all'interno del campo children (stesso meccanismo del nodo TouchSensor).DEF oggetto Transform {
children [
Shape {
appearance Appearance { material Material { diffuseColor 1 0 0 } }
geometry Sphere {}
}
DEF sensore PlaneSensor {}
]
}
Attraverso l'interfaccia EventOutObserver, possiamo fare in modo che l'applet java venga notificata del verificarsi di tali eventi (sfruttando il metodo advise visto nel terzo articolo). All'interno del metodo callback è dunque possibile accedere alla componente di traslazione rispetto all'asse y e quindi mapparla sulla posizione effettiva dell'oggetto.
Come simulare
il diverso peso di un oggetto? L'idea l'ho presa da un libro di realtà
virtuale di Joseph Gradecky, il quale implementava lo stesso meccanismo
utilizzando il mitico Rend386. La maggiore forza richiesta nello spostamento
di un oggetto pesante può essere simulata facendo in modo che a
pari spostamento del mouse lo spostamento effettivo dell'oggetto sia minore.
In pratica un oggetto leggero richiederà spostamenti minimi del
cursore; viceversa uno pesante richiederà operazioni di drag molto
più consistenti.
Per conseguire
questo risultato non si fa altro che dividere la componente di traslazione
lungo y per un coefficiente di peso. Pesi maggiori implicano minori spostamenti
a pari traslazioni del cursore.
Perchè
limitare gli spostamenti alla sola componente y? Perchè non considerare
anche gli spostamenti lungo x e z e mapparli nello stesso modo?
Vi sono diversi
motivi. In primo luogo perchè il nodo PlaneSensor nasconde un subdolo
problema quando viene utilizzato per spostare oggetti. Se infatti ci si
posiziona sul lato opposto del piano, spostamenti positivi lungo l'asse
x sono in realtà negativi per l'oggetto. Questo richiede pertanto
un certo processing da parte dell'applet java per gestire in modo corretto
il segno. Inoltre si deve considerare la possibilità di collisione
con altri oggetti posizionati sul piano, complicando di molto la gestione
dell'ambiente (nell'esempio riportato si gestisce solo la collisione con
il pavimento).
Caduta dell'oggetto
Una volta sollevato
un oggetto, questo deve ricadere al suolo se l'utente rilascia il pulsante
del mouse. L'evento di rilascio può essere rilevato semplicemente
monitorando il campo isActive del PlaneSensor. In tale occasione infatti
viene generato un evento di valore FALSE.
Una volta determinato
che l'oggetto è stato rilasciato, si deve procedere ad attivare
la fase di caduta. Questa deve seguire la legge di Newton, applicando all'oggetto
una accelerazione pari a g (trascuriamo, per ovvi motivi, l'attrito esercitato
dall'aria). Rispolverando qualche antica nozione di fisica imparata nel
biennio, l'equazione per determinare lo spazio percorso dopo un certo tempo
t dovrebbe essere la seguente: spazio = g * t * t.
La gestione
del posizionamento dell'oggetto viene effettuata sempre da applet. Serve
però un meccanismo per poter tenere traccia del trascorrere del
tempo (il parametro t della formula). A tal fine è stato introdotto
all'interno del mondo vrml un nodo TimeSensor, con campo loop posto a FALSE
e un campo cycleInterval sufficientemente alto (poi vedremo perchè).
In occasione
del rilascio del mouse (campo isActive del PlaneSensor a FALSE), si imposta
il campo startTime del nodo TimeSensor all'etichetta temporale corrente
(questo forza il sensore ad iniziare il ciclo temporale in questo preciso
istante). Subito dopo si procede ad attivare il nodo TimeSensor settando
a TRUE il campo enabled.
Dal momento
dell'attivazione, il nodo TimeSensor inizia ad emettere eventi fraction_changed.
Questi vengono recepiti dal metodo callback (ci si era posti in ascolto
invocando il metodo advise sulla reference al relativo campo). All'interno
del metodo callback è dunque possibile determinare la nuova posizione
dell'oggetto mediante la seguente equazione:
posizione =
altezza di partenza - g * t * t
Un semplice
test è in grado di determinare l'avvenuta collisione dell'oggetto
con il terreno. In tale occasione si deve disattivare il sensore di tempo.
Si può dunque comprendere il motivo del settaggio del campo cycleInterval
con un valore sufficientemente alto. Infatti si deve evitare il ritorno
a zero del campo fraction_changed, situazione che porterebbe l'oggetto
a ripartire dalla posizione iniziale.
A questo punto
dovrebbe risultare abbastanza semplice comprendere il sorgente del file
vrml e della classe Java che lo controlla. La demo prevede la presenza
di due blocchi cubici posizionati sopra un piano. Quello di colore rosso
risulta essere molto leggero (peso 1), mentre quello di colore blu ha un
coefficiente di peso maggiore (peso 5).
Sorgente Vrml:
Il sorgente dell'applet Java è il seguente:#VRML V2.0 utf8
# semplice mondo per dimostrare l'utilizzo di attrazione gravitazionale
#posizione iniziale dell'utente
Viewpoint {
description "entrance"
position 0 1 0
}
#definizione del background
Background {
skyAngle [1 1.57 3.14]
skyColor [0 0 1, 0 0 1, .6 .6 1, 0 0 1]
}
# piano a quota 0
Shape {
appearance Appearance {
material Material { diffuseColor 0 .5 0 }
}
geometry IndexedFaceSet {
coord Coordinate {
point [
-100 0 100, 100 0 100, 100 0 -100, -100 0 -100
]
}
coordIndex [ 0,1,2,3,-1 ]
}
}
# prima scatola (di colore rosso e leggera)
DEF Box1 Transform {
translation 2 .5 -8
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box { size 1 1 1 }
}
DEF sensore1 PlaneSensor {}
]
}
# seconda scatola (di colore blu e pesante)
DEF Box2 Transform {
translation -2 .5 -8
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 0 1 }
}
geometry Box { size 1 1 1 }
}
DEF sensore2 PlaneSensor {}
]
}
# sensore di tempo per il controllo della caduta della prima scatola
DEF orologio1 TimeSensor {
enabled FALSE
loop FALSE
cycleInterval 100
}
# sensore di tempo per il controllo della caduta della seconda scatola
DEF orologio2 TimeSensor {
enabled FALSE
loop FALSE
cycleInterval 100
}
Come sempre, un file html dovrà includere il file vrml e l'applet all'interno della stessa pagina: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 Gravita extends Applet implements EventOutObserver {
// riferimento al browser vrml
Browser browser;
// riferimenti ai nodi delle box
Node box1 = null;
Node box2 = null;
// campi per il loro posizionamento
EventInSFVec3f translation_box1 = null;
EventInSFVec3f translation_box2 = null;
// riferimenti ai nodi che definiscono i sensori
Node sensore1 = null;
Node sensore2 = null;
// eventi per il controllo delle azioni dell'utente
EventInSFVec3f offset_sensor_1 = null;
EventInSFVec3f offset_sensor_2 = null;
EventOutSFVec3f hit_changed_1 = null;
EventOutSFVec3f hit_changed_2 = null;
EventOutSFBool isActive_1 = null;
EventOutSFBool isActive_2 = null;
// nodi per il sensore di tempo
Node time_sensor1 = null;
Node time_sensor2 = null;
// eventi per la gestione dei nodi temporali (animazione di caduta)
EventInSFBool enable_time1 = null;
EventInSFTime startTime1 = null;
EventOutSFFloat fraction_changed1 = null;
EventInSFBool enable_time2 = null;
EventInSFTime startTime2 = null;
EventOutSFFloat fraction_changed2 = null;
// altezze dei due blocchi
float height1 = 0;
float height2 = 0;
// array contenenti le posizioni dei blocchi
float value_changed1[] = new float[3];
float value_changed2[] = new float[3];
boolean error = false;
// flag che indica se l'oggetto Š in fase di caduta.
boolean inCaduta1 = false;
boolean inCaduta2 = 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 {
// riferimenti a nodi e campi vrml
box1 = browser.getNode("Box1");
box2 = browser.getNode("Box2");
translation_box1 = (EventInSFVec3f) box1.getEventIn("set_translation");
translation_box2 = (EventInSFVec3f) box2.getEventIn("set_translation");
sensore1 = browser.getNode("sensore1");
sensore2 = browser.getNode("sensore2");
offset_sensor_1 = (EventInSFVec3f) sensore1.getEventIn("set_offset");
offset_sensor_2 = (EventInSFVec3f) sensore2.getEventIn("set_offset");
hit_changed_1 = (EventOutSFVec3f) sensore1.getEventOut("translation_changed");
hit_changed_2 = (EventOutSFVec3f) sensore2.getEventOut("translation_changed");
hit_changed_1.advise(this, new Integer(1));
hit_changed_2.advise(this, new Integer(2));
isActive_1 = (EventOutSFBool) sensore1.getEventOut("isActive");
isActive_2 = (EventOutSFBool) sensore2.getEventOut("isActive");
isActive_1.advise(this, new Integer(3));
isActive_2.advise(this, new Integer(4));
// sensori di tempo
time_sensor1 = browser.getNode("orologio1");
enable_time1 = (EventInSFBool) time_sensor1.getEventIn("enabled");
startTime1 = (EventInSFTime) time_sensor1.getEventIn("set_startTime");
fraction_changed1 = (EventOutSFFloat) time_sensor1.getEventOut("fraction_changed");
fraction_changed1.advise(this, new Integer(5));
time_sensor2 = browser.getNode("orologio2");
enable_time2 = (EventInSFBool) time_sensor2.getEventIn("enabled");
startTime2 = (EventInSFTime) time_sensor2.getEventIn("set_startTime");
fraction_changed2 = (EventOutSFFloat) time_sensor2.getEventOut("fraction_changed");
fraction_changed2.advise(this, new Integer(6));
}
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);
}
value_changed1[0] = 2;
value_changed1[1] = .5f;
value_changed1[2] = -8;
value_changed2[0] = -2;
value_changed2[1] = .5f;
value_changed2[2] = -8;
}
// metodo invocato automaticamente al verificarsi di un evento vrml
public void callback(EventOut evt, double when, Object which)
{
Integer whichNum = (Integer) which;
float vector[] = new float[3];
float time;
// gestione dell'evento translation_changed dal nodo PlaneSensor
// blocco rosso
if (whichNum.intValue() == 1) {
if (inCaduta1) {
enable_time1.setValue(false);
offset_sensor_1.setValue(value_changed1);
height1 = value_changed1[1];
inCaduta1 = false;
}
vector = hit_changed_1.getValue();
value_changed1[1] = .5f + vector[1];
if (value_changed1[1] < .5f) value_changed1[1] = .5f;
height1 = value_changed1[1];
translation_box1.setValue(value_changed1);
}
// gestione dell'evento translation_changed dal nodo PlaneSensor
// blocco blu
if (whichNum.intValue() == 2) {
if (inCaduta2) {
enable_time2.setValue(false);
offset_sensor_2.setValue(value_changed2);
height2 = value_changed2[1];
inCaduta2 = false;
}
vector = hit_changed_2.getValue();
value_changed2[1] = .5f + vector[1] / 5;
if (value_changed2[1] < .5f) value_changed2[1] = .5f;
height2 = value_changed2[1];
translation_box2.setValue(value_changed2);
}
// isActive = false --> blocco 1 rilasciato
if (whichNum.intValue() == 3) {
startTime1.setValue(when);
enable_time1.setValue(true);
inCaduta1 = true;
}
// isActive = false --> blocco 2 rilasciato
if (whichNum.intValue() == 4) {
startTime2.setValue(when);
enable_time2.setValue(true);
inCaduta2 = true;
}
// evento fraction_changed per la caduta del blocco rosso
if (whichNum.intValue() == 5) {
if (inCaduta1) {
time = fraction_changed1.getValue() * 100;
value_changed1[1] = height1 - 9.81f * time * time;
if (value_changed1[1] < .5f) {
// enable_time1.setValue(false);
value_changed1[1] = .5f;
offset_sensor_1.setValue(value_changed1);
inCaduta1 = false;
}
translation_box1.setValue(value_changed1);
}
}
// evento fraction_changed per la caduta del blocco blu
if (whichNum.intValue() == 6) {
if (inCaduta2) {
time = fraction_changed2.getValue() * 100;
value_changed2[1] = height2 - 9.81f * time * time;
if (value_changed2[1] < .5f) {
// enable_time2.setValue(false);
value_changed2[1] = .5f;
offset_sensor_2.setValue(value_changed2);
inCaduta2 = false;
}
translation_box2.setValue(value_changed2);
}
}
}
}
<HTML>
<HEAD><TITLE>Test accelerazione gravitazionale</TITLE></HEAD>
<CENTER>
<EMBED="testgrav.wrl" WIDTH=600 HEIGHT=600>
<P>
<APPLET CODE="Gravita.class" HEIGHT=10 WIDTH=400 mayscript>
</APPLET>
</HTML>
Questo esempio
vuole mostrare come sia possibile programmare il comportamento degli oggetti
all'interno di un mondo vrml in modo da rispecchiare precise proprietà
fisiche: un passo avanti verso la creazione di mondi sempre più
realistici e interattivi.
Semplice gioco: colpire un bersaglio con un cannone
Il semplice
gioco riportato a conclusione del presente articolo è una applicazione
di quanto visto in precedenza.
L'utente può
controllare un cannone virtuale, posizionato sopra un piano. Ad una certa
distanza si trova un obiettivo da colpire. In mezzo è stato piazzato
un muro come ostacolo. L'utente può controllare il cannone attraverso
due parametri:
- inclinazione
della canna
- forza di emissione
della palla
La palla nel corso della sua traiettoria subisce attrazione gravitazionale (la legge è sempre la stessa): si tratta pertanto di trovare la giusta inclinazione e forza per potere ottenere una parabola vincente.
L'utente ha a disposizione infinite munizioni; un semplice messaggio in una TextArea comunica l'avvenuto centro o meno.
Questa demo è
molto semplice e non richiede particolari spiegazioni aggiuntive.
Il controllo
dell'inclinazione della canna del cannone è realizzato attraverso
due pulsanti contenuti all'interno dell'area di visualizzazione java. All'interno
del metodo action non si fa altro che settare nel modo corretto il campo
rotation del nodo Transform che racchiude la canna.
Un ulteriore
pulsante consente di esplodere un colpo. In funzione dell'orientamento
correntemente assunto dalla canna del cannone, si procede a calcolare la
velocità iniziale, scomponendola nelle sue componenti x e y. Mentre
la componente x rimane invariata nel tempo (spazio = vel * t), la componente
in y subisce una attrazione gravitazionale verso il basso; questo porterà
la traiettoria ad assumere un provvidenziale andamento a parabola. Particolari
controlli vengono effettuati in modo da considerare i casi di collisione
con il terreno, con l'ostacolo o con l'obiettivo.
L'animazione
della palla di cannone viene implementata sfruttando un meccanismo identico
a quello visto per la caduta dei blocchi nell'esempio precedente: un sensore
di tempo con campo cycleInterval sufficientemente alto e monitoraggio del
campo fraction_changed per determinare il tempo trascorso (dato che tale
campo è normalizzato a uno si deve provvedere a moltiplicarlo per
il valore di fondo scala del campo cycleInterval).
Un aspetto interessante
è rappresentato dalla creazione a run-time delle palle sparate.
Quelle sparate in precedenza rimangono ferme sul terreno (non mi pare plausibile
che scompaiano). Ogni nuova palla creata viene aggiunta ad un nodo ad hoc
presente all'interno della scena vrml (nodo 'Palle').
Qui di seguito riporto i sorgenti del file vrml, della classe Java e del file html che li compone sulla stessa pagina.
Il sorgente della classe Java controllante è il seguente:#VRML V2.0 utf8
# posizione iniziale dell'utente
Viewpoint {
position -10 1 40
description "entrance"
}
# piano
DEF Piano Shape {
appearance Appearance {
material Material { diffuseColor 0 .5 0 }
}
geometry Box { size 1000, .1, 1000 }
}
# ostacolo
DEF Ostacolo Transform {
translation 0 5 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 0 .5 }
}
geometry Box { size 1 10 10 }
}
]
}
# cannone
DEF Cannone Transform {
translation -35 0 0
children [
DEF ruota1 Transform {
translation 0 1 -.75
rotation 1 0 0 1.57
children [
DEF Ruota Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Cylinder {
radius 1
height .2
}
}
]
}
DEF ruota2 Transform {
translation 0 1 .75
rotation 1 0 0 1.57
children [
USE Ruota
]
}
DEF sostegno Transform {
translation 0 1 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor .3 .3 .3 }
}
geometry Box { size .3 .3 2 }
}
]
}
DEF Canna Transform {
translation .5 1.3 0
rotation 0 0 1 1.57
children [
Shape {
appearance Appearance {
material Material { diffuseColor .5 .5 .5 }
}
geometry Cylinder {
radius .35
height 3.5
}
}
]
}
]
}
# obiettivo
DEF Obiettivo Transform {
translation 20 .5 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 1 0 }
}
geometry Box { size 2 1 2 }
}
]
}
# nodo che conterr… tutte le palle sparate
DEF Palle Group {}
# sensore di tempo per controllare lo spostamento dei colpi
DEF Orologio TimeSensor {
enabled FALSE
loop TRUE
startTime 1
cycleInterval 100
}
# orologio di appoggio, utilizzato per catturare il tempo di attivazione
# questo tempo di attivazione viene usato come etichetta temporale
# per settare il campo startTime di Orologio.
DEF Orologio_appo TimeSensor {
enabled FALSE
loop TRUE
startTime 1
cycleInterval 1
}
Infine, il file html risulta il seguente:import java.awt.*;
import java.applet.*;
import java.lang.Math;
import vrml.external.field.*;
import vrml.external.Node;
import vrml.external.Browser;
import vrml.external.exception.*;
import netscape.javascript.JSObject;
public class Shoot extends Applet implements EventOutObserver {
final int MAX_COLPI = 100;
final float massa = 1;
// riferimento al browser vrml
Browser browser;
// nodo deputato a contenere tutte le palle sparate
Node Root;
// nodo che controlla l'orientamento del cannone
Node cannone;
// nodo che controlla il trascorrere del tempo
Node tempo;
Node tempo_appo;
// nodi rappresentanti le varie palle sparate
Node palle[][] = new Node[MAX_COLPI][];
// traslazione delle palle
EventInSFVec3f[] trasl_palle = new EventInSFVec3f[MAX_COLPI];
// campo controllante la rotazione del cannone
EventInSFRotation rot_cannone = null;
// campi per aggiunta e rimozione delle palle
EventInMFNode addChildren_root = null;
EventInMFNode removeChildren_root = null;
// campi ed eventi di controllo del tempo
EventInSFBool enable_tempo = null;
EventOutSFFloat tempo_fraction = null;
EventInSFTime startTime_tempo = null;
EventInSFBool enable_tempo_appo = null;
EventOutSFBool isActive_tempo_appo = null;
float orient = 1.57f;
// flag che indica se il colpo non Š ancora terminato (palla non ferma)
boolean shooting = false;
// array contenente la rotazione del cannone
float orient_cannone[] = new float[4];
TextField forza;
float velx = 0;
float vely = 0;
float posx = 0;
float posy = 0;
float startx = 0;
float starty = 0;
float g = 9.81f;
// numero di tentativi
int tentativi = 0;
// tempo inziale associato ad un colpo
float tempo_iniziale = 0;
// vettore di traslazione per una palla
float trasl_palla[] = new float[3];
// flag utilizzato per sapere se si ha colpito l'ostacolo
boolean stopx = false;
boolean error = false;
TextArea output;
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 {
// acquisizione di references relative a nodi e campi vrml
Root = browser.getNode("Palle");
addChildren_root = (EventInMFNode) Root.getEventIn("addChildren");
removeChildren_root = (EventInMFNode) Root.getEventIn("removeChildren");
cannone = browser.getNode("Canna");
rot_cannone = (EventInSFRotation) cannone.getEventIn("set_rotation");
tempo = browser.getNode("Orologio");
enable_tempo = (EventInSFBool) tempo.getEventIn("enabled");
startTime_tempo = (EventInSFTime) tempo.getEventIn("set_startTime");
tempo_fraction = (EventOutSFFloat) tempo.getEventOut("fraction_changed");
tempo_fraction.advise(this, new Integer(1));
tempo_appo = browser.getNode("Orologio_appo");
enable_tempo_appo = (EventInSFBool) tempo_appo.getEventIn("enabled");
isActive_tempo_appo = (EventOutSFBool) tempo_appo.getEventOut("isActive");
isActive_tempo_appo.advise(this, new Integer(2));
}
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);
}
orient_cannone[0] = orient_cannone[1] = 0;
orient_cannone[2] = 1;
orient_cannone[3] = orient;
// layout dell'applet
setLayout(new GridLayout(2,3,5,5));
add(new Button("Cannon Up"));
add(new Label("Force"));
forza = new TextField("30.5");
add(forza);
add(new Button("Cannon Down"));
output = new TextArea(4,50);
add(output);
add(new Button("Shoot!"));
}
// invocato automaticamente in conseguenza di un evento AWT
public boolean action(Event evt, Object arg)
{
if (evt.target instanceof Button) {
String label = (String) arg;
if (label.equals("Cannon Up")) {
if (orient < 3.14f) {
orient += 0.05f;
orient_cannone[3] = orient;
rot_cannone.setValue(orient_cannone);
}
}
if (label.equals("Cannon Down")) {
if (orient > 1.57f) {
orient -= 0.05f;
orient_cannone[3] = orient;
rot_cannone.setValue(orient_cannone);
}
}
if (label.equals("Shoot!")) {
if (! shooting) {
shooting = true;
stopx = false;
tentativi++;
Spara();
}
}
return true;
}
else
return false;
}
// attiva l'animazione della palla sparata
private void Spara()
{
String stringa = new String();
float acc;
posx = -34.5f + 1.75f * (float) Math.cos(orient-1.57);
posy = 1f + 1.75f * (float) Math.sin(orient-1.57);
startx = posx;
starty = posy;
trasl_palla[0] = posx;
trasl_palla[1] = posy;
trasl_palla[2] = 0;
acc = getForza()/massa;
if (acc > 200) acc = 200;
velx = acc * (float) Math.cos(orient-1.57);
vely = acc * (float) Math.sin(orient-1.57);
stringa = "Transform { children [ Shape { appearance "";
stringa += "Appearance { material Material { diffuseColor 1 0 0 } } "";
stringa += "geometry Sphere { radius .2 } } ] }"";
palle[tentativi] = browser.createVrmlFromString(stringa);
trasl_palle[tentativi] = (EventInSFVec3f) palle[tentativi][0].getEventIn("set_translation");
trasl_palle[tentativi].setValue(trasl_palla);
addChildren_root.setValue(palle[tentativi]);
enable_tempo_appo.setValue(true);
}
// invocato automaticamente al verificarsi di un evento vrml
public void callback(EventOut event, double when, Object which)
{
Integer whichNum = (Integer) which;
float time_passed;
// evento fraction_changed -> consente di determinare il trascorrere del tempo
if (whichNum.intValue() == 1 && shooting) {
time_passed = tempo_fraction.getValue() * 100f;
if (! stopx)
trasl_palla[0] = startx + velx * time_passed;
trasl_palla[1] = starty + (vely - g * time_passed) * time_passed;
trasl_palla[2] = 0;
if (trasl_palla[1] < 0 && shooting) {
shooting = false;
enable_tempo.setValue(false);
trasl_palla[1] = 0;
if (trasl_palla[0] > 19 && trasl_palla[0] < 21)
output.appendText("\nColpo "" + tentativi + ":\nColpito!!!");
else
output.appendText("\nColpo "" + tentativi + ":\nMancato!");
}
if (trasl_palla[0] < .5f && trasl_palla[0] > -.5f) {
if (trasl_palla[1] < 10.2f) {
stopx = true;
}
}
trasl_palle[tentativi].setValue(trasl_palla);
}
// rileva il tempo di sparo.
if (whichNum.intValue() == 2 && isActive_tempo_appo.getValue() == true) {
enable_tempo_appo.setValue(false);
startTime_tempo.setValue(when);
enable_tempo.setValue(true);
}
}
// lettura del campo relativo alla forza
public float getForza()
{
Float value_read = new Float(0);
String str = new String();
str = forza.getText();
return value_read.valueOf(str).floatValue();
}
}
<HTML>
<HEAD><TITLE>Visualizzazione dati</TITLE></HEAD>
<CENTER>
<EMBED SRC="gravgame.wrl" height = 300 width = 600>
<P>
<APPLET CODE="Shoot.class" HEIGHT=100 WIDTH=400 mayscript>
<APPLET>
</HTML>
Con questo esempio
si chiude il quinto articolo della serie.
Come accennato
in precedenza, il prossimo articolo sarà incentrato sulla spiegazione
di come utilizzare il nodo Script per introdurre controllo da Java o Javascript.
I successivi
articoli si focalizzeranno invece su alcuni progetti più complessi,
tramite i quali si cercherà di evidenziare alcune delle caratteristiche
del linguaggio vrml non ancora analizzate. Per quanto rigurda gli aspetti
di interazione tra Java e Vrml sfruttando l'interfaccia EAI, direi che
gli aspetti principali sono stati abbondantemente trattati. Ritengo pertanto
che a questo punto dovreste essere in grado di realizzare applicazioni
JAVA-VRML mediamente complesse. In tal caso, fatemelo sapere: ci conto!
Prima di chiudere,
vi ricordo come sempre che presso il gruppo VRML DREAMERS (http://www.lucia.it/vrml)
è disponibile una gran quantità di risorse su vrml. Tra le
altre cose è presente un tutorial per esempi tenuto da Emiliano
Pecis, incentrato sull'interfaccia EAI. Inoltre, ho da poco iniziato a
scrivere brevi articoli su come introdurre particolari effetti all'interno
di un mondo vrml (vedere 'effetti speciali vrml'): l'accelerazione gravitazionale
era appunto il primo (i prossimi argomenti vanno dal morphing alla simulazione
di nuvole, ecc.)
|
||
MokaByte ricerca
nuovi collaboratori.
|
||
|