Nous allons créer une minuterie dont nous règlerons la
durée en tournant un bouton. Nous afficherons dans la scène
la durée de cette minuterie. Un interrupteur servira à
allumer une lumière. Une fois le temps de la minuterie écoulé,
la lumière s'éteindra.
Pour le bouton de réglage de la minuterie, nous utiliserons
un cylindre.
Shape {
geometry Cylinder {
}
}
Nous parentons ce cylindre à un noeud transform que nous nommons
"reglage".
DEF reglage Transform {
children [
Shape {
geometry Cylinder {
}
}
]
}
Nous associons un capteur CylinderSensor au cylindre. Nous l'appelons
"captRG".
DEF reglage Transform {
children [
Shape {
geometry Cylinder {
}
}
DEF captRG CylinderSensor {}
]
}
Nous routons la rotation detectée (rotation_changed) par ce
capteur vers la rotation du noeud reglage, ce qui permet de faire tourner
le cylindre.
ROUTE captRG.rotation_changed TO reglage.rotation
Limitons l'amplitude de ce CylinderSensor de façon qu'il ne
tourne pas à l'infini. Laissons lui une marge de manoeuvre de
6 rad, ce qui fait 343.77°. Le maxAngle sera donc à 6 et
le minAngle à 0, ce qui est sa valeur par défaut. Donc,
pas la peine de préciser le minAngle.
DEF capteur TouchSensor {
maxAngle 6
}Voir
la scène
Voir le source
La durée de la minuterie variera donc en fonction de cette angle.
1 rad d'angle correspondant à une seconde. Donc notre minuterie
va de 0 à 6 secondes. Par défaut, l'angle de rotation
de notre cylindre est de 0 rad, ce qui fait une durée de minuterie
de 0 seconde. Nous la mettrons à 1 seconde par défaut.
Pour celà, il faut tourner notre cylindre de 1 rad.
DEF reglage Transform {
rotation 0 1 0 1
children [
Shape {
geometry Cylinder {
}
}
DEF captRG CylinderSensor {}
]
}
Il faut aussi que nous ajoutions un offset au CylinderSensor, sinon,
dès que nous cliquerons dessus, il mettra la durée à
0 seconde.
DEF reglage Transform {
children [
Shape {
geometry Cylinder {
}
}
DEF captRG CylinderSensor {
offset 1
}
]
}
Nous allons utiliser un noeud texte nommé "affichage"
qui nous permettra de visualiser la durée de la minuterie. Nous
le parentons à un noeud Transform pour pouvoir le placer 1.5
m au-dessus du cylindre.
Par défaut; la rotation du bouton de minuterie est de 1 rad.
L'affichage doit donc indiqué 1 par défaut.
Transform {
translation 0 1.5 0
children [
Shape {
geometry DEF affichage Text { string "1"
}
}
]
}
L'affichage doit afficher la valeur de l'angle de la rotation détectée
par le capteur captRG. Cette rotation est un eventOut de type SFRotation.
Nous ne pouvons pas le router directement vers la string de notre
affichage. C'est un noeud script qui récupèrera la valeur
de l'angle et la convertira en texte que nous routerons vers la string
d'affichage. Appelons ce script "convert".
DEF convert Script {
}
Le script convert reçoit un eventIn de type SFRotation que nous
appelons "rot".
DEF convert Script {
eventIn SFRotation rot
}
Nous routons la rotation_changed de captRG vers le script convert.
ROUTE captRG.rotation_changed TO convert.rot
Il emet un eventOut de type MFString que nous appelons "duree".
DEF convert Script {
eventIn SFRotation rot
eventOut SFString duree
}
Nous routons cet eventOut duree vers la string de "affichage".
ROUTE convert.duree TO affichage.string
Nous définissons une fonction rot() qui s'exécutera à
chaque changement de la valeurs de l'angle de la rotation détectée
par captRG.
url "javascript: function rot(){
}
"
La fonction rot() travaille implicitement avec deux valeurs :
- la valeur de l'évènement qui l'active
- l'instant ou celle-ci est activée.
Il faut préciser dans la définition de la fonction la
valeur que nous voulons utiliser. En l'occurence, la valeur de l'évènement.
url "javascript: function rot(val){
}
"
Maintenant que nous avons accès à cette valeur, il faut
récupérer sa composante angle et l'affecter à l'eventOut
"duree". Attention, il y a un petit piège ! L'eventOut
"duree" étant un MFString, ll faut affecter la composante
angle à la première composante de "duree".
url "javascript: function rot(val){
duree[0] = val[3] ;
}
"
Note : Vous remarquerez que la conversion de val[3] qui est
un SFFloat vers duree[0] qui est un SFString est faite automatiquement
par le javascript.
Voir la scène
La détection de la rotation étant très précise,
nous obtenons pour l'affichage un nombre bien trop important de décimale.
2 serait bien suffisant. Pour ce faire, nous multiplions la composante
angle par 100, nous l'arrondissons et finalement la redivisons par 100.
duree[0] = Math.floor(100*val[3])/100 ;
Voir
la scène
Voir le source
La lumière "lamp" est un noeud PointLight de couleur
blanche. Nous la plaçons devant le cylindre, légèrement
sur la droite et en haut afin qu'elle fasse un joli reflet. Par défaut,
elle est éteinte.
DEF lamp PointLight {
on FALSE
color 1 1 1
location 4 1 10
}
Afin que l'on voit bien si elle est allumé ou éteinte,
précisons dans les infos de navigation que nous éteignons
la torche du navigateur.
NavigationInfo {
headlight FALSE
}
Créons un interrupteur qui allumera la lampe et déclenchera
l'écoulement de la minuterie. Pour l'interrupteur, nous utiliserons
une boite de 20 x 20 x 20 cms.
Shape {
geometry Box { size .2 .2 .2
}
}
Nous lui associons un TouchSensor dans un noeud Transform. Nommons
le TouchSensor "interupteur" et déplaçons la
boite afin qu'elle ne chevauche pas le cylindre.
Transform {
translation 2 0 0
children [
Shape {
geometry Box { size .2 .2 .2
}
}
DEF interupteur TouchSensor {
}
]
}
Passons maintenant à la minuterie. Nous utilisons un TimeSensor
que nous nommons "timer". Par défaut, la durée
est à 1 seconde. Inutile de la préciser donc.
DEF Timer TimeSensor {
}
Nous utiliserons le TouchTime de "interupteur" comme
StartTime de "Timer".
ROUTE interupteur.touchTime TO Timer.startTime
Du coup, nous utilisons la propriété isActive de "Timer"
pour allumer la lampe et l'y maintenir tant que la minuterie ne s'est
pas écoulé.
ROUTE Timer.isActive TO lamp.on
Si "timer" est actif, "lamp" aussi. Et donc si
"Timer" est inactif (ce qui se produit à la fin de
son cycleInterval vu qu'il n'est pas en boucle), la lumière
aussi.
Voilà le script presque complet...
DEF convert Script {
eventIn SFRotation rot
eventIn SFTime touch
field SFBool etat FALSE
eventOut MFString duree
eventOut SFBool active
url "javascript:
function rot(val){
duree[0] = Math.floor(100*val[3])/100 ;
}
"
}
Voir
la scène
Voir le source
Vous avez remarqué ?... On a oublié un truc :
La valeur de l'angle doit être routée vers la durée
du cycle du TimeSensor. Nous nous servons du script "convert"
pour cela. Rajoutons lui un eventOut de type SFTime que nous nommons
"temps". La valeur de cette eventOut est égal à
la valeur de la string duree.
eventOut SFTime temps
...
temps = duree[0] = Math.floor(100*val[3])/100 ;
Note : vous remarquerez que une fois encore, le javascript se
charge d'attribuer correctement les "unités" de chaque
eventOut :
- temps est une SFTime
- duree[0] est un SFString issu d'un MFString.
Nous devons router cet eventOut "temps" vers le cycleInterval de "Timer".
ROUTE convert.temps TO Timer.cycleInterval
Voir la scène
Voir le source
Pour aller plus loin :
Essayez de mettre en place un petit système qui permette d'afficher
le temps qui passe, histoire de vérifier que la durée
programmé est bien celle qui s'écoule.
En vous inspirant de la technique employée pour l'exercice sur
l'interrupteur à 2 états, changez la couleur de l'interrupteur
: vert quand la lumière est allumée et rouge quand elle
est éteinte.