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