Comprendre les Rotations en VRML

NdT : attention, "c'est du brutal !"      : o)

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.



Copyright© 2000-2008 Graham Perrett thyme@seamless3d.com
traduction Matthieu