shade.ca
Games
Art
Coding
Beauty Contest
Reviews
Spoofs
Corporate site
Webcams
Links




Tell Me the Truth

Hacky the raccoon
Shade.ca Web Design

3D Molecule representation (C++)

SouthPark Slot Machine

Random Review
Random Review



Join our mailing list!   Tell a friend about this site!

3DMOL - Le logiciel

Le logiciel

    Dans les pages qui suivent, nous allons vous expliquer comment nous avons créé notre logiciel et les connaissances que vous aller acquérir vous permettrons de créer votre propre moteur 3D sur ordinateur. Bien sûr, le tout sera vulgarisé et la programmation comme telle ne sera utilisée qu'à titre d'exemple. Évidemment, nous ne voulons pas vous donner le code de notre projet seulement pour que vous y fassiez quelques modifications pour ensuite affirmer que vous l'avez entièrement créé. Nous voulons vous aider à comprendre le fonctionnement d'un moteur 3D de base. De plus, un bon moteur 3D est indépendant du langage dans lequel il est programmé; ainsi, nous voulons vous permettre de créer le votre dans le langage de votre choix. Il est intéressant de noter que l'aspect le plus important d'un logiciel n'est pas la programmation mais plutôt la méthode utilisée pour résoudre le problème. De plus, ce qui distingue un programmeur avancé d'un débutant, c'est sa capacité d'analyser un problème et d'essayer de le résoudre sans même toucher à un ordinateur. Il doit penser à la structure et au fonctionnement du logiciel avant de se lancer dans sa création. Bref, ce qui suit est une explication étape par étape de la construction de notre moteur 3D. Veuillez noter que certains aspects ne seront pas traités puisque ce document était, à l'origine, pour notre professeur de chimie n'ayant aucune connaissance de la programmation. C'est à vous de découvrir ces aspects dans le code source.



La programmation orientée objet

    Nous devons d'abord vous dire que notre projet a été créé suivant la programmation orientée objet, que nous allons maintenant définir.

    Auparavant, la programmation était linéaire et consistait à une suite de lignes de code desquelles découlaient des actions. Par ailleurs, toutes les données étaient stockées dans des types prédéfinis et assez simples: caractère, entier, nombre réel, etc.





    Depuis, un nouveau type de programmation est né. La programmation orientée objet permet aux programmeurs de créer des " objets " sur mesure qui interagissent entre eux. Cette méthode permet de créer des objets symboliques complexes pour contenir de l'information d'une façon plus structurée et logique qu'avec les types plus simples. Le programmeur construit l'objet comme il le veut; il le définit. Ici, il faut prendre le terme objet au sens large car en programmation il peut signifier n'importe quoi et pas seulement les objets physiques. Un objet en programmation peut représenter quelque chose de physique ou symbolique. De plus, le programmeur peut définir les propriétés et les " méthodes " de ses objets. Les propriétés d'un objet sont habituellement des types de données plus simples comme une chaîne de caractères, ce sont des caractéristiques fondamentales de l'objet. De leur côté, les méthodes (terme de programmation tiré de l'anglais) sont des actions que l'objet peut faire sur lui-même ou sur d'autres objets. Voici deux exemples qui pourraient vous aider à comprendre :
        " objet " automobile (concret):
            propriétés : couleur, marque, année
            méthodes : avancer, reculer


        " objet " liste de mots (symbolique):
            propriétés : nombre de mots, chacun des mots
            méthodes : ajouter ou enlever un mot




Étapes de construction d'un moteur 3D

    La première étape lors de l'élaboration d'un moteur 3D est le développement de deux objets " Point "; ce sont les types de données assez simples avec lesquels nous pourrons en créer de plus complexes. Un point en deux dimensions possède deux coordonnées (x,y) (ce sont ses propriétés) et un point en trois dimensions possède trois coordonnées (x,y,z). Nous avons franchi la première étape vers la réalisation d'un moteur 3D fonctionnel.

class point_2D
{
    public:
        // float: to accept non integer values during rotations
        float x, y;

        // functions
        inline void setcoords(float fx, float fy) { x = fx; y = fy; }
        ~point_2D() { x=0;y=0; }
};


class point_3D
{
    public:
        // float: to accept non integer values during rotations
        float x, y,z;

        // functions
        inline void setcoords(float fx, float fy, float fz) { x = fx; y = fy; z=fz; }
        ~point_3D() { x=0;y=0;z=0; }
};


(Note: Le code source est entièrement en anglais)

    Deuxièmement, en liant deux points ensemble (2D ou 3D), nous obtenons un segment de droite. Les segments de droites ne sont pas importants dans la programmation d'un moteur 3D, mais les faces le sont. C'est ici que l'utilité de la programmation orientée objet apparaît. Nous voulons regrouper nos points en face : nous n'avons qu'à définir un objet " Face " composé d'une quantité indéfinie d'objets " Point ". Auparavant, il nous aurait fallu définir une face comme étant un ensemble de coordonnées. C'est pourquoi la programmation orientée objet aide à simplifier la conceptualisation du logiciel.


class face
{
    public:
        // variables
        int * points;
        int nbofptsinface;

        // functions
        void set(int inputnb, int * inputarray);

        // clear memory
        ~face() { delete [] points; points=0; nbofptsinface=0; }
};

// defines all the points in a face in one command
void face::set(int inputnb, int * inputarray)
{
    int i;
    nbofptsinface = inputnb;
    points = new int[nbofptsinface];

    // define every point
    for(i = 0; i     {
        points[i] = inputarray[i];
    }
};


    Prochaine étape dans cette démarche logique : un objet " Forme " regroupant une quantité indéfinie d'objets " Face ". Pour ce qui suit, nous allons utiliser comme exemple le cube pour représenter les différentes propriétés et méthodes de l'objet " Forme ". Un cube, 6 faces, 8 points. Sans planification, nous aurions pu définir notre cube comme étant 6 faces de 4 points chacune mais nous triplerions le nombre de points qui devraient être emmagasinés dans la mémoire de l'ordinateur. C'est pourquoi nous allons définir une liste de huit points qui seront utilisés par les faces. Nous définirons nos faces pour que leurs points soient tirés de cette liste. Cette idée très économique nous vient d'un petit guide sur le 3D accompagnant Allegro, notre librairie de fonctions graphiques.

    Nous venons de définir une liste de points que nous nommons la liste " Locale ". Ces points sont du type 3D puisqu'un cube est un objet tridimensionnel. Par défaut, nous choisissons de définir le point (0,0,0) comme étant le centre de l'objet " Forme ". Nous définissons tous les points de la liste " Locale " à partir du centre de l'objet. Par exemple, pour un cube d'arête 2 nous aurions la liste " Locale " suivante :
{ (-1,-1,-1), (-1,-1,1), (-1,1,-1), (-1,1,1), (1,-1,-1), (1,-1,1), (1,1,-1), (1,1,1) }

    Plaçons ce cube dans un monde virtuel. Il est centré à l'origine avec ces coordonnées. Imaginons que nous voulions le déplacer : nous devrions déplacer chacun des points individuellement. Nous avons créé une deuxième liste nommée " Monde " pour signifier la position de l'objet dans le monde. Définissons une propriété de l'objet " Forme " : ses coordonnées dans le monde virtuel (un objet " Point " de trois dimensions). De plus, nous devons ajouter une méthode qui applique une translation sur les coordonnées locales pour créer les coordonnées dans le monde. Par ailleurs, une autre propriété, un facteur de grossissement, est ajoutée à l'objet et utilisée dans la méthode. Maintenant, nous n'avons qu'à définir un objet " Forme ", indiquer son centre avec un objet " Point " 3D, décider du facteur de grossissement que nous voulons lui donner et appliquer la méthode de l'objet " Forme " qui crée la liste " Monde " à partir de la liste " Locale ". Ainsi, nous avons un objet n'importe où dans notre monde tridimensionnel et nous pouvons le déplacer en déplaçant son centre et recalculant la liste " Monde ".

    Ce n'est pas terminé, nous abordons le cœur d'un moteur 3D : les rotations en trois dimensions. Pour cela, nous devons définir une troisième liste " Tournée " et l'angle selon lequel nous voulons tourner autour de chacun des trois axes (x,y,z). Nous devons appliquer le même principe que précédemment. Il faut définir une méthode qui convertit la liste " Monde " en liste " Tournée " en utilisant les nouvelles propriétés que nous avons ajoutées à notre objet " Forme ". Cette méthode appliquera la rotation selon les trois axes. Veuillez noter que les équations pour les rotations autour de ces axes sont démontrées ici.

    Jusqu'à maintenant, nous avons manipulé nos points dans notre monde virtuel, mais nous devons les afficher sur l'écran. Pour ce faire, nous devons créer une nouvelle liste " Écran " faite de points en deux dimensions. En effet, notre monde est tridimensionnel, mais l'écran est un plan (deux dimensions). Avec cette liste vient une nouvelle méthode pour convertir les points de trois dimensions à deux (voir ici pour la démonstration). Puisque cette opération est à sens unique, chaque fois que nous voulons afficher notre objet à l'écran, nous devons créer la liste " Monde ", ensuite la liste " Tournée " et finalement la liste " Écran ". Nous ne pourrions pas deviner les coordonnées en trois dimensions de chacun des points à partir de leur projection sur l'écran.

    Nous sommes maintenant à un point qui dépend du langage de programmation : l'affichage. Dépendamment du langage, la programmation risque d'être très différente, mais tout revient à prendre chacune des faces individuellement et d'en dessiner les contours. Pour dessiner les contours, il faut simplement tracer une ligne entre chacun des points bidimensionnels consécutifs. Ceci dessine l'ossature de la forme à l'écran. La perspective est plus difficile à voir puisque les faces ne sont pas remplies et nous voyons à travers l'objet.


for (j=0;j     {
    // display border lines
    x1=(int) screenlist[ facelist[i].points[j] ].x;
    y1=(int) screenlist[ facelist[i].points[j] ].y;

    if (j==facelist[i].nbofptsinface-1)
        {
        x2=(int) screenlist[ facelist[i].points[0] ].x;
        y2=(int) screenlist[ facelist[i].points[0] ].y;
        }
    else
        {
        x2=(int) screenlist[ facelist[i].points[j+1] ].x;
        y2=(int) screenlist[ facelist[i].points[j+1] ].y;
        }

    line(bitmap, (int) x1, (int) y1, (int) x2, (int) y2, myColor);
    }



    À cette étape, nous pouvions faire bouger un seul cube dans notre monde virtuel, mais nous devons complexifier la situation. C'est pourquoi nous avons créé une sphère pour représenter des atomes. Il est évident qu'en général, on associe plus un atome à une sphère qu'un cube. Par contre, nous ne voulions pas réaliser une vraie sphère puisqu'en trois dimensions elle ressemble toujours à un cercle lorsque projetée sur l'écran. Pour qu'elle paraisse en trois dimensions, il faut quelle soit ombragée. Par manque de temps, nous avons décidé de ne pas essayer d'ajouter des ombres ou des textures aux objets dans notre logiciel. Nous avons créé une pseudo-sphère qui ressemble à une sphère, mais qui réellement est un agencement de 79 triangles. De plus, notre but premier était de créer une molécule et une molécule compte plus d'un atome, c'est pourquoi nous avons créé un objet " Molécule " qui est un regroupement d'une quantité indéfinie d'objets " Forme ". Pour dessiner les liens entre les atomes, nous n'avons qu'à lier les centres des " Forme "; ceux-ci sont définis dans la liste " Écran ".




(Image prise du logiciel 1: CH4 dans la version 1)


    Ensuite, nous avons décidé de représenter quelques molécules simples pour essayer notre logiciel (CH4, C2H4, C2H2) et d'embellir le tout en ajoutant de la couleur. Par contre, le plus impressionnant est le passage de l'ossature à l'affichage d'un vrai objet opaque. Pour ce faire, nous devons déterminer si chacune des faces a une normale qui pointe vers l'observateur ou si elle pointe vers le fond de l'écran. En prenant les trois premiers points de chacune des faces et en utilisant la formule 7 (tri des faces cachées), nous pouvons le déterminer. Pour que cette équation fonctionne, nous devons définir toutes nos faces de sorte que les points soient définis dans un ordre horaire, si la face est observée de l'extérieur de l'objet. Nous voyons donc les faces de l'objet " Forme " ne sont pas définies aléatoirement. De plus, nous ne pouvons nous arrêter ici puisque nous ne contrôlons pas l'ordre d'affichage des formes. Sans le faire, nous ne pouvons pas être certains que les atomes les plus près de l'observateur soient affichés après ceux qui sont plus loin. Si ceci ne se produit pas, la projection de ces formes sur l'écran ne sera pas réaliste : certains atomes plus près seront coupés par des atomes plus loin.

    Et voilà ! Nous avons maintenant créé un moteur 3D simple, mais efficace. Notre but personnel était de comprendre le développement que nous venons de vous expliquer brièvement et les équations mathématiques issues de cette démarche. Nous ne voulions pas appliquer les équations sans les comprendre, comme le font la plupart des programmeurs aujourd'hui. En effet, un programmeur peut réaliser un logiciel comparable sans avoir de connaissances mathématiques avancées. Cependant, il apprend peu à réaliser un tel projet. Par ailleurs, plusieurs choses pourraient être ajoutées à cette étape : éclairage, texture, etc. Par contre, ceci dépasserait le cadre de notre projet et des quinze semaines qui nous ont été allouées.




(Image prise du logiciel 2 : C6H12 (bateau) dans la version 7)



Types d'erreurs de programmation
Les types d'erreurs sont définis pour aider la compréhension de l'application de la démarche scientifique en programmation.

    Puisque personne n'est parfait, il est fort probable qu'un programmeur fera plusieurs erreurs lors de la construction d'un logiciel. C'est un procédé naturel et inévitable. Habituellement, les erreurs sont inoffensives : le logiciel ne démarre pas ou s'arrête subitement en cours d'exécution. Par contre, certaines erreurs sont plus graves et causent un arrêt total du système. Un bon programmeur doit pouvoir prévenir les erreurs éventuelles et savoir les repérer et les corriger lorsqu'elles se produisent.

Erreurs de syntaxe :
    Ce type d'erreur est le plus simple de tous. Chaque langage a des règles de syntaxes strictes et lorsqu'elles ne sont pas respectées le logiciel ne peut être compilé (transformé du code source vers une forme utilisable). Par exemple, si une commande doit être utilisée comme ceci :
        Retour();

    Une erreur de syntaxe sera causée si le programmeur écrit ceci :
        Retoru() ;

    Ainsi, les erreurs de syntaxes sont habituellement causées par des erreurs de frappe, d'inattention ou par une mauvaise connaissance du langage de programmation. Ces erreurs sont relevées par le compilateur (le logiciel qui transforme le code source en forme utilisable) et sont donc faciles à trouver et corriger.

Erreurs logiques :
    Les erreurs logiques sont plus difficiles à repérer. En effet, nous déduisons leur existence lorsque notre logiciel se comporte de façon inattendue. Voici un exemple ou un programmeur voudrait faire une action seulement si la valeur d'une variable est plus petite ou égale à zéro:
        Si ( monChiffre est plus petit que 0 ) et ( monChiffre égale 0 )
            Retour();

    Par contre, le programmeur remarquera que son action ne sera jamais produite. C'est une erreur logique puisque l'opérateur " ET " en programmation nécessite que les deux termes comparés avec ET soient vrais. Dans notre exemple, un chiffre ne peut jamais être égal à 0 en même temps qu'être plus petit que 0. C'est pourquoi il faut utiliser l'opérateur " OU ".

        Si ( monChiffre est plus petit que 0 ) ou ( monChiffre égale 0 )
            Retour();

    Habituellement, les erreurs logiques sont plus complexes, mais cet exemple devrait aider à comprendre que ce sont des erreurs ou le programmeur a mal réfléchi lorsqu'il programmait.

Erreurs de conceptualisation :
    Ce type d'erreur est le pire et le plus difficile à corriger. Une erreur de conceptualisation est une erreur causée par une mauvaise planification du programmeur au moment de la conception du logiciel (avant même qu'il le crée). Ce type d'erreur est semblable aux problèmes qu'envisageaient les programmeurs à l'arrivée de l'an 2000. Certains programmeurs des années 80 ont défini un objet " Date " en n'utilisant que deux chiffres pour représenter l'année. Ils ont vite compris que les années demandaient quatre chiffres puisque le passage de 1999 à 2000 aurait signifié un passage de 1999 à 1900 pour les ordinateurs.



Application de la démarche scientifique dans la conception de logiciels (cas général)

Problématique :
    Un logiciel est requis pour réaliser une certaine tâche.

Hypothèse :
    Le programmeur peut créer un logiciel satisfaisant aux exigences.

Méthode :
    Le programmeur doit d'abord créer un schéma du logiciel qui résoudra la problématique. Celui-ci ne doit pas trop entrer dans les détails : il doit être bref et concis sans toutefois être trop vague.

    Ensuite, il doit appliquer la démarche qu'il a conçue en programmant son logiciel dans le langage de son choix. À chaque étape de la programmation, il teste le logiciel et vérifie que tout fonctionne normalement. Il ne peut procéder à la prochaine étape que si aucune erreur se présente.

Résultat :
    Lorsque terminé, le logiciel fonctionne comme voulu. Si nécessaire, certaines corrections qui n'ont pas été planifiées lors de la conception sont appliquées.

Conclusion :
    Notre hypothèse est confirmée, nous avons été capables de créer le logiciel désiré.



Application de la démarche scientifique dans la résolution d'erreurs logiques (cas spécifique)

Problématique :
    Quelque chose d'inattendu se produit dans notre logiciel.

Hypothèse :
    Nous pouvons localiser cette erreur et la corriger.

Méthode :
    Dans une situation comme dans l'exemple d'erreur de logique précédent où l'action ne se produit jamais, nous irions voir quelles conditions nous avons indiquées pour qu'elle se produise.

    Si nous remarquons l'erreur d'un coup d'œil, nous la corrigeons. Sinon, nous devons essayer de comprendre pourquoi l'action ne se produit pas. Nous pourrions commencer par afficher à l'écran la valeur de la variable monChiffre avant d'évaluer si il est inférieur ou égal à zéro. Ici, nous remarquerions que même si le chiffre est négatif ou égal à zéro, rien ne se produit. Nous déduirions donc que l'erreur se trouve dans la ligne qui débute par " Si ". En analysant cette ligne, nous comprendrions l'erreur et la corrigerions.

Résultat :
    L'erreur est corrigée et le logiciel fonctionne comme voulu.

Conclusion :
    Notre hypothèse est confirmée, nous avons été capables de corriger l'erreur.



Note des auteurs - Introduction - Le logiciel
Les mathématiques derrière le projet - Discussion





Jason  
( 2001-05-23 )  




Lisez cet article en anglais




Back



Français - English