diff --git a/example-app/CMakeLists.txt b/example-app/CMakeLists.txt index 7bc945d..28918bf 100644 --- a/example-app/CMakeLists.txt +++ b/example-app/CMakeLists.txt @@ -57,7 +57,7 @@ endif() # Allow add_subdirectory on this project to use target ALIAS if available. # If this example project is opened standalone we will use find_package. if(NOT TARGET Qtk::qtk) - find_package(Qtk 0.2 REQUIRED) + find_package(Qtk 0.3 REQUIRED) endif() find_package(Qt6 COMPONENTS Core Widgets OpenGLWidgets REQUIRED) @@ -100,4 +100,4 @@ if((Qt6_VERSION VERSION_GREATER_EQUAL "6.3.0" AND (WIN32 OR APPLE)) install(SCRIPT ${QTK_EXAMPLE_DEPLOY_SCRIPT} COMPONENT qtk_example) elseif(NOT Qtk_IS_TOP_LEVEL) message(WARNING "[Qtk] Installation is only supported on Qt >=6.5.\n") -endif() \ No newline at end of file +endif() diff --git a/src/app/qtkmainwindow.ui b/src/app/qtkmainwindow.ui index d31489c..3078ef7 100644 --- a/src/app/qtkmainwindow.ui +++ b/src/app/qtkmainwindow.ui @@ -222,6 +222,9 @@ + + false + :/icons/fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg:/icons/fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg @@ -234,6 +237,9 @@ + + false + :/icons/fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg diff --git a/src/app/qtkscene.cpp b/src/app/qtkscene.cpp index 2fe1d71..4322432 100644 --- a/src/app/qtkscene.cpp +++ b/src/app/qtkscene.cpp @@ -482,21 +482,9 @@ void QtkScene::update() myCube->getTransform().rotate(-0.75f, 0.0f, 1.0f, 0.0f); } - // Helper lambda to set the light position used by GLSL shaders on the model. - // TODO: This could be a helper function on the Model class. - auto setLightPosition = [](const std::string & lightName, Model * model) { - if (auto light = Model::getInstance(lightName.c_str()); light) { - QVector3D position = light->getTransform().getTranslation(); - model->setUniform("uLight.position", position); - } else { - qDebug() << "[QtkScene] Failed to set light position: " - << lightName.c_str(); - } - }; - QMatrix4x4 posMatrix; if (auto alien = getModel("alienTest"); alien) { - setLightPosition("alienTestLight", alien); + alien->setLightPosition("alienTestLight"); alien->setUniform("uCameraPosition", cameraPosition); posMatrix = alien->getTransform().toMatrix(); @@ -508,7 +496,7 @@ void QtkScene::update() } if (auto spartan = getModel("spartanTest"); spartan) { - setLightPosition("spartanTestLight", spartan); + spartan->setLightPosition("spartanTestLight"); spartan->setUniform("uCameraPosition", cameraPosition); posMatrix = spartan->getTransform().toMatrix(); @@ -520,7 +508,7 @@ void QtkScene::update() } if (auto phong = getModel("testPhong"); phong) { - setLightPosition("testLight", phong); + phong->setLightPosition("testLight"); phong->getTransform().rotate(0.75f, 1.0f, 0.5f, 0.0f); phong->bindShaders(); diff --git a/src/designer-plugins/toolbox.cpp b/src/designer-plugins/toolbox.cpp index b59b55e..4d57129 100644 --- a/src/designer-plugins/toolbox.cpp +++ b/src/designer-plugins/toolbox.cpp @@ -16,20 +16,94 @@ using namespace Qtk; -ToolBox::ToolBox(QWidget * parent) : QDockWidget(parent), ui(new Ui::ToolBox) +ToolBox::ToolBox(QWidget * parent) : + QDockWidget(parent), objectDetails_(this), transformPanel_(this), + scalePanel_(this), vertex_(this, "Vertex Shader:"), + fragment_(this, "Fragment Shader:"), properiesForm_(new QFormLayout), + shaderForm_(new QFormLayout), ui(new Ui::ToolBox) { ui->setupUi(this); setMinimumWidth(350); + + // Object Properties. + ui->page_properties->setLayout(properiesForm_); + properiesForm_->addRow(objectDetails_.name.label, objectDetails_.name.value); + properiesForm_->addRow(objectDetails_.objectType.label, + objectDetails_.objectType.value); + properiesForm_->addRow(reinterpret_cast(&transformPanel_)); + properiesForm_->addRow(reinterpret_cast(&scalePanel_)); + ui->toolBox->setCurrentWidget(ui->page_properties); + + // Shader views. + ui->page_shaders->setLayout(shaderForm_); + shaderForm_->addRow(reinterpret_cast(&vertex_)); + shaderForm_->addRow(reinterpret_cast(&fragment_)); } void ToolBox::updateFocus(const QString & name) { auto object = - Qtk::QtkWidget::mWidgetManager.get_widget()->getScene()->getObject(name); + QtkWidget::mWidgetManager.get_widget()->getScene()->getObject(name); if (object != Q_NULLPTR) { - removePages(); - createPageProperties(object); - createPageShader(object); + refreshProperties(object); + refreshShaders(object); + } +} + +ToolBox::SpinBox3D::SpinBox3D(QWidget * parent, const char * l) : + QWidget(parent), layout(new QHBoxLayout(this)), label(new QLabel(tr(l))) +{ + // The layout owns the widget and will clean it up on destruction. + layout->addWidget(label); + for (const auto & f : fields) { + layout->addWidget(f->spinBox); + f->spinBox->setMinimum(std::numeric_limits::lowest()); + f->spinBox->setSingleStep(0.1); + f->spinBox->setFixedWidth(75); + } +} + +void ToolBox::SpinBox::disconnect() const +{ + Object::disconnect(connection); +} + +void ToolBox::TransformPanel::setObject(const Qtk::Object * object) +{ + // Reconnect translation panel controls to the new object. + const std::vector binds = {&Object::setTranslationX, + &Object::setTranslationY, + &Object::setTranslationZ}; + for (size_t i = 0; i < spinBox3D.fields.size(); i++) { + auto * f = spinBox3D.fields[i]; + // Disconnect before changing spin box value. + f->disconnect(); + + // Set the values in the spin box to the object's current X,Y,Z + f->spinBox->setValue(object->getTransform().getTranslation()[i]); + + // Reconnect to bind spin box value to the new object's position. + f->connection = + connect(f->spinBox, &QDoubleSpinBox::valueChanged, object, binds[i]); + } +} + +void ToolBox::ScalePanel::setObject(const Qtk::Object * object) +{ + // Reconnect scale panel controls to the new object. + const std::vector binds = { + &Object::setScaleX, &Object::setScaleY, &Object::setScaleZ}; + for (size_t i = 0; i < spinBox3D.fields.size(); i++) { + auto * f = spinBox3D.fields[i]; + // Disconnect before changing spin box value. + f->disconnect(); + + // Set the values in the spin box to the object's current X,Y,Z + f->spinBox->setValue(object->getTransform().getScale()[i]); + + // Reconnect to bind spin box value to the new object's scale. + f->connection = + connect(f->spinBox, &QDoubleSpinBox::valueChanged, object, binds[i]); } } @@ -38,110 +112,19 @@ ToolBox::~ToolBox() delete ui; } -void ToolBox::removePages() +void ToolBox::refreshProperties(const Object * object) { - // Remove all existing pages. - for (size_t i = 0; i < ui->toolBox->count(); i++) { - delete ui->toolBox->widget(i); - ui->toolBox->removeItem(i); - } + // Refresh to show the new object's details. + objectDetails_.setObject(object); + // Reconnect transform panel controls to the new object. + transformPanel_.setObject(object); + scalePanel_.setObject(object); } -void ToolBox::createPageProperties(const Object * object) +void ToolBox::refreshShaders(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); - shaderView->setText(object->getVertexShaderSourceCode().c_str()); - 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); - shaderView->setText(object->getFragmentShaderSourceCode().c_str()); - mainLayout->addRow(shaderView); - - widget->setLayout(mainLayout); + vertex_.path.setValue(object->getVertexShader().c_str()); + vertex_.editor->setText(object->getVertexShaderSourceCode().c_str()); + fragment_.path.setValue(object->getFragmentShader().c_str()); + fragment_.editor->setText(object->getFragmentShaderSourceCode().c_str()); } diff --git a/src/designer-plugins/toolbox.h b/src/designer-plugins/toolbox.h index 674fec4..6eb9769 100644 --- a/src/designer-plugins/toolbox.h +++ b/src/designer-plugins/toolbox.h @@ -13,7 +13,10 @@ #include #include #include -#include +#include +#include +#include +#include #include "qtk/scene.h" @@ -25,7 +28,7 @@ namespace Ui namespace Qtk { - class ToolBox : public QDockWidget + class ToolBox final : public QDockWidget { Q_OBJECT @@ -38,11 +41,9 @@ namespace Qtk ~ToolBox(); - void removePages(); + void refreshProperties(const Object * object); - void createPageProperties(const Object * object); - - void createPageShader(const Object * object); + void refreshShaders(const Object * object); void updateFocus(const QString & name); @@ -52,6 +53,143 @@ namespace Qtk * Private Members ************************************************************************/ + /// Displays details on the object. + struct ObjectDetails { + /// Single item containing a label and value. + struct Item { + explicit Item(QWidget * parent, + const char * l = "Item:", + const char * v = "") : + label(new QLabel(tr(l), parent)), + value(new QLabel(tr(v), parent)) + { + } + + void setValue(const char * v) { value->setText(tr(v)); } + + void setItem(const char * l, const char * v) + { + label->setText(tr(l)); + value->setText(tr(v)); + } + + QLabel * label; + QLabel * value; + }; + + /// We pass the parent widget so that Qt handles releasing memory. + explicit ObjectDetails(QWidget * parent) : + name(parent, "Name:"), objectType(parent, "Object Type:") + { + } + + /// Refresh to display the new object's details + void setObject(const Qtk::Object * object) + { + name.setItem("Name:", object->getName().toStdString().c_str()); + objectType.setItem( + "Type:", + object->getType() == Object::QTK_MESH ? "Mesh" : "Model"); + } + + Item name, objectType; + }; + ObjectDetails objectDetails_; + + /// Structure to associate a QSpinBox with a connection. + struct SpinBox { + /** + * Default constructor passes no parent to the QSpinBox. + * It must be added to a layout for Qt to clean up the resources. + */ + SpinBox() : spinBox(new QDoubleSpinBox) {} + + /// Disconnect the associated connection. + void disconnect() const; + + QDoubleSpinBox * spinBox; + QMetaObject::Connection connection; + }; + + /// Spinbox with 3 fields and a single label. + class SpinBox3D final : QWidget + { + public: + /// We pass a parent to ensure Qt will clean up resources. + /// Assigning a QWidget to a QLayout also ensures Qt will clean up. + explicit SpinBox3D(QWidget * parent, const char * l = "SpinBox3D:"); + + /// The main layout for the SpinBox3D widget. + QHBoxLayout * layout; + + /// Label for the SpinBox3D. + QLabel * label; + + /// SpinBox and a connection for each field. + SpinBox x, y, z; + /// Array for iterating over fields. + std::array fields {&x, &y, &z}; + }; + + /// Initialize the transform panel and configure QObject connections. + struct TransformPanel { + explicit TransformPanel(QWidget * parent) : + spinBox3D(parent, "Transform:") + { + } + + /// Reconnect QObject connections and spin box values in UI. + void setObject(const Qtk::Object * object); + + SpinBox3D spinBox3D; + }; + TransformPanel transformPanel_; + + /// Initialize the scale panel and configure QObject connections. + struct ScalePanel { + explicit ScalePanel(QWidget * parent) : spinBox3D(parent, "Scale:") {} + + /// Reconnect QObject connections and spin box values in UI. + void setObject(const Qtk::Object * object); + + SpinBox3D spinBox3D; + }; + ScalePanel scalePanel_; + + /// Displays shader name, path, and read-only text view. + class ShaderView final : QWidget + { + public: + explicit ShaderView(QWidget * parent, + const char * l = "ShaderView:") : + layout(new QVBoxLayout(this)), path(parent, l), + editor(new QTextEdit(parent)) + { + // Create a child horizontal layout for shader name and file path. + auto * pathLayout = new QHBoxLayout; + pathLayout->addWidget(path.label); + pathLayout->addWidget(path.value); + layout->addLayout(pathLayout); + + // Add the read-only text editor widget to the main layout. + editor->setReadOnly(true); + layout->addWidget(editor); + } + + /// The main layout for the ShaderView widget. + QVBoxLayout * layout; + + /// Shader name and path on disk. + ObjectDetails::Item path; + + /// Read-only (for now) display of the shader source code. + QTextEdit * editor; + }; + ShaderView vertex_, fragment_; + + QFormLayout * properiesForm_; + QFormLayout * shaderForm_; + Ui::ToolBox * ui; }; } // namespace Qtk diff --git a/src/designer-plugins/treeview.cpp b/src/designer-plugins/treeview.cpp index e79220b..72f22c6 100644 --- a/src/designer-plugins/treeview.cpp +++ b/src/designer-plugins/treeview.cpp @@ -43,7 +43,7 @@ void Qtk::TreeView::updateView(const Qtk::Scene * scene) mSceneName = scene->getSceneName(); auto objects = scene->getObjects(); for (const auto & object : objects) { - QStringList list(QStringList(QString(object->getName().c_str()))); + QStringList list(QStringList(QString(object->getName()))); ui->treeWidget->insertTopLevelItem(0, new QTreeWidgetItem(list)); } } diff --git a/src/qtk/meshrenderer.cpp b/src/qtk/meshrenderer.cpp index 3a021ed..7551e3a 100644 --- a/src/qtk/meshrenderer.cpp +++ b/src/qtk/meshrenderer.cpp @@ -45,7 +45,7 @@ MeshRenderer::MeshRenderer(const char * name, const ShapeBase & shape) : MeshRenderer::~MeshRenderer() { - sInstances.remove(mName.c_str()); + sInstances.remove(mName); } /******************************************************************************* diff --git a/src/qtk/model.cpp b/src/qtk/model.cpp index dc88f38..114a837 100644 --- a/src/qtk/model.cpp +++ b/src/qtk/model.cpp @@ -56,8 +56,19 @@ void Model::flipTexture(const std::string & fileName, bool flipX, bool flipY) } } +void Model::setLightPosition(const QString & lightName, const char * uniform) +{ + if (auto light = MeshRenderer::getInstance(lightName); light) { + QVector3D position = light->getTransform().getTranslation(); + setUniform(uniform, position); + } else { + qDebug() << "[QtkScene] Failed to set " << mName + << "light position: " << lightName; + } +} + // Static function to access ModelManager for getting Models by name -Model * Qtk::Model::getInstance(const char * name) +Model * Model::getInstance(const char * name) { return mManager[name]; } @@ -102,7 +113,7 @@ void Model::loadModel(const std::string & path) sortModelMeshes(); // Object finished loading, insert it into ModelManager - mManager.insert(getName().c_str(), this); + mManager.insert(getName(), this); } void Model::processNode(aiNode * node, const aiScene * scene) @@ -261,13 +272,11 @@ ModelMesh::Textures Model::loadMaterialTextures(aiMaterial * mat, void Model::sortModelMeshes() { - auto cameraPos = Scene::getCamera().getTransform(); + auto cameraPos = Scene::getCamera().getTransform().getTranslation(); auto cameraDistance = [&cameraPos](const ModelMesh & a, const ModelMesh & b) { // Sort by the first vertex position in the model - return (cameraPos.getTranslation().distanceToPoint( - a.mVertices[0].mPosition)) - < (cameraPos.getTranslation().distanceToPoint( - b.mVertices[0].mPosition)); + return cameraPos.distanceToPoint(a.mVertices[0].mPosition) + < cameraPos.distanceToPoint(b.mVertices[0].mPosition); }; std::sort(mMeshes.begin(), mMeshes.end(), cameraDistance); } diff --git a/src/qtk/model.h b/src/qtk/model.h index ee1ed1d..64d15ee 100644 --- a/src/qtk/model.h +++ b/src/qtk/model.h @@ -63,7 +63,7 @@ namespace Qtk loadModel(mModelPath); } - inline ~Model() override { mManager.remove(getName().c_str()); } + inline ~Model() override { mManager.remove(getName()); } /************************************************************************* * Public Methods @@ -113,6 +113,14 @@ namespace Qtk } } + /** + * Sets the position of a light used in GLSL unfiroms. + * + * @param lightName Object name of the light + */ + void setLightPosition(const QString & lightName, + const char * uniform = "uLight.position"); + /************************************************************************* * Accessors ************************************************************************/ diff --git a/src/qtk/object.h b/src/qtk/object.h index 342c3e6..046ce0e 100644 --- a/src/qtk/object.h +++ b/src/qtk/object.h @@ -100,7 +100,7 @@ namespace Qtk return mShape.mVertices; } - [[nodiscard]] inline std::string getName() const { return mName; } + [[nodiscard]] inline QString getName() const { return mName; } [[nodiscard]] inline const Type & getType() const { return mType; } @@ -143,7 +143,7 @@ namespace Qtk * Setters ************************************************************************/ - virtual inline void setName(const std::string & name) { mName = name; } + virtual inline void setName(const QString & name) { mName = name; } virtual inline void setColors(const Colors & value) { @@ -244,6 +244,23 @@ namespace Qtk mProgram.release(); } + /************************************************************************* + * Public Static Methods + ************************************************************************/ + + /** + * Helper to disconnect a QObject connection, only if it's valid. + * If the connection is valid and we fail to disconnect log a message. + * + * @param con QObject connection handle to disconnect. + */ + static void disconnect(const QMetaObject::Connection & con) + { + if (con && !QObject::disconnect(con)) { + qDebug() << "[Qtk] Failed to disconnect valid connection: " << con; + } + } + private: /************************************************************************* * Private Members @@ -255,7 +272,7 @@ namespace Qtk Transform3D mTransform; Shape mShape; Texture mTexture; - std::string mName; + QString mName; bool mBound; Type mType = QTK_OBJECT; }; diff --git a/src/qtk/scene.cpp b/src/qtk/scene.cpp index 150d0ca..54fb66f 100644 --- a/src/qtk/scene.cpp +++ b/src/qtk/scene.cpp @@ -103,7 +103,7 @@ std::vector Scene::getObjects() const Object * Scene::getObject(const QString & name) const { for (const auto & object : getObjects()) { - if (object->getName() == name.toStdString()) { + if (object->getName() == name) { return object; } } @@ -121,6 +121,6 @@ void Scene::initSceneObjectName(Object * object) // If the object name exists make it unique. auto count = ++mObjectCount[object->getName()]; if (count > 1) { - object->setName(object->getName() + " (" + std::to_string(count) + ")"); + object->setName(object->getName() + " (" + QString::number(count) + ")"); } } diff --git a/src/qtk/scene.h b/src/qtk/scene.h index 446c1b2..a4f9180 100644 --- a/src/qtk/scene.h +++ b/src/qtk/scene.h @@ -120,9 +120,7 @@ namespace Qtk */ [[nodiscard]] uint64_t getObjectCount(const QString & name) { - return mObjectCount.count(name.toStdString()) - ? mObjectCount[name.toStdString()] - : 0; + return mObjectCount.count(name) ? mObjectCount[name] : 0; } /** @@ -248,7 +246,7 @@ namespace Qtk /* MeshRenderers used simple geometry. */ std::vector mMeshes {}; /* Track count of objects with same initial name. */ - std::unordered_map mObjectCount; + std::unordered_map mObjectCount; }; } // namespace Qtk