Arborescence de scène - Géométrie personnalisée
Explique comment implémenter une géométrie personnalisée dans le graphe de scène d'Qt Quick.
L'exemple de géométrie personnalisée montre comment créer une classe ` QQuickItem ` qui utilise l'API du graphe de scène pour construire une géométrie personnalisée pour ce dernier. Pour ce faire, il crée un élément ` BezierCurve `, qui est intégré au module `CustomGeometry` et utilisé dans un fichier QML.

Déclaration de BezierCurve
#include <QtQuick/QQuickItem>
class BezierCurve : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QPointF p1 READ p1 WRITE setP1 NOTIFY p1Changed)
Q_PROPERTY(QPointF p2 READ p2 WRITE setP2 NOTIFY p2Changed)
Q_PROPERTY(QPointF p3 READ p3 WRITE setP3 NOTIFY p3Changed)
Q_PROPERTY(QPointF p4 READ p4 WRITE setP4 NOTIFY p4Changed)
Q_PROPERTY(int segmentCount READ segmentCount WRITE setSegmentCount NOTIFY segmentCountChanged)
QML_ELEMENT
public:
BezierCurve(QQuickItem *parent = nullptr);
~BezierCurve();
QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;
QPointF p1() const { return m_p1; }
QPointF p2() const { return m_p2; }
QPointF p3() const { return m_p3; }
QPointF p4() const { return m_p4; }
int segmentCount() const { return m_segmentCount; }
void setP1(const QPointF &p);
void setP2(const QPointF &p);
void setP3(const QPointF &p);
void setP4(const QPointF &p);
void setSegmentCount(int count);
signals:
void p1Changed(const QPointF &p);
void p2Changed(const QPointF &p);
void p3Changed(const QPointF &p);
void p4Changed(const QPointF &p);
void segmentCountChanged(int count);
private:
QPointF m_p1;
QPointF m_p2;
QPointF m_p3;
QPointF m_p4;
int m_segmentCount;
};La déclaration de l’élément hérite de la classe ` QQuickItem ` et ajoute cinq propriétés : une pour chacun des quatre points de contrôle de la courbe de Bézier, ainsi qu’un paramètre permettant de contrôler le nombre de segments dans lesquels la courbe est subdivisée. Pour chacune de ces propriétés, nous disposons de fonctions getter et setter correspondantes. Étant donné que ces propriétés peuvent être liées dans QML, il est également préférable de disposer de signaux de notification pour chacune d’entre elles, afin que les modifications soient détectées par le moteur QML et utilisées en conséquence.
Le point de synchronisation entre la scène QML et le graphe de scène de rendu est la fonction virtuelle ` QQuickItem::updatePaintNode()`, que tous les éléments dotés d’une logique de graphe de scène personnalisée doivent implémenter.
Remarque : sur de nombreuses configurations matérielles,le graphe de scène s’affiche sur un thread distinct. Il est donc essentiel que l’interaction avec le graphe de scène se fasse de manière contrôlée, avant tout via la fonction ` QQuickItem::updatePaintNode()`.
Implémentation de BezierCurve
BezierCurve::BezierCurve(QQuickItem *parent)
: QQuickItem(parent)
, m_p1(0, 0)
, m_p2(1, 0)
, m_p3(0, 1)
, m_p4(1, 1)
, m_segmentCount(32)
{
setFlag(ItemHasContents, true);
}Le constructeur de BezierCurve définit les valeurs par défaut des points de contrôle et du nombre de segments. La courbe de Bézier est spécifiée en coordonnées normalisées par rapport au rectangle englobant de l’élément.
Le constructeur active également l’indicateur QQuickItem::ItemHasContents. Cet indicateur signale au canevas que cet élément fournit du contenu visuel et qu’il appellera la fonction QQuickItem::updatePaintNode() lorsque la scène QML devra être synchronisée avec le graphe de scène de rendu.
BezierCurve::~BezierCurve() = default;La classe `BezierCurve` ne possède aucun membre de données nécessitant un nettoyage ; le destructeur ne fait donc rien. Il convient de noter que le graphe de scène de rendu est géré par le graphe de scène lui-même, potentiellement dans un thread différent ; il ne faut donc jamais conserver de références ` QSGNode ` dans la classe ` QQuickItem `, ni tenter de les nettoyer explicitement.
void BezierCurve::setP1(const QPointF &p)
{
if (p == m_p1)
return;
m_p1 = p;
emit p1Changed(p);
update();
}La fonction de mise en valeur de la propriété p1 vérifie si la valeur est inchangée et sort prématurément si tel est le cas. Elle met ensuite à jour la valeur interne et émet le signal « changed ». Elle appelle ensuite la fonction ` QQuickItem::update()`, qui notifie au graphe de scène de rendu que l’état de cet objet a changé et doit être synchronisé avec le graphe de scène de rendu. Un appel à `update()` entraînera un appel à ` QQuickItem::updatePaintNode()` ultérieurement.
Les autres accesseurs de propriété sont équivalents et sont omis dans cet exemple.
QSGNode *BezierCurve::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
QSGGeometryNode *node = nullptr;
QSGGeometry *geometry = nullptr;
if (!oldNode) {
node = new QSGGeometryNode;La fonction `updatePaintNode()` constitue le principal point d’intégration pour la synchronisation de l’état de la scène QML avec le graphe de scène de rendu. Cette fonction reçoit en paramètre un ` QSGNode `, qui correspond à l’instance renvoyée lors du dernier appel de la fonction. Cette instance sera nulle lors du premier appel de la fonction ; nous créons alors notre ` QSGGeometryNode `, que nous allons remplir avec une géométrie et un matériau.
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount);
geometry->setLineWidth(2);
geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);Nous créons ensuite la géométrie et l’ajoutons au nœud. Le premier argument du constructeur QSGGeometry est une définition du type de sommet, appelée « ensemble d’attributs ». Comme les graphiques souvent utilisés en QML s’articulent autour de quelques ensembles d’attributs standard courants, ceux-ci sont fournis par défaut. Ici, nous utilisons l’ensemble d’attributs Point2D qui comporte deux flottants, l’un pour les coordonnées x et l’autre pour les coordonnées y. Le deuxième argument correspond au nombre de sommets.
Il est également possible de créer des ensembles d’attributs personnalisés, mais cela n’est pas abordé dans cet exemple.
Comme nous n’avons pas d’exigences particulières en matière de gestion de la mémoire de la géométrie, nous précisons que l’ QSGGeometryNode doit être propriétaire de la géométrie.
Pour minimiser les allocations, réduire la fragmentation de la mémoire et améliorer les performances, il serait également possible de faire de la géométrie un membre d’une sous-classe de ` QSGGeometryNode ` ; dans ce cas, nous n’aurions pas défini l’indicateur `QSGGeometryNode::OwnsGeometry`.
auto *material = new QSGFlatColorMaterial;
material->setColor(QColor(255, 0, 0));
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);L’API du graphe de scène fournit quelques implémentations de matériaux couramment utilisées. Dans cet exemple, nous utilisons le matériau « QSGFlatColorMaterial », qui remplit la forme définie par la géométrie d’une couleur unie. Là encore, nous transférons la propriété du matériau au nœud, afin qu’il puisse être nettoyé par le graphe de scène.
} else {
node = static_cast<QSGGeometryNode *>(oldNode);
geometry = node->geometry();
geometry->allocate(m_segmentCount);
}Dans le cas où l’élément QML a changé et que nous souhaitons uniquement modifier la géométrie du nœud existant, nous convertissons l’ oldNode en une instance de QSGGeometryNode et en extrayons la géométrie. Si le nombre de segments a changé, nous appelons QSGGeometry::allocate() pour nous assurer qu’il comporte le bon nombre de sommets.
QSizeF itemSize = size();
QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();
for (int i = 0; i < m_segmentCount; ++i) {
qreal t = i / qreal(m_segmentCount - 1);
qreal invt = 1 - t;
QPointF pos = invt * invt * invt * m_p1
+ 3 * invt * invt * t * m_p2
+ 3 * invt * t * t * m_p3
+ t * t * t * m_p4;
float x = pos.x() * itemSize.width();
float y = pos.y() * itemSize.height();
vertices[i].set(x, y);
}
node->markDirty(QSGNode::DirtyGeometry);Pour remplir la géométrie, nous en extrayons d’abord le tableau de sommets. Comme nous utilisons l’un des ensembles d’attributs par défaut, nous pouvons utiliser la fonction pratique QSGGeometry::vertexDataAsPoint2D(). Nous parcourons ensuite chaque segment pour calculer sa position et enregistrer cette valeur dans le sommet correspondant.
return node;
}À la fin de la fonction, nous renvoyons le nœud afin que le graphe de scène puisse le rendre.
Point d’entrée de l’application
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
QQuickView view;
QSurfaceFormat format = view.format();
format.setSamples(16);
view.setFormat(format);
view.setSource(QUrl("qrc:///scenegraph/customgeometry/main.qml"));
view.show();
return app.exec();
}L’application est une application QML simple, dotée d’une fonction ` QGuiApplication ` et d’une fonction ` QQuickView ` auxquelles nous transmettons un fichier .qml.
QML_ELEMENTPour utiliser l’élément BezierCurve, nous devons l’enregistrer dans le moteur QML à l’aide de la macro ` QML_ELEMENT `. Cela lui attribue le nom « BezierCurve » et l’intègre au module ` CustomGeometry 1.0 `, tel que défini dans les fichiers de compilation du projet :
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(customgeometry_declarative LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick)
qt_standard_project_setup()
qt_add_executable(customgeometry_declarative WIN32 MACOSX_BUNDLE
beziercurve.cpp beziercurve.h
main.cpp
)
target_link_libraries(customgeometry_declarative PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Quick
)
qt_add_qml_module(customgeometry_declarative
URI CustomGeometry
QML_FILES main.qml
RESOURCE_PREFIX /scenegraph/customgeometry
NO_RESOURCE_TARGET_PATH
)
install(TARGETS customgeometry_declarative
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
qt_generate_deploy_qml_app_script(
TARGET customgeometry_declarative
OUTPUT_SCRIPT deploy_script
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})TARGET = customgeometry
QT += quick
CONFIG += qmltypes
QML_IMPORT_NAME = CustomGeometry
QML_IMPORT_MAJOR_VERSION = 1
SOURCES += \
main.cpp \
beziercurve.cpp
HEADERS += \
beziercurve.h
RESOURCES += customgeometry.qrc
target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/customgeometry
INSTALLS += targetComme la courbe de Bézier est dessinée sous forme de segments de ligne, nous spécifions que la vue doit faire l'objet d'un suréchantillonnage afin d'obtenir un lissage. Ce n'est pas obligatoire, mais cela améliorera légèrement l'aspect de l'élément sur les matériels qui le prennent en charge. Le suréchantillonnage n'est pas activé par défaut, car il entraîne souvent une consommation de mémoire plus importante.
Utilisation de l'élément
import QtQuick
import CustomGeometryNotre fichier .qml importe le module ` QtQuick 2.0 ` pour obtenir les types standard, ainsi que notre propre module ` CustomGeometry 1.0 `, qui contient nos objets `BezierCurve` nouvellement créés.
Item {
width: 300
height: 200
BezierCurve {
id: line
anchors.fill: parent
anchors.margins: 20Nous créons ensuite notre élément racine et une instance de BezierCurve que nous ancrons pour remplir l’élément racine.
property real t
SequentialAnimation on t {
NumberAnimation { to: 1; duration: 2000; easing.type: Easing.InOutQuad }
NumberAnimation { to: 0; duration: 2000; easing.type: Easing.InOutQuad }
loops: Animation.Infinite
}
p2: Qt.point(t, 1 - t)
p3: Qt.point(1 - t, t)
}Pour rendre l’exemple un peu plus intéressant, nous ajoutons une animation permettant de modifier les deux points de contrôle de la courbe. Les points d’extrémité restent inchangés.
Text {
anchors.bottom: line.bottom
x: 20
width: parent.width - 40
wrapMode: Text.WordWrap
text: qsTr("This curve is a custom scene graph item, implemented using line strips")
}
}Enfin, nous superposons un court texte décrivant ce que montre l’exemple.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.