From 41db5315e04a435ba4e64b1bef9b201f9c3209ba Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Wed, 27 Dec 2023 13:47:24 -0500 Subject: [PATCH] WIP --- CMakeLists.txt | 13 +++++++++---- README.md | 16 ++++++++-------- example-app/examplescene.cpp | 2 +- example-app/resources.h.in | 2 +- resources/resources.qrc | 1 + src/app/qtkmainwindow.cpp | 7 ++++++- src/app/qtkmainwindow.h | 9 ++++++++- src/app/qtkmainwindow.ui | 12 ++++++------ src/app/qtkwidget.cpp | 7 ++++--- src/qtk/model.cpp | 7 +++++-- src/qtk/modelmesh.cpp | 5 ++++- src/qtk/modelmesh.h | 15 +++++++++++++++ src/qtk/scene.h | 2 +- src/qtk/texture.cpp | 19 +++++++++---------- src/qtk/texture.h | 12 ++++++++++-- 15 files changed, 88 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bf74692..08aa479 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,12 +84,13 @@ endif () set(QTK_RESOURCES "${CMAKE_SOURCE_DIR}/resources") set(QTK_OSX_ICONS ${CMAKE_SOURCE_DIR}/resources/icons/osx/kilroy.icns) -################################################################################ -# External Dependencies -################################################################################ # 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}") +if (QTK_PREFIX_QTCREATOR) + # TODO: This might be a bit strange and needs more testing. + set(CMAKE_INSTALL_PREFIX "${QT_INSTALL_DIR}") +endif() set( QT_CREATOR_DIR @@ -97,8 +98,9 @@ set( CACHE PATH "Qt Creator path used to install Qtk plugins for Qt Designer." ) - # 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 "^[qQ][tT][kK]_.*$") list(SORT VAR_NAMES) @@ -143,6 +145,7 @@ set( 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. @@ -184,6 +187,8 @@ if(QTK_EXAMPLE) 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 diff --git a/README.md b/README.md index 9ebf9a7..623fbf3 100644 --- a/README.md +++ b/README.md @@ -40,16 +40,16 @@ 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 `-DASSIMP_NEW_INTERFACE` build flag. +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. @@ -59,7 +59,7 @@ sudo apt update -y && sudo apt install libassimp-dev cmake build-essential git c 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.5.0/gcc_64 +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/ ```` @@ -75,7 +75,7 @@ Windows / Mac / Linux) and may be easier to configure. ```bash -cmake -B build-all -DQTK_GUI=ON -DQTK_PLUGINS=ON -DQTK_EXAMPLE=ON -DQTK_SUBMODULES=ON -DCMAKE_PREFIX_PATH=$HOME/Qt/6.5.0/gcc_64 +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 @@ -149,9 +149,9 @@ cmake --build build-all/ --target qtk_plugins -- -j $(nproc) # 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.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 +-- 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 diff --git a/example-app/examplescene.cpp b/example-app/examplescene.cpp index 02f9463..d4d8deb 100644 --- a/example-app/examplescene.cpp +++ b/example-app/examplescene.cpp @@ -24,7 +24,7 @@ void ExampleScene::init() { setSkybox(skybox); std::string spartanPath = QTK_EXAMPLE_SOURCE_DIR; - spartanPath += "/../resources/models/spartan/spartan.obj"; + 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); diff --git a/example-app/resources.h.in b/example-app/resources.h.in index 14e4c24..90b73f1 100644 --- a/example-app/resources.h.in +++ b/example-app/resources.h.in @@ -1,6 +1,6 @@ #ifndef QTK_RESOURCES_H_IN_H #define QTK_RESOURCES_H_IN_H -#define QTK_EXAMPLE_SOURCE_DIR "@CMAKE_CURRENT_SOURCE_DIR@" +#define QTK_EXAMPLE_SOURCE_DIR "@CMAKE_SOURCE_DIR@" #endif // QTK_RESOURCES_H_IN_H 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/app/qtkmainwindow.cpp b/src/app/qtkmainwindow.cpp index 34998db..8c74bf8 100644 --- a/src/app/qtkmainwindow.cpp +++ b/src/app/qtkmainwindow.cpp @@ -41,6 +41,11 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent) { &Qtk::ToolBox::updateFocus); } + // 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. @@ -82,7 +87,7 @@ Qtk::QtkWidget * MainWindow::getQtkWidget(const QString & name) { return views_[name]; } -void MainWindow::refreshScene(QString 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..e2793d3 100644 --- a/src/app/qtkmainwindow.h +++ b/src/app/qtkmainwindow.h @@ -53,6 +53,13 @@ class MainWindow : public QMainWindow { */ static MainWindow * getMainWindow(); + static std::pair parseError( + const QOpenGLDebugMessage & msg); + + static void log(const QString & message); + + static void log(const QOpenGLDebugMessage & msg); + Qtk::QtkWidget * getQtkWidget(int64_t index = 0); /** @@ -69,7 +76,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 d04f507..324a7d9 100644 --- a/src/app/qtkmainwindow.ui +++ b/src/app/qtkmainwindow.ui @@ -37,10 +37,10 @@ - A custom widget tool tip. + Object details and configuration panel. - Custom widget what's this? + When an object is double-clicked in the TreeView for a scene, this panel will display relevant details and options. @@ -66,10 +66,10 @@ - A custom widget tool tip. + - Custom widget what's this? + Qtk scene view rendered using OpenGL. @@ -91,10 +91,10 @@ - A custom widget tool tip. + TreeView of objects within the current scene. - Custom widget what's this? + TreeView of objects within the current scene. Double-click to select an object and snap to it's position. diff --git a/src/app/qtkwidget.cpp b/src/app/qtkwidget.cpp index a82b679..6c2b91b 100644 --- a/src/app/qtkwidget.cpp +++ b/src/app/qtkwidget.cpp @@ -77,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()) { @@ -154,8 +155,8 @@ void QtkWidget::dragEnterEvent(QDragEnterEvent * event) { void QtkWidget::dropEvent(QDropEvent * event) { mConsole->sendLog(event->mimeData()->text()); - if(event->mimeData()->hasUrls()) { - auto urls = event->mimeData()->urls(); + auto urls = event->mimeData()->urls(); + if(!urls.isEmpty()) { if(urls.size() > 1) { qDebug() << "Cannot accept drop of multiple files."; event->ignore(); @@ -168,7 +169,7 @@ void QtkWidget::dropEvent(QDropEvent * event) { mScene->loadModel(url); event->acceptProposedAction(); } else { - qDebug() << "Unsupported file type."; + qDebug() << "Unsupported file type: " + url.fileName() + "\n"; event->ignore(); } } diff --git a/src/qtk/model.cpp b/src/qtk/model.cpp index f84140e..5dfbe70 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; } } @@ -94,6 +94,9 @@ void Model::loadModel(const std::string & path) { // Optimizes drawing so that overlapping objects are not overwritten // + Since the topmost object will be drawn first sortModelMeshes(); + if(mTexturesLoaded.empty()) { + mTexturesLoaded.emplace_back("basic", ":/textures/plaster2.png"); + } // Object finished loading, insert it into ModelManager mManager.insert(getName().c_str(), this); @@ -238,7 +241,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/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/scene.h b/src/qtk/scene.h index 5049c3b..6abd403 100644 --- a/src/qtk/scene.h +++ b/src/qtk/scene.h @@ -87,7 +87,7 @@ namespace Qtk { 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.push({name, path}); + mModelLoadQueue.emplace(name, path); } /************************************************************************* 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;