Politecnico di Milano - Facoltà del design
© Prof. Giovanni Gigante - Revisione 2 (12 maggio 2004)
Nel capitolo precedente si è visto come utilizzare il sistema degli eventi VRML per realizzare semplici animazioni. E' però anche possibile fare in modo che il mondo reagisca alle azioni dell'utente. In particolare, usando il dispositivo di puntamento (mouse, nel nostro caso) l'utente può "avvicinarsi" (passare sopra un oggetto) "toccare" (fare clic su un oggetto) o trascinare (fare clic su un oggetto e, tenendo premuto, spostare). Ci sono una serie di nodi VRML che possono essere utilizzati per reagire a questi eventi.
Il più importante è TouchSensor, che reagisce al passaggio e al clic del mouse:
| nodo TouchSensor | ||
enabled |
Il sensore è in funzione? | TRUE (in funzione) |
isOver |
eventOut: emette TRUE quando l'utente muove il cursore sopra l'oggetto, FALSE quando se ne distacca | |
isActive |
eventOut: emette TRUE quando l'utente fa clic sopra l'oggetto, FALSE quando l'utente rilascia il pulsante | |
touchTime |
eventOut: emette un valore tempo assoluto* relativo al momento in cui l'utente scatena l'evento | |
hitPoint_changed |
eventOut: le coordinate del punto toccato | |
Creiamo un cubo che ruota quando viene sfiorato. Per realizzare questo effetto
creiamo la consueta animazione del cubo, lasciandola però inizialmente
inattiva. Creiamo un nodo TouchSensor, e colleghiamo la sua uscita isOver
all'ingresso set_enabled del TimeSensor. In questo
modo, quando l'utente sfiora il cubo il connettore isOver di TouchSensor
emette TRUE, che viene instradato in set_enabled di
TimeSensor, attivando, l'animazione. Quando poi l'utente si allontana,
si ripete lo schema ma con l'invio di un segnale FALSE, che ferma
l'animazione.
#VRML V2.0 utf8
Group {
children [
DEF trasformacubetto Transform {
children Shape {
appearance Appearance {
material Material { }
}
geometry Box { }
}
},
DEF tocco TouchSensor { },
# non ci serve specificare nulla di particolare per il sensore
DEF orologio TimeSensor {
# l'orologio inizialmente è disattivato
enabled FALSE
cycleInterval 4.0
loop TRUE
},
DEF animazione OrientationInterpolator {
key [ 0, 0.5, 1 ]
keyValue [
0 0 1 0.0,
0 0 1 3.14,
0 0 0 6.28
]
}
]
}
ROUTE tocco.isOver TO orologio.set_enabled
ROUTE orologio.fraction_changed TO animazione.set_fraction
ROUTE animazione.value_changed TO trasformacubetto.set_rotation
Qui usiamo una tecnica diversa per creare un'animazione che si attiva soltanto
quando l'oggetto viene cliccato. In quel momento, il campo touchTime
del nodo TouchSensor emette il tempo assoluto dell'evento (il clic
del mouse). Ma inviando questo dato al campo startTime del TimeSensor,
si attiva effettivamente l'animazione in quello stesso momento.
L'animazione si ferma dopo un ciclo perché non abbiamo richiesto il loop
dell'animazione.
#VRML V2.0 utf8
Group {
children [
DEF trasformacubetto Transform {
children Shape {
appearance Appearance {
material Material {
diffuseColor 1 0.8 0.4
}
}
geometry Box { }
}
},
DEF tocco TouchSensor { },
# anche qui, il sensore non richede altro
DEF orologio TimeSensor {
cycleInterval 1.0
},
DEF animazione OrientationInterpolator {
key [ 0.0, 0.5, 1.0 ]
keyValue [
1.0 1.0 1.0 0.0,
1.0 1.0 1.0 3.14,
1.0 1.0 1.0 6.28
]
}
]
}
ROUTE tocco.touchTime TO orologio.set_startTime
ROUTE orologio.fraction_changed TO animazione.set_fraction
ROUTE animazione.value_changed TO trasformacubetto.set_rotation
Esistono poi tre tipi di sensori che servono a rendere mobili gli oggetti, cioè a fare in modo che si possano trascinare con il mouse.
Il primo di essi è PlaneSensor, che rileva le azioni di trascinamento eseguite con il mouse, calcolando le distanze di traslazione e riportandole in uscita:
| nodo PlaneSensor | ||
enabled |
Il sensore è in funzione? | TRUE (in funzione) |
autoOffset |
Il sensore deve "ricordare" la posizione in cui è stato lasciato l'ultima volta? | TRUE |
offset |
"Scostamento": un valore opzionale che viene sommato alle coordinate prodotte in uscita | 0 0 0 |
maxPosition |
Coordinate X Y massime e minime oltre alle quali non è consentita la traslazione. Con i valori di default, la traslazione non è vincolata. | -1 -1 0 0 |
isActive |
eventOut: emette TRUE quando viene attivato il sensore, FALSE quando viene rilasciato | |
| translation_changed | eventOut: le coordinate di traslazione prodotte dallo spostamento del mouse | |
Per rendere effettivamente traslabile un oggetto, si deve circondare l'oggetto
con un nodo Transform, e instradare l'uscita del nodo PlaneSensor
(translation_changed) alla proprietà translation del
nodo Transform, in modo da causare effettivamente lo spostamento.
Ricordiamo che è necessario assegnare un nome (con DEF) sia al sensore
che al nodo Transform, altrimenti non possiamo specificare i collegamenti in
ROUTE.
#VRML V2.0 utf8
Group {
children [
DEF trasformacubetto Transform { # il cubetto viene circondato da un nodo Transform che inizialmente # non specifica una traslazione, quindi a prima vista non fa nulla
children Shape {
appearance Appearance {
material Material { diffuseColor 1 0.8 0.4 }
}
geometry Box { }
}
}, # il sensore planare viene applicato al cubetto
DEF sensore PlaneSensor { }
]
} # instradando l'uscita del planesensore al nodo transform, il cubetto subisce # una traslazione in funzione di come viene trascinato con il mouse sullo schermo.
ROUTE sensore.translation_changed TO trasformacubetto.set_translation
PlaneSensor rileva ed emette i trascinamenti lungo il piano XY, e quindi, se
viene usato così com'è, rende un oggetto traslabile lungo il piano
XY (cioè, rispetto alla posizione iniziale sullo schermo, destra-sinistra
e alto-basso). Così nell'esempio precedente.
Se si vuole invece che il piano di riferimento sia un altro, occorre applicare
una ulteriore rotazione al sistema di coordinate, a valle di quanto
abbiamo appena visto. Nell'esempio seguente, vogliamo che il cubetto sia traslabile
lungo il piano XZ (cioè, rispetto alla posizione iniziale sullo schermo,
destra-sinistra e avanti-indietro). Per ottenere questo effetto, agiamo come
nell'esempio precedente, e poi circondiamo tutto il risultato con un ulteriore
nodo Transform, che "ribalta" il risultato di 90 gradi in
modo che il cubo si sposti lungo il piano XZ anziché XY:
#VRML V2.0 utf8
Group {
children [
Transform {
# questo ulteriore nodo transform ruota tutto quanto di
# 90 gradi (1.57 radianti) attorno all'asse X, in modo che lo
# spostamento XY prodotto dal nodo transform interno diventi
# in realta' uno spostamento XZ.
rotation 1.0 0.0 0.0 -1.57
children DEF trasformacubetto Transform {
# questo e' il nodo transform, inizialmente vuoto,
# che riceve il controllo da parte del sensore
children Shape {
appearance Appearance {
material Material { }
}
geometry Box { }
}
}
},
DEF sensore PlaneSensor {
# questa volta proviamo anche a specificare un limite
# massimo di spostamento: 2 unità in tutte le direzioni
minPosition -2 -2
maxPosition 2 2
}
]
}
ROUTE sensore.translation_changed TO trasformacubetto.set_translation
Il nodo SphereSensor funziona in modo concettualmente simile, ma converte il moto di trascinamento del mouse in una rotazione attorno a un centro, come se si facesse ruotare una sfera:
| nodo SphereSensor | ||
enabled |
Il sensore è in funzione? | TRUE (in funzione) |
autoOffset |
Il sensore deve "ricordare" la posizione in cui è stato lasciato l'ultima volta? | TRUE |
offset |
"Scostamento": un valore opzionale che viene sommato alle coordinate di rotazione prodotte in uscita | 0 1 0 0 (nessuna rotazione aggiuntiva) |
isActive |
eventOut: emette TRUE quando viene attivato il sensore, FALSE quando viene rilasciato | |
| rotation_changed | eventOut: le coordinate di rotazione prodotte dallo spostamento del mouse | |
Dal momento che il sensore sferico produce una rotazione, anziché una traslazione, esso emetterà un insieme di 4 valori (come tutte le rotazioni), che questa volta andrà diretto alla proprietà rotation (e non translation!) del nodo Transform:
#VRML V2.0 utf8
Group {
children [
DEF trasformacono Transform {
# questo nodo transform, inizialmente inerte,
# ricevera' il valore di rotation dal sensore
children Shape {
appearance Appearance {
material Material {
diffuseColor 1 0 0
}
}
geometry Cone { }
}
},
# il sensore sferico. Non ci serve specificare
# alcun valore particolare al suo interno
DEF sensore SphereSensor { }
]
}
# instradando l'uscita del sensore alla proprieta' rotation
# del nodo transform che circonda il cono, otteniamo di rendere
# il cono "ruotabile" con il mouse
ROUTE sensore.rotation_changed TO trasformacono.set_rotation
Infine, il nodo CylinderSensor converte il moto di trascinamento del puntatore (mouse) in una rotazione attorno a un asse, l'asse di un ipotetico cilindro per l'appunto. Usando il sensore nel solito modo diventa possibile creare un oggetto che si può far ruotare attorno a un asse con il mouse:
| nodo CylinderSensor | ||
enabled |
Il sensore è in funzione? | TRUE (in funzione) |
autoOffset |
Il sensore deve "ricordare" la posizione in cui è stato lasciato l'ultima volta? | TRUE |
offset |
"Scostamento": un valore opzionale, espresso come angolo in radianti, che viene sommato al valore di rotazione prodotto in uscita | 0 (nessuna rotazione aggiuntiva) |
| minAngle maxAngle |
Rotazione minima e massima, espressa in radianti. I valori di default non pongono alcun limite alla rotazione | -1 0 |
isActive |
eventOut: emette TRUE quando viene attivato il sensore, FALSE quando viene rilasciato | |
| rotation_changed | eventOut: le coordinate di rotazione prodotte dallo spostamento del mouse | |
Anche il sensore cilindrico emette una rotazione, come quello sferico, e quindi si instraderà la sua uscita alla proprietà rotation del nodo Transform:
#VRML V2.0 utf8
Group {
children [
DEF trasformacono Transform {
# questo nodo transform, inizialmente inerte,
# ricevera' il valore di rotation dal sensore
children Shape {
appearance Appearance {
material Material {
diffuseColor 1 0 0
}
}
geometry Cone { }
}
},
# il sensore cilindrico. Non ci serve specificare
# alcun valore particolare al suo interno
DEF sensore CylinderSensor { }
]
}
# instradando l'uscita del sensore alla proprieta' rotation
# del nodo transform che circonda il cono, otteniamo di rendere
# il cono "ruotabile" con il mouse
ROUTE sensore.rotation_changed TO trasformacono.set_rotation
Il sensore cilindrico converte i movimenti del mouse in una rotazione attorno
all'asse Y (verticale). Se si vuole far ruotare un oggetto attorno a un altro
asse, occorre circondare tutto quanto con un ulteriore nodo Transform che effettui
la rotazione degli assi (e quindi anche dell'asse di rotazione del sensore).
Questo però otterrà anche l'effetto indesiderato di far ruotare
l'oggetto: dovremo dunque inserire un terzo nodo Transform, internamente, per
raddrizzare l'oggetto.
Ecco come modificare in questo modo l'esempio precedente in modo che il cono
ruoti attorno all'asse Z anziché all'asse Y:
#VRML V2.0 utf8
Group {
children [
Transform {
# questo nodo transform esterno ci serve a far ruotare il
# sistema di riferimento, in modo che la rotazione indotta
# dal sensore avvenga attorno all'asse Z anziché attorno
# all'asse Y. Per cui dobbiamo ribaltare gli assi di 90 gradi
# attorno all'asse X:
rotation 1 0 0 1.57
children [
DEF trasformacono Transform {
# questo e' il nodo transform, inizialmente inerte,
# che ricevera' il valore di rotation dal sensore
children [
Transform {
# questo nodo transform interno serve a voltare il
# cono in senso INVERSO a quello del nodo transform
# piu' esterno: in questo modo il cono torna nella
# posizione dritta di partenza
rotation 1 0 0 -1.57
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 1 0 0
}
}
geometry Cone { }
}
]
}
]
},
DEF sensore CylinderSensor { }
]
}
]
}
ROUTE sensore.rotation_changed TO trasformacono.set_rotation
Per scegliere dove posizionare i sensori all'interno del codice VRML, basta sapere due regole:
L'esempio seguente, un po' complesso, mostra come funzionano queste regole.
Si tratta di un braccio snodato, formato da due bracci (cilindri slanciati)
e una base (un cubo). I due bracci insieme formano un gruppo al quale viene
legato un sensore (qui chiamato "sensoregenerale"), che permette di
far ruotare l'intero sistema trascinandolo. Viene poi posizionato un secondo
sensore (qui chiamato "sensorealto") sul solo braccio superiore, che
permette di muoverlo indipendentemente dal resto.
Si utilizzano sensori sferici, ipotizzando che ci siano delle giunture sferiche
tra le parti dell'oggetto.
#VRML V2.0 utf8
# l'intero oggetto e' un gruppo
Group {
children [
# la base e' un cubo rosso
# in questo gruppo esterno non ci sono sensori, e quindi il cubo non e'
# sensibile
Shape {
geometry Box { size 0.5 0.5 0.5 }
appearance Appearance { material Material { diffuseColor 1 0 0 }}
},
# il gruppo del braccio snodato, che comprende entrambi i segmenti
DEF tuttoilbraccio Transform {
# lo posizionamo sopra il cubo
translation 0 1 0
# spostiamo il centro di rotazione in modo da farlo coincidere
# con il punto alla sua base
center 0 -1 0
children [
# il braccio inferiore e' un cilindro verde
Shape {
geometry Cylinder { radius 0.2 height 2 }
appearance Appearance { material Material { diffuseColor 0 1 0 }}
},
# questo sensore si applica all'intero braccio snodato
DEF sensoregenerale SphereSensor { },
# il braccio superiore e' un sottogruppo
DEF bracciosuperiore Transform {
# lo posizioniamo sopra l'altro cilindro
translation 0 2 0
# spostiamo il centro di rotazione in modo da farlo coincidere
# con il punto alla sua base
center 0 -1 0
children [
# il braccio superiore e' un cilindro blu
Shape {
geometry Cylinder { radius 0.2 height 2 }
appearance Appearance { material Material { diffuseColor 0 0 1 }}
},
# questo sensore si applica SOLO a questo gruppo, cioe'
# al braccio superiore. Ottengo quindi che manipolando il
# braccio superiore spostero' solo lui
DEF sensorealto SphereSensor { },
]
}
]
}
]
}
ROUTE sensoregenerale.rotation_changed TO tuttoilbraccio.set_rotation
ROUTE sensorealto.rotation_changed TO bracciosuperiore.set_rotation