33 Commits

Author SHA1 Message Date
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
48 changed files with 557 additions and 1154 deletions

View File

@@ -43,16 +43,13 @@ jobs:
with: with:
version: ${{ env.QT_VERSION }} version: ${{ env.QT_VERSION }}
- name: Install pkgconfiglite # Windows
- 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'
@@ -194,11 +191,6 @@ 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
@@ -206,13 +198,13 @@ jobs:
- name: Build Qtk Library - name: Build Qtk Library
shell: bash shell: bash
run: cmake --build build/ --config Release --target qtk -- ${{ matrix.flags }} run: cmake --build build/ --config Release --target qtk_library -- ${{ matrix.flags }}
# Packaging # Packaging
- name: Install Qtk Library - name: Install Qtk Library
shell: bash shell: bash
run: cmake --install build/ --config Release --prefix=$(pwd)/install --component qtk run: cmake --install build/ --config Release --prefix=$(pwd)/install --component qtk_library
- name: Package Qtk Library - name: Package Qtk Library
shell: bash shell: bash
@@ -371,12 +363,13 @@ jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.event.workflow_run.head_branch, 'v')
needs: [Qtk, Qtk-Library, Qtk-Plugins] needs: [Qtk, Qtk-Library, Qtk-Plugins]
steps: steps:
- name: Download Installer Artifact - name: Download Installer Artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: Qtk Packages
path: | path: |
build/packages/* build/packages/*
install/* install/*
@@ -385,7 +378,8 @@ jobs:
id: create_release id: create_release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
name: Qtk ${{ github.ref_name }} tag_name: ${{ github.event.workflow_run.head_branch }}
name: Qtk ${{ github.event.workflow_run.head_branch }}
draft: true draft: true
prerelease: false prerelease: false
generate_release_notes: true generate_release_notes: true

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' ignore: '.github|build|extern/assimp/assimp'
# 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,12 +61,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: cpp-linter/cpp-linter-action@v2 - name: clang-format Check
uses: jidicula/clang-format-action@v4.9.0
with: with:
version: '18' clang-format-version: '18'
style: 'file' check-path: ${{ matrix.path }}
tidy-checks: ''
files-changed-only: false
ignore: '.github|build|extern'
extensions: 'cpp,h'
files: ${{ matrix.path }}

View File

@@ -4,7 +4,7 @@
## Author: Shaun Reed | Contact: shaunrd0@gmail.com | URL: www.shaunreed.com ## ## Author: Shaun Reed | Contact: shaunrd0@gmail.com | URL: www.shaunreed.com ##
## All Content (c) 2025 Shaun Reed, all rights reserved ## ## All Content (c) 2025 Shaun Reed, all rights reserved ##
################################################################################ ################################################################################
cmake_minimum_required(VERSION 3.28) cmake_minimum_required(VERSION 3.23)
################################################################################ ################################################################################
# Constants # Constants
@@ -31,7 +31,7 @@ add_compile_options(-fPIC)
################################################################################ ################################################################################
project( project(
#[[NAME]] Qtk #[[NAME]] Qtk
VERSION 0.3 VERSION 0.2
DESCRIPTION "Qt OpenGL library and desktop application." DESCRIPTION "Qt OpenGL library and desktop application."
LANGUAGES CXX C LANGUAGES CXX C
) )
@@ -48,20 +48,9 @@ include(GNUInstallDirs)
################################################################################ ################################################################################
option(QTK_DEBUG "Enable debugger" OFF) option(QTK_DEBUG "Enable debugger" OFF)
option(QTK_SUBMODULES "Update external project (assimp) submodule" OFF) option(QTK_SUBMODULES "Update external project (assimp) submodule" OFF)
option(QTK_PLUGINS "Install Qtk plugins to Qt Creator path." OFF)
option(QTK_EXAMPLE "Build the Qtk example desktop application" ON) option(QTK_EXAMPLE "Build the Qtk example desktop application" ON)
option(QTK_CCACHE "Enable ccache" ON) option(QTK_CCACHE "Enable ccache" ON)
# Install Qtk for use within Qt Creator projects only, instead of system-wide.
option(QTK_PREFIX_QTCREATOR "Install Qtk to Qt Creator. Untested." OFF)
# Option for bringing your own assimp installation; Otherwise not needed
# + If assimp is available system-wide we can just set QTK_SUBMODULES OFF
option(
QTK_ASSIMP_NEW_INTERFACE
"Use the assimp::assimp interface (WIN / OSX)"
OFF
)
# Qtk Component Options
option(QTK_PLUGINS "Install Qtk plugins to Qt Designer path." OFF)
# Options for qtk_gui # Options for qtk_gui
option(QTK_GUI "Build the Qtk desktop application" ON) option(QTK_GUI "Build the Qtk desktop application" ON)
option(QTK_GUI_SCENE option(QTK_GUI_SCENE
@@ -72,6 +61,17 @@ if (QTK_CCACHE)
set(CMAKE_CXX_COMPILER_LAUNCHER ccache) set(CMAKE_CXX_COMPILER_LAUNCHER ccache)
endif() endif()
# Install Qtk for use within Qt Creator projects only, instead of system-wide.
option(QTK_PREFIX_QTCREATOR "Install Qtk to Qt Creator. Untested." OFF)
# Option for bringing your own assimp installation; Otherwise not needed
# + If assimp is available system-wide we can just set QTK_SUBMODULES OFF
option(
QTK_ASSIMP_NEW_INTERFACE
"Use the assimp::assimp interface (WIN / OSX)"
OFF
)
if(QTK_DEBUG OR CMAKE_BUILD_TYPE MATCHES "^[Dd][Ee][Bb][Uu][Gg]$") if(QTK_DEBUG OR CMAKE_BUILD_TYPE MATCHES "^[Dd][Ee][Bb][Uu][Gg]$")
set(QTK_DEBUG ON) set(QTK_DEBUG ON)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
@@ -157,15 +157,8 @@ 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(
@@ -182,14 +175,6 @@ 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()
@@ -204,8 +189,8 @@ endif()
add_subdirectory(src) add_subdirectory(src)
if(QTK_EXAMPLE) if(QTK_EXAMPLE)
# Create a namespaced alias for linking with libqtk in the example. # Create a namespaced alias for linking with qtk_library in the example.
add_library(${PROJECT_NAME}::qtk ALIAS qtk) add_library(${PROJECT_NAME}::qtk_library ALIAS qtk_library)
add_subdirectory(example-app EXCLUDE_FROM_ALL) add_subdirectory(example-app EXCLUDE_FROM_ALL)
endif() endif()
@@ -220,25 +205,3 @@ foreach(VAR_NAME IN LISTS VAR_NAMES VAR_PATHS)
message(STATUS "[Qtk] ${VAR_NAME}=${${VAR_NAME}}") message(STATUS "[Qtk] ${VAR_NAME}=${${VAR_NAME}}")
endif() endif()
endforeach() 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()

194
README.md
View File

@@ -1,18 +1,13 @@
# Qtk # 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/all-builds.yml)
[![Linting](https://github.com/shaunrd0/qtk/actions/workflows/linting.yml/badge.svg)](https://github.com/shaunrd0/qtk/actions/workflows/linting.yml) [![Linting](https://github.com/shaunrd0/qtk/actions/workflows/linting.yml/badge.svg)](https://github.com/shaunrd0/qtk/actions/workflows/linting.yml)
Qtk is a Qt OpenGL graphics library that wraps some QOpenGL functionality in convenience classes
that allow rendering geometry in 2D and 3D using custom GLSL shader programs.
The Qtk desktop application provides a model loader using [Assimp](https://assimp.org/) within a Qt widget application. The Qtk desktop application provides a model loader using [Assimp](https://assimp.org/) within a Qt widget application.
You can fly around the scene using WASD while holding down the left or right mouse button. You can fly around the scene using WASD while holding down the left or right mouse button.
[QtkWidget](./src/designer-plugins/qtkwidget.h) is the primary QOpenGLWidget used to render the scene and handle input.
The underlying shared library [libqtk](./src/qtk) wraps QOpenGL objects in convenience classes that leverage
lower-level OpenGL APIs to handle the rendering process manually. Many of these classes offer
ways to expand the low-level OpenGL logic within a Qt application without having to set up much scaffolding.
The Qtk GUI is built using custom [Qt Designer plugins](https://doc.qt.io/qt-6/designer-creating-custom-widgets.html). These can be installed to Qt Designer for
use in other Qt applications, or built exclusively for Qtk. See [Build Options](#build-options) for more details.
Object names can be double-clicked in the tree view panel for quick camera navigation. Object names can be double-clicked in the tree view panel for quick camera navigation.
Properties of the object, like shader code and translation / scale, can be viewed and modified in the side panel. Properties of the object, like shader code and translation / scale, can be viewed and modified in the side panel.
@@ -29,13 +24,7 @@ detached from the main window in this way.
The small triangles floating near 3D models represent the light source being used for the shader. The small triangles floating near 3D models represent the light source being used for the shader.
These appear on models using phong, specular, and diffuse lighting techniques. These appear on models using phong, specular, and diffuse lighting techniques.
The example scene contains basic examples like texture mapping to make a crate from simple cube geometry. The default scene contains basic examples like texture mapping to make a crate from basic cube geometry
This scene is used in the following screenshots, and can be built locally by enabling
the `QTK_GUI_SCENE` [Build Option](#build-options) described below. Because this scene
uses large 3D model resources, this option is disabled by default.
The default scene with this option disabled is empty, but comes with a default skybox.
Models can be added to the scene by clicking and dragging an `.obj` into the scene view.
![](resources/screenshots/screen-texture.png) ![](resources/screenshots/screen-texture.png)
@@ -60,99 +49,43 @@ Key features that are planned:
- [x] Runtime loading of `.obj` or similar 3D models. - [x] Runtime loading of `.obj` or similar 3D models.
- [x] Drag-and-drop interaction for adding objects to the scene. - [x] Drag-and-drop interaction for adding objects to the scene.
- [x] Shader / object properties panel to modify related settings. - [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. - [ ] Runtime reloading of modified GLSL shaders attached to objects within scenes.
- [ ] Multiple views of a scene at one time. - [ ] Multiple views of a scene at one time.
- [ ] Camera control modes such as panning, orbiting, or following objects. - [ ] Camera control modes such as panning, orbiting, or following objects.
- [ ] Save / load scene data. The current model requires writing C++ code. - [ ] Save / load scene data. The current model requires writing C++ code.
- [ ] Basic text editor for quickly modifying shaders attached to objects. - [ ] 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 Qtk API, see the `example-app` project in the root of
project in the root of this repository. this repository.
To get textures loading on models look To get textures loading on models look
into [material files](http://www.paulbourke.net/dataformats/mtl/) into [material files](http://www.paulbourke.net/dataformats/mtl/)
and see some examples at [qtk-resources/resources/models](https://git.shaunreed.com/shaunrd0/qtk-resources/src/branch/master/models). and see some examples in the `resources/models/` directory.
### Source Builds ### Source Builds
Qtk was developed and tested using CLion Qtk was developed and tested using CLion
and [Qt Creator](https://github.com/qt-creator/qt-creator). and [Qt Creator](https://github.com/qt-creator/qt-creator).
Simply open the root `CMakeLists.txt` with either of these editors and default Simply open the root `CMakeLists.txt` with either of these editors and
configurations will be loaded. To simplify providing Qt to the build, Qt Creator configurations will be loaded.
is the recommended option.
If you have manually installed [Qt6 Open Source Binaries](https://www.qt.io/download-qt-installer) This project has been ported to **Qt 6.6.0**, which is not yet available in
for your system, be sure to correctly set your `CMAKE_PREFIX_PATH` in the next steps. Ubuntu apt repositories.
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`. To run this project, you will *need* to
install [Qt6 Open Source Binaries](https://www.qt.io/download-qt-installer) for
The Ubuntu apt repositories contain all the packages we need to build all targets. your system, **version 6.6.0** or later.
To build Qtk desktop application with the scene in the screenshots below run the following commands. 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.
```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
```
#### Build Options
Qtk is composed of three separate components.
* The shared library [libqtk](./src/qtk) provides classes that leverage QOpenGL functionality
while still using lower-level OpenGL APIs to customize the rendering process.
Many of these classes can be further expanded, such as [Qtk::Scene](./src/qtk/scene.h).
This taget, `qtk` in cmake, is always selected to build and install as
it is required by all other components in this project.
* The [Qtk desktop application](./src/app) is built using libqtk within a Qt application.
This target, `qtk_gui` in cmake, is optional and can be controlled using the `QTK_GUI` option below.
* The GUI for the Qtk desktop application is constructed using a [custom set of Qt Designer widget plugins](./src/designer-plugins) that are also built using libqtk.
If `QTK_GUI` is disabled this target (`qtk_plugins`) is optional and can be controlled using the `QTK_PLUGINS` options below.
| Name | Description | Default |
|--------------------------|--------------------------------------------------------------|:--------|
| QTK_DEBUG | Enable debug mode. | OFF |
| QTK_SUBMODULES | Use git submodules to fetch assimp dependency. | OFF |
| QTK_EXAMPLE | Build the libqtk example desktop application. | ON |
| QTK_CCACHE | Enable CCACHE. | ON |
| QTK_ASSIMP_NEW_INTERFACE | Use the assimp::assimp interface. Recommended for WIN / OSX. | OFF |
| QTK_PLUGINS* | Install Qtk plugins to Qt Designer. | OFF |
| QTK_GUI | Build and install Qtk desktop application. | ON |
| QTK_GUI_SCENE | Fetch external 3D model resources for example scene. | OFF |
*The Qtk plugins are always built if `QTK_GUI` is enabled. Disabling this option
with QTK_GUI set will not mark the plugins for installation if we do
`cmake --install build/` without selecting a component. If both `QTK_GUI` and
`QTK_PLUGINS` are unset, neither will be built.
If you are building on **Windows / Mac**, consider setting If you are building on **Windows / Mac**, consider setting
the `-DQTK_ASSIMP_NEW_INTERFACE` cmake build option. the `-DQTK_ASSIMP_NEW_INTERFACE` cmake build option.
By default, the build will not initialize Assimp as a git submodule and build If the build is configured with all options enabled, we can subsequently install
from source. individual components as needed with cmake.
We can turn this on by setting the `-DQTK_SUBMODULES=ON` flag when running
CMake.
Building using this option will fetch and build Assimp for us, but builds will
take longer as a result.
Using `-DQTK_SUBMODULES=ON` supports providing assimp on cross-platform builds (
Windows / Mac / Linux) and may be easier to configure.
```bash ```bash
cmake -B build-all -DQTK_GUI=ON -DQTK_PLUGINS=ON -DQTK_EXAMPLE=ON -DQTK_SUBMODULES=ON -DCMAKE_PREFIX_PATH=$HOME/Qt/6.6.0/gcc_64 sudo apt update -y && sudo apt install libassimp-dev cmake build-essential git ccache libgl1-mesa-dev libglvnd-dev zlib1g-dev -y
```
#### Qtk Components
As described in [Build Options](#build-options), Qtk is composed of three separate components.
Each component can be individually selected for building or installation.
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
git clone https://github.com/shaunrd0/qtk git clone https://github.com/shaunrd0/qtk
cd qtk cd qtk
# Configure the build with all components enabled # Configure the build with all components enabled
@@ -161,33 +94,54 @@ cmake -B build-all -DQTK_GUI=ON -DQTK_PLUGINS=ON -DQTK_EXAMPLE=ON -DCMAKE_PREFIX
cmake --build build-all/ cmake --build build-all/
```` ````
Now that we have all the components fully built, the following sections will By default, the build will not initialize Assimp as a git submodule and build
install each component individually. from source.
If you want to uninstall previously installed components, run the following command. We can turn this on by setting the `-DQTK_SUBMODULES=ON` flag when running
CMake.
Building using this option will fetch and build Assimp for us, but builds will
take longer as a result.
Using `-DQTK_SUBMODULES=ON` supports providing assimp on cross-platform builds (
Windows / Mac / Linux) and may be easier
to configure.
```bash ```bash
sudo xargs rm -v < install_manifest.txt cmake -B build-all -DQTK_GUI=ON -DQTK_PLUGINS=ON -DQTK_EXAMPLE=ON -DQTK_SUBMODULES=ON -DCMAKE_PREFIX_PATH=$HOME/Qt/6.6.0/gcc_64
``` ```
##### Qtk Library #### Qtk GUI
Shared libqtk library for working with lower-level OpenGL to customize the rendering process. ```bash
cmake --build build-all/ --target qtk_gui -- -j $(nproc)
# Install Qtk desktop application (output removed)
# Installation prefix path must be absolute, since Qtk uses Qt deploy tools.
cmake --install build-all/ --component qtk_gui --prefix=$(pwd)/install
./install/bin/qtk_gui
```
If any errors are encountered loading plugins, we can debug plugin loading by
setting the following environment variable -
```bash
QT_DEBUG_PLUGINS=1 ./install/bin/qtk_gui
```
#### Qtk Library
Qtk provides a simple library for working with QOpenGL.
We can install this library on a system path or a custom path and then We can install this library on a system path or a custom path and then
set `CMAKE_PREFIX_PATH` to point to this location when building an application set `CMAKE_PREFIX_PATH` to point to this location when building an application
using libqtk. using libqtk.
Here we will install to the `/usr/local/` path.
```bash ```bash
# Install libqtk only # Install libqtk only
cmake --build build-all/ --target qtk -- -j $(nproc) cmake --build build-all/ --target qtk_library -- -j $(nproc)
cmake --install build-all/ --component qtk --prefix=/usr/local cmake --install build-all/ --component qtk_library --prefix=/usr/local
-- Install configuration: "Release" -- Install configuration: "Release"
-- Installing: /usr/local/lib/cmake/Qtk/QtkConfig.cmake -- Installing: /usr/local/lib/cmake/Qtk/QtkConfig.cmake
-- Installing: /usr/local/lib/cmake/Qtk/QtkConfigVersion.cmake -- Installing: /usr/local/lib/cmake/Qtk/QtkConfigVersion.cmake
-- Installing: /usr/local/lib/cmake/Qtk/QtkTargets.cmake -- Installing: /usr/local/lib/cmake/Qtk/QtkTargets.cmake
-- Installing: /usr/local/lib/cmake/Qtk/QtkTargets-release.cmake -- Installing: /usr/local/lib/cmake/Qtk/QtkTargets-release.cmake
-- Installing: /usr/local/lib/static/libqtk.a -- Installing: /usr/local/lib/static/libqtk_library.a
-- Installing: /usr/local/include/qtk/camera3d.h -- Installing: /usr/local/include/qtk/camera3d.h
-- Installing: /usr/local/include/qtk/input.h -- Installing: /usr/local/include/qtk/input.h
-- Installing: /usr/local/include/qtk/meshrenderer.h -- Installing: /usr/local/include/qtk/meshrenderer.h
@@ -204,26 +158,7 @@ cmake --install build-all/ --component qtk --prefix=/usr/local
-- Installing: /usr/local/include/qtk/transform3D.h -- Installing: /usr/local/include/qtk/transform3D.h
``` ```
##### Qtk GUI #### Qtk Plugin Collection
The Qtk desktop application can be built and installed with the following commands.
```bash
cmake --build build-all/ --target qtk_gui -- -j $(nproc)
# Install Qtk desktop application (output removed)
# Installation prefix path must be absolute, since Qtk uses Qt deploy tools.
cmake --install build-all/ --component qtk_gui --prefix=$(pwd)/install
./install/bin/qtk_gui
```
If any errors are encountered loading plugins, we can debug plugin loading by
setting the following environment variable
```bash
QT_DEBUG_PLUGINS=1 ./install/bin/qtk_gui
```
##### Qtk Plugin Collection
This project defines a collection of widget plugins for use with Qt Designer. This project defines a collection of widget plugins for use with Qt Designer.
These plugins were used to build the interface for the Qtk desktop application. These plugins were used to build the interface for the Qtk desktop application.
@@ -244,12 +179,19 @@ cmake --build build-all/ --target qtk_plugins -- -j $(nproc)
# The path here should be initialized during build configuration, so no need for --prefix # The path here should be initialized during build configuration, so no need for --prefix
cmake --install build-all/ --component qtk_plugins cmake --install build-all/ --component qtk_plugins
-- Install configuration: "Release" -- Install configuration: "Release"
-- Up-to-date: /home/shaun/Qt/6.6.0/gcc_64/../../Tools/QtCreator/lib/Qt/lib/libqtk.a -- Up-to-date: /home/shaun/Qt/6.6.0/gcc_64/../../Tools/QtCreator/lib/Qt/lib/libqtk_library.a
-- Up-to-date: /home/shaun/Qt/6.6.0/gcc_64/../../Tools/QtCreator/lib/Qt/lib/libqtk_plugin_library.a -- Up-to-date: /home/shaun/Qt/6.6.0/gcc_64/../../Tools/QtCreator/lib/Qt/lib/libqtk_plugin_library.a
-- Up-to-date: /home/shaun/Qt/6.6.0/gcc_64/../../Tools/QtCreator/lib/Qt/plugins/designer/libqtk_collection.so -- Up-to-date: /home/shaun/Qt/6.6.0/gcc_64/../../Tools/QtCreator/lib/Qt/plugins/designer/libqtk_collection.so
``` ```
#### Example libqtk Application To uninstall after a previous installation, we can run the following command
from the root of the repository.
```bash
xargs rm < build/install_manifest.txt
```
#### Qtk Example
There is a simple example of using libqtk in the [example-app/](example-app) There is a simple example of using libqtk in the [example-app/](example-app)
directory. The example can be built standalone using `find_package` or as a directory. The example can be built standalone using `find_package` or as a
@@ -271,7 +213,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. On Ubuntu 24.04, clang-format 18 is available to install in apt repositories.
```bash ```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`. 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`.
@@ -296,18 +238,14 @@ If you'd still like to run these tools manually, see the instructions below.
cd qtk cd qtk
# Build # Build
cmake -B build && cmake --build build -- -j $(nproc) cmake -B build && cmake --build build -- -j $(nproc)
``` clang-tidy -p build/ --fix --config-file=.clang-tidy src/**/*.cpp src/**/*.h example-app/*.cpp example-app/*.h
```bash
export SOURCES=src/**/*.cpp src/**/*.h example-app/*.cpp example-app/*.h
run-clang-tidy -p build/ -j $(nproc --ignore=1) -fix -config-file=.clang-tidy $SOURCES
``` ```
Last we need to run `clang-format`, this can be done with the command directly. Last we need to run `clang-format`, this can be done with the command directly.
This will reformat all the code in the repository. This will reformat all the code in the repository.
```bash ```bash
clang-format -i --style=file:.clang-format $SOURCES clang-format -i --style=file:.clang-format src/**/*.cpp src/**/*.h example-app/*.cpp example-app/*.h
``` ```
`clang-format` can be run with git integration (or CLion if you prefer). `clang-format` can be run with git integration (or CLion if you prefer).

View File

@@ -56,8 +56,8 @@ 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_library)
find_package(Qtk 0.3 REQUIRED) find_package(Qtk 0.2 REQUIRED)
endif() endif()
find_package(Qt6 COMPONENTS Core Widgets OpenGLWidgets REQUIRED) find_package(Qt6 COMPONENTS Core Widgets OpenGLWidgets REQUIRED)
@@ -77,13 +77,9 @@ configure_file(
qt_add_executable(qtk_example ${EXAMPLE_SOURCES}) qt_add_executable(qtk_example ${EXAMPLE_SOURCES})
target_link_libraries(qtk_example PUBLIC Qt6::Widgets Qt6::OpenGLWidgets Qt6::Core) target_link_libraries(qtk_example PUBLIC Qt6::Widgets Qt6::OpenGLWidgets Qt6::Core)
target_link_libraries(qtk_example PUBLIC Qtk::qtk) target_link_libraries(qtk_example PUBLIC Qtk::qtk_library)
target_include_directories(qtk_example PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") 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( install(
TARGETS qtk_example TARGETS qtk_example
COMPONENT qtk_example COMPONENT qtk_example
@@ -98,6 +94,3 @@ if((Qt6_VERSION VERSION_GREATER_EQUAL "6.3.0" AND (WIN32 OR APPLE))
NO_UNSUPPORTED_PLATFORM_ERROR NO_UNSUPPORTED_PLATFORM_ERROR
) )
install(SCRIPT ${QTK_EXAMPLE_DEPLOY_SCRIPT} COMPONENT qtk_example) 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()

View File

@@ -22,7 +22,18 @@ ExampleScene::~ExampleScene() = default;
void ExampleScene::init() 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( auto mesh = addObject(
new Qtk::MeshRenderer("rightTriangle", Triangle(QTK_DRAW_ELEMENTS))); new Qtk::MeshRenderer("rightTriangle", Triangle(QTK_DRAW_ELEMENTS)));

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

View File

@@ -1,21 +0,0 @@
<RCC>
<qresource prefix="/textures">
<file alias="crate.png">images/crate.png</file>
<file alias="stone.png">images/stone.png</file>
<file alias="wood.png">images/wood.png</file>
<file>skybox/back.png</file>
<file>skybox/bottom.png</file>
<file>skybox/front.png</file>
<file>skybox/left.png</file>
<file>skybox/right.png</file>
<file>skybox/top.png</file>
</qresource>
<qresource prefix="/icons">
<file>fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg</file>
<file>fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg</file>
<file>fontawesome-free-6.2.1-desktop/svgs/regular/folder-open.svg</file>
<file>fontawesome-free-6.2.1-desktop/svgs/regular/floppy-disk.svg</file>
<file>fontawesome-free-6.2.1-desktop/svgs/brands/git-alt.svg</file>
<file alias="icon.png">icons/icon.png</file>
</qresource>
</RCC>

View File

@@ -1,5 +1,6 @@
<RCC> <RCC>
<qresource prefix="/textures"> <qresource prefix="/textures">
<file alias="plaster.png">images/plaster.png</file>
<file alias="crate.png">images/crate.png</file> <file alias="crate.png">images/crate.png</file>
<file alias="stone.png">images/stone.png</file> <file alias="stone.png">images/stone.png</file>
<file alias="wood.png">images/wood.png</file> <file alias="wood.png">images/wood.png</file>
@@ -27,6 +28,8 @@
<file alias="solid.vert">shaders/vertex/solid.vert</file> <file alias="solid.vert">shaders/vertex/solid.vert</file>
<file alias="solid-perspective.frag">shaders/fragment/solid-perspective.frag</file> <file alias="solid-perspective.frag">shaders/fragment/solid-perspective.frag</file>
<file alias="solid-perspective.vert">shaders/vertex/solid-perspective.vert</file> <file alias="solid-perspective.vert">shaders/vertex/solid-perspective.vert</file>
<file alias="multi-color.frag">shaders/fragment/multi-color.frag</file>
<file alias="multi-color.vert">shaders/vertex/multi-color.vert</file>
<file alias="rgb-normals.frag">shaders/fragment/rgb-normals.frag</file> <file alias="rgb-normals.frag">shaders/fragment/rgb-normals.frag</file>
<file alias="rgb-normals.vert">shaders/vertex/rgb-normals.vert</file> <file alias="rgb-normals.vert">shaders/vertex/rgb-normals.vert</file>
<file alias="texture-cubemap.frag">shaders/fragment/texture-cubemap.frag</file> <file alias="texture-cubemap.frag">shaders/fragment/texture-cubemap.frag</file>
@@ -41,9 +44,13 @@
<file alias="solid-specular.vert">shaders/vertex/solid-specular.vert</file> <file alias="solid-specular.vert">shaders/vertex/solid-specular.vert</file>
<file alias="solid-phong.frag">shaders/fragment/solid-phong.frag</file> <file alias="solid-phong.frag">shaders/fragment/solid-phong.frag</file>
<file alias="solid-phong.vert">shaders/vertex/solid-phong.vert</file> <file alias="solid-phong.vert">shaders/vertex/solid-phong.vert</file>
<file alias="model-basic.frag">shaders/fragment/model-basic.frag</file>
<file alias="model-basic.vert">shaders/vertex/model-basic.vert</file>
<file alias="model-phong.frag">shaders/fragment/model-phong.frag</file> <file alias="model-phong.frag">shaders/fragment/model-phong.frag</file>
<file alias="model-phong.vert">shaders/vertex/model-phong.vert</file> <file alias="model-phong.vert">shaders/vertex/model-phong.vert</file>
<file alias="model-normals.frag">shaders/fragment/model-normals.frag</file> <file alias="model-normals.frag">shaders/fragment/model-normals.frag</file>
<file alias="model-normals.vert">shaders/vertex/model-normals.vert</file> <file alias="model-normals.vert">shaders/vertex/model-normals.vert</file>
<file alias="skybox.frag">skybox/skybox.frag</file>
<file alias="skybox.vert">skybox/skybox.vert</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -0,0 +1,11 @@
#version 330 core
out vec4 fColor;
in vec2 vTextureCoord;
uniform sampler2D texture_diffuse1;
void main()
{
fColor = texture(texture_diffuse1, vTextureCoord);
}

View File

@@ -0,0 +1,9 @@
#version 330
in vec4 vColor;
out vec4 fColor;
void main()
{
fColor = vColor;
}

View File

@@ -0,0 +1,16 @@
#version 330 core
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTextureCoord;
out vec2 vTextureCoord;
uniform mat4 uModel;
uniform mat4 uView;
uniform mat4 uProjection;
void main()
{
vTextureCoord = aTextureCoord;
gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);
}

View File

@@ -0,0 +1,16 @@
#version 330
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec3 aColor;
out vec4 vColor;
uniform mat4 uModel; // Model
uniform mat4 uView; // View
uniform mat4 uProjection; // Projection
void main()
{
gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);
vColor = vec4(aColor, 1.0f);
}

View File

@@ -0,0 +1,9 @@
#version 330
uniform samplerCube uTexture;
varying vec3 vTexCoord;
void main()
{
gl_FragColor = texture(uTexture, vTexCoord);
}

View File

@@ -0,0 +1,15 @@
#version 330
layout(location = 0) in vec3 aPosition;
out vec3 vTexCoord;
uniform mat4 uProjectionMatrix;
uniform mat4 uViewMatrix;
void main()
{
// Strip translation column from camera's 4x4 matrix
mat4 view = mat4(mat3(uViewMatrix));
gl_Position = uProjectionMatrix * view * vec4(aPosition, 1.0);
vTexCoord = aPosition;
}

View File

@@ -13,22 +13,22 @@ install(
FILES FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
COMPONENT qtk COMPONENT qtk_library
DESTINATION lib/cmake/${PROJECT_NAME} DESTINATION lib/cmake/${PROJECT_NAME}
) )
install( install(
EXPORT qtk_export EXPORT qtk_export
FILE ${PROJECT_NAME}Targets.cmake FILE ${PROJECT_NAME}Targets.cmake
NAMESPACE ${PROJECT_NAME}:: NAMESPACE ${PROJECT_NAME}::
COMPONENT qtk COMPONENT qtk_library
DESTINATION lib/cmake/${PROJECT_NAME} DESTINATION lib/cmake/${PROJECT_NAME}
) )
# System install for libqtk # System install for qtk_library
install( install(
TARGETS qtk TARGETS qtk_library
# Associate libqtk target with qtk-export # Associate qtk_library target with qtk-export
EXPORT qtk_export EXPORT qtk_export
COMPONENT qtk COMPONENT qtk_library
FILE_SET HEADERS DESTINATION include FILE_SET HEADERS DESTINATION include
INCLUDES DESTINATION include INCLUDES DESTINATION include
LIBRARY DESTINATION lib LIBRARY DESTINATION lib

View File

@@ -17,8 +17,6 @@ if (QTK_GUI_SCENE)
qtkscene.cpp qtkscene.h qtkscene.cpp qtkscene.h
main.cpp main.cpp
) )
qt6_add_big_resources(QTK_GUI_SOURCES "${QTK_RESOURCES}/resources.qrc")
else() else()
# The scene will use a default skybox with no models or examples. # 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. # Models can be added by click-and-dragging an .obj into the scene.
@@ -26,18 +24,13 @@ else()
qtkmainwindow.cpp qtkmainwindow.h qtkmainwindow.ui qtkmainwindow.cpp qtkmainwindow.h qtkmainwindow.ui
main.cpp main.cpp
) )
qt6_add_big_resources(
QTK_GUI_SOURCES
"${QTK_RESOURCES}/minimal_resources.qrc"
)
endif() endif()
qt_add_executable(qtk_gui ${QTK_GUI_SOURCES}) qt_add_executable(qtk_gui ${QTK_GUI_SOURCES})
target_link_libraries(qtk_gui PRIVATE qtk_plugin_library) target_link_libraries(qtk_gui PRIVATE qtk_plugin_library)
if (QTK_GUI_SCENE) if (QTK_GUI_SCENE)
target_compile_definitions(qtk_gui PRIVATE -DQTK_GUI_SCENE) target_compile_definitions(qtk_gui PUBLIC QTK_GUI_SCENE)
endif() endif()
if (WIN32) if (WIN32)
@@ -56,10 +49,6 @@ elseif(APPLE)
) )
endif() 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( install(
TARGETS qtk_gui TARGETS qtk_gui
COMPONENT qtk_gui COMPONENT qtk_gui
@@ -68,13 +57,13 @@ if((Qt6_VERSION VERSION_GREATER_EQUAL "6.3.0" AND (WIN32 OR APPLE))
ARCHIVE DESTINATION lib ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
) )
qt_generate_deploy_app_script( qt_generate_deploy_app_script(
TARGET qtk_gui TARGET qtk_gui
OUTPUT_SCRIPT QTK_DEPLOY_SCRIPT OUTPUT_SCRIPT QTK_DEPLOY_SCRIPT
NO_UNSUPPORTED_PLATFORM_ERROR NO_UNSUPPORTED_PLATFORM_ERROR
) )
install(SCRIPT ${QTK_DEPLOY_SCRIPT} COMPONENT qtk_gui) install(SCRIPT ${QTK_DEPLOY_SCRIPT} COMPONENT qtk_gui)
endif()
if(WIN32) if(WIN32)
if(MSVC AND TARGET Qt6::qmake) if(MSVC AND TARGET Qt6::qmake)

View File

@@ -9,10 +9,12 @@
#include <QApplication> #include <QApplication>
#include "qtkmainwindow.h" #include "qtkmainwindow.h"
#include "qtkscene.h"
int main(int argc, char * argv[]) int main(int argc, char * argv[])
{ {
initResources(); Q_INIT_RESOURCE(resources);
QApplication a(argc, argv); QApplication a(argc, argv);
auto window = MainWindow::getMainWindow(); auto window = MainWindow::getMainWindow();
@@ -22,7 +24,11 @@ int main(int argc, char * argv[])
// NOTE: We set the scene here and not in QtkMainWindow to detach the scene // NOTE: We set the scene here and not in QtkMainWindow to detach the scene
// from the QtkWidget plugin (qtk_plugin_library build target). // from the QtkWidget plugin (qtk_plugin_library build target).
// Once we can save / load scenes, this call, and QtkScene, can be removed. // Once we can save / load scenes, this call, and QtkScene, can be removed.
window->setScene(new AppScene); #ifdef QTK_GUI_SCENE
window->setScene(new QtkScene);
#else
window->setScene(new EmptyScene);
#endif
window->show(); window->show();

View File

@@ -48,16 +48,6 @@ 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
@@ -72,7 +62,7 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent)
ui_->menuView->addAction(ui_->qtk__TreeView->toggleViewAction()); ui_->menuView->addAction(ui_->qtk__TreeView->toggleViewAction());
// Set the window icon used for Qtk. // Set the window icon used for Qtk.
setWindowIcon(getIcon()); setWindowIcon(Qtk::getIcon());
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
@@ -114,26 +104,6 @@ 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"
@@ -62,27 +62,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 * MainWindow class to provide an example of using a QtkWidget within a Qt
* window application. * window application.
@@ -132,11 +111,6 @@ class MainWindow : public QMainWindow
*/ */
void setScene(Qtk::Scene * scene); void setScene(Qtk::Scene * scene);
/**
* @return Default icon to use for Qtk desktop application.
*/
static QIcon getIcon() { return QIcon(":/icons/icon.png"); }
public slots: public slots:
/** /**
* Trigger a refresh for widgets related to a scene that has been updated. * Trigger a refresh for widgets related to a scene that has been updated.
@@ -144,23 +118,6 @@ 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,9 +222,6 @@
</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>
@@ -237,9 +234,6 @@
</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

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

@@ -29,7 +29,7 @@ target_sources(
"${QTK_PLUGIN_LIBRARY_SOURCES}" "${QTK_PLUGIN_LIBRARY_SOURCES}"
"${QTK_PLUGIN_LIBRARY_HEADERS}" "${QTK_PLUGIN_LIBRARY_HEADERS}"
) )
target_link_libraries(qtk_plugin_library PUBLIC Qt6::UiPlugin qtk) target_link_libraries(qtk_plugin_library PUBLIC Qt6::UiPlugin qtk_library)
################################################################################ ################################################################################
# Qtk Widget Plugins # Qtk Widget Plugins
@@ -47,7 +47,7 @@ target_link_libraries(qtk_plugins PUBLIC qtk_plugin_library)
# Otherwise, we just use them for building the Qtk desktop application. # Otherwise, we just use them for building the Qtk desktop application.
if(QTK_PLUGINS) if(QTK_PLUGINS)
install( install(
TARGETS qtk_plugins qtk qtk_plugin_library TARGETS qtk_plugins qtk_library qtk_plugin_library
COMPONENT qtk_plugins COMPONENT qtk_plugins
LIBRARY DESTINATION "${QTK_PLUGIN_INSTALL_DIR}" LIBRARY DESTINATION "${QTK_PLUGIN_INSTALL_DIR}"
ARCHIVE DESTINATION "${QTK_PLUGIN_INSTALL_DIR}" ARCHIVE DESTINATION "${QTK_PLUGIN_INSTALL_DIR}"

View File

@@ -98,18 +98,6 @@ void QtkWidget::initializeGL()
this, this,
SLOT(messageLogged(QOpenGLDebugMessage))); SLOT(messageLogged(QOpenGLDebugMessage)));
mDebugLogger->startLogging(); mDebugLogger->startLogging();
mConsole->sendLog(
"Object files such as .obj can be dragged into the scene to load new "
"models.",
DebugContext::Warn);
mConsole->sendLog("Click and hold LMB or RMB to move the camera with WASD.",
DebugContext::Warn);
mConsole->sendLog(
"Click an object name in the side panel to view or modify properties.",
DebugContext::Warn);
mConsole->sendLog(
"Double click an object name to move the camera to it's position.",
DebugContext::Warn);
} }
printContextInformation(); printContextInformation();

View File

@@ -264,7 +264,6 @@ namespace Qtk
<< "[QtkWidgetManager " << this << "[QtkWidgetManager " << this
<< " ] Failed to add a QtkWidget with the previously used name '" << " ] Failed to add a QtkWidget with the previously used name '"
<< name << "'"; << name << "'";
return;
} }
qDebug() << this << " Adding new QtkWidget named '" << name << "'"; qDebug() << this << " Adding new QtkWidget named '" << name << "'";
mQtkWidgets[name] = widget; mQtkWidgets[name] = widget;

View File

@@ -16,121 +16,20 @@
using namespace Qtk; using namespace Qtk;
ToolBox::ToolBox(QWidget * parent) : ToolBox::ToolBox(QWidget * parent) : QDockWidget(parent), ui(new Ui::ToolBox)
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 =
QtkWidget::mWidgetManager.get_widget()->getScene()->getObject(name); Qtk::QtkWidget::mWidgetManager.get_widget()->getScene()->getObject(name);
// If we can't find the object show a warning. if (object != Q_NULLPTR) {
if (object == Q_NULLPTR) { removePages();
qDebug() << "Failed to find selected object: " << name createPageProperties(object);
<< "; Clearing object panels."; createPageShader(object);
}
// 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]);
} }
} }
@@ -139,32 +38,120 @@ ToolBox::~ToolBox()
delete ui; delete ui;
} }
void ToolBox::refreshProperties(const Object * object) void ToolBox::removePages()
{ {
// Refresh to show the new object's details. // Remove all existing pages.
objectDetails_.setObject(object); for (size_t i = 0; i < ui->toolBox->count(); i++) {
// Reconnect transform panel controls to the new object. delete ui->toolBox->widget(i);
transformPanel_.setObject(object); ui->toolBox->removeItem(i);
scalePanel_.setObject(object); }
} }
void ToolBox::refreshShaders(const Object * object) void ToolBox::createPageProperties(const Object * object)
{ {
// Zero the panel contents if there is no object selected. auto transform = object->getTransform();
if (object == Q_NULLPTR) { auto type = object->getType();
vertex_.clear(); auto * widget = new QWidget;
fragment_.clear(); ui->toolBox->addItem(widget, "Properties");
return; 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);
} }
vertex_.path.setValue(object->getVertexShader().c_str()); void ToolBox::createPageShader(const Object * object)
vertex_.editor->setText(object->getVertexShaderSourceCode().c_str()); {
fragment_.path.setValue(object->getFragmentShader().c_str()); // Shaders page.
fragment_.editor->setText(object->getFragmentShaderSourceCode().c_str()); auto widget = new QWidget;
ui->toolBox->addItem(widget, "Shaders");
auto mainLayout = new QFormLayout;
auto rowLayout = new QHBoxLayout;
rowLayout->addWidget(new QLabel("Vertex Shader:"));
rowLayout->addWidget(new QLabel(object->getVertexShader().c_str()));
mainLayout->addRow(rowLayout);
auto shaderView = new QTextEdit;
shaderView->setReadOnly(true);
auto vertexFile = QFile(object->getVertexShader().c_str());
if (vertexFile.exists()) {
vertexFile.open(QIODeviceBase::ReadOnly);
shaderView->setText(vertexFile.readAll());
vertexFile.close();
mainLayout->addRow(shaderView);
} }
void ToolBox::refresh(const Object * object) rowLayout = new QHBoxLayout;
{ rowLayout->addWidget(new QLabel("Fragment Shader:"));
refreshProperties(object); rowLayout->addWidget(new QLabel(object->getFragmentShader().c_str()));
refreshShaders(object); mainLayout->addRow(rowLayout);
shaderView = new QTextEdit;
shaderView->setReadOnly(true);
auto fragmentfile = QFile(object->getFragmentShader().c_str());
if (fragmentfile.exists()) {
fragmentfile.open(QIODeviceBase::ReadOnly);
shaderView->setText(fragmentfile.readAll());
fragmentfile.close();
mainLayout->addRow(shaderView);
}
widget->setLayout(mainLayout);
} }

View File

@@ -13,13 +13,10 @@
#include <QDesignerExportWidget> #include <QDesignerExportWidget>
#include <QDockWidget> #include <QDockWidget>
#include <QDoubleSpinBox> #include <QDoubleSpinBox>
#include <QFormLayout> #include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QTextEdit>
#include "qtk/object.h"
#include "qtk/scene.h"
namespace Ui namespace Ui
{ {
@@ -28,7 +25,7 @@ namespace Ui
namespace Qtk namespace Qtk
{ {
class ToolBox final : public QDockWidget class ToolBox : public QDockWidget
{ {
Q_OBJECT Q_OBJECT
@@ -41,184 +38,20 @@ namespace Qtk
~ToolBox(); ~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); 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

@@ -19,8 +19,6 @@ Qtk::TreeView::TreeView(QWidget * parent) :
QDockWidget(parent), ui(new Ui::TreeView) QDockWidget(parent), ui(new Ui::TreeView)
{ {
ui->setupUi(this); ui->setupUi(this);
connect(
ui->treeWidget, &QTreeWidget::itemClicked, this, &TreeView::itemSelect);
connect(ui->treeWidget, connect(ui->treeWidget,
&QTreeWidget::itemDoubleClicked, &QTreeWidget::itemDoubleClicked,
this, this,
@@ -43,7 +41,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()))); QStringList list(QStringList(QString(object->getName().c_str())));
ui->treeWidget->insertTopLevelItem(0, new QTreeWidgetItem(list)); ui->treeWidget->insertTopLevelItem(0, new QTreeWidgetItem(list));
} }
} }
@@ -74,10 +72,3 @@ void Qtk::TreeView::itemFocus(QTreeWidgetItem * item, int column)
// Emit signal from qtk widget for new object focus. Triggers GUI updates. // Emit signal from qtk widget for new object focus. Triggers GUI updates.
emit QtkWidget::mWidgetManager.get_widget()->objectFocusChanged(name); emit QtkWidget::mWidgetManager.get_widget()->objectFocusChanged(name);
} }
void Qtk::TreeView::itemSelect(QTreeWidgetItem * item, int column)
{
// Emit signal from qtk widget for new object focus. Triggers GUI updates.
const QString & name = item->text(column);
emit QtkWidget::mWidgetManager.get_widget()->objectFocusChanged(name);
}

View File

@@ -58,16 +58,6 @@ namespace Qtk
*/ */
void itemFocus(QTreeWidgetItem * item, int column); void itemFocus(QTreeWidgetItem * item, int column);
/**
* Set the object to show details for.
* Triggered by QTreeWidget::itemClicked signal.
*
* @param item The item that was clicked
* @param column The column of the item that was double clicked.
* This param is currently not used but required for this signal.
*/
void itemSelect(QTreeWidgetItem * item, int column);
private: private:
/************************************************************************* /*************************************************************************
* Private Members * Private Members

View File

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

View File

@@ -24,7 +24,6 @@ set(
skybox.h skybox.h
texture.h texture.h
transform3D.h transform3D.h
shaders.h
) )
set( set(
@@ -44,10 +43,11 @@ set(
transform3D.cpp transform3D.cpp
) )
qt_add_library(qtk STATIC EXCLUDE_FROM_ALL) qt6_add_big_resources(QTK_LIBRARY_SOURCES "${QTK_RESOURCES}/resources.qrc")
target_sources(qtk PRIVATE ${QTK_LIBRARY_SOURCES}) qt_add_library(qtk_library STATIC EXCLUDE_FROM_ALL)
target_sources(qtk_library PRIVATE ${QTK_LIBRARY_SOURCES})
target_sources( target_sources(
qtk PUBLIC qtk_library PUBLIC
FILE_SET HEADERS FILE_SET HEADERS
BASE_DIRS $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src> BASE_DIRS $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src>
BASE_DIRS $<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include> BASE_DIRS $<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include>
@@ -55,25 +55,25 @@ target_sources(
) )
if(QTK_DEBUG) if(QTK_DEBUG)
target_compile_definitions(qtk PUBLIC -DQTK_DEBUG) target_compile_definitions(qtk_library PUBLIC QTK_DEBUG)
endif() endif()
set_target_properties( set_target_properties(
qtk PROPERTIES qtk_library PROPERTIES
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
) )
target_link_libraries( target_link_libraries(
qtk PUBLIC qtk_library PUBLIC
Qt6::Core Qt6::OpenGLWidgets Qt6::Widgets Qt6::Core Qt6::OpenGLWidgets Qt6::Widgets
) )
if(QTK_SUBMODULES OR NOT QTK_ASSIMP_NEW_INTERFACE) if(QTK_SUBMODULES OR NOT QTK_ASSIMP_NEW_INTERFACE)
target_link_libraries(qtk PUBLIC assimp) target_link_libraries(qtk_library PUBLIC assimp)
elseif(QTK_ASSIMP_NEW_INTERFACE) elseif(QTK_ASSIMP_NEW_INTERFACE)
target_link_libraries(qtk PUBLIC assimp::assimp) target_link_libraries(qtk_library PUBLIC assimp::assimp)
endif() endif()
if(WIN32) if(WIN32)
target_link_libraries(qtk PUBLIC OpenGL::GL) target_link_libraries(qtk_library PUBLIC OpenGL::GL)
endif() endif()

View File

@@ -10,7 +10,6 @@
#include "meshrenderer.h" #include "meshrenderer.h"
#include "scene.h" #include "scene.h"
#include "shaders.h"
#include "texture.h" #include "texture.h"
using namespace Qtk; using namespace Qtk;
@@ -36,7 +35,8 @@ MeshRenderer::MeshRenderer(const char * name) :
} }
MeshRenderer::MeshRenderer(const char * name, const ShapeBase & shape) : MeshRenderer::MeshRenderer(const char * name, const ShapeBase & shape) :
Object(name, shape, QTK_MESH), mDrawType(GL_TRIANGLES) Object(name, shape, QTK_MESH), mVertexShader(":/shaders/multi-color.vert"),
mFragmentShader(":/shaders/multi-color.frag"), mDrawType(GL_TRIANGLES)
{ {
mShape = Shape(shape); mShape = Shape(shape);
init(); init();
@@ -45,7 +45,7 @@ MeshRenderer::MeshRenderer(const char * name, const ShapeBase & shape) :
MeshRenderer::~MeshRenderer() MeshRenderer::~MeshRenderer()
{ {
sInstances.remove(mName); sInstances.remove(mName.c_str());
} }
/******************************************************************************* /*******************************************************************************
@@ -68,22 +68,10 @@ void MeshRenderer::init()
mVAO.bind(); mVAO.bind();
mProgram.create(); mProgram.create();
// If no shader is provided, use a default one.
if (mVertexShader.empty()) {
mProgram.addShaderFromSourceCode(QOpenGLShader::Vertex,
QTK_SHADER_VERTEX_MESH);
} else {
mProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, mProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,
mVertexShader.c_str()); mVertexShader.c_str());
}
if (mFragmentShader.empty()) {
mProgram.addShaderFromSourceCode(QOpenGLShader::Fragment,
QTK_SHADER_FRAGMENT_MESH);
} else {
mProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, mProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,
mFragmentShader.c_str()); mFragmentShader.c_str());
}
mProgram.link(); mProgram.link();
mProgram.bind(); mProgram.bind();
@@ -121,7 +109,9 @@ void MeshRenderer::draw()
bindShaders(); bindShaders();
mVAO.bind(); mVAO.bind();
mTexture.bind(); if (mTexture.hasTexture()) {
mTexture.getOpenGLTexture().bind();
}
// TODO: Automate uniforms some other way // TODO: Automate uniforms some other way
setUniformMVP(); setUniformMVP();
@@ -136,7 +126,9 @@ void MeshRenderer::draw()
mShape.mIndices.data()); mShape.mIndices.data());
} }
mTexture.bind(); if (mTexture.hasTexture()) {
mTexture.getOpenGLTexture().release();
}
mVAO.release(); mVAO.release();
releaseShaders(); 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 // 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]; return mManager[name];
} }
@@ -113,7 +102,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(), this); mManager.insert(getName().c_str(), this);
} }
void Model::processNode(aiNode * node, const aiScene * scene) void Model::processNode(aiNode * node, const aiScene * scene)
@@ -272,11 +261,13 @@ ModelMesh::Textures Model::loadMaterialTextures(aiMaterial * mat,
void Model::sortModelMeshes() void Model::sortModelMeshes()
{ {
auto cameraPos = Scene::getCamera().getTransform().getTranslation(); auto cameraPos = Scene::getCamera().getTransform();
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.distanceToPoint(a.mVertices[0].mPosition) return (cameraPos.getTranslation().distanceToPoint(
< cameraPos.distanceToPoint(b.mVertices[0].mPosition); a.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

@@ -55,15 +55,15 @@ namespace Qtk
*/ */
inline Model(const char * name, inline Model(const char * name,
const char * path, const char * path,
const char * vertexShader = "", const char * vertexShader = ":/shaders/model-basic.vert",
const char * fragmentShader = "") : const char * fragmentShader = ":/shaders/model-basic.frag") :
Object(name, QTK_MODEL), mModelPath(path), Object(name, QTK_MODEL), mModelPath(path),
mVertexShader(vertexShader), mFragmentShader(fragmentShader) mVertexShader(vertexShader), mFragmentShader(fragmentShader)
{ {
loadModel(mModelPath); loadModel(mModelPath);
} }
inline ~Model() override { mManager.remove(getName()); } inline ~Model() override { mManager.remove(getName().c_str()); }
/************************************************************************* /*************************************************************************
* Public Methods * 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 * Accessors
************************************************************************/ ************************************************************************/

View File

@@ -8,7 +8,6 @@
#include "modelmesh.h" #include "modelmesh.h"
#include "scene.h" #include "scene.h"
#include "shaders.h"
using namespace Qtk; using namespace Qtk;
@@ -74,12 +73,12 @@ void ModelMesh::draw(QOpenGLShaderProgram & shader)
* Private Member Functions * Private Member Functions
******************************************************************************/ ******************************************************************************/
void ModelMesh::initMesh(const std::string & vert, const std::string & frag) void ModelMesh::initMesh(const char * vert, const char * frag)
{ {
initializeOpenGLFunctions(); initializeOpenGLFunctions();
// Create VAO, VBO, EBO // Create VAO, VBO, EBO
mVAO->create(); bool status = mVAO->create();
mVBO->create(); mVBO->create();
mEBO->create(); mEBO->create();
@@ -98,26 +97,10 @@ void ModelMesh::initMesh(const std::string & vert, const std::string & frag)
mEBO->release(); mEBO->release();
// Load and link shaders // Load and link shaders
if (!vert.empty()) { mProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, vert);
mProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, vert.c_str()); mProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, frag);
} else { mProgram->link();
mProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, mProgram->bind();
QTK_SHADER_VERTEX_MODEL);
}
if (!frag.empty()) {
mProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, frag.c_str());
} else {
mProgram->addShaderFromSourceCode(QOpenGLShader::Fragment,
QTK_SHADER_FRAGMENT_MODEL);
}
if (!mProgram->link()) {
qDebug() << "Failed to link shader: " << mProgram->log();
}
if (!mProgram->bind()) {
qDebug() << "Failed to bind shader: " << mProgram->log();
}
// Positions // Positions
mProgram->enableAttributeArray(0); mProgram->enableAttributeArray(0);

View File

@@ -97,8 +97,8 @@ namespace Qtk
ModelMesh(Vertices vertices, ModelMesh(Vertices vertices,
Indices indices, Indices indices,
Textures textures, Textures textures,
const char * vertexShader = "", const char * vertexShader = ":/model-basic.vert",
const char * fragmentShader = "") : const char * fragmentShader = ":/model-basic.frag") :
mProgram(new QOpenGLShaderProgram), mProgram(new QOpenGLShaderProgram),
mVAO(new QOpenGLVertexArrayObject), mVAO(new QOpenGLVertexArrayObject),
mVBO(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)), mVBO(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)),
@@ -146,7 +146,7 @@ namespace Qtk
* @param vert Path to vertex shader to use for this model. * @param vert Path to vertex shader to use for this model.
* @param frag Path to fragment shader to use for this model. * @param frag Path to fragment shader to use for this model.
*/ */
void initMesh(const std::string & vert, const std::string & frag); void initMesh(const char * vert, const char * frag);
/************************************************************************* /*************************************************************************
* Private Members * Private Members

View File

@@ -9,15 +9,3 @@
#include "object.h" #include "object.h"
using namespace Qtk; using namespace Qtk;
std::string Object::getShaderSourceCode(
QOpenGLShader::ShaderType shader_type) const
{
for (const auto & shader : mProgram.shaders()) {
if (shader->shaderType() == shader_type) {
return shader->sourceCode().toStdString();
}
}
qDebug() << "Failed to find shader of type " << shader_type;
return "";
}

View File

@@ -51,6 +51,7 @@ namespace Qtk
mName(name), mVBO(QOpenGLBuffer::VertexBuffer), mBound(false), mName(name), mVBO(QOpenGLBuffer::VertexBuffer), mBound(false),
mType(type) mType(type)
{ {
initResources();
setObjectName(name); setObjectName(name);
} }
@@ -59,6 +60,7 @@ namespace Qtk
mName(name), mVBO(QOpenGLBuffer::VertexBuffer), mShape(shape), mName(name), mVBO(QOpenGLBuffer::VertexBuffer), mShape(shape),
mBound(false), mType(type) mBound(false), mType(type)
{ {
initResources();
setObjectName(name); setObjectName(name);
} }
@@ -100,7 +102,7 @@ namespace Qtk
return mShape.mVertices; 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; } [[nodiscard]] inline const Type & getType() const { return mType; }
@@ -124,26 +126,11 @@ namespace Qtk
return "Base Object has no fragment shader."; return "Base Object has no fragment shader.";
} }
[[nodiscard]] virtual std::string getShaderSourceCode(
QOpenGLShader::ShaderType shader_type) const;
[[nodiscard]] virtual inline std::string getVertexShaderSourceCode() const
{
return getShaderSourceCode(QOpenGLShader::Vertex);
}
[[nodiscard]] virtual inline std::string getFragmentShaderSourceCode()
const
{
return getShaderSourceCode(QOpenGLShader::Fragment);
}
/************************************************************************* /*************************************************************************
* Setters * 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) virtual inline void setColors(const Colors & value)
{ {
@@ -244,23 +231,6 @@ 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
@@ -272,7 +242,7 @@ namespace Qtk
Transform3D mTransform; Transform3D mTransform;
Shape mShape; Shape mShape;
Texture mTexture; Texture mTexture;
QString mName; std::string mName;
bool mBound; bool mBound;
Type mType = QTK_OBJECT; Type mType = QTK_OBJECT;
}; };

View File

@@ -22,6 +22,16 @@
#define QTKAPI #define QTKAPI
#endif #endif
/**
* Initialize Qt resources required by the Qtk library.
* This cannot be defined within any namespace, but can be called by ctors.
* See object.h for example.
*/
inline void initResources()
{
Q_INIT_RESOURCE(resources);
}
namespace Qtk namespace Qtk
{ {
/** /**
@@ -43,6 +53,14 @@ namespace Qtk
} }
return widget; return widget;
} }
/**
* @return Default icon to use for Qtk desktop application.
*/
static QIcon getIcon()
{
return QIcon(":/icons/icon.png");
}
} // namespace Qtk } // namespace Qtk
#endif // QTK_QTKAPI_H #endif // QTK_QTKAPI_H

View File

@@ -55,34 +55,6 @@ 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) {
@@ -131,7 +103,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) { if (object->getName() == name.toStdString()) {
return object; return object;
} }
} }
@@ -149,6 +121,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() + " (" + QString::number(count) + ")"); object->setName(object->getName() + " (" + std::to_string(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", ""); auto fileName = url.fileName().replace(".obj", "").toStdString();
auto filePath = url.toLocalFile(); auto filePath = url.toLocalFile().toStdString();
loadModel(fileName, filePath); 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. // 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.toStdString(), path.toStdString()); mModelLoadQueue.emplace(name, path);
} }
/************************************************************************* /*************************************************************************
@@ -120,7 +120,9 @@ namespace Qtk
*/ */
[[nodiscard]] uint64_t getObjectCount(const QString & name) [[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. * Adds objects to the scene.
* This template provides explicit specializations for the valid types: * This template provides explicit specializations for valid types.
* MeshRenderer, Model * Adding any object other than these types will cause errors.
* 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
@@ -195,17 +196,6 @@ 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.
*/ */
@@ -258,7 +248,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<QString, uint64_t> mObjectCount; std::unordered_map<std::string, uint64_t> mObjectCount;
}; };
} // namespace Qtk } // namespace Qtk

View File

@@ -1,121 +0,0 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2025 Shaun Reed, all rights reserved ##
## About: Default GLSL shaders to use for objects if no shader if provided. ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTK_SHADERS_H
#define QTK_SHADERS_H
//
// Model
#define QTK_SHADER_VERTEX_MODEL \
R"(
#version 330 core
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTextureCoord;
out vec2 vTextureCoord;
uniform mat4 uModel;
uniform mat4 uView;
uniform mat4 uProjection;
void main()
{
vTextureCoord = aTextureCoord;
gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);
}
)"
#define QTK_SHADER_FRAGMENT_MODEL \
R"(
#version 330 core
out vec4 fColor;
in vec2 vTextureCoord;
uniform sampler2D texture_diffuse1;
void main()
{
fColor = texture(texture_diffuse1, vTextureCoord);
}
)"
//
// MeshRenderer
#define QTK_SHADER_VERTEX_MESH \
R"(
#version 330
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec3 aColor;
out vec4 vColor;
uniform mat4 uModel; // Model
uniform mat4 uView; // View
uniform mat4 uProjection; // Projection
void main()
{
gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);
vColor = vec4(aColor, 1.0f);
}
)"
#define QTK_SHADER_FRAGMENT_MESH \
R"(
#version 330
in vec4 vColor;
out vec4 fColor;
void main()
{
fColor = vColor;
}
)"
//
// Skybox
#define QTK_SHADER_VERTEX_SKYBOX \
R"(
#version 330
layout(location = 0) in vec3 aPosition;
out vec3 vTexCoord;
uniform mat4 uProjectionMatrix;
uniform mat4 uViewMatrix;
void main()
{
// Strip translation column from camera's 4x4 matrix
mat4 view = mat4(mat3(uViewMatrix));
gl_Position = uProjectionMatrix * view * vec4(aPosition, 1.0);
vTexCoord = aPosition;
}
)"
#define QTK_SHADER_FRAGMENT_SKYBOX \
R"(
#version 330
uniform samplerCube uTexture;
in vec3 vTexCoord;
out vec4 FragColor;
void main()
{
FragColor = texture(uTexture, vTexCoord);
}
)"
#endif // QTK_SHADERS_H

View File

@@ -8,7 +8,6 @@
#include "skybox.h" #include "skybox.h"
#include "scene.h" #include "scene.h"
#include "shaders.h"
#include "texture.h" #include "texture.h"
using namespace Qtk; using namespace Qtk;
@@ -17,28 +16,9 @@ using namespace Qtk;
* Constructors / Destructors * Constructors / Destructors
******************************************************************************/ ******************************************************************************/
Skybox::Skybox(const std::string & name) : Skybox::Skybox(QOpenGLTexture * cubeMap, const std::string & name)
mVBO(QOpenGLBuffer::VertexBuffer),
mVertices(Cube(QTK_DRAW_ELEMENTS).getVertices()),
mIndices(Cube(QTK_DRAW_ELEMENTS).getIndexData())
{ {
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(); init();
} }
@@ -53,13 +33,13 @@ Skybox::Skybox(const std::string & right,
mVertices(Cube(QTK_DRAW_ELEMENTS).getVertices()), mVertices(Cube(QTK_DRAW_ELEMENTS).getVertices()),
mIndices(Cube(QTK_DRAW_ELEMENTS).getIndexData()) mIndices(Cube(QTK_DRAW_ELEMENTS).getIndexData())
{ {
init();
mTexture.setCubeMap(QImage(right.c_str()).mirrored(), mTexture.setCubeMap(QImage(right.c_str()).mirrored(),
QImage(top.c_str()), QImage(top.c_str()),
QImage(front.c_str()), QImage(front.c_str()),
QImage(left.c_str()), QImage(left.c_str()),
QImage(bottom.c_str()), QImage(bottom.c_str()),
QImage(back.c_str())); QImage(back.c_str()));
init();
} }
/******************************************************************************* /*******************************************************************************
@@ -73,7 +53,7 @@ void Skybox::draw()
mVAO.bind(); mVAO.bind();
mProgram.bind(); mProgram.bind();
mTexture.bind(); mTexture.getOpenGLTexture().bind();
mProgram.setUniformValue("uProjectionMatrix", Scene::getProjectionMatrix()); mProgram.setUniformValue("uProjectionMatrix", Scene::getProjectionMatrix());
mProgram.setUniformValue("uViewMatrix", Scene::getCamera().toMatrix()); mProgram.setUniformValue("uViewMatrix", Scene::getCamera().toMatrix());
@@ -81,7 +61,7 @@ void Skybox::draw()
glDrawElements( glDrawElements(
GL_TRIANGLES, mIndices.size(), GL_UNSIGNED_INT, mIndices.data()); GL_TRIANGLES, mIndices.size(), GL_UNSIGNED_INT, mIndices.data());
mTexture.bind(); mTexture.getOpenGLTexture().bind();
mProgram.release(); mProgram.release();
mVAO.release(); mVAO.release();
@@ -100,10 +80,10 @@ void Skybox::init()
// Set up shader program // Set up shader program
mProgram.create(); mProgram.create();
mProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, mProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,
QTK_SHADER_FRAGMENT_SKYBOX); ":/shaders/skybox.vert");
mProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, mProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,
QTK_SHADER_VERTEX_SKYBOX); ":/shaders/skybox.frag");
mProgram.link(); mProgram.link();
mProgram.bind(); mProgram.bind();

View File

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

View File

@@ -8,7 +8,6 @@
#include <QDebug> #include <QDebug>
#include <QImageReader> #include <QImageReader>
#include <QPainter>
#include "texture.h" #include "texture.h"
@@ -66,22 +65,6 @@ QOpenGLTexture * OpenGLTextureFactory::initCubeMap(const char * right,
QImage(back)); 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, QOpenGLTexture * OpenGLTextureFactory::initCubeMap(const QImage & right,
const QImage & top, const QImage & top,
const QImage & front, const QImage & front,
@@ -104,9 +87,9 @@ QOpenGLTexture * OpenGLTextureFactory::initCubeMap(const QImage & right,
QOpenGLTexture::CubeMapNegativeZ}; QOpenGLTexture::CubeMapNegativeZ};
int i = 0; int i = 0;
for (const auto & face : faces) { for (const auto & face : faces) {
QImage & faceImage = faceTextures[i]; QImage faceImage(faceTextures[i]);
if (faceImage.isNull()) { if (faceImage.isNull()) {
qDebug() << "[libqtk] Error loading cube map image\n"; qDebug() << "Error loading cube map image\n";
faceImage = defaultTexture(); faceImage = defaultTexture();
} }
faceImage = faceImage.convertToFormat(QImage::Format_RGBA8888); faceImage = faceImage.convertToFormat(QImage::Format_RGBA8888);

View File

@@ -145,7 +145,13 @@ namespace Qtk
const char * back); const char * back);
/// The texture used in place of a missing texture. /// 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:
// Private ctor to prevent creating instances of this class // Private ctor to prevent creating instances of this class
@@ -217,20 +223,6 @@ namespace Qtk
return mOpenGLTexture != Q_NULLPTR; 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 * Accessors
************************************************************************/ ************************************************************************/
@@ -314,12 +306,12 @@ namespace Qtk
/** /**
* Sets this Texture to be a cube map with provided sides. * Sets this Texture to be a cube map with provided sides.
* *
* @param right QImage texture to use for right cube map side. * @param right Path to texture to use for right cube map side.
* @param top QImage texture to use for top cube map side. * @param top Path to texture to use for top cube map side.
* @param front QImage texture to use for front cube map side. * @param front Path to texture to use for front cube map side.
* @param left QImage texture to use for left cube map side. * @param left Path to texture to use for left cube map side.
* @param bottom QImage texture to use for bottom cube map side. * @param bottom Path to texture to use for bottom cube map side.
* @param back QImage texture to use for back cube map side. * @param back Path to texture to use for back cube map side.
*/ */
virtual inline void setCubeMap(const QImage & right, virtual inline void setCubeMap(const QImage & right,
const QImage & top, const QImage & top,

View File

@@ -11,6 +11,5 @@ cmake -B build && cmake --build build -- -j $(nproc --ignore=1)
# Run clang-tidy and clang-format # Run clang-tidy and clang-format
SOURCES="src/**/*.cpp src/**/*.h example-app/*.cpp example-app/*.h" SOURCES="src/**/*.cpp src/**/*.h example-app/*.cpp example-app/*.h"
run-clang-tidy \ clang-tidy -p build/ --fix --config-file=.clang-tidy $SOURCES
-p build/ -j $(nproc --ignore=1) -fix -config-file=.clang-tidy $SOURCES
clang-format -i --style=file:.clang-format $SOURCES clang-format -i --style=file:.clang-format $SOURCES