retour à la
page de Thyme
en français.

Maîtriser le Triangle

Le Débutant absolu anime un Triangle

Si vous ne voulez pas vous poser d'inutiles questions, ajustez votre ordinateur de façon à pouvoir lire les extensions des fichiers.

et pour ça :
     clic sur "Démarrer"
     choisir "Paramètres"
     clic sur "Panneau de configuration"
     clic sur "Options des dossiers"
     clic sur l'onglet "Affichage"
     décocher "masquer l'extension des fichiers dont le type est connu"
          s'il n'est pas DEJA décoché.
     clic sur "OK"

Nous avons besoin d'installer le programme VrmlPad sur notre ordinateur. C'est un petit programme et il s'installe très rapidement. Entre le téléchargement et l'installation complète l'opération ne devrait prendre que quelques minutes. Téléchargez le fichier d'installation ici :

http://vrmlpad.parallelgraphics.com/

Il suffit de la version légère de 456 KB pour ce que nous voulons en faire maintenant. Sauvegardez-le sur votre bureau ou là où vous voulez. Une fois qu'il est téléchargé, il suffit de le double cliquer pour l'installer. Aucun besoin de redémarrer l'ordinateur pour l'utiliser de sorte qu'on peut s'en tirer à 100% sans quitter l'internet :)

Il nous faut maintenant un dossier pour y installer nos fichiers exemples
et pour ça :

     Ouvrez "Explorateur Windows".
     Choisissez C:\Mes Documents
     clic droit dans le blanc de ce fichier pour éviter de cliquer une icône
     cliquez Fichier
     cliquez Nouveau
     cliquez Dossier
     appelez-le Vrml_Exemples
     ouvrez ce nouveau fichier

Nous devons savoir ouvrir facilement les fichiers que nous voyons dans le menu d'un simple clic droit pour les modifier.
Pour y arriver :

    Ouvrez VrmlPad puis cliquez tools (outils) dans le menu puis choisissez options
    Cochez "Associate VrmlPad with VRML files" (associer VrmlPad avec les fichiers VRML)
    puis là choisissezt "As Secondary Edit Action" (pour les modifier)
    Cliquez OK

Nous pouvons commencer à écrire le code de notre triangle dans le VrmlPad.

La première ligne

   #VRML V2.0 utf8

doit toujours être exactement au début d'un fichier VRML. C'est cette ligne qui indique au programme visionneur qu'il doit l'interpréter comme un fichier vrml. Par la suite quand vous voyez un texte commencer par "#" et ceci jusqu'au début d'une nouvelle ligne, vous savez qu'il s'agit d'un commentaire. Ces commentaires sont à usage humain seul et n'ont aucun effet sur le déroulement du programme.

Noeuds et Champs

Pensez qu'un Noeud est un bloc de construction urilisé en Vrml.
Les Noeuds contiennent souvent d'autres Noeuds ou des champs. Ce sont les champs qui contiennent les données utilisées pour fixer les propriétés des Noeuds. Par exemple un champ peut spécifier la vitesse, la couleur ou la position d'un Noeud. Les autres Noeuds et les champs qui appartiennent à un Noeud sont des membres de ce Noeud. Tous les membres d'un Noeud, qu'ils soient des champs ou d'autres Noeuds, ont un nom.
Un nom de Noeud commence par une Majuscule, un nom de champ commence par une minuscule.
Il ne faut pas confondre le nom d'un champ avec le Type d'un champ. On précisera plus tard ce que sont ces types, mais sachons qu'il existe par exemple des champs SF ou MF (champ Simple ou Multiple), ils sont ensuite Vect2f ou Vect3f par exemple pour des vecteurs à deux ou trois dimensions en virgule flottante. Il y a encore Time pour une donnée de temps, String pour une chaîne de caractères, il y a encore Color et Rotation et Image... Ce sont ces types qui vont indiquer au visionneur ce qu'il va trouver et qu'il devra interpréter. Il est inutile de chercher à se souvenir de ces détails, le principe suffit et on reviendra plus tard seulement à cette question.

Ce qui est important, c'est de savoir que si un nom de champ et le nom du Noeud qui le suit (qui est contenu dans ce champ) sont les mêmes, l'un avec une minuscule, l'autre avec une majuscule, on ne doit pas s'en étonner, même si on est débutant.
Tout cela s'emboîte logiquement :
- Un Noeud est un bloc de construction. Un champ contient des données.
- Un Noeud contient un ou des champs, il peut contenir d'autres Noeuds
- Un champ contient des données. Il peut aussi contenir d'autres Noeuds.

Enfin, après la convention des minuscules et majuscules des noms de champ et de Noeud, il en existe une autre. Dans les deux cas, Nœud ou champ, quand un nom est un nom composé chaque nouveau mot du nom commence par une majuscule, exemples : IndexedFaceSet ou diffuseColor.

Nous allons fabriquer un polygone à trois côtés qui aura un mètre de haut et un mètre de gauche à droite. Pour fabriquer quoi que ce soit en Vrml, nous devons instancier un Noeud Shape (forme).
Pour ce faire nous ajoutons ce code :

   Shape{
   }

Remarquez qu'en VrmlPad une erreur nous est signalée tant que nous n'avons pas refermé une accolade. Ceci, parce que le code de l'instanciation est incomplet jusqu'à ce que l'accolade soit refermée. Mais dès que nous avons terminé d'écrire le code nécessaire au Noeud Shape le code est légal, et le visionneur Blaxxun contact peut l'ouvrir sans erreur. Avant toutefois de pouvoir ouvrir le code dans le vivionneur nous devons l'enregistrer dans VrmlPad avec les clés CTRL+S (ou un clic sur la vignette de sauvegarde, une petite disquette).
On laisse VrmlPad ouvert.
On peut ouvrir le code en cliquant sur une vignette de la ligne du haut, un petit écran d'ordinateur bleu. (On peut aussi rechercher le fichier dans l'Explorateur Windows et double cliquer dessus). Ce que nous voyons alors (avec Windows XP, il y a une étape de sécurité supplémentaire à franchir), c'est un écran noir, mais si tout va bien il n'y a pas de diagnostic d'erreur. En revanche on ne voit aucune forme, puisque nous n'en avons codé aucune dans le champ geometry (géométrie) du Noeud Shape. Tant que nous n'avons pas instancié un Noeud il n'existe pas.
Nous avons maintenant le choix du Noeud que nous allons installer dans le champ geometry. Comme nous voulons dessiner un polygone, le Noeud choisi sera IndexedFaceSet (ensemble de faces indicées).

On y arrive en insérant entre dans les accolades du Noeud Shape le code suivant :

    geometry IndexedFaceSet {
    }

Après insertion de ce code, VrmlPad ne devrait signaler aucune erreur. Le code ressemble à ceci :

    Shape {
        geometry IndexedFaceSet {
        }
    }

Notez comment j'ai décalé d'une tabulation à droite le champ geometry du Noeud Shape. En disposant les choses de cette façon, on voit mieux quel Noeud et quel champ appartient à quel Noeud. Il existe un dispositif de VrmlPad très utile pour que nos lignes de code soient automatiquement indentées de cette façon :
Pour l'utiliser :

     Tapez CTRL+A pour souligner tout le texte.
     Tapez ensuite CTRL+SHIFT+F ou cliquez "Edit" puis "Sélection" et choisissez "Format"

pour construire un polygone il nous faut deux listes de nombres, les coords (coordonnées) et les index (indices).

Occupons nous d'abord des coords (une coord peut aussi être appelée coordonnée, ou coin, ou angle, point ou sommet) qui indiquent l'emplacement des sommets du polygone.

Nous installons nos coords dans une instance du membre de l'IndexedFaceSet appelé coord.
Pour que ces coordonnées correspondent au type du Noeud Coordinate nous insérons dans les accolades du l'IndexedFaceSet le code suivant :

    coord Coordinate {
    }

     de telle sorte que notre code (toujours exempt d'erreurs) ressemble maintenant à :

    Shape {
        geometry IndexedFaceSet {
            coord Coordinate {
            }
        }
    }

Les données en Vrml sont toujours placées dans des champs, jamais directement dans un Noeud. Un Noeud Coordinate contient donc un champ appelé "point" (point). Le "point" est un champ de type MFVec3f et c'est là que nous installons notre liste de coords (coordonnées). Nous allons donc ajouter ce champ et ses crochets, avant d'y écrire notre liste.
Pour cela, nous insérons dans les accolades de Coordinate (coordonnées) le code suivant :

    point [
    ]

Entre les crochets du champ nous insérons maintenant les données x, y et z qui vont définir la position de chacun des points (sommets) de notre polygone.

     x définit en mètres, de droite à gauche, la position du sommet.
     y définit en mètres la hauteur de la position du sommet.
     z définit en mètres de combien le sommet se dirige vers l'écran.

La liste des points dans un IndexedFaceSet commence TOUJOURS par 0, puis 1, puis 2 et ainsi de suite.

Ceci ne veut pas dire que le point 0 sera toujours à cette place dans un polygone, ni qu'il est toujours en bas à gauche du polygone, il peut aussi bien être n'importe où. On l'appelle "point 0" parce qu'il est le premier de la liste des points.

il se trouve que dans cet exemple le point 0 est à gauche de la base, parce que ses données sont :

    -1 0 0

Le point 0 se situe à gauche de la base de notre triangle parce que le premier nombre, ou composante x de ce sommet, est -1 (un mètre à gauche) et que le deuxième nombre, la composante y, est 0 en hauteur (notre base est à zéro mètre de haut). Ici toutes nos composantes z (distances à l'écran ou au delà de l'écran) seront égales à 0, notre polygone est à plat contre le plan z.

pour que le point un spécifie la position du sommet droit de la base, nous reproduisons les mêmes composantes, sauf la composante x qui devient positive :

    1 0 0

pour que le point 2, notre troisième coordonnée, soit en haut et au milieu, la composante x est fixée à 0, milieu de la base, la composante y est fixée à 1 mètre de haut, comme suit :

    0 1 0

Note du traducteur : nous avons trois points numérotés de 0 à 2, il en est toujours ainsi en informatique. S'il y a "n" objets, on les numérote toujours de 0 à n-1, c'est une convention très pratique en mathématiques.
Après ajout des coords, notre code pour le Noeud Coordinate ressemble à ceci :

    coord Coordinate {
        point [
            -1 0 0
             1 0 0
             0 1 0
        ]
    }

Si les coords (les coordonnées des sommets)sont seules à être fixées, il n'y a toujours rien à voir.
Ce qu'il faut maintenant c'est joindre ces sommets pour en former un polygone. Reste donc à spécifier dans quel ordre il faut les joindre. La façon de joindre les somments est indiquée dans le champ coordIndex du Noeud IndexedFaceSet. Cela se fait toujours en tournant dans le sens inverse des aiguilles d'une montre (le sens trigonométrique, pour qui sait ce que c'est). Sinon, on ne le verra pas (voir plus loin pourquoi).
Nous notons dans le champ coordIndex les numéros des sommets 0 1 2, ce qui veut dire qu'il faut les lier dans cet ordre. Vous pouvez appeler ces numéros les "indices" ou encore les "références", toujours dans l'ordre 0 1 2, c'est ça qui compte.

Si nous avions choisi l'ordre 2 1 0, nous aurions toujours un triangle légal. Sauf que pour le voir, il aurait fallu le faire tourner d'un demi tour (ce qui revient à remettre les sommets dans l'ordre 0 1 2). Pourquoi? Vous pouvez imaginer que le triangle n'est "peint" que d'un seul côté...

Nous pouvons toujours écrire le nom du champ coordIndex et ses crochets avant d'y préciser les données voulues (entre ces mêmes crochets).
Pour ce faire, insérez le code suivant entre les accolades du Noeud IndexedFaceSet :

    coordIndex [
    ]

A ce moment là, aucune erreur n'est décelable par VrmlPad, en bas de la fenêtre. Par ailleurs, peu importe que coordIndex soit placé avant ou après coord, dès le moment que ces deux champs, tout deux membres du Noeud IndexedFaceSet, y sont. La syntaxe reste correcte.
En revanche, il ne faut surtout pas mettre le code d'un champ dans les crochets d'un autre champ d'un Noeud. Un champ est membre d'un Noeud, non un membre d'un autre membre. Insérer avant ou après oui, entrecroiser non et souvenez-vous que ce sont les accolades qui indiquent quel champ est membre de quel Noeud.

Après ajout de notre coordIndex notre IndexFaceSet doit ressembler à ceci :

    geometry IndexedFaceSet {
        coord Coordinate {
            point [
                -1 0 0
                 1 0 0
                 0 1 0
            ]
        }
        coordIndex [
        ]
    }

ou à celà :

    geometry IndexedFaceSet {
        coordIndex [
        ]
        coord Coordinate {
            point [
                -1 0 0
                 1 0 0
                 0 1 0
            ]
        }
    }

Une chose importante à noter dès maintenant c'est que notre champ coordIndex vient une tabulation plus loin que la colonne où se trouve notre Noeud IndexedFaceSet et son accolade de fermeture. Souvenez-vous que pour s'en assurer on tape CTRL+A puis CTRL+SHIFT+F dans VrmlPad et tout s'aligne en fonction de l'emplacement des accolades.
Si, quand c'est fait, le code n'est pas bien aligné, c'est qu'on s'est trompé quelque part en plaçant ces accolades et crochets.
Maintenant que notre champ est en place et ses crochets aussi, on peut y écrire les données de sorte que notre coordIndex ressemble à ceci :

    coordIndex [
        0 1 2
    ]

Taper CTRL+S dans vrmlPad pour sauvegarder puis rafraîchir et nous devrions voir maintenant notre triangle :)


Vous pouvez vérifier le code de notre fichier face à mon exemple de travail soit en regardant le code, soit en regardant le résultat :
       ( voir le code crudeTriangle.wrl        voir l'exemple crudeTriangle.wrl )

Examiner le triangle des deux côtés

Pour examiner notre triangle selon différents angles il faut faire passer Blaxxun contact en mode examine.
Pour cela s:

     Cliquez dans la scène 3d puis tapez CTRL+SHIFT+E
     ou bien clic droit dans la scène 3d puis choisir examine

Cliquez-tirez le pointeur de la souris dans la scène 3d pour faire tourner le triangle. Remarquez que vous ne pouvez pas voir l'autre côté du triangle, comme je l'ai déjà expliqué. C'est fait exprès. Le travail de l'ordinateur est accéléré s'il n'a qu'un seul côté à "peindre". Tous les triangles seront toujours dessinés en sens inverse des aiguilles d'une montre et nous ne verrons jamais les triangles bâtis dans un autre ordre.
Toutefois, si nous tenons à ce qu'un triangle soit vu des deux côtés, il suffit de faire passer le champ solid (objet plein) à FALSE (faux).
Une fois de plus, peu importe que ce membre solid soit installé au dessus ou au dessous des autres membres (coord et coordIndex) du Noeud, la syntaxe restera correcte. Il suffit de s'assurer qu'il est placé entre les bonnes accolades du Noeud IndexedFaceSet. Et souvenez-vous que ce sont les accolades qui disent quel membre appartient à quel Noeud.
Ajoutez cette ligne de code :

    solid FALSE

Sauvegardez et rafraîchissez pour constater que le triangle peut être vu des deux côtés.

On ajoute une ombre au triangle

Notre triangle est dépourvu d'ombres. Sa brillance est constante, quel que soit l'angle sous lequel on le regarde.

S'il fallait fabriquer un objet avec des triangles comme ça, il aurait une allure plutôt plate. Pour lui donner une ombre, il faut ajouter dans le Noeud Shape (forme) un champ appearance (apparence) contenant un Noeud Appearance (Apparence), puis dans le Noeud Appearance un champ material (matière) contenant lui-même un Noeud Material (Matière).

En résumé les lignes de code suivantes ...

    appearance Appearance {
        material Material {
        }
    }

ajoutées au Noeud Shape apportent une ombre à notre triangle.

Notez aussi que plus on le tourne, plus il est sombre.

Il est possible de donner à ce triangle n'importe quelle couleur en ajoutant au Noeud Material un champ diffuseColor (couleur diffusée) portant la couleur voulue. Le champ diffuseColor est du type SFColor (Simple champ, Couleur). Un champ de type SFColor comporte trois composantes r, g, et b.

    r  pour red (rouge)
    g pour green (vert)
    b pour blue (bleu)

Les composantes peuvent prendre toute valeur décimale de 0 à 1.

0 pour rien du tout, 1 pour la totale.

de cette façon, ajuster notre champ diffuseColor à 1 0 0 donne un triangle rouge.

Pour cela on ajoute dans les accolades du Noeud Material :

    diffuseColor 1 0 0

on sauvegarde et on rafraîchit pour voir le triangle rouge    :)

Notre fichier devrait ressembler à mon fichier de travail "triangle.wrl" :

       ( voir le code triangle.wrl        voir l'exemple triangle.wrl )

On obtient un rouge sombre en diminuant la composante rouge, par exemple :

    .7 0 0 devient rouge plus sombre
    .4 0 0 est rouge plus sombre encore.

Si on continue à diminuer la composante rouge on arrive à 0 0 0 qui est noir, puisque toutes les valeurs sont nulles.

Pour avoir un rouge plus clair, on augmente les 2 autres composantes à égalité,

par exemple :

    1 .4 .4 qui donne un rose et
    1 .8 .8 qui devient d'un rose plus léger encore.

Si on poursuit l'augmentation des 2 autres composantes on arrive à 1 1 1 qui représente un blanc, puisque les 3 valeurs sont bloquées au maximum.

Ci-dessous quelques combinaisons de couleurs, mais vous pourrez essayer vous-même de trouver à votre goût vos propres combinaisons :

    0  1  0  #vert
    0  0  1  #bleu
    1  1  0  #jaune
    1  1 .7  #jaune clair
    .3 .3 0  #jaune foncé
    .5 0 .5  #pourpre
    0  1  1  #cyan
    .5 .5 .5 #gris

Nous pouvons être heureux de notre compétence, ce triangle est déjà assez sophistiqué pour qu'on puisse le porter fièrement comme un avatar de plein droit.

On ajoute un autre triangle à notre IndexedFaceSet pour en faire un losange

Pour ajouter un triangle nul besoin de définir 3 coords car une seule suffit. Ceci parce qu'on utilise les indices pour savoir quels sommets joindre ensemble. Les indices peuvent désigner, pour tracer un polygone, un sommet pris n'importe où dans la liste coord. C'est tout l'avantage de disposer de deux listes, une de coords (coordonnées) et une de index (indices) au lieu d'une seule qui préciserait les coordonnées successives. Avec deux listes, on peut s'arranger pour que des polygones partagent les mêmes sommets dans un Noeud IndexedFaceSet. Typiquement un point dans un IndexedFaceSet est utilisé dans deux triangles. Il en résulte que l'ordinateur aura moins de sommets (coordonnées) à prendre en compte.

Pour installer le sommet supplémentaire à l'opposé du sommet supérieur nous fixons une coordonnée y identique au signe près -1.

de sorte que nos coordonnées sont maintenant :

    -1  0  0  #point 0 = gauche
     1  0 
0  #point 1 = droit
     0  1  0  #point 2 = haut
     0 -1  0  #point 3 = bas

Reste à joindre trois sommets indicés pour fabriquer un triangle tout neuf. Pour que Blaxxun contact sache où finit un triangle et où commence un autre, il est IMPERATIF d'ajouter -1 après le dernier indice du dernier polygone avant d'ajouter l'ensemble des indices du polygone suivant. (Les indices commencent avec 0, puis suivent les entiers positifs 1, 2, 3, ..., de sorte que la marque "-1" ne peut pas être interprétée comme un indice, c'est donc la "marque de fin"). Notre champ indexCoord ressemble alors à ceci :

    coordIndex [
        0 1 2 -1
        1 0 3
    ]

Bien sûr, au lieu de tracer deux triangles pour faire un polygone en losange nous pourrions tracer un polygone à quatre sommets en changeant les valeurs des indices en 0 3 1 2 et notre coordIndex, si nous le faisions, deviendrait :

    coordIndex [
        0 3 1 2
    ]

Mais je veux continuer à diviser moi-même mon polygone en triangles, parce que sans cela nous serions bloqués quand nous voudrions changer (par exemple) les composantes z si elles ne doivent pas rester toutes identiques. De plus on ne gagne rien en temps d'éxécution d'un polygone à quatre côtés, puisque l'ordinateur va de toute façon découper tout polygone de plus de trois arêtes en triangles avant de le représenter.

Pour montrer un carré au lieu d'un losange on écrit :

On peut très bien laisser les coordIndex comme ils sont en listant les coords ainsi :

    -1 -1  0  #angle inférieur gauche
     1  1  0  #angle supérieur droit
    -1  1  0  #angle supérieur gauche
     1 -1  0  #angle inférieur droit

Essayez de modifier les composantes x, y et z des coordonnées pour obtenir des formes différentes. Jouer avec les triangles est l'exercice le plus utile et de loin pour qui veut se sentir à l'aise, comme chez soi, avec eux.

Si nous fixons nos coords comme ça :

    -1  0  0  #point 0 = gauche
     1  0  0  #point 1 = droite
     0  1 -1  #point 2 = haut
     0 -1 -1  #point 3 = bas

alors les sommets du haut et du bas de notre losange, du fait des valeurs de z maintenant différentes de celles des sommets moyens droit et gauche, lui donnent un aspect plié. Notez que cette pliure (crease) apparaît aiguë entre les deux triangles lorsque nous les faisons tourner en mode "examine".

Bien souvent, quand on construit un objet, on préfère lui donner un aspect lisse. On pourrait le faire en dessinant plein de petits triangles ce qui serait catastrophique en temps d'exécution 3d.
Un moyen bien plus efficace d'y parvenir consiste à utiliser le moins possible de triangles et à leur donner un aspect lisse en les ombrant de façon de façon à ce qu'ils semblent arrondis et non pas plats comme ils le sont en réalité. En Vrml cela se fait facilement en fixant la valeur du champ creaseAngle du Noeud IndexedFaceSet à un niveau supérieur à celui de l'angle que nous voulons lisser.
On y parvient en écrivant cette ligne de code dans les accolades de l'IndexedFaceSet :

    creaseAngle 3.14

l'angle de notre pli est maintenant fixé à 1 PI radians (180 degrés)

Couleur Par Sommets
Jusqu'à maintenant, on a appris à donner à n'importe quelle forme la couleur que nous voulions. Mais que faire pour donner à un objet différentes couleurs selon les endroits? On l'obtient facilement avec la procédure color per vertex (couleur par sommets) du Vrml.
Toutefois, si on utilise Blaxxun contact 4.4 et antérieurs avec Direct X on risque de ne voir tristement aucune ombre avec cette couleur par sommets. Il suffit pour s'en persuader de regarder la capture d'écran ci-dessous :


En revanche Blaxxun contact 5 et plus donne un résultat bien plus satisfaisant :


Ceci dit, pour que tous les sommets soient judicieusement colorés la procédure consiste à ajouter au Noeud IndexedFaceSet un nouveau membre, le champ color contenant le Noeud Color.

Pour celà, on insère dans les accolades du Noeud IndexedFaceSet la ligne de code ci-dessous :

   color Color {
   }

Pas d'inquiétude... dans ce Noeud Color il faut à nouveau introduire un champ color car les données doivent toujours être dans un champ, jamais directement dans un Noeud. Cela fait trois colors de suite, eh bien c'est comme ça! Ici on a donc champ, Noeud, champ.
Ce champ color a un type, déjà aperçu tout au début de ce didacticiel, un type MFColor, c'est à dire Multiple et couleur. Ces champs MFColor contiennent une liste de valeurs SFColor (r g b). Puisqu'il y a quatre sommets dans notre objet il va falloir ajouter 4 valeurs r g b.
Encore que l'on puisse avoir une liste de couleurs indicées dans un index séparé pour les coordonnées qui utiliserait un colorIndex à la place du coordIndex, nous préférons ici partager le même index des coordonnées. Pourquoi? Utiliser le coordIndex à la fois pour les coordonnées et pour la liste des couleurs veut dire que le sommet 0 prendra la couleur 0, le sommet 1 la couleur 1, le sommet 2 la couleur 2 et ainsi de suite.

Pour que le sommet gauche de notre objet soit rouge, que le sommet droit soit jaune, le sommet haut bleu et le sommet inférieur vert, on inscrit dans les crochets du champ color le code suivant, en faisant attention de ne pas confondre les champs, c'est bien le champ color du Noeud Color (et non pas le premier) qui doit être utilisé :

    color [
        1 0 0 #color 0 r g b valeurs = rouge
        1 1 0 #color 1 r g b valeurs = jaune
        0 0 1 #color 2 r g b valeurs = bleu
        0 1 0 #color 3 r g b valeurs = vert
    ]

On sauvegarde et on rafraîchit, de sorte qu'on aperçoit maintenant nos adorables triangles bien colorés :)

Inutile de conserver le champ diffuseColor ni ses valeurs car il a été de toute façon annulé par le Noeud Color et nous pouvons aussi bien effacer la ligne qui lui correspond. Notre fichier devrait déjà ressembler à mon fichier de travail :

       ( voir le code colorPerVertex.wrl        voir l'exemple colorPerVertex.wrl )

Naturellement, on pourrait obtenir des objets plus complexes en ajoutant triangle sur triangle, toutefois, plutôt que d'écrire le code à la main pour les générer, définissant sommet après sommet, un à la fois, ajoutant des indexCoords l'un après l'autre avec un simple éditeur de texte, ce qui pourrait sembler à la longue une tâche bien fastidieuse, mieux vaut faire appel à une interface graphique. Seamless3D représente justement une telle solution qui peut produire autant de triangles en color per vertex que l'on veut, et avec facilité.

Translation d'un Triangle 2 mètres à droite
Pour déplacer un objet ou pour le faire tourner, il faut l'installer dans un Noeud Transform (Transformation).
La translation déplace un objet là où il le faut simplement en fixant des coordonnées.
Comm on est en 3d, nous avons besoin de trois nombres x, y et z pour les représenter.
x = quelle distance en mètres à droite ou à gauche;
y = quelle distance en mètres en haut ou en bas;
z = quelle distance en mètres vers l'écran.

donc, pour déplacer notre triangle de 2 mètres à droite nos coordonnées   x y et z seront   2 0 0
et le code pour le dire s'écrira

translation 2 0 0

Le champ translation appartient à un Noeud Transform on ajoutera donc d'abord la ligne

   Transform {

et puisque notre objet (le Noeud Shape) (c'est le triangle) est un descendant de notre Noeud Transform nous ajoutons le champ children devant le Noeud Shape.
De plus, nous avons ajouté une accolade avec le Noeud Transform, il faut donc la refermer à la fin.

       ( voir le code translate2MetersToTheRight.wrl
       voir l'exemple translate2MetersToTheRight.wrl )

ou encore, ci-dessous un résumé en français :


Transform {
	translation	2 0 0
	children Shape {
		appearance Appearance {
			material Material {
				diffuseColor 1 0 0	
			}
		}
		geometry IndexedFaceSet {
			solid FALSE
			coord Coordinate {
				point [
					-1  0  0  #point 0  
					 1  0  0  #point 1 
					 0  1  0  #point 2  
				]
			}
			coordIndex [
				0 1 2	#joindre les 3 points en sens anti-horaire
			]
		}
	}
}

Rotation de 1/8 de tour selon l'axe y
Nous allons utiliser notre Noeud Transform pour des expériences de rotation.

En Vrml un champ rotation contient 4 valeurs.

   x, y, z et angle

Les x, y et z sont les axes pris en compte et l'angle mesure la rotation autour de l'axe spécifié.

Bien que les valeurs de x, y et z puissent prendre toute valeur utile (de 0 à 1) la plupart du temps on se contente d'un seul axe pour faire tourner un objet, ce qui donne :

1 0 0 = une rotation autour de l'axe x
0 1 0 = une rotation autour de l'axe y
0 0 1 = une rotation autour de l'axe z

On fixe l'angle à la valeur voulue, compte tenu de ce qu'il est mesuré en radians. Un tour complet mesure 2 Pi radians, soit 6.283185307 donc, pour une rotation de 1/8 de tour, nous allons fixer notre angle à la valeur de 6.283185307 / 8 = 0.785398163

rotateOnTheYAxisOne8th.wrl est le même que translate2MetersToTheRight.wrl sauf que la ligne

rotation 0 0 1   0.785398163

remplace dans le Noeud Transform la ligne translation.

       ( voir le code rotateOnTheYAxisOne8th.wrl
       voir l'exemple rotateOnTheYAxisOne8th.wrl )

En français nous avons donc ce code :


Transform {
	rotation 0 0 1 0.785398163
	children Shape {
		appearance Appearance {
			material Material {
				diffuseColor 1 0 0	
			}
		}
		geometry IndexedFaceSet {
			solid FALSE
			coord Coordinate {
				point [
					-1  0  0  #point 0  
					 1  0  0  #point 1 
					 0  1  0  #point 2  
				]
			}
			coordIndex [
				0 1 2	#joindre les 3 points en sens anti-horaire
			]
		}
	}
}
Pour approfondir cette question des rotations, voir mon didacticiel Comprendre les Rotations Vrml

Animation Translation
Pour animer des objets, il faut maintenant apprendre quatre nouvelles procédures à la fois.
L'instructions DEF (définir), le Noeud TimeSensor (détecteur de temps) le Noeud Interpolator (interpolation) et les ROUTE (routages).
L'instruction DEF permet de donner un nom à un noeud de façon à pouvoir le réutiliser sans le re-écrire, simplement en le nommant.
Les ROUTEs spécifient d'où viennent les données et où elles vont.
Les Noeuds TimeSensor jouent un rôle essentiel dans la mesure du temps mis en oeuvre dans une animation.
Les Noeuds Interpolator calculent toutes les positions intermédiaires nécessaire à la fluidité d'un mouvement, entre les deux positions, initiale et finale, définies par le programmeur.
Les commentaires après dièse sont utiles pour la formation du débutant, mais le visionneur Blaxxun n'en tient pas compte du tout, on peut aussi bien les supprimer.

       ( voir le code tranAnimation.wrl
       voir l'exemple tranAnimation.wrl )

Le code traduit en français devient le suivant :

DEF myTimer TimeSensor{ 
    cycleInterval 4     # un cycle = 4 secondes 
    loop TRUE           # le cycle est répété en boucle 
} 
DEF myPosInt PositionInterpolator { 
	key [0 .25 .5 .75 1]  # le cycle comporte 4 phases de durée égale 
	keyValue [ 
        -3 0 0      # la phase 0 commence 3 mètres à gauche 
         0 0 0      # la phase 1 commence là où la phase 0 finit
                    #            c'est à dire au centre 
         3 0 0      # la phase 2 commence là où la phase 1 finit
                    #            soit 3 mètres à droite 
         0 0 0      # la phase 3 commence là où la phase 2 finit
                    #            de nouveau au centre
        -3 0 0      # la phase 3 finit au point de départ
                    #            le cycle peut recommencer 
    ] 
} 

DEF myNode Transform {
    children Shape {
        appearance Appearance {
            material Material {         # colore le triangle en rouge
            
                diffuseColor 1 0 0      # valeurs r v b = rouge au max
                                        #                 vert et bleu nuls
           }
         }
        geometry IndexedFaceSet {
             solid FALSE
             coord Coordinate {
                point [       # les 3 positions pour les trois sommets
                    -1  0  0  # point 0 x,y,z base à gauche
                     1  0  0  # point 1 x,y,z base à droite
                     0  1  0  # point 2 x,y,z en haut
				]
			}
            coordIndex [      # joindre les 3 points en sens anti-horaire
				0 1 2    
			]
		}
	}
}
# Envoyer les quatre fractions du cycle d'horloge interne à l'interpolateur 
# qui préparera les positions intermédiaires 
ROUTE myTimer.fraction_changed TO myPosInt.set_fraction 

# envoyer ces positions au Noeud Transform qui dessine l'objet
# dans ses positions successives
ROUTE myPosInt.value_changed TO myNode.translation 


Animation Rotation
L'ordre des transformations d'un objet est rigide. Il faut savoir que le visionneur interprète toujours la rotation avant la translation.

       ( voir le code rotAnimation.wrl
       voir l'exemple rotAnimation.wrl )

Voici le code français correspondant :
DEF myTimer TimeSensor{ 
	cycleInterval 4    # un cycle = 4 secondes 
	loop TRUE          # le cycle est en boucle 
} 
DEF myRotInt OrientationInterpolator { 
	key [0 .25 .5 .75 1]    # le cycle est découpé en 4 phases de durée égale 
	keyValue [ 
        0 1 0 0             # phase 0 commence à 0 revolution autour de y 
        0 1 0 1.570796327   # phase 1 commence où phase 0 finit, quart de tour 
        0 1 0 3.141592654   # phase 2 commence où phase 1 finit, demi tour 
        0 1 0 4.71238898    # phase 3 commence où phase 2 finit, 3 quarts de tour 
        0 1 0 6.283185307   # phase 3 finit après un tour complet, on recommence
	] 
} 

DEF myNode Transform {
	children 
	Shape {
		appearance Appearance {
			material Material { #Colorer le triangle en rouge
				diffuseColor 1 0 0  # r v b = plein rouge, vert et bleu nuls
			}
		}
		geometry IndexedFaceSet {
			solid FALSE
			coord Coordinate {
				point [         # les 3 positions des 3 sommets
					-1  0  0  # point 0 x,y,z = base gauche
					 1  0  0  # point 1 x,y,z = base droite
					 0  1  0  # point 2 x,y,z = haut
				]
			}
			coordIndex [        # joindre lese 3 points en sens anti-horaire
				0 1 2	
			]
		}
	}
}
# Envoyer les quatre fractions du cycle d'horloge interne à l'interpolateur  
ROUTE myTimer.fraction_changed TO myRotInt.set_fraction 

# envoyer ces positions au Noeud Transform qui dessine l'objet
# dans ses positions successives
ROUTE myRotInt.value_changed TO myNode.rotation
Si l'on veut expressément le contraire, pour obtenir un mouvement orbital par exemple, il est nécessaire d'ajouter un autre Noeud Transform.

       ( voir le code orbit.wrl
       voir l'exemple orbit.wrl )

dont le code des commentaires en français est le suivant :
DEF myTimer TimeSensor{ 
    cycleInterval 4    # une cycle = 4 secondes 
    loop TRUE          # le cycle est en boucle 
} 
DEF myRotInt OrientationInterpolator { 
    key [0 .25 .5 .75 1]    # break the cycle down to 4 phases of time all of equal length 
    keyValue [ 
        0 1 0 0             # phase 0 commence à 0 revolution autour de y 
        0 1 0 1.570796327   # phase 1 commence où phase 0 finit, quart de tour 
        0 1 0 3.141592654   # phase 2 commence où phase 1 finit, demi tour 
        0 1 0 4.71238898    # phase 3 commence où phase 2 finit, 3 quarts de tour 
        0 1 0 6.283185307   # phase 3 finit après un tour complet, on recommence
	] 
} 

DEF myNode Transform {
	children 
	Shape {
		appearance Appearance {
			material Material {     # Colore le triangle en rouge
				
				diffuseColor 1 0 0	# r v b = plein rouge, vert et bleu nuls
			}
		}
		geometry IndexedFaceSet {
			solid FALSE
			coord Coordinate {
				point [
				point [       # les 3 positions des 3 sommets
					-1  0  0  # point 0 x,y,z = base gauche
					 1  0  0  # point 1 x,y,z = base droite
					 0  1  0  # point 2 x,y,z = haut
				]
			}
			coordIndex [
				0 1 2	# joindre lese 3 points en sens anti-horaire
			]
		}
	}
}
# Envoyer les quatre fractions du cycle d'horloge interne à l'interpolateur 
ROUTE myTimer.fraction_changed TO myRotInt.set_fraction 

# envoyer ces positions au Noeud Transform qui dessine l'objet
# dans ses positions successives 
ROUTE myRotInt.value_changed TO myNode.rotation
Il est vraiment très important pour vous de jouer avec ces trois derniers exemples, afin de vous imprégner du sens, du mécanisme et de l'importance des instructions DEF, des TimeSensor, des Interpolator et des ROUTE.

Ceci complète notre survol des procédures d'animation d'un triangle.

Note du traducteur : A partir d'ici, Thyme cesse de prodiguer des explications aussi détaillées, point à point. C'est que dès maintenant vous avez compris "comment ça marche". Par la suite, de nombreux exemples doivent suffire pour progresser, à supposer, c'est bien entendu, que vous les examiniez dans le détail. On ne survole pas, on suit le code, "le crayon à la main". Bon travail. Fin de note.



Le Triangle
Il y a tant de choses que l'on peut faire avec des triangles, plus qu'avec des formes toutes faites comme les sphères, les cylindres ou les cubes. Avec des triangles, et à condition d'en utiliser assez, on peut fabriquer un objet de n'importe quelle forme. De toute façon tous les objets générés en 3d par des ordinateurs sont découpés en triangles. Les ordinateurs les manipulent facilement. De sorte qu'il n'est pas inutile de partir directement des triangles, du point de vue de l'ordinateur. Il en résulte que vous avez intérêt à comprendrer deux ou trois choses à propos de triangles.

Qu'est-ce qui est unique avec le triangle?
C'est le polygone qui présente le moins de sommets. Certains affirment avoir rencontré un polygone à deux sommets, mais je demande à voir ! :)
Une des choses qui concernent le fait de n'avoir que trois sommets c'est que, quelque soit la position qu'on leur donne en 3d, et pour autant que deux sommets ne sont pas confondus au même point, on a un polygone "légal". Entendons, sa surface est toujours plane. En revanchue, dès que l'on veut s'assurer qu'un polygone quelconque est plan, il faut entrer dans quelques complicanions mathématiques pour prouver que tous les sommets sont bien là où il faut. Sinon... ce n'est PAS un polygone. Aucun problème du genre avec un triangle. Vous pouvez construire un rectangle ou un hexagone avec des triangles, le contraire n'est pas possible. Les triangles ne sont pas seulement beaux, leur vraie vertu est d'être à la fois extrêmement polyvalents et très simples. Maîtrisez le triangle : vous en savez déjà long sur le graphisme 3d et avec un rien d'imagination vous pouvez faire de grandes choses avec lui !

Toujours du côté simple
Si vous croyez que la plupart de mes exemples sont ennuyeux je ne pense pas devoir m'en excuser. Ce qui me frustre le plus quand je tente d'apprendre quelque chose dans un exemple, c'est de ne pas arriver à trouver ce que je cherche au milieu d'une mer de code fantaisiste. J'ai fini par comprendre que bien des concepts réputés de haut niveau ne devraient pas l'être s'ils étaient présentés avec simplicité. C'est pourquoi ma priorité des priorités consiste à maintenir mes exemples au niveau le plus simple possible, et ceci, même au prix d'une aspect ennuyeux. Si vous tenez au code fantaisie, à vous le soin. :)


Deux Triangles
Remarquez qu'en ajoutant un seul point (sommet) a l'exemple triangle.wrl nous pouvons fabriquer un second triangle (et non pas en ajoutant 3 points). C'est pourquoi nous avons deux listes séparées pour les Coordinate points (coordonnées) et pour les coordIndex (indices). Plusieurs triangles peuvent partager les mêmes coordonnées, ce qui est économe en ressources et rapide en technique de programmation.

       ( voir le code twoTriangles.wrl
       voir l'exemple twoTriangles.wrl )

On peut aussi ire les commentaires du code en français :

Shape {
	appearance Appearance {
        material Material {         #Colorer le triangle en jaune
               diffuseColor 1 1 0   # rouge vert bleu
		}
	}
	geometry IndexedFaceSet {
		coord Coordinate {
            point [     #    Les 4 coordonnées pour deux triangles. 
                        #    Deux triangles partagent les 2 premiers points.
				-1  0  0  # point 0 x,y,z	
				 1  0  0  # point 1 x,y,z
				 0  1 -1  # point 2 x,y,z
				 0 -1 -1  # point 3 x,y,z
			]
		}
		coordIndex [ # Definir les deux triangles en sens anti-horaire.
					 # Le sens horaire serait [2 1 0 -1 3 0 1] et les rendrait invisibles,
					 # car ils ne sont "peints" que d'un seul côté.
			0 1 2 -1	# -1 signifie fin d'un triangle
			1 0 3		# Le triangle suivant.
		]
	}
}

Deux triangles avec le pli adouci de creaseAngle (faux pli)
Les triangles sont exactement les mêmes que ceux de l'exemple twoTriangles.wrl sauf qu'un creaseAngle (faux pli) de 3.14 radians (1 Pi ou 180 degrés) lui a été ajouté.
Nous voulons souvent créer des objets aux formes lisses, adoucies. On pourrait le faire en dessinant plein de petits triangles ce qui serait catastrophique en temps d'exécution 3d.
Un moyen bien plus efficace d'y parvenir consiste à utiliser le moins possible de triangles et à leur donner un aspect lisse en les ombrant de façon de façon à ce qu'ils semblent arrondis et non pas plats comme ils le sont en réalité. En Vrml cela se fait facilement en fixant la valeur du champ creaseAngle du Noeud IndexedFaceSet à un niveau supérieur à celui de l'angle que nous voulons lisser.
On y parvient en écrivant cette ligne de code dans les accolades de l'IndexedFaceSet. Quand l'ordinateur génère une ombre, il le fait d'après les normales, c'est à dire les droites à angle droit par rapport à la surface du triangle à ombrer. C'est en calculant des moyennes entre les normales de deux triangles contigus qu'il calcule ses ombres. creaseAngle est simple à utiliser et habituellement le résultat obtenu est convenable.

       ( voir le code twoSmoothTrianglesUsingCreaseAngle.wrl
       voir l'exemple twoSmoothTrianglesUsingCreaseAngle.wrl )

Parfois vous aurez envie de faire mieux et donc, au lieu de laisser à l'ordinateur le soin de calculer les normales, vous les fixerez vous-même dans le code Vrml.

Deux Triangles avec leur Normale
Ce sont encore une fois les mêmes triangles que dans l'exemple twoTriangles.wrl en ce qu'ils ont les mêmes coordonnées et le même angle entre les deux. De même, ils sont toujours aussi plats, mais ils semblent courbes. Cette courbure est une illusion d'optique...

Note du traducteur (hilare) : Mais ... TOUT ce qu'on fait en 3d est une illusion d'optique! Un jour, un jour où nous serions au coin du feu, grignotant des noix et jetant les coquilles dans le feu, oh les jolies flammes, je vous dirai (en confidence) que le monde, le real life, le vrai monde qui nous entoure, est aussi une illusion d'optique, c'est un jeu video généré par les couches optiques de notre cerveau, mais il faudrait se lancer dans une tartine neurobiologique et nous n'avons pas le temps. Cependant, c'est bien en fournissant à ces mêmes couches optiques, grâce au Vrml, les informations qu'elles attendent que nous pouvons créer des "mondes" qui nous semblent justement si réalistes. Je bavarde je bavarde ... il faut rendre la parole à Thyme. Fin de note.
... cette courbure est une illusion d'optique provoquée par les normales que nous avons ajoutées. Ces normales nous permettent de manipuler les ombres dans les triangles constitutifs des objets pour les lisser avec un nombre très réduit de triangles.

       ( voir le code twoTrianglesWithNormals.wrl
       voir l'exemple twoTrianglesWithNormals.wrl )

Si vous tenez à une expérience visuelle interactive de ce que sont ces normales, examinez donc ma Demo des Normales :

Les 4 gros bâtons rouges qui sortent des sommets des triangles représentent les normales. Je n'ai ajouté les petits bâtons verts et bleus que pour donner une meilleure idée visuelle de leur orientation. Les trois curseurs ne contrôlent que le bâton rouge du sommet gauche. Cliquez-tirez le curseur rouge pour orienter la normale à votre gré. A la première tentative une fenêtre devrait surgir et indiquer les données de la normale. Si vous y apercevez d'étranges nombres comportant un "e" vous pouvez considérer qu'ils sont égaux à zéro (il s'agit d'une notation mathématique exponentielle usuelle). Les trois nombres x, y et z représentent la position relative de la normale à son pied. Ces nombres sont toujours dans l'intervalle de 0 à 1 (les normales sont des vecteurs unité).
Le premier curseur tourne la normale autour de son axe x, le second autour de l'axe y et le troisième autour de l'axe z. Pour re-initialiser un curseur, cliquez son axe bleu.
Constatez que c'est alors que la normale est dirigée vers l'écran que le triangle est le plus brillant. Ceci, parce que la lumière définie par le Vrml est dirigée vers l'écran (rayons parallèles à l'axe z). Plus on en écarte la normale, plus le triangle est assombri. Ceci admis, tentez autre chose. Puisqu'on est en mode examine, ne touchez plus aux curseurs, mais faites donc tourner toute la scène pour ramener la normale en direction de l'écran: surprise, la partie du triangle qui entoure le pied de la normale retrouve sa brillance. C'est bien parce que c'est l'orientation des normales et non celle des triangles qui dicte la façon dont l'ordinateur doit répartir la brillance et les ombres. Les normales ne contrôlent pas la lumière, elles contrôlent la façon dont la lumière doit se comporter à leur pied.
La transition progressive des ombres d'une normale à la suivante est liée à une interpolation (exécutée par l'ordinateur). C'est de là que vient l'aspect de douce courbure de nos triangles alors qu'ils restent tout à fait plats.

Deux triangles texturés comportant des normales
texture.png (téléchargez cette image si vous voulez la conserver sur votre disque dur

Dans cet exemple nous avons ajouté une texture. Tout ce qui est en plus ce sont les coordonnées et une référence url à un fichier point png (un fichier point jpg aurait convenu aussi bien).
Si vous arrivez à comprendre tout ce que signifient les nombres qui figurent dans ce fichier vous pourrez vos vanter d'en connaître long sur les triangles et sur la façon de les utiliser.

Vous pouvez examiner le code et voir l'exemple ici :
       ( voir le code twoTexturedTrianglesWithNormals.wrl
       voir l'exemple twoTexturedTrianglesWithNormals.wrl )

Generation de deux Triangles en vrmlscript
       ( voir le code : AnimatedRotatingTriangles.wrl
       voir l'exemple : AnimatedRotatingTriangles.wrl )

Cet exemple est encore le même que celui des deux triangles twoTexturedTrianglesWithNormals.wrl sauf que toutes les coordonnées des triangles, les normales et les coordonnées des textures sont générées en Vrmlscript.

Generating d'un sol avec le vrmlscript
       ( voir le code : genLand.wrl
       voir l'exemple : genLand.wrl )
land.png (téléchargez cette image si vous voulez la conserver sur votre disque dur)

Cet exemple montre toute la puissance du Vrmlscript dans la génération des triangles, face au code rédigé à la main. Le fichier équivalent en Vrml pur pèserait encore 55k une fois g-zippé. La version Vrmlscript ne pèse que 2k dans les mêmes conditions, c'est une compression significative. Le fait est que le Vrmlscript peut générer autant de triangles qu'il vous est nécessaire.
Il y a mieux, il ne vous est pas du tout nécessaire de comprendre "comment ça marche". Tout ce que vous avez à comprendre c'est ce que portent les 4 paramètres que vous passez lorsque vous utilisez mes fonctions tug (tracteur).

Jetez un coup d'oeil à ma fonction GenLand( ). C'est elle qui contient tous mes appels à la fonction tug. Pour l'expérience, enlevez tout sauf UNE fonction tug et essayez de passer différents nombres.
Les 4 paramètres tug (tracteur) sont les suivants :

    // le premier paramètre spécifie la position x du centre du tracteur.
    // le second paramètre indique à quelle hauteur tracter la colline.
    // le troisième paramètre spécifie la position z du centre du tracteur.
    // le dernier paramètre précise le rayon (la largeur de la colline).

Quand vous aurez essayé quelques exemples en ajoutant de plus en plus de fonctions tug, une à la fois, de façon à ne pas prendre trop de risques sur l'endroit où se trouve celui sur lequel vous travaillez. Si vous ne savez plus où une des fonctions tug se trouve en train de tracter, faites-la temporairement tirer à une grande hauteur pour la repérer visuellement avec facilité. Revenez ensuite à la normale.

Animation de Triangles
       ( voir le code : AnimatedRotatingTriangles.wrl
       voir l'exemple : AnimatedRotatingTriangles.wrl )

Cet exemple est le même que twoTexturedTrianglesWithNormals.wrl sauf qu'on a ajouté un code d'animation pour que les triangles tournent autour de l'axe des x.

Morphing (déformation) de 2 Triangles

       ( voir le code : morphingTwoTexturedTrianglesWithNormals.wrl
       voir l'exemple : morphingTwoTexturedTrianglesWithNormals.wrl )

       ( voir le code : morphingBTwoTexturedTrianglesWithNormals.wrl
       voir l'exemple : morphingBTwoTexturedTrianglesWithNormals.wrl )

il vous faut le plugin de Blaxxun Contact pour ce didacticiel et aussi Seamless3d, un éditeur de Vrml.

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