Ce didacticiel suppose que vous connaissiez
la définitions des différents noeuds, events et
spécifications de base du VRML97.
Vous pourrez trouver
toutes ces infos (en français) sur le site de 3Dnet.
N'oubliez pas que tous les ROUTE doivent se trouver tout à la fin du fichier.
Cet exercice est basé sur l'utilisation du noeud KeySensor dévellopé par Blaxxun. Ce noeud fonctionne exclusivement sur les plugins Blaxxun Contact et BSContact de Bitmanagement. En conséquence, vous devrez disposer d'un de ces plugins pour pouvoir visualiser correctement les scènes d'exemple. Si vous testez ces scènes avec un autre plugin (Cortona, Cosmo Player, Pivoron, etc... ), il ne se passera tout simplement rien du tout.
Apprendre à utiliser le clavier pour déclencher des
interactions ou manipuler des objet dans une scène.
Voici plusieurs cours et exercices ou vous apprendrez :
Le KeySensor est un EXTERNPROTO qui doit être déclaré pour éviter l'affichage de messages d'erreur. Voici la syntaxe de la déclaration :
EXTERNPROTO KeySensor[ eventIn SFBool eventsProcessed exposedField SFBool enabled eventOut SFInt32 keyPress eventOut SFInt32 keyRelease eventOut SFInt32 actionKeyPress eventOut SFInt32 actionKeyRelease eventOut SFBool shiftKey_changed eventOut SFBool controlKey_changed eventOut SFBool altKey_changed eventOut SFBool isActive ] [ "urn:inet:blaxxun.com:node:KeySensor", "http://www.blaxxun.com/vrml/protos/nodes.wrl#KeySensor", "nodes.wrl#KeySensor" ]
Pour ces exercices, nous créons un nouveau fichier. Nous y
copions tout de suite la déclaration de l'EXTERNPROTO KeySensor.
Nous rajoutons un noeud KeySensor que nous nommerons clavier
:
DEF clavier KeySensor { }
Il nous faut un moyen de savoir sur quelle touche l'utilisateur a
appuyé.
Nous ajoutons donc un script gestionnaire avec un eventIn de
type SFInt32 nommé Kpressed.
Puis nous routons l' eventOut KeyPress de notre noeud clavier
vers l'eventIn Kpressed du script gestionnaire :
DEF gestionnaire Script { eventIn SFInt32 Kpressed } ROUTE clavier.keyPress TO gestionnaire.pressed
Nous ajoutons à ce script une fonction Kpressed() qui nous servira à afficher la valeur de la touche enfonçée. Nous récupèrons la valeur de l'eventIn Kpressed et nous l'affichons dans la console via la fonction print() :
url "javascript: function Kpressed(val){ print ('le code de la touche enfoncee est '+val) ;Voir la scène Voir le source
Comme vous le constatez, la scène est entièrement vide. Appuyez sur
un touche du clavier. Dans la console, il doit s'afficher un nombre
entier correspond au code de la touche. Par exemple, il doit s'afficher
"32" si vous appuyer sur la barre d'espacement.
Si la console de s'affiche pas d'elle même, affichez là via le menu
contextuel.
Vous pouvez de cette façon, retrouver le code de chaque touche de votre clavier.
Nous avons vu comment savoir le code d'une touche enfoncée. Il est tout aussi intéressant de savoir si une touche est relâchée, et de récupérer sont code.
La procédure est similaire à la détection de l'enfoncement d'une touche. La seul différence, c'est que ici nous routons l'eventOut keyRelease du noeud clavier vers un eventIn Kreleased de type SFInt32 du gestionnaire :
DEF gestionnaire Script { eventIn SFInt32 Kreleased url "javascript: function Kreleased(val){ print ('le code de la touche relachee est '+val) ; } " } ROUTE clavier.keyRelease TO gestionnaire.Kreleased
Ce principe de détection de relâchement de touches est semblable pour les touches "standarts" et les "actionKeys".
Sur le clavier de votre machine, il existe des touches spéciales
qui ne sont pas détectées via l'eventout keyPressed.
Ces touches sont par exemple les quatre fléches, page précedente
et suivante, fin de ligne, etc.... Ces sont ce que nous appelons les
actionsKeys.
Il y a un moyen de détecter si elles sont pressé ou
relâchées via les eventOut actionKeyPressed et
actionKeyReleased.
Pour récupérer le code de ces touches, nous allons simplement
router l'eventout actionKeyPressed du noeud clavier
vers l'eventIn Kpressed de notre script gestionnaire.
DEF gestionnaire Script { eventIn SFInt32 Kpressed url "javascript: function Kpressed(val){ print ('le code de la touche enfoncee est '+val) ; } " } ROUTE clavier.keyPress TO gestionnaire.Kpressed ROUTE clavier.actionKeyPress TO gestionnaire.Kpressed
Il existe encore d'autres touches spéciales : Maj, Ctrl
et Alt. Ces touches ont un numéro code comme toutes
les touches, mais le noeud KeySensor dispose d'eventOut spécifique
à chacune de ces clé.
En effet, les eventOut shiftKey_changed, controlKey_changed et
altKey_changed sont de type SFBool. Nous pouvons là
aussi très facilement mettre en place un sytème qui
nous permet de savoir si la touch Ctrl par exemple est enfonçé
:
DEF gestionnaire Script { eventIn SFBool ctrl url "javascript: function ctrl(val){ if (val) { print ('controle pressed') ; } } " } ROUTE clavier.controlKey_changed TO gestionnaire.ctrl
La condition if(val) vérifie que
la touche Ctrl est enfonçée.Si c'est vrai, nous affichons
un message.
Le principe est bien sûr similaire pour les touches Maj
et Alt.
Nous pourrions router les trois eventOut shiftKey_changed, controlKey_changed et altKey_changed vers le même eventIn SFBool, mais celà ne présente pas beaucoups d'intérêt. En plus, nous ne pourrions même pas savoir sur laquelle de ces touches nous appuyons.
Pour la détection du relâchement des touches Ctrl, Maj et Alt , nous prenons simplement le cas où la condition est fausse :
DEF gestionnaire Script { eventIn SFBool ctrl url "javascript: function ctrl(val){ if (val) { print ('controle pressed') ; } else{ print ('control released') ; } } " } ROUTE clavier.controlKey_changed TO gestionnaire.ctrl
Dans cet exemple, nous ne traitons que le cas de la touche Ctrl.
L'instant d'action est le moment ou se produit un évènement.
Dans le cas de notre KeySensor, il est intéressant de savoir quand
est-ce que l'on appuis ou relâche une touche du clavier. Par exemple
pour lancer ou arrêter un timeSensor.
Nous utilisons pour celà une des propriétés implicite
de tout eventIn :
Chaque eventIn, et ce quelque soit son type renvois deux informations :
En supposant ici que instant est un eventOut ou un field de type SFTime, celà nous donne une structure de fonction du type :
function mon_eventIn (value,time){ instant = time ; }
Les informations de l'eventIn sont récupérées
via la fonction qui lui est associée.
Voici un exemple de ce qu'il nous faut :
DEF gestionnaire Script { eventIn SFInt32 Kpressed eventIn SFInt32 Kreleased url "javascript: function Kpressed(value,time){ print ('le code de la touche enfoncee est '+value) ; print ('a l\'instant '+time) ; } function Kreleased(value,t){ print ('le code de la touche relachee est '+value) ; print ('a l\'instant '+time) ; } " }
L'état d'une touche définis son status. Celle-ci a
deux états possibles : enfoncée ou relâchée.
Le but est au final d'émuler une propriété viruelle
key_changed pous une touche quelconque à l'image
de l'eventOut _changed des touches Ctrl, Maj et Alt.
Pour l'exemple, nous mémoriserons l'état de la touche
P .
Le principe est simple. La fonction Kpressed() qui
reçoit l'eventOut keyPressed du noeud clavier
met un eventOut etatP de type SFBool à true.
La fonction Kreleased() qui reçoit l'enventOut
keyreleased de clavier met l'eventOut etatP
à false :
DEF gestionnaire Script { eventIn SFInt32 Kpressed eventIn SFInt32 Kreleased eventOut SFBool etatP url "javascript: function Kpressed(val){ if (val==80 ) { etatP = true ; } } function Kreleased(val) { if (val==80){ etatP = false ; } } " }
Nous avons vu précédemment comment attribuer une propriété
virtuelle _changed à n'importe quelle touche
du clavier.
Il est très utile de mémoriser cette état car
le field etatP deveint accessible pour d'autres fonctions.
Cela est aussi utile pour éviter le problème de répétition
lors du maintien d'une touche.
Pour l'exemple, nous mémoriserons l'état de la touche
P .
La fonction Kpressed() qui reçoit l'eventOut
keyPressed du noeud clavier met un field pression
de type SFBool à true.
La fonction Kreleased() qui reçoit l'enventOut
keyreleased de clavier met le field pression
à false :
DEF gestionnaire Script { eventIn SFInt32 Kpressed eventIn SFInt32 Kreleased field SFBool pression FALSE url "javascript: function Kpressed(val){ if (val==80 ) { pression = true ; } } function Kreleased(val) { if (val==80){ pression = false ; } } " }
Dorénavant, n'importe quelle fonction du script gestionnaire peut ainsi accéder à la variable pression. Nous pouvons donc savoir en permanence si une touche est enfoncée ou pas..
Exercice N° 1 : jouer une animation
en maintenant une touche
Exercice N° 2 : lancer et arrêter
une animation en pressant une touche
Dans ce premier exercice, nous allons déclencher l'animation
d'une boite via un KeySensor.
La boite sera animée d'un va et vient sur l'axe X.
Le fonctionnement est le suivant : tant que nous maintenons la touche
P enfonçée, l' animation se joue. Si nous relâchons
la touche P, l' animation s'arrête.
La première étape consiste donc à inclure
l'EXTERNPROTO KeySensor.
Puis, nous ajoutons à la scène un noeud KeySensor que
nous nommons clavier :
DEF clavier KeySensor { }
Ensuite, nous plaçons notre boite dans un noeud Transform que nous nommons obj :
DEF obj Transform { children Shape { geometry Box { } appearance Appearance { material Material { diffuseColor 1 0 0 } } } }
Nous créons un timeSensor nommé timer d'un cycleInterval de 5 secondes. Il tourne en boucle mais sera désactivé par défaut.
DEF timer TimeSensor { cycleInterval 5 loop TRUE enabled FALSE }
Il nous faut un positionInterpolator pour animer la position de obj. Nous le nommons interpoleur. Le mouvement sera un va et vient de 10 unités sur l'axe des X :
DEF interpoleur PositionInterpolator { key [0 .5 1] keyValue [-5 0 0 5 0 0 -5 0 0] }
Nous routons le fraction_changed de timer vers le set_fraction de interpoleur.
ROUTE timer.fraction_changed TO interpol.set_fraction
Puis le routage de value_changed de interpoleur vers le translation de obj :
ROUTE interpoleur.value_changed TO obj.translation
Nous définissons maintenant un script gestionnaire qui va nous servir à activer ou desactiver timer, ce qui declenchera l'animation de obj. timer est activé lorsque nous enfoncons une touche, et desactivé lorsque nous relachons la touche. Il faut donc que nous routions le keyPressed et le keyReleased de clavier vers ce script. Je passe sur les détails qui ont été abordé ici et ici.
DEF gestionnaire Script { eventIn SFInt32 Kpressed eventIn SFInt32 Kreleased url "javascript: function Kpressed(){ } function Kreleased(){ } " } ROUTE clavier.keyPress TO gestionnaire.Kpressed ROUTE clavier.keyRelease TO gestionnaire.Kreleased
Nous ajoutons un eventOut active de type SFBool qui sera router vers la propriété enabled de timer :
eventOut SFBool active ...... ROUTE gestionnaire.active TO timer.enabled
L'appuis sur une touche appelle la fonction Kpressed() qui met
active sur true.
Le relâchement de la touche appelle la fonction Kreleased() qui
met active à false :
ROUTE interpoleur.value_changed TO obj.translation
Quand on appuis sur une touche, l'animation se joue. Dès que
l'on relâche la touche, l'animation s'arrête. Pour l'instant,
n'importe quelle touche déclenche l'animation. Si nous voulons
que l'animation soit lancée avec une touche particulière,
il faut rajouter dans la fonction Kpressed() une condition qui
vérifie que c'est bien la bonne touche qui est enfonçé.
Et il faut la même condition dans la fonction Kreleased()
sinon, e relâchement d'une autre touche stopperais l'animation..
Nous allons commander l'animation avec la touche P du clavier.
Son code est 80 :
function Kpressed(val){ if (val==80) { active = true ; } } function Kreleased(){ if (val==80) { active = false ; } }
Cette fois, l'animation ne fonctionne que si l'on maintien la touche
P du clavier.
Cet exercice est très similaire à l'exercice n°1. Cependant,
le fonctionnement sera légèrement différent :
Le premier appuis sur la touche P lancera l' animation. Il
faudra relâcher la touche et re-appuyer sur la touche P
pour arrêter l'animation.
Les objet sont les mêmes qu'à l'exercie n°1. Nous retrouvons
donc :
Le script gestionnaire étant différent, nous repartirons d'une base
épurée. Copiez et collez ce
code dans un nouveau fichier.
Le nouveau script gestionnaire sera de cette forme :
DEF gestionnaire Script { eventIn SFInt32 Kpressed eventOut SFBool active url "javascript: function Kpressed(val){ " } ROUTE clavier.keyPress TO gestionnaire.Kpressed ROUTE gestionnaire.active TO timer.enabled
Nous retouvons le routage du keyPressed de clavier vers l'eventIn
Kpressed de type SFInt32. Cet eventIn appelera naturellement
la fonction Kpressed().
Il y a aussi l' eventOut active de type SFBool qui est routé
vers la propriété enabled de timer.
Rien de nouveau donc.
Si nous voulons que l'appuis sur la touche P lance l'animation, il suffit de vérifier le code de la touche pressée est bien 80 (le code de la touche P) et de mettre active à true :
url "javascript: function Kpressed(val){ if (val==80 ) { active = true ; } } "
Quand on appuis sur la touche P, l'animation se lance. Mais si l'on rappuis sur la touche P, elle ne s'arrête pas. Il nous faut une donnée qui enregistre si nous avons déja appuyer sur P. Cette donnée est à 2 états : soit nous n'avons pas appuyer sur P, soit nous avons appuyer sur P. Il y a deux états, nous mettrons donc cette donnée sous forme de field de type SFBool dans notre script gestionnaire. Nous appelons cette donnée pression et elle sera à FALSE par défaut.
field SFBool pression FALSE
A chaque appuis sur la touche P, nous inversons etat :
function Kpressed(val){ if (val==80 ) { pression = !pression ; } }
Si pression est true, cela veut dire que nous avons appuyer une première fois sur P. Nous lançons donc l'animation en mettant active à true. Si au contraire pression est false, nous avons appuyer deux fois sur P. Nous arrêtons donc l'animation en mettant active à false :
function Kpressed(val){ if (val==80 ) { pression = !pression ; if (pression) { active = true ; } else { active = false ; } } }
Cette fois c'est pas mal. On appuis sur la touche P une fois, ça lance l'animation. On rappuis sur P, ça l'arrête. Si on rappuis encore une fois, l'animation se relance et si l'on rappuis de nouveau, elle s'arrête.
Mais il y a encore quelque chose qui ne va pas. Si nous maintenons
P enfonçé, l'animation se lance, puis devient
saccadée, et le reste tant que nous maintenons P enfoncée.
Et quand enfin nous relâchons la touche P, soit l'animation
s'arrête, soi elle continue...
Qu' est-ce qui se passe ? Pourquoi cela ?
Ceci est dût au délais de répétitions.
Je vous invite à revenir sur cette patrie du cours si vous
n'avez pas bien compris à quoi cela est dût et quel conséquence
cela peut avoir.
Donc, le maintien de la touche P enfoncée inverse la valeur
de pression à chaque appel de la fonction Kpressed().
Du coup, active passe de true à false à chaque
appel de la fonction. Ce qui a pour conséquence de lancer et
stopper l'animation. Il faut enlever ce système d'inversion
de la valeur de pression. Nous le remplaçons par la
mise à true de pression. Ceci ce fait dans la fonction
Kpressed) :
function Kpressed(val){ if (val==80 ) { pression = true ; if (pression) { active = true ; } else { active = false ; } } }
Si nous conservons cette fonction tel quel, active vaudra
toujours true, car la condition pression = true sera toujours
vrai étant donné que pression est mis à
true juste avant. Mais laissons cela pour l'instant...
Donc, si nous appuyons sur la touche P et si nous la maintenons
appuyé, pression vaut true.
Mais il faut mettre pression à false dans le cas ou
nous relâchons la touche P. Nous devons pour cela récupérer
l'eventOut keyReleased de clavier et le router vers une fonction
Kreleased() qui mettra pression à false :
function Kreleased(val) { if (val==80){ pression = false ; } } ..... ROUTE clavier.keyRelease TO gestionnaire.Kreleased
Mais alors, quand et comment lancer et stopper l'animation maintenant
? L'animation est je le rappelle commander par la valeur de l'evenOut
active.
Réfléchissons :
- pression vaut au départ false
- quand on appuis une première fois sur P, pression
= true. Il faut aussi que active = true.
- quand on relâche P, pression = false, mais active
doit toujours valoir true
- quand on appuis une deuxième fois sur P, pression
= true , mais active = false
- quand on relâche P pour la deuxième fois, pression
= false et active = false
Si nous observons bien ce raisonnement, nous nous apercevons qu'en
fait la logique de commande de active est assez simple : à
chaque mise à true de pression, nous inversons la valeur
de active. Ce qui donnerait :
function Kpressed(val){ if (val==80 ) { pression = true ; if (pression) { active = !active ; } } }
Voir la scène
Voir le source
Mais ça ne marche pas !!! Il y a toujours l'effet de saccade
lorsqu'on maintien la touche P enfoncée !
Mais ceci est normal : pression est mise à true juste
avant la condition qui justement vérifie si pression =
true. La condition est donc toujours true, et donc à chaque
appel de la fonction Kpressed(), la valeur de active est
inverser. Notre fonction Kreleased() qui est appelé
après la fonction Kpressed() ne sert donc à rien.
En fait, ce qu'il faut faire, c'est inverser la valeur de active
quand pression = false, mais ceci AVANT de mettre pression
à true. De cette façon, la fonction Kreleased()
peut jouer son role de remise à false de pression.
Voici la fonction Kpressed() telle qu'elle doit être
:
function Kpressed(val){ if (val==80 ) { if (!pression) { active = !active ; } pression = true ; } }
Lors de l'appuis sur la touche P, pression = false.
D'une part parce que c'est sa valeur par défaut à l'ouverture
de la scène, d'autre part parce qu'il faut forcément
relâché la touche avant de pouvoir rappuyer dessus. Or,
si nous relâchons la touche, la fonction Kreleased() met
pression à false.
Vu que pression = false, la condition est vrai (la condition
renvois true SI pression = false), la valeur de active
est inverser. Donc l'animation se lance ou s'arrête.
Ensuite, pression = true. Lors du prochain appel à la
fonction Kpressed(), pression = true. La condition n'étant
pas vérifiée,on ne touche pas à la valeur de
active.
Q : J'appuis une première fois sur la touche, et après mon KeySensor ne fonctionne plus. Pourquoi ?
R : Si dans le script qui est le lié au KeySensor, il
y a une fonction print(), celle-ci ouvre la console du plugin. C'est
alors la console qui a le focus système. Donc tout appuis sur une
touche par la suite sera envoyé vers la console par le système.
Pour que tout fonctionne correctement, il faut que la scène reprenne
le focus. Il suffit pour cela de cliquer dans la vue 3D de la scène.
De cette façon, l'appuis sur une touche sera correctement envoyé
vers le plugin, qui lui fera suivre le signal vers le KeySensor.
Q : Dans les cours, il est question d'un affichage dans la console. Mais je ne la vois pas ?
R : La console permet d'afficher des informations d'un script
contenu dans la scène 3D. Lors de l'appel à la fonction print(),
le résultat est envoyé vers la console. Si la console
ne s'affiche pas d'elle même, faîtes un clic-droit dans
la scène. Dans le menu contextuel, cliquez sur "settings"
ou "configuration" et dans le sous-menu, cliquez
sur "console". La console apparait alors par dessus
la scène.
Vous pouvez faire en sorte que la console s'affiche automatiquement.
dans le menu contextuel du clic droit, cliquer sur "Préférences"
ou appuyer sur la touche F9. Dans l'onglet "general",
cocher "Messages d'erreur VRML complets" ou "Verbose
VRML warnings".
Si vous avez des questions, n' hésitez pas à me contacter
à cette adresse : Athanaze@fr.st
Je vous répondrais et ajouterais votre questions à cette
F.A.Q.