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 allons créez un systèmes qui permet de mélanger
les billes sur un plateau de billard.
Les billes peuvent être placées n'importe où sur
le plateau qui mesure 210 cm x 105 cm à 82.5 cm de haut. Les
billes ne doivent pas s'interpénétrer.
Ils y a 16 billes : 7 rouges, 7 jaunes et une noire de 5.08 cm de diamètre
et 1 blanche de 4.7 cm de diamètre
Au chargement de la scène, les billes seront placées aléatoirement
sur le plateau.
Commençons par définir la première bille rouge.
C'est une sphère rouge de 2.54 de rayon. Nous la nommons B1
:
DEF B1 Transform { children DEF billerouge Shape { geometry Sphere { radius 2.54 } appearance Appearance { material Material { diffuseColor .8 0 0 specularColor .5 .5 .5 emissiveColor .15 0 0 ambientIntensity 0 } } } }
Vous noterez qu'ici, nous ne précisons aucune information quant
à la position de la bille. Elle est donc située par défaut
au centre de la scène au point x=0 , y=0 et z=0.
Nous avons définis comme enfant de B1 la Shape billerouge
que nous réutilisons pour les autres billes rouges avec la commande
USE.
DEF B2 Transform { children USE billerouge } DEF B3 Transform { children USE billerouge } DEF B4 Transform { children USE billerouge } DEF B5 Transform { children USE billerouge } DEF B6 Transform { children USE billerouge } DEF B7 Transform { children USE billerouge }Les billes rouges B1, B2, B3, B4, B5, B6 et B7 sont toutes définies. Pour les jaunes, c'est le même principe avec une Shape billejaune. Ces billes jaunes seront nommées à la suite des billes rouges, soit B8, B9, B10, B11, B12, B13 et B14
DEF B8 Transform { children DEF billejaune Shape { geometry Sphere { radius 2.54 } appearance Appearance { material Material { diffuseColor .8 .63 0 specularColor .5 .5 .5 emissiveColor .14 .11 0 ambientIntensity 0 } } } } DEF B9 Transform { children USE billejaune } DEF B10 Transform { children USE billejaune } DEF B11 Transform { children USE billejaune } DEF B12 Transform { children USE billejaune } DEF B13 Transform { children USE billejaune } DEF B14 Transform { children USE billejaune }La 15ème bille, la noire :
DEF B15 Transform { children DEF billenoire Shape { geometry Sphere { radius 2.54 } appearance Appearance { material Material { diffuseColor 0 0 0 specularColor .502 .502 .502 emissiveColor .149 .149 .149 ambientIntensity 0 shininess .369 } } } }Reste la 16ème, la bille blanche. Attention, son rayon est plus petit !
DEF B16 Transform { children DEF billeblanche Shape { geometry Sphere { radius 2.35 } appearance Appearance { material Material { diffuseColor .75 .75 .75 specularColor .5 .5 .5 emissiveColor .15 .15 .15 ambientIntensity 0 } } } }
Toutes ces billes sont situées au même emplacement, le centre de la scène de coordonnées [ 0 0 0 ].
Créons une simple boite verte en guise de plateau : nous lui
donnons les dimensions suivantes.
L = 210, l = 105 et H = 82.5
Shape { appearance Appearance { material Material { diffuseColor .21 .44 0 emissiveColor .09 .18 0 ambientIntensity 0 shininess 0 } } geometry Box { size 210 82.5 105 } }
Ce plateau est situé lui aussi au point de coordonnées [0 0 0 ] de la scène.
Passons maintenant au script. Appelons le placeur. Il contient une fonction standart initialize(). Cette fonction nous oblige à mettre la propriété mustEvaluate du script à TRUE
DEF placeur Script { mustEvaluate TRUE url "javascript: function initialize(){ } " }
Le script fera appel à la position de chacune des billes via un champ bille de type MFNode. Ce champ référence les billes de la scènes avec USE.
field MFNode bille [ USE B1 USE B2 USE B3 USE B4 USE B5 USE B6 USE B7 USE B8 USE B9 USE B10 USE B11 USE B12 USE B13 USE B14 USE B15 USE B16 ]
Nous utilisons un champ postemp de type SFVec3f qui contiendra
une position générée aléatoirement. Toutefois,
nous savons que les billes seront toutes placées à la
même hauteur. (coordonnée Y).Cette hauteur est : la moitié
de la hauteur du tapis + le rayon des billes, soit 82.5/2 + 2.54 = 43.79.
Nous définissons donc à l'avance cette coordonnée
dans la valeur par défaut du champ postemp
(Nous verrons plus tard comment nous occuper du cas de la bille blanche)
field SFVec3f postemp 0 43.79 0
Nous définissons maintenant une boucle qui va affecter des valeurs aléatoires aux coordonnées X et Z de postemp. La valeur X variant de -105 à 105 et la Z de -52.5 à 52.5. (Le tapis étant centré sur [ 0 0 0], et sa longueur étant de 210, il va en réalité de la coordonnée X = - ( L/2) à X = L/2... Idem pour la largeur.)
for (i=0;i<16;i++){ postemp.x = Math.random()*210 - 105 ; postemp.z = Math.random()*105 - 52.5 ; bille[i].translation = postemp ; }
Le tapis du billard est entouré d'un rebord qui empêche les billes de tomber par terre. Par conséquent, c'est l'extérieur de la bille qui doit atteindre la coordonnées X = 105 ou X = -105 (idem pour la largeur). L'amplitude est donc la longueur du tapis moins le diamétre de la bille. Comme nous avons vus précédemment qui fallait recentrer ces coordonnées, il faut, dans notre opération, soustraire la moitié de la longueur moins le rayon de la bille. Ce qui donne :
for (i=0;i < 16;i++){ postemp.x = Math.random()*204.92 - 102.46 ; postemp.z = Math.random()*99.92 - 49.96 ; bille[i].translation = postemp ; }
Maintenant; il nous faut régler un problème : avec ce
script, les billes sont placées aléatoirement. Mais il
se peut que les billes se chevauchent. Les positions générées
aléatoirement ne tiennent pas compte des emplacements occupés
par les billes déjà placées.
Ce qu'il nous faut, c'est une fonction qui pour chaque bille génère
une position aléatoire et vérifie que cette position n'est
pas trop voisine d'une position déjà affectée.
Si c'est le cas, la fonction se relance.
Sinon, elle place la bille suivante.
La fonction pour générer une position aléatoire existe déjà : ce sont les instructions contenues dans la boucle de la fonction initialize(). Plaçons ces instructions dans une fonction genpos() qui sera exécuter par la function initialize()
function genpos(i){ postemp.x = Math.random()*204.92 - 102.46 ; postemp.z = Math.random()*99.92 - 49.96 ; bille[i].translation = postemp ; } function initialize(){ for (i=0;i<16;i++){ genpos(i); } }
Remarquez que nous passons à la fonction genpos() l'argument i. Cet argument est important car il est nécessaire pour savoir à quelle bille du champ MFNode bille nous affectons la postemp générée.
Rajoutons à notre fonction fonction genpos(i) des instructions
qui vérifieront si postemp correspond à une position
déjà occuper par une une bille.
Pour celà, nous utilisons une boucle. Cette boucle vérifie
si le centre de la bille que nous plaçons (autrement dit, postemp)
se trouve à une distance inférieur à son diamètre
du centre d'une des billes déjà placée.
function genpos(i){ postemp.x = Math.random()*204.92 - 102.46 ; postemp.z = Math.random()*99.92 - 49.96 ; bille[i].translation = postemp ; for (j=0;j<i;j++){ var dist = Math.sqrt( (bille[j].translation.x - postemp.x)*
(bille[j].translation.x - postemp.x)
+ (bille[j].translation.z - postemp.z)*
(bille[j].translation.z - postemp.z) ) ; } }
Nous connaissons maintenant la distance entre la bille que nous plaçons et la jème placée. Nous vérifions si cette distance est supérieur à deux rayons de bille, soit un diamètre : D = 5.08 cm
if ( dist < 5.08 ){ }
Il y a deux cas :
Si la distance est supérieur, nous passons à la boucle suivante de la fonction initialize().
Sinon, nous pouvons déjà arrêter de vérifier
les distance. Nous sortons de la boucle for de la fonction genpos(i)
avec l'instruction break.
if ( dist < 5.08 ){ break ; }
Nous voilà de retour dans la boucle for de la fonction initialize(). Nous savons que postemp ne convenait pas, mais le script lui ne le sait pas.Nous enregistrons donc cet état dans un field nommé collide de type SFBool. Par défaut, il sera à FALSE.
field SFBool collide FALSE
C'est dans la boucle for de la fonction genpos(i) qu'il faut enregistrer cet état.
if ( dist < 5.08 ){ collide = true ; break ; } else { collide = false ; }
Mais c'est dans la boucle for de la fonction initialize() que nous utiliserons cet donnée. Car si collide = TRUE, il faut relancer la fonction genpos(i). Et il faudra la relancer tant que collide sera égal à TRUE. Nous utilisons pour celà l'instruction while.
function initialize(){ for (i=1;i<16;i++){ genpos(i) ; while (collide){ genpos(i) ; } } }
Reste le cas de la bille blanche. C'est la dernière du champ MFNode bille. Nous ajoutons donc une condition dans la boucle de la fonction initialiaze(). Si c'est la dernière bille, on change la coordonnées Y de postemp à 43.6 (Hauteur du tapis/ 2 + rayon de la bille blanche) De cette façon, la bille blanche sera correctement posé sur le tapis.
if (i > 14) postemp.y = 43.6 ;
Les plus perspicaces remarqueront qu'ils faudrait aussi modifier les valeurs associées au Math.random() de postemp.x et postemp.z de la fonction genpos(i).
Sinon, nous pouvons optimiser ce script en plaçant l'affectation de postemp à bille[i].translation après avoir généré une postemp vérifiée. C'est à dire après le while de la fonction initialize().
Nous pouvons rajouter un bouton qui permet de remélanger les billes. Il suffit de rajouter dans un noeud Transform une boite et un touchSensor. Plaçons ce bouton en avant de la boite qui sert de tapis. Le capteur sera nommé melange.
Transform { translation 0 0 60 children [ Shape { geometry Box {size 15 5 5} } DEF melange TouchSensor{} ] }
Nous ajoutons un eventIn touch de type SFTime au script placeur.
eventIn SFTime touch
Cet eventIn lancera une fonction touch() qui lancera la fonction initialize().
function touch(){ initialize() ; }
Nous routons le touchTime de melange vers l'eventIn touch du script placeur.
ROUTE sensor.touchTime TO placeur.touch
Et pour remercier les courageux qui sont allez jusqu'au bout de l'exercice, voici le code d'un plateau de billard tout fait !