シーングラフ - カスタムジオメトリ
Qt Quick Scene Graph でカスタムジオメトリを実装する方法を紹介します。
カスタムジオメトリの例では、シーングラフ API を使用してシーングラフ用のカスタムジオメトリを構築するQQuickItem の作成方法を示します。これは、CustomGeometry モジュールの一部となるBezierCurve
アイテムを作成し、QML ファイルで利用します。
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; };
アイテム宣言はQQuickItem クラスをサブクラス化し、5つのプロパティを追加します。ひとつはベジェ曲線の4つの制御点、もうひとつは曲線を細分化するセグメントの数を制御するパラメータである。それぞれのプロパティに対応するゲッター関数とセッター関数があります。これらのプロパティはQMLでバインドすることができるため、それぞれのプロパティに対してノーティファイアシグナルを用意し、変更がQMLエンジンに拾われ、それに応じて使用されるようにすることも望ましい。
QMLシーンとレンダリングシーングラフの同期ポイントは仮想関数QQuickItem::updatePaintNode()です。
注意: 多くのハードウェア構成では、シーングラフは別スレッドでレンダリングされます。そのため、シーングラフとのやりとりは、何よりもまずQQuickItem::updatePaintNode() 関数を通して、制御された方法で行われることが重要です。
ベジェ曲線の実装
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); }
BezierCurve コンストラクタは、制御点とセグメント数のデフォルト値を設定します。ベジェ曲線は、アイテムの外接矩形に対する正規化座標で指定されます。
コンストラクタはまた、フラグQQuickItem::ItemHasContents を設定します。このフラグは、このアイテムがビジュアルコンテンツを提供し、QMLシーンがレンダリングシーングラフと同期する時にQQuickItem::updatePaintNode() を呼び出すことを canvas に伝えます。
BezierCurve::~BezierCurve() = default;
BezierCurveクラスにはクリーンアップが必要なデータメンバーはないので、デストラクタは何もしません。レンダリングシーングラフはシーングラフ自身によって管理され、異なるスレッドで管理される可能性があるため、QQuickItem クラスのQSGNode 参照を保持したり、明示的にクリーンアップしようとしてはいけません。
void BezierCurve::setP1(const QPointF &p) { if (p == m_p1) return; m_p1 = p; emit p1Changed(p); update(); }
p1プロパティのセッター関数は、値が変更されていないかどうかをチェックし、変更されていない場合は早めに終了する。その後、内部値を更新し、変更されたシグナルを出力します。その後、QQuickItem::update ()関数の呼び出しに進み、このオブジェクトの状態が変更され、レンダリングシーングラフと同期する必要があることをレンダリングシーングラフに通知する。update()を呼び出すと、後でQQuickItem::updatePaintNode()を呼び出すことになる。
他のプロパティ・セッタは同等であり、この例では省略されている。
QSGNode *BezierCurve::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { QSGGeometryNode *node = nullptr; QSGGeometry *geometry = nullptr; if (!oldNode) { node = new QSGGeometryNode;
updatePaintNode()関数は、QMLシーンの状態をレンダリングシーングラフと同期させるための主要な統合ポイントです。この関数にはQSGNode が渡されます。 は、最後にこの関数を呼び出したときに返されたインスタンスです。この関数が最初に呼び出されたときはNULLで、QSGGeometryNode を作成し、ジオメトリとマテリアルで塗りつぶします。
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount); geometry->setLineWidth(2); geometry->setDrawingMode(QSGGeometry::DrawLineStrip); node->setGeometry(geometry); node->setFlag(QSGNode::OwnsGeometry);
次にジオメトリを作成し、ノードに追加します。QSGGeometry コンストラクタの最初の引数は頂点タイプの定義で、"アトリビュートセット "と呼ばれます。QML でよく使われるグラフィックスは、いくつかの標準的な属性セットが中心となっているため、デフォルトでこれらの属性セットが提供されています。ここでは、x座標とy座標の2つのfloatを持つPoint2D属性セットを使用します。番目の引数は頂点数です。
カスタム属性セットも作成できますが、この例では扱いません。
ジオメトリのメモリ管理については特に必要ないので、QSGGeometryNode がジオメトリを所有するように指定します。
割り当てを最小限に抑え、メモリの断片化を減らし、パフォーマンスを向上させるために、ジオメトリをQSGGeometryNode サブクラスのメンバにすることも可能です。この場合、QSGGeometryNode::OwnsGeometry フラグは設定しません。
auto *material = new QSGFlatColorMaterial; material->setColor(QColor(255, 0, 0)); node->setMaterial(material); node->setFlag(QSGNode::OwnsMaterial);
シーングラフ API には、よく使用されるマテリアルの実装がいくつか用意されています。この例では、QSGFlatColorMaterial を使用します。これは、ジオメトリによって定義された形状をソリッドカラーで塗りつぶします。ここでもマテリアルのオーナーシップをノードに渡すことで、シーングラフがマテリアルをクリーンアップできるようにしています。
} else { node = static_cast<QSGGeometryNode *>(oldNode); geometry = node->geometry(); geometry->allocate(m_segmentCount); }
QML アイテムが変更され、既存のノードのジオメトリのみを変更したい場合は、oldNode
をQSGGeometryNode インスタンスにキャストし、そのジオメトリを抽出します。セグメント数が変更された場合は、QSGGeometry::allocate() を呼び出し、頂点数が正しいことを確認します。
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);
ジオメトリを埋めるために、まず頂点配列を取り出します。デフォルトの属性セットを使用しているので、便利な関数QSGGeometry::vertexDataAsPoint2D() を使用できます。次に、各セグメントを調べて位置を計算し、その値を頂点に書き込みます。
return node;
}
関数の最後に、シーングラフがレンダリングできるようにノードを返します。
アプリケーションのエントリーポイント
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(); }
このアプリケーションは、QGuiApplication と、.qmlファイルを渡すQQuickView を持つ、単純なQMLアプリケーションです。
QML_ELEMENT
BezierCurveを使うには、QML_ELEMENT マクロを使ってQMLエンジンに登録する必要があります。これにより、BezierCurveという名前が与えられ、プロジェクトのビルドファイルで定義されているCustomGeometry 1.0
モジュールの一部になります:
# 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 MACOS_BUNDLE_POST_BUILD 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 += target
ベジェ曲線は線分として描画されるため、アンチエイリアスを得るためにビューをマルチサンプリングするように指定します。これは必須ではありませんが、マルチサンプリングをサポートしているハードウェアでは、アイテムの見栄えが少し良くなります。マルチサンプリングはしばしばメモリ使用量が多くなるため、デフォルトでは有効になっていません。
アイテムの使用
import QtQuick import CustomGeometry
この.qmlファイルは、標準的な型を得るためにQtQuick 2.0
モジュールをインポートし、また新しく作成したBezierCurveオブジェクトを含む独自のCustomGeometry 1.0
モジュールをインポートしている。
Item { width: 300 height: 200 BezierCurve { id: line anchors.fill: parent anchors.margins: 20
そして、ルート・アイテムと、ルートを埋めるためにアンカーするBezierCurveのインスタンスを作成する。
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) }
この例をもう少し面白くするために、曲線の2つの制御点を変更するアニメーションを追加します。端点は変更しません。
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") } }
最後に、この例が示していることを説明する短いテキストを重ねます。
©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 ここで提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。