diff --git a/.clang-format b/.clang-format index aad4bd6..14b0695 100644 --- a/.clang-format +++ b/.clang-format @@ -1,8 +1,5 @@ - --- -# clang-format off BasedOnStyle: Google -# clang-format on AlignAfterOpenBracket: AlwaysBreak AlignArrayOfStructures: Left AlignConsecutiveAssignments: None diff --git a/CMakeLists.txt b/CMakeLists.txt index bbfebb5..75d043f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,8 +30,8 @@ option(QTK_DEBUG "Enable debugger" ON) message(STATUS "[Qtk] Compiling with QTK_DEBUG=${QTK_DEBUG}") option(QTK_UPDATE_SUBMODULES "Update external project (assimp) submodule" ON) message( - STATUS - "[Qtk] Compiling with QTK_UPDATE_SUBMODULES=${QTK_UPDATE_SUBMODULES}" + STATUS + "[Qtk] Compiling with QTK_UPDATE_SUBMODULES=${QTK_UPDATE_SUBMODULES}" ) # Qt options @@ -55,7 +55,7 @@ list(APPEND CMAKE_PREFIX_PATH "${QT_DIR}") # Find Qt find_package(Qt6 COMPONENTS OpenGLWidgets) -if (NOT Qt6_FOUND) +if(NOT Qt6_FOUND) message( SEND_ERROR "[Qtk] Error: Unable to find Qt6 at CMAKE_PREFIX_PATH: " "${CMAKE_PREFIX_PATH}" @@ -68,7 +68,7 @@ if (NOT Qt6_FOUND) ) endif() -if (QTK_UPDATE_SUBMODULES) +if(QTK_UPDATE_SUBMODULES) message(STATUS "[Qtk] Updating submodules...") include("${CMAKE_SOURCE_DIR}/cmake/include/git_submodule.cmake") submodule_update("${CMAKE_CURRENT_SOURCE_DIR}/extern/assimp/assimp/") @@ -83,34 +83,34 @@ endif() ################################################################################ set( - PUBLIC_HEADERS - src/qtkwidget.h - src/abstractscene.h - src/camera3d.h - src/mesh.h - src/meshrenderer.h - src/model.h - src/object.h - src/skybox.h - src/texture.h - src/transform3D.h + PUBLIC_HEADERS + src/qtkwidget.h + src/scene.h + src/camera3d.h + src/mesh.h + src/meshrenderer.h + src/model.h + src/object.h + src/skybox.h + src/texture.h + src/transform3D.h ) set( - SOURCE_FILES - src/qtkwidget.cpp - src/abstractscene.cpp - src/camera3d.cpp - src/input.cpp - src/input.h - src/mesh.cpp - src/meshrenderer.cpp - src/model.cpp - src/object.cpp - src/qtkapi.h - src/skybox.cpp - src/texture.cpp - src/transform3D.cpp + SOURCE_FILES + src/qtkwidget.cpp + src/scene.cpp + src/camera3d.cpp + src/input.cpp + src/input.h + src/mesh.cpp + src/meshrenderer.cpp + src/model.cpp + src/object.cpp + src/qtkapi.h + src/skybox.cpp + src/texture.cpp + src/transform3D.cpp ) include(GenerateExportHeader) @@ -118,7 +118,8 @@ include(GenerateExportHeader) add_library(qtk-widget STATIC ${PUBLIC_HEADERS} ${SOURCE_FILES}) target_include_directories(qtk-widget PRIVATE src/ app/) -set_target_properties(qtk-widget PROPERTIES +set_target_properties( + qtk-widget PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}" VERSION ${PROJECT_VERSION} ) @@ -126,7 +127,7 @@ set_target_properties(qtk-widget PROPERTIES target_link_libraries(qtk-widget PUBLIC Qt6::OpenGLWidgets) target_link_libraries(qtk-widget PUBLIC Qt6::Widgets) -if (QTK_UPDATE_SUBMODULES OR NOT ASSIMP_NEW_INTERFACE) +if(QTK_UPDATE_SUBMODULES OR NOT ASSIMP_NEW_INTERFACE) target_link_libraries(qtk-widget PUBLIC assimp) elseif(ASSIMP_NEW_INTERFACE) target_link_libraries(qtk-widget PUBLIC assimp::assimp) @@ -144,7 +145,8 @@ if(WIN32) endif() # Install files -install(TARGETS qtk-widget +install( + TARGETS qtk-widget # Associate qtk-widget target with qtk-export EXPORT qtk-export # /bin on DLL systems and /lib on non-dll systems @@ -157,7 +159,8 @@ install(TARGETS qtk-widget # Install export # qtkTargets.cmake will only be installed when one of the CONFIGURATIONS is installed # + The generated import will only reference that qtk configuration -install(EXPORT qtk-export +install( + EXPORT qtk-export FILE qtkTargets.cmake CONFIGURATIONS Debug|Release DESTINATION ${CMAKE_INSTALL_PREFIX}/cmake @@ -175,7 +178,8 @@ configure_file( ) # Add our Qt resources.qrc file to our application -set(QTK_APP_SOURCES app/main.cpp +set( + QTK_APP_SOURCES app/main.cpp app/examplescene.cpp app/examplescene.h app/mainwindow.cpp app/mainwindow.h app/mainwindow.ui app/resourcemanager.h @@ -189,13 +193,15 @@ target_include_directories(qtk-main PRIVATE src/ app/) # Link qtk-main executable to main qtk-widget library target_link_libraries(qtk-main PUBLIC qtk-widget) -set_target_properties(qtk-main PROPERTIES +set_target_properties( + qtk-main PROPERTIES WIN32_EXECUTABLE TRUE MACOSX_BUNDLE TRUE MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} ) -install(TARGETS qtk-main +install( + TARGETS qtk-main BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) @@ -206,17 +212,18 @@ if(WIN32) execute_process(COMMAND "${_qt6_qmake_location}" -query QT_INSTALL_PREFIX RESULT_VARIABLE return_code OUTPUT_VARIABLE qt6_install_prefix OUTPUT_STRIP_TRAILING_WHITESPACE) file(TO_NATIVE_PATH "${qt6_install_prefix}/bin" qt6_install_prefix) if(TARGET Qt6::windeployqt) - add_custom_command(TARGET qtk-main + add_custom_command( + TARGET qtk-main POST_BUILD COMMAND set PATH=%PATH%$${qt6_install_prefix} COMMAND Qt6::windeployqt --dir "${CMAKE_BINARY_DIR}/windeployqt" "$/$" - ) + ) install(DIRECTORY "${CMAKE_BINARY_DIR}/windeployqt/" DESTINATION bin) endif() if(MSVC AND TARGET Qt6::qmake) set(VSUSER_FILE ${CMAKE_CURRENT_BINARY_DIR}/qtk-main.vcxproj.user) file(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}/extern/assimp/assimp/bin" assimp_bin) - file(WRITE ${VSUSER_FILE} "\n") + file(WRITE ${VSUSER_FILE} "\n") file(APPEND ${VSUSER_FILE} "\n") file(APPEND ${VSUSER_FILE} " \n") file(APPEND ${VSUSER_FILE} " Path=$(SolutionDir)\\lib\\$(Configuration);${qt6_install_prefix};${assimp_bin};$(Path)\n") diff --git a/app/examplescene.cpp b/app/examplescene.cpp index 9aa433f..f5129f0 100644 --- a/app/examplescene.cpp +++ b/app/examplescene.cpp @@ -6,12 +6,12 @@ ## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## ##############################################################################*/ -#include #include #include #include #include #include +#include #include using namespace Qtk; @@ -21,8 +21,8 @@ using namespace Qtk; ******************************************************************************/ ExampleScene::ExampleScene() { - Camera().transform().setTranslation(0.0f, 0.0f, 20.0f); - Camera().transform().setRotation(-5.0f, 0.0f, 1.0f, 0.0f); + getCamera().getTransform().setTranslation(0.0f, 0.0f, 20.0f); + getCamera().getTransform().setRotation(-5.0f, 0.0f, 1.0f, 0.0f); } ExampleScene::~ExampleScene() { @@ -44,60 +44,40 @@ ExampleScene::~ExampleScene() { ******************************************************************************/ void ExampleScene::init() { - auto * sb = new Qtk::Skybox("Skybox"); - setSkybox(sb); + setSkybox(new Qtk::Skybox("Skybox")); - // Initialize Phong example cube - mTestPhong = new Qtk::MeshRenderer("phong", Qtk::Cube()); - mTestPhong->mTransform.setTranslation(3.0f, 0.0f, -2.0f); - mTestPhong->setShaders(":/solid-phong.vert", ":/solid-phong.frag"); + // + // Create simple shapes using MeshRenderer class and data in mesh.h - // You no longer need to manually bind shader program. - // + But, you can still bind it if you want to. - // mTestPhong->bindShaders(); - mTestPhong->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); - mTestPhong->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f)); - mTestPhong->setUniform("uAmbientStrength", 0.2f); - mTestPhong->setUniform("uSpecularStrength", 0.50f); - mTestPhong->setUniform("uSpecularShine", 256); - mTestPhong->reallocateNormals(mTestPhong->getNormals()); - // mTestPhong->releaseShaders(); + mMeshes.push_back( + new Qtk::MeshRenderer("rightTriangle", Triangle(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(-5.0f, 0.0f, -2.0f); - // Initialize Ambient example cube - mTestAmbient = new Qtk::MeshRenderer("ambient", Cube()); - mTestAmbient->mTransform.setTranslation(7.0f, 0.0f, -2.0f); - mTestAmbient->setShaders(":/solid-ambient.vert", ":/solid-ambient.frag"); - mTestAmbient->init(); - mTestAmbient->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); - mTestAmbient->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f)); - mTestAmbient->setUniform("uAmbientStrength", 0.2f); - mTestAmbient->reallocateNormals(mTestAmbient->getNormals()); + mMeshes.push_back( + new Qtk::MeshRenderer("centerCube", Cube(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(-7.0f, 0.0f, -2.0f); - // Initialize Diffuse example cube - mTestDiffuse = new Qtk::MeshRenderer("diffuse", Cube()); - mTestDiffuse->mTransform.setTranslation(9.0f, 0.0f, -2.0f); - mTestDiffuse->setShaders(":/solid-diffuse.vert", ":/solid-diffuse.frag"); - mTestDiffuse->init(); - mTestDiffuse->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); - mTestDiffuse->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f)); - mTestDiffuse->setUniform("uAmbientStrength", 0.2f); - mTestDiffuse->reallocateNormals(mTestDiffuse->getNormals()); + mMeshes.push_back( + new Qtk::MeshRenderer("leftTriangle", Triangle(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(-9.0f, 0.0f, -2.0f); + mMeshes.back()->setDrawType(GL_LINE_LOOP); - // Initialize Specular example cube - mTestSpecular = new Qtk::MeshRenderer("specular", Cube()); - mTestSpecular->mTransform.setTranslation(11.0f, 0.0f, -2.0f); - mTestSpecular->setShaders(":/solid-specular.vert", ":/solid-specular.frag"); - mTestSpecular->init(); - mTestSpecular->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); - mTestSpecular->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f)); - mTestSpecular->setUniform("uAmbientStrength", 0.2f); - mTestSpecular->setUniform("uSpecularStrength", 0.50f); - mTestSpecular->setUniform("uSpecularShine", 256); - mTestSpecular->reallocateNormals(mTestSpecular->getNormals()); + mMeshes.push_back( + new Qtk::MeshRenderer("topTriangle", Triangle(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(-7.0f, 2.0f, -2.0f); + mMeshes.back()->mTransform.scale(0.25f); + + mMeshes.push_back( + new Qtk::MeshRenderer("bottomTriangle", Triangle(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(-7.0f, -2.0f, -2.0f); + mMeshes.back()->mTransform.scale(0.25f); + mMeshes.back()->setDrawType(GL_LINE_LOOP); + // This function changes values we have allocated in a buffer, so init() after + mMeshes.back()->setColor(GREEN); // - // Model loading + // 3D Model loading mModels.push_back( new Qtk::Model("backpack", ":/models/backpack/backpack.obj")); @@ -129,18 +109,124 @@ void ExampleScene::init() { new Qtk::Model("masterChief", ":/models/spartan/spartan.obj")); mModels.back()->mTransform.setTranslation(-1.5f, 0.5f, -2.0f); + // - // Building example mesh objects + // Simple cube lighting examples. - // Render an alien with specular - // Test alien Model with phong lighting and specular mapping - mMeshes.push_back(new Qtk::MeshRenderer( - "alienTestLight", Triangle(Qtk::QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(4.0f, 1.5f, 10.0f); + /* Phong lighting example on a basic cube. */ + mTestPhong = new Qtk::MeshRenderer("phong", Qtk::Cube()); + mTestPhong->mTransform.setTranslation(3.0f, 0.0f, -2.0f); + // NOTE: You no longer need to manually bind shader program to set uniforms. + // + You can still bind it if you want to for performance reasons. + // + Qtk will only bind / release if the shader program is not already bound. + mTestPhong->setShaders(":/solid-phong.vert", ":/solid-phong.frag"); + + // For example this would technically not be efficient, because each one of + // these calls will bind, set, release. We could instead bind, set N uniforms, + // and release when we are finished. + // mTestPhong->bindShaders(); + mTestPhong->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); + mTestPhong->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f)); + mTestPhong->setUniform("uAmbientStrength", 0.2f); + mTestPhong->setUniform("uSpecularStrength", 0.50f); + mTestPhong->setUniform("uSpecularShine", 256); + // mTestPhong->releaseShaders(); + mTestPhong->reallocateNormals(mTestPhong->getNormals()); + // NOTE: This is only an example and I won't worry about this kind of + // efficiency while initializing the following objects. + + // Phong lighting example light source. This is just for visual reference. + // + We refer to the position of this object in draw() to update lighting. + mMeshes.push_back( + new Qtk::MeshRenderer("phongLight", Triangle(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(3.0f, 2.0f, -2.0f); mMeshes.back()->mTransform.scale(0.25f); - // This function changes values we have allocated in a buffer, so init() after - mMeshes.back()->setColor(GREEN); + /* Example of a cube with no lighting applied */ + mMeshes.push_back(new Qtk::MeshRenderer("noLight", Cube(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(5.0f, 0.0f, -2.0f); + mMeshes.back()->setShaders( + ":/solid-perspective.vert", ":/solid-perspective.frag"); + mMeshes.back()->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); + // No light source needed for this lighting technique + + /* Initialize Ambient example cube */ + mTestAmbient = new Qtk::MeshRenderer("ambient", Cube()); + mTestAmbient->mTransform.setTranslation(7.0f, 0.0f, -2.0f); + mTestAmbient->setShaders(":/solid-ambient.vert", ":/solid-ambient.frag"); + // Changing these uniform values will alter lighting effects. + mTestAmbient->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); + mTestAmbient->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f)); + mTestAmbient->setUniform("uAmbientStrength", 0.2f); + mTestAmbient->reallocateNormals(mTestAmbient->getNormals()); + // No light source needed for this lighting technique + + /* Initialize Diffuse example cube */ + mTestDiffuse = new Qtk::MeshRenderer("diffuse", Cube()); + mTestDiffuse->mTransform.setTranslation(9.0f, 0.0f, -2.0f); + mTestDiffuse->setShaders(":/solid-diffuse.vert", ":/solid-diffuse.frag"); + mTestDiffuse->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); + mTestDiffuse->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f)); + mTestDiffuse->setUniform("uAmbientStrength", 0.2f); + mTestDiffuse->reallocateNormals(mTestDiffuse->getNormals()); + + // Diffuse lighting example light source. This is just for visual reference. + mMeshes.push_back( + new Qtk::MeshRenderer("diffuseLight", Triangle(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(9.0f, 2.0f, -2.0f); + mMeshes.back()->mTransform.scale(0.25f); + + /* Initialize Specular example cube */ + mTestSpecular = new Qtk::MeshRenderer("specular", Cube()); + mTestSpecular->mTransform.setTranslation(11.0f, 0.0f, -2.0f); + mTestSpecular->setShaders(":/solid-specular.vert", ":/solid-specular.frag"); + mTestSpecular->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); + mTestSpecular->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f)); + mTestSpecular->setUniform("uAmbientStrength", 0.2f); + mTestSpecular->setUniform("uSpecularStrength", 0.50f); + mTestSpecular->setUniform("uSpecularShine", 256); + mTestSpecular->reallocateNormals(mTestSpecular->getNormals()); + + // Specular lighting example light source. This is just for visual reference. + mMeshes.push_back( + new Qtk::MeshRenderer("specularLight", Triangle(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(11.0f, 2.0f, -2.0f); + mMeshes.back()->mTransform.scale(0.25f); + + + /* Test basic cube with phong.vert and phong.frag shaders */ + mMeshes.push_back(new Qtk::MeshRenderer("testPhong", Cube(QTK_DRAW_ARRAYS))); + mMeshes.back()->mTransform.setTranslation(5.0f, 0.0f, 10.0f); + mMeshes.back()->setShaders(":/phong.vert", ":/phong.frag"); + // WARNING: Set color before reallocating normals. + mMeshes.back()->setColor(QVector3D(0.0f, 0.25f, 0.0f)); + mMeshes.back()->reallocateNormals(mMeshes.back()->getNormals()); + + mMeshes.back()->setUniform("uMaterial.ambient", QVector3D(0.0f, 0.3f, 0.0f)); + mMeshes.back()->setUniform("uMaterial.diffuse", QVector3D(0.0f, 0.2f, 0.0f)); + mMeshes.back()->setUniform("uMaterial.specular", QVector3D(1.0f, 1.0f, 1.0f)); + mMeshes.back()->setUniform("uMaterial.ambientStrength", 1.0f); + mMeshes.back()->setUniform("uMaterial.diffuseStrength", 1.0f); + mMeshes.back()->setUniform("uMaterial.specularStrength", 1.0f); + mMeshes.back()->setUniform("uMaterial.shine", 64.0f); + mMeshes.back()->setUniform("uLight.ambient", QVector3D(0.25f, 0.2f, 0.075f)); + mMeshes.back()->setUniform("uLight.diffuse", QVector3D(0.75f, 0.6f, 0.22f)); + mMeshes.back()->setUniform("uLight.specular", QVector3D(0.62f, 0.55f, 0.37f)); + mMeshes.back()->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); + + // Light source for testPhong cube + mMeshes.push_back( + new Qtk::MeshRenderer("testLight", Triangle(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(5.0f, 1.25f, 10.0f); + mMeshes.back()->mTransform.scale(0.25f); + mMeshes.back()->setDrawType(GL_LINE_LOOP); + mMeshes.back()->setColor(RED); + + + // + // Building more complex objects for showing examples of lighting techniques + + /* Test alien Model with phong lighting and specular mapping. */ mModels.push_back(new Qtk::Model( "alienTest", ":/models/alien-hominid/alien.obj", ":/model-specular.vert", ":/model-specular.frag")); @@ -158,14 +244,15 @@ void ExampleScene::init() { mModels.back()->setUniform("uLight.diffuse", QVector3D(1.0f, 1.0f, 1.0f)); mModels.back()->setUniform("uLight.specular", QVector3D(1.0f, 1.0f, 1.0f)); - // Test spartan Model with phong lighting, specular and normal mapping - mMeshes.push_back( - new Qtk::MeshRenderer("spartanTestLight", Triangle(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(1.0f, 1.5f, 10.0f); + // Light source for alienTest object. + mMeshes.push_back(new Qtk::MeshRenderer( + "alienTestLight", Triangle(Qtk::QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(4.0f, 1.5f, 10.0f); mMeshes.back()->mTransform.scale(0.25f); // This function changes values we have allocated in a buffer, so init() after mMeshes.back()->setColor(GREEN); + /* Test spartan Model with phong lighting, specular and normal mapping. */ mModels.push_back(new Qtk::Model( "spartanTest", ":/models/spartan/spartan.obj", ":/model-normals.vert", ":/model-normals.frag")); @@ -182,65 +269,17 @@ void ExampleScene::init() { mModels.back()->setUniform("uLight.diffuse", QVector3D(1.0f, 1.0f, 1.0f)); mModels.back()->setUniform("uLight.specular", QVector3D(1.0f, 1.0f, 1.0f)); - // Test basic cube with phong.vert and phong.frag shaders + // Light source for spartanTest object. mMeshes.push_back( - new Qtk::MeshRenderer("testLight", Triangle(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(5.0f, 1.25f, 10.0f); + new Qtk::MeshRenderer("spartanTestLight", Triangle(QTK_DRAW_ELEMENTS))); + mMeshes.back()->mTransform.setTranslation(1.0f, 1.5f, 10.0f); mMeshes.back()->mTransform.scale(0.25f); - mMeshes.back()->setDrawType(GL_LINE_LOOP); // This function changes values we have allocated in a buffer, so init() after mMeshes.back()->setColor(GREEN); - mMeshes.push_back(new Qtk::MeshRenderer("testPhong", Cube(QTK_DRAW_ARRAYS))); - mMeshes.back()->mTransform.setTranslation(5.0f, 0.0f, 10.0f); - mMeshes.back()->setShaders(":/phong.vert", ":/phong.frag"); - mMeshes.back()->setColor(QVector3D(0.0f, 0.25f, 0.0f)); - mMeshes.back()->reallocateNormals(mMeshes.back()->getNormals()); - - mMeshes.back()->setUniform("uMaterial.ambient", QVector3D(0.0f, 0.3f, 0.0f)); - mMeshes.back()->setUniform("uMaterial.diffuse", QVector3D(0.0f, 0.2f, 0.0f)); - mMeshes.back()->setUniform("uMaterial.specular", QVector3D(1.0f, 1.0f, 1.0f)); - mMeshes.back()->setUniform("uMaterial.ambientStrength", 1.0f); - mMeshes.back()->setUniform("uMaterial.diffuseStrength", 1.0f); - mMeshes.back()->setUniform("uMaterial.specularStrength", 1.0f); - mMeshes.back()->setUniform("uMaterial.shine", 64.0f); - mMeshes.back()->setUniform("uLight.ambient", QVector3D(0.25f, 0.2f, 0.075f)); - mMeshes.back()->setUniform("uLight.diffuse", QVector3D(0.75f, 0.6f, 0.22f)); - mMeshes.back()->setUniform("uLight.specular", QVector3D(0.62f, 0.55f, 0.37f)); - mMeshes.back()->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); - // - // Create simple shapes using MeshRenderer class and data in mesh.h - - mMeshes.push_back( - new Qtk::MeshRenderer("rightTriangle", Triangle(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(-5.0f, 0.0f, -2.0f); - - mMeshes.push_back( - new Qtk::MeshRenderer("centerCube", Cube(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(-7.0f, 0.0f, -2.0f); - - mMeshes.push_back( - new Qtk::MeshRenderer("leftTriangle", Triangle(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(-9.0f, 0.0f, -2.0f); - mMeshes.back()->setDrawType(GL_LINE_LOOP); - - mMeshes.push_back( - new Qtk::MeshRenderer("topTriangle", Triangle(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(-7.0f, 2.0f, -2.0f); - mMeshes.back()->mTransform.scale(0.25f); - - mMeshes.push_back( - new Qtk::MeshRenderer("bottomTriangle", Triangle(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(-7.0f, -2.0f, -2.0f); - mMeshes.back()->mTransform.scale(0.25f); - mMeshes.back()->setDrawType(GL_LINE_LOOP); - // This function changes values we have allocated in a buffer, so init() after - mMeshes.back()->setColor(GREEN); - - // - // Testing for normals, texture coordinates + // Test drawing simple geometry with various OpenGL drawing modes // RGB Normals cube to show normals are correct with QTK_DRAW_ARRAYS mMeshes.push_back( @@ -345,35 +384,11 @@ void ExampleScene::init() { mMeshes.back()->setTexture(":/crate.png"); mMeshes.back()->setUniform("uTexture", 0); mMeshes.back()->reallocateTexCoords(mMeshes.back()->getTexCoords()); - - // - // Lighting cube examples - - // Example of a cube with no lighting applied - mMeshes.push_back(new Qtk::MeshRenderer("noLight", Cube(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(5.0f, 0.0f, -2.0f); - mMeshes.back()->setShaders( - ":/solid-perspective.vert", ":/solid-perspective.frag"); - mMeshes.back()->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f)); - - // Create objects that represent light sources for lighting examples - mMeshes.push_back( - new Qtk::MeshRenderer("phongLight", Triangle(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(3.0f, 2.0f, -2.0f); - mMeshes.back()->mTransform.scale(0.25f); - - mMeshes.push_back( - new Qtk::MeshRenderer("diffuseLight", Triangle(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(9.0f, 2.0f, -2.0f); - mMeshes.back()->mTransform.scale(0.25f); - - mMeshes.push_back( - new Qtk::MeshRenderer("specularLight", Triangle(QTK_DRAW_ELEMENTS))); - mMeshes.back()->mTransform.setTranslation(11.0f, 2.0f, -2.0f); - mMeshes.back()->mTransform.scale(0.25f); } void ExampleScene::draw() { + // WARNING: We must call the base class draw() function first. + // + This will handle rendering core scene components like the Skybox. Scene::draw(); for(const auto & model : mModels) { @@ -392,13 +407,15 @@ void ExampleScene::draw() { "uLightPosition", MeshRenderer::getInstance("phongLight")->mTransform.getTranslation()); mTestPhong->setUniform( - "uCameraPosition", ExampleScene::Camera().transform().getTranslation()); + "uCameraPosition", + ExampleScene::getCamera().getTransform().getTranslation()); mTestPhong->releaseShaders(); mTestPhong->draw(); mTestAmbient->bindShaders(); mTestAmbient->setUniform( - "uCameraPosition", ExampleScene::Camera().transform().getTranslation()); + "uCameraPosition", + ExampleScene::getCamera().getTransform().getTranslation()); mTestAmbient->releaseShaders(); mTestAmbient->draw(); @@ -410,7 +427,8 @@ void ExampleScene::draw() { "uLightPosition", MeshRenderer::getInstance("diffuseLight")->mTransform.getTranslation()); mTestDiffuse->setUniform( - "uCameraPosition", ExampleScene::Camera().transform().getTranslation()); + "uCameraPosition", + ExampleScene::getCamera().getTransform().getTranslation()); mTestDiffuse->releaseShaders(); mTestDiffuse->draw(); @@ -422,7 +440,8 @@ void ExampleScene::draw() { "uLightPosition", MeshRenderer::getInstance("specularLight")->mTransform.getTranslation()); mTestSpecular->setUniform( - "uCameraPosition", ExampleScene::Camera().transform().getTranslation()); + "uCameraPosition", + ExampleScene::getCamera().getTransform().getTranslation()); mTestSpecular->releaseShaders(); mTestSpecular->draw(); } @@ -433,12 +452,13 @@ void ExampleScene::update() { auto alien = Model::getInstance("alienTest"); alien->setUniform("uLight.position", position); alien->setUniform( - "uCameraPosition", ExampleScene::Camera().transform().getTranslation()); + "uCameraPosition", + ExampleScene::getCamera().getTransform().getTranslation()); auto posMatrix = alien->mTransform.toMatrix(); alien->setUniform("uMVP.normalMatrix", posMatrix.normalMatrix()); alien->setUniform("uMVP.model", posMatrix); - alien->setUniform("uMVP.view", ExampleScene::Camera().toMatrix()); - alien->setUniform("uMVP.projection", ExampleScene::Projection()); + alien->setUniform("uMVP.view", ExampleScene::getCamera().toMatrix()); + alien->setUniform("uMVP.projection", ExampleScene::getProjectionMatrix()); alien->mTransform.rotate(0.75f, 0.0f, 1.0f, 0.0f); position = MeshRenderer::getInstance("spartanTestLight") @@ -446,12 +466,13 @@ void ExampleScene::update() { auto spartan = Model::getInstance("spartanTest"); spartan->setUniform("uLight.position", position); spartan->setUniform( - "uCameraPosition", ExampleScene::Camera().transform().getTranslation()); + "uCameraPosition", + ExampleScene::getCamera().getTransform().getTranslation()); posMatrix = spartan->mTransform.toMatrix(); spartan->setUniform("uMVP.normalMatrix", posMatrix.normalMatrix()); spartan->setUniform("uMVP.model", posMatrix); - spartan->setUniform("uMVP.view", ExampleScene::Camera().toMatrix()); - spartan->setUniform("uMVP.projection", ExampleScene::Projection()); + spartan->setUniform("uMVP.view", ExampleScene::getCamera().toMatrix()); + spartan->setUniform("uMVP.projection", ExampleScene::getProjectionMatrix()); spartan->mTransform.rotate(0.75f, 0.0f, 1.0f, 0.0f); auto phong = MeshRenderer::getInstance("testPhong"); @@ -461,12 +482,13 @@ void ExampleScene::update() { MeshRenderer::getInstance("testLight")->mTransform.getTranslation(); phong->setUniform("uLight.position", position); phong->setUniform( - "uCameraPosition", ExampleScene::Camera().transform().getTranslation()); + "uCameraPosition", + ExampleScene::getCamera().getTransform().getTranslation()); posMatrix = phong->mTransform.toMatrix(); phong->setUniform("uMVP.normalMatrix", posMatrix.normalMatrix()); phong->setUniform("uMVP.model", posMatrix); - phong->setUniform("uMVP.view", ExampleScene::Camera().toMatrix()); - phong->setUniform("uMVP.projection", ExampleScene::Projection()); + phong->setUniform("uMVP.view", ExampleScene::getCamera().toMatrix()); + phong->setUniform("uMVP.projection", ExampleScene::getProjectionMatrix()); phong->releaseShaders(); // Rotate lighting example cubes diff --git a/app/examplescene.h b/app/examplescene.h index 117cbcf..9ea0cb2 100644 --- a/app/examplescene.h +++ b/app/examplescene.h @@ -9,22 +9,65 @@ #ifndef QTK_EXAMPLE_SCENE_H #define QTK_EXAMPLE_SCENE_H -#include #include +#include #include #include +/** + * Example scene using QtkWidget to render 3D models and simple geometry within + * QtOpenGLWidgets. This scene also shows some examples of using GLSL shaders to + * apply the basic lighting techniques leading up to Phong. + * + * The Qtk::Scene base class provides containers for N pointers to MeshRenderer + * and Model objects. We can create and insert as many as we like within this + * child class implementation. This child class does not need to manually draw + * objects inserted into these containers. The child class would only need to + * update uniform or other data that may change per-frame. + * See scene.h and `init()` for more information. + * + * To modify the scene objects should be initialized within the `init()` public + * method. Any required movement or updates should be applied within `draw()` or + * `update()`. + * + * To create your own Scene from scratch see Qtk::Scene. + */ class ExampleScene : public Qtk::Scene { public: + /*************************************************************************** + * Contructors / Destructors + **************************************************************************/ + ExampleScene(); ~ExampleScene(); + /*************************************************************************** + * Inherited Public Overrides + **************************************************************************/ + + /** + * Initialize objects within the scene + */ void init() override; + /** + * Called when OpenGL repaints the widget. + */ void draw() override; + + /** + * Called when the Qt `frameSwapped` signal is caught. + * See definition of `QtkWidget::initializeGL()` + */ void update() override; private: + /*************************************************************************** + * Private Members + **************************************************************************/ + + // Additional example objects created within this example. + // + The base class Scene manages objects stored within mMeshes or mModels Qtk::MeshRenderer * mTestPhong {}; Qtk::MeshRenderer * mTestSpecular {}; Qtk::MeshRenderer * mTestDiffuse {}; diff --git a/app/main.cpp b/app/main.cpp index 1adf4d2..3fe4a92 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -16,20 +16,6 @@ int main(int argc, char * argv[]) { QApplication a(argc, argv); - // Set OpenGL Version information - // Note: This format must be set before show() is called. - QSurfaceFormat format; - format.setRenderableType(QSurfaceFormat::OpenGL); - format.setProfile(QSurfaceFormat::CoreProfile); - format.setVersion(4, 5); - // 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); -#ifdef QTK_DEBUG - format.setOption(QSurfaceFormat::DebugContext); -#endif // QTK_DEBUG - // Create window for Qt application using custom mainwindow.h MainWindow w; w.show(); diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 2369f4d..31a889f 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -1,22 +1,37 @@ +/*############################################################################## +## Author: Shaun Reed ## +## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ## +## About: MainWindow for creating an example Qt application using QtkWidget ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +##############################################################################*/ + #include #include #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent), ui(new Ui::MainWindow) { - ui->setupUi(this); // For use in design mode using Qt Creator // + We can use the `ui` member to access nested widgets by name + ui->setupUi(this); + + // Find all QtkWidgets in this QMainWindow and initialize their scenes. for(const auto widget : ui->qWidget->children()) { auto qtkWidget = dynamic_cast(widget); if(qtkWidget != nullptr) { std::string key = qtkWidget->objectName().toStdString(); + // Initialize each scene into a map if it doesn't exist. if(mScenes[key] == nullptr) { - mScenes[qtkWidget->objectName().toStdString()] = new ExampleScene(); + mScenes[key] = new ExampleScene(); } - qtkWidget->setScene(mScenes[qtkWidget->objectName().toStdString()]); + // Set the QtkWidget to use the scene associated with this widget. + qtkWidget->setScene(mScenes[key]); } } + + // Set the window icon used for Qtk. + // TODO: Update this to be something other than kilroy. setWindowIcon(QIcon("../resources/icon.png")); } diff --git a/app/mainwindow.h b/app/mainwindow.h index c389295..11ec7df 100644 --- a/app/mainwindow.h +++ b/app/mainwindow.h @@ -1,3 +1,11 @@ +/*############################################################################## +## Author: Shaun Reed ## +## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ## +## About: MainWindow for creating an example Qt application using QtkWidget ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +##############################################################################*/ + #ifndef MAINWINDOW_H #define MAINWINDOW_H @@ -12,16 +20,32 @@ namespace Ui { class MainWindow; } -class QTK_WIDGET_EXPORT MainWindow - - : public QMainWindow { +/** + * MainWindow class to provide an example of using a QtkWidget within a Qt + * window application. + */ +class QTK_WIDGET_EXPORT MainWindow : public QMainWindow { Q_OBJECT public: + /*************************************************************************** + * Contructors / Destructors + **************************************************************************/ + + /** + * This ctor also initializes the Scene for each QtkWidget in the window. + * To load a different scene this would need to be updated. + * + * @param parent The parent for this QMainWindow + */ explicit MainWindow(QWidget * parent = nullptr); ~MainWindow() override; private: + /*************************************************************************** + * Private Members + **************************************************************************/ + Ui::MainWindow * ui {}; std::unordered_map mScenes {}; }; diff --git a/app/resourcemanager.h b/app/resourcemanager.h index 47dcf82..2df646e 100644 --- a/app/resourcemanager.h +++ b/app/resourcemanager.h @@ -13,6 +13,19 @@ #ifndef QTK_RESOURCEMANAGER_H #define QTK_RESOURCEMANAGER_H +/** + * ResourceManager class used to construct absolute paths to files within the Qt + * resources path. There is no need to manually call this method. + * Model::loadModel(...) will use this method if a Qt resource path is provided. + * The Model constructor behaves the same. If a path is prefixed with `:/` this + * static method will be used to resolve a full system path. + * + * This will likely be deprecated. It has a single call site and it is not + * meant for public use. It is public only for convenience. + * + * RM::getPath(":/models/alien-hominid/alien.obj") = + * /full/path/to/models/alien-hominid/alien.obj + */ typedef class ResourceManager { public: /** @@ -24,7 +37,7 @@ typedef class ResourceManager { * ':/models/backpack/backpack.obj' An asset at location * qtk/resources/path/to/asset.obj Should be given in qrc format: * ':/path/to/asset.obj' - * @return Absoulte system path to a qtk asset + * @return Absolute system path to a qtk asset */ static std::string getPath(const std::string & path) { // Only construct qtk resource path if in qrc format; else return it as-is diff --git a/src/abstractscene.h b/src/abstractscene.h deleted file mode 100644 index 8e27178..0000000 --- a/src/abstractscene.h +++ /dev/null @@ -1,56 +0,0 @@ -/*############################################################################## -## Author: Shaun Reed ## -## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ## -## About: Classes for managing objects and data within a scene ## -## ## -## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## -##############################################################################*/ - -#ifndef QTK_SCENE_H -#define QTK_SCENE_H - -#include -#include -#include -#include - -#include - -namespace Qtk { - class Scene : protected QOpenGLFunctions { - friend class MainWidget; - - public: - Scene(); - ~Scene(); - - virtual void init() = 0; - - virtual void draw() { privDraw(); }; - virtual void update() = 0; - - static Camera3D & Camera() { return mCamera; } - - static QMatrix4x4 View() { return mCamera.toMatrix(); } - - static QMatrix4x4 & Projection() { return mProjection; } - - inline Skybox * getSkybox() { return mSkybox; } - - inline void setSkybox(Skybox * skybox) { mSkybox = skybox; } - - private: - static Camera3D mCamera; - static QMatrix4x4 mProjection; - bool mInit = false; - - void privDraw(); - - protected: - Skybox * mSkybox {}; - std::vector mMeshes {}; - std::vector mModels {}; - }; -} // namespace Qtk - -#endif // QTK_SCENE_H diff --git a/src/camera3d.cpp b/src/camera3d.cpp index a40d8ad..68ce14c 100644 --- a/src/camera3d.cpp +++ b/src/camera3d.cpp @@ -32,22 +32,22 @@ const QMatrix4x4 & Camera3D::toMatrix() { ******************************************************************************/ QDataStream & operator<<(QDataStream & out, Camera3D & transform) { - out << transform.transform(); + out << transform.getTransform(); return out; } QDataStream & operator>>(QDataStream & in, Camera3D & transform) { - in >> transform.transform(); + in >> transform.getTransform(); return in; } QDebug operator<<(QDebug dbg, const Camera3D & transform) { dbg << "Camera3D\n{\n"; - dbg << "Position: <" << transform.translation().x() << ", " - << transform.translation().y() << ", " << transform.translation().z() - << ">\n"; - dbg << "Rotation: <" << transform.rotation().x() << ", " - << transform.rotation().y() << ", " << transform.rotation().z() << " | " - << transform.rotation().scalar() << ">\n}"; + dbg << "Position: <" << transform.getTranslation().x() << ", " + << transform.getTranslation().y() << ", " + << transform.getTranslation().z() << ">\n"; + dbg << "Rotation: <" << transform.getRotation().x() << ", " + << transform.getRotation().y() << ", " << transform.getRotation().z() + << " | " << transform.getRotation().scalar() << ">\n}"; return dbg; } diff --git a/src/camera3d.h b/src/camera3d.h index 01e14ab..89b714d 100644 --- a/src/camera3d.h +++ b/src/camera3d.h @@ -17,19 +17,25 @@ namespace Qtk { class QTKAPI Camera3D { public: - // Constants + /************************************************************************* + * Constants + ************************************************************************/ + static const QVector3D LocalForward; static const QVector3D LocalUp; static const QVector3D LocalRight; - // Accessors - inline Transform3D & transform() { return mTransform; } + /************************************************************************* + * Accessors + ************************************************************************/ - [[nodiscard]] inline const QVector3D & translation() const { + inline Transform3D & getTransform() { return mTransform; } + + [[nodiscard]] inline const QVector3D & getTranslation() const { return mTransform.getTranslation(); } - [[nodiscard]] inline const QQuaternion & rotation() const { + [[nodiscard]] inline const QQuaternion & getRotation() const { return mTransform.getRotation(); } @@ -49,9 +55,17 @@ namespace Qtk { } private: + /************************************************************************* + * Private Members + ************************************************************************/ + Transform3D mTransform; QMatrix4x4 mWorld; + /************************************************************************* + * Private Methods + ************************************************************************/ + #ifndef QT_NO_DATASTREAM friend QDataStream & operator<<(QDataStream & out, Camera3D & transform); friend QDataStream & operator>>(QDataStream & in, Camera3D & transform); diff --git a/src/input.h b/src/input.h index 5863105..b9cdfec 100644 --- a/src/input.h +++ b/src/input.h @@ -17,10 +17,15 @@ namespace Qtk { class QTKAPI Input { + public: + /************************************************************************* + * Typedefs + ************************************************************************/ friend class Qtk::QtkWidget; - public: - // Possible key states + /** + * Possible key states + */ enum InputState { InputInvalid, InputRegistered, @@ -30,6 +35,10 @@ namespace Qtk { InputReleased }; + /************************************************************************* + * Public Methods + ************************************************************************/ + // State checking inline static bool keyTriggered(Qt::Key key) { return keyState(key) == InputTriggered; @@ -63,6 +72,10 @@ namespace Qtk { static QPoint mouseDelta(); private: + /************************************************************************* + * Private Methods + ************************************************************************/ + // State updating static void update(); static void registerKeyPress(int key); diff --git a/src/mainwidget.h b/src/mainwidget.h deleted file mode 100644 index 589b852..0000000 --- a/src/mainwidget.h +++ /dev/null @@ -1,75 +0,0 @@ -/*############################################################################## -## Author: Shaun Reed ## -## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ## -## About: Main window for Qt6 OpenGL widget application ## -## ## -## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## -##############################################################################*/ -#ifndef QTK_MAINWIDGET_H -#define QTK_MAINWIDGET_H - -#include - -#include -#include -#include -#include - -#define QTK_DEBUG - -class MeshRenderer; - -class Model; - -class Object; - -class Scene; - -class Skybox; - -class OpenGLTextureFactory; - -class MainWidget : public QOpenGLWidget, protected QOpenGLFunctions { - Q_OBJECT; - - public: - // Constructors - MainWidget(); - explicit MainWidget(QWidget * parent); - explicit MainWidget(const QSurfaceFormat & format); - ~MainWidget() override; - - private: - void teardownGL(); - void initObjects(); - - public: - // Inherited virtual Members - void paintGL() override; - void initializeGL() override; - void resizeGL(int width, int height) override; - - protected slots: - void update(); - void messageLogged(const QOpenGLDebugMessage & msg); - - // Protected Helpers - protected: - void keyPressEvent(QKeyEvent * event) override; - void keyReleaseEvent(QKeyEvent * event) override; - void mousePressEvent(QMouseEvent * event) override; - void mouseReleaseEvent(QMouseEvent * event) override; - - private: - // Private helpers - void initializeWidget(); - void printContextInformation(); - void updateCameraInput(); - - Scene * mScene; - Object * mObject; - - QOpenGLDebugLogger * mDebugLogger; -}; - -#endif // QTK_MAINWIDGET_H diff --git a/src/mesh.h b/src/mesh.h index a411fef..a669c31 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -84,13 +84,26 @@ namespace Qtk { typedef std::vector TexCoords; typedef std::vector Normals; + /** + * The OpenGL draw mode to initialize QTK shape data for. + * Different draw modes require different organization of data. + * This enum allows us to predefine simple geometry for different draw modes. + */ enum DrawMode { QTK_DRAW_ARRAYS, QTK_DRAW_ELEMENTS, QTK_DRAW_ELEMENTS_NORMALS }; + /** + * Base class for all simple shape objects. + */ struct QTKAPI ShapeBase { + public: + /************************************************************************* + * Constructors / Destructors + ************************************************************************/ + explicit ShapeBase( DrawMode mode = QTK_DRAW_ARRAYS, Vertices v = {}, Indices i = {}, Colors c = {}, TexCoords t = {}, Normals n = {}) : @@ -99,6 +112,10 @@ namespace Qtk { mIndices(std::move(i)), mTexCoords(std::move(t)), mNormals(std::move(n)) {} + /************************************************************************* + * Accessors + ************************************************************************/ + [[nodiscard]] inline const Vertices & getVertices() const { return mVertices; } @@ -122,6 +139,10 @@ namespace Qtk { } protected: + /************************************************************************* + * Protected Members + ************************************************************************/ + DrawMode mDrawMode; Vertices mVertices {}; @@ -132,13 +153,26 @@ namespace Qtk { }; struct Shape : public ShapeBase { + public: + /************************************************************************* + * Typedefs + ************************************************************************/ + friend MeshRenderer; friend Object; + /************************************************************************* + * Constructors / Destructors + ************************************************************************/ + Shape() = default; explicit Shape(const ShapeBase & rhs) : ShapeBase(rhs) {} + /************************************************************************* + * Setters + ************************************************************************/ + virtual inline void setVertices(const Vertices & value) { mVertices = value; } @@ -160,13 +194,15 @@ namespace Qtk { virtual inline void setShape(const Shape & value) { *this = value; } }; - // Primitives inherit from ShapeBase, does not allow setting of shape values + /* Primitives inherit from ShapeBase, doesn't allow setting shape values. */ class QTKAPI Mesh {}; + /* Simple Cube shape. */ struct QTKAPI Cube : public ShapeBase { explicit Cube(DrawMode mode = QTK_DRAW_ARRAYS); }; + /* Simple Triangle shape. */ struct QTKAPI Triangle : public ShapeBase { explicit Triangle(DrawMode mode = QTK_DRAW_ARRAYS); }; diff --git a/src/meshrenderer.cpp b/src/meshrenderer.cpp index 1d8dc21..7372cf4 100644 --- a/src/meshrenderer.cpp +++ b/src/meshrenderer.cpp @@ -8,8 +8,8 @@ #include -#include #include +#include #include using namespace Qtk; @@ -31,6 +31,13 @@ MeshRenderer::~MeshRenderer() { // Static member function to retrieve instances of MeshRenderers MeshRenderer * MeshRenderer::getInstance(const QString & name) { + if(!sInstances.contains(name)) { +#if QTK_DEBUG + qDebug() << "Attempt to access MeshRenderer instance that does not exist! (" + << qPrintable(name) << ")\n"; +#endif + return nullptr; + } return sInstances[name]; } @@ -126,8 +133,8 @@ void MeshRenderer::setShaders( void MeshRenderer::setUniformMVP( const char * model, const char * view, const char * projection) { ShaderBindScope lock(&mProgram, mBound); - mProgram.setUniformValue(projection, Scene::Projection()); - mProgram.setUniformValue(view, Scene::View()); + mProgram.setUniformValue(projection, Scene::getProjectionMatrix()); + mProgram.setUniformValue(view, Scene::getViewMatrix()); mProgram.setUniformValue(model, mTransform.toMatrix()); } @@ -141,6 +148,8 @@ void MeshRenderer::setColor(const QVector3D & color) { mShape.mColors[i] = color; } } + // TODO: Factor this out so we don't need to reinitialize + init(); } void MeshRenderer::reallocateTexCoords(const TexCoords & t, unsigned dims) { diff --git a/src/meshrenderer.h b/src/meshrenderer.h index f33a2d6..4d8a876 100644 --- a/src/meshrenderer.h +++ b/src/meshrenderer.h @@ -15,31 +15,19 @@ #include namespace Qtk { - class QTKAPI ShaderBindScope { - public: - explicit ShaderBindScope( - QOpenGLShaderProgram * program, bool was_locked) : - mWasBound(was_locked) { - mProgram = program; - if(!mWasBound) { - mProgram->bind(); - } - } - - ~ShaderBindScope() { - if(!mWasBound) { - mProgram->release(); - } - } - - private: - QOpenGLShaderProgram * mProgram; - bool mWasBound; - }; - - class QTKAPI MeshRenderer : public Object { public: + /************************************************************************* + * Typedefs + ************************************************************************/ + + /* Static QHash of all mesh objects within the scene. */ + typedef QHash MeshManager; + + /************************************************************************* + * Constructors / Destructors + ************************************************************************/ + // Delegate constructors MeshRenderer( const char * name, Vertices vertices, Indices indices, @@ -54,12 +42,28 @@ namespace Qtk { MeshRenderer(const char * name, const ShapeBase & shape); ~MeshRenderer() override; - // Retrieve a mesh by name stored within a static QHash - static MeshRenderer * getInstance(const QString & name); + /************************************************************************* + * Public Methods + ************************************************************************/ void init(); void draw(); + inline void enableAttributeArray(int location) { + ShaderBindScope lock(&mProgram, mBound); + mVAO.bind(); + mProgram.enableAttributeArray(location); + mVAO.release(); + } + + void reallocateTexCoords(const TexCoords & t, unsigned dims = 2); + + void reallocateNormals(const Normals & n, unsigned dims = 3); + + /************************************************************************* + * Setters + ************************************************************************/ + // Draw types like GL_TRIANGLES, GL_POINTS, GL_LINES, etc void setDrawType(int drawType) { mDrawType = drawType; } @@ -105,21 +109,22 @@ namespace Qtk { mVAO.release(); } - inline void enableAttributeArray(int location) { - ShaderBindScope lock(&mProgram, mBound); - mVAO.bind(); - mProgram.enableAttributeArray(location); - mVAO.release(); - } + /************************************************************************* + * Getters + ************************************************************************/ - void reallocateTexCoords(const TexCoords & t, unsigned dims = 2); - - void reallocateNormals(const Normals & n, unsigned dims = 3); - - // Static QHash of all mesh objects within the scene - typedef QHash MeshManager; + /** + * Retrieve a mesh by name stored within static QHash private member + * @param name The name of the MeshRenderer we want to retrieve. + * @return Pointer to the MeshRenderer, or nullptr if not found. + */ + static MeshRenderer * getInstance(const QString & name); private: + /************************************************************************* + * Private Members + ************************************************************************/ + static MeshManager sInstances; int mDrawType {}; diff --git a/src/model.cpp b/src/model.cpp index 373e9c8..8d6f520 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -9,9 +9,9 @@ #include -#include #include #include +#include #include using namespace Qtk; @@ -97,8 +97,8 @@ void ModelMesh::draw(QOpenGLShaderProgram & shader) { // Set Model View Projection values shader.setUniformValue("uModel", mTransform.toMatrix()); - shader.setUniformValue("uView", Scene::View()); - shader.setUniformValue("uProjection", Scene::Projection()); + shader.setUniformValue("uView", Scene::getViewMatrix()); + shader.setUniformValue("uProjection", Scene::getProjectionMatrix()); GLuint diffuseCount = 1; GLuint specularCount = 1; @@ -180,21 +180,6 @@ void Model::flipTexture(const std::string & fileName, bool flipX, bool flipY) { * Model Private Member Functions ******************************************************************************/ -/** - * Loads a model in .obj, .fbx, .gltf, and other formats - * For a full list of formats see assimp documentation: - * https://github.com/assimp/assimp/blob/master/doc/Fileformats.md - * - * Models should not be loaded into Qt resource system - * Instead pass an *absolute* path to this function - * Relative paths will break if Qtk is executed from different locations - * - * Models can also be loaded from the `qtk/resource` directory using qrc format - * loadModel(":/models/backpack/backpack.obj") - * See resourcemanager.h for more information - * - * @param path Absolute path to a model .obj or other format accepted by assimp - */ void Model::loadModel(const std::string & path) { Assimp::Importer import; @@ -362,7 +347,7 @@ ModelMesh::Textures Model::loadMaterialTextures( // If the texture has not yet been loaded if(!skip) { ModelTexture texture; - texture.mTexture = OpenGLTextureFactory::initTexture2D( + texture.mTexture = OpenGLTextureFactory::initTexture( std::string(mDirectory + '/' + fileName.C_Str()).c_str(), false, false); texture.mID = texture.mTexture->textureId(); @@ -380,7 +365,7 @@ ModelMesh::Textures Model::loadMaterialTextures( } void Model::sortModels() { - auto cameraPos = Scene::Camera().transform(); + auto cameraPos = Scene::getCamera().getTransform(); auto cameraDistance = [&cameraPos](const ModelMesh & a, const ModelMesh & b) { // Sort by the first vertex position in the model return (cameraPos.getTranslation().distanceToPoint( diff --git a/src/model.h b/src/model.h index 432add0..264988b 100644 --- a/src/model.h +++ b/src/model.h @@ -28,14 +28,20 @@ #include namespace Qtk { + /** + * 3D models will store this data for each vertex in geometry + */ struct QTKAPI ModelVertex { QVector3D mPosition; QVector3D mNormal; + QVector2D mTextureCoord; QVector3D mTangent; QVector3D mBitangent; - QVector2D mTextureCoord; }; + /** + * Struct to store model textures. 3D Models may have multiple. + */ struct QTKAPI ModelTexture { GLuint mID {}; QOpenGLTexture * mTexture {}; @@ -45,14 +51,25 @@ namespace Qtk { class Model; + /** + * Mesh class specialized for storing 3D model data. + * Eventually this can be consolidated into a more generic class. + */ class QTKAPI ModelMesh : protected QOpenGLFunctions { public: + /************************************************************************* + * Typedefs + ************************************************************************/ + friend Model; typedef std::vector Vertices; typedef std::vector Indices; typedef std::vector Textures; - // Constructors, Destructors + /************************************************************************* + * Constructors, Destructors + ************************************************************************/ + ModelMesh( Vertices vertices, Indices indices, Textures textures, const char * vertexShader = ":/model-basic.vert", @@ -68,78 +85,180 @@ namespace Qtk { ~ModelMesh() = default; - private: - void initMesh(const char * vert, const char * frag); + /************************************************************************* + * Public Methods + ************************************************************************/ - // ModelMesh Private Members - QOpenGLBuffer *mVBO, *mEBO; - QOpenGLVertexArrayObject * mVAO; - QOpenGLShaderProgram * mProgram; - - public: inline void draw() { draw(*mProgram); } void draw(QOpenGLShaderProgram & shader); - // ModelMesh Public Members + /************************************************************************* + * Public Members + ************************************************************************/ + Vertices mVertices {}; Indices mIndices {}; Textures mTextures {}; Transform3D mTransform; + + private: + /************************************************************************* + * Private Methods + ************************************************************************/ + + void initMesh(const char * vert, const char * frag); + + /************************************************************************* + * Private Members + ************************************************************************/ + + QOpenGLBuffer *mVBO, *mEBO; + QOpenGLVertexArrayObject * mVAO; + QOpenGLShaderProgram * mProgram; }; + /** + * Model object that has a ModelMesh. + * Top-level object that represents 3D models stored within a scene. + */ class QTKAPI Model : public QObject { Q_OBJECT public: + /************************************************************************* + * Typedefs + ************************************************************************/ + + /* ModelManager typedef that will manage global model access. */ + typedef QHash ModelManager; + + /************************************************************************* + * Constructors, Destructors + ************************************************************************/ + + // Default model shaders are provided but we can override them in the ctor inline Model( const char * name, const char * path, const char * vertexShader = ":/model-basic.vert", const char * fragmentShader = ":/model-basic.frag") : mName(name), - mVertexShader(vertexShader), mFragmentShader(fragmentShader) { + mModelPath(path), mVertexShader(vertexShader), + mFragmentShader(fragmentShader) { loadModel(path); } inline ~Model() override { mManager.remove(mName); } + /************************************************************************* + * Public Methods + ************************************************************************/ + void draw(); void draw(QOpenGLShaderProgram & shader); + /** + * Flip a texture associated with this model + * + * @param fileName The name of the texture to flip as it is stored on disk + * @param flipX Flip the texture along the X axis + * @param flipY Flip the texture along the Y axis + */ void flipTexture( const std::string & fileName, bool flipX = false, bool flipY = true); + /************************************************************************* + * Setters + ************************************************************************/ + + /** + * Sets a uniform value + * + * @tparam T The type of the value we are settings + * @param location The uniform location + * @param value The value to assign to the uniform + */ template void setUniform(const char * location, T value) { for(auto & mesh : mMeshes) { mesh.mProgram->bind(); - mesh.mProgram->setUniformValue(location, value); - mesh.mProgram->release(); } } - Transform3D mTransform; + /************************************************************************* + * Accessors + ************************************************************************/ + /** + * Accessor function for retrieving a ModelMesh globally. + * The mesh is retrieved from the mManager private member. + * + * @param name The name of the model to load as it was constructed. + * @return Pointer to the model stored within the scene. + */ static Model * getInstance(const char * name); - typedef QHash ModelManager; + /************************************************************************* + * Public Members + ************************************************************************/ + + /* The position of this model in 3D space */ + // TODO: Make private + Transform3D mTransform; private: - static ModelManager mManager; + /************************************************************************* + * Private Methods + ************************************************************************/ + + /** + * Loads a model in .obj, .fbx, .gltf, and other formats. + * For a full list of formats see assimp documentation: + * https://github.com/assimp/assimp/blob/master/doc/Fileformats.md + * + * Models should not be loaded into Qt resource system. + * Instead pass an *absolute* path to this function. + * Relative paths will break if Qtk is executed from different locations. + * + * Models can also be loaded from the `qtk/resource` directory using qrc + * format loadModel(":/models/backpack/backpack.obj"). + * This does not use Qt resource system, it just provides similar syntax + * for accessing files within the same `resources/` directory. + * + * See resourcemanager.h for more information on how this works. + * + * @param path Absolute path to a model in .obj or another format accepted + * by assimp. + */ void loadModel(const std::string & path); + void processNode(aiNode * node, const aiScene * scene); + ModelMesh processMesh(aiMesh * mesh, const aiScene * scene); + ModelMesh::Textures loadMaterialTextures( aiMaterial * mat, aiTextureType type, const std::string & typeName); + void sortModels(); - // Model Private Members + /************************************************************************* + * Private Members + ************************************************************************/ + /* Static QHash used to store and access models globally. */ + static ModelManager mManager; + + /* Container to store N loaded textures for this model. */ ModelMesh::Textures mTexturesLoaded {}; + /* Container to store N loaded meshes for this model. */ std::vector mMeshes {}; + /* The directory this model and it's textures are stored. */ std::string mDirectory {}; - const char *mVertexShader, *mFragmentShader, *mName; + /* File names for shaders and 3D model on disk. */ + const char *mVertexShader, *mFragmentShader, *mModelPath; + /* Name of the model object within the scene. */ + const char * mName; }; } // namespace Qtk diff --git a/src/object.h b/src/object.h index 90098f0..3e5ae36 100644 --- a/src/object.h +++ b/src/object.h @@ -18,12 +18,24 @@ #include namespace Qtk { + /** + * Object base class for objects that can exist within a scene. + * An object could be a Cube, Skybox, 3D Model, or other standalone entities. + */ class QTKAPI Object : public QObject { Q_OBJECT public: + /************************************************************************* + * Typedefs + ************************************************************************/ + friend MeshRenderer; + /************************************************************************* + * Constructors / Destructors + ************************************************************************/ + // Initialize an object with no shape data assigned explicit Object(const char * name) : mName(name), mVBO(QOpenGLBuffer::VertexBuffer), mBound(false) {} @@ -35,6 +47,10 @@ namespace Qtk { ~Object() override = default; + /************************************************************************* + * Accessors + ************************************************************************/ + inline const Colors & getColors() { return mShape.mColors; } inline const Indices & getIndexData() { return mShape.mIndices; } @@ -49,6 +65,10 @@ namespace Qtk { inline const Vertices & getVertices() { return mShape.mVertices; } + /************************************************************************* + * Setters + ************************************************************************/ + virtual inline void setColors(const Colors & value) { mShape.mColors = value; } @@ -84,6 +104,10 @@ namespace Qtk { mShape.mVertices = value; } + /************************************************************************* + * Public Methods + ************************************************************************/ + virtual inline void bindShaders() { mBound = true; mProgram.bind(); @@ -94,6 +118,10 @@ namespace Qtk { mProgram.release(); } + /************************************************************************* + * Public Members + ************************************************************************/ + QOpenGLBuffer mVBO, mNBO; QOpenGLVertexArrayObject mVAO; @@ -104,9 +132,9 @@ namespace Qtk { bool mBound; private: - virtual inline void setTexture(QOpenGLTexture * value) { - mTexture.setTexture(value); - } + /************************************************************************* + * Private Members + ************************************************************************/ QOpenGLShaderProgram mProgram; }; diff --git a/src/qtkwidget.cpp b/src/qtkwidget.cpp index 34cd9a3..14ffccc 100644 --- a/src/qtkwidget.cpp +++ b/src/qtkwidget.cpp @@ -8,10 +8,10 @@ #include -#include #include #include #include +#include using namespace Qtk; @@ -23,7 +23,6 @@ QtkWidget::QtkWidget() : mScene(Q_NULLPTR), mDebugLogger(Q_NULLPTR) { initializeWidget(); } -// Constructor for using this widget in QtDesigner QtkWidget::QtkWidget(QWidget * parent) : QOpenGLWidget(parent), mScene(Q_NULLPTR), mDebugLogger(Q_NULLPTR) { initializeWidget(); @@ -41,27 +40,9 @@ QtkWidget::~QtkWidget() { } /******************************************************************************* - * Private Member Functions + * Public Inherited Virtual Methods ******************************************************************************/ -void QtkWidget::teardownGL() { - // Nothing to teardown yet... -} - -/******************************************************************************* - * Inherited Virtual Member Functions - ******************************************************************************/ - -void QtkWidget::paintGL() { - // Clear buffers - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - - // Draw the scene first, since it handles drawing our skybox - if(mScene != Q_NULLPTR) { - mScene->draw(); - } -} - void QtkWidget::initializeGL() { initializeOpenGLFunctions(); // Connect the frameSwapped signal to call the update() function @@ -93,11 +74,19 @@ void QtkWidget::initializeGL() { } void QtkWidget::resizeGL(int width, int height) { - Scene::Projection().setToIdentity(); - Scene::Projection().perspective( + Scene::getProjectionMatrix().setToIdentity(); + Scene::getProjectionMatrix().perspective( 45.0f, float(width) / float(height), 0.1f, 1000.0f); } +void QtkWidget::paintGL() { + // Clear buffers and draw the scene if it is valid. + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + if(mScene != Q_NULLPTR) { + mScene->draw(); + } +} + /******************************************************************************* * Protected Slots ******************************************************************************/ @@ -175,7 +164,7 @@ void QtkWidget::messageLogged(const QOpenGLDebugMessage & msg) { } /******************************************************************************* - * Protected Helpers + * Protected Methods ******************************************************************************/ void QtkWidget::keyPressEvent(QKeyEvent * event) { @@ -204,7 +193,7 @@ void QtkWidget::mouseReleaseEvent(QMouseEvent * event) { } /******************************************************************************* - * Private Helpers + * Private Methods ******************************************************************************/ void QtkWidget::initializeWidget() { @@ -265,31 +254,31 @@ void QtkWidget::updateCameraInput() { static const float rotSpeed = 0.5f; // Handle rotations - Scene::Camera().transform().rotate( + Scene::getCamera().getTransform().rotate( -rotSpeed * Input::mouseDelta().x(), Camera3D::LocalUp); - Scene::Camera().transform().rotate( - -rotSpeed * Input::mouseDelta().y(), Scene::Camera().right()); + Scene::getCamera().getTransform().rotate( + -rotSpeed * Input::mouseDelta().y(), Scene::getCamera().right()); // Handle translations QVector3D translation; if(Input::keyPressed(Qt::Key_W)) { - translation += Scene::Camera().forward(); + translation += Scene::getCamera().forward(); } if(Input::keyPressed(Qt::Key_S)) { - translation -= Scene::Camera().forward(); + translation -= Scene::getCamera().forward(); } if(Input::keyPressed(Qt::Key_A)) { - translation -= Scene::Camera().right(); + translation -= Scene::getCamera().right(); } if(Input::keyPressed(Qt::Key_D)) { - translation += Scene::Camera().right(); + translation += Scene::getCamera().right(); } if(Input::keyPressed(Qt::Key_Q)) { - translation -= Scene::Camera().up() / 2.0f; + translation -= Scene::getCamera().up() / 2.0f; } if(Input::keyPressed(Qt::Key_E)) { - translation += Scene::Camera().up() / 2.0f; + translation += Scene::getCamera().up() / 2.0f; } - Scene::Camera().transform().translate(transSpeed * translation); + Scene::getCamera().getTransform().translate(transSpeed * translation); } } diff --git a/src/qtkwidget.h b/src/qtkwidget.h index f3647d4..a19e7b6 100644 --- a/src/qtkwidget.h +++ b/src/qtkwidget.h @@ -15,60 +15,141 @@ #include #include -#include #include +#include namespace Qtk { + /** + * QtkWidget class to define required QOpenGLWidget functionality. + * + * This object has a Scene attached which manages the objects to render. + * Client input is passed through this widget to control the camera view. + */ class QTKAPI QtkWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT; public: - // Constructors + /************************************************************************* + * Contructors / Destructors + ************************************************************************/ + + /** + * Default ctor will configure a QSurfaceFormat with default settings. + */ QtkWidget(); + + /** + * Qt Designer will call this ctor when creating this widget as a child. + * + * @param parent The parent QWidget + */ explicit QtkWidget(QWidget * parent); + + /** + * Allow constructing the widget with a preconfigured QSurfaceFormat. + * + * @param format QSurfaceFormat already configured by the caller. + */ explicit QtkWidget(const QSurfaceFormat & format); + ~QtkWidget() override; private: - void teardownGL(); + /************************************************************************* + * Private Methods + ************************************************************************/ + + // clang-format off + void teardownGL() { /* Nothing to teardown yet... */ } + // clang-format on public: - // Inherited virtual Members - void paintGL() override; + /************************************************************************* + * Public Inherited Virtual Methods + ************************************************************************/ + + /** + * Called when the widget is first constructed. + */ void initializeGL() override; + + /** + * Called when the application window is resized. + * + * @param width The new width of the window. + * @param height The new height of the window. + */ void resizeGL(int width, int height) override; + /** + * Called when OpenGL repaints the widget. + */ + void paintGL() override; + + /************************************************************************* + * Accessors + ************************************************************************/ + inline Qtk::Scene * getScene() { return mScene; } + /************************************************************************* + * Setters + ************************************************************************/ + inline void setScene(Qtk::Scene * scene) { delete mScene; - mScene = scene; } protected slots: + /************************************************************************* + * Qt Slots + ************************************************************************/ + + /** + * Called when the `frameSwapped` signal is caught. + * See definition of initializeGL() + */ void update(); + #ifdef QTK_DEBUG + /** + * Called when the `messageLogged` signal is caught. + * See definition of initializeGL() + * + * @param msg The message logged. + */ static void messageLogged(const QOpenGLDebugMessage & msg); #endif - // Protected Helpers protected: + /************************************************************************* + * Protected Methods + ************************************************************************/ + void keyPressEvent(QKeyEvent * event) override; void keyReleaseEvent(QKeyEvent * event) override; void mousePressEvent(QMouseEvent * event) override; void mouseReleaseEvent(QMouseEvent * event) override; private: - // Private helpers + /************************************************************************* + * Private Methods + ************************************************************************/ + void initializeWidget(); static void updateCameraInput(); - Qtk::Scene * mScene; #ifdef QTK_DEBUG void printContextInformation(); QOpenGLDebugLogger * mDebugLogger; #endif + + /************************************************************************* + * Private Members + ************************************************************************/ + + Qtk::Scene * mScene; }; } // namespace Qtk diff --git a/src/abstractscene.cpp b/src/scene.cpp similarity index 88% rename from src/abstractscene.cpp rename to src/scene.cpp index 6e6fcdc..9a05e66 100644 --- a/src/abstractscene.cpp +++ b/src/scene.cpp @@ -6,9 +6,9 @@ ## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## ##############################################################################*/ -#include #include #include +#include #include using namespace Qtk; @@ -21,8 +21,8 @@ QMatrix4x4 Scene::mProjection; ******************************************************************************/ Scene::Scene() { - mCamera.transform().setTranslation(0.0f, 0.0f, 20.0f); - mCamera.transform().setRotation(-5.0f, 0.0f, 1.0f, 0.0f); + mCamera.getTransform().setTranslation(0.0f, 0.0f, 20.0f); + mCamera.getTransform().setRotation(-5.0f, 0.0f, 1.0f, 0.0f); } Scene::~Scene() { @@ -32,11 +32,10 @@ Scene::~Scene() { for(auto & model : mModels) { delete model; } - delete mSkybox; } -void Scene::privDraw() { +void Scene::privateDraw() { if(!mInit) { initializeOpenGLFunctions(); init(); diff --git a/src/scene.h b/src/scene.h new file mode 100644 index 0000000..439b6f9 --- /dev/null +++ b/src/scene.h @@ -0,0 +1,125 @@ +/*############################################################################## +## Author: Shaun Reed ## +## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ## +## About: Classes for managing objects and data within a scene ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +##############################################################################*/ + +#ifndef QTK_SCENE_H +#define QTK_SCENE_H + +#include +#include +#include +#include + +#include + +namespace Qtk { + /** + * An abstract Scene class to inherit from when building new scenes. + * + * This class provides the following objects to any inheriting scene: + * Skybox, Camera + * This class also provides containers for N instances of these objects: + * MeshRenderers, Models + * + * To inherit from this class and define our own scene we must: + * + * Override and define the `init()` virtual member function. If we want our + * scene to render using a Skybox, we should also initialize the mSkybox + * member within the overridden definition of `init()` using + * `Scene::setSkybox(...)` + * + * If the scene is to render any kind of movement we are required to override + * the `update()` virtual method. + * + * If the child scene adds any objects which are not managed (drawn) by this + * base class, the child scene class must also override the `draw()` method. + */ + class Scene : protected QOpenGLFunctions { + public: + /************************************************************************* + * Contructors / Destructors + ************************************************************************/ + + Scene(); + + ~Scene(); + + /************************************************************************* + * Public Methods + ************************************************************************/ + + /** + * Initialize objects within the scene + */ + virtual void init() = 0; + + /** + * Function called during OpenGL drawing event. + * + * This function is only called when the widget is redrawn. + */ + virtual void draw() { privateDraw(); }; + + /** + * Function called to update the QOpenGLWidget. Does not trigger a redraw. + * + * Calling this several times will still result in only one repaint. + */ + virtual void update() {} + + /************************************************************************* + * Accessors + ************************************************************************/ + + static Camera3D & getCamera() { return mCamera; } + + static QMatrix4x4 getViewMatrix() { return mCamera.toMatrix(); } + + static QMatrix4x4 & getProjectionMatrix() { return mProjection; } + + inline Skybox * getSkybox() { return mSkybox; } + + /************************************************************************* + * Setters + ************************************************************************/ + + inline void setSkybox(Skybox * skybox) { mSkybox = skybox; } + + private: + /************************************************************************* + * Private Members + ************************************************************************/ + + static Camera3D mCamera; + static QMatrix4x4 mProjection; + bool mInit = false; + + /************************************************************************* + * Private Methods + ************************************************************************/ + + /** + * Handles drawing members encapsulated by this base class. + * Child classes do not need to draw these objects manually. + */ + void privateDraw(); + + protected: + /************************************************************************* + * Protected Members + ************************************************************************/ + + /* 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 {}; + }; +} // namespace Qtk + +#endif // QTK_SCENE_H diff --git a/src/skybox.cpp b/src/skybox.cpp index 7088c35..29cda26 100644 --- a/src/skybox.cpp +++ b/src/skybox.cpp @@ -6,7 +6,7 @@ ## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## ##############################################################################*/ -#include +#include #include #include @@ -32,8 +32,8 @@ Skybox::Skybox(const std::string & name) : ":/right.png", ":/top.png", ":/front.png", ":/left.png", ":/bottom.png", ":/back.png", name) {} -Skybox::Skybox(QOpenGLTexture * cubeMap, const std::string & name) : - mTexture(cubeMap) { +Skybox::Skybox(QOpenGLTexture * cubeMap, const std::string & name) { + mTexture.setTexture(cubeMap); init(); } @@ -49,8 +49,8 @@ void Skybox::draw() { mProgram.bind(); mTexture.getOpenGLTexture().bind(); - mProgram.setUniformValue("uProjectionMatrix", Scene::Projection()); - mProgram.setUniformValue("uViewMatrix", Scene::Camera().toMatrix()); + mProgram.setUniformValue("uProjectionMatrix", Scene::getProjectionMatrix()); + mProgram.setUniformValue("uViewMatrix", Scene::getCamera().toMatrix()); mProgram.setUniformValue("uTexture", 0); glDrawElements( GL_TRIANGLES, mIndices.size(), GL_UNSIGNED_INT, mIndices.data()); diff --git a/src/skybox.h b/src/skybox.h index edbace4..6033c19 100644 --- a/src/skybox.h +++ b/src/skybox.h @@ -21,13 +21,23 @@ #include namespace Qtk { + /** + * Skybox object for rendering a skybox within a Scene. + * A skybox is typically implemented using a cube map texture centered around + * the camera and projected outwards in all directions. + */ class QTKAPI Skybox : protected QOpenGLFunctions { public: + /************************************************************************* + * Constructors / Destructors + ************************************************************************/ + // Delegate this constructor to use default skybox images explicit Skybox(const std::string & name = "Skybox"); + explicit Skybox( QOpenGLTexture * cubeMap, const std::string & name = "Skybox"); - // Constructor, Destructor + Skybox( const std::string & right, const std::string & top, const std::string & front, const std::string & left, @@ -36,11 +46,23 @@ namespace Qtk { ~Skybox() = default; + /************************************************************************* + * Public Methods + ************************************************************************/ + void draw(); private: + /************************************************************************* + * Private Methods + ************************************************************************/ + void init(); + /************************************************************************* + * Private Members + ************************************************************************/ + Vertices mVertices {}; Indices mIndices {}; diff --git a/src/texture.cpp b/src/texture.cpp index 72e4b06..5745bf0 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -28,7 +28,7 @@ QImage * OpenGLTextureFactory::initImage( return loadedImage; } -QOpenGLTexture * OpenGLTextureFactory::initTexture2D( +QOpenGLTexture * OpenGLTextureFactory::initTexture( const char * texture, bool flipX, bool flipY) { QImage * image = initImage(texture, flipX, flipY); auto newTexture = new QOpenGLTexture(QOpenGLTexture::Target2D); diff --git a/src/texture.h b/src/texture.h index acf3464..548968f 100644 --- a/src/texture.h +++ b/src/texture.h @@ -9,30 +9,124 @@ #ifndef QTOPENGL_TEXTURE_H #define QTOPENGL_TEXTURE_H +#include #include #include #include namespace Qtk { + /** + * Binds shader programs until the end of scope. + * Does nothing if the shader program was already bound. + * + * See MeshRenderer::setUniform() for example. + */ + class QTKAPI ShaderBindScope { + public: + /************************************************************************* + * Constructors / Destructors + ************************************************************************/ + + explicit ShaderBindScope( + QOpenGLShaderProgram * program, bool was_locked) : + mWasBound(was_locked) { + mProgram = program; + if(!mWasBound) { + mProgram->bind(); + } + } + + ~ShaderBindScope() { + if(!mWasBound) { + mProgram->release(); + } + } + + private: + /************************************************************************* + * Private Members + ************************************************************************/ + + QOpenGLShaderProgram * mProgram; + bool mWasBound; + }; + + /** + * Factories for initializing various OpenGL textures + */ class QTKAPI OpenGLTextureFactory { public: + /************************************************************************* + * Constructors / Destructors + ************************************************************************/ ~OpenGLTextureFactory() = default; - // QImage + /************************************************************************* + * Texture Factories + ************************************************************************/ + + /** + * QImage factory + * + * @param image Path to image we want to load. + * 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. + */ static QImage * initImage( const char * image, bool flipX = false, bool flipY = false); - // 2D Texture - static QOpenGLTexture * initTexture2D( + /** + * QOpenGLTexture factory + * + * @param texture Path to texture we want to load. + * 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 QOpenGLTexture object. + */ + static QOpenGLTexture * initTexture( const char * texture, bool flipX = false, bool flipY = false); - // Cube maps + /** + * Cube map factory for initializing all sides of a CubeMap. + * All of these parameters can be absolute or Qt resource paths. + * + * @param right Path to image for the right side of the CubeMap. + * @param top Path to image for the top side of the CubeMap. + * @param front Path to image for the front side of the CubeMap. + * @param left Path to image for the left side of the CubeMap. + * @param bottom Path to image for the bottom side of the CubeMap. + * @param back Path to image for the back side of the CubeMap. + * @return Pointer to an initialized QOpenGLTexture object. + */ static QOpenGLTexture * initCubeMap( const QImage & right, const QImage & top, const QImage & front, const QImage & left, const QImage & bottom, const QImage & back); - // Overloads for cube map initialization + + /** + * CubeMap factory for tiling the same image on all sides. + * + * @param tile Path to the image we want to make into a CubeMap. + * Can be absolute or Qt resource path. + * @return Pointer to an initialized QOpenGLTexture object. + */ static QOpenGLTexture * initCubeMap(const char * tile); + + /** + * Cube map factory for initializing all sides of a CubeMap. + * All of these parameters can be absolute or Qt resource paths. + * + * @param right Path to image for the right side of the CubeMap. + * @param top Path to image for the top side of the CubeMap. + * @param front Path to image for the front side of the CubeMap. + * @param left Path to image for the left side of the CubeMap. + * @param bottom Path to image for the bottom side of the CubeMap. + * @param back Path to image for the back side of the CubeMap. + * @return Pointer to an initialized QOpenGLTexture object. + */ static QOpenGLTexture * initCubeMap( const char * right, const char * top, const char * front, const char * left, const char * bottom, const char * back); @@ -42,48 +136,66 @@ namespace Qtk { OpenGLTextureFactory() = default; }; - // TODO: Struct for (re)storing texture state + /** + * Texture object component class + * + * TODO: Struct for (re)storing texture state + * A struct to store flipX, flipY and other initial state needed to copy + */ class Texture { public: + /************************************************************************* + * Typedefs + ************************************************************************/ + friend class Skybox; + + /************************************************************************* + * Constructors / Destructors + ************************************************************************/ + Texture() = default; + Texture(const Texture & value) { - mOpenGLTexture = OpenGLTextureFactory::initTexture2D(value.mPath); + mOpenGLTexture = OpenGLTextureFactory::initTexture(value.mPath); mPath = value.mPath; } + explicit Texture( const char * path, bool flipX = false, bool flipY = false) : - mOpenGLTexture( - OpenGLTextureFactory::initTexture2D(path, flipX, flipY)), + mOpenGLTexture(OpenGLTextureFactory::initTexture(path, flipX, flipY)), mPath(path) {} + explicit Texture(QOpenGLTexture * texture) : mOpenGLTexture(texture) {} ~Texture() { mOpenGLTexture->destroy(); } + /************************************************************************* + * Accessors + ************************************************************************/ + [[nodiscard]] inline QOpenGLTexture & getOpenGLTexture() const { return *mOpenGLTexture; } [[nodiscard]] inline std::string getPath() const { return mPath; } + /************************************************************************* + * Setters + ************************************************************************/ + void setTexture( const std::string & path, bool flipX = false, bool flipY = false) { mOpenGLTexture = - OpenGLTextureFactory::initTexture2D(path.data(), flipX, flipY); + OpenGLTextureFactory::initTexture(path.data(), flipX, flipY); mPath = path.data(); } void setTexture( const char * path, bool flipX = false, bool flipY = false) { - mOpenGLTexture = - OpenGLTextureFactory::initTexture2D(path, flipX, flipY); + mOpenGLTexture = OpenGLTextureFactory::initTexture(path, flipX, flipY); mPath = path; } - // TODO: This is unsafe because we don't have a path. Encapsulate it. - inline void setTexture(QOpenGLTexture * texture) { - mOpenGLTexture = texture; - } - virtual inline void setCubeMap(const char * path) { mOpenGLTexture = OpenGLTextureFactory::initCubeMap(path); mPath = path; @@ -103,12 +215,24 @@ namespace Qtk { right, top, front, left, bottom, back); } + /************************************************************************* + * Public Methods + ************************************************************************/ + [[nodiscard]] inline bool hasTexture() const { return mOpenGLTexture != Q_NULLPTR; } private: + /************************************************************************* + * Private Members + ************************************************************************/ + inline void setTexture(QOpenGLTexture * texture) { + mOpenGLTexture = texture; + } + QOpenGLTexture * mOpenGLTexture = Q_NULLPTR; + /* Path to this texture on disk or Qt resource. */ const char * mPath {}; }; diff --git a/src/transform3D.h b/src/transform3D.h index 974aabd..81c815d 100644 --- a/src/transform3D.h +++ b/src/transform3D.h @@ -23,15 +23,22 @@ #include namespace Qtk { + /** + * Transform3D class to represent and modify object position in 3D space. + */ class QTKAPI Transform3D { public: - // Constructors + /************************************************************************* + * Constructors, Destructors + ************************************************************************/ + inline Transform3D() : m_dirty(true), mScale(1.0f, 1.0f, 1.0f), mTranslation(0.0f, 0.0f, 0.0f) {} - // - // Transformations + /************************************************************************* + * Transformations + ************************************************************************/ void translate(const QVector3D & dt); @@ -72,8 +79,9 @@ namespace Qtk { grow(QVector3D(factor, factor, factor)); } - // - // Setters + /************************************************************************* + * Setters + ************************************************************************/ // Set object position void setTranslation(const QVector3D & t); @@ -102,8 +110,9 @@ namespace Qtk { setRotation(QQuaternion::fromAxisAndAngle(ax, ay, az, angle)); } - // - // Accessors + /************************************************************************* + * Getters + ************************************************************************/ [[nodiscard]] inline const QVector3D & getTranslation() const { return mTranslation; @@ -121,9 +130,17 @@ namespace Qtk { [[nodiscard]] QVector3D getUp() const; [[nodiscard]] QVector3D getRight() const; + /************************************************************************* + * Public members + ************************************************************************/ + static const QVector3D LocalForward, LocalUp, LocalRight; private: + /************************************************************************* + * Private members + ************************************************************************/ + QVector3D mTranslation; QQuaternion mRotation; QVector3D mScale;