diff --git a/.gitmodules b/.gitmodules index 79f4a6f..eba892e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,18 @@ [submodule "magnum"] path = magnum url = https://github.com/mosra/magnum.git +[submodule "entt"] + path = entt + url = https://github.com/skypjack/entt.git +[submodule "magnum-integration"] + path = magnum-integration + url = https://github.com/mosra/magnum-integration.git +[submodule "imgui"] + path = imgui + url = https://github.com/ocornut/imgui.git +[submodule "magnum-plugins"] + path = magnum-plugins + url = https://github.com/mosra/magnum-plugins.git +[submodule "ImGuiFileDialog"] + path = ImGuiFileDialog + url = https://github.com/aiekick/ImGuiFileDialog.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 552fa55..641223d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,20 @@ add_subdirectory(corrade EXCLUDE_FROM_ALL) # Add Magnum as a subproject, enable Sdl2Application set(CMAKE_PREFIX_PATH ${PROJECT_SOURCE_DIR}/SDL2-2.32.6 ${CMAKE_PREFIX_PATH}) +set(MAGNUM_WITH_ANYIMAGEIMPORTER ON CACHE BOOL "" FORCE) +set(MAGNUM_WITH_ANYSCENEIMPORTER ON CACHE BOOL "" FORCE) +set(MAGNUM_WITH_OBJIMPORTER ON CACHE BOOL "" FORCE) set(MAGNUM_WITH_SDL2APPLICATION ON CACHE BOOL "" FORCE) add_subdirectory(magnum EXCLUDE_FROM_ALL) +set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/imgui) +set(MAGNUM_WITH_IMGUIINTEGRATION ON CACHE BOOL "" FORCE) +add_subdirectory(magnum-integration EXCLUDE_FROM_ALL) + +set(MAGNUM_WITH_STBIMAGEIMPORTER ON CACHE BOOL "" FORCE) +set(MAGNUM_WITH_GLTFIMPORTER ON CACHE BOOL "" FORCE) +add_subdirectory(magnum-plugins EXCLUDE_FROM_ALL) + +add_subdirectory(entt) + add_subdirectory(src) diff --git a/ImGuiFileDialog b/ImGuiFileDialog new file mode 160000 index 0000000..e763838 --- /dev/null +++ b/ImGuiFileDialog @@ -0,0 +1 @@ +Subproject commit e76383898501dab0d0fddc23a1ccbfb46105f5cd diff --git a/entt b/entt new file mode 160000 index 0000000..b4e58bd --- /dev/null +++ b/entt @@ -0,0 +1 @@ +Subproject commit b4e58bdd364ad72246c123a0c28538eab3252672 diff --git a/imgui b/imgui new file mode 160000 index 0000000..960921f --- /dev/null +++ b/imgui @@ -0,0 +1 @@ +Subproject commit 960921f03a2bf628a802b4611af9fc3c3c9430fe diff --git a/magnum-integration b/magnum-integration new file mode 160000 index 0000000..0184c83 --- /dev/null +++ b/magnum-integration @@ -0,0 +1 @@ +Subproject commit 0184c8371d499d4a4f20b5982722f7942ff6f364 diff --git a/magnum-plugins b/magnum-plugins new file mode 160000 index 0000000..0d212b5 --- /dev/null +++ b/magnum-plugins @@ -0,0 +1 @@ +Subproject commit 0d212b5698297607314730dd415182c14f0f81b3 diff --git a/modules/FindMagnumIntegration.cmake b/modules/FindMagnumIntegration.cmake new file mode 100644 index 0000000..bb7b195 --- /dev/null +++ b/modules/FindMagnumIntegration.cmake @@ -0,0 +1,456 @@ +#.rst: +# Find Magnum integration library +# ------------------------------- +# +# Finds the Magnum integration library. Basic usage:: +# +# find_package(MagnumIntegration REQUIRED) +# +# This command tries to find Magnum integration library and then defines the +# following: +# +# MagnumIntegration_FOUND - Whether the library was found +# +# This command alone is useless without specifying the components: +# +# Bullet - Bullet Physics integration library +# Dart - Dart Physics integration library +# Eigen - Eigen integration library +# Glm - GLM integration library +# ImGui - ImGui integration library +# Ovr - Oculus SDK integration library +# Yoga - Yoga Layout integration library +# +# Example usage with specifying additional components is: +# +# find_package(MagnumIntegration REQUIRED Bullet) +# +# For each component is then defined: +# +# MagnumIntegration_*_FOUND - Whether the component was found +# MagnumIntegration::* - Component imported target +# +# The package is found if either debug or release version of each requested +# library is found. If both debug and release libraries are found, proper +# version is chosen based on actual build configuration of the project (i.e. +# Debug build is linked to debug libraries, Release build to release +# libraries). +# +# Additionally these variables are defined for internal usage: +# +# MAGNUMINTEGRATION_INCLUDE_DIR - Magnum integration include dir (w/o +# dependencies) +# MAGNUMINTEGRATION_*_LIBRARY_DEBUG - Debug version of given library, if found +# MAGNUMINTEGRATION_*_LIBRARY_RELEASE - Release version of given library, if +# found +# + +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, +# 2020, 2021, 2022, 2023, 2024, 2025, 2026 +# Vladimír Vondruš +# Copyright © 2018 Konstantinos Chatzilygeroudis +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +# Magnum library dependencies +set(_MAGNUMINTEGRATION_MAGNUM_DEPENDENCIES ) +set(_MAGNUMINTEGRATION_MAGNUMEXTRAS_DEPENDENCIES ) +foreach(_component ${MagnumIntegration_FIND_COMPONENTS}) + if(_component STREQUAL Bullet) + set(_MAGNUMINTEGRATION_${_component}_MAGNUM_DEPENDENCIES SceneGraph Shaders GL) + elseif(_component STREQUAL Dart) + set(_MAGNUMINTEGRATION_${_component}_MAGNUM_DEPENDENCIES SceneGraph Primitives MeshTools GL) + elseif(_component STREQUAL ImGui) + set(_MAGNUMINTEGRATION_${_component}_MAGNUM_DEPENDENCIES GL Shaders) + elseif(_component STREQUAL Yoga) + set(_MAGNUMINTEGRATION_${_component}_MAGNUM_DEPENDENCIES GL Shaders) + set(_MAGNUMINTEGRATION_${_component}_MAGNUMEXTRAS_DEPENDENCIES Ui) + endif() + + list(APPEND _MAGNUMINTEGRATION_MAGNUM_DEPENDENCIES ${_MAGNUMINTEGRATION_${_component}_MAGNUM_DEPENDENCIES}) + list(APPEND _MAGNUMINTEGRATION_MAGNUMEXTRAS_DEPENDENCIES ${_MAGNUMINTEGRATION_${_component}_MAGNUMEXTRAS_DEPENDENCIES}) +endforeach() +find_package(Magnum REQUIRED ${_MAGNUMINTEGRATION_MAGNUM_DEPENDENCIES}) +if(_MAGNUMINTEGRATION_MAGNUMEXTRAS_DEPENDENCIES) + find_package(MagnumExtras REQUIRED ${_MAGNUMINTEGRATION_MAGNUMEXTRAS_DEPENDENCIES}) +endif() + +# Global include dir that's unique to Magnum Integration. Often it will be +# installed alongside Magnum, which is why the hint, but if not, it shouldn't +# just pick MAGNUM_INCLUDE_DIR because then _MAGNUMINTEGRATION_*_INCLUDE_DIR +# will fail to be found. In case of CMake subprojects the versionIntegration.h +# is generated inside the build dir so this won't find it, instead +# src/CMakeLists.txt forcibly sets MAGNUMINTEGRATION_INCLUDE_DIR as an internal +# cache value to make that work. +find_path(MAGNUMINTEGRATION_INCLUDE_DIR Magnum/versionIntegration.h + HINTS ${MAGNUM_INCLUDE_DIR}) +mark_as_advanced(MAGNUMINTEGRATION_INCLUDE_DIR) + +# CMake module dir for dependencies. It might not be present at all if no +# feature that needs them is enabled, in which case it'll be left at NOTFOUND. +# But in that case it should also not be subsequently needed for any +# find_package(). If this is called from a superproject, the +# _MAGNUMINTEGRATION_DEPENDENCY_MODULE_DIR is already set by +# modules/CMakeLists.txt. +find_path(_MAGNUMINTEGRATION_DEPENDENCY_MODULE_DIR + NAMES + FindBullet.cmake FindGLM.cmake FindImGui.cmake FindOVR.cmake + PATH_SUFFIXES share/cmake/MagnumIntegration/dependencies) +mark_as_advanced(_MAGNUMINTEGRATION_DEPENDENCY_MODULE_DIR) + +# If the module dir is found and is not present in CMAKE_MODULE_PATH already +# (such as when someone explicitly added it, or if it's the Magnum's modules/ +# dir in case of a superproject), add it as the first before all other. Set a +# flag to remove it again at the end, so the modules don't clash with Find +# modules of the same name from other projects. +if(_MAGNUMINTEGRATION_DEPENDENCY_MODULE_DIR AND NOT _MAGNUMINTEGRATION_DEPENDENCY_MODULE_DIR IN_LIST CMAKE_MODULE_PATH) + set(CMAKE_MODULE_PATH ${_MAGNUMINTEGRATION_DEPENDENCY_MODULE_DIR} ${CMAKE_MODULE_PATH}) + set(_MAGNUMINTEGRATION_REMOVE_DEPENDENCY_MODULE_DIR_FROM_CMAKE_PATH ON) +else() + unset(_MAGNUMINTEGRATION_REMOVE_DEPENDENCY_MODULE_DIR_FROM_CMAKE_PATH) +endif() + +# Component distinction (listing them explicitly to avoid mistakes with finding +# components from other repositories) +set(_MAGNUMINTEGRATION_LIBRARY_COMPONENTS Bullet Dart Eigen ImGui Glm Yoga) +if(CORRADE_TARGET_WINDOWS) + list(APPEND _MAGNUMINTEGRATION_LIBRARY_COMPONENTS Ovr) +endif() +set(_MAGNUMINTEGRATION_HEADER_ONLY_COMPONENTS Eigen) +# Nothing is enabled by default right now +set(_MAGNUMINTEGRATION_IMPLICITLY_ENABLED_COMPONENTS ) + +# Inter-component dependencies (none yet) +# set(_MAGNUMINTEGRATION_Component_DEPENDENCIES Dependency) + +# Ensure that all inter-component dependencies are specified as well +set(_MAGNUMINTEGRATION_ADDITIONAL_COMPONENTS ) +foreach(_component ${MagnumIntegration_FIND_COMPONENTS}) + # Mark the dependencies as required if the component is also required + if(MagnumIntegration_FIND_REQUIRED_${_component}) + foreach(_dependency ${_MAGNUMINTEGRATION_${_component}_DEPENDENCIES}) + set(MagnumIntegration_FIND_REQUIRED_${_dependency} TRUE) + endforeach() + endif() + + list(APPEND _MAGNUMINTEGRATION_ADDITIONAL_COMPONENTS ${_MAGNUMINTEGRATION_${_component}_DEPENDENCIES}) +endforeach() + +# Join the lists, remove duplicate components +set(_MAGNUMINTEGRATION_ORIGINAL_FIND_COMPONENTS ${MagnumIntegration_FIND_COMPONENTS}) +if(_MAGNUMINTEGRATION_ADDITIONAL_COMPONENTS) + list(INSERT MagnumIntegration_FIND_COMPONENTS 0 ${_MAGNUMINTEGRATION_ADDITIONAL_COMPONENTS}) +endif() +if(MagnumIntegration_FIND_COMPONENTS) + list(REMOVE_DUPLICATES MagnumIntegration_FIND_COMPONENTS) +endif() + +# Special cases of include paths for header-only libraries. Libraries not +# listed here have a path suffix and include name derived from the library name +# in the loop below. Non-header-only libraries have a configure.h file. +set(_MAGNUMINTEGRATION_EIGEN_INCLUDE_PATH_NAMES GeometryIntegration.h) + +# Find all components +foreach(_component ${MagnumIntegration_FIND_COMPONENTS}) + string(TOUPPER ${_component} _COMPONENT) + + # Create imported target in case the library is found. If the project is + # added as subproject to CMake, the target already exists and all the + # required setup is already done from the build tree. + if(TARGET "MagnumIntegration::${_component}") # Quotes to fix KDE's hiliter + set(MagnumIntegration_${_component}_FOUND TRUE) + else() + # Find library include dir for header-only libraries + if(_component IN_LIST _MAGNUMINTEGRATION_HEADER_ONLY_COMPONENTS) + # Include path names to find, unless specified above + if(NOT _MAGNUMINTEGRATION_${_COMPONENT}_INCLUDE_PATH_NAMES) + set(_MAGNUMINTEGRATION_${_COMPONENT}_INCLUDE_PATH_NAMES ${_component}Integration.h) + endif() + + find_path(_MAGNUMINTEGRATION_${_COMPONENT}_INCLUDE_DIR + NAMES ${_MAGNUMINTEGRATION_${_COMPONENT}_INCLUDE_PATH_NAMES} + HINTS ${MAGNUMINTEGRATION_INCLUDE_DIR}/Magnum/${_component}Integration) + mark_as_advanced(_MAGNUMINTEGRATION_${_COMPONENT}_CONFIGURE_FILE) + + # Non-header-only libraries have a configure file which we need to + # subsequently read, so find that one directly + elseif(_component IN_LIST _MAGNUMINTEGRATION_LIBRARY_COMPONENTS) + find_file(_MAGNUMINTEGRATION_${_COMPONENT}_CONFIGURE_FILE configure.h + HINTS ${MAGNUMINTEGRATION_INCLUDE_DIR}/Magnum/${_component}Integration) + mark_as_advanced(_MAGNUMINTEGRATION_${_COMPONENT}_CONFIGURE_FILE) + endif() + + # Library components + if(_component IN_LIST _MAGNUMINTEGRATION_LIBRARY_COMPONENTS AND NOT _component IN_LIST _MAGNUMINTEGRATION_HEADER_ONLY_COMPONENTS) + # Try to find both debug and release version + find_library(MAGNUMINTEGRATION_${_COMPONENT}_LIBRARY_DEBUG Magnum${_component}Integration-d) + find_library(MAGNUMINTEGRATION_${_COMPONENT}_LIBRARY_RELEASE Magnum${_component}Integration) + mark_as_advanced(MAGNUMINTEGRATION_${_COMPONENT}_LIBRARY_DEBUG + MAGNUMINTEGRATION_${_COMPONENT}_LIBRARY_RELEASE) + + # Determine if the library is static or dynamic by reading the + # per-library config file. If the file wasn't found, skip this so + # it fails on the FPHSA below and not right here. + if(_MAGNUMINTEGRATION_${_COMPONENT}_CONFIGURE_FILE) + file(READ ${_MAGNUMINTEGRATION_${_COMPONENT}_CONFIGURE_FILE} _magnumIntegrationConfigure) + string(REGEX REPLACE ";" "\\\\;" _magnumIntegrationConfigure "${_magnumIntegrationConfigure}") + string(REGEX REPLACE "\n" ";" _magnumIntegrationConfigure "${_magnumIntegrationConfigure}") + list(FIND _magnumIntegrationConfigure "#define MAGNUM_${_COMPONENT}INTEGRATION_BUILD_STATIC" _magnumIntegrationBuildStatic) + if(NOT _magnumIntegrationBuildStatic EQUAL -1) + # The variable is inconsistently named between C++ and + # CMake, so keep it underscored / private + set(_MAGNUMINTEGRATION_${_COMPONENT}_BUILD_STATIC ON) + endif() + endif() + + # On Windows, if we have a dynamic build of given library, find the + # DLLs as well. Abuse find_program() since the DLLs should be + # alongside usual executables. On MinGW they however have a lib + # prefix. + if(CORRADE_TARGET_WINDOWS AND NOT _MAGNUMINTEGRATION_${_COMPONENT}_BUILD_STATIC) + find_program(MAGNUMINTEGRATION_${_COMPONENT}_DLL_DEBUG ${CMAKE_SHARED_LIBRARY_PREFIX}Magnum${_component}Integration-d.dll) + find_program(MAGNUMINTEGRATION_${_COMPONENT}_DLL_RELEASE ${CMAKE_SHARED_LIBRARY_PREFIX}Magnum${_component}Integration.dll) + mark_as_advanced(MAGNUMINTEGRATION_${_COMPONENT}_DLL_DEBUG + MAGNUMINTEGRATION_${_COMPONENT}_DLL_RELEASE) + # If not on Windows or on a static build, unset the DLL variables + # to avoid leaks when switching shared and static builds + else() + unset(MAGNUMINTEGRATION_${_COMPONENT}_DLL_DEBUG CACHE) + unset(MAGNUMINTEGRATION_${_COMPONENT}_DLL_RELEASE CACHE) + endif() + + # If not a header-only component it's something unknown, skip. FPHSA + # will take care of handling this below. + elseif(NOT _component IN_LIST _MAGNUMINTEGRATION_HEADER_ONLY_COMPONENTS) + continue() + endif() + + # Decide if the library was found. If not, skip the rest, which + # populates the target properties and finds additional dependencies. + # This means that the rest can also rely on that e.g. FindGLM.cmake is + # present in _MAGNUMPLUGINS_DEPENDENCY_MODULE_DIR -- given that the + # library needing GLM was found, it likely also installed FindGLM for + # itself. + if( + # If the component is a header-only library it should have an + # include dir + (_component IN_LIST _MAGNUMINTEGRATION_HEADER_ONLY_COMPONENTS AND _MAGNUMINTEGRATION_${_COMPONENT}_INCLUDE_DIR) OR + # Or, if it's a real library, it should have a configure file + (_component IN_LIST _MAGNUMINTEGRATION_LIBRARY_COMPONENTS AND _MAGNUMINTEGRATION_${_COMPONENT}_CONFIGURE_FILE AND ( + # Or have a debug library, and a DLL found if expected + (MAGNUMINTEGRATION_${_COMPONENT}_LIBRARY_DEBUG AND ( + NOT DEFINED MAGNUMINTEGRATION_${_COMPONENT}_DLL_DEBUG OR + MAGNUMINTEGRATION_${_COMPONENT}_DLL_DEBUG)) OR + # Or have a release library, and a DLL found if expected + (MAGNUMINTEGRATION_${_COMPONENT}_LIBRARY_RELEASE AND ( + NOT DEFINED MAGNUMINTEGRATION_${_COMPONENT}_DLL_RELEASE OR + MAGNUMINTEGRATION_${_COMPONENT}_DLL_RELEASE)))) + ) + set(MagnumIntegration_${_component}_FOUND TRUE) + else() + set(MagnumIntegration_${_component}_FOUND FALSE) + continue() + endif() + + # Target for header-only library components + if(_component IN_LIST _MAGNUMINTEGRATION_HEADER_ONLY_COMPONENTS) + add_library(MagnumIntegration::${_component} INTERFACE IMPORTED) + + # Target and location for libraries + elseif(_component IN_LIST _MAGNUMINTEGRATION_LIBRARY_COMPONENTS) + if(_MAGNUMINTEGRATION_${_COMPONENT}_BUILD_STATIC) + add_library(MagnumIntegration::${_component} STATIC IMPORTED) + else() + add_library(MagnumIntegration::${_component} SHARED IMPORTED) + endif() + + foreach(_CONFIG DEBUG RELEASE) + if(NOT MAGNUMINTEGRATION_${_COMPONENT}_LIBRARY_${_CONFIG}) + continue() + endif() + + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + IMPORTED_CONFIGURATIONS ${_CONFIG}) + # Unfortunately for a DLL the two properties are swapped out, + # *.lib goes to IMPLIB, so it's duplicated like this + if(DEFINED MAGNUMINTEGRATION_${_COMPONENT}_DLL_${_CONFIG}) + # Quotes to "fix" KDE's higlighter + set_target_properties("MagnumIntegration::${_component}" PROPERTIES + IMPORTED_LOCATION_${_CONFIG} ${MAGNUMINTEGRATION_${_COMPONENT}_DLL_${_CONFIG}} + IMPORTED_IMPLIB_${_CONFIG} ${MAGNUMINTEGRATION_${_COMPONENT}_LIBRARY_${_CONFIG}}) + else() + set_property(TARGET MagnumIntegration::${_component} PROPERTY + IMPORTED_LOCATION_${_CONFIG} ${MAGNUMINTEGRATION_${_COMPONENT}_LIBRARY_${_CONFIG}}) + endif() + endforeach() + endif() + + # Bullet integration library + if(_component STREQUAL Bullet) + # On Emscripten, Bullet could be taken from ports. If that's the + # case, propagate proper compiler flag. + if(CORRADE_TARGET_EMSCRIPTEN) + # The library-specific configure file was read above already + list(FIND _magnumIntegrationConfigure "#define MAGNUM_USE_EMSCRIPTEN_PORTS_BULLET" _magnum${_component}Integration_USE_EMSCRIPTEN_PORTS_BULLET) + if(NOT _magnum${_component}Integration_USE_EMSCRIPTEN_PORTS_BULLET EQUAL -1) + set(MAGNUM_USE_EMSCRIPTEN_PORTS_BULLET 1) + endif() + endif() + + if(MAGNUM_USE_EMSCRIPTEN_PORTS_BULLET) + if(CMAKE_VERSION VERSION_LESS 3.13) + message(FATAL_ERROR "${_component}Integration was compiled against an emscripten-ports version of Bullet but linking to it requires CMake 3.13 at least") + endif() + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_COMPILE_OPTIONS "SHELL:-s USE_BULLET=1") + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_OPTIONS "SHELL:-s USE_BULLET=1") + else() + find_package(Bullet) + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES Bullet::LinearMath) + endif() + + # Eigen integration library + elseif(_component STREQUAL Eigen) + find_package(Eigen3) + # We could drop this once we can use at least 3.3.1 (Ubuntu 16.04 + # has only 3.3 beta, which doesn't have this target yet), however + # for Travis and AppVeyor we're using FindEigen3.cmake from the + # downloaded sources (because the Eigen3Config.cmake, which + # produces the actual targets, is not there -- only + # Eigen3Config.cmake.in). See the YML files for an extended rant. + # Also, FindEigen3 only defines EIGEN3_INCLUDE_DIR, not even + # EIGEN3_INCLUDE_DIRS, so be extra careful. + # https://eigen.tuxfamily.org/index.php?title=ChangeLog#Eigen_3.3.1 + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_INCLUDE_DIRECTORIES ${EIGEN3_INCLUDE_DIR}) + + # ImGui integration library + elseif(_component STREQUAL ImGui) + find_package(ImGui) + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES ImGui::ImGui) + + # GLM integration library + elseif(_component STREQUAL Glm) + find_package(GLM) + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES GLM::GLM) + + # Dart integration library + elseif(_component STREQUAL Dart) + find_package(DART 6.0.0 CONFIG REQUIRED) + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES dart) + + # Oculus SDK integration library + elseif(_component STREQUAL Ovr) + find_package(OVR) + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES OVR::OVR) + + # Yoga integration library + elseif(_component STREQUAL Yoga) + # Since 2.0.0 the project provides a CMake config file, force it. + # Before 2.0 it didn't even have an install target, so assume those + # versions just aren't used at all. + find_package(yoga CONFIG REQUIRED) + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES yoga::yogacore) + endif() + + if(_component IN_LIST _MAGNUMINTEGRATION_LIBRARY_COMPONENTS) + # Link to core Magnum library, add other Magnum required and + # optional dependencies + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES Magnum::Magnum) + foreach(_dependency ${_MAGNUMINTEGRATION_${_component}_MAGNUM_DEPENDENCIES}) + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES Magnum::${_dependency}) + endforeach() + foreach(_dependency ${_MAGNUMINTEGRATION_${_component}_MAGNUMEXTRAS_DEPENDENCIES}) + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES MagnumExtras::${_dependency}) + endforeach() + foreach(_dependency ${_MAGNUMINTEGRATION_${_component}_MAGNUM_OPTIONAL_DEPENDENCIES}) + if(Magnum_${_dependency}_FOUND) + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES Magnum::${_dependency}) + endif() + endforeach() + + # Add inter-project dependencies + foreach(_dependency ${_MAGNUMINTEGRATION_${_component}_DEPENDENCIES}) + set_property(TARGET MagnumIntegration::${_component} APPEND PROPERTY + INTERFACE_LINK_LIBRARIES MagnumIntegration::${_dependency}) + endforeach() + endif() + endif() +endforeach() + +# For CMake 3.16+ with REASON_FAILURE_MESSAGE, provide additional potentially +# useful info about the failed components. +if(NOT CMAKE_VERSION VERSION_LESS 3.16) + set(_MAGNUMINTEGRATION_REASON_FAILURE_MESSAGE ) + # Go only through the originally specified find_package() components, not + # the dependencies added by us afterwards + foreach(_component ${_MAGNUMINTEGRATION_ORIGINAL_FIND_COMPONENTS}) + if(MagnumIntegration_${_component}_FOUND) + continue() + endif() + + # If it's not known at all, tell the user -- it might be a new library + # and an old Find module, or something platform-specific. + if(NOT _component IN_LIST _MAGNUMINTEGRATION_LIBRARY_COMPONENTS) + list(APPEND _MAGNUMINTEGRATION_REASON_FAILURE_MESSAGE "${_component} is not a known component on this platform.") + # Otherwise, if it's not among implicitly built components, hint that + # the user may need to enable it + # TODO: currently, the _FOUND variable doesn't reflect if dependencies + # were found. When it will, this needs to be updated to avoid + # misleading messages. + elseif(NOT _component IN_LIST _MAGNUMINTEGRATION_IMPLICITLY_ENABLED_COMPONENTS) + string(TOUPPER ${_component} _COMPONENT) + list(APPEND _MAGNUMINTEGRATION_REASON_FAILURE_MESSAGE "${_component} is not built by default. Make sure you enabled MAGNUM_WITH_${_COMPONENT}INTEGRATION when building Magnum Integration.") + # Otherwise we have no idea. Better be silent than to print something + # misleading. + else() + endif() + endforeach() + + string(REPLACE ";" " " _MAGNUMINTEGRATION_REASON_FAILURE_MESSAGE "${_MAGNUMINTEGRATION_REASON_FAILURE_MESSAGE}") + set(_MAGNUMINTEGRATION_REASON_FAILURE_MESSAGE REASON_FAILURE_MESSAGE "${_MAGNUMINTEGRATION_REASON_FAILURE_MESSAGE}") +endif() + +# Remove Magnum Integration dependency module dir from CMAKE_MODULE_PATH again. +# Do it before the FPHSA call which may exit early in case of a failure. +if(_MAGNUMINTEGRATION_REMOVE_DEPENDENCY_MODULE_DIR_FROM_CMAKE_PATH) + list(REMOVE_ITEM CMAKE_MODULE_PATH ${_MAGNUMINTEGRATION_DEPENDENCY_MODULE_DIR}) + unset(_MAGNUMINTEGRATION_REMOVE_DEPENDENCY_MODULE_DIR_FROM_CMAKE_PATH) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MagnumIntegration + REQUIRED_VARS MAGNUMINTEGRATION_INCLUDE_DIR + HANDLE_COMPONENTS + ${_MAGNUMINTEGRATION_REASON_FAILURE_MESSAGE}) diff --git a/resources/resources.conf b/resources/resources.conf new file mode 100644 index 0000000..3a9135d --- /dev/null +++ b/resources/resources.conf @@ -0,0 +1,4 @@ +group=texture-resources + +[file] +filename=stone.tga \ No newline at end of file diff --git a/resources/stone.tga b/resources/stone.tga new file mode 100644 index 0000000..4b30cd5 Binary files /dev/null and b/resources/stone.tga differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bd0828b..40a76fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,12 +1,55 @@ -find_package(Magnum REQUIRED GL Sdl2Application) +find_package(Corrade REQUIRED Main) +find_package(Magnum REQUIRED + GL + MeshTools + Shaders + SceneGraph + Trade + Sdl2Application) +find_package(MagnumIntegration REQUIRED ImGui) set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON) -add_executable(${PROJECT_NAME} Chocolate.cpp) +add_executable(${PROJECT_NAME} WIN32 + Chocolate.h + Chocolate.cpp + Camera.h + Camera.cpp + MagnumDrawable.h + MagnumDrawable.cpp + Common.h + ${PROJECT_SOURCE_DIR}/ImGuiFileDialog/ImGuiFileDialog.cpp + Components.cpp + Components.h + Scene.cpp + Scene.h + Materials.h + UI.cpp + UI.h +) + +target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/ImGuiFileDialog) + +add_dependencies(${PROJECT_NAME} + Magnum::AnyImageImporter + Magnum::AnySceneImporter + Magnum::ObjImporter + MagnumPlugins::StbImageImporter + MagnumPlugins::GltfImporter +) + target_link_libraries(${PROJECT_NAME} PRIVATE + Corrade::Main Magnum::Application Magnum::GL - Magnum::Magnum) + Magnum::Magnum + Magnum::MeshTools + Magnum::SceneGraph + Magnum::Shaders + Magnum::Trade + EnTT::EnTT + MagnumIntegration::ImGui +) # Make the executable a default target to build & run in Visual Studio set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 0000000..2f274ff --- /dev/null +++ b/src/Camera.cpp @@ -0,0 +1,42 @@ +#include "Camera.h" + +Camera::Camera(Scene3D& scene, const float aspectRatio, const float near, const float far) +{ + _cameraObject + .setParent(&scene) + .translate(Vector3::zAxis(5.0f)); + (_camera = new SceneGraph::Camera3D{_cameraObject}) + ->setAspectRatioPolicy(SceneGraph::AspectRatioPolicy::Extend) + .setProjectionMatrix( + Matrix4::perspectiveProjection(35.0_degf, aspectRatio, near, far) + ) + .setViewport(GL::defaultFramebuffer.viewport().size()); +} + +void Camera::Init(Scene3D& scene, float aspectRatio, float near, float far) +{ + _cameraObject + .setParent(&scene); + + (_camera = new SceneGraph::Camera3D{_cameraObject}) + ->setAspectRatioPolicy(SceneGraph::AspectRatioPolicy::Extend) + .setProjectionMatrix( + Matrix4::perspectiveProjection(35.0_degf, aspectRatio, near, far) + ) + .setViewport(GL::defaultFramebuffer.viewport().size()); +} + +void Camera::Draw(SceneGraph::DrawableGroup3D& _drawables) const +{ + _camera->draw(_drawables); +} + +void Camera::SetViewport(const Vector2i& size) const +{ + _camera->setViewport(size); +} + +void Camera::Translate(const Math::Vector3& vector) +{ + _cameraObject.translate(vector); +} \ No newline at end of file diff --git a/src/Camera.h b/src/Camera.h new file mode 100644 index 0000000..e379ae0 --- /dev/null +++ b/src/Camera.h @@ -0,0 +1,32 @@ +#ifndef CHOCOLATE_CAMERA_H +#define CHOCOLATE_CAMERA_H + +#include +#include +#include +#include +#include + +#include "Common.h" + +using namespace Magnum; +using namespace Math::Literals; + +class Camera +{ +public: + explicit Camera(): _camera(nullptr) {} + explicit Camera(Scene3D& scene, float aspectRatio, float near, float far); + + void Init(Scene3D& scene, float aspectRatio, float near, float far); + void Draw(SceneGraph::DrawableGroup3D& _drawables) const; + void SetViewport(const Vector2i& size) const; + void Translate(const Math::Vector3& vector); +private: + Object3D _cameraObject; + SceneGraph::Camera3D* _camera; +}; + + + +#endif //CHOCOLATE_CAMERA_H \ No newline at end of file diff --git a/src/Chocolate.cpp b/src/Chocolate.cpp index cbf8c8f..3034997 100644 --- a/src/Chocolate.cpp +++ b/src/Chocolate.cpp @@ -1,26 +1,395 @@ -#include -#include +#include "Chocolate.h" +#include "MagnumDrawable.h" +#include "Components.h" -using namespace Magnum; +#include +#include +#include +#include +#include +#include +#include +#include +#include -class Chocolate: public Platform::Application { - public: - explicit Chocolate(const Arguments& arguments); +#include "Magnum/ShaderTools/Stage.h" +#include "Magnum/Trade/ObjectData2D.h" +#include - private: - void drawEvent() override; -}; +Chocolate::Chocolate(const Arguments& arguments) : + Platform::Application{ + arguments, Configuration{} + .setTitle("Chocolate") + .setSize({1920, 1080}) + .setWindowFlags(Configuration::WindowFlag::Resizable) + } +{ + _imgui = ImGuiIntegration::Context(Vector2{windowSize()}/dpiScaling(), + windowSize(), framebufferSize()); -Chocolate::Chocolate(const Arguments& arguments): Platform::Application{arguments} { - /* TODO: Add your initialization code here */ + /* Setup renderer and shader defaults */ + GL::Renderer::enable(GL::Renderer::Feature::DepthTest); + GL::Renderer::enable(GL::Renderer::Feature::FaceCulling); + + GL::Renderer::setBlendEquation(GL::Renderer::BlendEquation::Add, + GL::Renderer::BlendEquation::Add); + GL::Renderer::setBlendFunction(GL::Renderer::BlendFunction::SourceAlpha, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + _camera.Init(_scene, 1.f, 0.01f, 1000.f); + _camera.Translate(Vector3::zAxis(10.f)); + + _materials["matte_rubber"] = PhongMaterialConfig{ + { 0.02, 0.02, 0.02, 1.f}, + {0.1, 0.1, 0.1, 1.f}, + {0.05, 0.05, 0.05, 1.f}, + 2.f + }; + + const auto viewportSize = GL::defaultFramebuffer.viewport().size(); + + Vector2i renderWindowSize = {static_cast(viewportSize.x() * (1 - _sidebarWidth)), viewportSize.y()}; + Vector2i renderWindowPosition = {static_cast(viewportSize.x() * _sidebarWidth), 0}; + _uiSceneRenderer = UI::RenderWindow(renderWindowSize, renderWindowPosition); + _uiComponents.push_back(&_uiSceneRenderer); + + Vector2i entityListPanelSize = {static_cast(viewportSize.x() * _sidebarWidth), viewportSize.y() / 2}; + Vector2i entityListPanelPosition = {0, 0}; + _uiEntityListPanel = UI::EntityListPanel(entityListPanelSize, entityListPanelPosition, &_registry, + [this](const std::string& file) + { + Import(file); + }, + [this](entt::entity entitySelected) + { + EntitySelected(entitySelected); + }); + _uiComponents.push_back(&_uiEntityListPanel); + + Vector2i entityEditorWindowSize = {static_cast(viewportSize.x() * _sidebarWidth), viewportSize.y() / 2}; + Vector2i entityEditorWindowPosition = {0, viewportSize.y() / 2}; + _uiEntityEditorWindow = UI::EntityEditorWindow(entityEditorWindowSize, entityEditorWindowPosition, &_registry); + _uiComponents.push_back(&_uiEntityEditorWindow); +} + +void Chocolate::tickEvent() +{ + UpdateMagnumObjects(); } void Chocolate::drawEvent() { - GL::defaultFramebuffer.clear(GL::FramebufferClear::Color); - /* TODO: Add your drawing code here */ + RenderToFramebuffer(_uiSceneRenderer.Framebuffer()); + + DrawUI(); swapBuffers(); + redraw(); } -MAGNUM_APPLICATION_MAIN(Chocolate) +void Chocolate::UpdateMagnumObjects() +{ + for (const auto entity : _registry.view()) + { + const auto obj = _registry.get(entity).obj; + if (const auto transform = _registry.try_get(entity)) + { + Matrix4 matrix = Matrix4::translation(transform->translation) * Matrix4{transform->rotation.toMatrix()} * Matrix4::scaling(transform->scale); + obj->setTransformation(matrix); + } + } +} + +void Chocolate::viewportEvent(ViewportEvent& event) { + GL::defaultFramebuffer.setViewport({{}, event.framebufferSize()}); + + _camera.SetViewport(event.windowSize()); + + _imgui.relayout(Vector2{event.windowSize()}/event.dpiScaling(), + event.windowSize(), event.framebufferSize()); + + + auto windowSize = event.framebufferSize(); + ResizeUI(windowSize); +} + +void Chocolate::pointerPressEvent(PointerEvent& event) { + _imgui.handlePointerPressEvent(event); +} + +void Chocolate::pointerReleaseEvent(PointerEvent& event) { + _imgui.handlePointerReleaseEvent(event); +} + +void Chocolate::pointerMoveEvent(PointerMoveEvent& event) { + _imgui.handlePointerMoveEvent(event); +} + +void Chocolate::keyPressEvent(KeyEvent& event) { + if(_imgui.handleKeyPressEvent(event)) return; +} + +void Chocolate::keyReleaseEvent(KeyEvent& event) { + if(_imgui.handleKeyReleaseEvent(event)) return; +} + +void Chocolate::textInputEvent(TextInputEvent& event) { + if(_imgui.handleTextInputEvent(event)) return; +} + +void Chocolate::RenderToFramebuffer(GL::Framebuffer& framebuffer) +{ + framebuffer + .clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth) + .bind(); + + _camera.Draw(_drawables); +} + +void Chocolate::DrawUI() +{ + GL::defaultFramebuffer + .clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth) + .bind(); + + _imgui.newFrame(); + if(ImGui::GetIO().WantTextInput && !isTextInputActive()) + startTextInput(); + else if(!ImGui::GetIO().WantTextInput && isTextInputActive()) + stopTextInput(); + + for (const auto comp : _uiComponents) + { + comp->Draw(); + } + + /* Update application cursor */ + _imgui.updateApplicationCursor(*this); + + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::enable(GL::Renderer::Feature::ScissorTest); + GL::Renderer::disable(GL::Renderer::Feature::FaceCulling); + GL::Renderer::disable(GL::Renderer::Feature::DepthTest); + _imgui.drawFrame(); + GL::Renderer::enable(GL::Renderer::Feature::DepthTest); + GL::Renderer::enable(GL::Renderer::Feature::FaceCulling); + GL::Renderer::disable(GL::Renderer::Feature::ScissorTest); + GL::Renderer::disable(GL::Renderer::Feature::Blending); +} + +void Chocolate::ResizeUI(Vector2i windowSize) +{ + Vector2i renderWindowSize = {static_cast(windowSize.x() * (1 - _sidebarWidth)), windowSize.y()}; + Vector2i renderWindowPosition = {static_cast(windowSize.x() * _sidebarWidth), 0}; + _uiSceneRenderer.SetPosition(renderWindowPosition); + _uiSceneRenderer.Resize(renderWindowSize); + + Vector2i entityListPanelSize = {static_cast(windowSize.x() * _sidebarWidth), windowSize.y()}; + Vector2i entityListPanelPosition = {0, 0}; + _uiEntityListPanel.SetPosition(entityListPanelPosition); + _uiEntityListPanel.Resize(entityListPanelSize); + + Vector2i entityEditorWindowSize = {static_cast(windowSize.x() * _sidebarWidth), windowSize.y() / 2}; + Vector2i entityEditorWindowPosition = {0, windowSize.y() / 2}; + _uiEntityEditorWindow.SetPosition(entityEditorWindowPosition); + _uiEntityEditorWindow.Resize(entityEditorWindowSize); +} + +void Chocolate::Import(const std::string& file) +{ + PluginManager::Manager manager; + Containers::Pointer importer = + manager.loadAndInstantiate("AnySceneImporter"); + + if (!importer || !importer->openFile(file)) + std::exit(1); + + auto sceneMeshes = Containers::Array>{importer->meshCount()}; + for(UnsignedInt i = 0; i != importer->meshCount(); ++i) { + Containers::Optional meshData = importer->mesh(i); + + if(!meshData) { + Warning{} << "Cannot load mesh" << i << importer->meshName(i); + sceneMeshes[i] = nullptr; + continue; + } + + MeshTools::CompileFlags flags; + if(!meshData->hasAttribute(Trade::MeshAttribute::Normal)) + flags |= MeshTools::CompileFlag::GenerateFlatNormals; + + GL::Mesh mesh = MeshTools::compile(*meshData, flags); + + _meshes.push_back(std::make_unique(std::move(mesh))); + sceneMeshes[i] = _meshes.back().get(); + } + + auto sceneTextures = Containers::Array>{importer->textureCount()}; + for(UnsignedInt i = 0; i != importer->textureCount(); ++i) { + Containers::Optional textureData = + importer->texture(i); + if(!textureData || textureData->type() != Trade::TextureType::Texture2D) { + Warning{} << "Cannot load texture" << i + << importer->textureName(i); + continue; + } + + Containers::Optional imageData = + importer->image2D(textureData->image()); + if(!imageData || imageData->isCompressed()) { + Warning{} << "Cannot load image" << textureData->image() + << importer->image2DName(textureData->image()); + continue; + } + + _textures.push_back(std::make_unique(std::move(GL::Texture2D{} + .setMagnificationFilter(textureData->magnificationFilter()) + .setMinificationFilter(textureData->minificationFilter(), + textureData->mipmapFilter()) + .setWrapping(textureData->wrapping().xy()) + .setStorage(Math::log2(imageData->size().max()) + 1, + GL::textureFormat(imageData->format()), imageData->size()) + .setSubImage(0, {}, *imageData) + .generateMipmap()))); + + auto texture = _textures.back().get(); + sceneTextures[i] = texture; + } + + Containers::Array> materials{importer->materialCount()}; + for(UnsignedInt i = 0; i != importer->materialCount(); ++i) { + Containers::Optional materialData = importer->material(i); + + if(!materialData) { + Warning{} << "Cannot load material" << i + << importer->materialName(i); + continue; + } + + materials[i] = std::move(*materialData).as(); + } + + if(importer->defaultScene() == -1 && !sceneMeshes.isEmpty() && sceneMeshes[0]) { + const auto& entity = _registry.create(); + + _registry.emplace(entity); + _registry.emplace(entity); + + auto& [mesh] = _registry.emplace(entity); + mesh = *sceneMeshes[0]; + + auto& [shader, config, texture] = _registry.emplace(entity); + shader = &_phongShader; + config = &_materials["matte_rubber"]; + + auto& [obj, drawable] = _registry.emplace(entity); + obj = new Object3D{}; + obj->setParent(&_scene); + drawable = new Rendering::Magnum::Drawable(obj, &_drawables, _registry, entity); + + return; + } + + Containers::Optional scene = importer->scene(importer->defaultScene()); + + if(!scene || + !scene->is3D() || + !scene->hasField(Trade::SceneField::Parent) || + !scene->hasField(Trade::SceneField::Mesh)) + { + Fatal{} << "Couldn't load scene" << importer->defaultScene() + << importer->sceneName(importer->defaultScene()); + } + + Containers::Array objects{scene->mappingBound()}; + Containers::Array> parents = scene->parentsAsArray(); + Containers::Array> transforms = scene->transformations3DAsArray(); + Containers::Array>> meshMap = scene->meshesMaterialsAsArray(); + + for (const auto& parent : parents) + { + objects[parent.first()] = new Object3D{}; + } + + for (const auto& parent : parents) + { + objects[parent.first()]->setParent(parent.second() == -1 ? &_scene : objects[parent.second()]); + } + + for (const auto& transform : transforms) + { + if (auto* obj = objects[transform.first()]) + { + obj->setTransformation(transform.second()); + } + } + + for (UnsignedInt i = 0; i != objects.size(); ++i) + { + const auto& entity = _registry.create(); + _registry.emplace(entity); + + auto& [obj, drawable] = _registry.emplace(entity); + obj = objects[i]; + drawable = new Rendering::Magnum::Drawable(obj, &_drawables, _registry, entity); + auto& [translation, rotation, scale] = _registry.emplace(entity); + translation = obj->transformation().translation(); + scale = obj->transformation().scaling(); + rotation = Quaternion::fromMatrix(obj->transformation().rotation()); + + if (scene->hasFieldObject(Trade::SceneField::MeshMaterial, i)) + { + auto meshMaterial = scene->meshesMaterialsFor(i)[0]; + + auto meshId = meshMaterial.first(); + auto materialId = meshMaterial.second(); + + if (meshId != -1 && sceneMeshes[meshId]) + { + auto& [mesh] = _registry.emplace(entity); + mesh = *sceneMeshes[meshId]; + } + + auto& [shader, config, texture] = _registry.emplace(entity); + shader = &_phongShader; + + if (materialId == -1 || !materials[materialId]) + { + config = &_materials["matte_rubber"]; + } + else + { + auto& material = materials[materialId]; + + char buf [64] = {}; + sprintf(buf, "Material_%d", materialId); + + std::string materialName = buf; + + if (_materials.find(materialName) == _materials.end()) + { + _materials[materialName] = PhongMaterialConfig{ + material->ambientColor(), + material->diffuseColor(), + material->specularColor(), + material->shininess(), + }; + } + + config = &_materials[materialName]; + + if (material->hasAttribute(Trade::MaterialAttribute::DiffuseTexture)) + { + texture = *sceneTextures[material->diffuseTexture()]; + } + } + } + } +} + +void Chocolate::EntitySelected(entt::entity entity) +{ + _uiEntityEditorWindow.SetEntity(entity); +} + +MAGNUM_APPLICATION_MAIN(Chocolate) \ No newline at end of file diff --git a/src/Chocolate.h b/src/Chocolate.h new file mode 100644 index 0000000..9f2aae2 --- /dev/null +++ b/src/Chocolate.h @@ -0,0 +1,100 @@ +#ifndef CHOCOLATE_CHOCOLATE_H +#define CHOCOLATE_CHOCOLATE_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include + +#include +#include + +#include "Camera.h" + +#include "Materials.h" +#include "UI.h" + +using namespace Magnum; +using namespace Math::Literals; + +class Chocolate: public Platform::Application { +public: + virtual ~Chocolate() = default; + explicit Chocolate(const Arguments& arguments); + +private: + //SDL 2 events + void drawEvent() override; + void tickEvent() override; + void viewportEvent(ViewportEvent& event) override; + void pointerPressEvent(PointerEvent& event) override; + void pointerReleaseEvent(PointerEvent& event) override; + void pointerMoveEvent(PointerMoveEvent& event) override; + void keyPressEvent(KeyEvent& event) override; + void keyReleaseEvent(KeyEvent& event) override; + void textInputEvent(TextInputEvent& event) override; + + // Render + void RenderToFramebuffer(GL::Framebuffer& framebuffer); + + // UI + void DrawUI(); + void ResizeUI(Vector2i windowSize); + + //Systems + void UpdateMagnumObjects(); + + // Callbacks + void Import(const std::string& file); + void EntitySelected(entt::entity entity); + + //Scene + Scene3D _scene; + SceneGraph::DrawableGroup3D _drawables; + Camera _camera; + + // Resources + std::vector> _meshes; + std::vector> _textures; + std::unordered_map _materials; + + Shaders::PhongGL _phongShader; + + entt::registry _registry; + + entt::entity selectedEntityToEdit = entt::null; + bool showEntityEditorWindow = false; + ImGuiIntegration::Context _imgui{NoCreate}; + float _sidebarWidth = 0.17f; + + std::vector _uiComponents; + UI::RenderWindow _uiSceneRenderer; + UI::EntityListPanel _uiEntityListPanel; + UI::EntityEditorWindow _uiEntityEditorWindow; +}; + + +#endif //CHOCOLATE_CHOCOLATE_H \ No newline at end of file diff --git a/src/Common.h b/src/Common.h new file mode 100644 index 0000000..1672140 --- /dev/null +++ b/src/Common.h @@ -0,0 +1,12 @@ +#ifndef CHOCOLATE_COMMON_H +#define CHOCOLATE_COMMON_H + +#include +#include + +using namespace Magnum; + +typedef SceneGraph::Object Object3D; +typedef SceneGraph::Scene Scene3D; + +#endif //CHOCOLATE_COMMON_H \ No newline at end of file diff --git a/src/Components.cpp b/src/Components.cpp new file mode 100644 index 0000000..664b827 --- /dev/null +++ b/src/Components.cpp @@ -0,0 +1,10 @@ +// +// Created by lbmas on 1/3/2026. +// + +#include "Components.h" + +namespace Components +{ + +} \ No newline at end of file diff --git a/src/Components.h b/src/Components.h new file mode 100644 index 0000000..214e431 --- /dev/null +++ b/src/Components.h @@ -0,0 +1,60 @@ +#ifndef CHOCOLATE_COMPONENTS_H +#define CHOCOLATE_COMPONENTS_H + +#include +#include +#include +#include +#include + +#include "Common.h" +#include + +#include "MagnumDrawable.h" +#include "Materials.h" + +using namespace Magnum; + +namespace Components +{ + struct Transform + { + Vector3 translation{0.0f, 0.0f, 0.0f}; + Quaternion rotation {}; + Vector3 scale{1.0f, 1.0f, 1.0f}; + }; + + struct Mesh + { + GL::Mesh* mesh; + }; + + struct Material + { + Shaders::PhongGL* _shader {nullptr}; + + PhongMaterialConfig* phongConfig {nullptr}; + + GL::Texture2D* texture {nullptr}; + }; + + struct Parent + { + entt::entity entity{entt::null}; + }; + + struct Label + { + std::string content; + + static constexpr std::size_t MAX_SIZE = 128; + }; + + struct MagnumObject + { + Object3D* obj {nullptr}; + Rendering::Magnum::Drawable* drawable {nullptr}; + }; +} + +#endif //CHOCOLATE_COMPONENTS_H \ No newline at end of file diff --git a/src/MagnumDrawable.cpp b/src/MagnumDrawable.cpp new file mode 100644 index 0000000..c9d6b79 --- /dev/null +++ b/src/MagnumDrawable.cpp @@ -0,0 +1,40 @@ +#include "MagnumDrawable.h" + +#include + +#include "Components.h" + +namespace Rendering::Magnum +{ + void Drawable::draw(const Matrix4& transformationMatrix, SceneGraph::Camera3D& camera) + { + const auto mesh = _registry.try_get(_entity); + if (!mesh) return; + + if (const auto material = _registry.try_get(_entity)) + { + material->_shader-> + setDiffuseColor(material->phongConfig->diffuse) + .setAmbientColor(material->phongConfig->ambient) + .setSpecularColor(material->phongConfig->specular) + .setShininess(material->phongConfig->shininess) + .setLightPositions({ + {camera.cameraMatrix().transformPoint({-3.0f, 10.0f, 10.0f}), 0.0f} + }) + .setTransformationMatrix(transformationMatrix) + .setNormalMatrix(transformationMatrix.normalMatrix()) + .setProjectionMatrix(camera.projectionMatrix()) + .draw(*mesh->mesh); + + if (material->texture) + { + material->_shader->bindDiffuseTexture(*material->texture); + } + } + else + { + // TODO: Have a default rendering method + } + } +} + diff --git a/src/MagnumDrawable.h b/src/MagnumDrawable.h new file mode 100644 index 0000000..248b2b3 --- /dev/null +++ b/src/MagnumDrawable.h @@ -0,0 +1,30 @@ +#ifndef CHOCOLATE_DRAWABLE_H +#define CHOCOLATE_DRAWABLE_H + +#include +#include + +#include "Common.h" + +#include + +namespace Rendering::Magnum +{ + class Drawable : public SceneGraph::Drawable3D{ + public: + explicit Drawable( + Object3D* object, + SceneGraph::DrawableGroup3D* group, + entt::registry& registry, + entt::entity entity + ): SceneGraph::Drawable3D{*object, group}, _registry(registry), _entity(entity) {} + + private: + void draw(const Matrix4& transformationMatrix, SceneGraph::Camera3D& camera) override; + + entt::registry& _registry; + entt::entity _entity; + }; +} + +#endif //CHOCOLATE_DRAWABLE_H \ No newline at end of file diff --git a/src/Materials.h b/src/Materials.h new file mode 100644 index 0000000..7370871 --- /dev/null +++ b/src/Materials.h @@ -0,0 +1,16 @@ +#ifndef CHOCOLATE_MATERIALS_H +#define CHOCOLATE_MATERIALS_H + +#include + +using namespace Magnum; + +struct PhongMaterialConfig +{ + Color4 ambient{1.0f, 1.0f, 1.0f}; + Color4 diffuse{1.0f, 1.0f, 1.0f}; + Color4 specular{1.0f, 1.0f, 1.0f}; + float shininess{0.0f}; +}; + +#endif //CHOCOLATE_MATERIALS_H \ No newline at end of file diff --git a/src/Scene.cpp b/src/Scene.cpp new file mode 100644 index 0000000..3e8f0e3 --- /dev/null +++ b/src/Scene.cpp @@ -0,0 +1,5 @@ +// +// Created by lbmas on 1/4/2026. +// + +#include "Scene.h" \ No newline at end of file diff --git a/src/Scene.h b/src/Scene.h new file mode 100644 index 0000000..864e6a9 --- /dev/null +++ b/src/Scene.h @@ -0,0 +1,11 @@ +#ifndef CHOCOLATE_SCENE_H +#define CHOCOLATE_SCENE_H + + +struct Scene +{ + +}; + + +#endif //CHOCOLATE_SCENE_H \ No newline at end of file diff --git a/src/UI.cpp b/src/UI.cpp new file mode 100644 index 0000000..d3c59a7 --- /dev/null +++ b/src/UI.cpp @@ -0,0 +1,215 @@ +// +// Created by lbmas on 1/5/2026. +// + +#include "UI.h" +#include "Components.h" + +#include +#include + +#include +#include +#include + +#include "SDL_stdinc.h" + +namespace UI +{ + // RENDER WINDOW + RenderWindow::RenderWindow() : _framebuffer(NoCreate), _size(Vector2i{}), _position(Vector2i{}) + { + } + + RenderWindow::RenderWindow(const Vector2i& size, const Vector2i& position) : _framebuffer({{}, size}), _size(size), _position(position) + { + _renderTexture.setStorage(1, GL::TextureFormat::RGBA8, size); + _depthStencil.setStorage(GL::RenderbufferFormat::Depth24Stencil8, size); + + _framebuffer.attachTexture(GL::Framebuffer::ColorAttachment{0}, _renderTexture, 0); + _framebuffer.attachRenderbuffer(GL::Framebuffer::BufferAttachment::DepthStencil, _depthStencil); + } + + GL::Framebuffer& RenderWindow::Framebuffer() + { + return _framebuffer; + } + + void RenderWindow::Resize(const Vector2i& size) + { + _renderTexture.setStorage(1, GL::TextureFormat::RGBA8, size); + _depthStencil.setStorage(GL::RenderbufferFormat::Depth24Stencil8, size); + } + + void RenderWindow::SetPosition(const Vector2i& position) + { + _position = position; + } + + void RenderWindow::Draw() + { + ImGui::Begin("Scene Preview", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDecoration); + + ImGui::SetWindowSize({static_cast(_size.x()), static_cast(_size.y())}); + ImGui::SetWindowPos({static_cast(_position.x()), static_cast(_position.y())}); + + ImGui::Text("%.3f ms/frame (%.1f FPS)", + 1000.0/Double(ImGui::GetIO().Framerate), Double(ImGui::GetIO().Framerate)); + + const ImTextureID textureId = _renderTexture.id(); + const ImVec2 previewSize = ImGui::GetContentRegionAvail(); + ImGui::Image(textureId, previewSize, ImVec2(0, 1), ImVec2(1, 0)); + + ImGui::End(); + } + // END RENDER WINDOW + + + // ENTITY LIST PANEL + EntityListPanel::EntityListPanel() + : _size({0, 0}), _position({0, 0}), _reg(nullptr), _importFunc(nullptr), _entitySelectedFunc(nullptr), _selectedEntity(entt::null) + {} + + EntityListPanel::EntityListPanel(Vector2i size, Vector2i position, entt::registry* reg, std::function importFunc, std::function entitySelectedFunc) + : _size(size), _position(position), _selectedEntity(entt::null), _reg(reg), _importFunc(std::move(importFunc)), _entitySelectedFunc(std::move(entitySelectedFunc)) + { + } + + void EntityListPanel::Resize(const Vector2i& size) + { + _size = size; + } + + void EntityListPanel::SetPosition(const Vector2i& position) + { + _position = position; + } + + void EntityListPanel::Draw() + { + ImGui::Begin("Entities", nullptr, ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + ImGui::SetWindowSize({(float)_size.x(), (float)_size.y()}); + ImGui::SetWindowPos({(float)_position.x(), (float)_position.y()}); + + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Open")) + { + IGFD::FileDialogConfig config; + config.path = "."; + ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open Object File", ".obj,.glb", config); + } + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey", ImGuiWindowFlags_NoCollapse, ImVec2(800, 600))) { + if (ImGuiFileDialog::Instance()->IsOk()) { + std::string absolutePath = ImGuiFileDialog::Instance()->GetFilePathName(); + Debug{} << "Opening file: " << Containers::String(absolutePath); + _importFunc(absolutePath); + } + + ImGuiFileDialog::Instance()->Close(); + } + + if (ImGui::BeginTable("EntityTable", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + // Define columns + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 40.0f); + ImGui::TableSetupColumn("Label"); + ImGui::TableHeadersRow(); + + for (const auto& entity : _reg->view()) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + // Create a selectable that spans the entire row + char id[32]; + sprintf(id, "%u", entity); + + bool isSelected = _selectedEntity == entity; + if (ImGui::Selectable(id, isSelected, ImGuiSelectableFlags_SpanAllColumns)) { + _selectedEntity = entity; + _entitySelectedFunc(_selectedEntity); + } + + ImGui::TableNextColumn(); + const auto* label = _reg->try_get(entity); + ImGui::Text("%s", label ? label->content.data() : ""); + } + ImGui::EndTable(); + } + + ImGui::End(); // ENTITIES + } + // END ENTITY LIST PANEL + + + // ENTITY EDITOR WINDOW + EntityEditorWindow::EntityEditorWindow() + : _size({0, 0}), _position({0, 0}), _reg(nullptr), _selectedEntity(entt::null) + {} + + EntityEditorWindow::EntityEditorWindow(Vector2i size, Vector2i position, entt::registry* reg) + : _size(size), _position(position), _reg(reg), _selectedEntity(entt::null) + {} + + void EntityEditorWindow::SetEntity(entt::entity entity) + { + _selectedEntity = entity; + } + + void EntityEditorWindow::Resize(const Vector2i& size) + { + _size = size; + } + + void EntityEditorWindow::SetPosition(const Vector2i& position) + { + _position = position; + } + + void EntityEditorWindow::Draw() + { + ImGui::Begin("Entity Editor", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + ImGui::SetWindowSize({(float)_size.x(), (float)_size.y()}); + ImGui::SetWindowPos({(float)_position.x(), (float)_position.y()}); + + if (_selectedEntity != entt::null) + { + if (auto* label = _reg->try_get(_selectedEntity)) + { + static char entityLabelBuffer[Components::Label::MAX_SIZE] = {}; + memcpy(entityLabelBuffer, label->content.data(), label->content.size()); + if (ImGui::InputText("Label", entityLabelBuffer, Components::Label::MAX_SIZE)) + { + label->content = std::string(entityLabelBuffer); + } + } + + if (auto* transform = _reg->try_get(_selectedEntity)) + { + ImGui::SliderFloat3("Translation", transform->translation.data(), -10, 10); + ImGui::SliderFloat3("Scale", transform->scale.data(), -10, 10); + + // Vector3 eulerDegrees = Vector3{transform->rotation.toEuler()} * (180.0f / M_PI); + // if (ImGui::SliderFloat3("Rotation", eulerDegrees.data(), -180.0f, 180.0f)) { + // // 3. If changed, convert Degrees -> Radians -> Quaternion + // Vector3 eulerRadians = eulerDegrees * (M_PI / 180.0f); + // + // // We recreate the quaternion from the updated Euler angles + // transform->rotation = Quaternion::rotation(Rad(eulerRadians.x()), Vector3::xAxis()) + // * Quaternion::rotation(Rad(eulerRadians.y()), Vector3::yAxis()) + // * Quaternion::rotation(Rad(eulerRadians.z()), Vector3::zAxis()); + //} + } + } + + ImGui::End(); + } + + // END ENTITY EDITOR WINDOW +} diff --git a/src/UI.h b/src/UI.h new file mode 100644 index 0000000..69b0737 --- /dev/null +++ b/src/UI.h @@ -0,0 +1,89 @@ +// +// Created by lbmas on 1/5/2026. +// + +#ifndef CHOCOLATE_UI_H +#define CHOCOLATE_UI_H + +#include +#include +#include + +#include + +#include + +using namespace Magnum; + +namespace UI +{ + class Component + { + public: + virtual ~Component() = default; + + virtual void Draw() = 0; + virtual void Resize(const Vector2i& size) = 0; + virtual void SetPosition(const Vector2i& position) = 0; + }; + + class RenderWindow : public Component + { + public: + RenderWindow(); + RenderWindow(const Vector2i& size, const Vector2i& position); + + [[nodiscard]] GL::Framebuffer& Framebuffer(); + + void Draw() final; + void Resize(const Vector2i& size) final; + void SetPosition(const Vector2i& position) final; + private: + GL::Texture2D _renderTexture; + GL::Renderbuffer _depthStencil; + GL::Framebuffer _framebuffer; + + Vector2i _size; + Vector2i _position; + }; + + class EntityListPanel : public Component + { + public: + EntityListPanel(); + explicit EntityListPanel(Vector2i size, Vector2i position, entt::registry* reg, std::function importFunc, std::function entitySelectedFunc); + + void Draw() final; + void Resize(const Vector2i& size) final; + void SetPosition(const Vector2i& position) final; + private: + Vector2i _size; + Vector2i _position; + + entt::registry* _reg; + std::function _importFunc; + std::function _entitySelectedFunc; + entt::entity _selectedEntity; + }; + + class EntityEditorWindow : public Component + { + public: + EntityEditorWindow(); + explicit EntityEditorWindow(Vector2i size, Vector2i position, entt::registry* reg); + + void SetEntity(entt::entity entity); + + void Draw() final; + void Resize(const Vector2i& size) final; + void SetPosition(const Vector2i& position) final; + private: + Vector2i _size; + Vector2i _position; + entt::registry* _reg; + entt::entity _selectedEntity; + }; +} + + +#endif //CHOCOLATE_UI_H \ No newline at end of file