Support du VRML dans la biblioth`eque CImg
Transcription
Support du VRML dans la biblioth`eque CImg
Institut Supérieur d’ Informatique de Modélisation et de leurs Applications The Cool Image Library Projet ISIMA troisième année Option F4 : Calcul et Modélisation Scientifique Support du VRML dans la bibliothèque CImg Rédigé par : Tuteur de projet : G REGORY R AMI DAVID T SCHUMPERL É A NN ÉE 2010/2011 RAMI REMERCIEMENTS REMERCIEMENTS Je tiens tout simplement à remercier M. David Tschumplerlé, mon tuteur de stage et créateur de CImg, pour ses explications claires et sa très bonne présentation du projet qui m’ont permis de rentrer très rapidement dans le vif du sujet. J’ai ainsi pu avancer assez vite dans mon travail et le finir à temps. Merci également pour avoir relevé, pendant la phase de test, les points qui m’avaient échappés. Projet ISIMA 3ème année RAMI GLOSSAIRE GLOSSAIRE CImg : Librairie open source développée en C++ contenant de nombreux outils facilitant le traitement d’images. Langage C++ : Langage de programmation informatique orienté objet. Noeud : Structure VRML représentée par l’utilisation d’un mot clé et généralement délimitée par des accolades. Par exemple, le noeud ”Shape” permet de définir un objet 3D. OFF : Object File Format, type de fichier permettant de stocker la représentation d’un objet 3D en spécifiant les polygones, représentant les faces de l’objet. C’est aujourd’hui le seul format de fichier implémenté dans CImg permettant le stockage d’objets 3D. Plugin : Bout de programme qui rajoute des fonctionnalités à un programme principal. Primitives : Nom donné à la représentation d’une face (au sens d’un maillage) dans CImg. Une primitive contient la liste des sommets composant la face d’un objet. VRML : Virtual Reality Modeling Language est à la fois un format de fichier et un langage qui permet de représenter des objets 3D qui peuvent être mis en scène dans des univers 3D virtuels. Ce langage est assez simple d’utilisation étant basé sur un principe de noeud décrivant de manière logique l’univers 3D à représenter. Projet ISIMA 3ème année RAMI RÉSUMÉ / ABSTRACT RÉSUMÉ La librairie CImg est une bibliothèque open source développée en C++ contenant un grand nombre d’outils pour le traitement d’images. Cette librairie se définit par sa simplicité d’utilisation, sa portabilité mais aussi par son efficacité et son extensibilité. À ce titre, le besoin s’est fait sentir de pouvoir prendre en charge dans CImg le format VRML. Le Virtual Reality Modeling Language(VRML) est à la fois un langage de présentation et un format de fichier. Comme son nom l’indique le VRML est un langage qui permet de représenter des univers interactifs 3D virtuels. Les avantages de ce langage sont sa simplicité et ses multiples fonctionnalités en comparaison du seul autre type de fichier pris en charge dans CImg pour la 3D : le format .off. Le besoin d’avoir un support pour un tel format dans une librairie de traitement d’images est apparu comme évident. Mon travail fut par conséquent de coder deux méthodes : load vrml() et save vrml() qui permettent respectivement de charger un fichier VRML dans CImg et de sauvegarder un objet de CImg dans un fichier VRML. Le codage de ces fonctions a été assez complexe et leurs validations a été effectuées sur un ensemble de tests dans tous les cas particuliers possibles. La prise en charge de la texture également ne marque pas la fin du développement de ces deux méthodes car VRML permet aussi des animations de scènes assez poussées qui ne sont pas gérées par les fonctions développées ici. Mots-clés : librairie CImg, format VRML, load vrml(), save vrml() ABSTRACT The CImg library is an open source library developed in C++ containing a variety of tools to process images. This library is defined by its simplicity, its portability, and also by its efficiency and extensibility. Therefore, the need appears to have a support of the VRML format in CImg. The Virtual Reality Modeling Language (VRML) is all at once a presentation language and a file format. It is a language that enables users to represent virtual 3D interactive universe. The qualities of this language are its simplicity and power compared to the only other type of file supported by CImg for 3D : the .off format. The need to support this type of file in an image processing library appeared obvious. My work was consequently to develop two methods : load vrml() and save vrml() that enable respectively to load a VRML file in CImg and to save a CImg object into a VRML file. The programming of these functions was quite complicated and to be sure they met all the requirements, I ran a set of tests covering all possible cases. The support of texturized images does not mean the end of these two functions programming. In fact, VRML also enables powerful 3D scene animation that is not yet handled in CImg. Keywords : CImg library, VRML format, load vrml(), save vrml() Projet ISIMA 3ème année RAMI TABLE DES MATIÈRES TABLE DES MATIÈRES REMERCIEMENTS GLOSSAIRE RÉSUMÉ / ABSTRACT TABLE DES MATIÈRES TABLE DES FIGURES INTRODUCTION 8 1 Présentation du problème 9 1.1 Rappel du sujet . . . . . . 1.2 Connaissance du problème 1.3 Analyse du problème . . . 1.4 Étude du problème . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Déroulement de l’étude 2.1 Prise en main de CImg et VRML . . 2.1.1 La bibliothèque CImg . . . . 2.1.2 Le langage VRML . . . . . 2.2 Les algorithmes de principe . . . . . 2.3 Les choix d’implémentation . . . . 2.3.1 Implémentation de save vrml 2.3.2 Implémentation de load vrml 13 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Résultats et discussion 3.1 Résultats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Discussion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CONCLUSION RÉFÉRENCES BIBLIOGRAPHIQUES ANNEXES Projet ISIMA 3ème année 10 10 11 12 14 14 17 19 24 24 25 26 27 29 31 RAMI TABLE DES FIGURES TABLE DES FIGURES 1 2 3 4 5 6 7 Objet du projet . . . . . . . . . . . . . . . . . . . . . Repérage des composantes d’un pavé droit dans CImg Représentation d’un pavé droit dans CImg . . . . . . . Code utilisé pour les tests des méthodes . . . . . . . . Résultat de la méthode load vrml() . . . . . . . . . . . Résultat de la méthode save vrml() . . . . . . . . . . . Chronologie non exhaustive du projet . . . . . . . . . Projet ISIMA 3ème année . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 15 16 27 29 29 30 RAMI INTRODUCTION INTRODUCTION Dans le cadre de ma troisième année d’études à l’ISIMA, j’ai effectué en monôme un projet de 120 heures autour de la librairie CImg, encadré par M. David Tschumperlé. La librairie CImg est une boı̂te à outils C++ qui contient un grand nombre de méthodes qui facilitent le traitement d’images. Cette bibliothèque posséde un visualiseur d’objets 3D maillés, mais ne permet pas de lire beaucoup de formats de fichiers différents pour ce type de données 3D (seulement les formats .OFF et .CIMGZ actuellement). Il apparait alors le besoin d’être capable d’utiliser des formats de fichiers différents qui sont plus évolués et qui permettent d’exploiter plus grandement les capacités de CImg. Le choix s’est porté sur le format de fichier VRML (Virtual Markup Reality Language). Le VRML est un language et un format de fichier standardisé qui permet de représenter des objets 3D que l’on peut mettre en animation dans des univers virtuels 3D de manière assez simple. Ce format est fort utilisé et a été accepté comme norme internationale par l’organisme ISO (International Organization for Standardization). Dans ce contexte, mon travail consistait à développer les fonctions C++ permettant la sauvegarde et le chargement de données 3D maillées en format VRML. Ces fonctions load vrml() et save vrml() loin d’exploiter toutes les capacités du VRML doivent permettre d’enregistrer et charger des objets 3D du plus simple (cube,sphère,...) au plus complexe (formes 3D arbitraires). Si les deux méthodes développées sont fonctionnelles quant à l’affichage d’objets 3D, la suite du projet était d’afficher des objets 3D texturés. Il n’était pas demandé ici de développer la gestion d’animation d’objets 3D et les contraintes temporelles l’empêchaient. J’exposerai dans une première partie l’objet de l’étude. Dans un deuxième temps, je présenterai CImg et VRML puis les algorithmes de principe des deux méthodes développées et enfin les difficultés rencontrées. Pour finir, dans une troisième partie j’aborderai les résultats des tests que j’ai effectués afin de vérifier le bon fonctionnement des méthodes développées et j’analyserai les résultats de ce projet. Projet ISIMA 3ème année Page 8 RAMI PARTIE 1 PREMIÈRE PARTIE Présentation du problème Projet ISIMA 3ème année Page 9 RAMI PARTIE 1 1.1. Rappel du sujet Le sujet de mon projet était de fournir des méthodes à la librairie CImg qui lui permettent de sauvegarder ou de charger des objets maillés 3D dans le format VRML. Ce choix s’explique par la volonté d’une plus grande flexibilité dans la gestion et le choix des formats de fichiers susceptibles d’être utilisés par la librairie CImg. De plus, le language VRML est assez simple à prendre en mains et à comprendre. Ce format permet également d’animer des objets 3D dans un univers virtuel ou encore d’appliquer des textures sur les objets et tout cela toujours de manière assez simple. L’objectif de tout mon travail consistait donc à coder deux fonctions en language C++. Ces deux fonctions ont pour signature : – CImg<T>& load vrml(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors) – const CImg<T>& save vrml(const char *const filename,const CImgList<tf>& primitives, const CImgList<tc>& colors, const char *const texturefile = 0) const La première doit permettre de charger un objet ou un ensemble d’objets 3D et de les retranscrire au format interne à CImg afin de pouvoir les utiliser dans la librairie CImg. À partir d’un nom de fichier ou d’un objet de type File, on récupére la liste des primitives et des couleurs de l’objet 3D et l’on retourne la liste des points de l’objet. La seconde doit sauvegarder dans un fichier VRML un objet 3D de CImg désigné par ses primitives, ses couleurs et sa texture s’il en a une. 1.2. Connaissance du problème Actuellement la librairie CImg ne permet d’utiliser que deux types de fichiers pour la gestion d’images 3D, le format .off et .cimgz. Le format cimgz est un format propre à CImg et les fichiers OFF sont un format de fichier très simple. En effet, un fichier OFF est toujours formé de la même façon : le mot clé OFF puis à la ligne le nombre de sommets, le nombre de faces et le nombre de bords du modèle (cette valeur n’est pas utile). Ensuite, les coordonnées des sommets sont listées, ainsi que les sommets composant chaque faces. La librairie CImg contient déjà les fonctions save off() et load off() qui permettent respectivement de sauver et charger un objet dans un fichier OFF. On veut pouvoir maintenant utiliser un format très répandu et reconnu par la norme ISO : le format VRML. On veut donc pouvoir utiliser de la même façon que save off() et load off() les fonctions save vrml() et load vrml(). Projet ISIMA 3ème année Page 10 RAMI PARTIE 1 1.3. Analyse du problème Actuellement, si l’on fournit un fichier .off décrivant par exemple un cube, par la fonction load off(), on charge le fichier en question dans CImg. Le fonctionnement est le suivant : la méthode load off() est appelé par un objet de type CImg<tp> dans lequel est retourné la liste des sommets du cube. Puis la liste des sommets composant les faces est stockée dans l’objet CImgList<tf>& primitives, on aura donc ici dans ”primitives” une liste de 6 groupes de quatre points (un cube est composé de 6 faces définies par quatre sommets). On aura finalement dans l’objet CImgList<tc>& colors la liste des couleurs de chaque face, définie dans l’exemple du cube par une liste de 6 groupes de 3 valeurs (la couleur de chaque face est définit par leur niveau de rouge, vert et bleu). On voudrait pouvoir réaliser la même chose avec la méthode load vrml() pour des fichiers de type VRML. De même, si actuellement on appelle la méthode save off() à partir d’un objet de type CImg<tp> contenant une liste de points et qu’on fournit à la méthode un objet CImgList<tf>& primitives contenant les faces, un objet CImgList<tc>& colors contenant les couleurs et le nom du fichier .off dans lequel on veut sauvegarder cet objet on aura alors un fichier de type OFF contenant notre objet. On voudrait également que cela fonctionne de la même manière pour la méthode save vrml(). La difficulté et le but du projet étaient de faire le lien entre les deux types de représentation : le VRML et ses balises et CImg qui décompose l’objet en sommets, faces et couleurs. La figure 1 représente la problèmatique : F IG . 1 – Objet du projet Projet ISIMA 3ème année Page 11 RAMI PARTIE 1 1.4. Étude du problème Aujourd’hui lorsqu’on crée un objet 3D avec CImg, on ne peut que le sauvegarder au format .off. Ce type de fichier n’est rien d’autre qu’un fichier texte contenant la liste des sommets et des faces composant l’objet. Le langage VRML permet d’animer des scènes virtuelles et d’y incorporer une quantité illimitée d’objets 3D. Devant la limitation des fichiers OFF le besoin se fait sentir, pour une librairie graphique comme CImg, d’avoir recours à un type de fichier et de représentation plus évolués. Dans cette optique, mon projet consiste à développer les méthodes load vrml() et save vrml(). J’ai développé ces deux méthodes en C++ afin de pouvoir les incorporer directement dans CImg si leur fonctionnement est assuré. Pour m’assurer du bon fonctionnement de ces deux méthodes, j’ai essayé de dresser une liste exhaustive de tous les cas pouvant être rencontrés dans leurs utilisations. Il s’agissait ensuite de tester les méthodes sur tous ces cas particuliers, et d’adapter si besoin était, mon code. Projet ISIMA 3ème année Page 12 RAMI PARTIE 2 DEUXIÈME PARTIE Déroulement de l’étude Projet ISIMA 3ème année Page 13 RAMI PARTIE 2 2.1. Prise en main de CImg et VRML 2.1.1 La bibliothèque CImg La bibliothèque CImg est une librairie contenant un très grand nombre d’outils qui facilitent le traitement des images. Il existe également des outils pour afficher des objets 3D, leur appliquer des transformations, des textures, etc. À la base de toutes ces méthodes et ces outils il faut bien comprendre la façon dont CImg stocke et représente les objets 3D. Tout objet 3D dans CImg est représenté par trois éléments : – Un objet de type CImg que l’on appellera points – Un objet de type CImgList que l’on appellera primitives – Un objet de type CImgList que l’on appellera colors L’objet points contient les coordonnées des points composant l’objet 3D. On peut voir l’objet points comme une liste de valeurs réelles avec la liste des abscisses, des ordonnées puis des côtes. Si l’on prend l’exemple d’un parallélépipède rectangle, ou pavé droit, de longueur 10, de hauteur 20 et de profondeur 30 on obtient l’objet points suivant : 0 10 10 0 0 10 10 0 0 0 20 20 0 0 20 20 0 0 0 0 30 30 30 30 Ceci correspond à la liste des abscisses puis des ordonnées et finalement des côtes. Si l’on regroupe ces valeurs par 3 toutes les 8 valeurs (il y a 8 sommets dans un pavé droit), on obtient les 8 sommets de notre parallélépipède rectangle : (0,0,0) -> 0 (10,0,0) -> 1 (10,20,0) -> 2 (0,20,0) -> 3 (0,0,30) -> 4 (10,0,30) -> 5 (10,20,30) -> 6 (0,20,30) -> 7 La représentation est donc assez simple, on fait la liste des coordonnées dans une CImg qui est ici une simple liste de valeurs. Dans la représentation des points j’ai associé à chaque point un numéro. Ceci n’est pas anodin car il s’agit en fait de la procédure utilisée par CImg pour repérer les points et ensuite pouvoir utiliser cette numérotation dans la représentation des primitives. En effet, pour représenter les primitives de notre pavé droit, CImg va utiliser un objet de type CImgList que l’on appelle primitives. Cet objet comme son nom l’indique est une liste d’objets de type CImg qui sert à savoir quels sont les points à relier pour créer une face. Ces points seront repérés par leurs indices. L’objet primitives décrit donc la composition de chacune des faces, dans notre exemple un pavé droit a 6 faces, notre liste primitives contiendra donc 6 objets de type CImg. Dans chacun de ces objets on trouvera les points composant la face. Dans notre exemple, l’objet primitives est composé comme suit : 0 3 2 1 4 5 6 7 0 1 5 4 3 7 6 2 Projet ISIMA 3ème année Page 14 RAMI PARTIE 2 0 4 7 3 1 2 6 5 Le schéma ci-dessous reprend ce système de représentation : F IG . 2 – Repérage des composantes d’un pavé droit dans CImg Cette représentation est assez simple et elle correspond en tant que code C++ aux lignes suivantes : CImgList<unsigned int> primitives ; const CImg<float> points = CImg<float> : :box3d(primitives,10,20,30) ; La première ligne crée simplement un objet de type CImgList qui va contenir des entiers non signés (entiers positifs). La deuxième ligne appelle la méthode box3d(CImgList<T> p,int x,int y,int z) qui retourne les coordonnées d’un pavé droit de taille xyz et qui remplit la CImgList p avec les primitives correspondantes. Il reste ensuite à définir la couleur de notre objet 3D. Pour cela, CImg utilise une CImgList que l’on appellera ici colors. La méthode employée est de définir une couleur pour chaque primitive d’un objet, la taille de colors sera donc toujours égale à celle de primitives. Chaque couleur est définie par trois valeurs : un niveau de rouge, de vert et de bleu. L’objet colors est donc une liste d’objets de type CImg contenant trois valeurs, cette liste d’objets correspond à chacune des primitives. Si l’on reprend l’exemple du pavé droit, on va avoir 6 objets de type CImg donnant la couleur de chacune des 6 faces de notre pavé. Si par exemple on veut que toutes les faces du pavé soient rouges, on obtient l’objet colors suivant : 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 Si on reprend le contenu de primitives, on obtient que la face composée des sommets 0 3 2 1 aura Projet ISIMA 3ème année Page 15 RAMI PARTIE 2 la couleur (255,0,0), la face composée des sommets 4 5 6 7 aura la couleur (255,0,0) etc. Le code C++ correspondant est : CImgList<float> colors ; colors.insert(primitives.size(),CImg<unsigned char> : :vector(255,0,0)) ; Dans la première ligne on crée simplement un objet de type CImgList qui va contenir des valeurs réelles (type float). Dans la deuxième ligne on appelle la méthode insert (const unsigned int n, const CImg<t> img) sur colors, ce qui va avoir pour effet de remplir colors avec n fois la CImg img. En effet, la méthode vector(int r, int g, int b) retourne un vecteur contenant les valeurs r,g et b. Le schéma ci-dessous récapitule la situation. F IG . 3 – Représentation d’un pavé droit dans CImg De la même manière que dans l’exemple précédent, on peut créer toute forme d’objets en utilisant des méthodes comme sphere3d(primitives, rayon), cylinder3d(primitives, rayon, hauteur), cone3d(primitives, rayon, hauteur), etc en lieu et place de box3d(primitives, x, y, z). On peut également créer n’importe quelle forme d’objet tant que l’on respecte la structure des objets points, primitives et colors. Lorsque l’on veut appliquer une texture à un objet dans CImg on utilise la méthode texturize object3d(CImgList<tp> primitives, CImgList<tc> colors, CImg<tt> texture). On voit que l’on passe en argument à cette méthode les objets primitives et colors car c’est dans ces structures que l’on va faire les changements correspondant à la texturisation de l’objet. Dans l’objet colors, la méthode texturize object3d va simplement ajouter toutes les couleurs composant l’image utilisée comme texture correspondant à l’objet CImg<tt> texture. L’objet primitives est modifié de la manière suivante : pour toutes les primitives on ajoute, à la suite des indices, les coordonnées de la zone de l’image que l’on va utiliser comme texture. On aura par conséquent dans primitives des vecteurs de la forme (i0,i1,i2,x0,y0,x1,y1,x2,y2) où i0, i1 et i2 sont les indices des points composant la primitive et les points Projet ISIMA 3ème année Page 16 RAMI PARTIE 2 (x0,y0), (x1,y1) et (x2,y2) correspondent aux sommets du triangle dans l’image utilisée comme texture de la primitive. La représentation sous CImg d’un objet 3D est très structurée et on va maintenant voir comment cette représentation s’articule en VRML pour appréhender la façon dont se construit l’interface entre les deux formats. 2.1.2 Le langage VRML Dans toute la conduite du projet je me suis focalisé sur l’utilisation du VRML 2.0. Il existe plusieurs versions du VRML qui différent dans leurs syntaxes, le choix s’est donc porté sur la version 2.0 la plus récente à l’heure actuelle. En langage VRML la représentation et le codage d’un objet sont très ”textuels”. Le langage VRML se rapproche très fortement du langage HTML, en effet ici tout fonctionne sous forme de balise et mots clés. Pour simplifier, et pour coller aux fonctions que j’ai développées, tout nouvel objet est déclaré par le mot clé Shape. Dans une structure Shape il existe seulement 2 mots clés : geometry et appearance. ”geometry” permet comme son nom l’indique de définir la géométrie de l’objet à créer tandis que avec la structure appearance on va pouvoir modifier l’apparence de notre objet : sa couleur, sa texture, sa transparence ... Si l’on reprend l’exemple précédent du pavé droit rouge, on obtient le fichier VRML suivant : Shape { geometry Box { size 10 20 30 } appearance Appearance { material Material { diffuseColor 1,0,0 } } } La représentation en VRML est donc très simple. Si l’on analyse le code précédent, on observe qu’il suffit de déclarer un noeud Shape pour définir la structure d’un objet. Ensuite le mot clé geometry permet de préciser le type d’objet que l’on veut dessiner, ici une ”box” ou en terme plus mathématique : un pavé droit. Dans ce noeud geometry on définit les propriétés de l’objet que l’on veut créer, ici les dimensions du pavé. Ensuite on définit l’apparence de l’objet dans la structure appearance. Ici on veut donner une couleur à notre objet, on utilise pour cela le mot clé Material. Dans ce noeud on définit la couleur que l’on souhaite en niveau de rouge, vert et bleu de 0 à 1. Le terme diffuseColor permet simplement de dire que l’on veut que l’intensité de la couleur soit diffuse. On peut également dans une structure geometry à la place de “Box” utiliser “Cone”, “Cylinder” ou “Sphere” et définir dans le noeud les paramètres de chacun de ces objets. De même dans une structure appearance on peut définir une texture par la commande suivante : texture ImageTexture url[“nomFichier.jpg”] Il est également possible de définir la texturisation de l’image pixel par pixel afin de choisir les parties de l’image que l’on souhaitent texturiser. Dans le noeud appearance, il y a la structure Material qui permet de définir tout aspect visuel d’un objet Projet ISIMA 3ème année Page 17 RAMI PARTIE 2 autre que la texture. Ci-dessous la liste de ses modifications d’aspect : – diffuseColor 1 0 0.5 définit la couleur de l’objet qui varie en fonction de l’angle de réflection de la lumière sur l’objet. – emissiveColor 0.2 0 0.1 définit une couleur propre à l’objet, comme s’il était luminescent. – specularColor 0.2 0.6 0.2 définit la couleur des rayons lumineux réfléchis sur la surface de l’objet et renvoyés vers le point de vue de l’utilisateur. – ambientIntensity 0.3 coefficient entre 0 et 1 correspondant à l’influence de la lumière ambiante de la scène sur l’objet. – shininess 0.5 coefficient entre 0 et 1 correspondant au niveau de brillance de l’objet. – transparency 0.3 coefficient entre 0 et 1 correspondant au niveau de transparence de l’objet. Il existe également en langage VRML une manière de créer des objets 3D de formes quelconques. Pour cela il faut déterminer les coordonnées de chaque point et la composition de chaque face, on peut ensuite également déterminer une couleur pour chaque face. Il y a trois autres types de geometry qui peuvent être utilisés : PointSet, IndexedLineSet et IndexedFaceSet. Par PointSet on peut définir une série de points, IndexedLineSet permet de définir des séries de segments et IndexedFaceSet permet de définir des séries de faces. Ci-après on retrouve la structure de ces trois types de géométries : geometry PointSet { coord Coordinate { point [ -2 0 2, -2 0 -2, ] } } geometry IndexedLineSet(IndexedFaceSet) { coord Coordinate { point [ ] -2 0 2, -2 0 -2, } coordIndex [ 0, 1, -1, ] } Les structures de IndexedLineSet et de IndexedFaceSet sont similaires. Le noeud coord permet de donner les coordonnées de chacun des sommets composant l’objet à créer, chaque ligne étant composée par l’abscisse, l’ordonnée, la côte du point. Ensuite, dans coordIndex on donne la liste des connexions à faire entre les points, en désignant les points par leurs indices. Dans l’exemple ci-dessus on connecte le point (-2,0,2) au point (-2,0,-2). La valeur -1 sert à désigner la fin de la liste d’indices. Projet ISIMA 3ème année Page 18 RAMI PARTIE 2 2.2. Les algorithmes de principe Dans cette partie je vais présenter rapidement les algorithmes de principe des deux méthodes que j’ai codées. Dans un premier temps je vais présenter l’algorithme de principe de la méthode save vrml() qui permet de sauvegarder un objet créé dans CImg au format VRML. save vrml() : Si le fichier cible n’existe pas ou si l’objet à sauver est vide ou si ce n’est pas un objet 3D alors Lever une exception Parcourir CImgList colors pour déterminer si l’objet contient qu’une seule couleur Ouvrir le fichier cible Écrire “VRML V2.0 utf8” dans le fichier cible Écrire “Shape geometry IndexedFaceSet coord Coordinate point [” dans le fichier cible Pour tous les points de l’objet Écrire l’abscisse, l’ordonnée,la côte dans le fichier cible Écrire “coordIndex[” dans le fichier cible Pour toutes les primitives de l’objet Définir l’objet color valant la couleur de la primitive courante si cette couleur est définie, la couleur par défaut sinon Définir le niveau de rouge comme étant la 1ère composante de color Si le niveau de vert est défini alors Définir la couleur associée Sinon Définir le niveau de vert égal au niveau de rouge Si le niveau de bleu est défini alors Définir la couleur associée Sinon Définir le niveau de bleu égal au niveau de vert Écrire la liste des primitives dans le fichier cible Si il y’a plus d’une couleur dans l’objet alors Si on est à la première couleur alors Ajouter cette couleur au vecteur listant les couleurs Ajouter l’indice 0 à la chaine de caractères des indices de couleur Sinon Chercher dans le vecteur listant les couleurs si la couleur actuelle est déjà présente Si la couleur n’est pas présente Ajouter cette couleur au vecteur listant les couleurs Ajouter l’indice taille du vecteur à la chaine de caractères des indices de couleur Sinon Chercher l’indice correspondant à l’endroit où se trouve la couleur dans le vecteur Ajouter cette indice à la chaine de caractères des indices de couleur Écrire ”]” dans le fichier cible Projet ISIMA 3ème année Page 19 RAMI PARTIE 2 Si une texture a été définie alors Écrire ”} appearance Appearance { texture ImageTexture { url [nomDuFichier] } } }” dans le fichier cible Sinon Si il y a plus d’une couleur dans l’objet alors Écrire ”colorPerVertex FALSE color Color { color [” dans le fichier cible Tant que le vecteur listant les couleurs n’est pas vide Écrire la couleur dans le fichier cible Supprimer l’élément du vecteur Écrire ”] } colorIndex [” dans le fichier cible Écrire la chaine de caractères des indices de couleur dans le fichier cible Écrire ”] } }” dans le fichier cible Sinon Écrire ”} appearance Appearance { material Material { diffuseColor niveauDeRouge, niveauDeVert, niveauDeBleu } } }” Si l’utilisateur n’a pas fourni le fichier cible sous forme d’objet File alors Fermer le fichier cible Dans un deuxième temps je vais présenter l’algorithme de principe de la méthode load vrml() qui permet de charger un objet d’un fichier VRML dans des objets CImg en respectant la représentation type de CImg vue dans la partie 2.1. load vrml() : Si le fichier source n’existe pas alors Lever une exception Ouvrir le fichier source Lire le fichier source jusqu’à trouver autre chose qu’une ligne de commentaire Si la ligne atteinte dans le fichier n’est pas un noeud VRML alors Lever une exception Tant que l’on n’a pas atteint le noeud ”Shape” ou la fin du fichier Projet ISIMA 3ème année Page 20 RAMI PARTIE 2 Lire dans le fichier Tant que l’on n’a pas atteint les noeuds ”geometry” ou ”appearance” ou la fin du fichier Lire dans le fichier Tant que l’on n’est pas à la fin du fichier Ici il s’agit de déterminer quel type de géométrie nous trouvons dans le fichier et de faire les traitements adéquats. Je ne vais expliciter l’algorithme que pour le cas du pavé droit, les autres cas étant très sensiblement similaires. En effet, il s’agit toujours de chercher les paramètres de l’objet puis d’appeler la bonne méthode (box3d, sphere3d, ...). Si l’on est au noeud ”Box” alors Tant que nous n’avons pas trouvé la taille du pavé droit Parcourir le fichier source Si les 3 dimensions du pavé ne sont pas définies alors Lancer une exception Appeler la méthode box3d pour récupérer les points et les primitives de l’objet Si une texture est définie alors Charger l’image utilisée comme texture Initialiser l’objet colorsTextured Appeler la méthode texturize object3d Si le nombre de points est différent de 0 (il y’a déjà des objets dans la scène) alors Pour toutes les primitives Décaler les 4 premières composantes du nombre de points déjà existants Ajouter l’objet primitivesTemp ainsi créé à la CImgList primitives de l’objet appelant Pour tous les points du pavé Ajouter les abscisses, les ordonnées puis les côtes de ces points à la liste des Points Mettre à jour le nombre de points total de la scène Les autres conditions sont des ”sinon si” pour la sphère, le cylindre et le cône. Le traitement pour le noeud PointSet est également assez similaire, même si pour les noeuds PointSet, IndexedLineSet et IndexedFaceSet je ne gère pas les textures. Cependant je vais définir l’algorithme pour les noeuds IndexedFaceSet et IndexedLineSet, le traitement de ces deux noeuds étant le même, il se fait dans une même et unique boucle. Sinon si l’on est au noeud IndexedFaceSet ou IndexedLineSet alors Tant que l’on n’a pas trouvé la structure ”point [” Parcourir le fichier source Tant que l’on n’a pas parcouru tous les points Si les 3 coordonnées du point sont définies alors Ajouter les abscisses, les ordonnées puis les côtes de ces points à la liste des Points incrémenter le nombre de points Initialiser la CImgList primitivesTemp Tant que l’on n’a pas trouvé la structure ”coordIndex” Parcourir le fichier source Tant que l’on n’a pas parcouru tous les indices Si la ligne n’est pas un commentaire alors Découper la ligne à chaque virgule Tant qu’il y a des éléments issus de ce découpage Projet ISIMA 3ème année Page 21 RAMI PARTIE 2 Ajouter ces indices au vecteur primitiveComponents Pour tous les objets de primitiveComponents Transférer les indices de primitiveComponents dans l’objet CImg temp ajouter cet objet temp à primitivesTemp Si le nombre de points est différent de 0 (il y a déjà des objets dans la scène) alors Pour toutes les primitives Décaler toutes les composantes du nombre de points déjà existant Ajouter l’objet primitivesTemp ainsi créé à la CImgList primitives de l’objet appelant Mettre à jour le nombre de points total de la scène Tant que l’on n’est pas à la structure ”color [” ou à la fin du noeud IndexedFaceSet(ou IndexedLineSet) ou si l’on est à la fin du fichier Si l’on est à la structure ”color [” alors Mettre le booléen multipleColors à vrai Tant que l’on n’a pas parcouru toutes les couleurs Si la ligne n’est pas un commentaire alors Ajouter la CImg contenant la couleur au vecteur listColors Tant que l’on n’a pas trouvé la structure ”colorIndex” Parcourir le fichier source Tant que l’on n’a pas parcouru tous les indices de couleur Si la ligne n’est pas un commentaire alors Découper la ligne à chaque espace Tant qu’il y a des éléments issus de ce découpage Ajouter l’élément de listColors correspondant à l’indice dans l’objet colors Sinon Lever une exception comme quoi aucun noeud de geométrie valide n’a été trouvé dans le fichier source Si l’objet a une texture alors Ajouter l’objet colorsTextured dans la CImgList colors Réinitialiser la chaine de caractères contenant le chemin de l’image de texture Tant que l’on n’a pas trouvé le noeud ”material” ou ”texture” ou ”Shape” ou qu’on n’a pas atteint la fin du fichier Parcourir le fichier source Le reste du code consiste au traitement de l’apparence de l’objet. On considère ici que l’on a trouvé le noeud ”material” ou ”texture” ou ”Shape” ou qu’on a atteint la fin du fichier. Le cas où l’on est à la fin du fichier ne demande aucun traitement spécial dû à l’implémentation du reste du code. Si l’on est au noeud ”material” alors Tant que l’on n’est pas sorti du noeud material ou que l’on n’a pas atteint la fin du fichier Si l’on est à la ligne ”diffuseColor” et que l’on ne récupére pas bien la couleur alors Lancer une exception Si l’on est au noeud ”texture ImageTexture” alors Passer les booléens textureTest et colorDefined respectivement à vrai et faux Tant que l’on n’a pas trouvé l’url Parcourir le fichier source Découper la ligne par le caractère ” Récupérer le deuxième élément de ce découpage copier cet élément dans la chaine de caractères contenant le chemin vers l’image texture Projet ISIMA 3ème année Page 22 RAMI PARTIE 2 Si l’on est au noeud ”Shape” alors Mettre le booléen textureTest à faux Si il existe des primitives d’objet et si une couleur est définie et s’il n’y a pas de couleurs multiples et si l’objet n’est pas texturisé alors Insérer dans colors l’objet colorsTemp contenant les niveaux de rouge, vert et bleu Réinitialiser le nombre de primitives et les niveaux RGB Allouer la taille de l’objet appelant Pour tous les éléments de l’objet appelant Y insérer les points du vecteur listePoints Retourner l’objet appelant On peut voir que le code est assez simple et il était assez simple de l’implémenter. La plus grande difficulté fut de bien comprendre la façon dont CImg représente un objet 3D de manière interne et comment faire l’interface entre CImg et VRML. Je vais donc présenter, dans la dernière section de cette partie 2, les difficultés rencontrées et les choix d’implémentation réalisés. Projet ISIMA 3ème année Page 23 RAMI PARTIE 2 2.3. Les choix d’implémentation L’implémentation des méthodes save vrml et load vrml consistait à créer une interface entre CImg et le format VRML. La plus grosse problèmatique a donc été de comprendre les deux mécanismes de représentation d’objets 3D et, une fois qu’ils furent maitrisés, de créer des passerelles entre les deux. La méthode save vrml est assez simple car elle utilise la facilité du VRML sans avoir à en gérer sa vastitude. La méthode load vrml ne peut l’éviter, les problèmatiques d’implémentation furent donc plus nombreuses pour cette méthode. Dans un premier temps, je vais détailler les choix d’implémentation de la méthode save vrml. 2.3.1 Implémentation de save vrml L’implémentation de la méthode save vrml est assez simple. En effet, le but de cette méthode est de prendre un objet 3D représenté dans CImg par une CImg points, une CImgList primitives et une CImgList colors et de sauver cet objet au format VRML avec un type de géométrie et une apparence. Lorsqu’on a un objet dans CImg il est assez compliqué d’en déterminer la nature géométrique. C’est pour cette raison que j’ai fait le choix de représenter tous les objets par le type IndexedFaceSet. La grande flexibilité de ce type permet de représenter tout type d’objet 3D sans avoir à se soucier de sa nature. La méthode est alors très simple, on parcourt notre CImg points et on copie les coordonnées des points dans la structure ”point [ ... ]” d’IndexedFaceSet. On a ainsi la liste des sommets de notre objet qui est déjà correctement représentée dans notre fichier VRML. Le traitement des primitives est le même, le type IndexedFaceSet contient une structure ”coordIndex [...]” qui donne les connexions à réaliser entre les sommets pour former les primitives de notre objet. Il s’agit donc de récupérer chacune des primitives de la CImgList primitives et de copier leur contenu dans la structure ”coordIndex [...]”. La problèmatique pour cette méthode réside finalement simplement dans le traitement de l’apparence des objets 3D. En effet, il faut considérer et traiter indépendamment les trois cas de figure : – L’objet est composé d’une seul couleur – L’objet est composé de plusieurs couleurs – L’objet est texturisé Prenons tout d’abord le cas des couleurs. Pour déterminer si l’objet est composé d’une ou plusieurs couleurs il n’y a pas d’autre solution que de parcourir la CImgList colors et de s’arrêter dès que l’on trouve une couleur différente de la première. C’est l’implémentation que j’ai mise en place. Si l’objet est composé d’une seule couleur le traitement est très simple, il suffit d’utiliser le noeud ”material” et de préciser à la suite de diffuseColor le niveau de rouge, vert et bleu issu de n’importe quel élément de la CImgList colors. Si l’on a détecté au moins deux couleurs, on va utiliser la flexibilité de IndexedFaceSet qui permet de définir une couleur pour chaque primitive. Il existe dans le type IndexedFaceSet la possibilité d’utiliser le noeud ”color”. Ce noeud doit être utilisé de la façon suivante : on utilise la structure ”color [...]” à l’intérieur de laquelle on définit la palette des couleurs utilisées dans l’objet puis on utilise la structure ”colorIndex [...]”. Dans cette structure colorIndex il faut donner les indices des couleurs définies dans color [...] en fonction des couleurs que l’on veut donner à chaque primitive. Par exemple si on donne la suite d’entier suivante dans colorIndex : 0 1 1, cela veut dire que la première primitive aura la première couleur définie dans color [...] et la deuxième et troisième primitive auront la seconde couleur définie. La méthode choisie fut donc de parcourir la CImgList colors et de stocker dans des vecteurs la couleur et l’indice de cette couleur. À chaque tour de boucle, il fallait parcourir notre vecteur de couleurs pour s’assurer que la nouvelle couleur n’était pas déjà définie. Si elle l’était, on ajoute son indice au vecteur des indices. Projet ISIMA 3ème année Page 24 RAMI PARTIE 2 Enfin pour le cas des textures, la différence de représentation entre VRML et CImg n’a guère laissé le choix quant à l’implémentation. En effet, en VRML il suffit de définir le noeud ”texture ImageTexture” et de donner l’URL de l’image utilisée comme texture. En CImg, on ajoute les couleurs de l’image texture dans colors et on définit les zones de l’image utilisées pour chaque primitive. Il n’y a donc aucune façon, lorsqu’on est en CImg, de savoir quelle est l’URL de l’image, si ce n’est en la passant directement en argument de la méthode save vrml. 2.3.2 Implémentation de load vrml L’implémentation de la méthode load vrml() se révéla plus complexe car il fallait gérer la diversité du langage VRML et la prise en charge de scènes à objets multiples dans CImg. La boucle principale de cette fonction est un parcours du fichier VRML ligne par ligne. Dans ce parcours, on traite la géométrie et l’apparence de l’objet. Il a donc fallu gérer indépendamment les 7 types de géométrie du langage VRML excepté pour IndexedFaceSet et IndexedLineSet qui, ayant tout à fait la même structure, sont gérés ensemble. Pour les 4 types de géométrie box, sphere, cylinder et cone l’implémentation fût très simple car il suffisait d’utiliser les méthodes déjà existantes dans CImg : box3d, sphere3d, cylinder3d et cone3d. La première difficulté vint de la possibilité d’avoir des scènes 3D avec plus d’un objet. En effet, la méthode load vrml retourne un seul de chaque objet points, primitives et colors. Il fallait donc regrouper toutes les informations sur tous les objets 3D dans un seul exemplaire de composants CImg. Pour la structure points j’ai stocké les coordonnées dans une liste de points intermédiaire avant de copier cette liste dans la CImg points. La liste de points intermédiaire m’a permis de gérer le cas où l’on a de multiples objets 3D dans la scène. Comme je l’ai précisé dans la section 1 de cette partie 2, l’objet primitives donne les indices des points composant une face. De fait, si l’on a déjà des points présents dans la scène, l’objet primitives va faire référence aux mauvais points, j’ai donc fait un décalage des indices du nombre de points déjà présents dans la scène. Le décalage n’est pas nécessaire pour la structure colors car l’ordre est implicite dans cette structure. Toutefois, dans le décalage des indices de primitives, il faut être vigilant lorsque l’objet est texturé de ne pas déplacer les valeurs de primitives faisant référence aux coordonnées de l’image de texture. L’autre grande difficulté fût de bien gérer l’apparence des objets. Dans le cas d’un objet à couleurs multiples, défini dans le type IndexedFaceSet ou IndexedLineSet, j’ai d’abord stocké dans un vecteur toutes les couleurs définies dans la structure ”color[...]” puis directement copié chacune des couleurs indicées par la liste issu de ”colorIndex[...]” dans la CImgList colors. Une autre difficulté venait de la gestion des apparences quant à savoir si l’objet est texturé, composé d’une seule couleur ou de plusieurs. J’ai réglé ce problème en utilisant 4 booléens qui m’ont permis de gérer les 4 cas possibles (on pose n le nombre de primitives de l’objet courant) : – Aucune couleur n’est définie dans le fichier VRML, il faut dans ce cas ajouter dans colors n fois la CImg correspondant à la couleur par défaut. – Une seule couleur est définie, on copie dans colors n fois la CImg correspondant à la couleur unique de l’objet. – Plusieurs couleurs sont définies, cas détaillé ci-dessus. – Une texture est définie, on utilise la méthode texturize object3d() implémentée dans CImg. Le langage VRML permet également de définir à la fois des couleurs et des textures pour un même objet, en texturisant seulement une partie de l’objet. Je n’ai pas géré ce cas-là tout comme d’autres fonctionnalités du VRML. Projet ISIMA 3ème année Page 25 RAMI PARTIE 3 TROISIÈME PARTIE Résultats et discussion Projet ISIMA 3ème année Page 26 RAMI PARTIE 3 3.1. Résultats L’objectif de mon projet était de coder deux fonctions dans le langage C++. Ces fonctions étaient sensées faire le lien entre deux types de représentation d’objet 3D : le VRML et CImg. Durant la phase de développement du code j’ai régulièrement testé mes deux fonctions pour vérifier la justesse de mon code. Cependant, une fois les deux méthodes complétement codées, j’ai essayé de lister tous les cas possibles d’utilisation. La liste des cas est similaire pour les deux méthodes : 1. un objet quelconque sans couleur ni texture 2. un objet quelconque avec une couleur unique 3. un objet quelconque avec au moins deux couleurs 4. un objet quelconque avec une texture 5. au moins deux objets avec des couleurs uniques 6. au moins deux objets avec des textures et des couleurs Cette liste de tests m’a permis de repérer un grand nombre d’erreur d’implémentation et de faire les rectifications nécessaires pour essayer de satisfaire tous ces cas. J’ai effectué les tests tour à tour sur les méthodes save vrml() et load vrml(). Pour tester ces méthodes, le code utilisé est représenté dans la figure 4. F IG . 4 – Code utilisé pour les tests des méthodes Pour la fonction load vrml() tous les cas ont été vérifiés avec succès. Le processus est de créer le fichier .wrl approprié et d’observer l’affichage résultat de la méthode. Par exemple, pour le test numéro 6 de la méthode load vrml(), j’ai fourni le fichier “FichierTest.wrl” suivant : Shape { Projet ISIMA 3ème année Page 27 RAMI PARTIE 3 appearance Appearance { texture ImageTexture { url [”brick1.jpg”] } } geometry Box { size 30 20 10 } } Shape { geometry Box { size 10 20 30 } appearance Appearance { material Material { diffuseColor 0.831373,0.141176,0.125490 } } } Shape { geometry Sphere { radius 2 } appearance Appearance { material Material { diffuseColor 0.141176,0.125490,0.831373 } } } Le résultat de l’exécution du code de test sur ce fichier VRML est représenté dans la figure 5 : En ce qui concerne les tests pour la méthode save vrml(), ils sont tous concluants sauf le test numéro 6. Cela s’explique par la façon dont j’ai choisi d’implémenter les méthodes save vrml() et load vrml(). En effet, j’ai fait le choix de coder save vrml() de sorte que je puisse l’utiliser en parallèle de load vrml(). C’est-à-dire, pouvoir appeler load vrml() pour charger un fichier VRML dans les objets points, primitives, colors et les utiliser pour appeler la méthode save vrml(). Ainsi, le problème apparait lorsque l’on utilise load vrml() avec un fichier qui décrit plusieurs objets dont certains ont une couleur et d’autres ont une texture. À ce moment-là nous avons une CImg points et une CImgList primitives qui contiennent tous les points et toutes les primitives de nos objets. Il est alors difficile de repérer quels points et primitives décrivent tel ou tel objet, et lorsque la méthode save vrml est appelée, ce travail n’est pas fait. De plus, pour avoir le chemin du fichier image utilisé comme texture, on doit le passer en argument de la méthode save vrml(). Lorsque dans cette méthode on repére que l’on a un chemin de fichier pour la texture, on décréte que notre objet est texturisé on crée ainsi un noeud texture et on ne fait pas la différence entre l’apparence des deux objets. Par exemple, pour le test numéro 6, j’ai pris deux objets : deux pavés droits qui ont pour dimensions respectives (10,20,30) et (30,-5,-6). -5 et -6 indiquent seulement que l’on va dans les valeurs négatives du repère. De plus le premier pavé est défini avec une texture de “brique” et le deuxième est défini avec une couleur bleue. L’appel à la méthode save vrml() a généré un fichier .wrl dont l’affichage par la visionneuse Projet ISIMA 3ème année Page 28 RAMI PARTIE 3 F IG . 5 – Résultat de la méthode load vrml() Cortona 3D est donné dans la figure 6 : F IG . 6 – Résultat de la méthode save vrml() 3.2. Discussion On observe que les résultats obtenus sont bons et les plugins load vrml et save vrml sont aujourd’hui fonctionnels et ils ont été ajoutés par Mr. David Tschumperlé à la librairie CImg. Cependant, on observe qu’il reste beaucoup de travail à faire. Le projet était sensé mener à deux méthodes qui chargent et sauvent des fichiers VRML faisant ainsi l’interface entre la bibliothèque CImg et le langage VRML. Dans la phase de test on observe que la méthode save vrml() telle qu’elle est actuellement implémentée ne permet pas de sauver plusieurs objets 3D ayant des couleurs et des textures. Ce problème peut-être résolu en repensant l’implémentation des méthodes sous forme de listes d’objets 3D. Effectivement, lorsque l’on utilise la méthode load vrml() à partir d’un fichier décrivant plusieurs objets 3D, on place dans une même Projet ISIMA 3ème année Page 29 RAMI PARTIE 3 structure, “CImg points”, tous les sommets composant les objets. De même avec les structures “CImgList colors” et “CImgList primitives”. On pourrait ainsi retourner, par cette méthode load vrml, une CImgList de points et des CImgList de CImgList de primitives et colors. On aurait alors une division bien distincte entre chacun des objets 3D défini et on pourrait ainsi restituer les caractéristiques de chaque objet et appeler la méthode save vrml() sans aucune contrariété. Il faudrait bien évidemment adapter le code save vrml() à une telle représentation de scènes. Il y a également d’autres améliorations à faire afin que le code soit plus “propre”. Comme se débarasser des inclusions de classes comme “vector” et “string” en utilisant à la place des objets CImg et CImgList. On pourrait en outre adapter le code afin qu’il soit plus flexible à l’implémentation des fichiers VRML. Par exemple, actuellement, pour utiliser une texture il faut la placer avant le noeud “geometry” d’un objet, cela peut s’avérer génant si l’utilisateur ne respecte pas ces règles d’implémentation car l’erreur est difficilement détectable. De même, de par les nombreuses possibilités offertes par le langage VRML, il reste fort à faire dans la poursuite de ce projet. On pourra donc implémenter les différents effets de couleur et de luminance proposés par le VRML et faire la différence entre emissiveColor, diffuseColor et specularColor ou encore influer sur la transparence et la brillance de l’objet par les options transparency et shininess. Le langage VRML permet également de texturiser seulement des zones d’un objet. Ce genre d’option est tout à fait implémentable en CImg. On pourra également implémenter toutes les possibilités liées au noeud “Transform” qui permet d’appliquer des transformations à notre objet. On peut par exemple faire subir des rotations, des translations ou encore des changements d’échelle à notre objet. Il est également possible de réaliser des extrusions sur un objet ou d’ajouter du texte dans une scène, là encore se sont des fonctionnalités qui peuvent être implémentées car on en retrouve un équivalent dans CImg. Il y a ensuite d’énormes possibilités sur l’animation de scènes et l’intéraction avec celles-ci. Le langage VRML est donc très complet et il reste énormément de travail à accomplir si l’on veut avoir un support total du VRML dans CImg. La figure 3.4 récapitule la chronologie de mon travail. F IG . 7 – Chronologie non exhaustive du projet Projet ISIMA 3ème année Page 30 RAMI CONCLUSION CONCLUSION L’objectif de ce projet était de développer deux fonctions C++ load vrml() et save vrml() afin d’avoir un support du langage VRML dans la bibliothèque CImg. Le résultat obtenu qui a servi a la phase de test est l’affichage d’une scène 3D pour la méthode load vrml() et la création d’un fichier .wrl pour la méthode save vrml(), fichier .wrl que l’on visualise par un visualiseur VRML. La consigne du projet était d’obtenir un support du VRML pour des objets 3D maillés quelconques. Puis, si cela fonctionnait bien, de gérer les primitives texturées. J’ai atteint ces deux objectifs en ayant toutefois géré les textures seulement pour les objets 3D standards de la bibliothèque CImg. Les principales difficultés que j’ai rencontré étaient principalement liées à la compréhension de la représentation des objets 3D dans CImg. Pouvoir gérer les différentes possibilités d’apparence d’un objet et choisir la bonne implémentation en tant qu’objets CImg. Pour me simplifier un peu la tâche j’ai utilisé des objets de type vector et de type string car ces types sont dynamiques. Cependant, l’inclusion d’en-tête réduit la portabilité du code. Une autre difficulté fut le caractère assez complet du VRML qui en fait un langage très étoffé, il a donc fallu gérer souvent de nombreux cas de figure et prendre en compte au fur et à mesure des fonctionnalités ou possibilités que je n’avais pas envisagées. Ces problèmes je les ai assez bien résolus par les différentes phases de tests. Cette vastitude dans les capacités du langage VRML laisse la porte ouverte à l’extension du projet. On pourra implémenter les différentes fonctionnalités offertes par le VRML que je n’ai pas eu le temps de mettre en place. Je poursuivrai donc ce projet par la suite en continuant le développement de ces deux plugins. Outre des extensions, il faut aussi éliminer les inclusions réalisées et adapter le code en conséquence. On pourra également gérer la texture d’objets créés par IndexedFaceSet. Projet ISIMA 3ème année Page 31 RAMI RÉFÉRENCES BIBLIOGRAPHIQUES RÉFÉRENCES BIBLIOGRAPHIQUES Projet ISIMA 3ème année RAMI RÉFÉRENCES BIBLIOGRAPHIQUES Webographie [Site web officiel CImg] http ://cimg.sourceforge.net/, Octobre 2010 [Wikipedia VRML] http ://en.wikipedia.org/wiki/VRML, Octobre 2010 [Tutoriel VRML 2.0] http ://www.cs.iupui.edu/ aharris/webDesign/vrml/, Octobre 2010 [Fichier OFF] http ://shape.cs.princeton.edu/benchmark/documentation/off format.html, Octobre 2010 [Apprendre le VRML] http ://www.web3d-fr.com/tutoriels/Atelier-VRML.php, Novembre 2010 [VRML viewer] http ://www.cortona3d.com/Products/Cortona-3D-Viewer.aspx, Novembre 2010 [C++.com] http ://www.cplusplus.com/, Novembre 2010 Projet ISIMA 3ème année RAMI ANNEXES ANNEXES Projet ISIMA 3ème année RAMI ANNEXES A. Code de la méthode save vrml() Ci-dessous, le code complet de la méthode save vrml() commenté et indenté. save vrml.cpp 1 // ! Save VRML files. 2 template<typename tf, typename tc> 3 const CImg<T>& save_vrml(const char *const filename,const CImgList<tf>& primitives, const CImgList<tc>& colors, const char *const texturefile = 0) const { 4 return _save_vrml(0,filename,primitives,colors,texturefile); 5} 6 7 // ! Save VRML files. 8 template<typename tf, typename tc> 9 const CImg<T>& save_vrml(std::FILE *const file,const CImgList<tf>& primitives, const CImgList<tc>& colors, const char * const texturefile = 0) const { 10 return _save_vrml(file,0,primitives,colors,texturefile); 11 } 12 13 // Save VRML files ( internal ) . 14 template<typename tf, typename tc> 15 const CImg<T>& _save_vrml(std::FILE *const file, const char *const filename, const CImgList<tf>& primitives, const CImgList <tc>& colors, const char *const texturefile) const{ 16 // Check that the user furnished a file to save the object and that the object is not empty. 17 if (! file && !filename) 18 throw CImgArgumentException(_cimg_instance "save_vrml() : Specified filename is (null).",cimg_instance); 19 if (is_empty()) 20 throw CImgInstanceException(_cimg_instance "save_vrml() : Empty instance, for file ’%s’.",cimg_instance, filename?filename:"(FILE*)"); 21 // Check that the object we want to save is a 3D object . 22 CImgList<T> opacities; 23 char error_message[1024] = {0}; 24 if (! is_object3d(primitives,colors,opacities,true,error_message)) 25 throw CImgInstanceException(_cimg_instance "save_vrml() : Invalid specified 3d object, for file ’%s’ (%s ).",cimg_instance,filename?filename:"(FILE*)",error_message); 26 const CImg<tc> default_color(1,3,1,1,200); 27 // We open the file in which we will save the 3D object . 28 std::FILE * nfile; 29 if (file) 30 nfile = file; 31 else 32 nfile = cimg::fopen(filename,"w"); 33 std::fprintf(nfile,"#VRML V2.0 utf8\n"); // We use the version 2.0 of VRML 34 // We copy the coordinates of all the points 35 std::fprintf(nfile,"Shape {\n\tgeometry IndexedFaceSet {\n\t\tcoord Coordinate {\n\t\t\tpoint [\n"); 36 cimg_forX(*this,i) 37 std::fprintf(nfile,"\t\t\t\t%f %f %f,\n",(float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2) ) ) ; 38 std::fprintf(nfile,"\t\t\t]\n\t\t}\n\t\tcoordIndex [\n"); 39 bool sameColor = true; 40 float r = colors[0][0]/255.0f, g = colors[0][1]/255.0f, b = colors[0][2]/255.0f; 41 std::vector<std::string> listColor; 42 std::string listColorPerFace(""); 43 for ( int i=0;i<(int)colors.size();++i) {// Test if the object is composed of only one color 44 float valR = (colors[i][0])/255.0f, valG = (colors[i][1])/255.0f, valB = (colors[i][2])/255.0f; 45 if (r!=valR || g!=valG || b!=valB) { // If the object has different colors 46 sameColor = false; 47 i = colors.size(); 48 } 49 } 50 cimglist_for(primitives,l) { // For each primitive 51 const CImg<tc>& color = l<colors.width()?colors[l]:default_color; 52 const unsigned int psiz = primitives[l].size(), csiz = color.size(); 53 float r = color[0]/255.0f, g, b; 54 if (csiz > 1) 55 g = color[1]/255.0f; 56 else 57 g = r/255.0f; 58 if (csiz > 2) 59 b = color[2]/255.0f; 60 else 61 b = g/255.0f; 62 switch (psiz) { Projet ISIMA 3ème année RAMI ANNEXES case 1 : std::fprintf(nfile,"\t\t\t%u,-1\n",(unsigned int)primitives(l,0)); break; case 6 : case 2 : std::fprintf(nfile,"\t\t\t%u,%u,-1\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1)); break; case 9 : case 3 : std::fprintf(nfile,"\t\t\t%u,%u,%u,-1\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,2),(unsigned int) primitives(l,1)); break; case 12 : case 4 : std::fprintf(nfile,"\t\t\t%u,%u,%u,%u,-1\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,3),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1)); break; 63 64 65 66 67 68 69 70 71 72 73 } if (! sameColor) { // If there are different colors we store on every loop the RGB values into the vector listColor std::ostringstream oss; oss << r << " " << g << " " << b << "\n"; if (listColor.size() == 0) { listColor.push_back(oss.str()); listColorPerFace += "0"; // We store the indice of the color } else { std::vector<std::string>::iterator it; it = find (listColor.begin(), listColor.end(), oss.str()); std::ostringstream oss2; if (it==listColor.end()) { oss2 << " " << listColor.size(); listColorPerFace += oss2.str(); listColor.push_back(oss.str()); } else { int n = 0; for (std::vector<std::string>::iterator iter = listColor.begin(); iter != it; iter++) n++; oss2 << " " << n; listColorPerFace += oss2.str(); } } } 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 } } std::fprintf(nfile,"\t\t]\n"); if (texturefile) // If we have a texture instead of a color std::fprintf(nfile,"\n\t}\n\tappearance Appearance {\n\t\ttexture ImageTexture {\n\t\t\turl [\"%s\"]\n\t \t}\n\t}\n}",texturefile); else { if (! sameColor) { // If there are different colors we add all of them std::fprintf(nfile,"\tcolorPerVertex FALSE\n\tcolor Color {\n\t\tcolor [\n"); while (! listColor.empty()) { std::fprintf(nfile,"\t\t\t%s",(listColor.back()).c_str()); listColor.pop_back(); } std::fprintf(nfile,"\t\t]\n\t}\n\tcolorIndex [\n\t\t"); std::fprintf(nfile,"%s",listColorPerFace.c_str()); std::fprintf(nfile,"\n\t]\n\t}\n}"); } else // If there is only one color we add it with the Material node std::fprintf(nfile,"\t}\n\tappearance Appearance {\n\t\tmaterial Material {\n\t\t\tdiffuseColor %f,%f ,%f\n\t\t}\n\t}\n}",colors[0][0]/255.0f,colors[0][1]/255.0f,colors[0][2]/255.0f); } if (! file) cimg::fclose(nfile); return * this ; Projet ISIMA 3ème année RAMI ANNEXES B. Code de la méthode load vrml() Ci-dessous, le code complet de la méthode load vrml() commenté et indenté. load vrml.cpp 1 // ! Load a 3d object from a .VRML file. 2 template<typename tf, typename tc> CImg<T>& load_vrml(const char *const filename, CImgList<tf>& primitives, CImgList<tc >& colors) { 3 return _load_vrml(0,filename,primitives,colors); 4} 5 6 template<typename tf, typename tc> 7 static CImg<T> get_load_vrml(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors) { 8 return CImg<T>().load_vrml(filename,primitives,colors); 9} 10 11 // ! Load a 3d object from a .VRML file. 12 template<typename tf, typename tc> 13 CImg<T>& load_vrml(std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors) { 14 return _load_vrml(file,0,primitives,colors); 15 } 16 17 template<typename tf, typename tc> 18 static CImg<T> get_load_vrml(std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors) { 19 return CImg<T>().load_vrml(file,primitives,colors); 20 } 21 22 template<typename tf, typename tc> 23 CImg<T>& _load_vrml(std::FILE *const file, const char *const filename,CImgList<tf>& primitives, CImgList<tc>& colors) { 24 if (! file && !filename) 25 throw CImgArgumentException(_cimg_instance "load_vrml() : Specified filename is (null).",cimg_instance); 26 std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); 27 char line[256] = {0}; 28 int err; 29 // Skip comments, and read the first node 30 do { 31 err = std::fscanf(nfile,"%255[ˆ\n] ",line); 32 } 33 while (! err || (err==1 && *line==’#’)); 34 // Check for a vrml valid node 35 if (cimg::strncasecmp(line,"Shape",5) && cimg::strncasecmp(line,"Transform",9) && cimg::strncasecmp(line," NavigationInfo",14) && cimg::strncasecmp(line,"Billboard",9)) { 36 if (! file) 37 cimg::fclose(nfile); 38 throw CImgIOException(_cimg_instance "load_vrml() : VRML nodes not found in file ’%s’.",cimg_instance, filename?filename:"(FILE*)"); 39 } 40 // Look for the Shape node (as we do not manage the treatment for the other nodes yet ) 41 while(cimg::strncasecmp(line,"Shape",5) && !feof(nfile)) 42 err = std::fscanf(nfile,"%255[ˆ\n] ",line); 43 if (feof(nfile)) { 44 if (! file) 45 cimg::fclose(nfile); 46 throw CImgIOException(_cimg_instance "load_vrml() : Shape node not found in file ’%s’.",cimg_instance, filename?filename:"(FILE*)"); 47 } 48 // Look for either geometry or appearance node 49 while(cimg::strncasecmp(line,"geometry",8) && cimg::strncasecmp(line,"appearance",10) && !feof(nfile)) 50 err = std::fscanf(nfile,"%255[ˆ\n] ",line); 51 if (feof(nfile)) { // If none of these nodes are defined 52 if (! file) 53 cimg::fclose(nfile); 54 throw CImgIOException(_cimg_instance "load_vrml() : geometry and appearance nodes not found in file ’%s ’.",cimg_instance,filename?filename:"(FILE*)"); 55 } 56 std::vector<T> listePoints; // Intermediate list containing the points of the whole object 57 primitives.assign(); 58 colors.assign(); 59 int nbPointsTotal = 0, nbPrimitives = 0; // Count the number of points of the whole object and the number of primitives 60 float r = 0.7f, g = 0.7f, b = 0.7f; // RGB level of the object , the object is gray by default Projet ISIMA 3ème année RAMI 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 ANNEXES bool colorDefined = true, multipleColors = false, textureTest = false; // Boolean used to know if a color is defined for an object , if this object has multiple colors or if the object has a texture char textureFile[256] = {0}; // Variable containing the name of the image used as a texture while (! feof(nfile)) { char type[256] = {0}, textureFileTemp[256] = {0}; colorDefined = true; if (! cimg::strncasecmp(line,"geometry",8)) { // We are at the geometry node std::sscanf(line,"geometry %s",type);// We are looking for the type of geometry to draw const CImg<float> coords = CImg<float>::empty();// CImg used for the texturization of an object CImgList<tc> colorsTextured;// CImgList used for the texturization of the color of an object CImgList<tf> primitivesTemp;// Intermediate CImgList used to update the primitives of the whole object if (! cimg::strncasecmp(type,"Box",3)) { // If the object to draw is a box while(cimg::strncasecmp(line,"size",4) && !feof(nfile)) // We are looking for the size of the box err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { // If no size is specified if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : size of box not defined in file ’%s’.", cimg_instance, filename?filename:"(FILE*)"); } float X = 0, Y = 0, Z = 0; // The width, height and depth of the box if (( err = std::sscanf(line,"size %f %f %f[ˆ\n] ",&X,&Y,&Z))!=3 && (err = std::sscanf(line,"size %f,%f,%f[ˆ\n ] ",&X,&Y,&Z))!=3) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : Failed to read box size in file ’%s’.", cimg_instance,filename?filename:"(FILE*)"); } const CImg<T> pointsTemp = CImg<T>::box3d(primitivesTemp,(T)X,(T)Y,(T)Z); // We generate the primitives and the points of the box nbPrimitives = primitivesTemp.size(); // We save the number of primitives of the object if (textureTest) { // If the object has a texture const CImg<float> texture(textureFile);// We put the image used as a texture into a CImg object colorsTextured.insert(primitivesTemp.size(),CImg<unsigned char>::vector(0,50,250));// We initialize the colorsTextured list pointsTemp.texturize_object3d(primitivesTemp,colorsTextured,texture,coords); // We texturize the object nbPrimitives = 0; } if (nbPointsTotal) { // If there are already some objects in the scene for ( int j=0;j<(int)primitivesTemp.size();j++) { for ( int i=0;i<4;i++ primitivesTemp(j).at(i) += (tf)nbPointsTotal; // We shift the indices in the primitives to designate the right points } } primitives.push_back(primitivesTemp); // We add the primitives of the box to the general primitives variable for ( int i=0;i<(int)pointsTemp.size()/3;++i) { // We add the points into the temporary list in the right order listePoints.push_back((T)pointsTemp.at(i)); listePoints.push_back((T)pointsTemp.at(i+8)); listePoints.push_back((T)pointsTemp.at(i+16)); } nbPointsTotal += pointsTemp.size()/3; // We increase the number of points of the whole object } else if (! cimg::strncasecmp(type,"Sphere",6)) { // If the object to draw is a sphere while(cimg::strncasecmp(line,"radius",6) && !feof(nfile)) err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : radius of sphere not defined in file ’%s’.", cimg_instance, filename?filename:"(FILE*)"); } float R = 0; if (( err = std::sscanf(line,"radius %f[ˆ\n] ",&R))!=1) { // We get the radius of the sphere if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : Failed to read sphere radius in file ’%s’.", cimg_instance,filename?filename:"(FILE*)"); } const CImg<T> pointsTemp = CImg<T>::sphere3d(primitivesTemp,(T)R); // Compute the necessary points and primitives for a sphere of radius R nbPrimitives = primitivesTemp.size(); // We get the number of primitives to used on the attribution of a color , in case no specific color is defined if (textureTest) { // If the object has a texture Projet ISIMA 3ème année RAMI 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 ANNEXES const CImg<float> texture(textureFile);// We put the image used as a texture into a CImg object colorsTextured.insert(primitivesTemp.size(),CImg<unsigned char>::vector(0,50,250));// We initialize the colorsTextured list pointsTemp.texturize_object3d(primitivesTemp,colorsTextured,texture,coords); // We texturize the object nbPrimitives = 0; // We set to 0 because there is no color to use } if (nbPointsTotal) { // If there are already some objects in the scene for ( int j=0;j<(int)primitivesTemp.size();j++) { for ( int i=0;i<3;i++) primitivesTemp(j).at(i) += (tf)nbPointsTotal; } } primitives.push_back(primitivesTemp); for ( int i=0;i<(int)pointsTemp.size()/3;++i) { listePoints.push_back((T)pointsTemp.at(i)); listePoints.push_back((T)pointsTemp.at(i+pointsTemp.size()/3)); listePoints.push_back((T)pointsTemp.at(i+2*pointsTemp.size()/3)); } nbPointsTotal += pointsTemp.size()/3; } else if (! cimg::strncasecmp(type,"Cone",4)) { // If the object to draw is a cone while(cimg::strncasecmp(line,"bottomRadius",12) && !feof(nfile) && cimg::strncasecmp(line,"height",6)) err = std::fscanf(nfile,"%255[ˆ\n] ",line); float R = 0, H = 0; if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : bottom radius and height of cone not defined in file ’%s’.",cimg_instance, filename?filename:"(FILE*)"); } else if (! cimg::strncasecmp(line,"bottomRadius",12)) { // We find the bottom radius of the cone first if (( err = std::sscanf(line,"bottomRadius %f[ˆ\n] ",&R))!=1) { // We get the radius into the variable R if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : Failed to read cone bottomRadius in file ’% s’.",cimg_instance,filename?filename:"(FILE*)"); } while (! feof(nfile) && cimg::strncasecmp(line,"height",6)) // We look for the height of the cone err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : height of cone not defined in file ’%s’.", cimg_instance, filename?filename:"(FILE*)"); } if (( err = std::sscanf(line,"height %f[ˆ\n] ",&H))!=1) { // We get the height into the variable H if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : Failed to read cone height in file ’%s’.", cimg_instance,filename?filename:"(FILE*)"); } } else { // We find the height of the cone first if (( err = std::sscanf(line,"height %f[ˆ\n] ",&H))!=1) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : Failed to read cone height in file ’%s’.", cimg_instance,filename?filename:"(FILE*)"); } while (! feof(nfile) && cimg::strncasecmp(line,"bottomRadius",12)) err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : bottom radius of cone not defined in file ’%s’.",cimg_instance, filename?filename:"(FILE*)"); } if (( err = std::sscanf(line,"bottomRadius %f[ˆ\n] ",&R))!=1) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : Failed to read cone bottom radius in file ’%s’.",cimg_instance,filename?filename:"(FILE*)"); } Projet ISIMA 3ème année RAMI 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 ANNEXES } const CImg<T> pointsTemp = CImg<T>::cone3d(primitivesTemp,(T)R,(T)H); // Compute the necessary points and primitives for a cone of radius R and height H nbPrimitives = primitivesTemp.size(); if (textureTest) { // If the object has a texture const CImg<float> texture(textureFile);// We put the image used as a texture into a CImg object colorsTextured.insert(primitivesTemp.size(),CImg<unsigned char>::vector(0,50,250)); // We initialize the colorsTextured list pointsTemp.texturize_object3d(primitivesTemp,colorsTextured,texture,coords); // We texturize the object nbPrimitives = 0; } if (nbPointsTotal) { for ( int j=0;j<(int)primitivesTemp.size();j++) { for ( int i=0;i<3;i++) primitivesTemp(j).at(i) += (tf)nbPointsTotal; } } primitives.push_back(primitivesTemp); for ( int i=0;i<(int)pointsTemp.size()/3;++i) { listePoints.push_back((T)pointsTemp.at(i)); listePoints.push_back((T)pointsTemp.at(i+pointsTemp.size()/3)); listePoints.push_back((T)pointsTemp.at(i+2*pointsTemp.size()/3)); } nbPointsTotal += pointsTemp.size()/3; } else if (! cimg::strncasecmp(type,"Cylinder",8)) { // If the object to draw is a cylinder while(cimg::strncasecmp(line,"radius",6) && !feof(nfile) && cimg::strncasecmp(line,"height",6)) err = std::fscanf(nfile,"%255[ˆ\n] ",line); float R = 0, H = 0; if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : radius or height of cylinder not file ’%s’.",cimg_instance, filename?filename:"(FILE*)"); } else if (! cimg::strncasecmp(line,"radius",6)) { // If we find the radius first if (( err = std::sscanf(line,"radius %f[ˆ\n] ",&R))!=1) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : Failed to read cylinder radius ’.",cimg_instance,filename?filename:"(FILE*)"); } while (! feof(nfile) && cimg::strncasecmp(line,"height",6)) // We now look for the height of the cylinder err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : height of cylinder not defined ’.",cimg_instance, filename?filename:"(FILE*)"); } if (( err = std::sscanf(line,"height %f[ˆ\n] ",&H))!=1) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : Failed to read cylinder height ’.",cimg_instance,filename?filename:"(FILE*)"); } } else { // If we find the height first if (( err = std::sscanf(line,"height %f[ˆ\n] ",&H))!=1) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : Failed to read cylinder height ’.",cimg_instance,filename?filename:"(FILE*)"); } while (! feof(nfile) && cimg::strncasecmp(line,"radius",6))// We now look for the radius of the cylinder err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : radius of cylinder not defined ’.",cimg_instance, filename?filename:"(FILE*)"); } if (( err = std::sscanf(line,"radius %f[ˆ\n] ",&R))!=1) { Projet ISIMA 3ème année defined in in file ’%s in file ’%s in file ’%s in file ’%s in file ’%s RAMI 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 ANNEXES if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : Failed to read cylinder radius in file ’%s ’.",cimg_instance,filename?filename:"(FILE*)"); } } const CImg<T> pointsTemp = CImg<T>::cylinder3d(primitivesTemp,(T)R,(T)H); // Compute the necessary points and primitives for a cylinder of radius R and height H if (textureTest) { // If the object has a texture const CImg<float> texture(textureFile);// We put the image used as a texture into a CImg object colorsTextured.insert(primitivesTemp.size(),CImg<unsigned char>::vector(0,50,250));// We initialize the colorsTextured list pointsTemp.texturize_object3d(primitivesTemp,colorsTextured,texture,coords); // We texturize the object nbPrimitives = 0; } nbPrimitives = primitivesTemp.size(); if (nbPointsTotal) { for ( int j=0;j<(int)primitivesTemp.size();j++) { for ( int i=0;i<3;i++) primitivesTemp(j).at(i) += (tf)nbPointsTotal; } } primitives.push_back(primitivesTemp); for ( int i=0;i<(int)pointsTemp.size()/3;++i) { listePoints.push_back((T)pointsTemp.at(i)); listePoints.push_back((T)pointsTemp.at(i+pointsTemp.size()/3)); listePoints.push_back((T)pointsTemp.at(i+2*pointsTemp.size()/3)); } nbPointsTotal += pointsTemp.size()/3; } else if (! cimg::strncasecmp(type,"PointSet",8)) { // If the object to draw is a set of points while(cimg::strncasecmp(line,"point [",7) && !feof(nfile)) err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : points of pointSet node not defined in file ’%s’.",cimg_instance, filename?filename:"(FILE*)"); } err = std::fscanf(nfile,"%255[ˆ\n] ",line); int nbPoints = 0; while(cimg::strncasecmp(line,"]",1) && !feof(nfile)) { // while we did not get all the points and while we are not at the end of the file float X=0,Y=0,Z=0; if (( err = std::sscanf(line,"%f %f %f,[ˆ\n] ",&X,&Y,&Z))==3 || (err = std::sscanf(line,"%f,%f,%f,[ˆ\n] ",&X ,&Y,&Z))==3) { listePoints.push_back((T)X); listePoints.push_back((T)Y); listePoints.push_back((T)Z); ++nbPoints; } err = std::fscanf(nfile,"%255[ˆ\n] ",line); } if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : bad structure of pointSet node in file ’%s’.", cimg_instance, filename?filename:"(FILE*)"); } primitivesTemp.assign(); for ( int i=0;i<nbPoints;++i) { // The primitive is only composed of the indice of the point itself CImg<tf> temp(1,1,1,1,(tf)i); primitivesTemp.push_back(temp); } if (nbPointsTotal) { for ( int j=0;j<(int)primitivesTemp.size();j++) { for ( int i=0;i<(int)primitivesTemp(j).size();i++) primitivesTemp(j).at(i) += (tf)nbPointsTotal; } } nbPrimitives = primitivesTemp.size(); primitives.push_back(primitivesTemp); nbPointsTotal += nbPoints; Projet ISIMA 3ème année RAMI 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 ANNEXES } else if (! cimg::strncasecmp(type,"IndexedLineSet",14) || !cimg::strncasecmp(type,"IndexedFaceSet",14)) { // If the object to draw is a set of lines or a set of faces while(cimg::strncasecmp(line,"point [",7) && !feof(nfile)) err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : points of IndexedSet node not defined in file ’%s’.",cimg_instance, filename?filename:"(FILE*)"); } err = std::fscanf(nfile,"%255[ˆ\n] ",line); int nbPoints = 0; while(cimg::strncasecmp(line,"]",1) && !feof(nfile)) { // As long as there are points defined we add them to the list float X=0,Y=0,Z=0; if (( err = std::sscanf(line,"%f %f %f,[ˆ\n] ",&X,&Y,&Z))==3 || (err = std::sscanf(line,"%f,%f,%f,[ˆ\n] ",&X ,&Y,&Z))==3) { listePoints.push_back((T)X);// We get the coordinates of the points into a list of points listePoints.push_back((T)Y); listePoints.push_back((T)Z); ++nbPoints; } err = std::fscanf(nfile,"%255[ˆ\n] ",line); } if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : bad structure of point vector node in file ’% s’.",cimg_instance, filename?filename:"(FILE*)"); } primitivesTemp.assign(); while(cimg::strncasecmp(line,"coordIndex [",12) && !feof(nfile)) // We are looking for the index of the points err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : coordIndex not furnished for IndexedSet node in file ’%s’.",cimg_instance, filename?filename:"(FILE*)"); } err = std::fscanf(nfile,"%255[ˆ\n] ",line); while(cimg::strncasecmp(line,"]",1) && !feof(nfile)) { // As long as there are indices if (* line!=’#’) { std::vector<tf> primitiveComponents; char * pch; pch = strtok (line,","); while (pch != NULL && atof(pch)!=−1) { // We extract the list of indices and store them into a vector if (!( int )count(primitiveComponents.begin(),primitiveComponents.end(),(tf)atof(pch))) primitiveComponents.push_back((tf)atof(pch)); pch = strtok (NULL, ","); } CImg<tf> temp(1,primitiveComponents.size(),1,1); for ( int i=0;i<(int)primitiveComponents.size();++i) temp(0,i) = primitiveComponents.at(i); primitivesTemp.push_back(temp); } err = std::fscanf(nfile,"%255[ˆ\n] ",line); } if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : bad structure of coordIndex in file ’%s’.", cimg_instance, filename?filename:"(FILE*)"); } if (nbPointsTotal) { for ( int j=0;j<(int)primitivesTemp.size();j++) { for ( int i=0;i<(int)primitivesTemp(j).size();i++) primitivesTemp(j).at(i) += (tf)nbPointsTotal; } } nbPrimitives = primitivesTemp.size(); primitives.push_back(primitivesTemp); nbPointsTotal += nbPoints; while(cimg::strncasecmp(line,"color [",7) && cimg::strncasecmp(line,"}",1) && !feof(nfile)) Projet ISIMA 3ème année RAMI 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 ANNEXES err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : bad structure of coordIndex in file ’%s’.", cimg_instance, filename?filename:"(FILE*)"); } else if (! cimg::strncasecmp(line,"color [",7)) { // If there are different colors defined for each faces multipleColors = true; std::vector<CImg<tc> > listColors; err = std::fscanf(nfile,"%255[ˆ\n] ",line); while(cimg::strncasecmp(line,"]",1) && !feof(nfile)) { // We add the list of all colors defined into the vector listColors if (* line!=’#’) { if (( err = std::sscanf(line,"%f %f %f[ˆ\n] ",&r,&g,&b))!=3) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : wrong number of color furnished in file ’%s’.",cimg_instance,filename?filename:"(FILE*)"); } CImg<tc> img(3,1,1,1,(tc)(r*255),(tc)(g*255),(tc)(b*255)); listColors.push_back(img); } err = std::fscanf(nfile,"%255[ˆ\n] ",line); } if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : bad structure of color in file ’%s’.", cimg_instance, filename?filename:"(FILE*)"); } else { while(cimg::strncasecmp(line,"colorIndex [",12) && !feof(nfile)) err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : colorIndex not furnished for Color node in file ’%s’.",cimg_instance, filename?filename:"(FILE*)"); } err = std::fscanf(nfile,"%255[ˆ\n] ",line); while(cimg::strncasecmp(line,"]",1) && !feof(nfile)) { // We add the colors at the right index into the vector colors if (* line!=’#’) { char * pch; pch = strtok (line," "); while (pch != NULL) { int indice = atoi(pch); colors.insert(CImg<tc>::vector((tc)(listColors[indice])[0],(tc)(listColors[indice])[1],(tc)( listColors[indice])[2])); pch = strtok (NULL, " "); } } err = std::fscanf(nfile,"%255[ˆ\n] ",line); } } } } else // If none of the known type of shape is defined cimg::warn(_cimg_instance "load_vrml() : Failed to read type of geometry to draw from file ’%s’.", cimg_instance,filename?filename:"(FILE*)"); if (textureTest) { // If the object considered is texturized colors.push_back(colorsTextured); *textureFile = 0; } while(cimg::strncasecmp(line,"appearance",10) && cimg::strncasecmp(line,"Shape",5) && !feof(nfile)) // We look for the node appearance or for another shape err = std::fscanf(nfile,"%255[ˆ\n] ",line); } if (! cimg::strncasecmp(line,"appearance",10)) { // We are at the appearance node while(cimg::strncasecmp(line,"texture ImageTexture",20) && cimg::strncasecmp(line,"diffuseColor",12) && !feof( nfile)) // We are looking for a valid appearance node err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (! cimg::strncasecmp(line,"diffuseColor",12)) { // If the object as a unique diffuse color Projet ISIMA 3ème année RAMI ANNEXES if (( err = std::sscanf(line,"diffuseColor %f,%f,%f[ˆ\n] ",&r,&g,&b))!=3 && (err = std::sscanf(line," diffuseColor %f %f %f[ˆ\n] ",&r,&g,&b))!=3) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : wrong number of color furnished in file ’%s’. ",cimg_instance,filename?filename:"(FILE*)"); } 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 } else if (! cimg::strncasecmp(line,"texture ImageTexture",20)) { // If there is a texture defined in the VRML file textureTest = true; colorDefined = false; while(cimg::strncasecmp(line,"url",3) && !feof(nfile)) err = std::fscanf(nfile,"%255[ˆ\n] ",line); if (feof(nfile)) { if (! file) cimg::fclose(nfile); throw CImgIOException(_cimg_instance "load_vrml() : texture not defined in file ’%s’.", cimg_instance, filename?filename:"(FILE*)"); } std::sscanf(line,"url [%s][ˆ\n] ",textureFileTemp); // We temporary put the name of the texture image into textureFileTemp char * pch; pch = strtok (textureFileTemp,"\""); strcpy(textureFile,pch); // We put the url of the texture image into textureFile } 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 } } else if (! cimg::strncasecmp(line,"Shape",5)) // We have another shape node textureTest = false; // We reinitialize the texture boolean if (nbPrimitives && colorDefined && !multipleColors && !textureTest) { // If there is only one color defined we add it to the colors CImgList or if no color is defined for an object , we add the default color CImgList<tc> colorsTemp; colorsTemp.insert(nbPrimitives,CImg<tc>::vector((tc)(r*255),(tc)(g*255),(tc)(b*255))); colors.push_back(colorsTemp); nbPrimitives = 0; r = 0.7f, g = 0.7f, b = 0.7f; } err = std::fscanf(nfile,"%255[ˆ\n] ",line); } assign(listePoints.size()/3,3); cimg_forX(*this,l) { // We add the points coordinates to the calling object (* this ) (l,0) = (T)(listePoints.at(l*3)), (*this)(l,1) = (T)(listePoints.at(l*3+1)), (*this)(l,2) = (T)(listePoints.at(l*3+2)); } if (! file) cimg::fclose(nfile); return * this ; Projet ISIMA 3ème année