From 0ef62ebfa55bc524b3fcef1b3669fb09ebcf1217 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 24 Dec 2023 12:24:58 -0500 Subject: [PATCH] Add object details + Object and Shader detail panels + Connect model data to details panel + Ability to move and scale objects in GUI + View of shader code for selected object --- .github/workflows/all-builds.yml | 4 +- extern/assimp/assimp | 2 +- src/app/qtkmainwindow.cpp | 18 ++-- src/app/qtkmainwindow.ui | 70 ++++++++-------- src/app/qtkwidget.cpp | 12 +-- src/app/qtkwidget.h | 4 + src/app/toolbox.cpp | 136 ++++++++++++++++++++++++++++++- src/app/toolbox.h | 14 ++++ src/app/toolbox.ui | 57 +++++++++---- src/app/treeview.cpp | 17 +++- src/qtk/meshrenderer.h | 8 ++ src/qtk/model.h | 11 +++ src/qtk/object.h | 49 ++++++++++- src/qtk/scene.cpp | 16 ++-- src/qtk/scene.h | 5 +- 15 files changed, 343 insertions(+), 80 deletions(-) diff --git a/.github/workflows/all-builds.yml b/.github/workflows/all-builds.yml index dedf9a8..466de5c 100644 --- a/.github/workflows/all-builds.yml +++ b/.github/workflows/all-builds.yml @@ -118,7 +118,7 @@ jobs: - name: Upload Qtk install directory uses: actions/upload-artifact@v3 with: - name: qtk-gui-${{ matrix.os }}-installdir + name: qtk-gui-${{ matrix.os }}-install path: install/* # TODO: Enable after trimming resources. @@ -238,7 +238,7 @@ jobs: uses: actions/upload-artifact@v3 if: always() with: - name: libqtk-${{ matrix.os }}-installdir + name: libqtk-${{ matrix.os }}-install path: install/* Qtk-Plugins: diff --git a/extern/assimp/assimp b/extern/assimp/assimp index eb328ce..5d5496f 160000 --- a/extern/assimp/assimp +++ b/extern/assimp/assimp @@ -1 +1 @@ -Subproject commit eb328ce69dd7b06977aed125e967a41e835b8431 +Subproject commit 5d5496f1ad895297cede723b3c96b513263f82ed diff --git a/src/app/qtkmainwindow.cpp b/src/app/qtkmainwindow.cpp index eec7a17..888a366 100644 --- a/src/app/qtkmainwindow.cpp +++ b/src/app/qtkmainwindow.cpp @@ -29,17 +29,25 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent) { for(auto & qtkWidget : qtkWidgets) { qtkWidget->setScene(new Qtk::SceneEmpty); views_.emplace(qtkWidget->getScene()->getSceneName(), qtkWidget); + + // Add GUI 'view' toolbar option to show debug console. ui_->menuView->addAction(qtkWidget->getActionToggleConsole()); + // Refresh GUI widgets when scene or objects are updated. connect( qtkWidget->getScene(), &Qtk::Scene::sceneUpdated, this, &MainWindow::refreshScene); + connect( + qtkWidget, &Qtk::QtkWidget::objectFocusChanged, ui_->qtk__ToolBox, + &Qtk::ToolBox::updateFocus); } - auto docks = findChildren(); - for(auto & dock : docks) { - addDockWidget(Qt::RightDockWidgetArea, dock); - ui_->menuView->addAction(dock->toggleViewAction()); - } + // Dock the toolbox widget to the main window. + addDockWidget(Qt::LeftDockWidgetArea, ui_->qtk__ToolBox); + // Add an option to toggle active widgets in the GUI's toolbar 'view' menu. + ui_->menuView->addAction(ui_->qtk__ToolBox->toggleViewAction()); + + addDockWidget(Qt::RightDockWidgetArea, ui_->qtk__TreeView); + ui_->menuView->addAction(ui_->qtk__TreeView->toggleViewAction()); // Set the window icon used for Qtk. setWindowIcon(Qtk::getIcon()); diff --git a/src/app/qtkmainwindow.ui b/src/app/qtkmainwindow.ui index 7fef4f5..d04f507 100644 --- a/src/app/qtkmainwindow.ui +++ b/src/app/qtkmainwindow.ui @@ -6,7 +6,7 @@ 0 0 - 824 + 1034 601 @@ -28,11 +28,27 @@ + + + + + 1 + 0 + + + + A custom widget tool tip. + + + Custom widget what's this? + + + - 0 + 3 0 @@ -67,28 +83,20 @@ - - - - - A custom widget tool tip. - - - Custom widget what's this? - - - - - - - A custom widget tool tip. - - - Custom widget what's this? - - - - + + + + 1 + 0 + + + + A custom widget tool tip. + + + Custom widget what's this? + + @@ -97,7 +105,7 @@ 0 0 - 824 + 1034 22 @@ -179,7 +187,7 @@ - + :/icons/fontawesome-free-6.2.1-desktop/svgs/regular/folder-open.svg:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/folder-open.svg @@ -188,7 +196,7 @@ - + :/icons/fontawesome-free-6.2.1-desktop/svgs/regular/floppy-disk.svg:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/floppy-disk.svg @@ -215,7 +223,7 @@ - + :/icons/fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg:/icons/fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg @@ -227,7 +235,7 @@ - + :/icons/fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg @@ -318,9 +326,7 @@ 1 - - - + actionExit diff --git a/src/app/qtkwidget.cpp b/src/app/qtkwidget.cpp index 7c7b8a4..8e35eff 100644 --- a/src/app/qtkwidget.cpp +++ b/src/app/qtkwidget.cpp @@ -117,11 +117,11 @@ void QtkWidget::paintGL() { } } -void QtkWidget::setScene(Qtk::Scene * scene) { +void QtkWidget::setScene(Scene * scene) { if(mScene != Q_NULLPTR) { delete mScene; connect( - scene, &Qtk::Scene::sceneUpdated, MainWindow::getMainWindow(), + scene, &Scene::sceneUpdated, MainWindow::getMainWindow(), &MainWindow::refreshScene); } @@ -139,8 +139,7 @@ void QtkWidget::toggleConsole() { mConsoleActive = false; } else { MainWindow::getMainWindow()->addDockWidget( - Qt::DockWidgetArea::BottomDockWidgetArea, - dynamic_cast(mConsole)); + Qt::DockWidgetArea::BottomDockWidgetArea, mConsole); mConsole->setHidden(false); mConsoleActive = true; } @@ -165,6 +164,7 @@ void QtkWidget::dropEvent(QDropEvent * event) { event->ignore(); return; } + // TODO: Support other object types. auto url = urls.front(); if(url.fileName().endsWith(".obj")) { @@ -173,7 +173,6 @@ void QtkWidget::dropEvent(QDropEvent * event) { } else { qDebug() << "Unsupported file type."; event->ignore(); - return; } } } @@ -291,7 +290,8 @@ void QtkWidget::teardownGL() { /* Nothing to teardown yet... */ void QtkWidget::updateCameraInput() { Input::update(); // Camera Transformation - if(Input::buttonPressed(Qt::RightButton)) { + if(Input::buttonPressed(Qt::LeftButton) + || Input::buttonPressed(Qt::RightButton)) { static const float transSpeed = 0.1f; static const float rotSpeed = 0.5f; diff --git a/src/app/qtkwidget.h b/src/app/qtkwidget.h index 8643bc4..a028700 100644 --- a/src/app/qtkwidget.h +++ b/src/app/qtkwidget.h @@ -131,6 +131,10 @@ namespace Qtk { */ void sendLog(const QString & message, DebugContext context = Status); + // TODO: Use this signal in treeview and toolbox to update object + // properties + void objectFocusChanged(const QString objectName); + protected: /************************************************************************* * Protected Methods diff --git a/src/app/toolbox.cpp b/src/app/toolbox.cpp index 72f9275..18341ee 100644 --- a/src/app/toolbox.cpp +++ b/src/app/toolbox.cpp @@ -8,13 +8,143 @@ */ #include "toolbox.h" +#include "qtkmainwindow.h" #include "ui_toolbox.h" -Qtk::ToolBox::ToolBox(QWidget * parent) : - QDockWidget(parent), ui(new Ui::ToolBox) { +#include +#include + +using namespace Qtk; + +ToolBox::ToolBox(QWidget * parent) : QDockWidget(parent), ui(new Ui::ToolBox) { ui->setupUi(this); + setMinimumWidth(350); } -Qtk::ToolBox::~ToolBox() { +void ToolBox::updateFocus(const QString & name) { + qDebug() << "Called updateFocus on Toolbox."; + auto object = + MainWindow::getMainWindow()->getQtkWidget()->getScene()->getObject(name); + if(object != Q_NULLPTR) { + removePages(); + createPageProperties(object); + createPageShader(object); + } +} + +ToolBox::~ToolBox() { delete ui; } + +void ToolBox::removePages() { + // Remove all existing pages. + for(size_t i = 0; i < ui->toolBox->count(); i++) { + delete ui->toolBox->widget(i); + ui->toolBox->removeItem(i); + } +} + +void ToolBox::createPageProperties(const Object * object) { + auto transform = object->getTransform(); + auto type = object->getType(); + auto * widget = new QWidget; + ui->toolBox->addItem(widget, "Properties"); + ui->toolBox->setCurrentWidget(widget); + + auto * layout = new QFormLayout; + layout->addRow( + new QLabel(tr("Name:")), new QLabel(object->getName().c_str())); + + layout->addRow( + new QLabel(tr("Type:")), + new QLabel(type == Object::Type::QTK_MESH ? "Mesh" : "Model")); + + auto rowLayout = new QHBoxLayout; + rowLayout->addWidget(new QLabel(tr("Translation:"))); + int minWidth = 75; + for(size_t i = 0; i < 3; i++) { + auto spinBox = new QDoubleSpinBox; + spinBox->setMinimum(std::numeric_limits::lowest()); + spinBox->setSingleStep(0.1); + spinBox->setValue(transform.getTranslation()[i]); + spinBox->setFixedWidth(minWidth); + rowLayout->addWidget(spinBox); + + if(i == 0) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, + &Object::setTranslationX); + } else if(i == 1) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, + &Object::setTranslationY); + } else if(i == 2) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, + &Object::setTranslationZ); + } + } + layout->addRow(rowLayout); + + rowLayout = new QHBoxLayout; + rowLayout->addWidget(new QLabel(tr("Scale:"))); + for(size_t i = 0; i < 3; i++) { + auto spinBox = new QDoubleSpinBox; + spinBox->setMinimum(std::numeric_limits::lowest()); + spinBox->setSingleStep(0.1); + spinBox->setValue(transform.getScale()[i]); + spinBox->setFixedWidth(minWidth); + rowLayout->addWidget(spinBox); + + if(i == 0) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, &Object::setScaleX); + } else if(i == 1) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, &Object::setScaleY); + } else if(i == 2) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, &Object::setScaleZ); + } + } + layout->addRow(rowLayout); + widget->setLayout(layout); +} + +void ToolBox::createPageShader(const Object * object) { + // Shaders page. + auto widget = new QWidget; + ui->toolBox->addItem(widget, "Shaders"); + auto mainLayout = new QFormLayout; + auto rowLayout = new QHBoxLayout; + rowLayout->addWidget(new QLabel("Vertex Shader:")); + rowLayout->addWidget(new QLabel(object->getVertexShader().c_str())); + mainLayout->addRow(rowLayout); + + auto shaderView = new QTextEdit; + shaderView->setReadOnly(true); + auto vertexFile = QFile(object->getVertexShader().c_str()); + if(vertexFile.exists()) { + vertexFile.open(QIODeviceBase::ReadOnly); + shaderView->setText(vertexFile.readAll()); + vertexFile.close(); + mainLayout->addRow(shaderView); + } + + rowLayout = new QHBoxLayout; + rowLayout->addWidget(new QLabel("Fragment Shader:")); + rowLayout->addWidget(new QLabel(object->getFragmentShader().c_str())); + mainLayout->addRow(rowLayout); + + shaderView = new QTextEdit; + shaderView->setReadOnly(true); + auto fragmentfile = QFile(object->getFragmentShader().c_str()); + if(fragmentfile.exists()) { + fragmentfile.open(QIODeviceBase::ReadOnly); + shaderView->setText(fragmentfile.readAll()); + fragmentfile.close(); + mainLayout->addRow(shaderView); + } + + widget->setLayout(mainLayout); +} diff --git a/src/app/toolbox.h b/src/app/toolbox.h index 601d4d5..7c4ac2b 100644 --- a/src/app/toolbox.h +++ b/src/app/toolbox.h @@ -12,6 +12,11 @@ #include #include +#include +#include + + +#include "qtk/scene.h" namespace Ui { class ToolBox; @@ -30,6 +35,15 @@ namespace Qtk { ~ToolBox(); + void removePages(); + + void createPageProperties(const Object * object); + + void createPageShader(const Object * object); + + void updateFocus(const QString & name); + + private: /************************************************************************* * Private Members diff --git a/src/app/toolbox.ui b/src/app/toolbox.ui index 0de2672..78e9555 100644 --- a/src/app/toolbox.ui +++ b/src/app/toolbox.ui @@ -10,17 +10,57 @@ 300 + + + 0 + 0 + + + + + 86 + 167 + + Object Details - + + + 0 + 0 + + + 0 - + + + true + + + + 0 + 0 + 382 + 201 + + + + + 0 + 0 + + + + Properties + + + 0 @@ -33,19 +73,6 @@ Shaders - - - - 0 - 0 - 382 - 201 - - - - Properties - - diff --git a/src/app/treeview.cpp b/src/app/treeview.cpp index 98f3d8d..0239b90 100644 --- a/src/app/treeview.cpp +++ b/src/app/treeview.cpp @@ -48,16 +48,25 @@ void Qtk::TreeView::itemFocus(QTreeWidgetItem * item, int column) { auto scene = MainWindow::getMainWindow()->getQtkWidget()->getScene(); auto & transform = scene->getCamera().getTransform(); auto object = scene->getObject(name); + Transform3D * objectTransform; + // If the object is a mesh or model, focus the camera on it. if(object == Q_NULLPTR) { qDebug() << "Attempt to get non-existing object with name '" << name << "'\n"; - } - Transform3D * objectTransform; - if(object->getType() == Object::QTK_MESH) { + } else if(object->getType() == Object::QTK_MESH) { objectTransform = &dynamic_cast(object)->getTransform(); } else if(object->getType() == Object::QTK_MODEL) { objectTransform = &dynamic_cast(object)->getTransform(); } - transform.setTranslation(objectTransform->getTranslation()); + auto focusScale = objectTransform->getScale(); + float width = focusScale.x() / 2.0f; + float height = focusScale.y() / 2.0f; + QVector3D pos = objectTransform->getTranslation(); + // pos.setX(pos.x() + width); + pos.setY(pos.y() + height); + transform.setTranslation(pos); transform.translate(0.0f, 0.0f, 3.0f); + + // Emit signal from qtk widget for new object focus. Triggers GUI updates. + emit MainWindow::getMainWindow()->getQtkWidget()->objectFocusChanged(name); } diff --git a/src/qtk/meshrenderer.h b/src/qtk/meshrenderer.h index 28a8fbb..cf5ddea 100644 --- a/src/qtk/meshrenderer.h +++ b/src/qtk/meshrenderer.h @@ -211,6 +211,14 @@ namespace Qtk { */ inline Transform3D & getTransform() { return mTransform; } + inline std::string getVertexShader() const override { + return mVertexShader; + } + + inline std::string getFragmentShader() const override { + return mFragmentShader; + } + private: /************************************************************************* * Private Members diff --git a/src/qtk/model.h b/src/qtk/model.h index 66f18c2..f81575d 100644 --- a/src/qtk/model.h +++ b/src/qtk/model.h @@ -18,6 +18,9 @@ #include // Qtk +#include + + #include "modelmesh.h" #include "qtkapi.h" @@ -124,6 +127,14 @@ namespace Qtk { */ inline Transform3D & getTransform() { return mTransform; } + inline std::string getVertexShader() const override { + return mVertexShader; + } + + inline std::string getFragmentShader() const override { + return mFragmentShader; + } + private: /************************************************************************* * Private Methods diff --git a/src/qtk/object.h b/src/qtk/object.h index ffa0d80..bbf8382 100644 --- a/src/qtk/object.h +++ b/src/qtk/object.h @@ -96,13 +96,23 @@ namespace Qtk { [[nodiscard]] inline const Type & getType() const { return mType; } + [[nodiscard]] inline virtual const Transform3D & getTransform() const { + return mTransform; + } + + [[nodiscard]] inline virtual std::string getVertexShader() const { + return "Base Object has no vertex shader."; + } + + virtual inline std::string getFragmentShader() const { + return "Base Object has no fragment shader."; + } + /************************************************************************* * Setters ************************************************************************/ - virtual inline void setName(const std::string & name) { - mName = name; - } + virtual inline void setName(const std::string & name) { mName = name; } virtual inline void setColors(const Colors & value) { mShape.mColors = value; @@ -139,6 +149,39 @@ namespace Qtk { mShape.mVertices = value; } + inline void setScaleX(double x) { + mTransform.setScale( + x, mTransform.getScale().y(), mTransform.getScale().z()); + } + + inline void setScaleY(double y) { + mTransform.setScale( + mTransform.getScale().x(), y, mTransform.getScale().z()); + } + + inline void setScaleZ(double z) { + mTransform.setScale( + mTransform.getScale().x(), mTransform.getScale().y(), z); + } + + inline void setTranslationX(double x) { + mTransform.setTranslation( + x, mTransform.getTranslation().y(), + mTransform.getTranslation().z()); + } + + inline void setTranslationY(double y) { + mTransform.setTranslation( + mTransform.getTranslation().x(), y, + mTransform.getTranslation().z()); + } + + inline void setTranslationZ(double z) { + mTransform.setTranslation( + mTransform.getTranslation().x(), mTransform.getTranslation().y(), + z); + } + /************************************************************************* * Public Methods ************************************************************************/ diff --git a/src/qtk/scene.cpp b/src/qtk/scene.cpp index 684a557..b107c94 100644 --- a/src/qtk/scene.cpp +++ b/src/qtk/scene.cpp @@ -58,14 +58,14 @@ void Scene::draw() { mInit = true; } - while (!mModelLoadQueue.empty()) { + while(!mModelLoadQueue.empty()) { auto modelSpec = mModelLoadQueue.front(); // Load the model and add it to the scene. addObject(new Model(modelSpec.first.c_str(), modelSpec.second.c_str())); mModelLoadQueue.pop(); } - if (mPause) { + if(mPause) { return; } @@ -83,8 +83,8 @@ void Scene::draw() { std::vector Scene::getObjects() const { // All scene objects must inherit from Qtk::Object. std::vector objects(mMeshes.begin(), mMeshes.end()); - for(auto model : mModels) { - objects.push_back(dynamic_cast(model)); + for(const auto & model : mModels) { + objects.push_back(model); if(objects.back() == nullptr) { return {}; } @@ -92,8 +92,8 @@ std::vector Scene::getObjects() const { return objects; } -Object * Scene::getObject(const QString & name) { - for(auto object : getObjects()) { +Object * Scene::getObject(const QString & name) const { + for(const auto & object : getObjects()) { if(object->getName() == name.toStdString()) { return object; } @@ -106,14 +106,14 @@ void Scene::setSkybox(Skybox * skybox) { mSkybox = skybox; } -void Qtk::Scene::initSceneObjectName(Qtk::Object * object) { +void Scene::initSceneObjectName(Object * object) { if(!mObjectCount.count(object->getName())) { mObjectCount[object->getName()] = 1; } else { mObjectCount[object->getName()]++; } auto count = mObjectCount[object->getName()]; - if (count > 1) { + if(count > 1) { object->setName(object->getName() + " (" + std::to_string(count) + ")"); } } diff --git a/src/qtk/scene.h b/src/qtk/scene.h index 780d0ca..4d9a585 100644 --- a/src/qtk/scene.h +++ b/src/qtk/scene.h @@ -105,8 +105,11 @@ namespace Qtk { * @param name The objectName to look for within this scene. * @return The found object or Q_NULLPTR if none found. */ - [[nodiscard]] Object * getObject(const QString & name); + [[nodiscard]] Object * getObject(const QString & name) const; + /** + * @return The number of objects within the scene with the given name. + */ [[nodiscard]] uint64_t getObjectCount(const QString & name) { return mObjectCount.count(name.toStdString()) ? mObjectCount[name.toStdString()]