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 :
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 :
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 :
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 :
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
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é
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 :
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
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.

