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.
Nous avons une série de N objets.
Par défaut, ces objets utilisent N emplacements dans l'espace.
Nous voulons qu'en cliquant sur n'importe lequel de ces objets, leurs
emplacements soient permutées aléatoirement.
Commençons par définir les positions que nous souhaitons.
Ce sont des noeuds transform sans enfants. Dans cet exemple, nous n'en
utiliserons que 6, mais vous pouvez le faire pour N.
DEF pos1 Transform {
}
DEF pos2 Transform {
translation 5 0 0 }
DEF pos3 Transform {
translation 5 5 0 }
DEF pos4 Transform {
translation 0 5 0 }
DEF pos5 Transform {
translation 10 0 0 } DEF pos6 Transform {
translation 10 5 0 }
Vous noterez qu'ici, nous utilisons un exemple simple. Vous pourriez rajouter des paramètres de rotation ou d'échelle pour chacun de ces noeuds.
Nous allons maintenant définir les objets, plus précisement les géomètries qui seront associées aux positions définies plus haut. On utilise des primitives, mais vous pouvez en fait utiliser n'importe quel type de noeud, Transform, Group, Material, etc...
DEF geom1 Shape { geometry Box {} } DEF geom2 Shape { geometry Sphere {} } DEF geom3 Shape { geometry Cone {} }Nous avons donc une boite,une sphère, un cône, un cylindre, une lettre et un triangle, tous placés par défaut en 0 0 0 de notre scène. Nous allons les parenter un par un à chaque position afin qu'ils ne soient pas tous superposés au lancement de la scène.
DEF geom4 Shape { geometry Cylinder {} }
DEF geom5 Shape { geometry Text{string"A"} }
DEF geom6 Shape {
geometry IndexedLineSet {
coord Coordinate {
point [-.5 0 0 0 1 0 .5 0 0]
}
coordIndex [0 1 2 0 -1]
}
}
DEF pos1 Transform { children DEF geom1 Shape { geometry Box {} } } DEF pos2 Transform { translation 5 0 0 children DEF geom2 Shape { geometry Sphere {} } } DEF pos3 Transform { translation 5 5 0 children DEF geom3 Shape { geometry Cone {} } } DEF pos4 Transform { translation 0 5 0 children DEF geom4 Shape { geometry Cylinder {} } } DEF pos5 Transform { translation 10 0 0 children DEF geom5 Shape { geometry Text{string"A"} } } DEF pos6 Transform { translation 10 5 0 children DEF geom6 Shape { geometry IndexedLineSet { coord Coordinate { point[-.5 0 0 0 1 0 .5 0 0] } coordIndex[0 1 2 0 -1] } } }Voir la scène
Toutes les géomètries sont maintenant réparties
sur une aire rectangulaire.
Pour déclencher la permutation de position, nous utiliserons
un ToucheSensor. Pour l'associer avec tous les objets, il suffit de
le placer tel quel dans la scène.
DEF capteur TouchSensor{}
Maintenant, le script qui va faire la permutation de position. La fonction de permutation se déclenchera au moment ou l'on clique sur la sphere rouge. L'eventIn sera donc un SFTime.
TDEF permut Script { eventIn SFTime touch url "javascript: function touch(){ } " }
Routons tout de suite notre événement touchTime vers le script.
ROUTE capteur.touchTime TO permut.touchVoir la scène
Le capteur est actif, mais il lance une fonction qui pour l'instant ne fait rien.
Prenons le temps de comprendre le principe que nous allons utiliser. D'abord, que savons nous ?
Les coordonnées des noeuds Transform peuvent être très
complexes : translation, rotation, scale, centre et scaleOrientation.
Plutôt que de permuter toutes ses coordonnées, nous allons
permuter les enfants de ces noeuds. Ce qui revient au même.
Notre script utilisera donc deux listes :
Nous utilisons pour celà des champs MFNode.
field MFNode pos [] field MFNode enfants []
Pour l'instant, ces liste sont vide. Ils nous faut les remplir avec les élément correspondant. Servons nous de l'instruction USE qui appelle les noeuds (coordonnées et géomètries) déjà définies.
field MFNode pos [USE pos1 USE pos2 USE pos3 USE pos4 USE pos5 USE pos6 ] field MFNode enfants [USE geom1 USE geom2 USE geom3 USE geom4 USE geom5 USE geom6 ]
Il nous faut une fonction qui associe à chaque élément
de la liste pos un élément choisis au hazard dans
la liste enfants, mais sans jamais utilisé le même
enfant.
Là, nous rentrons dans la programmation pur. Connaissance en
javascript bienvenue !
Je prend le temps de vous expliquer 3 principes possibles.
Cela reviens à piocher au hazard et une par une toutes les cartes d'un jeu jusqu'à la dernière. Les cartes tirées ne sont pas remises dans le jeu. Ce qui veut dire qu'on ne peut jamais piocher deux fois la même carte.
Dans notre cas de figure, on veut un enfant choisis au hazard dans une liste enfants que l'on parente à une position issue d'une liste pos.
La première méthode :
1) On tire un enfant au hazard dans la liste enfants
et on mémorise son nom dans un autre tableau mem.
.(l'enfant fait toujours partie de notre liste enfants.)Puis,
on l'associe au premier élément de la liste pos.
2) Pour l'enfant suivant, on vérifie si son nom est déjà
dans notre tableau mem des enfants déjà
utilisés :
- Si oui, on retire au hazard un autre enfant dans la liste enfants.
- Sinon, on mémorise son nom dans le tableau mem et on l'associe au 2ème éléments de la liste pos.
3) Ainsi de suite jusqu'à avoir tiré chacun des enfant de la liste enfants.
Le problème, c'est qu'on peut repiocher pas mal de fois,
surtout vers la fin. :-(
Deuxième méthode :
1) On tire au hazard un enfant dans la liste enfants,
on l'associe au 1er élément de la liste pos.
2) On enlève l'enfant choisis de la liste enfants
et la pos correspondante de la liste pos.
3) On recommence jusqu'à épuisement des enfants,
et donc des pos..
Ici, nous devons redimensionner et reclasser deux liste à
chaque tirage.
Troisième méthode :
1) On mélange les éléments de la liste
enfants
2) On tire le 1er enfant, on l'associe à la 1ère
pos.
3) On tire le 2ème enfant, on l'associe à la 2ème
pos.
4) Ainsi de suite jusqu'à épuisement des enfants
et des pos.
Le truc, c'est que à chaque clic sur le capteur, on remélange la liste enfants. Puis on distribue le 1er enfant à la première pos, etc..
Nous utiliserons la 3ème méthode que je trouve au finale
comme étant la plus simple et la plus efficace.
Qu'est ce que ça donne pour notre script ?
Il nous faut deux boucles :
- une pour mélanger les enfants
- une pour attribuer chaque enfant à chaque pos
Pour la boucle de mélange des enfants, il nous faut une donnée supplémentaire : un champ qui stockera temporairement le noeuds déplacé pendant le mélange.
field SFNode temp NULL
Voici la boucle pour le mélange des enfants. Le mélange se fait par permutation d'éléments. Ce principe permet de ne jamais avoir deux fois le même élément dans la liste après le mélange.
for (j = 0; j < pos.length; j++) { k = Math.floor(Math.random() * pos.length); temp = enfants [ j ] ; enfants [ j ] = enfants [ k ] ; enfants [ k ] = temp ; }
Nous avons donc maintenant les éléments de la liste enfants mélangés de façon aléatoire. Plus de détails sur le fonctionnement de cette boucle ici.
La boucle pour associer les enfants aux pos est plus simple.
for (i=0;i<pos.length;i++){
pos [ i ] .children [ 0 ] = enfants [ i ] ;
}
La propriété children des noeuds Transform est un exposedField de type MFNode. Le script a directement accès aux children des noeuds pos qui sont définis dans le champ MFNode pos. Nous remplaçons seulement le premier des children, d'où le .[ 0 ] à la suite de children.
Voici le code final du script
DEF permut Script { eventIn SFTime touch field MFNode pos [USE pos1 USE pos2 USE pos3 USE pos4 USE pos5 USE pos6 ] field MFNode enfants [USE geom1 USE geom2 USE geom3 USE geom4 USE geom5 USE geom6 ] field SFNode temp NULL url "javascript: function touch(){ for (j = 0; j < pos.length; j++) { k = Math.floor(Math.random() * pos.length); temp = enfants [ j ] ; enfants [ j ] = enfants [ k ] ; enfants [ k ] = temp ; } for (i=0;i<pos.length;i++){ pos [ i ] .children [ 0 ] = enfants [ i ] ; } } " }
Pour l'instant, à chaque lancement de notre scène, les objets ont toujours la même position. Ce serait bien mieux, si au lancement les objets étaient déjà mélangés.
Pour exécuter le mélange à l'ouverture de la scène, on utilise la fonction standart initialize.
function initialize(){ }
Cette fonction ne fait en fait qu'exécuter la fonction touch
function initialize(){ touch(); }
A chaque chargement de la scène, les objets auront une positions différentes. Essayez et vous verrez !
Vous avez tout compris du premier coup?
Félicitations, vous êtes très fort !
Petite question subsidiaire pour départager les ex-aequos :
Combien y a t'il de dispositions différentes possibles pour les
6 objets de cette scène ?