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.
Le but est d'arriver à parenter interactivement une sphère
à plusieurs objets différents.
Au lancement de la scène, notre sphère n'a pas de parent.
Lorsque nous cliquerons sur une boite, nous parenterons la sphère
soit à une boite animée en translation, soit à
un cone déplaçable, ou bien à aucun objet.
Nous utiliserons pour cela les méthodes AddChildren
et removeChildren.
Tout d'abord, les méthodes addChildren et removeChildren ne s'appliques qu'aux noeuds possèdant un exposedField children. Il existe 5 noeuds VRML standarts ayant ces caractérisiques :
L'exposedField children est de type MFNode, ainsi que les méthodes addChildren et removeChildren.
Notes : ils existent des PROTOS qui possèdent aussi ces méthodes.
Afin de bien illustrer les changements de parent, nous allons utiliser 4 objets :
Les noms des objets correspondent aux noms utilisés lors de l'utilisation de la commande DEF. Ceux-ci sont indiqués en caractères gras.
Nous allons d'abord créer le parent cible et son interpolateur :
DEF cible Transform { translation -2.5 5 0 children [ Shape { geometry Box { } appearance Appearance { material Material { diffuseColor .5 0 0 } } } ] } DEF timer TimeSensor { cycleInterval 3 loop TRUE } DEF interpoleur PositionInterpolator { key [0 .5 1] keyValue [-2.5 5 0 2.5 5 0 -2.5 5 0] } ROUTE timer.fraction_changed TO interpoleur.set_fraction ROUTE interpoleur.value_changed TO cible.translation
Nous passons maintenant au parent source, c'est à dire le cone jaune déplaçable :
DEF source Transform { translation -2 0 0 children [ Shape { geometry Cone { } appearance Appearance { material Material { diffuseColor .5 .5 0 } } } DEF mover PlaneSensor { } ] } ROUTE mover.translation_changed TO source.translation
Ici , la sphère bleu qui sera la sphere qui changera de parent :
DEF sphere Transform { translation 2 0 0 children [ Shape { geometry Sphere { } appearance Appearance { material Material { diffuseColor 0 0 .5 } } } ] }
Pour l'instant, ce noeud sphere n'a pas de parent. Il est donc placé dans le sytème de coordonnées de la scènes. Il est placé au point de coordonnées X= 2, Y = 0 et Z = 0.
Reste notre boite qui sert de "parenteur". Nous lui associons un TouchSensor nommé change en les plaçant tous deux comme enfant d'un noeud Transform. Nous plaçon légèrement plus bas afin qu'il ne soit pas masqué par un autre objet :
DEF boite Transform { translation 0 -2 0 children [ Shape { geometry Box { size .5 .5 .5 } appearance Appearance { material Material { diffuseColor 1 1 1 } } } DEF change TouchSensor { } ] }
Pour gérer ce changement de parent, nous avons besoin d'un noeud script que nous appellerons parenteur.
DEF parenteur Script {
}
Ce script sera appelé au moment on l'on clique sur la boite. Nous ferons la liaison de l'eventOut touchTime du TouchSensor change vers le script parenteur via un ROUTE. Le script doit donc avoir un eventIn de type SFTime pour recevoir l'évènement touchTime. Nous appellerons cet eventIn touch. Cet eventIn appellera la fonction du même nom, la fonction touch() :
DEF parenteur Script { eventIn SFTime touch url "javascript: function touch(){ } " } ROUTE change.touchTime TO parenteur.touch
Le script doit d'abord connaitre quel objet nous parenterons. Nous ajoutons donc un champ enfant_temp de type SFNode qui fera référence au noeud sphere de la scène via la commande USE.
field SFNode sphere USE sphere
Le script à aussi besoin de savoir à quel objet nous parenterons le noeud sphere. Nous allons dans un premier temps nous contenter de la parenter au cube rouge animé qui est le noeud cible. Nous ajoutons donc un autre champ de type SFNode qui fera référence au noeud cible :
field SFNode cible USE cible
Maintenant, la fonction touch(). Cette fonction utilise la méthode addChildren. Mais cette méthode est du type MFNode, et notre noeud cible est lui de type SFNode. Ces deux types ne sont pas compatibles. Nous allons donc changer le type du champ enfant_temp. Nous le changeons de SFNode en MFNode. Dans le premier élément de ce MFNode, nous mettrons la référence à notre noeud sphere. Cette manipulation donnera au final exactement le même résultat :
field MFNode enfant_temp [USE sphere] url"javascript: function touch(){ cible.addChildren = enfant_temp ; }
En testant la scène, on voit tout de suite un problème
flagrant : le script à dupliquer notre sphere
!!
Un se retrouve parenter au noeud cible et c'est ce
que nous voulions. Mais le premier sphere est toujours
là...
De plus, et bien que celà ne se voit pas, à chaque nouveau
clic sur la boite, nous dupliquons un autre sphere.
Si nous ne le voyons pas, c'est parce que tous ces noeuds sphere
sont superposées les uns sur les autres.
Occupons nous déja du premier problème : faire diparaitre
le premier noeud sphere.
Pour celà, nous allons mettre notre noeud Tranform sphere
dans un group que nous nommerons root.
DEF root Group { children [ DEF sphere Transform { translation 2 0 0 children [ Shape { geometry Sphere {} appearance Appearance { material Material { diffuseColor 0 0 .5 } } } ] } ] }
Nous modifions notre fonction touch() de façon que en même
temps que nous ajoutons comme enfant le noeud sphere
au noeud cible, nous enlevons l'enfant sphere
du noeud root.
Nous ajoutons aussi un champ de type SFNode qui fait référence
au noeud root :
DEF parenteur Script { eventIn SFTime touch field SFNode cible USE cible field SFNode root USE root field MFNode enfant_temp [USE sphere] url"javascript: function touch(){ cible.addChildren = enfant_temp ; root.removeChildren = enfant_temp ; } " }
C'est déja mieux.
De plus, nous avons régler le 2ème problème en
même temps. Le script ne peut pas ajouter un autre noeud sphere
comme enfant au noeud cible, vu que sphere
est déja enfant de cible.
Maintenant, occupons-nous de parenter sphere à
l'objet suivant, le cone jaune qui est en fait le noeud source.
Vu que notre noeud sphere peut être parenté
à au moins trois objets, il nous faudrait une liste des parents
possibles. Cette liste est facile à faire : nous utilisons
un champ MFNode parents qui référence
chacun des parents possibles. Et du même coup, nous n'avons
plus besoin des différents champ SFNode qui recensent actuellement
les parents possibles. Notre script devient donc :
DEF parenteur Script { eventIn SFTime touch field MFNode parents [USE root USE cible USE source ] field MFNode enfant_temp [USE sphere] url"javascript: function touch(){ ...... } " }
Nous rajoutons un champ indice de type SFInt32 qui enregistrera l'indice du parent courant dans la liste parents.
field SFInt32 indice 0
Nous modifions notre fonction touch() de façon à incrémenter indice de 1 à chaque clic. Nous mettons une condition qui vérifiera que notre indice n'excéde pas le nombre de parent possible contenus dans la liste parents. Si c'est le cas, nous remettons indice à 0. Attention, il y a un petit piège : indice ne dois pas être supérieur au nombre d'éléments de parents - 1 !!!
function touch(){ if (indice < parents.length-1) { indice ++ ; } else { indice = 0 ; } ...... }
Ici, il faut faire attention et être logique :
Si nous incrémentons indice tout de suite,
indice correspondra à l'indice du futur parent.
Ce qui nous arrange pour parenter sphere au futur
parent. Mais ceci va singulièrement nous compliquer la vie
pour enlever sphere de son précédent
parent. Car l'indice du précédent parent est en réalité
indice-1. Mais comme nous avons un sytème
de remise à 0 de indice, nous allons avoir
un problème dans le cas ou le précédent parent
sera le derniers de la liste parents. Car dans ce cas, nous ne pourrons
pas utiliser indice-1.
Pour contourner le problème, il y a plus simple : nous enlevons
sphere du parents d'indice courant, nous incrémentons
indice, puis nous parentons sphere
au parents d'indice courant. sphere n'a donc pas de parent le temps que dure l'incrémentation de indice.
Vous remarquerez que avec cette technique, nous n'avons pas à
nous tracasser avec des histoire de indice-1 et
indice si c'est le dernier de parents.
Voici donc la fonction touch() telle qu'elle doit
être :
function touch(){ parents[indice].removeChildren = enfant_temp ; if (indice < parents.length-1) { indice ++ ; } else { indice = 0 ; } parents[indice].addChildren = enfant_temp ; }
Il est maintenant facile de rajouter un parent potentiel à notre sphère. Il suffit de créer un noeud qui possédent un exposedFields children, de le nommer avec DEF et de d'ajouter sa référence dans le champs parents du script.
Nous pouvons aussi envisager de rajouter un bouton qui permettrait
de faire "marche arrière" dans l'ordre de parentage.
Il convient pour cela :
- de rajouter un objet avec un TouchSensor :
DEF boite2 Transform { translation 2 -2 0 children [ Shape { geometry Box { size .5 .5 .5 } appearance Appearance { material Material { diffuseColor 1 1 1 } } } DEF change2 TouchSensor { } ] }
- rajouter un eventIn de type SFTime au script ainsi que les ROUTE :
eventIn SFTime touch2 ..... ROUTE change2.touchTime TO parenteur.touch2
- le script demande lui plus de modifications. Il faut en effet rajouter un fonction touch2() qui décrémentera indice, avec là aussi une condition qui vérifie que indice ne tombe pas en dessous de 0 et le remette à la valeur nombre d'éléments de parents-1 le cas échéant :
function touch2(){ if (indice > 0) { indice -- ; } else { indice = parents.length-1 ; } }
Les function touch() et touch2()
feront toutes les deux appels à deux autre fonctions :
- une fonction add() pour ajouter un enfant
- une fonction remove() pour enlever un enfant.
function touch(){ remove() ; if (indice < parents.length-1) { indice ++ ; } else { indice = 0 ; } add() ; } function touch2(){ remove() ; if (indice > 0) { indice -- ; } else { indice = parents.length-1 ; } add() ; } function add(){ parents[indice].addChildren = enfant_temp ; } function remove(){ parents[indice].removeChildren = enfant_temp ; }
Dernière petite variante possible, nous ajoutons un troisième
bouton qui lui parentera sphere à un objet
au hasard.
Il faut donc :
- rajouter un bouton
- un eventIn, une ROUTE et une fonction.
La fonction touch3() est assez simple et posséde
une structure très similaire au fonction touch() et
touch2(). Sa particularité est d'utiliser
une fonction standard Math.random() multipliée
par le nombre d'éléments de parents
-1, que nous arrondissons pour avoir un indice entier
naturel :
function touch3(){ remove() ; indice = Math.round((parents.length-1)*Math.random()) ; add() ; }