diff --git a/.github/workflows/all-builds.yml b/.github/workflows/all-builds.yml index 2596ad4..fbc3abe 100644 --- a/.github/workflows/all-builds.yml +++ b/.github/workflows/all-builds.yml @@ -5,21 +5,27 @@ on: pull_request: workflow_dispatch: +env: + QT_VERSION: 6.6.0 + jobs: - Build-Qtk: + Qtk: env: - CONFIG: -DQTK_UPDATE_SUBMODULES=ON -DQTK_DEBUG=OFF -DQTK_ENABLE_CCACHE=OFF -DQTK_BUILD_GUI=ON -DQTK_INSTALL_LIBRARY=ON -DQTK_INSTALL_PLUGINS=OFF + CONFIG: -DQTK_SUBMODULES=ON -DQTK_DEBUG=OFF -DQTK_CCACHE=OFF -DQTK_GUI=ON -DQTK_PLUGINS=OFF -DQTK_EXAMPLE=ON strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] include: - os: ubuntu-latest - cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/6.5.0/gcc_64/ $CONFIG + cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG + flags: -j $(nproc) - os: windows-latest - cmake: -DCMAKE_PREFIX_PATH=D:/a/qtk/qtk/Qt/6.5.0/mingw81_64/ $CONFIG + cmake: -DCMAKE_PREFIX_PATH=D:/a/qtk/qtk/Qt/$QT_VERSION/mingw81_64/ $CONFIG + flags: '' - os: macos-latest - cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/6.5.0/gcc_64/ $CONFIG + cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG + flags: -j $(nproc) runs-on: ${{ matrix.os }} steps: @@ -28,7 +34,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '6.5.0' + version: ${{ env.QT_VERSION }} # Windows @@ -44,33 +50,24 @@ jobs: sudo apt update -y sudo apt install libxcb-cursor0 -y - - name: Configure Qtk Application (Windows) - if: matrix.os == 'windows-latest' + - name: Configure Qtk Application shell: bash run: cmake -B build/ ${{ matrix.cmake }} - - name: Build Qtk Application (Windows) - if: matrix.os == 'windows-latest' + - name: Build Qtk Application shell: bash - run: cmake --build build/ --config Release + run: cmake --build build/ --config Release --target qtk_gui ${{ matrix.flags }} - # OSX / Linux - - - name: Configure Qtk Application (OSX / Linux) + - name: Build Qtk Example if: matrix.os != 'windows-latest' shell: bash - run: cmake -B build/ ${{ matrix.cmake }} - - - name: Build Qtk Application (OSX / Linux) - if: matrix.os != 'windows-latest' - shell: bash - run: cmake --build build/ --config Release --target qtk_app -- -j $(nproc) + run: cmake --build build/ --config Release --target qtk_example ${{ matrix.flags }} # Packaging - name: Install Qtk Application shell: bash - run: cmake --install build/ --config Release --prefix=$(pwd)/install --component qtk + run: cmake --install build/ --config Release --component qtk_gui - name: Package Qtk Application shell: bash @@ -87,7 +84,7 @@ jobs: if: matrix.os == 'ubuntu-latest' uses: actions/upload-artifact@v3 with: - name: qtk-app-${{ matrix.os }} + name: qtk-gui-${{ matrix.os }} path: | build/packages/*.deb @@ -102,7 +99,7 @@ jobs: if: matrix.os == 'windows-latest' uses: actions/upload-artifact@v3 with: - name: qtk-app-${{ matrix.os }} + name: qtk-gui-${{ matrix.os }} path: | build/packages/*.exe @@ -117,14 +114,14 @@ jobs: if: matrix.os == 'macos-latest' uses: actions/upload-artifact@v3 with: - name: qtk-app-${{ matrix.os }} + name: qtk-gui-${{ matrix.os }} path: | build/packages/*.tar.gz - name: Upload Qtk install directory uses: actions/upload-artifact@v3 with: - name: qtk-app-${{ matrix.os }}-install + name: qtk-gui-${{ matrix.os }}-install path: install/* # TODO: Enable after trimming resources. @@ -142,20 +139,23 @@ jobs: # build/packages/* # !build/packages/_CPack_Packages/* - Build-Qtk-Library: + Qtk-Library: env: - CONFIG: -DQTK_UPDATE_SUBMODULES=ON -DQTK_DEBUG=OFF -DQTK_ENABLE_CCACHE=OFF -DQTK_BUILD_GUI=OFF -DQTK_INSTALL_LIBRARY=ON -DQTK_INSTALL_PLUGINS=OFF + CONFIG: -DQTK_SUBMODULES=ON -DQTK_DEBUG=OFF -DQTK_CCACHE=OFF -DQTK_GUI=OFF -DQTK_PLUGINS=OFF -DQTK_EXAMPLE=OFF strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] include: - os: ubuntu-latest - cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/6.5.0/gcc_64/ $CONFIG + cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG + flags: -j $(nproc) - os: windows-latest - cmake: -DCMAKE_PREFIX_PATH=D:/a/qtk/qtk/Qt/6.5.0/mingw81_64/ $CONFIG + cmake: -DCMAKE_PREFIX_PATH=D:/a/qtk/qtk/Qt/$QT_VERSION/mingw81_64/ $CONFIG + flags: '' - os: macos-latest - cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/6.5.0/gcc_64/ $CONFIG + cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG + flags: -j $(nproc) runs-on: ${{ matrix.os }} steps: @@ -164,7 +164,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '6.5.0' + version: ${{ env.QT_VERSION }} # Windows @@ -174,33 +174,19 @@ jobs: with: args: install pkgconfiglite --checksum e87b5ea3c9142256af60f2d5b917aa63b571e6a0 --checksum-type sha1 - - name: Configure Qtk Library (Windows) - if: matrix.os == 'windows-latest' + - name: Configure Qtk Library shell: bash run: cmake -B build/ ${{ matrix.cmake }} - - name: Build Qtk Library (Windows) - if: matrix.os == 'windows-latest' + - name: Build Qtk Library shell: bash - run: cmake --build build/ --config Release - - # OSX / Linux - - - name: Configure Qtk Library (OSX / Linux) - if: matrix.os != 'windows-latest' - shell: bash - run: cmake -B build/ ${{ matrix.cmake }} - - - name: Build Qtk Library (OSX / Linux) - if: matrix.os != 'windows-latest' - shell: bash - run: cmake --build build/ --config Release --target qtk_library -- -j $(nproc) + run: cmake --build build/ --config Release --target qtk_library -- ${{ matrix.flags }} # Packaging - name: Install Qtk Library shell: bash - run: cmake --install build/ --config Release --prefix=$(pwd)/install --component libqtk + run: cmake --install build/ --config Release --prefix=$(pwd)/install --component qtk_library - name: Package Qtk Library shell: bash @@ -258,20 +244,23 @@ jobs: name: libqtk-${{ matrix.os }}-install path: install/* - Build-Qtk-Plugins: + Qtk-Plugins: env: - CONFIG: -DQTK_UPDATE_SUBMODULES=ON -DQTK_DEBUG=OFF -DQTK_ENABLE_CCACHE=OFF -DQTK_BUILD_GUI=OFF -DQTK_INSTALL_LIBRARY=OFF -DQTK_INSTALL_PLUGINS=ON + CONFIG: -DQTK_SUBMODULES=ON -DQTK_DEBUG=OFF -DQTK_CCACHE=OFF -DQTK_GUI=OFF -DQTK_PLUGINS=ON -DQTK_EXAMPLE=OFF strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] include: - os: ubuntu-latest - cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/6.5.0/gcc_64/ $CONFIG + cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG + flags: -j $(nproc) - os: windows-latest - cmake: -DCMAKE_PREFIX_PATH=D:/a/qtk/qtk/Qt/6.5.0/mingw81_64/ $CONFIG + cmake: -DCMAKE_PREFIX_PATH=D:/a/qtk/qtk/Qt/$QT_VERSION/mingw81_64/ $CONFIG + flags: '' - os: macos-latest - cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/6.5.0/gcc_64/ $CONFIG + cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ $CONFIG + flags: -j $(nproc) runs-on: ${{ matrix.os }} steps: @@ -280,9 +269,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '6.5.0' - - # Windows + version: ${{ env.QT_VERSION }} - name: Chocolatey Action if: matrix.os == 'windows-latest' @@ -290,44 +277,30 @@ jobs: with: args: install pkgconfiglite --checksum e87b5ea3c9142256af60f2d5b917aa63b571e6a0 --checksum-type sha1 - - name: Configure Qtk Plugins (Windows) - if: matrix.os == 'windows-latest' + - name: Configure Qtk Plugins shell: bash run: cmake -B build/ ${{ matrix.cmake }} - - name: Build Qtk Plugins (Windows) - if: matrix.os == 'windows-latest' + - name: Build Qtk Plugins shell: bash - run: cmake --build build/ --config Release --target qtk_collection - - # OSX / Linux - - - name: Configure Qtk Plugins (OSX / Linux) - if: matrix.os != 'windows-latest' - shell: bash - run: cmake -B build/ ${{ matrix.cmake }} - - - name: Build Qtk Plugins (OSX / Linux) - if: matrix.os != 'windows-latest' - shell: bash - run: cmake --build build/ --config Release --target qtk_collection -- -j $(nproc) + run: cmake --build build/ --config Release --target qtk_plugins -- ${{ matrix.flags }} # Packaging - name: Install Qtk Plugins shell: bash - run: cmake --install build/ --config Release --prefix=$(pwd)/install --component collection + run: cmake --install build/ --config Release --component qtk_plugins - Build-Qtk-Assimp-Targets: + Qtk-Assimp-Targets: strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] include: - os: ubuntu-latest - cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/6.5.0/gcc_64/ + cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ - os: macos-latest - cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/6.5.0/gcc_64/ -DASSIMP_NEW_INTERFACE=ON + cmake: -DCMAKE_PREFIX_PATH=/home/runner/work/qtk/Qt/$QT_VERSION/gcc_64/ runs-on: ${{ matrix.os }} steps: @@ -336,7 +309,7 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v2 with: - version: '6.5.0' + version: ${{ env.QT_VERSION }} - name: Install Assimp MacOS if: matrix.os == 'macos-latest' @@ -352,7 +325,7 @@ jobs: - name: Configure Qtk shell: bash - run: cmake -B build/ ${{ matrix.cmake }} -DQTK_ENABLE_CCACHE=OFF + run: cmake -B build/ ${{ matrix.cmake }} -DQTK_CCACHE=OFF -DQTK_ASSIMP_NEW_INTERFACE=ON - name: Build Qtk shell: bash diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 8568d35..fd01ba9 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -21,8 +21,8 @@ jobs: - name: Build Qtk run: | - cmake -B build -DQTK_UPDATE_SUBMODULES=OFF -DQTK_ENABLE_CCACHE=OFF - cmake --build build + cmake -B build -DQTK_SUBMODULES=OFF -DQTK_CCACHE=OFF -DQTK_PLUGINS=OFF -DQTK_GUI=ON + cmake --build build --target qtk_gui -- -j $(nproc) - uses: cpp-linter/cpp-linter-action@v2 id: linter diff --git a/.gitignore b/.gitignore index 4a6a3a8..776470a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # CLion **/.idea/** +# VS Code +**/.vscode/** + # CMake build files **/cmake-build-debug/** **/build/** diff --git a/CMakeLists.txt b/CMakeLists.txt index 57cc0a2..08aa479 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,14 +46,13 @@ include(GNUInstallDirs) # Options ################################################################################ option(QTK_DEBUG "Enable debugger" OFF) -option(QTK_UPDATE_SUBMODULES "Update external project (assimp) submodule" OFF) -option(QTK_BUILD_GUI "Build the Qtk desktop application" ON) -option(QTK_INSTALL_LIBRARY "Install libqtk to CMAKE_INSTALL_PREFIX path." ON) -option(QTK_INSTALL_PLUGINS "Install Qtk plugin collection to Qt Creator." OFF) -option(QTK_BUILD_EXAMPLE "Build the Qtk example desktop application" ON) -option(QTK_ENABLE_CCACHE "Enable ccache" ON) +option(QTK_SUBMODULES "Update external project (assimp) submodule" OFF) +option(QTK_GUI "Build the Qtk desktop application" ON) +option(QTK_PLUGINS "Install Qtk plugins to Qt Creator path." OFF) +option(QTK_EXAMPLE "Build the Qtk example desktop application" ON) +option(QTK_CCACHE "Enable ccache" ON) -if (QTK_ENABLE_CCACHE) +if (QTK_CCACHE) set(CMAKE_CXX_COMPILER_LAUNCHER ccache) endif() @@ -61,21 +60,30 @@ endif() 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_UPDATE_SUBMODULES OFF +# + 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(NOT QTK_DEBUG) - set(CMAKE_BUILD_TYPE Release) -else() +if(QTK_DEBUG OR CMAKE_BUILD_TYPE MATCHES "^[Dd][Ee][Bb][Uu][Gg]$") + set(QTK_DEBUG ON) set(CMAKE_BUILD_TYPE Debug) +else() + set(QTK_DEBUG OFF) + set(CMAKE_BUILD_TYPE Release) endif() # This should be set to your Qt6 installation directory. -set(QT_INSTALL_DIR "$ENV{HOME}/Qt/6.5.0/gcc_64" CACHE PATH "Path to Qt6 install.") +set(QT_INSTALL_DIR "$ENV{HOME}/Qt/6.5.0/gcc_64/lib/cmake" CACHE PATH "Path to Qt6 install.") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install") +endif () + +set(QTK_RESOURCES "${CMAKE_SOURCE_DIR}/resources") +set(QTK_OSX_ICONS ${CMAKE_SOURCE_DIR}/resources/icons/osx/kilroy.icns) + # Point CMAKE_PREFIX_PATH to Qt6 install directory # If Qtk is built within Qt Creator this is not required. list(APPEND CMAKE_PREFIX_PATH "${QT_INSTALL_DIR}") @@ -89,31 +97,13 @@ set( "${QT_INSTALL_DIR}/../../Tools/QtCreator" CACHE PATH "Qt Creator path used to install Qtk plugins for Qt Designer." ) -# Qt Designer will look in different locations if WIN / Unix. -# These paths are for using Qt Designer integrated within Qt Creator. -# Standalone Qt Designer may use different paths. -if (WIN32) - # These paths may be different on windows. I have not tested this. - set(QT_PLUGIN_INSTALL_DIR "${QT_CREATOR_DIR}/bin/plugins/designer") - set(QT_PLUGIN_LIBRARY_DIR "${QT_CREATOR_DIR}/lib/Qt/lib") -else() - set(QT_PLUGIN_INSTALL_DIR "${QT_CREATOR_DIR}/lib/Qt/plugins/designer") - set(QT_PLUGIN_LIBRARY_DIR "${QT_CREATOR_DIR}/lib/Qt/lib") -endif() -set(QTK_PLUGIN_LIBRARY_DIR "${QT_PLUGIN_LIBRARY_DIR}") -set(QTK_PLUGIN_INSTALL_DIR "${QT_PLUGIN_INSTALL_DIR}") -message(STATUS "[Qtk] CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -set(QTK_RESOURCES "${CMAKE_SOURCE_DIR}/resources") -set(QTK_OSX_ICONS ${CMAKE_SOURCE_DIR}/resources/icons/osx/kilroy.icns) - -# Print all QTK options and their values. +# Print all QTK options and their values at the end of configuration. +# We initialize this list here so that we can append to it as needed. +# All variables in this list will be printed at the end of configuration. get_cmake_property(VAR_NAMES VARIABLES) -list(FILTER VAR_NAMES INCLUDE REGEX "^Q[tT][kK]_.*$") +list(FILTER VAR_NAMES INCLUDE REGEX "^[qQ][tT][kK]_.*$") list(SORT VAR_NAMES) -foreach(VAR_NAME ${VAR_NAMES}) - message(STATUS "[Qtk] ${VAR_NAME}=${${VAR_NAME}}") -endforeach() ################################################################################ # External Dependencies @@ -133,8 +123,33 @@ if(NOT Qt6_FOUND) ) endif() +# +# To use custom plugins, set QT_PLUGIN_PATH environment variable before running designer +# Or, we can install plugins to the designer for use across all projects. +# Qt Creator on linux will look here for widget plugins in the integrated designer +# /home/shaun/Qt/Tools/QtCreator/lib/Qt/lib +# Qt Designer will use the following path on linux +# /home/shaun/Qt/6.5.0/gcc_64/plugins/designer/ +# We can use this path after find_package(Qt6) to install our plugins on all systems +# ${QT6_INSTALL_PREFIX}/${QT6_INSTALL_PLUGINS}/designer +# And run designer at ${QT6_INSTALL_PREFIX}/bin/designer +# Use cmake -DQTK_PLUGIN_INSTALL_DIR=/some/path to override this install path +set( + QTK_PLUGIN_INSTALL_DIR + "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_PLUGINS}/designer" CACHE PATH + "Path to install Qtk plugin collection." +) +# See cmake configure output for values of these variables on your system +set( + VAR_PATHS + CMAKE_PREFIX_PATH CMAKE_INSTALL_PREFIX QTK_PLUGIN_INSTALL_DIR QT6_INSTALL_PREFIX + QT_INSTALL_DIR +) +# Add QT6_INSTALL_PLUGINS to VAR_NAMES so it is printed at end of configuration. +list(APPEND VAR_NAMES QT6_INSTALL_PLUGINS) + # Find Assimp. -if(QTK_UPDATE_SUBMODULES) +if(QTK_SUBMODULES) # Required to statically link. add_compile_options(-fPIC) set(BUILD_SHARED_LIBS OFF CACHE STRING "Build static assimp libs" FORCE) @@ -166,8 +181,20 @@ endif() ################################################################################ add_subdirectory(src) -if(QTK_BUILD_EXAMPLE) +if(QTK_EXAMPLE) # Create a namespaced alias for linking with qtk_library in the example. add_library(${PROJECT_NAME}::qtk_library ALIAS qtk_library) - add_subdirectory(example-app) + add_subdirectory(example-app EXCLUDE_FROM_ALL) endif() + +# Print all QTK options and their values at the end of configuration. This also +# prints any additional variables that we have added to VAR_NAMES and VAR_PATHS. +foreach(VAR_NAME IN LISTS VAR_NAMES VAR_PATHS) + if(VAR_NAME IN_LIST VAR_PATHS) + # Print absolute if variable is path + get_filename_component(VAR_REALPATH "${${VAR_NAME}}" REALPATH) + message(STATUS "[Qtk] ${VAR_NAME}=${VAR_REALPATH}") + else() + message(STATUS "[Qtk] ${VAR_NAME}=${${VAR_NAME}}") + endif() +endforeach() diff --git a/README.md b/README.md index a1302b0..623fbf3 100644 --- a/README.md +++ b/README.md @@ -40,88 +40,59 @@ and [Qt Creator](https://github.com/qt-creator/qt-creator). Simply open the root `CMakeLists.txt` with either of these editors and configurations will be loaded. -This project has been ported to **Qt 6.5.0**, which is not yet available in +This project has been ported to **Qt 6.6.0**, which is not yet available in Ubuntu apt repositories. To run this project, you will *need* to install [Qt6 Open Source Binaries](https://www.qt.io/download-qt-installer) for -your system, **version 6.5.0** or later. +your system, **version 6.6.0** or later. Be sure to take note of the Qt6 installation directory, as we will need it to correctly set our `CMAKE_PREFIX_PATH` in the next steps. +If you are building on **Windows / Mac**, consider setting +the `-DQTK_ASSIMP_NEW_INTERFACE` cmake build option. + If the build is configured with all options enabled, we can subsequently install individual components as needed with cmake. ```bash -cmake -B build-all/ -DQTK_BUILD_GUI=ON -DQTK_INSTALL_LIBRARY=ON -DQTK_INSTALL_PLUGINS=ON -``` +sudo apt update -y && sudo apt install libassimp-dev cmake build-essential git ccache libgl1-mesa-dev libglvnd-dev zlib1g-dev -y +git clone https://github.com/shaunrd0/qtk +cd qtk +# Configure the build with all components enabled +cmake -B build-all -DQTK_GUI=ON -DQTK_PLUGINS=ON -DQTK_EXAMPLE=ON -DCMAKE_PREFIX_PATH=$HOME/Qt/6.6.0/gcc_64 +# Build all targets +cmake --build build-all/ +```` + +By default, the build will not initialize Assimp as a git submodule and build +from source. +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 -# Install libqtk only -cmake --install build-all/ --prefix=$(pwd)/install --component libqtk --- Install configuration: "Release" --- Up-to-date: /home/shaun/Code/qtk/install/lib/cmake/Qtk/QtkConfig.cmake --- Up-to-date: /home/shaun/Code/qtk/install/lib/cmake/Qtk/QtkConfigVersion.cmake --- Up-to-date: /home/shaun/Code/qtk/install/lib/cmake/Qtk/QtkTargets.cmake --- Up-to-date: /home/shaun/Code/qtk/install/lib/cmake/Qtk/QtkTargets-release.cmake --- Up-to-date: /home/shaun/Code/qtk/install/lib/static/libqtk_library.a --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/camera3d.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/input.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/meshrenderer.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/model.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/modelmesh.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/object.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/qtkapi.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/qtkiostream.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/qtkiosystem.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/scene.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/shape.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/skybox.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/texture.h --- Up-to-date: /home/shaun/Code/qtk/install/include/qtk/transform3D.h - -# Install Qtk widget collection to use Qt Designer -cmake --install build-all/ --prefix=$(pwd)/install --component collection --- Install configuration: "Release" --- Up-to-date: /home/shaun/Qt/6.5.0/gcc_64/../../Tools/QtCreator/lib/Qt/lib/libqtk_library.a --- Up-to-date: /home/shaun/Qt/6.5.0/gcc_64/../../Tools/QtCreator/lib/Qt/lib/libqtk_plugin_library.a --- Up-to-date: /home/shaun/Qt/6.5.0/gcc_64/../../Tools/QtCreator/lib/Qt/plugins/designer/libqtk_collection.so - -# Install Qtk desktop application (output removed) -cmake --install build-all/ --prefix=$(pwd)/install --component qtk +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 GUI -Once Qt6 is installed, to build and run `qtk` on Ubuntu - - ```bash -sudo apt update -y && sudo apt install libassimp-dev cmake build-essential git ccache -y -git clone https://github.com/shaunrd0/qtk -cmake -S qtk/ -B qtk/build/ -DCMAKE_PREFIX_PATH=$HOME/Qt/6.5.0/gcc_64 -cmake --build qtk/build/ -j $(nproc --ignore=2) -./qtk/build/bin/qtk-main -``` - -By default, the build will not initialize Assimp as a git submodule and build -from source. -We can turn this on by setting the `-DQTK_UPDATE_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_UPDATE_SUBMODULES=ON` supports providing assimp on cross-platform -builds (Windows / Mac / Linux) and may be easier to configure. - -```bash -cmake -S qtk/ -B qtk/build/ -DQTK_UPDATE_SUBMODULES=ON -DCMAKE_PREFIX_PATH=$HOME/Qt/6.5.0/gcc_64 -cmake --build qtk/build/ -j $(nproc --ignore=2) -./qtk/build/bin/qtk-main +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 ./qtk-main +QT_DEBUG_PLUGINS=1 ./install/bin/qtk_gui ``` #### Qtk Library @@ -131,12 +102,10 @@ 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 using libqtk. -Below is an example of installing on a system path. - ```bash -cmake -S qtk/ -B qtk/build/ -DCMAKE_PREFIX_PATH=$HOME/Qt/6.5.0/gcc_64 -DQTK_BUILD_GUI=OFF -DQTK_INSTALL_PLUGINS=OFF -DQTK_DEBUG=OFF -cmake --build qtk/build/ -j $(nproc --ignore=2) -sudo cmake --install . --prefix=/usr/local +# Install libqtk only +cmake --build build-all/ --target qtk_library -- -j $(nproc) +cmake --install build-all/ --component qtk_library --prefix=/usr/local -- Install configuration: "Release" -- Installing: /usr/local/lib/cmake/Qtk/QtkConfig.cmake -- Installing: /usr/local/lib/cmake/Qtk/QtkConfigVersion.cmake @@ -175,9 +144,14 @@ interfaces. To build and install the Qtk plugin collection - ```bash -cmake -S /path/to/qtk -B /path/to/qtk/build -DCMAKE_PREFIX_PATH=$HOME/Qt/6.5.0/gcc_64 -DQTK_INSTALL_PLUGINS=ON -DQTK_BUILD_GUI=OFF -DQTK_INSTALL_LIBRARY=OFF -cmake --build /path/to/qtk/build -cmake --install /path/to/qtk/build +cmake --build build-all/ --target qtk_plugins -- -j $(nproc) +# Install Qtk widget collection to use Qt Designer +# The path here should be initialized during build configuration, so no need for --prefix +cmake --install build-all/ --component qtk_plugins +-- Install configuration: "Release" +-- 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/plugins/designer/libqtk_collection.so ``` To uninstall after a previous installation, we can run the following command @@ -187,16 +161,22 @@ from the root of the repository. xargs rm < build/install_manifest.txt ``` -#### Windows / MacOS +#### Qtk Example -If you are building on **Windows / Mac**, consider setting -the `-DASSIMP_NEW_INTERFACE` build flag. +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 +target within any qtk build. ```bash -cmake -S qtk/ -B qtk/build/ -DASSIMP_NEW_INTERFACE=ON -DCMAKE_PREFIX_PATH=$HOME/Qt/6.5.0/gcc_64;/path/to/assimp/ -cmake --build qtk/build/ -j $(nproc --ignore=2) +# Build the example from a configured qtk build tree +cmake --build build-all/ --target qtk_example -- -j $(nproc) +cmake --install build-all/ --component qtk_example --prefix=install +./install/bin/qtk_example ``` +See the README in the [example-app/](example-app) subdirectory for instructions +on standalone builds. + ### Controls You can fly around the scene if you hold the right mouse button and use WASD. @@ -253,7 +233,7 @@ CLion automatically. # Move to the root of the repo cd qtk # Build -cmake -B build && cmake --build build +cmake -B build && cmake --build build -- -j $(nproc) clang-tidy -p build/ --fix --config-file=.clang-tidy src/*.cpp src/*.h app/*.cpp app/*.h ``` @@ -261,7 +241,7 @@ Last we need to run `clang-format`, this can be done with the command directly. This will reformat all the code in the repository. ```bash -clang-format -i --style=file:.clang-format src/*.cpp src/*.h app/*.cpp app/*.h +clang-format -i --style=file:.clang-format src/app/*.cpp src/app/*.h src/qtk/*.cpp src/qtk/*.h example-app/*.cpp example-app/*.h ``` `clang-format` can be run with git integration (or CLion if you prefer). @@ -320,12 +300,8 @@ Any of the above options can be appended with `--trace-expand` to debug package generation issues. The contents of all packages will depend on how the build was configured. -If we are generating packages for *only* libqtk, we -set `-DQTK_INSTALL_LIBRARY=ON` -during the cmake configuration step. To generate packages for Qtk desktop application, we should -set `-DQTK_BUILD_GUI=ON`, and optionally `-DQTK_INSTALL_LIBRARY=ON` if we would -like to bundle libqtk with the desktop application. +set `-DQTK_GUI=ON`. If this option is not set we will only package libqtk. The NSIS installer will allow component-specific path modification for all of these installation components through a GUI install application. diff --git a/cmake/include/git_submodule.cmake b/cmake/include/git_submodule.cmake index 26274e2..33f2f03 100644 --- a/cmake/include/git_submodule.cmake +++ b/cmake/include/git_submodule.cmake @@ -10,7 +10,7 @@ find_package(Git) # _PATH: Path to git submodule location that we want to update # + submodule_update(extern/assimp) function(submodule_update _PATH) - if (NOT QTK_UPDATE_SUBMODULES) + if (NOT QTK_SUBMODULES) return() endif() diff --git a/cmake/templates/Config.cmake.in b/cmake/templates/Config.cmake.in index 8026caa..cd8a459 100644 --- a/cmake/templates/Config.cmake.in +++ b/cmake/templates/Config.cmake.in @@ -2,8 +2,12 @@ include("${CMAKE_CURRENT_LIST_DIR}/QtkTargets.cmake") -set_and_check(QTK_EXECUTABLE "${PACKAGE_PREFIX_DIR}/bin/qtk_app") +set_and_check(QTK_EXECUTABLE "${PACKAGE_PREFIX_DIR}/bin/qtk_gui") set_and_check(QTK_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include") set_and_check(QTK_LIBRARIES "${PACKAGE_PREFIX_DIR}/lib") +set_and_check(Qtk_EXECUTABLE "${PACKAGE_PREFIX_DIR}/bin/qtk_gui") +set_and_check(Qtk_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include") +set_and_check(Qtk_LIBRARIES "${PACKAGE_PREFIX_DIR}/lib") + check_required_components(Qtk) diff --git a/example-app/CMakeLists.txt b/example-app/CMakeLists.txt index 673b8a3..fb92b2c 100644 --- a/example-app/CMakeLists.txt +++ b/example-app/CMakeLists.txt @@ -22,7 +22,11 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") endif() # If you did not install Qtk on a system path, point cmake to installation. -set(QTK_PATH /usr/local CACHE PATH "Path to installation of Qtk") +set( + QTK_PATH ../build/install/lib/cmake/Qtk + CACHE PATH "Path to installation of Qtk" + FORCE +) # If you did not install Qt6 on a system path, point cmake to installation. set(QT_INSTALL_DIR "$ENV{HOME}/Qt/6.5.0/gcc_64/" CACHE PATH "Path to Qt6") @@ -40,12 +44,6 @@ project( list(APPEND CMAKE_PREFIX_PATH "${QTK_PATH}") list(APPEND CMAKE_PREFIX_PATH "${QT_INSTALL_DIR}") -# Allow add_subdirectory on this project to use target ALIAS if available. -# If this example project is opened standalone we will use find_package. -if(NOT TARGET Qtk::qtk_library) - find_package(Qtk 0.2 REQUIRED) -endif() - # Print all QTK variables if (NOT Qtk_IS_TOP_LEVEL) get_cmake_property(VAR_NAMES VARIABLES) @@ -56,6 +54,12 @@ if (NOT Qtk_IS_TOP_LEVEL) endforeach() endif() +# Allow add_subdirectory on this project to use target ALIAS if available. +# If this example project is opened standalone we will use find_package. +if(NOT TARGET Qtk::qtk_library) + find_package(Qtk 0.2 REQUIRED) +endif() + find_package(Qt6 COMPONENTS Core Widgets OpenGLWidgets REQUIRED) set( @@ -65,6 +69,28 @@ set( examplewidget.cpp examplewidget.h ) -add_executable(example_app ${EXAMPLE_SOURCES}) -target_link_libraries(example_app PUBLIC Qt6::Widgets Qt6::OpenGLWidgets Qt6::Core) -target_link_libraries(example_app PUBLIC Qtk::qtk_library) +configure_file( + #[[INPUT]] "${CMAKE_CURRENT_SOURCE_DIR}/resources.h.in" + #[[OUTPUT]] "${CMAKE_CURRENT_BINARY_DIR}/resources.h" + @ONLY +) + +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 Qtk::qtk_library) +target_include_directories(qtk_example PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + +install( + TARGETS qtk_example + COMPONENT qtk_example + BUNDLE DESTINATION . + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib/static + RUNTIME DESTINATION bin +) +qt_generate_deploy_app_script( + TARGET qtk_example + OUTPUT_SCRIPT QTK_EXAMPLE_DEPLOY_SCRIPT + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${QTK_EXAMPLE_DEPLOY_SCRIPT} COMPONENT qtk_example) diff --git a/example-app/README.md b/example-app/README.md index 9dccf95..500ee10 100644 --- a/example-app/README.md +++ b/example-app/README.md @@ -62,11 +62,12 @@ custom installation directory. ```bash cmake -S /path/to/qtk/example-app/ -B /path/to/qtk/example-app/build -DQTK_PATH=/path/to/qtk/install/ -cmake --build /path/to/qtk/example-app/build +cmake --build /path/to/qtk/example-app/build --target qtk_example -- -j $(nproc) +cmake --install build/ --component qtk_example ``` After this, we can run the example application - ```bash -./path/to/qtk/example-app/build/bin/example +./path/to/qtk/example-app/build/install/bin/example ``` diff --git a/example-app/examplescene.cpp b/example-app/examplescene.cpp index 8e14f36..d4d8deb 100644 --- a/example-app/examplescene.cpp +++ b/example-app/examplescene.cpp @@ -7,6 +7,7 @@ ##############################################################################*/ #include "examplescene.h" +#include using namespace Qtk; @@ -22,9 +23,11 @@ void ExampleScene::init() { auto skybox = new Qtk::Skybox("Skybox"); setSkybox(skybox); - auto spartan = new Model( - "spartan", "/home/kapper/Code/qtk/resources/models/spartan/spartan.obj"); + std::string spartanPath = QTK_EXAMPLE_SOURCE_DIR; + spartanPath += "/resources/models/spartan/spartan.obj"; + auto spartan = new Model("spartan", spartanPath.c_str()); addObject(spartan); + spartan->getTransform().setTranslation(-4.0f, 0.0f, 0.0f); auto mesh = addObject( new Qtk::MeshRenderer("rightTriangle", Triangle(QTK_DRAW_ARRAYS))); diff --git a/example-app/resources.h.in b/example-app/resources.h.in new file mode 100644 index 0000000..90b73f1 --- /dev/null +++ b/example-app/resources.h.in @@ -0,0 +1,6 @@ +#ifndef QTK_RESOURCES_H_IN_H +#define QTK_RESOURCES_H_IN_H + +#define QTK_EXAMPLE_SOURCE_DIR "@CMAKE_SOURCE_DIR@" + +#endif // QTK_RESOURCES_H_IN_H diff --git a/extern/assimp/assimp b/extern/assimp/assimp index eb328ce..5d5496f 160000 --- a/extern/assimp/assimp +++ b/extern/assimp/assimp @@ -1 +1 @@ -Subproject commit eb328ce69dd7b06977aed125e967a41e835b8431 +Subproject commit 5d5496f1ad895297cede723b3c96b513263f82ed diff --git a/resources/images/plaster.png b/resources/images/plaster.png new file mode 100644 index 0000000..38c615a Binary files /dev/null and b/resources/images/plaster.png differ diff --git a/resources/resources.qrc b/resources/resources.qrc index 6d0c0de..4aaf9bc 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -1,5 +1,6 @@ + images/plaster.png images/crate.png images/stone.png images/wood.png diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 982c95f..29e4e48 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,14 +13,14 @@ install( FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" - COMPONENT libqtk + COMPONENT qtk_library DESTINATION lib/cmake/${PROJECT_NAME} ) install( EXPORT qtk_export FILE ${PROJECT_NAME}Targets.cmake NAMESPACE ${PROJECT_NAME}:: - COMPONENT libqtk + COMPONENT qtk_library DESTINATION lib/cmake/${PROJECT_NAME} ) # System install for qtk_library @@ -28,53 +28,45 @@ install( TARGETS qtk_library # Associate qtk_library target with qtk-export EXPORT qtk_export - COMPONENT libqtk + COMPONENT qtk_library FILE_SET HEADERS DESTINATION include INCLUDES DESTINATION include LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib/static + ARCHIVE DESTINATION lib RUNTIME DESTINATION bin ) # Qtk Application -if(QTK_BUILD_GUI OR QTK_INSTALL_PLUGINS) +if(QTK_GUI OR QTK_PLUGINS) add_subdirectory(app) endif() -if(QTK_INSTALL_PLUGINS) - # Optionally install custom Qtk plugins for Qt Designer. +if(QTK_PLUGINS) install( - TARGETS qtk_library qtk_plugin_library - COMPONENT collection - LIBRARY DESTINATION "${QTK_PLUGIN_LIBRARY_DIR}" - ARCHIVE DESTINATION "${QTK_PLUGIN_LIBRARY_DIR}" - RUNTIME DESTINATION "${QTK_PLUGIN_LIBRARY_DIR}" - ) - install( - TARGETS qtk_collection - COMPONENT collection + TARGETS qtk_plugins qtk_library qtk_plugin_library + COMPONENT qtk_plugins LIBRARY DESTINATION "${QTK_PLUGIN_INSTALL_DIR}" ARCHIVE DESTINATION "${QTK_PLUGIN_INSTALL_DIR}" RUNTIME DESTINATION "${QTK_PLUGIN_INSTALL_DIR}" ) endif() -if(QTK_BUILD_GUI) +if(QTK_GUI) install( - TARGETS qtk_app - COMPONENT qtk + TARGETS qtk_gui + COMPONENT qtk_gui BUNDLE DESTINATION . LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib/static + ARCHIVE DESTINATION lib RUNTIME DESTINATION bin ) qt_generate_deploy_app_script( - TARGET qtk_app + TARGET qtk_gui OUTPUT_SCRIPT QTK_DEPLOY_SCRIPT NO_UNSUPPORTED_PLATFORM_ERROR ) - install(SCRIPT ${QTK_DEPLOY_SCRIPT} COMPONENT qtk) + install(SCRIPT ${QTK_DEPLOY_SCRIPT} COMPONENT qtk_gui) if(WIN32) if(MSVC AND TARGET Qt6::qmake) @@ -87,7 +79,7 @@ if(QTK_BUILD_GUI) ) file(TO_NATIVE_PATH "${QT6_INSTALL_PREFIX}/bin" QT6_INSTALL_PREFIX) - set(VSUSER_FILE "${CMAKE_CURRENT_BINARY_DIR}/qtk_app.vcxproj.user") + set(VSUSER_FILE "${CMAKE_CURRENT_BINARY_DIR}/qtk_gui.vcxproj.user") file(WRITE ${VSUSER_FILE} "\n") file(APPEND ${VSUSER_FILE} "\n") file(APPEND ${VSUSER_FILE} " \n") @@ -124,7 +116,7 @@ set(CPACK_THREADS 0) set(CPACK_PACKAGE_INSTALL_DIRECTORY "Qtk") # Remove any assimp components if defined by submodule. -if (QTK_UPDATE_SUBMODULES) +if (QTK_SUBMODULES) get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) list(FILTER CPACK_COMPONENTS_ALL EXCLUDE REGEX .*assimp.*) list(REMOVE_ITEM CPACK_COMPONENTS_ALL Unspecified) @@ -136,7 +128,7 @@ set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) # https://nsis.sourceforge.io/Reference/CreateShortCut set( CPACK_NSIS_CREATE_ICONS_EXTRA - "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Qtk.lnk' '$INSTDIR\\\\bin\\\\qtk_app.exe'" + "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Qtk.lnk' '$INSTDIR\\\\bin\\\\qtk_gui.exe'" ) set( CPACK_NSIS_DELETE_ICONS_EXTRA @@ -152,7 +144,7 @@ set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) # OSX set(CPACK_BUNDLE_NAME ${PROJECT_NAME}) -set(CPACK_BUNDLE_PLIST $/Info.plist) +set(CPACK_BUNDLE_PLIST $/Info.plist) set(CPACK_BUNDLE_ICON ${QTK_OSX_ICONS}) # Platform defaults for source bundles. diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 6bbfcb6..5f85378 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -33,32 +33,32 @@ target_sources( target_link_libraries(qtk_plugin_library PUBLIC Qt6::UiPlugin qtk_library) ################################################################################ -# Qtk Widget Collection Plugin +# Qtk Widget Plugins ################################################################################ # Create a Qt Designer plugin for a collection of widgets from our library. -qt_add_plugin(qtk_collection SHARED) +qt_add_plugin(qtk_plugins SHARED) target_sources( - qtk_collection PRIVATE + qtk_plugins PRIVATE widgetplugincollection.cpp widgetplugincollection.h widgetplugin.cpp widgetplugin.h ) -target_link_libraries(qtk_collection PUBLIC qtk_plugin_library) +target_link_libraries(qtk_plugins PUBLIC qtk_plugin_library) ################################################################################ # Final Qtk Application ################################################################################ set( - QTK_APP_SOURCES + QTK_GUI_SOURCES qtkscene.cpp qtkscene.h main.cpp ) -qt_add_executable(qtk_app ${QTK_APP_SOURCES}) -target_link_libraries(qtk_app PRIVATE qtk_plugin_library) +qt_add_executable(qtk_gui ${QTK_GUI_SOURCES}) +target_link_libraries(qtk_gui PRIVATE qtk_plugin_library) set_target_properties( - qtk_app PROPERTIES + qtk_gui PROPERTIES WIN32_EXECUTABLE TRUE MACOSX_BUNDLE TRUE MACOSX_BUNDLE_BUNDLE_NAME Qtk diff --git a/src/app/qtkmainwindow.cpp b/src/app/qtkmainwindow.cpp index eec7a17..8c74bf8 100644 --- a/src/app/qtkmainwindow.cpp +++ b/src/app/qtkmainwindow.cpp @@ -29,17 +29,30 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent) { for(auto & qtkWidget : qtkWidgets) { qtkWidget->setScene(new Qtk::SceneEmpty); views_.emplace(qtkWidget->getScene()->getSceneName(), qtkWidget); + + // Add GUI 'view' toolbar option to show debug console. ui_->menuView->addAction(qtkWidget->getActionToggleConsole()); + // Refresh GUI widgets when scene or objects are updated. connect( qtkWidget->getScene(), &Qtk::Scene::sceneUpdated, this, &MainWindow::refreshScene); + connect( + qtkWidget, &Qtk::QtkWidget::objectFocusChanged, ui_->qtk__ToolBox, + &Qtk::ToolBox::updateFocus); } - auto docks = findChildren(); - for(auto & dock : docks) { - addDockWidget(Qt::RightDockWidgetArea, dock); - ui_->menuView->addAction(dock->toggleViewAction()); - } + // TODO: Fix / use MainWindow in Qt Designer to add these dock widgets. + // For now we will add them manually, but we should be able to do this in the + // designer. At the moment if you edit the UI in designer the dock widget + // areas below will override the designer settings. + + // Dock the toolbox widget to the main window. + addDockWidget(Qt::LeftDockWidgetArea, ui_->qtk__ToolBox); + // Add an option to toggle active widgets in the GUI's toolbar 'view' menu. + ui_->menuView->addAction(ui_->qtk__ToolBox->toggleViewAction()); + + addDockWidget(Qt::RightDockWidgetArea, ui_->qtk__TreeView); + ui_->menuView->addAction(ui_->qtk__TreeView->toggleViewAction()); // Set the window icon used for Qtk. setWindowIcon(Qtk::getIcon()); @@ -74,7 +87,7 @@ Qtk::QtkWidget * MainWindow::getQtkWidget(const QString & name) { return views_[name]; } -void MainWindow::refreshScene(QString sceneName) { - // TODO: Select TreeView using sceneName> +void MainWindow::refreshScene(const QString & sceneName) { + // TODO: Select TreeView using sceneName ui_->qtk__TreeView->updateView(getQtkWidget()->getScene()); } diff --git a/src/app/qtkmainwindow.h b/src/app/qtkmainwindow.h index 844adf0..d36f884 100644 --- a/src/app/qtkmainwindow.h +++ b/src/app/qtkmainwindow.h @@ -69,7 +69,7 @@ class MainWindow : public QMainWindow { * Trigger a refresh for widgets related to a scene that has been updated. * @param sceneName The name of the scene that has been modified. */ - void refreshScene(QString sceneName); + void refreshScene(const QString & sceneName); private: /*************************************************************************** diff --git a/src/app/qtkmainwindow.ui b/src/app/qtkmainwindow.ui index 7fef4f5..324a7d9 100644 --- a/src/app/qtkmainwindow.ui +++ b/src/app/qtkmainwindow.ui @@ -6,7 +6,7 @@ 0 0 - 824 + 1034 601 @@ -28,11 +28,27 @@ + + + + + 1 + 0 + + + + Object details and configuration panel. + + + When an object is double-clicked in the TreeView for a scene, this panel will display relevant details and options. + + + - 0 + 3 0 @@ -50,10 +66,10 @@ - A custom widget tool tip. + - Custom widget what's this? + Qtk scene view rendered using OpenGL. @@ -67,28 +83,20 @@ - - - - - A custom widget tool tip. - - - Custom widget what's this? - - - - - - - A custom widget tool tip. - - - Custom widget what's this? - - - - + + + + 1 + 0 + + + + TreeView of objects within the current scene. + + + TreeView of objects within the current scene. Double-click to select an object and snap to it's position. + + @@ -97,7 +105,7 @@ 0 0 - 824 + 1034 22 @@ -179,7 +187,7 @@ - + :/icons/fontawesome-free-6.2.1-desktop/svgs/regular/folder-open.svg:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/folder-open.svg @@ -188,7 +196,7 @@ - + :/icons/fontawesome-free-6.2.1-desktop/svgs/regular/floppy-disk.svg:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/floppy-disk.svg @@ -215,7 +223,7 @@ - + :/icons/fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg:/icons/fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg @@ -227,7 +235,7 @@ - + :/icons/fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg:/icons/fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg @@ -318,9 +326,7 @@ 1 - - - + actionExit diff --git a/src/app/qtkscene.cpp b/src/app/qtkscene.cpp index ba7abb0..4216d9c 100644 --- a/src/app/qtkscene.cpp +++ b/src/app/qtkscene.cpp @@ -38,11 +38,12 @@ void QtkScene::init() { /* Create a red cube with a mini master chief on top. */ auto myCube = new MeshRenderer("My cube", Cube(Qtk::QTK_DRAW_ELEMENTS)); myCube->setColor(RED); + myCube->getTransform().setTranslation(5.0f, 0.0f, 0.0f); addObject(myCube); auto mySpartan = new Model("My spartan", ":/models/models/spartan/spartan.obj"); - mySpartan->getTransform().setTranslation(0.0f, 0.5f, 0.0f); + mySpartan->getTransform().setTranslation(5.0f, 0.5f, 0.0f); mySpartan->getTransform().setScale(0.5f); addObject(mySpartan); diff --git a/src/app/qtkwidget.cpp b/src/app/qtkwidget.cpp index 48a19a3..6c2b91b 100644 --- a/src/app/qtkwidget.cpp +++ b/src/app/qtkwidget.cpp @@ -7,6 +7,12 @@ ##############################################################################*/ #include +#include +#include + +#include +#include +#include #include #include @@ -31,6 +37,7 @@ QtkWidget::QtkWidget(QWidget * parent, const QString & name) : QtkWidget::QtkWidget(QWidget * parent, const QString & name, Scene * scene) : QOpenGLWidget(parent), mDebugLogger(Q_NULLPTR), mConsole(new DebugConsole(this, name)), mScene(Q_NULLPTR) { + setAcceptDrops(true); setScene(scene); setObjectName(name); QSurfaceFormat format; @@ -70,6 +77,7 @@ void QtkWidget::initializeGL() { // Connect the frameSwapped signal to call the update() function connect(this, SIGNAL(frameSwapped()), this, SLOT(update())); + toggleConsole(); // Initialize OpenGL debug context mDebugLogger = new QOpenGLDebugLogger(this); if(mDebugLogger->initialize()) { @@ -107,11 +115,11 @@ void QtkWidget::paintGL() { } } -void QtkWidget::setScene(Qtk::Scene * scene) { +void QtkWidget::setScene(Scene * scene) { if(mScene != Q_NULLPTR) { delete mScene; connect( - scene, &Qtk::Scene::sceneUpdated, MainWindow::getMainWindow(), + scene, &Scene::sceneUpdated, MainWindow::getMainWindow(), &MainWindow::refreshScene); } @@ -129,8 +137,7 @@ void QtkWidget::toggleConsole() { mConsoleActive = false; } else { MainWindow::getMainWindow()->addDockWidget( - Qt::DockWidgetArea::BottomDockWidgetArea, - dynamic_cast(mConsole)); + Qt::DockWidgetArea::BottomDockWidgetArea, mConsole); mConsole->setHidden(false); mConsoleActive = true; } @@ -140,6 +147,34 @@ void QtkWidget::toggleConsole() { * Protected Methods ******************************************************************************/ +void QtkWidget::dragEnterEvent(QDragEnterEvent * event) { + if(event->mimeData()->hasFormat("text/plain")) { + event->acceptProposedAction(); + } +} + +void QtkWidget::dropEvent(QDropEvent * event) { + mConsole->sendLog(event->mimeData()->text()); + auto urls = event->mimeData()->urls(); + if(!urls.isEmpty()) { + if(urls.size() > 1) { + qDebug() << "Cannot accept drop of multiple files."; + event->ignore(); + return; + } + + // TODO: Support other object types. + auto url = urls.front(); + if(url.fileName().endsWith(".obj")) { + mScene->loadModel(url); + event->acceptProposedAction(); + } else { + qDebug() << "Unsupported file type: " + url.fileName() + "\n"; + event->ignore(); + } + } +} + void QtkWidget::keyPressEvent(QKeyEvent * event) { if(event->isAutoRepeat()) { // Do not repeat input while a key is held down @@ -253,7 +288,8 @@ void QtkWidget::teardownGL() { /* Nothing to teardown yet... */ void QtkWidget::updateCameraInput() { Input::update(); // Camera Transformation - if(Input::buttonPressed(Qt::RightButton)) { + if(Input::buttonPressed(Qt::LeftButton) + || Input::buttonPressed(Qt::RightButton)) { static const float transSpeed = 0.1f; static const float rotSpeed = 0.5f; diff --git a/src/app/qtkwidget.h b/src/app/qtkwidget.h index 81f9114..a028700 100644 --- a/src/app/qtkwidget.h +++ b/src/app/qtkwidget.h @@ -131,11 +131,19 @@ namespace Qtk { */ void sendLog(const QString & message, DebugContext context = Status); + // TODO: Use this signal in treeview and toolbox to update object + // properties + void objectFocusChanged(const QString objectName); + protected: /************************************************************************* * Protected Methods ************************************************************************/ + void dragEnterEvent(QDragEnterEvent * event) override; + + void dropEvent(QDropEvent * event) override; + /** * @param event Key press event to update camera input manager. */ @@ -166,6 +174,7 @@ namespace Qtk { /** * Called when the `messageLogged` signal is caught. * See definition of initializeGL() + * https://doc.qt.io/qt-6/qopengldebuglogger.html#signals * * @param msg The message logged. */ diff --git a/src/app/toolbox.cpp b/src/app/toolbox.cpp index 72f9275..046932d 100644 --- a/src/app/toolbox.cpp +++ b/src/app/toolbox.cpp @@ -8,13 +8,142 @@ */ #include "toolbox.h" +#include "qtkmainwindow.h" #include "ui_toolbox.h" -Qtk::ToolBox::ToolBox(QWidget * parent) : - QDockWidget(parent), ui(new Ui::ToolBox) { +#include +#include + +using namespace Qtk; + +ToolBox::ToolBox(QWidget * parent) : QDockWidget(parent), ui(new Ui::ToolBox) { ui->setupUi(this); + setMinimumWidth(350); } -Qtk::ToolBox::~ToolBox() { +void ToolBox::updateFocus(const QString & name) { + auto object = + MainWindow::getMainWindow()->getQtkWidget()->getScene()->getObject(name); + if(object != Q_NULLPTR) { + removePages(); + createPageProperties(object); + createPageShader(object); + } +} + +ToolBox::~ToolBox() { delete ui; } + +void ToolBox::removePages() { + // Remove all existing pages. + for(size_t i = 0; i < ui->toolBox->count(); i++) { + delete ui->toolBox->widget(i); + ui->toolBox->removeItem(i); + } +} + +void ToolBox::createPageProperties(const Object * object) { + auto transform = object->getTransform(); + auto type = object->getType(); + auto * widget = new QWidget; + ui->toolBox->addItem(widget, "Properties"); + ui->toolBox->setCurrentWidget(widget); + + auto * layout = new QFormLayout; + layout->addRow( + new QLabel(tr("Name:")), new QLabel(object->getName().c_str())); + + layout->addRow( + new QLabel(tr("Type:")), + new QLabel(type == Object::Type::QTK_MESH ? "Mesh" : "Model")); + + auto rowLayout = new QHBoxLayout; + rowLayout->addWidget(new QLabel(tr("Translation:"))); + int minWidth = 75; + for(size_t i = 0; i < 3; i++) { + auto spinBox = new QDoubleSpinBox; + spinBox->setMinimum(std::numeric_limits::lowest()); + spinBox->setSingleStep(0.1); + spinBox->setValue(transform.getTranslation()[i]); + spinBox->setFixedWidth(minWidth); + rowLayout->addWidget(spinBox); + + if(i == 0) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, + &Object::setTranslationX); + } else if(i == 1) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, + &Object::setTranslationY); + } else if(i == 2) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, + &Object::setTranslationZ); + } + } + layout->addRow(rowLayout); + + rowLayout = new QHBoxLayout; + rowLayout->addWidget(new QLabel(tr("Scale:"))); + for(size_t i = 0; i < 3; i++) { + auto spinBox = new QDoubleSpinBox; + spinBox->setMinimum(std::numeric_limits::lowest()); + spinBox->setSingleStep(0.1); + spinBox->setValue(transform.getScale()[i]); + spinBox->setFixedWidth(minWidth); + rowLayout->addWidget(spinBox); + + if(i == 0) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, &Object::setScaleX); + } else if(i == 1) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, &Object::setScaleY); + } else if(i == 2) { + connect( + spinBox, &QDoubleSpinBox::valueChanged, object, &Object::setScaleZ); + } + } + layout->addRow(rowLayout); + widget->setLayout(layout); +} + +void ToolBox::createPageShader(const Object * object) { + // Shaders page. + auto widget = new QWidget; + ui->toolBox->addItem(widget, "Shaders"); + auto mainLayout = new QFormLayout; + auto rowLayout = new QHBoxLayout; + rowLayout->addWidget(new QLabel("Vertex Shader:")); + rowLayout->addWidget(new QLabel(object->getVertexShader().c_str())); + mainLayout->addRow(rowLayout); + + auto shaderView = new QTextEdit; + shaderView->setReadOnly(true); + auto vertexFile = QFile(object->getVertexShader().c_str()); + if(vertexFile.exists()) { + vertexFile.open(QIODeviceBase::ReadOnly); + shaderView->setText(vertexFile.readAll()); + vertexFile.close(); + mainLayout->addRow(shaderView); + } + + rowLayout = new QHBoxLayout; + rowLayout->addWidget(new QLabel("Fragment Shader:")); + rowLayout->addWidget(new QLabel(object->getFragmentShader().c_str())); + mainLayout->addRow(rowLayout); + + shaderView = new QTextEdit; + shaderView->setReadOnly(true); + 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); +} diff --git a/src/app/toolbox.h b/src/app/toolbox.h index 601d4d5..7c4ac2b 100644 --- a/src/app/toolbox.h +++ b/src/app/toolbox.h @@ -12,6 +12,11 @@ #include #include +#include +#include + + +#include "qtk/scene.h" namespace Ui { class ToolBox; @@ -30,6 +35,15 @@ namespace Qtk { ~ToolBox(); + void removePages(); + + void createPageProperties(const Object * object); + + void createPageShader(const Object * object); + + void updateFocus(const QString & name); + + private: /************************************************************************* * Private Members diff --git a/src/app/toolbox.ui b/src/app/toolbox.ui index 0de2672..78e9555 100644 --- a/src/app/toolbox.ui +++ b/src/app/toolbox.ui @@ -10,17 +10,57 @@ 300 + + + 0 + 0 + + + + + 86 + 167 + + Object Details - + + + 0 + 0 + + + 0 - + + + true + + + + 0 + 0 + 382 + 201 + + + + + 0 + 0 + + + + Properties + + + 0 @@ -33,19 +73,6 @@ Shaders - - - - 0 - 0 - 382 - 201 - - - - Properties - - diff --git a/src/app/treeview.cpp b/src/app/treeview.cpp index 98f3d8d..0239b90 100644 --- a/src/app/treeview.cpp +++ b/src/app/treeview.cpp @@ -48,16 +48,25 @@ void Qtk::TreeView::itemFocus(QTreeWidgetItem * item, int column) { auto scene = MainWindow::getMainWindow()->getQtkWidget()->getScene(); auto & transform = scene->getCamera().getTransform(); auto object = scene->getObject(name); + Transform3D * objectTransform; + // If the object is a mesh or model, focus the camera on it. if(object == Q_NULLPTR) { qDebug() << "Attempt to get non-existing object with name '" << name << "'\n"; - } - Transform3D * objectTransform; - if(object->getType() == Object::QTK_MESH) { + } else if(object->getType() == Object::QTK_MESH) { objectTransform = &dynamic_cast(object)->getTransform(); } else if(object->getType() == Object::QTK_MODEL) { objectTransform = &dynamic_cast(object)->getTransform(); } - transform.setTranslation(objectTransform->getTranslation()); + auto focusScale = objectTransform->getScale(); + float width = focusScale.x() / 2.0f; + float height = focusScale.y() / 2.0f; + QVector3D pos = objectTransform->getTranslation(); + // pos.setX(pos.x() + width); + pos.setY(pos.y() + height); + transform.setTranslation(pos); transform.translate(0.0f, 0.0f, 3.0f); + + // Emit signal from qtk widget for new object focus. Triggers GUI updates. + emit MainWindow::getMainWindow()->getQtkWidget()->objectFocusChanged(name); } diff --git a/src/qtk/CMakeLists.txt b/src/qtk/CMakeLists.txt index 42b9dcc..4eca544 100644 --- a/src/qtk/CMakeLists.txt +++ b/src/qtk/CMakeLists.txt @@ -68,9 +68,9 @@ target_link_libraries( Qt6::Core Qt6::OpenGLWidgets Qt6::Widgets ) -if(QTK_UPDATE_SUBMODULES OR NOT ASSIMP_NEW_INTERFACE) +if(QTK_SUBMODULES OR NOT QTK_ASSIMP_NEW_INTERFACE) target_link_libraries(qtk_library PUBLIC assimp) -elseif(ASSIMP_NEW_INTERFACE) +elseif(QTK_ASSIMP_NEW_INTERFACE) target_link_libraries(qtk_library PUBLIC assimp::assimp) endif() diff --git a/src/qtk/meshrenderer.h b/src/qtk/meshrenderer.h index 28a8fbb..cf5ddea 100644 --- a/src/qtk/meshrenderer.h +++ b/src/qtk/meshrenderer.h @@ -211,6 +211,14 @@ namespace Qtk { */ inline Transform3D & getTransform() { return mTransform; } + inline std::string getVertexShader() const override { + return mVertexShader; + } + + inline std::string getFragmentShader() const override { + return mFragmentShader; + } + private: /************************************************************************* * Private Members diff --git a/src/qtk/model.cpp b/src/qtk/model.cpp index f84140e..2483783 100644 --- a/src/qtk/model.cpp +++ b/src/qtk/model.cpp @@ -43,7 +43,7 @@ void Model::flipTexture(const std::string & fileName, bool flipX, bool flipY) { texture.mTexture->destroy(); texture.mTexture->create(); texture.mTexture->setData( - *OpenGLTextureFactory::initImage(fullPath.c_str(), flipX, flipY)); + OpenGLTextureFactory::initImage(fullPath.c_str(), flipX, flipY)); modified = true; } } @@ -238,7 +238,7 @@ ModelMesh::Textures Model::loadMaterialTextures( // Add the texture to the textures container textures.push_back(texture); // Add the texture to the loaded textures to avoid loading it twice - mTexturesLoaded.push_back(texture); + mTexturesLoaded.push_back(textures.back()); } } diff --git a/src/qtk/model.h b/src/qtk/model.h index 66f18c2..f81575d 100644 --- a/src/qtk/model.h +++ b/src/qtk/model.h @@ -18,6 +18,9 @@ #include // Qtk +#include + + #include "modelmesh.h" #include "qtkapi.h" @@ -124,6 +127,14 @@ namespace Qtk { */ inline Transform3D & getTransform() { return mTransform; } + inline std::string getVertexShader() const override { + return mVertexShader; + } + + inline std::string getFragmentShader() const override { + return mFragmentShader; + } + private: /************************************************************************* * Private Methods diff --git a/src/qtk/modelmesh.cpp b/src/qtk/modelmesh.cpp index 79b2e3e..26e7a50 100644 --- a/src/qtk/modelmesh.cpp +++ b/src/qtk/modelmesh.cpp @@ -52,6 +52,10 @@ void ModelMesh::draw(QOpenGLShaderProgram & shader) { shader.setUniformValue((name + number).c_str(), i); } + // Always reset active texture to GL_TEXTURE0 before we draw. + // This is important for models with no textures. + glActiveTexture(GL_TEXTURE0); + // Draw the mesh glDrawElements( GL_TRIANGLES, mIndices.size(), GL_UNSIGNED_INT, mIndices.data()); @@ -62,7 +66,6 @@ void ModelMesh::draw(QOpenGLShaderProgram & shader) { } shader.release(); mVAO->release(); - glActiveTexture(GL_TEXTURE0); } /******************************************************************************* diff --git a/src/qtk/modelmesh.h b/src/qtk/modelmesh.h index cefdf88..3860dcf 100644 --- a/src/qtk/modelmesh.h +++ b/src/qtk/modelmesh.h @@ -30,6 +30,21 @@ namespace Qtk { * Struct to store model textures. 3D Models may have multiple. */ struct QTKAPI ModelTexture { + ModelTexture() = default; + + /** + * Construct a ModelTexture. + * + * @param id Texture ID for this texture. + * @param type Type of texture in string format. + * @param path Path to the texture on disk. + */ + ModelTexture(const std::string & type, const std::string & path) : + mType(type), mPath(path) { + mTexture = OpenGLTextureFactory::initTexture(path.c_str()); + mID = mTexture->textureId(); + } + /** Texture ID for for this texture. */ GLuint mID {}; QOpenGLTexture * mTexture {}; diff --git a/src/qtk/object.h b/src/qtk/object.h index 6092608..bbf8382 100644 --- a/src/qtk/object.h +++ b/src/qtk/object.h @@ -96,10 +96,24 @@ namespace Qtk { [[nodiscard]] inline const Type & getType() const { return mType; } + [[nodiscard]] inline virtual const Transform3D & getTransform() const { + return mTransform; + } + + [[nodiscard]] inline virtual std::string getVertexShader() const { + return "Base Object has no vertex shader."; + } + + virtual inline std::string getFragmentShader() const { + return "Base Object has no fragment shader."; + } + /************************************************************************* * Setters ************************************************************************/ + virtual inline void setName(const std::string & name) { mName = name; } + virtual inline void setColors(const Colors & value) { mShape.mColors = value; } @@ -135,6 +149,39 @@ namespace Qtk { mShape.mVertices = value; } + inline void setScaleX(double x) { + mTransform.setScale( + x, mTransform.getScale().y(), mTransform.getScale().z()); + } + + inline void setScaleY(double y) { + mTransform.setScale( + mTransform.getScale().x(), y, mTransform.getScale().z()); + } + + inline void setScaleZ(double z) { + mTransform.setScale( + mTransform.getScale().x(), mTransform.getScale().y(), z); + } + + inline void setTranslationX(double x) { + mTransform.setTranslation( + x, mTransform.getTranslation().y(), + mTransform.getTranslation().z()); + } + + inline void setTranslationY(double y) { + mTransform.setTranslation( + mTransform.getTranslation().x(), y, + mTransform.getTranslation().z()); + } + + inline void setTranslationZ(double z) { + mTransform.setTranslation( + mTransform.getTranslation().x(), mTransform.getTranslation().y(), + z); + } + /************************************************************************* * Public Methods ************************************************************************/ diff --git a/src/qtk/scene.cpp b/src/qtk/scene.cpp index 8585c21..b107c94 100644 --- a/src/qtk/scene.cpp +++ b/src/qtk/scene.cpp @@ -37,16 +37,42 @@ Scene::~Scene() { * Public Methods ******************************************************************************/ +template <> MeshRenderer * Scene::addObject(MeshRenderer * object) { + initSceneObjectName(object); + mMeshes.push_back(object); + sceneUpdated(mSceneName); + return object; +} + +template <> Model * Scene::addObject(Model * object) { + initSceneObjectName(object); + mModels.push_back(object); + sceneUpdated(mSceneName); + return object; +} + void Scene::draw() { if(!mInit) { initializeOpenGLFunctions(); init(); mInit = true; } + + while(!mModelLoadQueue.empty()) { + auto modelSpec = mModelLoadQueue.front(); + // Load the model and add it to the scene. + addObject(new Model(modelSpec.first.c_str(), modelSpec.second.c_str())); + mModelLoadQueue.pop(); + } + + if(mPause) { + return; + } + if(mSkybox != Q_NULLPTR) { mSkybox->draw(); } - for(auto & model : mModels) { + for(const auto & model : mModels) { model->draw(); } for(const auto & mesh : mMeshes) { @@ -57,8 +83,8 @@ void Scene::draw() { std::vector Scene::getObjects() const { // All scene objects must inherit from Qtk::Object. std::vector objects(mMeshes.begin(), mMeshes.end()); - for(auto model : mModels) { - objects.push_back(dynamic_cast(model)); + for(const auto & model : mModels) { + objects.push_back(model); if(objects.back() == nullptr) { return {}; } @@ -66,8 +92,8 @@ std::vector Scene::getObjects() const { return objects; } -Object * Scene::getObject(const QString & name) { - for(auto object : getObjects()) { +Object * Scene::getObject(const QString & name) const { + for(const auto & object : getObjects()) { if(object->getName() == name.toStdString()) { return object; } @@ -80,14 +106,14 @@ void Scene::setSkybox(Skybox * skybox) { mSkybox = skybox; } -template <> MeshRenderer * Scene::addObject(MeshRenderer * object) { - mMeshes.push_back(object); - sceneUpdated(mSceneName); - return object; -} - -template <> Model * Scene::addObject(Model * object) { - mModels.push_back(object); - sceneUpdated(mSceneName); - return object; +void Scene::initSceneObjectName(Object * object) { + if(!mObjectCount.count(object->getName())) { + mObjectCount[object->getName()] = 1; + } else { + mObjectCount[object->getName()]++; + } + auto count = mObjectCount[object->getName()]; + if(count > 1) { + object->setName(object->getName() + " (" + std::to_string(count) + ")"); + } } diff --git a/src/qtk/scene.h b/src/qtk/scene.h index 4431478..6abd403 100644 --- a/src/qtk/scene.h +++ b/src/qtk/scene.h @@ -10,7 +10,10 @@ #define QTK_SCENE_H #include +#include +#include +#include #include #include "camera3d.h" @@ -75,6 +78,18 @@ namespace Qtk { */ virtual void update() {} + void loadModel(const QUrl & url) { + auto fileName = url.fileName().replace(".obj", "").toStdString(); + auto filePath = url.toLocalFile().toStdString(); + loadModel(fileName, filePath); + } + + void loadModel(const std::string & name, const std::string & path) { + // Add the dropped model to the load queue. + // This is consumed during rendering of the scene if not empty. + mModelLoadQueue.emplace(name, path); + } + /************************************************************************* * Accessors ************************************************************************/ @@ -91,7 +106,16 @@ namespace Qtk { * @param name The objectName to look for within this scene. * @return The found object or Q_NULLPTR if none found. */ - [[nodiscard]] Object * getObject(const QString & name); + [[nodiscard]] Object * getObject(const QString & name) const; + + /** + * @return The number of objects within the scene with the given name. + */ + [[nodiscard]] uint64_t getObjectCount(const QString & name) { + return mObjectCount.count(name.toStdString()) + ? mObjectCount[name.toStdString()] + : 0; + } /** * @return Camera attached to this scene. @@ -166,6 +190,8 @@ namespace Qtk { */ inline void setSceneName(QString name) { mSceneName = std::move(name); } + inline void setPause(bool pause) { mPause = pause; } + signals: /** * Signal thrown when the scene is modified by adding or removing objects. @@ -175,7 +201,26 @@ namespace Qtk { */ void sceneUpdated(QString sceneName); + + /************************************************************************* + * Public Members + ************************************************************************/ + public: + /* Models used for storing 3D models in the scene. */ + std::vector mModels {}; + + /* Queue of models requested to load at runtime. */ + std::queue> mModelLoadQueue; + private: + /** + * Initialize an object name relative to other objects already loaded. + * Protects against having two objects with the same name. + * + * @param object Qtk Object to name within this scene. + */ + void initSceneObjectName(Qtk::Object * object); + /************************************************************************* * Private Members ************************************************************************/ @@ -183,14 +228,16 @@ namespace Qtk { static Camera3D mCamera; static QMatrix4x4 mProjection; bool mInit = false; + /* Pause rendering of the scene. */ + bool mPause = false; QString mSceneName; /* The skybox for this scene. */ Skybox * mSkybox {}; /* MeshRenderers used simple geometry. */ std::vector mMeshes {}; - /* Models used for storing 3D models in the scene. */ - std::vector mModels {}; + /* Track count of objects with same initial name. */ + std::unordered_map mObjectCount; }; class SceneEmpty : public Scene { diff --git a/src/qtk/texture.cpp b/src/qtk/texture.cpp index 87191a5..17f496b 100644 --- a/src/qtk/texture.cpp +++ b/src/qtk/texture.cpp @@ -9,19 +9,18 @@ #include #include +#include "app/qtkmainwindow.h" #include "texture.h" using namespace Qtk; -QImage * OpenGLTextureFactory::initImage( +QImage OpenGLTextureFactory::initImage( const char * image, bool flipX, bool flipY) { // Qt6 limits loaded images to 256MB by default - QImageReader::setAllocationLimit(512); - auto loadedImage = new QImage(QImage(image).mirrored(flipX, flipY)); - if(loadedImage->isNull()) { - qDebug() << "[Qtk::OpenGLTextureFactory] Error loading image: " << image - << "\nSupported types: " << QImageReader::supportedImageFormats(); - return Q_NULLPTR; + QImageReader::setAllocationLimit(1024); + auto loadedImage = QImage(image).mirrored(flipX, flipY); + if(loadedImage.isNull()) { + return defaultTexture(); } return loadedImage; @@ -29,13 +28,12 @@ QImage * OpenGLTextureFactory::initImage( QOpenGLTexture * OpenGLTextureFactory::initTexture( const char * texture, bool flipX, bool flipY) { - QImage * image = initImage(texture, flipX, flipY); + QImage image = initImage(texture, flipX, flipY); auto newTexture = new QOpenGLTexture(QOpenGLTexture::Target2D); - newTexture->setData(*image); + newTexture->setData(image); newTexture->setWrapMode(QOpenGLTexture::Repeat); newTexture->setMinMagFilters( QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear); - delete image; return newTexture; } @@ -71,6 +69,7 @@ QOpenGLTexture * OpenGLTextureFactory::initCubeMap( QImage faceImage(faceTextures[i]); if(faceImage.isNull()) { qDebug() << "Error loading cube map image\n"; + faceImage = defaultTexture(); } faceImage = faceImage.convertToFormat(QImage::Format_RGBA8888); diff --git a/src/qtk/texture.h b/src/qtk/texture.h index 19ea18b..9739923 100644 --- a/src/qtk/texture.h +++ b/src/qtk/texture.h @@ -74,9 +74,9 @@ namespace Qtk { * Can be absolute or Qt resource path. * @param flipX If true the image will be flipped on X axis. * @param flipY If true the image will be flipped on Y axis. - * @return Pointer to an initialized QImage object. + * @return QImage object. */ - static QImage * initImage( + static QImage initImage( const char * image, bool flipX = false, bool flipY = false); /** @@ -132,6 +132,14 @@ namespace Qtk { const char * right, const char * top, const char * front, const char * left, const char * bottom, const char * back); + /// The texture used in place of a missing texture. + static QImage defaultTexture() { + // Use plaster for default texture if image fails to load. + // This prevents segfaults when loading a texture that doesn't exist. + // TODO: Replace with a '?' texture to indicate missing texture. + return QImage(":/textures/plaster.png"); + } + private: // Private ctor to prevent creating instances of this class OpenGLTextureFactory() = default;