39 Commits

Author SHA1 Message Date
51643c4019 Factor out more resources.
+ Use QOpenGLShader to get compiled shader code in TreeView.
+ Remove last remaining Qt resources dependency in libqtk.
+ shaders.h in libqtk to define default GLSL shader programs.
2025-03-16 20:10:01 -04:00
f7b807d3a5 Include example scene header if needed. 2025-03-16 15:32:41 -04:00
d6dbb5c2ff Rename qtk_library target to qtk. 2025-03-16 14:53:52 -04:00
dab3fdc9bd Fix CI link in README. 2025-03-16 14:43:36 -04:00
8d7bbb75fb README and build options. 2025-03-16 14:36:48 -04:00
c9004027b9 Enable single-click to show object details.
+ Use run-clang-tidy in format script.
+ Fix missing return in QtkWidgetManager.
+ Fix CI tag ref.
+ Add output when starting GUI to explain controls.
2025-03-16 12:58:43 -04:00
0a71bdc1c0 Update header comments. 2025-03-15 15:05:20 -04:00
ac87635ff7 Fix example, set rcc cmake option. 2025-03-15 14:43:31 -04:00
4a1050e02f Remove large model resources from the build.
+ Remove Qtk::Skybox dependency on Qt resources.
2025-03-15 14:24:25 -04:00
8d60355ccf Remove QtkWidget dependency on MainWindow. 2025-03-15 13:10:42 -04:00
0551b4f91f Move MainWindow->Scene connection out of QtkWidget. 2025-03-15 12:37:09 -04:00
ecae09f82d Remove TreeView dependency on MainWindow. 2025-03-15 12:31:17 -04:00
e68d384340 Remove ToolBox MainWindow dependency. 2025-03-15 12:29:15 -04:00
e2c7f5ba75 Fix bug in unique object names. 2025-03-15 11:31:16 -04:00
f1d9a07940 Merge release and build workflow. 2025-03-15 11:19:43 -04:00
f5a38892b1 Move mainwindow to app/ 2025-03-15 11:18:12 -04:00
d230662924 Organize code for plugins. 2025-03-15 10:14:16 -04:00
97bf086a87 Cmake strings. 2025-03-15 09:10:10 -04:00
e97ccd8195 Combine NSIS cmake commands. 2025-03-15 09:05:22 -04:00
728710ece2 Add desktop shortcut for windows. 2025-03-14 17:40:16 -04:00
40119856de Fix release job trigger. 2025-03-14 17:35:48 -04:00
de9ef4c948 Add release CI. 2025-03-14 16:18:24 -04:00
b252922b0a Finish fixing windows CI.
:')
2025-03-14 15:49:23 -04:00
12883e8c20 Fix ci. 2025-03-14 15:42:17 -04:00
bdd178d0f2 Add windows changes. 2025-03-14 15:31:00 -04:00
9bd09b2bb0 Debug CI. 2025-03-14 15:22:36 -04:00
0d2a73a35f Cmake changes. 2025-03-14 15:07:51 -04:00
ea25ba312a README screenshots. 2025-03-10 18:15:12 -04:00
78639cf1c2 Reposition models in scene. 2025-03-09 11:11:09 -04:00
941f2d228c Clean up separation between plugins and app. 2025-03-08 14:09:56 -05:00
16baf6cdaf CI 2025-03-08 12:57:12 -05:00
c86a7744b3 Set clang-format argument options. 2025-03-08 11:52:39 -05:00
7fac6bafb4 Clang format.
Some checks failed
All Builds / Qtk (-DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG, -j $(nproc), ubuntu-latest) (push) Failing after 23s
All Builds / Qtk-Library (-DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG, -j $(nproc), ubuntu-latest) (push) Failing after 22s
All Builds / Qtk-Plugins (-DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG, -j $(nproc), ubuntu-latest) (push) Failing after 20s
All Builds / Qtk-Assimp-Targets (-DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/, ubuntu-latest) (push) Failing after 18s
Linting / Tidy (push) Failing after 24s
Linting / Format (app) (push) Failing after 23s
Linting / Format (src) (push) Failing after 23s
All Builds / Qtk (-DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG, -j $(nproc), macos-latest) (push) Has been cancelled
All Builds / Qtk (-DCMAKE_PREFIX_PATH=D:/a/qtk/qtk/Qt/$QT_VERSION/mingw81_64/ $CONFIG, , windows-latest) (push) Has been cancelled
All Builds / Qtk-Library (-DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG, -j $(nproc), macos-latest) (push) Has been cancelled
All Builds / Qtk-Library (-DCMAKE_PREFIX_PATH=D:/a/qtk/qtk/Qt/$QT_VERSION/mingw81_64/ $CONFIG, , windows-latest) (push) Has been cancelled
All Builds / Qtk-Plugins (-DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG, -j $(nproc), macos-latest) (push) Has been cancelled
All Builds / Qtk-Plugins (-DCMAKE_PREFIX_PATH=D:/a/qtk/qtk/Qt/$QT_VERSION/mingw81_64/ $CONFIG, , windows-latest) (push) Has been cancelled
All Builds / Qtk-Assimp-Targets (-DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/, macos-latest) (push) Has been cancelled
2025-03-08 11:47:19 -05:00
1bed9545c9 Clean up qtk. 2025-03-08 11:40:00 -05:00
32641acd8d Clean up example code.
+ Fix spartan path.
+ Fix triangle draw mode.
2025-03-08 10:46:53 -05:00
68bfff7bcd Modify clang-tidy checks and options.
+ Disable static accessed though instance.
+ Do not mark destructors with override.
2025-03-08 09:38:04 -05:00
92e5937cc7 Add default options for enabled clang-tidy checks. 2025-03-08 09:35:12 -05:00
1e1c328a5a clang-tidy example-app 2025-03-08 09:27:39 -05:00
dcbeb26738 README 2025-03-08 09:02:55 -05:00
32 changed files with 354 additions and 749 deletions

View File

@@ -43,16 +43,13 @@ jobs:
with:
version: ${{ env.QT_VERSION }}
- name: Install pkgconfiglite
# Windows
- name: Chocolatey Action
if: matrix.os == 'windows-latest'
uses: crazy-max/ghaction-chocolatey@v2
with:
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
if: matrix.os == 'ubuntu-latest'
@@ -194,11 +191,6 @@ jobs:
uses: crazy-max/ghaction-chocolatey@v2
with:
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
shell: bash
@@ -377,6 +369,7 @@ jobs:
- name: Download Installer Artifact
uses: actions/download-artifact@v4
with:
name: Qtk Packages
path: |
build/packages/*
install/*

View File

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

View File

@@ -4,7 +4,7 @@
## Author: Shaun Reed | Contact: shaunrd0@gmail.com | URL: www.shaunreed.com ##
## All Content (c) 2025 Shaun Reed, all rights reserved ##
################################################################################
cmake_minimum_required(VERSION 3.28)
cmake_minimum_required(VERSION 3.23)
################################################################################
# Constants
@@ -31,7 +31,7 @@ add_compile_options(-fPIC)
################################################################################
project(
#[[NAME]] Qtk
VERSION 0.3
VERSION 0.2
DESCRIPTION "Qt OpenGL library and desktop application."
LANGUAGES CXX C
)
@@ -65,7 +65,7 @@ option(QTK_PLUGINS "Install Qtk plugins to Qt Designer path." OFF)
# Options for qtk_gui
option(QTK_GUI "Build the Qtk desktop application" ON)
option(QTK_GUI_SCENE
"Fetch model resources and build the GUI with an example scene." OFF
"Fetch model resources and build the GUI with an example scene." ON
)
if (QTK_CCACHE)
@@ -84,7 +84,7 @@ endif()
set(QT_INSTALL_DIR "$ENV{HOME}/Qt/6.5.0/gcc_64/lib/cmake" CACHE PATH "Path to Qt6 install.")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install")
endif()
endif ()
set(QTK_RESOURCES "${CMAKE_SOURCE_DIR}/resources")
set(QTK_OSX_ICONS ${CMAKE_SOURCE_DIR}/resources/icons/osx/kilroy.icns)
@@ -157,15 +157,8 @@ list(APPEND VAR_NAMES QT6_INSTALL_PLUGINS)
# Find Assimp.
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.
add_compile_options(-fPIC)
endif()
# Required to statically link.
add_compile_options(-fPIC)
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(
@@ -182,14 +175,6 @@ if(QTK_SUBMODULES)
"${CMAKE_CURRENT_SOURCE_DIR}/extern/assimp/assimp/"
EXCLUDE_FROM_ALL
)
install(
TARGETS assimp zlibstatic
EXPORT qtk_export
COMPONENT qtk
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
else()
find_package(assimp REQUIRED)
endif()
@@ -220,25 +205,3 @@ foreach(VAR_NAME IN LISTS VAR_NAMES VAR_PATHS)
message(STATUS "[Qtk] ${VAR_NAME}=${${VAR_NAME}}")
endif()
endforeach()
message(STATUS "[Qtk] Installation prefix: ${CMAKE_INSTALL_PREFIX}")
message(STATUS "[Qtk] Found Qt6: ${Qt6Core_VERSION}")
# qt_generate_deploy_app_script is supported on Linux in QtCore >= 6.5.0.
# qt_generate_deploy_app_script supports Windows and OSX in QtCore >= 6.3.
# https://doc.qt.io/qt-6.5/qt-generate-deploy-app-script.html
# https://doc.qt.io/archives/qt-6.4/qt-generate-deploy-app-script.html
# The application can still build and run, we just can't install.
if(QTK_GUI OR QTK_EXAMPLE)
if(UNIX AND NOT APPLE)
# Ubuntu 24.04 provides QtCore 6.4.2 in qt-base-dev.
# This version of qt_generate_deploy_app_script does not support Linux.
if (Qt6_VERSION VERSION_LESS "6.5.0")
message(WARNING "[Qtk] Installation is only supported on Qt >=6.5.\n")
endif()
elseif(APPLE OR WIN32)
# Predates qt_generate_deploy_app_script.
if (Qt6_VERSION VERSION_LESS "6.3.0")
message(WARNING "[Qtk] Installation is only supported on Qt >=6.5.\n")
endif()
endif()
endif()

View File

@@ -1,6 +1,6 @@
# Qtk
[![All Builds](https://github.com/shaunrd0/qtk/actions/workflows/build.yml/badge.svg)](https://github.com/shaunrd0/qtk/actions/workflows/build.yml)
[![All Builds](https://github.com/shaunrd0/qtk/actions/workflows/all-builds.yml/badge.svg)](https://github.com/shaunrd0/qtk/actions/workflows/build.yml)
[![Linting](https://github.com/shaunrd0/qtk/actions/workflows/linting.yml/badge.svg)](https://github.com/shaunrd0/qtk/actions/workflows/linting.yml)
The Qtk desktop application provides a model loader using [Assimp](https://assimp.org/) within a Qt widget application.
@@ -60,14 +60,14 @@ Key features that are planned:
- [x] Runtime loading of `.obj` or similar 3D models.
- [x] Drag-and-drop interaction for adding objects to the scene.
- [x] Shader / object properties panel to modify related settings.
- [x] Reduce size of application resources and git references.
- [ ] Runtime reloading of modified GLSL shaders attached to objects within scenes.
- [ ] Multiple views of a scene at one time.
- [ ] Camera control modes such as panning, orbiting, or following objects.
- [ ] Save / load scene data. The current model requires writing C++ code.
- [ ] Basic text editor for quickly modifying shaders attached to objects.
- [ ] Reduce size of application resources and git references.
For examples of using libqtk, see the [example-app](./example-app)
For examples of using the libqtk API, see the [example-app](./example-app)
project in the root of this repository.
To get textures loading on models look
@@ -82,19 +82,13 @@ Simply open the root `CMakeLists.txt` with either of these editors and default
configurations will be loaded. To simplify providing Qt to the build, Qt Creator
is the recommended option.
If you have manually installed [Qt6 Open Source Binaries](https://www.qt.io/download-qt-installer)
for your system, be sure to correctly set your `CMAKE_PREFIX_PATH` in the next steps.
On Ubuntu 24.04 the default installation directory to use for this path using Qt 6.5.0 is `$HOME/Qt/6.5.0/gcc_64/lib/cmake`.
The Ubuntu apt repositories contain all the packages we need to build all targets.
To build Qtk desktop application with the scene in the screenshots below run the following commands.
```bash
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 --build build
./build/bin/qtk_gui
```
This project has been ported to **Qt 6.6.0**, which is not yet available in
Ubuntu apt repositories.
To run this project, you will *need* to
install [Qt6 Open Source Binaries](https://www.qt.io/download-qt-installer) for
your system, **version 6.6.0** or later.
Be sure to take note of the Qt6 installation directory, as we will need it to
correctly set our `CMAKE_PREFIX_PATH` in the next steps.
#### Build Options
@@ -152,7 +146,7 @@ For this example we will configure the build with all options enabled.
In the separate sections below we can install individual components with cmake.
```bash
sudo apt update -y && sudo apt install cmake build-essential git ccache libxkbcommon-dev libassimp-dev qt6-base-dev qt6-tools-dev -y
sudo apt update -y && sudo apt install libassimp-dev cmake build-essential git ccache libgl1-mesa-dev libglvnd-dev zlib1g-dev -y
git clone https://github.com/shaunrd0/qtk
cd qtk
# Configure the build with all components enabled
@@ -271,7 +265,7 @@ This project is using `clang-format` version `>=15.0.5`.
On Ubuntu 24.04, clang-format 18 is available to install in apt repositories.
```bash
sudo apt install clang-format clang-tidy
sudo apt install clang-format
```
If `clang-format --version` is any earlier than `15.0.0`, running `clang-format` will fail because this project uses configuration options made available since `15.0.0`.

View File

@@ -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.3 REQUIRED)
find_package(Qtk 0.2 REQUIRED)
endif()
find_package(Qt6 COMPONENTS Core Widgets OpenGLWidgets REQUIRED)
@@ -80,24 +80,17 @@ target_link_libraries(qtk_example PUBLIC Qt6::Widgets Qt6::OpenGLWidgets Qt6::Co
target_link_libraries(qtk_example PUBLIC Qtk::qtk)
target_include_directories(qtk_example PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
# qt_generate_deploy_app_script supports Windows and OSX in QtCore >= 6.3.
# qt_generate_deploy_app_script is supported on Linux in QtCore >= 6.5.0.
if((Qt6_VERSION VERSION_GREATER_EQUAL "6.3.0" AND (WIN32 OR APPLE))
OR Qt6_VERSION VERSION_GREATER_EQUAL "6.5.0")
install(
TARGETS qtk_example
COMPONENT qtk_example
BUNDLE DESTINATION .
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static
RUNTIME DESTINATION bin
)
qt_generate_deploy_app_script(
TARGET qtk_example
OUTPUT_SCRIPT QTK_EXAMPLE_DEPLOY_SCRIPT
NO_UNSUPPORTED_PLATFORM_ERROR
)
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()
install(
TARGETS qtk_example
COMPONENT qtk_example
BUNDLE DESTINATION .
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static
RUNTIME DESTINATION bin
)
qt_generate_deploy_app_script(
TARGET qtk_example
OUTPUT_SCRIPT QTK_EXAMPLE_DEPLOY_SCRIPT
NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${QTK_EXAMPLE_DEPLOY_SCRIPT} COMPONENT qtk_example)

View File

@@ -22,7 +22,18 @@ ExampleScene::~ExampleScene() = default;
void ExampleScene::init()
{
setSkybox(new Qtk::Skybox);
setSkybox(new Qtk::Skybox(":/textures/skybox/right.png",
":/textures/skybox/top.png",
":/textures/skybox/front.png",
":/textures/skybox/left.png",
":/textures/skybox/bottom.png",
":/textures/skybox/back.png",
"Skybox"));
std::string spartanPath = QTK_EXAMPLE_SOURCE_DIR;
spartanPath += "/../resources/models/spartan/spartan.obj";
auto spartan = addObject(new Model("spartan", spartanPath.c_str()));
spartan->getTransform().setTranslation(-4.0f, 0.0f, 0.0f);
auto mesh = addObject(
new Qtk::MeshRenderer("rightTriangle", Triangle(QTK_DRAW_ELEMENTS)));

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

View File

@@ -1,5 +1,6 @@
<RCC>
<qresource prefix="/textures">
<file alias="plaster.png">images/plaster.png</file>
<file alias="crate.png">images/crate.png</file>
<file alias="stone.png">images/stone.png</file>
<file alias="wood.png">images/wood.png</file>

View File

@@ -1,5 +1,6 @@
<RCC>
<qresource prefix="/textures">
<file alias="plaster.png">images/plaster.png</file>
<file alias="crate.png">images/crate.png</file>
<file alias="stone.png">images/stone.png</file>
<file alias="wood.png">images/wood.png</file>

View File

@@ -17,8 +17,6 @@ if (QTK_GUI_SCENE)
qtkscene.cpp qtkscene.h
main.cpp
)
qt6_add_big_resources(QTK_GUI_SOURCES "${QTK_RESOURCES}/resources.qrc")
else()
# The scene will use a default skybox with no models or examples.
# Models can be added by click-and-dragging an .obj into the scene.
@@ -26,7 +24,11 @@ else()
qtkmainwindow.cpp qtkmainwindow.h qtkmainwindow.ui
main.cpp
)
endif()
if(QTK_GUI_SCENE)
qt6_add_big_resources(QTK_GUI_SOURCES "${QTK_RESOURCES}/resources.qrc")
else()
qt6_add_big_resources(
QTK_GUI_SOURCES
"${QTK_RESOURCES}/minimal_resources.qrc"
@@ -34,10 +36,11 @@ else()
endif()
qt_add_executable(qtk_gui ${QTK_GUI_SOURCES})
target_link_libraries(qtk_gui PRIVATE qtk_plugin_library)
if (QTK_GUI_SCENE)
target_compile_definitions(qtk_gui PRIVATE -DQTK_GUI_SCENE)
target_compile_definitions(qtk_gui PUBLIC QTK_GUI_SCENE)
endif()
if (WIN32)
@@ -56,25 +59,21 @@ elseif(APPLE)
)
endif()
# qt_generate_deploy_app_script supports Windows and OSX in QtCore >= 6.3.
# qt_generate_deploy_app_script is supported on Linux in QtCore >= 6.5.0.
if((Qt6_VERSION VERSION_GREATER_EQUAL "6.3.0" AND (WIN32 OR APPLE))
OR Qt6_VERSION VERSION_GREATER_EQUAL "6.5.0")
install(
TARGETS qtk_gui
COMPONENT qtk_gui
BUNDLE DESTINATION .
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
qt_generate_deploy_app_script(
TARGET qtk_gui
OUTPUT_SCRIPT QTK_DEPLOY_SCRIPT
NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${QTK_DEPLOY_SCRIPT} COMPONENT qtk_gui)
endif()
install(
TARGETS qtk_gui
COMPONENT qtk_gui
BUNDLE DESTINATION .
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
qt_generate_deploy_app_script(
TARGET qtk_gui
OUTPUT_SCRIPT QTK_DEPLOY_SCRIPT
NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${QTK_DEPLOY_SCRIPT} COMPONENT qtk_gui)
if(WIN32)
if(MSVC AND TARGET Qt6::qmake)

View File

@@ -48,16 +48,6 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent)
&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.
// 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
@@ -72,7 +62,7 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent)
ui_->menuView->addAction(ui_->qtk__TreeView->toggleViewAction());
// Set the window icon used for Qtk.
setWindowIcon(getIcon());
setWindowIcon(Qtk::getIcon());
}
MainWindow::~MainWindow()
@@ -114,26 +104,6 @@ void MainWindow::refreshScene(const QString & sceneName)
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)
{
connect(scene,

View File

@@ -11,11 +11,26 @@
#include <unordered_map>
#include <QFileDialog>
#include <QMainWindow>
#include <QPlainTextEdit>
#include "designer-plugins/debugconsole.h"
#ifdef QTK_GUI_SCENE
#include "qtkscene.h"
using AppScene = QtkScene;
inline void initResources()
{
Q_INIT_RESOURCE(resources);
}
#else
using AppScene = EmptyScene;
inline void initResources()
{
Q_INIT_RESOURCE(minimal_resources);
}
#endif
namespace Ui
{
class MainWindow;
@@ -62,27 +77,6 @@ class EmptyScene : public Qtk::Scene
}
};
/*
* Conditionally include the QtkScene header if the example is enabled.
* Set AppScene type to use in main() for creating the scene.
* Define helper function to initialize Qt resources for the application.
* These resources are different based on if the example is enabled.
*/
#ifdef QTK_GUI_SCENE
#include "qtkscene.h"
using AppScene = QtkScene;
inline void initResources()
{
Q_INIT_RESOURCE(resources);
}
#else
using AppScene = EmptyScene;
inline void initResources()
{
Q_INIT_RESOURCE(minimal_resources);
}
#endif
/**
* MainWindow class to provide an example of using a QtkWidget within a Qt
* window application.
@@ -132,11 +126,6 @@ class MainWindow : public QMainWindow
*/
void setScene(Qtk::Scene * scene);
/**
* @return Default icon to use for Qtk desktop application.
*/
static QIcon getIcon() { return QIcon(":/icons/icon.png"); }
public slots:
/**
* Trigger a refresh for widgets related to a scene that has been updated.
@@ -144,23 +133,6 @@ class MainWindow : public QMainWindow
*/
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 Members

View File

@@ -222,9 +222,6 @@
</property>
</action>
<action name="actionLoad_Model">
<property name="enabled">
<bool>true</bool>
</property>
<property name="icon">
<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>
@@ -237,9 +234,6 @@
</property>
</action>
<action name="actionDelete_Object">
<property name="enabled">
<bool>true</bool>
</property>
<property name="icon">
<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

@@ -426,7 +426,6 @@ void QtkScene::draw()
// WARNING: We must call the base class draw() function first.
// + This will handle rendering core scene components like the Skybox.
Scene::draw();
const QVector3D cameraPosition = getCamera().getTransform().getTranslation();
mTestPhong->bindShaders();
mTestPhong->setUniform("uModelInverseTransposed",
@@ -434,12 +433,14 @@ void QtkScene::draw()
mTestPhong->setUniform(
"uLightPosition",
MeshRenderer::getInstance("phongLight")->getTransform().getTranslation());
mTestPhong->setUniform("uCameraPosition", cameraPosition);
mTestPhong->setUniform("uCameraPosition",
QtkScene::getCamera().getTransform().getTranslation());
mTestPhong->releaseShaders();
mTestPhong->draw();
mTestAmbient->bindShaders();
mTestAmbient->setUniform("uCameraPosition", cameraPosition);
mTestAmbient->setUniform(
"uCameraPosition", QtkScene::getCamera().getTransform().getTranslation());
mTestAmbient->releaseShaders();
mTestAmbient->draw();
@@ -451,7 +452,8 @@ void QtkScene::draw()
MeshRenderer::getInstance("diffuseLight")
->getTransform()
.getTranslation());
mTestDiffuse->setUniform("uCameraPosition", cameraPosition);
mTestDiffuse->setUniform(
"uCameraPosition", QtkScene::getCamera().getTransform().getTranslation());
mTestDiffuse->releaseShaders();
mTestDiffuse->draw();
@@ -463,70 +465,67 @@ void QtkScene::draw()
MeshRenderer::getInstance("specularLight")
->getTransform()
.getTranslation());
mTestSpecular->setUniform("uCameraPosition", cameraPosition);
mTestSpecular->setUniform(
"uCameraPosition", QtkScene::getCamera().getTransform().getTranslation());
mTestSpecular->releaseShaders();
mTestSpecular->draw();
}
void QtkScene::update()
{
auto getModel = Model::getInstance;
const QVector3D cameraPosition = getCamera().getTransform().getTranslation();
auto mySpartan = Model::getInstance("My spartan");
mySpartan->getTransform().rotate(0.75f, 0.0f, 1.0f, 0.0f);
// Models may have failed to load, so we should check before accessing.
if (auto mySpartan = getModel("My spartan"); mySpartan) {
mySpartan->getTransform().rotate(0.75f, 0.0f, 1.0f, 0.0f);
}
auto myCube = MeshRenderer::getInstance("My cube");
myCube->getTransform().rotate(-0.75f, 0.0f, 1.0f, 0.0f);
if (auto myCube = getModel("My cube"); myCube) {
myCube->getTransform().rotate(-0.75f, 0.0f, 1.0f, 0.0f);
}
auto position = MeshRenderer::getInstance("alienTestLight")
->getTransform()
.getTranslation();
auto alien = Model::getInstance("alienTest");
alien->setUniform("uLight.position", position);
alien->setUniform("uCameraPosition",
QtkScene::getCamera().getTransform().getTranslation());
auto posMatrix = alien->getTransform().toMatrix();
alien->setUniform("uMVP.normalMatrix", posMatrix.normalMatrix());
alien->setUniform("uMVP.model", posMatrix);
alien->setUniform("uMVP.view", QtkScene::getCamera().toMatrix());
alien->setUniform("uMVP.projection", QtkScene::getProjectionMatrix());
alien->getTransform().rotate(0.75f, 0.0f, 1.0f, 0.0f);
QMatrix4x4 posMatrix;
if (auto alien = getModel("alienTest"); alien) {
alien->setLightPosition("alienTestLight");
position = MeshRenderer::getInstance("spartanTestLight")
->getTransform()
.getTranslation();
auto spartan = Model::getInstance("spartanTest");
spartan->setUniform("uLight.position", position);
spartan->setUniform("uCameraPosition",
QtkScene::getCamera().getTransform().getTranslation());
posMatrix = spartan->getTransform().toMatrix();
spartan->setUniform("uMVP.normalMatrix", posMatrix.normalMatrix());
spartan->setUniform("uMVP.model", posMatrix);
spartan->setUniform("uMVP.view", QtkScene::getCamera().toMatrix());
spartan->setUniform("uMVP.projection", QtkScene::getProjectionMatrix());
spartan->getTransform().rotate(0.75f, 0.0f, 1.0f, 0.0f);
alien->setUniform("uCameraPosition", cameraPosition);
posMatrix = alien->getTransform().toMatrix();
alien->setUniform("uMVP.normalMatrix", posMatrix.normalMatrix());
alien->setUniform("uMVP.model", posMatrix);
alien->setUniform("uMVP.view", QtkScene::getCamera().toMatrix());
alien->setUniform("uMVP.projection", QtkScene::getProjectionMatrix());
alien->getTransform().rotate(0.75f, 0.0f, 1.0f, 0.0f);
}
if (auto spartan = getModel("spartanTest"); spartan) {
spartan->setLightPosition("spartanTestLight");
spartan->setUniform("uCameraPosition", cameraPosition);
posMatrix = spartan->getTransform().toMatrix();
spartan->setUniform("uMVP.normalMatrix", posMatrix.normalMatrix());
spartan->setUniform("uMVP.model", posMatrix);
spartan->setUniform("uMVP.view", QtkScene::getCamera().toMatrix());
spartan->setUniform("uMVP.projection", QtkScene::getProjectionMatrix());
spartan->getTransform().rotate(0.75f, 0.0f, 1.0f, 0.0f);
}
if (auto phong = getModel("testPhong"); phong) {
phong->setLightPosition("testLight");
phong->getTransform().rotate(0.75f, 1.0f, 0.5f, 0.0f);
phong->bindShaders();
phong->setUniform("uCameraPosition", cameraPosition);
posMatrix = phong->getTransform().toMatrix();
phong->setUniform("uMVP.normalMatrix", posMatrix.normalMatrix());
phong->setUniform("uMVP.model", posMatrix);
phong->setUniform("uMVP.view", QtkScene::getCamera().toMatrix());
phong->setUniform("uMVP.projection", QtkScene::getProjectionMatrix());
phong->releaseShaders();
}
// MeshRenderers are lower level opengl objects baked into the source code.
auto getMesh = MeshRenderer::getInstance;
auto phong = MeshRenderer::getInstance("testPhong");
phong->getTransform().rotate(0.75f, 1.0f, 0.5f, 0.0f);
phong->bindShaders();
position =
MeshRenderer::getInstance("testLight")->getTransform().getTranslation();
phong->setUniform("uLight.position", position);
phong->setUniform("uCameraPosition",
QtkScene::getCamera().getTransform().getTranslation());
posMatrix = phong->getTransform().toMatrix();
phong->setUniform("uMVP.normalMatrix", posMatrix.normalMatrix());
phong->setUniform("uMVP.model", posMatrix);
phong->setUniform("uMVP.view", QtkScene::getCamera().toMatrix());
phong->setUniform("uMVP.projection", QtkScene::getProjectionMatrix());
phong->releaseShaders();
// Rotate lighting example cubes
mTestPhong->getTransform().rotate(0.75f, 0.5f, 0.3f, 0.2f);
getMesh("noLight")->getTransform().rotate(0.75f, 0.5f, 0.3f, 0.2f);
MeshRenderer::getInstance("noLight")->getTransform().rotate(
0.75f, 0.5f, 0.3f, 0.2f);
mTestAmbient->getTransform().rotate(0.75f, 0.5f, 0.3f, 0.2f);
mTestDiffuse->getTransform().rotate(0.75f, 0.5f, 0.3f, 0.2f);
mTestSpecular->getTransform().rotate(0.75f, 0.5f, 0.3f, 0.2f);
@@ -534,27 +533,46 @@ void QtkScene::update()
// Examples of various translations and rotations
// Rotate in multiple directions simultaneously
getMesh("rgbNormalsCube")->getTransform().rotate(0.75f, 0.2f, 0.4f, 0.6f);
MeshRenderer::getInstance("rgbNormalsCube")
->getTransform()
.rotate(0.75f, 0.2f, 0.4f, 0.6f);
// Pitch forward and roll sideways
getMesh("leftTriangle")->getTransform().rotate(0.75f, 1.0f, 0.0f, 0.0f);
getMesh("rightTriangle")->getTransform().rotate(0.75f, 0.0f, 0.0f, 1.0f);
MeshRenderer::getInstance("leftTriangle")
->getTransform()
.rotate(0.75f, 1.0f, 0.0f, 0.0f);
MeshRenderer::getInstance("rightTriangle")
->getTransform()
.rotate(0.75f, 0.0f, 0.0f, 1.0f);
// Move between two positions over time
static float translateX = 0.025f;
float limit = -9.0f; // Origin position.x - 2.0f
float posX = getMesh("topTriangle")->getTransform().getTranslation().x();
float posX = MeshRenderer::getInstance("topTriangle")
->getTransform()
.getTranslation()
.x();
if (posX < limit || posX > limit + 4.0f) {
translateX = -translateX;
}
getMesh("topTriangle")->getTransform().translate(translateX, 0.0f, 0.0f);
getMesh("bottomTriangle")->getTransform().translate(-translateX, 0.0f, 0.0f);
MeshRenderer::getInstance("topTriangle")
->getTransform()
.translate(translateX, 0.0f, 0.0f);
MeshRenderer::getInstance("bottomTriangle")
->getTransform()
.translate(-translateX, 0.0f, 0.0f);
// And lets rotate the triangles in two directions at once
getMesh("topTriangle")->getTransform().rotate(0.75f, 0.2f, 0.0f, 0.4f);
getMesh("bottomTriangle")->getTransform().rotate(0.75f, 0.0f, 0.2f, 0.4f);
MeshRenderer::getInstance("topTriangle")
->getTransform()
.rotate(0.75f, 0.2f, 0.0f, 0.4f);
MeshRenderer::getInstance("bottomTriangle")
->getTransform()
.rotate(0.75f, 0.0f, 0.2f, 0.4f);
// And make the bottom triangle green, instead of RGB
// Rotate center cube in several directions simultaneously
// + Not subject to gimbal lock since we are using quaternions :)
getMesh("centerCube")->getTransform().rotate(0.75f, 0.2f, 0.4f, 0.6f);
MeshRenderer::getInstance("centerCube")
->getTransform()
.rotate(0.75f, 0.2f, 0.4f, 0.6f);
}

View File

@@ -16,121 +16,20 @@
using namespace Qtk;
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)
ToolBox::ToolBox(QWidget * parent) : QDockWidget(parent), 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<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)
{
auto object =
QtkWidget::mWidgetManager.get_widget()->getScene()->getObject(name);
// If we can't find the object show a warning.
if (object == Q_NULLPTR) {
qDebug() << "Failed to find selected object: " << name
<< "; 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]);
Qtk::QtkWidget::mWidgetManager.get_widget()->getScene()->getObject(name);
if (object != Q_NULLPTR) {
removePages();
createPageProperties(object);
createPageShader(object);
}
}
@@ -139,32 +38,110 @@ ToolBox::~ToolBox()
delete ui;
}
void ToolBox::refreshProperties(const Object * object)
void ToolBox::removePages()
{
// 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::refreshShaders(const Object * object)
{
// Zero the panel contents if there is no object selected.
if (object == Q_NULLPTR) {
vertex_.clear();
fragment_.clear();
return;
// Remove all existing pages.
for (size_t i = 0; i < ui->toolBox->count(); i++) {
delete ui->toolBox->widget(i);
ui->toolBox->removeItem(i);
}
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());
}
void ToolBox::refresh(const Object * object)
void ToolBox::createPageProperties(const Object * object)
{
refreshProperties(object);
refreshShaders(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<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;
rowLayout->addWidget(new QLabel(tr("Scale:")));
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.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);
}

View File

@@ -13,13 +13,10 @@
#include <QDesignerExportWidget>
#include <QDockWidget>
#include <QDoubleSpinBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QTextEdit>
#include <QGroupBox>
#include "qtk/object.h"
#include "qtk/scene.h"
namespace Ui
{
@@ -28,7 +25,7 @@ namespace Ui
namespace Qtk
{
class ToolBox final : public QDockWidget
class ToolBox : public QDockWidget
{
Q_OBJECT
@@ -41,184 +38,20 @@ namespace Qtk
~ToolBox();
void refreshProperties(const Object * object);
void removePages();
void refreshShaders(const Object * object);
void createPageProperties(const Object * object);
void refresh(const Object * object);
void createPageShader(const Object * object);
void updateFocus(const QString & name);
[[nodiscard]] Object * getObjectFocus() const { return objectFocus_; }
void clearFocus();
private:
/*************************************************************************
* 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;
};
} // namespace Qtk

View File

@@ -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())));
QStringList list(QStringList(QString(object->getName().c_str())));
ui->treeWidget->insertTopLevelItem(0, new QTreeWidgetItem(list));
}
}

View File

@@ -67,7 +67,7 @@ QString WidgetPlugin::whatsThis() const
QIcon WidgetPlugin::icon() const
{
return QIcon(":/icons/icon.png");
return Qtk::getIcon();
}
bool WidgetPlugin::isContainer() const

View File

@@ -55,7 +55,7 @@ target_sources(
)
if(QTK_DEBUG)
target_compile_definitions(qtk PUBLIC -DQTK_DEBUG)
target_compile_definitions(qtk PUBLIC QTK_DEBUG)
endif()
set_target_properties(

View File

@@ -45,7 +45,7 @@ MeshRenderer::MeshRenderer(const char * name, const ShapeBase & shape) :
MeshRenderer::~MeshRenderer()
{
sInstances.remove(mName);
sInstances.remove(mName.c_str());
}
/*******************************************************************************
@@ -121,7 +121,9 @@ void MeshRenderer::draw()
bindShaders();
mVAO.bind();
mTexture.bind();
if (mTexture.hasTexture()) {
mTexture.getOpenGLTexture().bind();
}
// TODO: Automate uniforms some other way
setUniformMVP();
@@ -136,7 +138,9 @@ void MeshRenderer::draw()
mShape.mIndices.data());
}
mTexture.bind();
if (mTexture.hasTexture()) {
mTexture.getOpenGLTexture().release();
}
mVAO.release();
releaseShaders();

View File

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

View File

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

View File

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

View File

@@ -43,6 +43,14 @@ namespace Qtk
}
return widget;
}
/**
* @return Default icon to use for Qtk desktop application.
*/
static QIcon getIcon()
{
return QIcon(":/icons/icon.png");
}
} // namespace Qtk
#endif // QTK_QTKAPI_H

View File

@@ -55,34 +55,6 @@ template <> Model * Scene::addObject(Model * 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()
{
if (!mInit) {
@@ -131,7 +103,7 @@ std::vector<Object *> Scene::getObjects() const
Object * Scene::getObject(const QString & name) const
{
for (const auto & object : getObjects()) {
if (object->getName() == name) {
if (object->getName() == name.toStdString()) {
return object;
}
}
@@ -149,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() + " (" + QString::number(count) + ")");
object->setName(object->getName() + " (" + std::to_string(count) + ")");
}
}

View File

@@ -85,16 +85,16 @@ namespace Qtk
void loadModel(const QUrl & url)
{
auto fileName = url.fileName().replace(".obj", "");
auto filePath = url.toLocalFile();
auto fileName = url.fileName().replace(".obj", "").toStdString();
auto filePath = url.toLocalFile().toStdString();
loadModel(fileName, filePath);
}
void loadModel(const QString & name, const QString & path)
void loadModel(const std::string & name, const std::string & path)
{
// Add the dropped model to the load queue.
// This is consumed during rendering of the scene if not empty.
mModelLoadQueue.emplace(name.toStdString(), path.toStdString());
mModelLoadQueue.emplace(name, path);
}
/*************************************************************************
@@ -120,7 +120,9 @@ namespace Qtk
*/
[[nodiscard]] uint64_t getObjectCount(const QString & name)
{
return mObjectCount.count(name) ? mObjectCount[name] : 0;
return mObjectCount.count(name.toStdString())
? mObjectCount[name.toStdString()]
: 0;
}
/**
@@ -182,9 +184,8 @@ namespace Qtk
/**
* Adds objects to the scene.
* This template provides explicit specializations for the valid types:
* MeshRenderer, Model
* Any other object type will cause errors.
* This template provides explicit specializations for valid types.
* Adding any object other than these types will cause errors.
* 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
@@ -195,17 +196,6 @@ namespace Qtk
*/
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.
*/
@@ -258,7 +248,7 @@ namespace Qtk
/* MeshRenderers used simple geometry. */
std::vector<MeshRenderer *> mMeshes {};
/* Track count of objects with same initial name. */
std::unordered_map<QString, uint64_t> mObjectCount;
std::unordered_map<std::string, uint64_t> mObjectCount;
};
} // namespace Qtk

View File

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

View File

@@ -17,28 +17,9 @@ using namespace Qtk;
* Constructors / Destructors
******************************************************************************/
Skybox::Skybox(const std::string & name) :
mVBO(QOpenGLBuffer::VertexBuffer),
mVertices(Cube(QTK_DRAW_ELEMENTS).getVertices()),
mIndices(Cube(QTK_DRAW_ELEMENTS).getIndexData())
Skybox::Skybox(QOpenGLTexture * cubeMap, const std::string & name)
{
QImage image({1024, 1024}, QImage::Format_RGBA8888);
image.fill(Qt::darkGray);
mTexture.setCubeMap(image, image, image, image, image, image);
init();
}
Skybox::Skybox(QOpenGLTexture * cubeMap, const std::string & name) :
mVBO(QOpenGLBuffer::VertexBuffer),
mVertices(Cube(QTK_DRAW_ELEMENTS).getVertices()),
mIndices(Cube(QTK_DRAW_ELEMENTS).getIndexData())
{
if (cubeMap == Q_NULLPTR) {
qDebug()
<< "[Qtk] Failed to set cubemap for skybox with null QOpenGLTexture.";
} else {
mTexture.setTexture(cubeMap);
}
mTexture.setTexture(cubeMap);
init();
}
@@ -53,13 +34,13 @@ Skybox::Skybox(const std::string & right,
mVertices(Cube(QTK_DRAW_ELEMENTS).getVertices()),
mIndices(Cube(QTK_DRAW_ELEMENTS).getIndexData())
{
init();
mTexture.setCubeMap(QImage(right.c_str()).mirrored(),
QImage(top.c_str()),
QImage(front.c_str()),
QImage(left.c_str()),
QImage(bottom.c_str()),
QImage(back.c_str()));
init();
}
/*******************************************************************************
@@ -73,7 +54,7 @@ void Skybox::draw()
mVAO.bind();
mProgram.bind();
mTexture.bind();
mTexture.getOpenGLTexture().bind();
mProgram.setUniformValue("uProjectionMatrix", Scene::getProjectionMatrix());
mProgram.setUniformValue("uViewMatrix", Scene::getCamera().toMatrix());
@@ -81,7 +62,7 @@ void Skybox::draw()
glDrawElements(
GL_TRIANGLES, mIndices.size(), GL_UNSIGNED_INT, mIndices.data());
mTexture.bind();
mTexture.getOpenGLTexture().bind();
mProgram.release();
mVAO.release();

View File

@@ -34,12 +34,7 @@ namespace Qtk
* Constructors / Destructors
************************************************************************/
/**
* Construct a skybox with a default texture.
*
* @param name The objectName to use for the Skybox.
*/
explicit Skybox(const std::string & name = "Skybox");
// Delegate this constructor to use default skybox images
/**
* Construct a skybox with an existing QOpenGLTexture.

View File

@@ -8,7 +8,6 @@
#include <QDebug>
#include <QImageReader>
#include <QPainter>
#include "texture.h"
@@ -66,22 +65,6 @@ QOpenGLTexture * OpenGLTextureFactory::initCubeMap(const char * right,
QImage(back));
}
QImage OpenGLTextureFactory::defaultTexture()
{
QImage image({256, 256}, QImage::Format_RGBA8888);
image.fill(Qt::lightGray);
// Draw a red '?' to the center of the image.
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::red);
painter.setFont({"Helvetica", 100, QFont::Bold});
constexpr QRect rect(0, 0, 256, 256);
painter.drawText(rect, Qt::AlignCenter, "?");
return image;
}
QOpenGLTexture * OpenGLTextureFactory::initCubeMap(const QImage & right,
const QImage & top,
const QImage & front,
@@ -104,9 +87,9 @@ QOpenGLTexture * OpenGLTextureFactory::initCubeMap(const QImage & right,
QOpenGLTexture::CubeMapNegativeZ};
int i = 0;
for (const auto & face : faces) {
QImage & faceImage = faceTextures[i];
QImage faceImage(faceTextures[i]);
if (faceImage.isNull()) {
qDebug() << "[libqtk] Error loading cube map image\n";
qDebug() << "Error loading cube map image\n";
faceImage = defaultTexture();
}
faceImage = faceImage.convertToFormat(QImage::Format_RGBA8888);

View File

@@ -145,7 +145,13 @@ namespace Qtk
const char * back);
/// The texture used in place of a missing texture.
static QImage defaultTexture();
static QImage defaultTexture()
{
// Use plaster for default texture if image fails to load.
// This prevents segfaults when loading a texture that doesn't exist.
// TODO: Replace with a '?' texture to indicate missing texture.
return QImage(":/textures/plaster.png");
}
private:
// Private ctor to prevent creating instances of this class
@@ -217,20 +223,6 @@ namespace Qtk
return mOpenGLTexture != Q_NULLPTR;
}
/**
* Bind the OpenGL texture if it exists, avoiding segmentation faults.
*/
bool bind() const
{
if (hasTexture()) {
// TODO: It would be nice to warn here but some objects may not have
// a texture. Factor Texture out of those objects so we don't bind.
mOpenGLTexture->bind();
return true;
}
return false;
}
/*************************************************************************
* Accessors
************************************************************************/
@@ -314,12 +306,12 @@ namespace Qtk
/**
* Sets this Texture to be a cube map with provided sides.
*
* @param right QImage texture to use for right cube map side.
* @param top QImage texture to use for top cube map side.
* @param front QImage texture to use for front cube map side.
* @param left QImage texture to use for left cube map side.
* @param bottom QImage texture to use for bottom cube map side.
* @param back QImage texture to use for back cube map side.
* @param right Path to texture to use for right cube map side.
* @param top Path to texture to use for top cube map side.
* @param front Path to texture to use for front cube map side.
* @param left Path to texture to use for left cube map side.
* @param bottom Path to texture to use for bottom cube map side.
* @param back Path to texture to use for back cube map side.
*/
virtual inline void setCubeMap(const QImage & right,
const QImage & top,