L'utilisation des QGraphicsScene

Julien Chichignoud

Dans ce tutoriel, on va voir comment utiliser la classe QGraphicsItem, qui permet de façon générale d'intégrer des objets dans une scène 2D mais aussi de leur donner un aspect 3D, moyennant quelques astuces. Attention toutefois, ces outils de Qt ne sont pas non plus conçus pour afficher des scènes en 3D, ils ont simplement une bonne souplesse qui permet de réaliser de belles interfaces assez évoluées.

On abordera donc l'intégration d'objets dans une scène en deux dimensions grâce à Qt, avant de montrer comment simuler la profondeur afin de donner une impression de 3D à ces objets.

Commentez

- Titre : L'utilisation des QGraphicsScene

- Auteur : Julien Chichignoud

- Parution : 23 mars 2011

- Mise à jour : 30 mai 2012

- Licence : Copyright ® 2012 Julien Chichignoud. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Insérer des widgets dans une scène

- II-A. Quelques classes utiles

- II-B. Déplacer les objets dans la scène

- II-B-1. Les coordonnées

- II-B-2. Translation

- II-B-3. Rotation

- II-B-4. Mise à l'échelle

- II-B-5. Conclusion

- III. Les transformations avec QTransform

- III-A. Utiliser un QTransform avec un QGraphicsItem

- III-B. Composer les matrices

- III-B-1. Un outil pratique

- III-B-2. Une question d'ordre

- IV. Des widgets en 3D

- IV-A. Rotation autour de l'axe Y

- IV-B. Implémenter le déplacement

- V. Pour aller plus loin : gestion de la caméra

- V-A. La caméra : reprenons à l'envers !

- V-B. Créer une caméra

- VI. Conclusions

- VII. Remerciements

I - Introduction

Dans ce tutoriel, on va voir comment utiliser la classe QGraphicsItem, qui permet de façon générale d'intégrer des objets dans une scène 2D mais aussi de leur donner un aspect 3D, moyennant quelques astuces. Attention toutefois, ces outils de Qt ne sont pas non plus conçus pour afficher des scènes en 3D, ils ont simplement une bonne souplesse qui permet de réaliser de belles interfaces assez évoluées.

On abordera donc l'intégration d'objets dans une scène en deux dimensions grâce à Qt, avant de montrer comment simuler la profondeur afin de donner une impression de 3D à ces objets.

II - Insérer des widgets dans une scène

II-A - Quelques classes utiles

Afin de visualiser des widgets (et plus généralement des objets graphiques) dans une scène, il faut commencer par en créer une qui va contenir tout ce beau monde. La classe fournie par Qt s'appelle sobrement QGraphicsScene et permet d'insérer des QGraphicsItem. La classe abstraite QGraphicsItem représente tout ce qui est affichable dans une QGraphicsScene. On trouve notamment un nombre important de classes permettant de gérer des polygones, des textes, des QPixmap, des widgets…

La classe QGraphicsProxyWidget, héritant de QGraphicsItem, est particulièrement adaptée à l'insertion des widgets dans une QGraphicsScene. Il suffit de créer votre QGraphicsProxyWidget, de lui attacher le widget que vous désirez intégrer à la scène puis d'ajouter le proxy en tant qu'objet. Vous manipulez donc votre widget à travers le proxy, tout ceci étant transparent une fois que le proxy et le widget sont attachés. Il est de plus tout à fait possible d'intégrer des widgets complexes, comme une QTabWidget, qui contient elle-même d'autres widgets.

Dans les exemples, j'appliquerai les notions de ce tutoriel à des widgets, car c'est leur intégration qui m'a poussé à utiliser ces classes. Cependant, les méthodes utilisées sont pour la plupart héritées de QGraphicsItem, on peut donc les adapter à d'autres objets.

Enfin, comme la scène n'est pas un widget mais un simple conteneur, il faut aussi créer une vue avec la classe QGraphicsView, qui permet d'afficher le contenu de la scène. Sans plus attendre, on commence par créer la première scène, avec un bouton au milieu.

#include <QApplication>
#include <QtGui>

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QPushButton *bouton = new QPushButton("Mon bouton entre en scène !");
QGraphicsScene scene;
QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget();
proxy->setWidget(bouton);
scene.addItem(proxy);

QGraphicsView view(&scene);
view.show();

return app.exec();
}

Résultat.

Ce code devrait afficher un bouton au milieu d'un écran blanc. Vous venez d'intégrer un premier widget dans une QGraphicsScene.

II-B - Déplacer les objets dans la scène

Une fois le widget intégré à la scène à travers le proxy, Qt offre un grand nombre de méthodes pour modifier son positionnement.

II-B-1 - Les coordonnées

Si les systèmes de coordonnées 2D ne vous sont pas familiers, sachez que l'axe X est l'axe horizontal orienté vers la droite et que l'axe Y est l'axe vertical orienté vers le bas. A priori, si vous avez déjà un peu manipulé Qt ou la SDL par exemple, vous devriez vous y retrouver.

Il faut de plus savoir que la vue, la scène et les items ont chacun leur propre système de coordonnées. Ainsi, le point (0, 0) qui est le point en haut à gauche de la vue dans son propre système aura des coordonnées différentes dans le système de coordonnées de la scène. Ceci se modélise par le schéma suivant, issu de la documentation, et dans lequel le repère de la vue est en vert, la scène est en bleu et l'item en rouge.

Image issue de la documentation officielle Qt (http://doc.qt.nokia.com/4.7/graphicsview.html).

Afin de simplifier les conversions d'un système de coordonnées à un autre, Qt fournit des méthodes mapToScene() et mapFromScene() dans les classes QGraphicsView et QGraphicsItem qui permettent de retrouver les coordonnées d'un point dans la scène à partir des coordonnées dans la vue ou l'item et inversement.

II-B-2 - Translation

La fonction setPos() de QGraphicsItem prend deux paramètres, dx et dy, qui sont les déplacements relatifs respectivement sur l'axe X et l'axe Y de la scène, paramètres qui deviendront alors les nouvelles coordonnées de notre objet. À savoir qu'une méthode surchargée existe, prenant en paramètre un QPointF. À vous de voir laquelle vous préférez. Il est aussi possible d'appeler la fonction move(), qui ajoute le déplacement sur les deux axes à la position actuelle de l'objet. On essaie tout de suite de déplacer notre objet dans la scène :

#include <QApplication>
#include <QtGui>

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QPushButton *bouton = new QPushButton("Mon bouton entre en scène !");
QGraphicsScene scene;
QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget();
proxy->setWidget(bouton);
scene.addItem(proxy);

proxy->setPos(150, 200);

QGraphicsView view(&scene);
view.show();

return app.exec();
}

En exécutant ce code, on se rend compte que le widget ne s'est pas déplacé. En effet, c'est une des particularités de la QGraphicsView, qui s'arrange par défaut pour placer les objets au milieu de la vue.

Heureusement, il est possible de définir la zone de la scène que l'on désire voir à l'écran. Ajoutez cette ligne après la création de la scène.

scene.setSceneRect(-150, -150, 300, 300)

Les deux premiers nombres sont les coordonnées du point qui doit être placé dans le coin supérieur gauche de la vue (ici, (-150, -150)). Les deux nombres suivants sont la largeur et la hauteur de la scène que je désire afficher. Ainsi, comme le point (-150, -150) est le coin supérieur gauche de ma fenêtre, on retrouve l'origine de la scène au centre de l'écran. En relançant votre code avec et sans le déplacement du bouton, on voit cette fois-ci une différence de position.

Résultat.

II-B-3 - Rotation

Afin d'appliquer une rotation à l'objet, il suffit d'utiliser la méthode setRotation(), pour laquelle vous devrez fournir l'angle de rotation en degrés. Un angle positif tourne l'objet dans le sens des aiguilles d'une montre, un angle négatif le tourne dans le sens inverse.

Par exemple :

proxy->setRotation(45);

Résultat

II-B-4 - Mise à l'échelle

Enfin, la méthode setScale() permet d'augmenter ou de diminuer la taille prise par l'objet dans la scène. Un nombre supérieur à 1 agrandit l'image, tandis qu'un nombre compris entre 0 et 1 la réduit. Ci-dessous, on a pris un facteur de 2 pour agrandir le bouton, dont une partie sort maintenant de l'écran.

proxy->setScale(2);

Résultat

Cette fonction prend aussi en compte les nombres négatifs, ce qui permet d'effectuer une symétrie de l'image en plus de modifier sa taille. Avec un facteur -1 on obtient ainsi un bouton renversé.

Résultat.

II-B-5 - Conclusion

Ces opérations de base sont en gros le minimum pour pouvoir gérer vos objets dans la scène 2D.

On parle ici de scène 2D puisque l'on n'a parlé que de deux axes pour la translation et uniquement d'une rotation autour de l'axe perpendiculaire à l'écran. En effet, un QGraphicsItem est bien affiché en deux dimensions dans la scène et ne prend pas directement la profondeur en paramètre.

Avant de donner pour de bon de la profondeur à notre scène et de faire des manipulations 3D, il va falloir passer un peu de temps sur les matrices. Le calcul matriciel est un outil mathématique qui permet d'effectuer les manipulations de base en 3D et pour lequel Qt donne tous les outils nécessaires pour limiter les calculs à faire.

III - Les transformations avec QTransform

On a vu que les QGraphicsItem proposaient diverses méthodes pour déplacer, tourner ou agrandir les widgets insérés. Seulement, tout cela n'a pas vraiment l'air d'avoir la profondeur qu'on attend d'une scène 3D. Pour cela, on va utiliser une nouvelle classe de Qt, la classe QTransform.

Si vous avez travaillé un peu les mathématiques dans vos études supérieures, ce qui suit devrait vous être familier, dans le cas contraire, lisez attentivement la suite.

III-A - Utiliser un QTransform avec un QGraphicsItem

Un QTransform représente un objet mathématique appelé matrice, que l'on peut représenter dans notre cas comme un tableau comportant trois lignes et trois colonnes et contenant toutes les informations nécessaires pour effectuer des modifications sur les coordonnées d'un objet dans l'espace.

Voici comment on représente généralement une matrice :

L'édition d'un QTransform est très simple dans le code, puisque Qt s'occupe de toute la partie mathématique, en proposant des fonctions permettant de générer et modifier ces matrices.

Comme un exemple vaut mieux qu'un long discours, voici tout de suite la manière de réaliser une translation avec un QTransform.

#include <QApplication>
#include <QtGui>

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QPushButton *bouton = new QPushButton("Mon bouton entre en scène !");

QGraphicsScene scene;
scene.setSceneRect(-150 , -150, 300, 300);

QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget();
proxy->setWidget(bouton);
scene.addItem(proxy);

QTransform matrix;
matrix.translate(150, 200)
proxy->setTransform(matrix);

QGraphicsView view(&scene);
view.show();

return app.exec();
}

Comme précédemment, il a suffi de renseigner les valeurs de mon vecteur déplacement et Qt a généré tout seul la matrice correspondante.

Exécutez ce code qui doit vous afficher la même chose que tout à l'heure. Oui, en l'occurrence, le résultat est le même lorsque l'on regarde la fenêtre. Le widget s'est bien déplacé de 150 pixels vers la droite et de 200 pixels vers le bas.

De même, on peut générer des rotations et des mises à l'échelle à l'aide des méthodes rotate() et scale() de la classe QTransform.

L'avantage des QTransform par rapport à l'utilisation des méthodes précédentes est la possibilité de faire des transformations composées en une seule opération, plutôt que d'appliquer une méthode à la fois. En revanche, il n'est pas souhaitable de combiner l'utilisation des QTransform avec la méthode move() du QGraphicsItem par exemple, puisque les modifications effectuées par un QTransform ne sont récupérables que par la méthode transform() du QGraphicsItem modifié. Cela veut dire que les coordonnées de l'objet, auxquelles on pouvait accéder par QGraphicsItem::x() et QGraphicsItem::y(), n'ont pas été modifiées et valent toujours 0 !

QTransform matrix;
matrix.translate(150, 200);
proxy->setTransform(matrix);

matrix = QTransfrom().translate(proxy->x(), proxy->y())
proxy->setTransform(matrix);

En remplaçant le code précédent par ces lignes, on se rend compte que le widget n'a pas bougé de l'origine de la scène. En effet, la dernière transformation étant celle qui prend en compte les paramètres x() et y() du proxy, il est revenu à la position (0, 0) car l'application du QTransform ne les a pas modifiés. Il faut donc éviter de mélanger les outils et préférer celui-ci qui semble convenir au besoin.

Le QTransform a donc plutôt pour but de modifier la façon dont est affiché l'objet, que de modifier l'objet lui-même.

III-B - Composer les matrices

III-B-1 - Un outil pratique

Maintenant qu'on a vu comment utiliser les matrices avec les objets de la scène, voyons pourquoi cette nouvelle notion a été introduite : c'est un outil très puissant, souvent utilisé lorsque l'on travaille en trois dimensions.

Une matrice (et donc ici un QTransform) ne se limite pas à effectuer une seule opération à la fois. Elle peut en effet contenir les informations nécessaires pour effectuer à la fois une rotation, une translation et une mise à l'échelle ! Il ne suffira plus que d'appliquer la matrice à l'objet avec la méthodeQGraphicsItem::setTransform() et le tour est joué.

Mathématiquement, la composition de matrices s'obtient en les multipliant entre elles, pour donner une nouvelle matrice qui contient les deux transformations. Qt permet de faire ces transformations directement via des méthodes de la classe QTransform.

Par exemple, si on veut créer une matrice de rotation puis ajouter une translation, on écrit ceci :

QTransform matrix;
matrix.rotate(30, Qt::YAxis);
matrix.translate(20, 15);

Il est aussi possible d'utiliser directement la multiplication pour composer vos matrices. Cependant il faut faire attention à l'ordre des calculs. La matrice la plus à gauche dans la multiplication correspond à la transformation effectuée en dernier. Les QTransform tiennent compte de cela, l'utilisation des méthodes rotate() et translate() place la matrice paramètre au début du calcul et non à la fin. Par exemple, voici deux lignes de code qui effectuent le même calcul.

matrix.rotate(30, Qt::YAxis);
matrix = QTransform::rotate(30, Qt::Axis) * matrix;

Par conséquent, soyez encore plus prudent dans le choix de l'ordre des lignes de code.

Si vous utilisez les multiplications directement, placez vos matrices dans l'ordre inverse de celui dans lequel vous devez faire vos transformations. Si vous utilisez les méthodes fournies par Qt, vous devrez les écrire dans l'ordre normal, ce qui est plus intuitif.

Il suffit alors d'appliquer la matrice à l'objet situé dans la scène pour que ces deux transformations soient prises en compte.

III-B-2 - Une question d'ordre

Voyons maintenant pour quelle raison l'ordre des transformations est important et doit être bien choisi. Par exemple, les deux codes suivants donneront deux matrices (et donc deux transformations) différentes.

QTransform matrix;
matrix.translate(10, 10);
matrix.rotate(45, Qt::YAxis);

QTransform matrix;
matrix.rotate(45, Qt::YAxis);
matrix.translate(10, 10);

Avec le premier code, on a déplacé l'objet avant de le faire tourner autour de l'origine ; dans le second, on l'a déplacé après l'avoir fait tourner autour de l'origine.

Dans le premier cas, la transformation finale pourrait se schématiser ainsi :

L'objet a parcouru le chemin en pointillés et ne se trouve ainsi plus dans le plan (x, y) ! L'autre cas, en revanche, correspond bien à ce qu'on attend intuitivement, c'est-à-dire un objet situé dans le plan (x, y) mais qui a tourné sur lui-même.

Ainsi, pour orienter un objet dans la scène puis le placer, il faut effectuer d'abord la rotation suivant l'angle voulu, puis le déplacement. La seconde solution est donc celle vous devriez utiliser.

IV - Des widgets en 3D

IV-A - Rotation autour de l'axe Y

On va tout d'abord voir comment appliquer aux widgets des rotations autour de l'axe Y. Un petit tour dans la documentation apprend que la méthodeQTransform::rotate(), qui prend en paramètre un angle et un axe, correspond tout à fait à ce besoin.

Voyons ce que ça va donner avec un QWebView, par exemple (n'oubliez pas d'ajouter la ligne QT += webkit à votre fichier .pro).

main.cpp

#include <QApplication>
#include <QtGui>
#include <QWebView>

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QWebView *web = new QWebView();
web->load(QUrl("http://www.developpez.com"));
web->show();

QGraphicsScene scene;
scene.setSceneRect(0 , 0, 1000, 800);

QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget();
proxy->setWidget(web);
scene.addItem(proxy);

QTransform matrix;
matrix.rotate(45, Qt::YAxis);
proxy->setTransform(matrix);

QGraphicsView view(&scene);
view.show();

view.setWindowTitle("Ma première scène");
return app.exec();
}

Compilez ça et normalement vous obtenez une petite perspective sur votre widget, dont les fonctionnalités sont toujours utilisables ! Un QTextEdit continuera à être éditable dans l'espace, une QWebView continuera à charger les pages demandées, etc. Bien sûr, on peut se contenter de formes géométriques plus basiques, mais il est bon de savoir que cela fonctionne avec tous les widgets, il est donc possible d'afficher une page Web, par exemple.

IV-B - Implémenter le déplacement

Maintenant, on peut ajouter un peu d'interaction à la scène, en permettant à notre vision de bouger par rapport aux objets. Pour cela, on crée une classe spéciale, héritée de QGraphicsProxyWidget, qui va contenir les informations dont on souhaite garder la trace : orientation, position, échelle…

Voici l'en-tête de la classe proposée.

MyProxy.h

#include <QGraphicsProxyWidget>

class MyProxy : public QGraphicsProxyWidget
{
public:
MyProxy();

qreal rotationY();
void setRotationY(qreal rotation);

QPointF center();

private:
qreal m_rotationY; // enregistre la rotation autour de l'axe Y
QPointF m_center; // contient la position du centre du widget
};

Puis on crée une classe dérivée de QGraphicsScene pour gérer les événements de la souris. Je vous laisse créer la classe tout seul, il suffit de réimplémenter la méthode mouseMoveEvent(), appelée en cas de déplacement de la souris et de créer un bouton dans le constructeur que l'on attache à une instance de MyProxy placée en attribut de MyScene.

MyScene.cpp

MyScene::MyScene() : QGraphicsScene()
{
QPushButton *bouton = new QPushButton("Mon bouton entre en scène !");
m_proxy = new MyProxy();
m_proxy->setWidget(bouton);
this->addItem(m_proxy);
}

On va, par exemple, demander d'effectuer une rotation autour de l'axe Y lorsque la souris est déplacée latéralement et que le bouton gauche est enfoncé.

MyScene.cpp

void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent*e)
{
if(e->buttons() & Qt::LeftButton)
{
QPointF delta(e->scenePos() - e->lastScenePos());
qreal rotation = delta.x();
m_proxy->setRotationY(rotation + m_proxy->rotationY());

QTransform matrix;
matrix.rotate(m_proxy->rotationY(), Qt::YAxis);
m_proxy.setTransform(matrix);
}
}

Dans la fonction main il suffit maintenant de créer une instance de MyScene et de lui associer la vue.

main.cpp

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

MyScene scene;
scene.setSceneRect(-150, -150, 300, 300);

QGraphicsView view(&scene);
view.setWindowTitle("Ma première scène");
view.show();
return app.exec();
}

Maintenant, en déplaçant la souris avec le bouton gauche enfoncé, le widget devrait se mettre à tourner verticalement autour de l'origine de la scène. Cependant, cette origine coïncide avec le bord gauche du widget, celui-ci ne tourne donc pas sur lui-même.

Par conséquent, pour remédier à ce problème, il faut déplacer le widget pour que son centre se situe au point (0, 0) ! Une autre solution consisterait à utiliser la méthode QGraphicsItem::setTransformOriginPoint() et à envoyer le centre de l'item en paramètre.

On reprend la scène avec la QWebView. Le déplacement à effectuer est représenté par le schéma ci-dessous : pour que la rotation s'effectue par rapport au centre du widget, il faut ramener ce centre à l'origine de la scène. C'est donc le déplacement représenté par la flèche rouge qui va réaliser cela.

A priori, vous devriez savoir faire cela tout seul. Il suffit de composer notre matrice d'une translation supplémentaire pour déplacer notre widget vers la gauche et en haut, sur une longueur respective de la moitié de la largeur et la moitié de la hauteur (avec un signe négatif, car on va vers la gauche et vers le haut). Vous pouvez récupérer la taille de vos widgets avec les méthodes proxy->widget()->width() et proxy->widget()->height() et les ajouter comme attributs de votre classe MyProxy pour ne plus avoir à les rechercher. Voici donc le code de la rotation avec le recentrage du widget.

QTransform matrix;
matrix.rotate(m_proxy->rotationY(), Qt::YAxis);
matrix.translate(-m_proxy->widget()->width()/2, -m_proxy->widget()->height()/2);
m_proxy->setTransform(matrix);

Pensez à déplacer l'origine de la scène au centre de la fenêtre avec la méthode QGraphicsScene::setSceneRect() si ce n'est déjà fait, ou bien votre widget sera coupé par le bord de l'écran à cause de ce décalage.

scene.setSceneRect(-150, -150, 300, 300);

V - Pour aller plus loin : gestion de la caméra

V-A - La caméra : reprenons à l'envers !

On a déjà dit que l'ordre de composition des transformations était important. Ça le devient encore plus pour une caméra dans l'espace !

Prenons un exemple. Considérons la tourelle d'un tank dans la scène. Pour la placer correctement, vous allez devoir lui appliquer sa rotation par rapport au tank, puis la rotation du tank par rapport à la scène, puis enfin la translation qui la positionnera à l'endroit voulu.

Si maintenant vous voulez placer une caméra (votre point de vue) dans la scène, que se passe-t-il ? Dans ce cas, si vous tournez sur vous-même, vous voyez le monde 3D entier qui tourne autour de votre position. La rotation de la caméra doit donc être appliquée après sa translation.

Autant vous prévenir tout de suite, cet exemple ne vous permettra pas de tourner sur vous-même et de vous déplacer dans l'espace, mais plutôt de déplacer tous les objets de la scène d'un coup ou bien de tous les faire tourner devant vous. La construction des matrices utilisées dans les jeux vidéo, par exemple pour afficher correctement la scène devant la caméra, n'est pas évidente à faire à la main et dépasse le cadre de ce tutoriel.

Voyons donc tout de suite concrètement comment on peut donner l'impression que l'on se déplace dans la scène.

V-B - Créer une caméra

Déterminons tout de suite de quelles informations nous allons principalement avoir besoin pour notre caméra : position dans la scène et orientation, tout comme les objets de notre scène.

Camera.h

#include <QPointF>

class Camera
{
public:
Camera();

QPointF pos();
qreal zpos();
qreal rotationY();

void setPos(QPointF pos);
void setZPos(qreal pos);
void setRotationY(qreal angle);

private:
QPointF m_pos;
qreal m_zpos;
qreal m_rotationY;
MyScene *m_scene;
};

Vous remarquerez aussi que j'ai ajouté un attribut m_zpos à la caméra.

C'est en fait une astuce que je voulais vous montrer et qui permettra de donner l'impression que l'on déplace celle-ci vers l'avant où l'arrière. Nous verrons comment faire lors de l'implémentation dans la scène. Sachez simplement que la valeur de cet attribut est modifiée lors de l'appui d'une touche, par exemple des flèches haut et bas ou bien la molette de la souris, au choix.

Ainsi, il n'y a pas grand-chose de plus à implémenter pour avoir une caméra de base. Notez qu'ici je ne considère qu'une rotation par rapport à l'axe Y, mais libre à vous d'ajouter les deux autres axes si vous le voulez.

L'avantage d'utiliser un objet caméra dans cette scène, c'est que vous n'avez que les paramètres de ladite caméra à modifier lorsque vous la tournez ou la déplacez, plutôt que de redéplacer tous les objets un par un.

MyScene.h

QList<MyProxy *> m_objets;

Pour ajouter un objet à la liste, c'est très simple, vous pouvez utiliser l'opérateur de flux <<.

MyProxy *proxy;
// ...
m_objets << proxy;

Lorsque l'on modifie un paramètre de la caméra comme la rotation, on appelle une méthode updateScene() de la classe MyScene qui va parcourir tous les objets de la scène et appeler, pour chacun d'eux une méthode replacer() se chargeant d'appliquer les transformations dues à la caméra.

MyScene.cpp

void MyScene::updateScene()
{
foreach (MyProxy *objet, m_objets)
{
objet->replacer();
}
}

Il suffit maintenant d'ajouter un attribut caméra dans la classe MyProxy et d'écrire la fonction replacer() comme suit.

MyScene.cpp

void MyProxy::replacer()
{
QTransform m;
m.translate(this->center().x(), this->center().y());
m.translate(-this->widget()->width()/2, -this->widget()->height()/2);

// ajout des transformations liées à la position de la caméra
m.translate(-m_camera.pos().x(), -m_camera.pos().y());
m *= QTransform().rotate(m_camera.yaw(), Qt::YAxis);

// simulation du déplacement suivant Z
qreal scale = m_camera.zpos();
m *= QTransform().scale(scale, scale);

this->setTransform(m);
}

Enfin vous remarquez l'utilisation de la fonction QTransform::scale() qui a un paramètre dépendant de l'attribut m_zpos de ma caméra. En effet, si je considère que lorsque ma position en Z augmente je me rapproche de la scène, alors cela revient à voir les objets dans la scène en plus gros, donc à les agrandir ! Ici j'ai fait une implémentation assez basique pour que vous compreniez le principe, on peut cependant affiner le comportement en ajoutant des coefficients multiplicateurs pour gagner en précision.

VI - Conclusions

Voilà ! J'espère que les classes QGraphics**** de Qt vous paraissent plus familières et que vous avez une idée du genre de scènes qu'il est possible de réaliser.

Pour des notions plus exhaustives, vous pouvez de plus visiter la documentation sur ce module de Qt.

VII - Remerciements

J'adresse aussi des remerciements tous particuliers à dourouc05 et gbdivers pour leurs relectures et conseils tout au long de la correction afin de proposer un cours le plus juste possible. De même, j'aimerais remercier Claude Leloup et Jacques Thery pour leur relecture orthographique.