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 e3a573a..362eb7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,31 +101,12 @@ 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. get_cmake_property(VAR_NAMES VARIABLES) list(FILTER VAR_NAMES INCLUDE REGEX "^Q[tT][kK]_.*$") list(SORT VAR_NAMES) -foreach(VAR_NAME ${VAR_NAMES}) - message(STATUS "[Qtk] ${VAR_NAME}=${${VAR_NAME}}") -endforeach() ################################################################################ # External Dependencies 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 afdb7ac..7c7b8a4 100644 --- a/src/app/qtkwidget.cpp +++ b/src/app/qtkwidget.cpp @@ -7,8 +7,8 @@ ##############################################################################*/ #include -#include #include +#include #include #include @@ -34,8 +34,22 @@ QtkWidget::QtkWidget(QWidget * parent) : QtkWidget(parent, "QtkWidget") {} QtkWidget::QtkWidget(QWidget * parent, const QString & name) : QtkWidget(parent, name, Q_NULLPTR) {} -QtkWidget::QtkWidget(const QSurfaceFormat & format) : - mScene(Q_NULLPTR), mDebugLogger(Q_NULLPTR) { +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; + format.setRenderableType(QSurfaceFormat::OpenGL); + format.setProfile(QSurfaceFormat::CoreProfile); + format.setVersion(4, 6); + // Set the number of samples used for glEnable(GL_MULTISAMPLING) + format.setSamples(4); + // Set the size of the depth bufer for glEnable(GL_DEPTH_TEST) + format.setDepthBufferSize(16); + // If QTK_DEBUG is set, enable debug context + format.setOption(QSurfaceFormat::DebugContext); setFormat(format); setFocusPolicy(Qt::ClickFocus); } @@ -70,9 +84,9 @@ void QtkWidget::initializeGL() { connect( mDebugLogger, SIGNAL(messageLogged(QOpenGLDebugMessage)), this, SLOT(messageLogged(QOpenGLDebugMessage))); -// connect( -// mDebugLogger, SIGNAL(messageLogged(QOpenGLDebugMessage)), mConsole, -// SLOT(sendLog(QOpenGLDebugMessage))); + // connect( + // mDebugLogger, SIGNAL(messageLogged(QOpenGLDebugMessage)), + // mConsole, SLOT(sendLog(QOpenGLDebugMessage))); mDebugLogger->startLogging(); } @@ -144,22 +158,22 @@ void QtkWidget::dragEnterEvent(QDragEnterEvent * event) { void QtkWidget::dropEvent(QDropEvent * event) { mConsole->sendLog(event->mimeData()->text()); - if (event->mimeData()->hasUrls()) { + if(event->mimeData()->hasUrls()) { auto urls = event->mimeData()->urls(); -// if (urls.size() > 1) { -// qDebug() << "Cannot accept drop of multiple files.\n"; -// event->ignore(); -// } + 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")) { - auto fileName = url.fileName().replace(".obj", "").toStdString(); - auto filePath = url.toLocalFile().toStdString(); - mScene->blockSignals(true); - mScene->addObject(new Qtk::Model(url.fileName().replace(".obj", "").toStdString().c_str(), ":/models/models/scythe/scythe.obj")); - mScene->blockSignals(false); - mScene->sceneUpdated(mScene->getSceneName()); -// mScene->mModels.push_back(new Qtk::Model(url.fileName().replace(".obj", "").toStdString().c_str(), url.path().toStdString().c_str())); -// event->acceptProposedAction(); + if(url.fileName().endsWith(".obj")) { + mScene->loadModel(url); + event->acceptProposedAction(); + } else { + qDebug() << "Unsupported file type."; + event->ignore(); + return; } } } @@ -189,31 +203,6 @@ void QtkWidget::mouseReleaseEvent(QMouseEvent * event) { Input::registerMouseRelease(event->button()); } -void QtkWidget::keyPressEvent(QKeyEvent * event) { - if(event->isAutoRepeat()) { - // Do not repeat input while a key is held down - event->ignore(); - } else { - Input::registerKeyPress(event->key()); - } -} - -void QtkWidget::keyReleaseEvent(QKeyEvent * event) { - if(event->isAutoRepeat()) { - event->ignore(); - } else { - Input::registerKeyRelease(event->key()); - } -} - -void QtkWidget::mousePressEvent(QMouseEvent * event) { - Input::registerMousePress(event->button()); -} - -void QtkWidget::mouseReleaseEvent(QMouseEvent * event) { - Input::registerMouseRelease(event->button()); -} - void QtkWidget::update() { updateCameraInput(); @@ -299,44 +288,6 @@ void QtkWidget::messageLogged(const QOpenGLDebugMessage & msg) { void QtkWidget::teardownGL() { /* Nothing to teardown yet... */ } -void QtkWidget::updateCameraInput() { - Input::update(); - // Camera Transformation - if(Input::buttonPressed(Qt::RightButton)) { - static const float transSpeed = 0.1f; - static const float rotSpeed = 0.5f; - - // Handle rotations - Scene::getCamera().getTransform().rotate( - -rotSpeed * Input::mouseDelta().x(), Camera3D::LocalUp); - Scene::getCamera().getTransform().rotate( - -rotSpeed * Input::mouseDelta().y(), Scene::getCamera().getRight()); - - // Handle translations - QVector3D translation; - if(Input::keyPressed(Qt::Key_W)) { - translation += Scene::getCamera().getForward(); - } - if(Input::keyPressed(Qt::Key_S)) { - translation -= Scene::getCamera().getForward(); - } - if(Input::keyPressed(Qt::Key_A)) { - translation -= Scene::getCamera().getRight(); - } - if(Input::keyPressed(Qt::Key_D)) { - translation += Scene::getCamera().getRight(); - } - if(Input::keyPressed(Qt::Key_Q)) { - translation -= Scene::getCamera().getUp() / 2.0f; - } - if(Input::keyPressed(Qt::Key_E)) { - translation += Scene::getCamera().getUp() / 2.0f; - } - Scene::getCamera().getTransform().translate(transSpeed * translation); - } -void QtkWidget::teardownGL() { /* Nothing to teardown yet... */ -} - void QtkWidget::updateCameraInput() { Input::update(); // Camera Transformation diff --git a/src/app/qtkwidget.h b/src/app/qtkwidget.h index 1d7089b..8643bc4 100644 --- a/src/app/qtkwidget.h +++ b/src/app/qtkwidget.h @@ -170,6 +170,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. */ @@ -179,10 +180,6 @@ namespace Qtk { /************************************************************************* * Private Methods ************************************************************************/ - inline void loadModel(const QString & path) { - auto * model = new Model("loadedModel", path.toStdString().c_str()); - mScene->mModels.push_back(model); - } /** * Deconstruct any resources we have allocated for this widget. diff --git a/src/qtk/object.h b/src/qtk/object.h index 6092608..ffa0d80 100644 --- a/src/qtk/object.h +++ b/src/qtk/object.h @@ -100,6 +100,10 @@ namespace Qtk { * Setters ************************************************************************/ + virtual inline void setName(const std::string & name) { + mName = name; + } + virtual inline void setColors(const Colors & value) { mShape.mColors = value; } diff --git a/src/qtk/scene.cpp b/src/qtk/scene.cpp index 8585c21..684a557 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) { @@ -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 Qtk::Scene::initSceneObjectName(Qtk::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..780d0ca 100644 --- a/src/qtk/scene.h +++ b/src/qtk/scene.h @@ -10,7 +10,9 @@ #define QTK_SCENE_H #include +#include +#include #include #include "camera3d.h" @@ -75,6 +77,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.push({name, path}); + } + /************************************************************************* * Accessors ************************************************************************/ @@ -93,6 +107,12 @@ namespace Qtk { */ [[nodiscard]] Object * getObject(const QString & 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 +186,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 +197,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 +224,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 {