Qt/C++ による GUI ソフトウェア作成の練習 6:ピッキング

このページは,Qt3D で描画したオブジェクトをピッキング(マウスで描画オブジェクトを選択)する手法を勉強したときのまとめです.
QObjectPicker クラスのインスタンスを QEntity のコンポーネントとして追加するだけで,その QEntity はピッキングできるようになります.

ここでは,QEntity のインスタンスを識別番号で区別するための方法を試してみました.
これを当サイトでは,分子モデルを処理する際に原子を区別するための手段として使います.

Before : 5:Qt3D による描画 Next : 7:国際化

インフォメーション

インストールした Debian パッケージ

パッケージ名(バージョン)
g++(12.2.0)GNU C++ コンパイラ
make (4.3)コンパイルを制御するユーティリティ
qt6-base-dev(6.4.2)Qt6 アプリケーションのビルドに使用されるヘッダ開発ファイル
qt6-3d-dev(6.4.2)Qt3D ライブラリを使用した Qt6 アプリケーションのビルドに使用されるヘッダ開発ファイル

ドキュメント

Qt3D による描画で紹介したリンクに,QObjectPicker クラスを追加しておきます.


Qt 3D Overview | Qt 3D 6.5.2
QEntity
Component
Qt 3D Examples | Qt 3D 6.5.2
QObjectPicker Class

QObjectPicker Class のページに記されている注意事項


ソースコード

Qt3D による描画で作成したコードをわずかに変更して雛形としました.
このページで追加した箇所は赤色で示しています.

>

draw_obj.h


#include <Qt3DCore>
#include <Qt3DExtras>
#include <Qt3DRender>

class Draw_Obj : public QObject {
 private:
  Qt3DExtras::Qt3DWindow *DrawWindow;
  Qt3DCore::QEntity *RootEntity;
  Qt3DCore::QEntity *Sphere;

  bool Picked;  //ピック済みかどうかを示すフラグ

 private slots:
  void slot_clicked(int i);  //このコードでは球をクリックしたときに呼び出される,引数付きのスロット関数

 public:
  Draw_Obj();
  ~Draw_Obj() {}

  Qt3DExtras::Qt3DWindow *get_draw_window()
        { return DrawWindow; }
  void init();
  void add_obj();
};

draw_obj.cc


#include "draw_obj.h"
#include <iostream>

Draw_Obj::Draw_Obj()
 : DrawWindow(nullptr),
   RootEntity(nullptr),
   Sphere(nullptr),
   Picked(false)  //最初はピックされていない
{
 RootEntity = new Qt3DCore::QEntity();
 DrawWindow = new Qt3DExtras::Qt3DWindow();
 DrawWindow->setRootEntity(RootEntity);
}

void Draw_Obj::init() {
 if(Sphere) {
  delete Sphere;
  Sphere = nullptr;
 }

 if(RootEntity) {
  delete RootEntity;
  RootEntity = new Qt3DCore::QEntity();
 }

 if(DrawWindow) {
  delete DrawWindow;
  DrawWindow = new Qt3DExtras::Qt3DWindow();
 }

 DrawWindow->setRootEntity(RootEntity);

 Qt3DRender::QCamera *CameraEntity
        = DrawWindow->camera();
 CameraEntity->lens()->setPerspectiveProjection(
        45.0f, 1.0f, 10.f, 30.0f);
 CameraEntity->setPosition(QVector3D(0, 0, 20.0f));
 CameraEntity->setUpVector(QVector3D(0, 1, 0));
 CameraEntity->setViewCenter(QVector3D(0, 0, 0));

 Qt3DCore::QEntity *light_entity
        = new Qt3DCore::QEntity(RootEntity);
 Qt3DRender::QPointLight *light
        = new Qt3DRender::QPointLight(light_entity);
 light->setColor("white");
 light->setIntensity(1);
 light_entity->addComponent(light);
 Qt3DCore::QTransform *light_pos
        = new Qt3DCore::QTransform(light_entity);
 light_pos->setTranslation(
        CameraEntity->position());
 light_entity->addComponent(light_pos);
}

void Draw_Obj::add_obj() {
 if(Sphere) return;

 Sphere = new Qt3DCore::QEntity(RootEntity);

 Qt3DExtras::QSphereMesh *mesh
        = new Qt3DExtras::QSphereMesh();
 mesh->setRings(20);
 mesh->setSlices(20);
 mesh->setRadius(2.5);
 Sphere->addComponent(mesh);

 Qt3DExtras::QPhongMaterial *material
        = new Qt3DExtras::QPhongMaterial();
 material->setDiffuse(QColor(QRgb(0xFF0000)));
 Sphere->addComponent(material);

 Qt3DCore::QTransform *transform
        = new Qt3DCore::QTransform();
 transform->setTranslation(
        QVector3D(1.0f, 1.0f, 1.0f));
 Sphere->addComponent(transform);

 //ピッカーを Sphere のコンポーネントとして,識別番号を付けて作成する
 int id = 123;  //識別番号.ここでは値はてきとうでよい
 Qt3DRender::QObjectPicker *picker =
        new Qt3DRender::QObjectPicker(Sphere);
 connect(
        picker,
        &Qt3DRender::QObjectPicker::clicked,
        [this, id]() { slot_clicked(id); } );  //引数付きのスロット関数と接続するときは,ラムダ式を使えばよい
 Sphere->addComponent(picker);
}

//connect() を使って接続したので, 上で作成した Sphere がクリックされるとこの関数が呼び出される.引数は識別番号
void Draw_Obj::slot_clicked(int i) {
 //確認のため,識別番号を出力
 std::cout << "id : " << i << std::endl;

 //ピックされたら色を変える.これだと,ピックのたびにコンポーネントが増える一方のような気がする.何かうまい方法があるのかもしれない
 Qt3DExtras::QPhongMaterial *material
        = new Qt3DExtras::QPhongMaterial();
 if(!Picked)
  material->setDiffuse(QColor(QRgb(0x0000FF)));
 else
  material->setDiffuse(QColor(QRgb(0xFF0000)));

 Sphere->addComponent(material);

 //フラグを反転
 Picked = !Picked;
}

main_window.h


#include <QMainWindow>
#include <QMenuBar>
#include <QHBoxLayout>
#include "draw_obj.h"

class Main_Window : public QMainWindow {
 private:
  Q_OBJECT

  QMenuBar *MenuBar;

  QHBoxLayout *CentralLayout;
  QWidget *Container;
  Draw_Obj DrawObj;
  Qt3DExtras::Qt3DWindow *DrawWindow;

  private slots:
  void slot_draw();
  void slot_clear() { DrawObj.init(); }

 public:
  Main_Window(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
};

main_window.cc


#include "main_window.h"

Main_Window::Main_Window(QWidget *parent, Qt::WindowFlags f)
 : QMainWindow(parent, f),
   MenuBar(menuBar()),
   CentralLayout(nullptr),
   Container(nullptr),
   DrawWindow(nullptr)
{
 QAction *action_quit = new QAction("終了(&Q)", this);
 connect(action_quit, SIGNAL(triggered()), this, SLOT(close()));

 QMenu *file_menu = new QMenu("ファイル(&F)", this);
 file_menu->addAction(action_quit);
 MenuBar->addMenu(file_menu);

 QAction *action_draw
        = new QAction(tr("描画(&D)"), this);
 connect(action_draw, SIGNAL(triggered()), this,
        SLOT(slot_draw()));

 QAction *action_clear
        = new QAction(tr("クリア(&C)"), this);
 connect(action_clear, SIGNAL(triggered()), this,
        SLOT(slot_clear()));

 QMenu *view_menu = new QMenu(tr("表示(&V)"), this);
 view_menu->addAction(action_draw);
 view_menu->addAction(action_clear);
 MenuBar->addMenu(view_menu);

 QWidget *central_widget = new QWidget(this);
 setCentralWidget(central_widget);
 CentralLayout = new QHBoxLayout(central_widget);

 DrawWindow = DrawObj.get_draw_window();
 Container = createWindowContainer(DrawWindow);
 CentralLayout->addWidget(Container);

 QSize screenSize = screen()->size();
 setMaximumSize(screenSize);

 setMinimumWidth(600);
 setMinimumHeight(600);
}

void Main_Window::slot_draw() {
 DrawObj.init();

 CentralLayout->removeWidget(Container);
 delete Container;
 DrawWindow = DrawObj.get_draw_window();
 Container = createWindowContainer(DrawWindow);
 CentralLayout->addWidget(Container);

 DrawObj.add_obj();
}

main.cc


#include <QMainWindow>
#include <QApplication>
#include "main_window.h"

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

 Main_Window window(nullptr, Qt::Window);

 window.show();

 return app.exec();
}

tmp.pro

qmake のプロジェクトファイルです.名前を変更しても問題ありません.


TEMPLATE = app
TARGET = window
QT += widgets
INCLUDEPATH += .
HEADERS += main_window.h draw_obj.h
SOURCES += main.cc main_window.cc draw_obj.cc

QT += 3dcore
QT += 3dextras
#QT += 3drender
#QT += 3dinput

ビルドと実行

Qt3D で球を描画

まず Makefile を作成.
~/tmp$ qmake6

次いでビルド.実行ファイル window が生成します.
~/tmp$ make

ビルドに成功したら実行.
~/tmp$ ./window &
とするとウィンドウが表示されます.
メニューで [表示(V)]-[描画(D)] とすると球が描画されます.
球をクリックするごと,球の色がと変化します.
画像は,青に変化したときのキャプチャしたものです.
同時に端末に,ソースコード内で設定した識別番号が出力されます.

id : 123

参考書の検索