Pourquoi les rotations font-elles appel à autant de nombres rien que pour faire tourner quelque chose?
Ne serait-il pas plus simple d'avoir trois rotations, une pour chaque axe x, y ou z?
Tout ce qu'il faudrait alors serait de préciser de quel angle l'objet devrait tourner.
Ne serait-ce pas également plus facile pour l'ordinateur, plutôt que de se lancer dans les
mathématiques compliquées mises en oeuvre par les rotations en Vrml?
C'est vrai que l'on peut en faire beaucoup, rien qu'avec des rotations autour de x, de y et de z,
que c'est plus rapide et plus à la portée de n'importe quel débutant. On peut se servir
des rotations Vrml de cette façon en s'en tenant à une seule des trois composantes de l'axe
et annulant les deux autres, comme ceci :
1 0 0 pour choisir la rotation autour de x
0 1 0 pour choisir la rotation autour de y
0 0 1 pour choisir la rotation autour de z
La composante angulaire de ma rotation Vrml est ensuite ajustée pour que l'objet tourne de la
valeur voulue autour de l'axe spécifié.
Ce serait la pire façon d'exploiter un bon plan. Celà nous entraînerait à supporter la complexité
d'emploi de 4 valeurs, comme à supporter la perte de performance impliquée par
la mathématique sous-jacente, sans en tirer aucun bénéfice en retour.
Les rotations Vrml sont un excellent exemple de la pratique qui consiste à tolérer une
complexité supplémentaire dans un premier temps, de façon à se simplifier considérablement
la tâche par la suite.
La grande affaire des rotations Vrml est qu'elles autorisent toutes les interpolations
pensables entre une orientation quelconque au départ et n'importe quelle autre
orientation imaginable. Si nous tentions d'y parvenir avec seulement trois fonctions
d'orientation selon trois axes comme on vient de le suggérer, nous comprendrions que
notre merveilleuse idée nous compliquerait terriblement la vie pour obtenir ce résultat.
Dès que nous aurons compris que nous pouvons animer un objet sans trop d'efforts
de programmation de notre part, comme je le montre dans mon didacticiel sur l'Animation
des sommets, nous apprécierons très vite les rotations Vrml sans plus nous soucier
d'avoir à leur fournir quatre données plutôt qu'une.
Une autre bonne chose bien significative dans les rotations Vrml tient à ce que nous
pouvons concaténer une séquence de rotations en une rotation unique grâce à la fonction
de multiplication.
Pour comprendre en quoi consistent les rotations Vrml, il nous faut comprendre ce que
signifie la rotation d'un objet selon un axe quelconque et pour cela, mieux vaut commencer
par comprendre aussi ce qu'est un vecteur unité (ou un vecteur normalisé).
Qu'est-ce qu'un Vecteur Unité ?
Encore qu'un vecteur unité n'ait aucune forme physique, ni origine particulière,
il n'est pas mauvais de se le représenter comme un objet mince et pointu de un
mètre de long, et de penser à ce que représentent ses trois composantes en lui
attribuant (artificiellement) une base en position 0 0 0.
Dans ces conditions, les trois composantes x, y et z désigneraient sa pointe.
Qu'est-ce qu'un Axe ?
Dans le cas des rotations Vrml, nous pouvons considérer l'axe comme un vecteur unité
qui embrocherait l'objet que l'on veut faire tourner.
Cette broche ne se tordra jamais, quand elle tourne elle le fait
de façon parfaite, quelle que soit l'orientation qu'on lui a imposée.
Cela veut dire aussi que la pointe du vecteur reste exactement à sa place,
quelle que soit la composante angulaire qu'on a également fixée.
La rotation Vrml semble donc particulièrement simple de ce point de vue.
Amener un objet à pointer vers un autre, n'importe où dans l'espace 3d.
Amener un objet à pointer vers un autre dans l'espace 2d s'obtient
facilement par la fonction Math.atan2
Dans l'exemple suivant nous avons deux objets
#VRML V2.0 utf8
NavigationInfo { type "examine"}
Viewpoint { position 0 0 11}
DEF objectA Transform {
translation 1.7 2.6 0 #position de l'objet A
children Shape {
appearance Appearance {
material Material {diffuseColor 0 0 1}
}
geometry Sphere {radius .5}
}
}
DEF pointer Transform {
translation 0 0 0 #position de la base du pointeur
children Transform {
translation 2.5 0 0
rotation 0 0 1 1.5708
children Shape {
appearance Appearance {material Material {diffuseColor 1 0 0}}
geometry Cylinder {
radius .08
height 5
}
}
}
}
L'un des objets est une sphère bleue appelée
ObjectA (
objet A). Il servira d'objet
vers lequel on doit pointer.
L'autre objet est un long et mince cylindre rouge appelé le
pointer (
pointeur).
C'est lui qui sera l'objet à pointer vers l'objet A. La base du pointeur est fixée en 0 0 0.
Pour faire pointer notre pointeur vers l'objet A dans le plan z nous ajoutons le script suivant :
DEF myScript Script{
directOutput TRUE
field SFNode objectA USE objectA
field SFNode pointer USE pointer
url "javascript:
function initialize(){
zAngle=Math.atan2( objectA.translation.y, objectA.translation.x);
pointer.rotation = new SFRotation(0,0,1,zAngle);
}
"
}
Dès maintenant, quelque translation que l'on puisse imposer à l'objet A (dans son champ
translation) nous devrions voir le pointeur toujours pointer vers lui dans les
directions x et y. Cela se vérifie facilement en maintenant toujours à 0 la composante z
du champ translation de l'objet A. Ceci entraîne en effet que le pointeur traverse
toujours l'objet A en son centre.
Maintenant, si nous voulons que le pointeur puisse pointer l'objet A lorsque sa base occupe
une position différente de 0 0 0, nous avons à reprendre nos calculs pour que la base du
pointeur revienne en 0 0 0 dans l'environnement relatif à l'objet A.
Cela s'obtient par soustraction des coordonnées du pointeur de celles
de l'objet A.
Pour ce faire on ajoute le code :
relObjectAPos=objectA.translation.subtract(pointer.translation);
de sorte que ce soit la première ligne de code dans les accolades de notre
fonction d'initialisation.
on modifie ensuite la ligne de code :
zAngle=Math.atan2( objectA.translation.y, objectA.translation.x);
en
zAngle=Math.atan2( relObjectAPos.y, relObjectAPos.x);
Maintenant il nous est possible de changer à la fois la position de l'objet A
(la sphère bleue) et du pointeur (le long cylindre rouge) et nous verrons toujours
le pointeur traverser le centre de l'objet A, pour autant que les composantes z
de nos deux objets soient toujours fixées à 0 dans leur champ translation.
Bien sûr on peut fixer la composante z à d'autres valeurs que 0. Notre pointeur
va toujours pointer dans la direction de l'objet A, mais seulement en tournant autour de
son axe z, de sorte qu'il ne pourra jamais pointer dans la direction z mais seulement
dans les directions x et y.
Du fait que Math.atan2 est une fonction qui ne nous donne que l'angle pour une
position dans un espace 2d, comment allons-nous lui demander une orientation en 3d?
Pour pouvoir obtenir n'importe quelle orientation d'un objet dans un espace 3d, il nous
suffit le le faire tourner en deux rotations successives, n'utilisant à chaque fois
que 2 axes sur les 3 axes x, y et z disponibles.
Dans cet exemple nous utiliserons les axes z et y pour nos 2 rotations :
Pour obtenir les 2 rotations de notre pointeur nous allons d'abord exécuter les calculs
comme si nous devions faire tourner l'objet A 2 fois pour qu'il aboutisse finalement
sur l'axe x du côté des valeurs positives de x dans le même environnement que notre
pointeur lorsqu'il n'a subi encore aucune rotation.
Cela signifie qu'il est logique de pouvoir ramener notre objet A là où il était,
par les mêmes rotations mais en ordre inverse et négativement. Toutefois, au lieu
d'imposer cette rotation en retour à notre objet A, c'est le pointeur que nous allons faire
tourner, de sorte qu'il va pointer exactement au point d'où l'objet A serait venu.
Evidemment, au lieu de faire tourner l'objet A il nous suffit de faire tourner la variable
relObjectAPos (champ SFVec3f) temporairement pour mener à bien le calcul :
Après la ligne de code qui obtient l'angle zAngle nous voulons tourner
relObjectAPos sur l'axe z d'un angle zAngle négatif en ajoutant la
ligne de code suivante :
relObjectAPos = new SFRotation(0,0,1,-zAngle).multVec(relObjectAPos);
cette ligne de code va fixer à 0 la composante z de relObjectAPos
ce qui nous permet d'obtenir l'angle yAngle en ajoutant cette ligne :
yAngle=-Math.atan2( relObjectAPos.z, relObjectAPos.x);
si nous faisons tourner la position de relObjectAPos d'un angle yAngle,
il en résulte que relObjectAPos devient toujours = 0 pour
les composantes y et z avec une valeur positive pour la composante x.
En fait il n'est pas vraiment utile d'effectuer la rotation de relObjectAPos
d'un angle yAngle pour nos calculs, puisque nous avons déjà obtenu cette valeur
à l'étape actuelle, mais pour vérifier que notre code reste en accord avec le concept
de rotation inverse de l'obet A nous pouvons ajouter les lignes suivantes si
nous le souhaitons :
relObjectAPos = new SFRotation(0,1,0,-yAngle).multVec(relObjectAPos);
print(relObjectAPos);
ce qui devrait montrer que les composantes z et y de relObjectAPos sont toutes
deux égales à 0 et que sa composante x est positive.
pour la rotation arrière du pointeur en ordre inverse de celui dont a tourné relObjectAPos
il faut changer la dernière ligne de code en :
pointer.rotation = new SFRotation(0,1,0,yAngle).multiply(new SFRotation(0,0,1,zAngle));
notre fonction d'initialisation complète devient alors :
function initialize(){
relObjectAPos=objectA.translation.subtract(pointer.translation);
zAngle=Math.atan2( relObjectAPos.y, relObjectAPos.x);
relObjectAPos = new SFRotation(0,0,1,-zAngle).multVec(relObjectAPos);
yAngle=-Math.atan2( relObjectAPos.z, relObjectAPos.x);
pointer.rotation = new SFRotation(0,1,0,yAngle).multiply(new SFRotation(0,0,1,zAngle));
}
Il suffit de rafraîchir pour voir le pointeur pointer dans la direction de l'objet A
dans l'espace 3d.
Essayez de changer les données de l'objet A ou le champ translation de la base du pointeur.
On devrait toujours voir le pointeur pointer vers l'objet A avec précision dans l'espace 3d.
(
voir le code pointeur_vers_sphere.wrl
voir l'exemple pointeur_vers_sphere.wrl )
---> il vous faut le plugin de
Blaxxun Contact pour ce didacticiel.