G2D


To translate this course in your native langage:

  • In Chrome: right-click anywhere on the page and select “Translate to…”

  • Or use this language selector:

Les mots colorés en anglais représentent des mots-clés de la norme du langage C++. Ils sont conservés à l’identique lors de l’utilisation d’une traduction automatique avec Google Translate. Exemple : l’héritage (inheritance) est un principe fondamental de C++.


SDL3 — (Simple DirectMedia Layer 3) est une bibliothèque C++ multiplateforme pour gérer les fenêtres, les entrées, l’audio et le rendu à l’écran. Elle sert de couche d’abstraction entre l’application, le système d’exploitation et le matériel. Ce n’est pas un moteur de jeu, mais une base bas niveau pour créer des applications interactives.

Nous allons présenter l’architecture de la librairie G2D.

G2D demo

Nous avons écrit une surcouche de SDL3 : G2D (Graphics for 2D) pour faciliter la prise en main de la SDL. La librairie G2D fournit 7 classes et une trentaine de fonctions convrant 95% des besoins d’un projet graphique en 2D. Nous allons voir comment est construite une application G2D.

Téléchargez le projet à partir du lien reçu par mail. Décompressez le sur votre disque et ouvrez le répertoire du projet :

../_images/rep.png

Vous trouvez plusieurs répertoires et fichiers C++ :

  • font: répertoire contenant les fichiers de police texte

  • G2D : contient les sources de la librairie G2D, vous ne devez pas modifier les fichiers dans ce répertoire

  • BUILD : contient les fichiers compilés du projet.

  • SDL3import : contient les sources de la librairie SDL3, vous ne devez pas toucher à ce répertoire

  • ìmages : répertoire contenant les images et les icônes utilisées dans le projet. Ajoutez vos nouvelles images dans ce répertoire.

  • Color.h, Event.h, Font.h, Graphic.h, Model.h et V2.h les headers principaux des classes fournies par G2D

  • student.cpp le fichier dans lequel vous allez commencer à ajouter du code

  • PICTOR.sln et PICTOR.vcxproj les fichiers de projet Visual Studio

Note

Le répertoire BUILD peut être supprimé avant de transmettre votre projet car il occupe généralement beaucoup de place.

Solution explorer

Double-cliquez sur PICTOR.sln ou PICTOR.vcxproj pour ouvrir le projet de base dans Visual.

Si le Solution Explorer de Visual Studio n’est pas ouvert, appuyez sur les touches CTRL ALT L. Dépliez le projet PICTOR pour voir la hiérarchie du projet :

../_images/pictor.png

Pour éditer un fichier, double-cliquez tout simplement sur les noms des fichiers .h ou .cpp.

Remarquez que la hiérarchie du projet correspond à sa structure sur le disque.

Student.cpp

Appuyez sur la touche F5 pour lancer le projet, une démo affiche le résultat suivant :

../_images/demo.png

Plusieurs formes apparaissent à l’écran. Remarquez qu’en déplaçant la souris, la mire rouge bouge en même temps.

Editez ce fichier et examinez la fonction drawApp(), elle contient tous les appels de fonctions pour dessiner cette démo :

void drawApp(Graphics& G, const Model & D)
{
    G.clearWindow(Color::Black);

    // Monospaced font  = all characters have the same width
    Font F1 = { FontFolder::LiberationMono, 25, false, false, Color::Yellow };
    G.drawString(F1, V2(90, 420), "Liberation font");
    G.drawString(F1, V2(90, 370), "Bold+Italic");
    G.drawString(F1, V2(90, 320), "Italic");
    G.drawString(F1, V2(90, 270), "Bold");
    G.drawString(F1, V2(90, 220), "Regular");
    ...

    // drawing a button background with a texture
    G.fillRectangle(V2(530, 300), V2(64, 64), Color::Gray);
    G.drawRectWithTexture("64_ellipsetool.png", V2(530, 300), V2(64,64),0);

    // draw geometric lines
    G.drawLine(V2(670, 500), V2(760, 590), Color::Red, 1);
    G.drawLine(V2(870, 500), V2(960, 590), Color::Red, 5);

    G.drawCircle(V2(730, 400), 60, Color::Cyan, 1);
    G.drawRectangle(V2(670, 220), V2(120, 80), Color::Yellow, 3);

    G.fillCircle(V2(920, 400), 50, Color::Beige);
    G.fillRectangle(V2(870, 220), V2(120, 80), Color::Magenta);


    // draw moving cursor
    V2 P = D.currentMousePos;
    int r = 7;
    Color c = Color::Red;
    G.drawLine(P + V2( r, 0), P + V2(-r, 0), c);
    G.drawLine(P + V2( 0,-r), P + V2( 0, r), c);
}

Cette fonction reçoit un objet Graphics G permettant d’effectuer différentes opérations de dessin à l’écran.

Classes

V2

Dans V2.h nous trouvons la classe V2 servant à représenter les points et les vecteurs 2D. L’addition et la soustraction entre V2 sont fournies, ainsi que la multiplication/division par un numérique. On peut remarquer que les membres des V2 sont des valeurs entières nommées x et y.

struct V2
{
    int x, y;
    ...
};

Deux fonctions utilitaires sont fournies :

  • getPWH() qui à partir de deux points \(P_1\) et \(P_2\) retourne le coin bas-gauche \(P\) et la largeur hauteur du rectangle associé :

    ../_images/PLH2.jpg
  • isInside() qui détermine si un point appartient au rectangle défini par son coin bas-gauche et sa largeur hauteur :

    ../_images/PLH.jpg

Color

Dans Color.h, nous trouvons la classe Color servant à décrire une couleur :

struct Color
{
    float R, G, B, A;   // 0<= R,G,B,A <= 1
    ...
};

Les trois premières composantes (rouge, vert, bleu) décrivent la couleur, ces valeurs sont comprises entre 0 et 1, 0 = absence de lumière et 1 = maximum d’intensité. Ainsi (1,1,1) correspond au blanc, (1,0,0) à un rouge saturé et (0,0,0) au noir. Le 4ème paramètre correspond à la transparence : 1 = opaque, 0 = transparent.

A noter que toute une liste de couleurs prédéfinies est disponible :

  • Transparent : aucune couleur, le pixel est invisible

  • Black & White

  • 21 couleurs comportant chacune 2 variantes : Light plus claire et dark plus sombre.

Voici la liste exhaustive :

const Color Transparent,
Black,    White,                         Gray,          GrayLight,     GrayDark,
Red,      RedLight,   RedDark,           Green,         GreenLight,    GreenDark,
Blue,     BlueLight,  BlueDark,          Yellow,        YellowLight,   YellowDark,
Cyan,     CyanLight,  CyanDark,          Magenta,       MagentaLight,  MagentaDark,
Orange,   OrangeLight,OrangeDark,        Purple,        PurpleLight,   PurpleDark,
Brown,    BrownLight, BrownDark,         Pink,          PinkLight,     PinkDark,
Lime,     LimeLight,  LimeDark,          Teal,          TealLight,     TealDark,
Navy,     NavyLight,  NavyDark,          Maroon,        MaroonLight,   MaroonDark,
Olive,    OliveLight, OliveDark,         Silver,        SilverLight,   SilverDark,
Gold,     GoldLight,  GoldDark,          Beige,         BeigeLight,    BeigeDark,
Cream,    CreamLight, CreamDark,         Violet,        VioletLight,   VioletDark;

Pour utiliser une de ces couleurs prédéfinies, il suffit d’écrire Color::MaroonLight dans le code.

Deux fonctions utilitaires sont fournies pour créer un objet Color à partir des codes hexadécimaux x12AC44 ou des codes RGB (111,222,45) que vous trouverez sur le net.

Color ColorFrom255(int r, int g, int b);
Color ColorFromHex(int hexCode);

Font

Dans Font.h, nous trouvons la classe Font permettant de décrire une police texte à partir de différents paramètres :

struct FontName
{
    string name;
    int    size;
    bool   bold;
    bool   italic;
    Color  color = Color::Black;
};

Le nom doit correspondre aux fichiers de fonts présents dans le répertoire font du projet. Pour éviter toute erreur de frappe, deux constantes ont été définies pour les deux fonts déjà présentes dans la librairie G2D:

namespace FontName
{
    const string LiberationMono = "LiberationMono";
    const string Tinos = "Tinos";
};

On peut les utiliser en écrivant FontName::Tinos dans le code.

Graphics

Dans Graphics.h, nous trouvons la classe Graphics permettant d’effectuer plusieurs opérations de dessin à l’écran. On peut les regrouper en plusieurs catégories :

  • Information : la fonction getWindowSize() retourne la taille en pixel de la zone de tracé

  • Initialization : la fonction clearWindow() efface la zone de tracé

  • Texte : la fonction drawString() affiche du texte à l’écran et getTextSize() retourne la taille d’un rectangle englobant sur ce texte

  • drawXXX : fonction de tracé des objets non pleins : segment, rectangle, cercle, ligne polygonale

  • fillXXX : fonction de tracé des objets pleins : rectangle plein, cercle plein, polygone convexe plein

  • Image : la fonction drawRectWithTexture() affiche une image à l’écran dans une zone rectangulaire donnée

Note

Pour afficher une image avec la fonction drawRectWithTexture(), il faut mettre le fichier png ou jpg dans le répertoire images du projet. Donnez le nom du fichier avec l’extension sans rien d’autre.

Les coordonnées écran correspondent à celles du cours de géométrie dans le plan :

  • L’origine (0, 0) est positionnée en bas à gauche

  • L’axe des x est orienté vers la droite

  • L’axe des y est orienté vers le haut

../_images/reperes.png

Todo

Dessinez :

  • Des rectangles imbriqués traversés par les deux mêmes diagonales

  • Des cercles pleins imbriqués avec des couleurs différentes

  • Sur un fond gris foncé

../_images/dessin.png

Architecture MLR

L’architecture de notre application est organisée autour de trois éléments distincts :

  • Le Modèle qui contient l’état (les données) de l’application

  • La Logique qui traite les actions de l’utilisateur et modifie le modèle

  • Le Rendu qui utilise le modèle pour produire l’affichage

Cette architecture est une version simplifiée de l’architecture MVU (Model View Update).

Le Modèle

Le Modèle contient l’ensemble des données décrivant l’état courant de l’application.

Il peut par exemple contenir :

  • les objets présents dans la scène

  • l’outil actuellement sélectionné

  • la couleur courante

  • la position de certains éléments

  • l’état d’une sélection

Avertissement

Le modèle ne réalise ni traitement graphique ni gestion des événements.

On peut le considérer comme la mémoire de l’application. Dans G2D, toutes les données sont stockées dans la structure Model présente dans le fichier Model.h.

Avertissement

Vous ne devez pas instancier cette structure, elle est instanciée par G2D et ensuite l’objet Model est transmis à toutes les fonctions.

La Logique

La Logique est la partie du programme qui reçoit les événements provenant de l’utilisateur : clavier, souris, déplacement du curseur, clic. Elle interprète ces événements puis modifie le modèle en conséquence.

Par exemple :

clic souris
    |
    v
fonction de logique
    |
    v
création d'un rectangle
    |
    v
modification du modèle

La logique peut donc :

  • ajouter ou supprimer un objet

  • modifier une position

  • changer l’outil actif

  • modifier une couleur

  • effectuer une sélection

Avertissement

La règle importante est la suivante : la logique modifie le modèle, mais ne réalise aucun affichage.

Le Rendu

Le Rendu est la partie du programme responsable de l’affichage. Il consulte les données contenues dans le modèle et dessine la représentation graphique correspondante.

Avertissement

Le rendu doit uniquement lire le modèle, il ne le modifie JAMAIS.

Avertissement

Le rendu part de zéro à chaque fois. La zone d’affichage est effacée et les objets sont dessinés un à un. Il n’y a pas réutilisation du tracé précédent.

Résumé

L’architecture complète peut être représentée ainsi :

../_images/flux.png

On parle de flux de données unidirectionnel.

A retenir :

La logique écrit dans le modèle, le rendu lit le modèle. La logique ne dessine pas et le rendu ne modifie pas le modèle.

Avertissement

Lors du codage de votre projet, il faudra adopter les réflexes suivants : ne pas faire d’affichage lorsque vous traitez un évènement. Cela vous semblera au début illogique.

Pourquoi cette séparation ? Pour plusieurs raisons :

  • Un déplacement de la souris peut générer de nombreux événements alors que le rendu n’est effectué que quelques fois. Les deux traitements ne s’exécutent donc pas nécessairement à la même fréquence.

  • Si on pouvait faire les affichages n’importe où dans le code, très rapidement, on en verrait fleurir un peu partout, et à la fin, il serait difficile de savoir dans quel ordre ils sont réellement effectués, ce qui produira de nombreux bugs.

MLR inside G2D

Model

Vous remarquez que la classe Model dans le fichier Model.h est relativement vide. Ce sera à vous de trouver les données centrales de l’application et de les créer à cet endroit. Comment les reconnaître :

  • Vous en avez besoin pour effectuer une action : la position de la souris, la couleur du rectangle, la police du texte, l’outil de tracé courant,

  • Ce ne sont pas des données locales ou des calculs intermédiaires

  • Le rendu doit effectuer l’affichage, pour cela, il doit connaître les objets à dessiner…

Event - Logic

Chaque action de l’utilisateur envoie un évènement Event à la fonction processEvent(const Event& Ev, Model & Data) contenue dans student.cpp. Cette fonction est le point d’entrée de tous vos traitements concernant la LOGIQUE de l’application. Nous aurions pu faire une hiérarchie de classes pour modéliser les différents évènements, mais il en existe plus de 200 et ce n’est donc pas l’approche retenue. Nous avons choisi de faire une structure unique qui inclut

  • Le type de l’évènement

  • Deux paramètres entiers qui stockent des informations numériques ayant un sens particulier en fonction du type de l’évènement

  • Un champ Key qui indique la touche concernée pour les entrées clavier

EventType Type;
int       x, y;
Key       key;

Plusieurs fonctions utilitaires sont fournies :

  • isKeyboardEvent() : qui indique s’il s’agit d’un évènement clavier

  • isMouseEvent() : qui indique s’il s’agit d’un évènement souris

  • print() qui affiche toutes les infos de l’évènement

ToDo

  • Lancez l’application

  • Deux fenêtres s’ouvrent, la fenêtre graphique de l’application et une fenêtre texte de débogage

  • Mettez les fenêtres côte à côte

  • Effectuez diverses actions : clic souris, molette, touches du clavier et analysez les informations de chaque évènement

../_images/events.png

Rendu

Chaque demande d’affichage de l’OS déclenche un appel à la fonction drawApp(Graphics& G, const Model & D) contenue dans student.cpp. Cette fonction est le point d’entrée de tous vos affichages graphiques de l’application.