Politecnico di Milano - Facoltà del design
© Prof. Giovanni Gigante - Revisione 1 (28 maggio 2003)
Un'animazione, in senso generale, è il succedersi di una serie di immagini progressivamente diverse tra loro (fotogrammi, in inglese frame), in modo da dare l'illusione del movimento. Nei disegni animati tradizionali, veniva ripreso ogni singolo fotogramma. L'animazione al computer invece fa uso dei cosiddetti fotogrammi chiave (keyframe) che, potremmo dire, sono quelli in cui c'è un cambiamento significativo. I fotogrammi intermedi vengono calcolati automaticamente. Per esempio, per animare uno spostamento rettilineo, basterà specificare la posizione iniziale e quella finale. Per uno spostamento lungo una linea spezzata, occorrerà specificare i punti "di svolta", e così via.
In VRML esistono due tipi di tempo ai quali può fare riferimento un'animazione.
Il tempo assoluto è un tempo di calendario (per esempio,
le ore x del giorno y). Il tempo relativo è un tempo
fittizio, che va da 0 (all'inizio dell'animazione) fino a 1 (alla fine dell'animazione).
Il tempo relativo naturalmente non specifica quanto dura l'animazione;
questo sarà stabilito altrove.Vedremo che entrambi i sistemi tornano
utili in determinate circostanze.
Le animazioni possono inoltre essere eseguite una volta sola, oppure ricominciare
da capo in un ciclo continuo (loop).
Il VRML ha un sofisticato sistema di eventi, che servono sia per creare animazioni, sia per creare meccanismi interattivi. Gli eventi sono dei "segnali" che certi nodi VRML emettono in certe circostanze (quando succede qualcosa di stabilito), e che possono essere "instradati" verso altri nodi per provocare in essi dei cambiamenti.
Gran parte dei campi presenti nei vari nodi VRML ha un "connettore"
invisibile attraverso il quale esso può essere modificato, e il cui nome
inizia con "set_". Per esempio, il campo radius del nodo
Sphere ha un corrispondente connettore set_radius,
agendo sul quale si può modificare la dimensione della sfera. Questi
connettori "in ingresso" si chiamano eventIn.
Altri nodi hanno anche dei connettori in uscita (eventOut), che emettono dei segnali quando si verificano particolari eventi (un'animazione giunta a un certo punto, il bottone del mouse premuto, ecc.).
Utilizzando la parola chiave ROUTE (collegamento) si possono "cablare" questi connettori gli uni agli altri, e provocare effetti visibili quando succedono determinate cose. Il formato del comando route è:
ROUTE nodoorigine.campoorigine TO nododestinazione.campodestinazione
Per creare una semplice animazione dobbiamo procedere così:
TimeSensor emette un segnale temporale, che l'interpolatore
trasforma in una posizione (o una rotazione, ecc.) che poi sarà diretta
all'oggetto che vogliamo modificare.Vediamo in particolare il nodo TimeSensor. Qui citiamo tutti i nodi per completezza, ma generalmente per creare una semplice animazione ne bastano molti di meno:
| nodo TimeSensor | ||
enabled |
L'orologio è in funzione? | TRUE (in funzione) |
startTime |
L'ora di inizio (tempo assoluto*) | 0 |
stopTime |
L'ora di arresto (tempo assoluto*) | 0 |
cycleInterval |
Quanti secondi dura l'animazione nel suo complesso | 1.0 |
loop |
L'animazione si ripete? | FALSE (viene eseguita una volta sola) |
isActive |
eventOut: emette TRUE quando inizia l'animazione, e FALSE quando termina | |
time |
eventOut: emette continuamente un valore tempo assoluto* | |
cycleTime |
eventOut: emette un valore tempo assoluto solamente all'inizio di ogni ciclo dell'animazione | |
fraction_changed |
eventOut: emette un numero decimale, 0 all'inizio dell'animazione, 1 alla fine | |
* Lavorando in VRML non ci interessa conoscere effettivamente questo valore (che è espresso in un modo abbastanza complicato), ci basta sapere quali sono i connettori che trattano tempo assoluto.
Il nodo PositionInterpolator ci permette di realizzare animazioni spostando la posizione di oggetti. Eccolo:
| nodo PositionInterpolator | ||
key |
Una serie di valori compresi tra 0 e 1 (0 è l'inizio dell'animazione, 1 è la fine), in corrispondenza dei quali vogliamo far succedere qualcosa. In altre parole, indichiamo quali sono i fotogrammi chiave. | [ ] (nessuno) |
keyValue |
Una lista di terne di valori, che rappresentano le posizioni X Y Z nelle
quali spostare l'oggetto. Ciascuna posizione corrisponde a un valore di
key |
[ ] (nessuno) |
set_fraction |
eventIn: riceve dall'esterno un valore compreso tra 0 e 1, che viene poi confrontato con i vari key | |
value_changed |
eventOut: la posizione X Y Z valida in questo momento | |
Per capire meglio vediamo l'esempio commentato di un cubo che si muove:
#VRML V2.0 utf8
Group {
children [
# il cubetto da spostare. Siccome va spostato,
# dobbiamo includerlo in un nodo Transform, e
# dare un nome a questo nodo.
DEF spostacubetto Transform {
children Shape {
geometry Box { size 1 1 1 }
}
},
# l'orologio dell'animazione
# l'animazione dura 4 secondi e si ripete
DEF orologio TimeSensor {
cycleInterval 4.0
loop TRUE
},
# il percorso dell'animazione
DEF percorso PositionInterpolator {
# la nostra animazione ha cinque
# momenti significativi:
key [
0, 0.25, 0.5, 0.75, 1
]
# a ciascuno di questi momenti associamo
# una traslazione da inviare al cubetto.
# il valore iniziale 0 0 0 vuol dire che
# l'animazione inizia dalla posizione di base
# del cubo. Il valore finale 0 0 0 vuol dire
# che alla fine l'animazione torna in quello
# stesso punto (altrimenti si avrebbe un salto)
keyValue [
0 0 0,
0 2 0,
1 2 0,
2 1 2,
0 0 0
]
}
]
}
# infine, colleghiamo l'uscita dell'orologio all'ingresso
# dell'interpolatore, e l'uscita dell'interpolatore al nodo
# spostacubetto (il nodo Transform che circonda il cubo).
# set_translation è il connettore in ingresso del campo
# translation del nodo Transform.
ROUTE orologio.fraction_changed TO percorso.set_fraction
ROUTE percorso.value_changed TO spostacubetto.set_translation
PositionInterpolator emette terne di valori X Y Z che in questo
esempio abbiamo inviato al campo translation del nodo Transform,
ottenendo di animare lo spostamento del cubo. Ma possiamo anche utilizzarlo
per animare un cambiamento di dimensioni. Basterà inviarlo, anziché
al campo translation, al campo scale del nodo Transform:
#VRML V2.0 utf8
Group {
children [
DEF trasformapalla Transform {
children Shape {
appearance Appearance {
material Material {
diffuseColor 0.6 0 0
}
}
geometry Sphere { }
}
},
DEF orologio TimeSensor {
cycleInterval 2.0
loop TRUE
},
DEF pulsazione PositionInterpolator {
key [ 0, 0.5, 1 ]
keyValue [
1 1 1,
1.2 1.2 1.2,
1 1 1
]
}
]
}
ROUTE orologio.fraction_changed TO pulsazione.set_fraction
ROUTE pulsazione.value_changed TO trasformapalla.set_scale
Per animare una rotazione non si può usare PositionInterpolator,
perché esso emette solo terne di valori, mentre come sappiamo per specificare
le rotazioni ne occorrono quattro (tre per l'asse di rotazione più uno
per l'angolo). Pertanto per questo tipo di animazioni occorre un nodo diverso,
OrientationInterpolator, che come si vede è uguale a
PositionInterpolator, salvo che tratta quattro valori alla volta
anziché tre::
| nodo OrientationInterpolator | ||
key |
Una serie di valori compresi tra 0 e 1 (0 è l'inizio dell'animazione, 1 è la fine), in corrispondenza dei quali vogliamo far succedere qualcosa. In altre parole, indichiamo quali sono i fotogrammi chiave. | [ ] (nessuno) |
keyValue |
Una lista di quaterne di valori, che rappresentano le rotazioni X Y Z
A da applicare all'oggetto. Ciascuna orientamento corrisponde a un valore
di key |
[ ] (nessuno) |
set_fraction |
eventIn: riceve dall'esterno un valore compreso tra 0 e 1, che viene poi confrontato con i vari key | |
value_changed |
eventOut: la rotazione X Y Z A valida in questo momento | |
#VRML V2.0 utf8
Group {
children [
DEF ruotacubo Transform {
children Shape {
appearance Appearance {
material Material {
diffuseColor 0 0.7 0.7
}
}
geometry Box { }
}
},
DEF orologio TimeSensor {
cycleInterval 3
loop TRUE
},
DEF percorso OrientationInterpolator {
key [ 0.0, 0.50, 1.0 ]
keyValue [
# una rotazione completa lungo l'asse Z
0.0 0.0 1.0 0.0,
0.0 0.0 1.0 3.14,
0.0 0.0 1.0 6.28
]
}
]
}
ROUTE orologio.fraction_changed TO percorso.set_fraction
ROUTE percorso.value_changed TO ruotacubo.set_rotation
L'animazione dei colori richiede qualche accorgimento supplementare. Come sappiamo i colori sono definiti con terne RGB (rosso-verde-blu), ma se si fa una semplice interpolazione tra questi valori si ottengono risultati visivamente spiacevoli (per esempio, una transizione dal rosso al blu passa attraverso il grigio, anziché attraverso il viola come ci si aspetterebbe). Pertanto, per animare colori si usa lo speciale nodo ColorInterpolator, che funziona nel solito modo, ma lavora internamente nello spazio di colore HSV (tono-saturazione-intensità), il che vuol dire che produce i colori intermedi che ci aspettiamo intuitivamente.
| nodo ColorInterpolator: identico a PositionInterpolator , ma si usa con i colori |
#VRML V2.0 utf8
Group {
children [
Shape {
appearance Appearance {
material DEF colorepalla Material {
diffuseColor 1 0 0
}
}
geometry Sphere { }
},
DEF orologio TimeSensor {
cycleInterval 2.0
loop TRUE
},
DEF animazione ColorInterpolator {
key [ 0.0, 0.5, 1 ]
keyValue [
1 0 0,
0 0 1,
1 0 0
]
}
]
}
ROUTE orologio.fraction_changed TO animazione.set_fraction
ROUTE animazione.value_changed TO colorepalla.set_diffuseColor
Infine, il nodo ScalarInterpolator serve ad animare valori singoli:
| nodo ScalarInterpolator | ||
key |
Una serie di valori compresi tra 0 e 1 (0 è l'inizio dell'animazione, 1 è la fine), in corrispondenza dei quali vogliamo far succedere qualcosa. In altre parole, indichiamo quali sono i fotogrammi chiave. | [ ] (nessuno) |
keyValue |
Una lista di valori singoli, da emettere. Ciascuna valore corrisponde
a un valore di key |
[ ] (nessuno) |
set_fraction |
eventIn: riceve dall'esterno un valore compreso tra 0 e 1, che viene poi confrontato con i vari key | |
value_changed |
eventOut: il valore in uscita valido in questo momento | |
Per esempio, si può usare per regolare la trasparenza di un oggetto, che è un numero singolo:
#VRML V2.0 utf8
Group {
children [
Shape {
# il cubo
appearance Appearance {
material DEF materialecubo Material {
# giallo
diffuseColor 1.0 1.0 0.0
transparency 0
}
}
geometry Box {
size 2.0 2.0 2.0
}
},
Shape {
# il cono
appearance Appearance {
material Material {
diffuseColor 0.0 0.0 1.0
}
}
geometry Cone {
height 5.0
bottomRadius 1.0
}
},
DEF orologio TimeSensor {
cycleInterval 2.0
loop TRUE
},
DEF animazione ScalarInterpolator {
key [ 0, 0.5, 1 ]
keyValue [
1, 0.5, 1
]
}
]
}
ROUTE orologio.fraction_changed TO animazione.set_fraction
ROUTE animazione.value_changed TO materialecubo.set_transparency
Si possono usare contemporaneamente diversi TimeSensor e diversi interpolatori, e anche collegare con ROUTE un TimeSensor a più interpolatori, o altre configurazioni ancora per creare animazioni complesse, o animare molti oggetti nello stesso modo.