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.
Dans cet exercice, nous allons créer un objet qui pivotera de 90° lorsque l'on cliquera dessus. La difficulté étant de faire en sorte que à chaque clic supplémentaire, nous rajoutions 90° à l'orientation courante de l'objet.
Notre objet est une boite surmonter d'un cône pour bien voir son orientation. Ils sont groupés dans un noeud Transform que nous nommons "aiguille".
DEF aiguille Transform { children [ Shape { geometry Box { } appearance Appearance { material Material { diffuseColor .15 .15 .15 specularColor .5 .5 .5 emissiveColor .14 .14 .14 ambientIntensity 0 } } } Transform { translation 0 2 0 children Shape { geometry Cone { } appearance Appearance { material Material { diffuseColor .8 0 0 specularColor .5 .5 .5 emissiveColor .15 0 0 ambientIntensity 0 } } } } ] }
Nous associons un TouchSensor nommé "clique" à cette "aiguille".
DEF clique TouchSensor {}
Nous créons un OrientationInterpolator nommé "rotator" qui tourne de 90° durant son cycle.
DEF rotator OrientationInterpolator { key [0 1] keyValue [0 0 1 0, 0 0 1 1.57079632] }
Cette rotation s'appliquera à notre groupe "aiguille".
ROUTE rotator.value_changed TO aiguille.rotation
Il nous faut aussi un TimeSensor nommé "timer" qui commandera notre "rotator"
DEF timer TimeSensor {}
Nous routons la fraction de temp de "timer" vers l'eventIn set_fraction de "rotator".
ROUTE timer.fraction_changed TO rotator.set_fraction
Le clic sur "aiguille" déclenchera le "timer" qui commandera le "rotator".
ROUTE clique.touchTime TO timer.startTime
A ce stade, un clic fait pivoter notre objet de 90°. Mais si on recliques dessus, l' "aiguille" est ramené à son orientation initiale(tel qu'elle était à l'ouverture de la scène), puis pivoté de 90°. Or nous voudrions qu'elle tourne de 90° à partir de son orientation actuelle.
Ce qu'il nous faudrait, c'est une fonction qui redéfinisse les valeurs des clés de "rotator" une fois la première rotation effectuée. Nous utilisons pour cela un noeud script nommé "redefine".
DEF redefine Script { }
Ce script utilisera un champ de type SFNode qui sera lié au "rotator" de notre scène. Ainsi, le script pourra accéder facilement à chacune des propriétés (key, key_value...) de l'OrientationInterpolator.
field SFNode rotator USE rotator
Il utilisera un autre field de même type qui sera lié au Transform "aiguille". De cette façon, le script aura accès à une propriété très importante de "aiguille" : sla rotation courante!
field SFNode aiguille USE aiguille
Maintenant, il faut définir la fonction qui redéfinie les key_value de "rotator". Cette fonction sera exécutée après chacune des interpolations de rotation. "rotator" étant commander par "timer", c'est à la fin du cycle de timer que nous l'exécuterons. Nous utilisons donc l'eventOut cycleTime de "timer" que nous routons vers un eventIn de type SFTime du script "redefine".
DEF redefine Script { eventIn SFTime cycle field SFNode rotator USE rotator field SFNode aiguille USE aiguille url "javascript: function cycle(){ } " } ROUTE timer.cycleTime TO redefine.cycle
La propriété Key_value de "rotator" est de type MFRotation. La valeur de la première clé de "rotator" peut s'écrire Key_value[0]. Key_value[0] est quant à elle du type SFRotation.
Avant le premier clic sur "aiguille", la valeur de la première
clé de "rotator", Key_value[0], est égale à
l'orientation initiale de "aiguille", soit 0 0 1 0.
La valeur de la 2ème clé, Key_value[1], indique la valeur
de rotation de "aiguille" après interpolation, soit
0 0 1 1.57079632 .
Après l'interpolation, Key_value[0] doit être réinitialiséeà
0 0 1 1.57079632 qui est l'orientation courante de "aiguille".
Notons déjà cela dans notre fonction :
url "javascript: function cycle(){ rotator.keyValue[0] = aiguille.rotation ; } "
La 2ème clé de "rotator" est égale à l'orientation courante de "aiguille" mais incrémentée de PI/2 radians. Nous pourrions utiliser une opération du type :
Key_value[1][3] = aiguille.rotation[3] + Math.PI/2
Mais nous serions très vite handicapés par deux choses :
Pour ces raisons, nous utiliserons un outil existant et beaucoup plus puissant, une fonction javascript standart, la fonction multiply().
Cette fonction utilise une syntaxe un peu spéciale.
Voici la syntaxe générique :
SFRotation = SFRotation.multiply(SFRotation);
rotator.keyValue[1] = aiguille.rotation.multiply(new SFRotation(0, 0, 1, Math.PI /2));
rot + rot = rot * rot !?!
Aussi bizarre que celà puisse paraître, l'ajout
de rotation se fait par une multiplication !! Ceci est possible
car les rotations en VRML sont codées sous formes de
"quaternion".
Pour plus d'infos sur les quaternions, je vous recommande le
site de michael
wagner, dont on dis qu'il est le "Docteur Quaternion".
(et c'est vrai !)
Les rotations sous formes de quaternions sont très souvent
utilisées par les logiciels de 3D car elles ont l'avantage
d'éviter les phénomènes de basculement,
problème récurrent d'autre méthode (Rotation
de Euler notamment ).
Il existe une fonction javascript qui gère automatiquement
les interpolations de rotation. C'est la fonction slerp(),
qui est définie par :
rotation finale = rotation de départ.slerp( rotation de destination, temp);
Nous pouvons utiliser cette fonction dans notre exemple, mais il nous faut modifier plusieurs choses :
Nous n'auront plus besoin de "rotator". Vous pouvez donc
l'effacer ainsi que les ROUTE qui le concerne.
De même, le script n'a plus besoin du field rotator.
Le contenu de la fonction cycle() est à refaire, vous
pouvez l'effacer.
Le script contiendra une nouvelle fonction, qui s'exécutera pendant toute la durée de "timer". Pour activer cette fonction, il faut un eventIn de type SFFloat que nous nommons "duree".
DEF redefine Script { eventIn SFTime cycle eventIn SFFloat duree field SFNode aiguille USE aiguille url "javascript: function cycle(){ } function duree(){ } " }
Nous avons toujours besoin de la fonction cycle(). Elle servira
à définir les rotations de départ et d'arrivée
qui seront utilisées par l'instruction slerp(). Ces deux
rotations (départ et arrivé ), sont stockées chacune
dans un field de type SFRotation et se nomment rotdep et rotend.
Ces champs SFRotation ont par défaut la valeur 0 0 1 0.
field SFRotation rotdep 0 0 1 0 field SFRotation rotend 0 0 1 0
Comme précedemment, la rotation d'arrivé est égale à la rotation courante incrémenter de PI/2. Nous utilisons toujours la fonction multiply() pour celà :
function cycle(){ rotdep = aiguille.rotation ; rotend = aiguille.rotation.multiply(new SFRotation(0, 0, 1, Math.PI /2)); }
La fonction duree() va elle calculer à chaque fraction de temp t l'interpolation linéaire entre rotdep et rotend via la fonction slerp(). Il faut que nous récupérions la valeur de cette fraction t :
function duree(t){ }
La valeur obtenu par la fonction slerp() est affectée en temps réel à l'orientation de "aiguille".
function duree(t){ aiguille.rotation = rotdep.slerp (rotend, t) ; }
Pour que cela marche, il faut bien sûr router la fraction_changed de "timer" vers l'eventIn duree du script, qui exécutera la function duree().
ROUTE timer.fraction_changed TO redefine.duree