ROBOTICA

video
immagini
papers
progettati
costruiti
toolbox
vrml
simulatori

links

 

Papers
meccanica
sistemi
documents

Capitolo 24° Aggiungiamo un pizzico di interattivita': il nodo TouchSensor Capitolo 26°

Con l’introduzione di animazione abbiamo fatto un grosso salto nella realizzazione di mondi vrml. Ma ancora manca qualcosa. Il vostro visitatore attraversa la scena, può contemplarne la forma, può osservare anche oggetti in movimento, ma non può in alcun modo intervenire nell’evolversi della scena stessa.

Se dunque nel nostro mondo ci sta venendo contro una pallina, non potremo fare altro che osservarla attraversarci, senza la possibilità di afferrarla e magari rilanciarla da qualche altra parte. Manca insomma qualsiasi forma di interattività.

Vrml consente di raggiungere con lo scopo tramite dei sensori. I sensori sono rappresentati da nodi che emettono degli eventi in relazione al comportamento dell’utente. In questo paragrafo analizzeremo più in dettaglio il sensore TouchSensor. Vrml rende disponibili però anche altri sensori che vedremo brevemente in conclusione di paragrafo. Torniamo al problema che ci eravamo posti in conclusione del paragrafo precedente. Come aggiungere un interruttore alla stanza che abbiamo appena creato.

Si potrebbe legare l’accensione della luce alla pressione del sensore. Per catturare la pressione di un oggetto si ricorre al nodo TouchSensor.

Per motivi che ho esposto già da tempo, preferisco non procedere a gestire l’interruttore per la luce. Usare le luci in browsers vrml 2.0 al momento (specie se di tipo PointLight o SpotLight) porta ad ottenere effetti molto diversi con browsers diversi. Ci si potrebbe chiedere il motivo del valore .1 posto al campo radius del nodo PointLight del nodo precedente. Il motivo è solo dovuto al fatto che con Cosmo Player viene bene. Trasportato su Live3d la stanza risulterebbe molto buia; ma un valore più elevato porterebbe a problemi di sovrailluminazione in CosmoPlayer. Al momento attuale si devono cercare dei trade-offs in quest’ambito.

Per cui, preferisco introdurre un altro tipo di interazione. Una volta compresa questa, lo stesso meccanismo si applica anche all’interruttore della luce.

Supponiamo di volere riempire un po’ la nostra stanza. Metteremo nel centro della stanza una piccola colonnina con sopra un cubo. Sulla colonna mettiamo un piccolo interuttore, alla pressione del quale il cubo comincia a ruotare. Se si ripreme poi l’interruttore il cubo si ferma.

Gestire questa situazione è molto semplice. L’animazione viene realizzata sempre nel solito modo ; il problema è che essa non deve essere sempre attiva. Serve un modo per attivare o disattivare l’animazione in conseguenza della pressione dell’interruttore. Ricordandoci che gli interpolatori emettono nuovi valori di posizione in funzione della frazione di tempo che viene spedita dal nodo TimeSensor, risulta che il modo più semplice di manovrare l’animazione è quello di pilotare il campo enabled del nodo TimeSensor da cui dipende l’animazione.

Vediamo come funziona il sensore TouchSensor. Definito un certo oggetto, basta aggiungere il nodo TouchSensor nel campo children dove l’oggetto stesso è definito.

DEF placchetta Transform {
  translation 0 0 -1
  children [
    Shape {
      appearance Appearance {
	material Material { diffuseColor 1 }
      }
      geometry Box { size .05 .05 .001 }
    }
    DEF interruttore TouchSensor {}
  ]
}

Come si può vedere, includendo il sensore all’interno del campo children, rendiamo l’oggetto cliccabile. Quando questo viene cliccato il nodo TouchSensor manda in uscita un evento isActive di tipo booleano.

A questo punto il gioco è fatto. Ridirigiamo l’evento isActive del sensore al campo enabled del nodo TimeSensor.

Vediamo ora l’esempio, da introdurre nel sorgente del precedente paragrafo.

DEF base_appoggio Transform {
  translation 0 .1 -2
  children [
    DEF blocco Shape {
      appearance Appearance {
	material Material { diffuseColor 1 1 1 }
	texture ImageTexture {url “textures/marmi/marble.jpg”}
      }
      geometry Box { size .8 .2 .8 }
    }
    Transform {
      translation 0 .5 0 
      scale  .6 3 .6
      children [
	USE blocco
       ]
     }
     Transform {
	  translation 0 .8 0
	  children [
	    USE blocco
	# definisco qui la placchetta (figlia del blocco cui e’ collegata).
	     Transform {
		translation 0 0 .42
		children [
		Shape {
		 appearance Appearance {
		  material Material {  diffuseColor 1 1 1 }
    texture ImageTexture { url "../textures/generali/x.gif" }
		    }
		   geometry Box { size .2 .2 .004 }
		  }
		  # definisco ora il sensore per l’interruttore
		  DEF interruttore TouchSensor {}
		 ]
	      }
	       # definisco ora il cubo da ruotare (figlio
        # della base di appoggio...) 
	       # se sposto questa il cubo si sposta
	       # si sposta con essa
	       DEF cubo Transform {
		  translation 0 .5 0
		  children [
		    Shape {
		      appearance Appearance {
	material Material { diffuseColor 1 0 0 }
		      }
		      geometry Box { size .3 .3 .3 }
		    }
		  ]
		}
	     ]
	    }
	   ]
	}
  ]
}
# definisco ora il sensore del tempo. Lo setto disattivato
# inizialmente.
# L’animazione parte premendo sulla placca.
DEF tempo TimeSensor {
  cycleInterval 5
   enabled FALSE
   loop  TRUE
   startTime 1
}
DEF orient_cubo OrientationInterpolator {
  key [0 .25 .5 .75 1]
  keyValue [0 1 0 0, 0 1 0 1.57, 0 1 0 3.14, 0 1 0 4.71, 0 1 0 6.28]
}
ROUTE interruttore.isActive TO tempo.enabled
ROUTE tempo.fraction_changed TO orient_cubo.set_fraction
ROUTE orient_cubo.value_changed TO cubo.set_rotation

Stanza con orologio e cubo

Ho modificato alcune posizioni per far risultare il mondo piu' vedibile.

Aggiungendo queste istruzioni al mondo creato in precedenza otteniamo l’effetto. La dimensione del file è cresciuta ora a 7Kb più texture; comunque una dimensione ancora limitata.

Si noti che non è molto intuitivo che cliccando sulla placchetta si ruoti il cubo. In mondi meno didattici dovremmo cercare di evidenziare questa possibilità all’utente in qualche modo. Per esempio si potrebbe attaccare una texture a forma di pulsante sulla placchetta stessa. Oppure piazzare sotto di essa una freccia che la indichi o qualcos’altro.

Prima di analizzare il sensore, faccio notare il modo con cui ho creato il supporto per il cubo (anche se di fatto il cubo volteggia leggermente sopra di esso). Ho utilizzato ancora una volta il comando USE per riusare il nodo che definisce il blocco. Questo mi consente di risparmiare spazio (e molto). Dovendo adattare i blocchi ho usato il campo scale del nodo Transform il cui significato mi pare ovvio.

Tornando al nodo TouchSensor, la cosa più importante risulta il campo isActive che esporta. Se proviamo a far girare questo sorgente troveremo però un inconveniente. L’interruttore non si comporta proprio come avremmo voluto. Se teniamo premuto l’interruttore il cubo ruota (dove per premere intendo cliccare sopra l’oggetto cui è associato il sensore).

Il problema è il seguente. Il nodo TouchSensor invia un evento sia alla pressione del pulsante che al suo rilascio. Ne segue che il nodo TimeSensor si attiva alla pressione e si deattiva al rilascio (quando isActive spedisce il valore 0). Non possiamo risolvere il problema con i mezzi che abbiamo a disposizione per il momento. Ci serve qualcosa di più e questo sarà fornito dall’ultimo argomento che analizzeremo con un po’ di dettaglio : vrmlscript, un linguaggio di scripting molto simile agli script java.

In conclusione, parlavo in precedenza della presenza di altri sensori messi a disposizione da vrml. Il loro funzionamento è molto simile a quello del nodo TouchSensor. Per esempio abbiamo sensori di prossimità (ProximitySensor) e sensori di visibilità (VisibilitySensor).