3 Commits

Author SHA1 Message Date
7fbd4c0226 Fix CI. (#22) 2026-02-15 01:41:27 +00:00
ed604eb655 ToolBar buttons to add and remove objects. (#18) 2025-04-12 16:53:40 +00:00
f40366cb8d ToolBox widget cleanup. (#17) 2025-03-23 22:28:41 +00:00
20 changed files with 497 additions and 166 deletions

View File

@@ -43,13 +43,16 @@ jobs:
with: with:
version: ${{ env.QT_VERSION }} version: ${{ env.QT_VERSION }}
# Windows - name: Install pkgconfiglite
- name: Chocolatey Action
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
uses: crazy-max/ghaction-chocolatey@v2 uses: crazy-max/ghaction-chocolatey@v2
with: with:
args: install pkgconfiglite --checksum e87b5ea3c9142256af60f2d5b917aa63b571e6a0 --checksum-type sha1 args: install pkgconfiglite --checksum e87b5ea3c9142256af60f2d5b917aa63b571e6a0 --checksum-type sha1
- name: Install nsis
if: matrix.os == 'windows-latest'
uses: crazy-max/ghaction-chocolatey@v2
with:
args: install nsis
- name: Install Debian packaging dependencies - name: Install Debian packaging dependencies
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
@@ -191,6 +194,11 @@ jobs:
uses: crazy-max/ghaction-chocolatey@v2 uses: crazy-max/ghaction-chocolatey@v2
with: with:
args: install pkgconfiglite --checksum e87b5ea3c9142256af60f2d5b917aa63b571e6a0 --checksum-type sha1 args: install pkgconfiglite --checksum e87b5ea3c9142256af60f2d5b917aa63b571e6a0 --checksum-type sha1
- name: Install nsis
if: matrix.os == 'windows-latest'
uses: crazy-max/ghaction-chocolatey@v2
with:
args: install nsis
- name: Configure Qtk Library - name: Configure Qtk Library
shell: bash shell: bash

View File

@@ -39,7 +39,7 @@ jobs:
# Check the entire repo for source files to tidy # Check the entire repo for source files to tidy
files-changed-only: false files-changed-only: false
# Ignore qtk build and external assimp directories # Ignore qtk build and external assimp directories
ignore: '.github|build|extern/assimp/assimp' ignore: '.github|build|extern'
# Point to compile_commands.json produced by build # Point to compile_commands.json produced by build
database: 'build' database: 'build'
# Use thread comments as feedback # Use thread comments as feedback
@@ -61,8 +61,12 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: clang-format Check - uses: cpp-linter/cpp-linter-action@v2
uses: jidicula/clang-format-action@v4.9.0
with: with:
clang-format-version: '18' version: '18'
check-path: ${{ matrix.path }} style: 'file'
tidy-checks: ''
files-changed-only: false
ignore: '.github|build|extern'
extensions: 'cpp,h'
files: ${{ matrix.path }}

View File

@@ -157,8 +157,15 @@ list(APPEND VAR_NAMES QT6_INSTALL_PLUGINS)
# Find Assimp. # Find Assimp.
if(QTK_SUBMODULES) if(QTK_SUBMODULES)
if(APPLE)
# Avoid zlib redefining fdopen, causing build failures in apple clang.
# https://github.com/assimp/assimp/issues/6118
add_compile_definitions(-Dfdopen=fdopen)
endif()
if(NOT WIN32)
# Required to statically link. # Required to statically link.
add_compile_options(-fPIC) add_compile_options(-fPIC)
endif()
set(BUILD_SHARED_LIBS OFF CACHE STRING "Build static assimp libs" FORCE) set(BUILD_SHARED_LIBS OFF CACHE STRING "Build static assimp libs" FORCE)
set(ASSIMP_BUILD_ZLIB ON CACHE STRING "Build Zlib with assimp." FORCE) set(ASSIMP_BUILD_ZLIB ON CACHE STRING "Build Zlib with assimp." FORCE)
set( set(
@@ -175,6 +182,14 @@ if(QTK_SUBMODULES)
"${CMAKE_CURRENT_SOURCE_DIR}/extern/assimp/assimp/" "${CMAKE_CURRENT_SOURCE_DIR}/extern/assimp/assimp/"
EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL
) )
install(
TARGETS assimp zlibstatic
EXPORT qtk_export
COMPONENT qtk
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
else() else()
find_package(assimp REQUIRED) find_package(assimp REQUIRED)
endif() endif()

View File

@@ -90,7 +90,7 @@ The Ubuntu apt repositories contain all the packages we need to build all target
To build Qtk desktop application with the scene in the screenshots below run the following commands. To build Qtk desktop application with the scene in the screenshots below run the following commands.
```bash ```bash
sudo apt update && sudo apt install cmake build-essential git ccache libxkbcommon-dev libassimp-dev qt6-base-dev qt6-tools-dev sudo apt update && sudo apt install cmake build-essential git ccache libxkbcommon-dev libassimp-dev qt6-base-dev qt6-tools-dev zlib1g-dev
cmake -DQTK_GUI_SCENE=ON -B build cmake -DQTK_GUI_SCENE=ON -B build
cmake --build build cmake --build build
./build/bin/qtk_gui ./build/bin/qtk_gui

View File

@@ -57,7 +57,7 @@ endif()
# Allow add_subdirectory on this project to use target ALIAS if available. # 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 this example project is opened standalone we will use find_package.
if(NOT TARGET Qtk::qtk) if(NOT TARGET Qtk::qtk)
find_package(Qtk 0.2 REQUIRED) find_package(Qtk 0.3 REQUIRED)
endif() endif()
find_package(Qt6 COMPONENTS Core Widgets OpenGLWidgets REQUIRED) find_package(Qt6 COMPONENTS Core Widgets OpenGLWidgets REQUIRED)

View File

@@ -48,6 +48,16 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent)
&Qtk::ToolBox::updateFocus); &Qtk::ToolBox::updateFocus);
} }
connect(ui_->actionDelete_Object,
&QAction::triggered,
this,
&MainWindow::deleteObject);
connect(ui_->actionLoad_Model,
&QAction::triggered,
this,
&MainWindow::loadObject);
// TODO: Fix / use MainWindow in Qt Designer to add these dock widgets. // TODO: Fix / use MainWindow in Qt Designer to add these dock widgets.
// For now we will add them manually, but we should be able to do this in the // For now we will add them manually, but we should be able to do this in the
// designer. At the moment if you edit the UI in designer the dock widget // designer. At the moment if you edit the UI in designer the dock widget
@@ -104,6 +114,26 @@ void MainWindow::refreshScene(const QString & sceneName)
ui_->qtk__TreeView->updateView(getQtkWidget()->getScene()); ui_->qtk__TreeView->updateView(getQtkWidget()->getScene());
} }
void MainWindow::deleteObject()
{
if (auto object = ui_->qtk__ToolBox->getObjectFocus(); object != Q_NULLPTR) {
auto scene = getQtkWidget()->getScene();
switch (object->getType()) {
case Qtk::Object::Type::QTK_MESH:
scene->removeObject(dynamic_cast<Qtk::MeshRenderer *>(object));
ui_->qtk__ToolBox->clearFocus();
break;
case Qtk::Object::Type::QTK_MODEL:
scene->removeObject(dynamic_cast<Qtk::Model *>(object));
ui_->qtk__ToolBox->clearFocus();
break;
default:
qDebug() << "Failed to delete model with invalid type";
break;
}
}
}
void MainWindow::setScene(Qtk::Scene * scene) void MainWindow::setScene(Qtk::Scene * scene)
{ {
connect(scene, connect(scene,

View File

@@ -11,8 +11,8 @@
#include <unordered_map> #include <unordered_map>
#include <QFileDialog>
#include <QMainWindow> #include <QMainWindow>
#include <QPlainTextEdit>
#include "designer-plugins/debugconsole.h" #include "designer-plugins/debugconsole.h"
@@ -144,6 +144,23 @@ class MainWindow : public QMainWindow
*/ */
void refreshScene(const QString & sceneName); void refreshScene(const QString & sceneName);
/**
* Opens a QFileDialog for selecting an object file to load into the scene.
*/
void loadObject()
{
const QUrl file = QFileDialog::getOpenFileName(
this, tr("Load Model"), QDir::homePath(), tr("Object Files (*.obj)"));
getQtkWidget()->getScene()->loadModel(file.fileName().replace(".obj", ""),
file.toString());
}
/**
* Deletes the currently selected object from the scene.
*/
void deleteObject();
private: private:
/*************************************************************************** /***************************************************************************
* Private Members * Private Members

View File

@@ -222,6 +222,9 @@
</property> </property>
</action> </action>
<action name="actionLoad_Model"> <action name="actionLoad_Model">
<property name="enabled">
<bool>true</bool>
</property>
<property name="icon"> <property name="icon">
<iconset> <iconset>
<normaloff>:/icons/fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg</normaloff>:/icons/fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg</iconset> <normaloff>:/icons/fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg</normaloff>:/icons/fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg</iconset>
@@ -234,6 +237,9 @@
</property> </property>
</action> </action>
<action name="actionDelete_Object"> <action name="actionDelete_Object">
<property name="enabled">
<bool>true</bool>
</property>
<property name="icon"> <property name="icon">
<iconset> <iconset>
<normaloff>:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg</normaloff>:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg</iconset> <normaloff>:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg</normaloff>:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg</iconset>

View File

@@ -482,21 +482,9 @@ void QtkScene::update()
myCube->getTransform().rotate(-0.75f, 0.0f, 1.0f, 0.0f); 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; QMatrix4x4 posMatrix;
if (auto alien = getModel("alienTest"); alien) { if (auto alien = getModel("alienTest"); alien) {
setLightPosition("alienTestLight", alien); alien->setLightPosition("alienTestLight");
alien->setUniform("uCameraPosition", cameraPosition); alien->setUniform("uCameraPosition", cameraPosition);
posMatrix = alien->getTransform().toMatrix(); posMatrix = alien->getTransform().toMatrix();
@@ -508,7 +496,7 @@ void QtkScene::update()
} }
if (auto spartan = getModel("spartanTest"); spartan) { if (auto spartan = getModel("spartanTest"); spartan) {
setLightPosition("spartanTestLight", spartan); spartan->setLightPosition("spartanTestLight");
spartan->setUniform("uCameraPosition", cameraPosition); spartan->setUniform("uCameraPosition", cameraPosition);
posMatrix = spartan->getTransform().toMatrix(); posMatrix = spartan->getTransform().toMatrix();
@@ -520,7 +508,7 @@ void QtkScene::update()
} }
if (auto phong = getModel("testPhong"); phong) { if (auto phong = getModel("testPhong"); phong) {
setLightPosition("testLight", phong); phong->setLightPosition("testLight");
phong->getTransform().rotate(0.75f, 1.0f, 0.5f, 0.0f); phong->getTransform().rotate(0.75f, 1.0f, 0.5f, 0.0f);
phong->bindShaders(); phong->bindShaders();

View File

@@ -16,20 +16,121 @@
using namespace Qtk; 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), objectFocus_(Q_NULLPTR)
{ {
ui->setupUi(this); ui->setupUi(this);
setMinimumWidth(350); 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<QWidget *>(&transformPanel_));
properiesForm_->addRow(reinterpret_cast<QWidget *>(&scalePanel_));
ui->toolBox->setCurrentWidget(ui->page_properties);
// Shader views.
ui->page_shaders->setLayout(shaderForm_);
shaderForm_->addRow(reinterpret_cast<QWidget *>(&vertex_));
shaderForm_->addRow(reinterpret_cast<QWidget *>(&fragment_));
} }
void ToolBox::updateFocus(const QString & name) void ToolBox::updateFocus(const QString & name)
{ {
auto object = auto object =
Qtk::QtkWidget::mWidgetManager.get_widget()->getScene()->getObject(name); QtkWidget::mWidgetManager.get_widget()->getScene()->getObject(name);
if (object != Q_NULLPTR) { // If we can't find the object show a warning.
removePages(); if (object == Q_NULLPTR) {
createPageProperties(object); qDebug() << "Failed to find selected object: " << name
createPageShader(object); << "; Clearing object panels.";
}
// We should still pass the nullptr here if we failed to find the object
// above.
objectFocus_ = object;
refreshProperties(object);
refreshShaders(object);
}
void ToolBox::clearFocus()
{
objectFocus_ = Q_NULLPTR;
refreshProperties(objectFocus_);
refreshShaders(objectFocus_);
}
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<double>::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)
{
// Zero the panel contents if there is no object selected.
if (object == Q_NULLPTR) {
spinBox3D.clear();
return;
}
// 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)
{
// Zero the panel contents if there is no object selected.
if (object == Q_NULLPTR) {
spinBox3D.clear();
return;
}
// 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 +139,32 @@ ToolBox::~ToolBox()
delete ui; delete ui;
} }
void ToolBox::removePages() void ToolBox::refreshProperties(const Object * object)
{ {
// Remove all existing pages. // Refresh to show the new object's details.
for (size_t i = 0; i < ui->toolBox->count(); i++) { objectDetails_.setObject(object);
delete ui->toolBox->widget(i); // Reconnect transform panel controls to the new object.
ui->toolBox->removeItem(i); transformPanel_.setObject(object);
} scalePanel_.setObject(object);
} }
void ToolBox::createPageProperties(const Object * object) void ToolBox::refreshShaders(const Object * object)
{ {
auto transform = object->getTransform(); // Zero the panel contents if there is no object selected.
auto type = object->getType(); if (object == Q_NULLPTR) {
auto * widget = new QWidget; vertex_.clear();
ui->toolBox->addItem(widget, "Properties"); fragment_.clear();
ui->toolBox->setCurrentWidget(widget); return;
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<double>::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; vertex_.path.setValue(object->getVertexShader().c_str());
rowLayout->addWidget(new QLabel(tr("Scale:"))); vertex_.editor->setText(object->getVertexShaderSourceCode().c_str());
for (size_t i = 0; i < 3; i++) { fragment_.path.setValue(object->getFragmentShader().c_str());
auto spinBox = new QDoubleSpinBox; fragment_.editor->setText(object->getFragmentShaderSourceCode().c_str());
spinBox->setMinimum(std::numeric_limits<double>::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) void ToolBox::refresh(const Object * object)
{ {
// Shaders page. refreshProperties(object);
auto widget = new QWidget; refreshShaders(object);
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);
} }

View File

@@ -13,10 +13,13 @@
#include <QDesignerExportWidget> #include <QDesignerExportWidget>
#include <QDockWidget> #include <QDockWidget>
#include <QDoubleSpinBox> #include <QDoubleSpinBox>
#include <QGroupBox> #include <QFormLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QTextEdit>
#include "qtk/object.h"
#include "qtk/scene.h"
namespace Ui namespace Ui
{ {
@@ -25,7 +28,7 @@ namespace Ui
namespace Qtk namespace Qtk
{ {
class ToolBox : public QDockWidget class ToolBox final : public QDockWidget
{ {
Q_OBJECT Q_OBJECT
@@ -38,20 +41,184 @@ namespace Qtk
~ToolBox(); ~ToolBox();
void removePages(); void refreshProperties(const Object * object);
void createPageProperties(const Object * object); void refreshShaders(const Object * object);
void createPageShader(const Object * object); void refresh(const Object * object);
void updateFocus(const QString & name); void updateFocus(const QString & name);
[[nodiscard]] Object * getObjectFocus() const { return objectFocus_; }
void clearFocus();
private: private:
/************************************************************************* /*************************************************************************
* Private Members * 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) const { value->setText(tr(v)); }
void setItem(const char * l, const char * v) const
{
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) const
{
// Zero contents if there is no object selected.
if (object == Q_NULLPTR) {
name.setValue("");
objectType.setValue("No object selected");
return;
}
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:");
/// Zero the SpinBox3D display.
void clear()
{
for (auto & field : fields) {
field->disconnect();
field->spinBox->setValue(0.0);
}
}
/// 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<SpinBox *, 3> 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);
}
/// Zero the ShaderView display.
void clear() const
{
path.setValue("");
editor->setText("");
}
/// 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_;
Object * objectFocus_ {};
Ui::ToolBox * ui; Ui::ToolBox * ui;
}; };
} // namespace Qtk } // namespace Qtk

View File

@@ -43,7 +43,7 @@ void Qtk::TreeView::updateView(const Qtk::Scene * scene)
mSceneName = scene->getSceneName(); mSceneName = scene->getSceneName();
auto objects = scene->getObjects(); auto objects = scene->getObjects();
for (const auto & object : objects) { 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)); ui->treeWidget->insertTopLevelItem(0, new QTreeWidgetItem(list));
} }
} }

View File

@@ -45,7 +45,7 @@ MeshRenderer::MeshRenderer(const char * name, const ShapeBase & shape) :
MeshRenderer::~MeshRenderer() MeshRenderer::~MeshRenderer()
{ {
sInstances.remove(mName.c_str()); sInstances.remove(mName);
} }
/******************************************************************************* /*******************************************************************************

View File

@@ -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 // 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]; return mManager[name];
} }
@@ -102,7 +113,7 @@ void Model::loadModel(const std::string & path)
sortModelMeshes(); sortModelMeshes();
// Object finished loading, insert it into ModelManager // 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) void Model::processNode(aiNode * node, const aiScene * scene)
@@ -261,13 +272,11 @@ ModelMesh::Textures Model::loadMaterialTextures(aiMaterial * mat,
void Model::sortModelMeshes() void Model::sortModelMeshes()
{ {
auto cameraPos = Scene::getCamera().getTransform(); auto cameraPos = Scene::getCamera().getTransform().getTranslation();
auto cameraDistance = [&cameraPos](const ModelMesh & a, const ModelMesh & b) { auto cameraDistance = [&cameraPos](const ModelMesh & a, const ModelMesh & b) {
// Sort by the first vertex position in the model // Sort by the first vertex position in the model
return (cameraPos.getTranslation().distanceToPoint( return cameraPos.distanceToPoint(a.mVertices[0].mPosition)
a.mVertices[0].mPosition)) < cameraPos.distanceToPoint(b.mVertices[0].mPosition);
< (cameraPos.getTranslation().distanceToPoint(
b.mVertices[0].mPosition));
}; };
std::sort(mMeshes.begin(), mMeshes.end(), cameraDistance); std::sort(mMeshes.begin(), mMeshes.end(), cameraDistance);
} }

View File

@@ -63,7 +63,7 @@ namespace Qtk
loadModel(mModelPath); loadModel(mModelPath);
} }
inline ~Model() override { mManager.remove(getName().c_str()); } inline ~Model() override { mManager.remove(getName()); }
/************************************************************************* /*************************************************************************
* Public Methods * 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 * Accessors
************************************************************************/ ************************************************************************/

View File

@@ -100,7 +100,7 @@ namespace Qtk
return mShape.mVertices; 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; } [[nodiscard]] inline const Type & getType() const { return mType; }
@@ -143,7 +143,7 @@ namespace Qtk
* Setters * 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) virtual inline void setColors(const Colors & value)
{ {
@@ -244,6 +244,23 @@ namespace Qtk
mProgram.release(); 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:
/************************************************************************* /*************************************************************************
* Private Members * Private Members
@@ -255,7 +272,7 @@ namespace Qtk
Transform3D mTransform; Transform3D mTransform;
Shape mShape; Shape mShape;
Texture mTexture; Texture mTexture;
std::string mName; QString mName;
bool mBound; bool mBound;
Type mType = QTK_OBJECT; Type mType = QTK_OBJECT;
}; };

View File

@@ -55,6 +55,34 @@ template <> Model * Scene::addObject(Model * object)
return object; return object;
} }
template <> void Scene::removeObject(MeshRenderer * object)
{
auto it = std::find(mMeshes.begin(), mMeshes.end(), object);
if (it == mMeshes.end()) {
qDebug() << "[Scene::removeObject]: Failed to remove object: "
<< object->getName() << " (" << object << ")";
return;
}
--mObjectCount[object->getName()];
mMeshes.erase(it);
emit sceneUpdated(mSceneName);
}
template <> void Scene::removeObject(Model * object)
{
auto it = std::find(mModels.begin(), mModels.end(), object);
if (it == mModels.end()) {
qDebug() << "[Scene::removeObject]: Failed to remove object: "
<< object->getName() << " (" << object << ")";
return;
}
--mObjectCount[object->getName()];
mModels.erase(it);
emit sceneUpdated(mSceneName);
}
void Scene::draw() void Scene::draw()
{ {
if (!mInit) { if (!mInit) {
@@ -103,7 +131,7 @@ std::vector<Object *> Scene::getObjects() const
Object * Scene::getObject(const QString & name) const Object * Scene::getObject(const QString & name) const
{ {
for (const auto & object : getObjects()) { for (const auto & object : getObjects()) {
if (object->getName() == name.toStdString()) { if (object->getName() == name) {
return object; return object;
} }
} }
@@ -121,6 +149,6 @@ void Scene::initSceneObjectName(Object * object)
// If the object name exists make it unique. // If the object name exists make it unique.
auto count = ++mObjectCount[object->getName()]; auto count = ++mObjectCount[object->getName()];
if (count > 1) { if (count > 1) {
object->setName(object->getName() + " (" + std::to_string(count) + ")"); object->setName(object->getName() + " (" + QString::number(count) + ")");
} }
} }

View File

@@ -85,16 +85,16 @@ namespace Qtk
void loadModel(const QUrl & url) void loadModel(const QUrl & url)
{ {
auto fileName = url.fileName().replace(".obj", "").toStdString(); auto fileName = url.fileName().replace(".obj", "");
auto filePath = url.toLocalFile().toStdString(); auto filePath = url.toLocalFile();
loadModel(fileName, filePath); loadModel(fileName, filePath);
} }
void loadModel(const std::string & name, const std::string & path) void loadModel(const QString & name, const QString & path)
{ {
// Add the dropped model to the load queue. // Add the dropped model to the load queue.
// This is consumed during rendering of the scene if not empty. // This is consumed during rendering of the scene if not empty.
mModelLoadQueue.emplace(name, path); mModelLoadQueue.emplace(name.toStdString(), path.toStdString());
} }
/************************************************************************* /*************************************************************************
@@ -120,9 +120,7 @@ namespace Qtk
*/ */
[[nodiscard]] uint64_t getObjectCount(const QString & name) [[nodiscard]] uint64_t getObjectCount(const QString & name)
{ {
return mObjectCount.count(name.toStdString()) return mObjectCount.count(name) ? mObjectCount[name] : 0;
? mObjectCount[name.toStdString()]
: 0;
} }
/** /**
@@ -184,8 +182,9 @@ namespace Qtk
/** /**
* Adds objects to the scene. * Adds objects to the scene.
* This template provides explicit specializations for valid types. * This template provides explicit specializations for the valid types:
* Adding any object other than these types will cause errors. * MeshRenderer, Model
* Any other object type will cause errors.
* TODO: Refactor to use Object base class container for scene objects. * TODO: Refactor to use Object base class container for scene objects.
* *
* If creating a new object type for a scene, it must inherit Qtk::Object * If creating a new object type for a scene, it must inherit Qtk::Object
@@ -196,6 +195,17 @@ namespace Qtk
*/ */
template <typename T> T * addObject(T * object); template <typename T> T * addObject(T * object);
/**
* Removes an object from the scene.
* This template provides explicit specializations for the valid types:
* MeshRenderer, Model
* Any other object type will cause errors.
* TODO: Refactor to use Object base class container for scene objects.
*
* @param object Pointer to the object to remove from the scene.
*/
template <typename T> void removeObject(T * object);
/** /**
* @param name The name to use for this scene. * @param name The name to use for this scene.
*/ */
@@ -248,7 +258,7 @@ namespace Qtk
/* MeshRenderers used simple geometry. */ /* MeshRenderers used simple geometry. */
std::vector<MeshRenderer *> mMeshes {}; std::vector<MeshRenderer *> mMeshes {};
/* Track count of objects with same initial name. */ /* Track count of objects with same initial name. */
std::unordered_map<std::string, uint64_t> mObjectCount; std::unordered_map<QString, uint64_t> mObjectCount;
}; };
} // namespace Qtk } // namespace Qtk

View File

@@ -109,11 +109,12 @@ void main()
#version 330 #version 330
uniform samplerCube uTexture; uniform samplerCube uTexture;
varying vec3 vTexCoord; in vec3 vTexCoord;
out vec4 FragColor;
void main() void main()
{ {
gl_FragColor = texture(uTexture, vTexCoord); FragColor = texture(uTexture, vTexCoord);
} }
)" )"